From baffc26c8f07856e14a64390b7e7564e5b4bac4f Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Mon, 16 Feb 2026 12:25:37 +0100 Subject: [PATCH 001/102] multipath-tools: add missing colon to the multipath output Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Martin Wilck --- libmultipath/propsel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 56895c4bb..0c10ebc1c 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -640,7 +640,7 @@ int select_checker_timeout(struct config *conf, struct path *pp) } pp_set_default(checker_timeout, DEF_TIMEOUT); out: - condlog(3, "%s checker timeout = %u s %s", pp->dev, pp->checker_timeout, + condlog(3, "%s: checker timeout = %u s %s", pp->dev, pp->checker_timeout, origin); return 0; } From bcfa02e2942adbe506b06f7f5eae708332dc6537 Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Fri, 13 Mar 2026 12:00:13 +0100 Subject: [PATCH 002/102] multipath-tools: identify the hds.c licence explicitly ACK from the Copyright holder. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Signed-off-by: Matthias Rudolph Reviewed-by: Martin Wilck --- libmultipath/prioritizers/hds.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libmultipath/prioritizers/hds.c b/libmultipath/prioritizers/hds.c index 212301ea9..70eaa8c01 100644 --- a/libmultipath/prioritizers/hds.c +++ b/libmultipath/prioritizers/hds.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright HDS GmbH 2006. All Rights Reserved. * @@ -60,9 +61,6 @@ * not work under RHEL4 U3 i386 * Changes 2007-06-27: * - switched from major:minor argument to device node argument - * - * This file is released under the GPL. - * */ #include From ed6f6aef4fd7f763beb2cb43d509c4d191d973f2 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 13 Apr 2026 18:38:35 -0400 Subject: [PATCH 003/102] libmultipath: Handle SCSI-2 style vpd page 0x83 descriptors Some older SCSI devices return a SCSI-2 style vpd page 0x83, instead of a SPC-2/3 format one. The SCSI-2 page 83 format returns an IEEE WWN in binary encoded hexi-decimal in the 16 bytes following the initial 4-byte page 83 reply header. Check the 7th byte of the vpd page 83 buffer to determine whether this is a SCSI-2 or SPC-2/3 confomant one. Byte 7 is the 3rd byte of first Identification descriptor in a SPC-2/3 confromant vpd page 83. This is a reserved field, and is guaranteed to be 0. If it is not zero, then it is likely the 3rd byte of a SCSI-2 Identifier (The first 3 bytes of the ID are the Organizationally Unique Identifier). Both the sg_inq and scsi_id commands handle vpd page 83 this way. To make sure that the WWID which multipath reads directly from the device matches, it should handle this format as well. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/discovery.c | 13 +++++++++ tests/vpd.c | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index 0efb8213d..f59fa6d4a 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -1208,6 +1208,18 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, if (out_len <= 1) return 0; + /* + * Not a valid SPC-2/3 vpd page 83. Assume it's a SCSI-2 style + * descriptor. + */ + if (in_len >= 20 && in[6] != 0) { + len = 0; + vpd_type = 0x3; + vpd_len = in_len - 4; + vpd = in + 4; + goto decode; + } + d = in + 4; while (d <= in + in_len - 4) { bool invalid = false; @@ -1323,6 +1335,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, vpd_type = vpd[1] & 0xf; vpd_len = vpd[3]; vpd += 4; +decode: /* untaint vpd_len for coverity */ if (vpd_len > WWID_SIZE) { condlog(1, "%s: suspicious designator length %zu truncated to %u", diff --git a/tests/vpd.c b/tests/vpd.c index f9e22cd87..b02179483 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -331,6 +331,28 @@ static int create_vpd83(unsigned char *buf, size_t bufsiz, const char *id, return n + 4; } +/** + * create_pre_spc3_vpd83() - create pre-SPC3 "device identification" VPD page + * + * @buf, @bufsiz: see above. + * @id: input ID (third byte must be non-zero) + * + * Create a pre-SPC3 "device identification" VPD page. See comments in + * sg3_utils/src/sg_inq.c for details + * + * Return: VPD page length. + */ +static int create_pre_spc3_vpd83(unsigned char *buf, size_t bufsize, + const char *id) +{ + memset(buf, 0, bufsize); + buf[1] = 0x83; + + hex2bin(buf + 4, id, 16, 32); + put_unaligned_be16(16, buf + 2); + return 20; +} + /** * assert_correct_wwid() - test that a retrieved WWID matches expectations * @test: test name @@ -479,6 +501,30 @@ static void test_vpd_str_ ## typ ## _ ## len ## _ ## wlen(void **state) \ test_id, vt->wwid); \ } +/** + * test_vpd_prespc3_WLEN() - test code for pre-SPC3 VPD 83 + * @WLEN: WWID buffer size + */ +#define make_test_vpd_prespc3(wlen) \ +static void test_vpd_prespc3_ ## wlen(void **state) \ +{ \ + struct vpdtest *vt = *state; \ + int n, ret; \ + int exp_len; \ + \ + /* returned size is always uneven */ \ + exp_len = wlen > 33 ? 33 : \ + wlen % 2 == 0 ? wlen - 1 : wlen - 2; \ + \ + n = create_pre_spc3_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), \ + test_id); \ + wrap_will_return(WRAP_IOCTL, n); \ + wrap_will_return(WRAP_IOCTL, vt->vpdbuf); \ + ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, wlen); \ + assert_correct_wwid("test_vpd_prespc3_" #wlen, exp_len, ret, \ + '3', 0, true, test_id, vt->wwid); \ +} + /** * test_vpd_naa_NAA_WLEN() - test code for VPD 83 NAA designation * @NAA: Network Name Authority (2, 3, 5, or 6) @@ -814,6 +860,13 @@ make_test_vpd_str(18, 20, 17) make_test_vpd_str(18, 20, 16) make_test_vpd_str(18, 20, 15) +/* PRE-SPC3, WWID size: 34 */ +make_test_vpd_prespc3(40) +make_test_vpd_prespc3(34) +make_test_vpd_prespc3(33) +make_test_vpd_prespc3(32) +make_test_vpd_prespc3(20) + static int test_vpd(void) { const struct CMUnitTest tests[] = { @@ -945,6 +998,11 @@ static int test_vpd(void) cmocka_unit_test(test_vpd_str_18_20_17), cmocka_unit_test(test_vpd_str_18_20_16), cmocka_unit_test(test_vpd_str_18_20_15), + cmocka_unit_test(test_vpd_prespc3_40), + cmocka_unit_test(test_vpd_prespc3_34), + cmocka_unit_test(test_vpd_prespc3_33), + cmocka_unit_test(test_vpd_prespc3_32), + cmocka_unit_test(test_vpd_prespc3_20), }; return cmocka_run_group_tests(tests, setup, teardown); } From 0e07fbc9c650e3d8bd12c89f8cd8d522bd2634de Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 17:01:48 +0100 Subject: [PATCH 004/102] .clang-format: Add AllignArrayOfStructures Signed-off-by: Martin Wilck --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index 5c762eba5..f8b87a2c6 100644 --- a/.clang-format +++ b/.clang-format @@ -9,6 +9,7 @@ BreakBeforeBraces: Linux BreakStringLiterals: false AllowShortFunctionsOnASingleLine: Empty AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left PointerAlignment: Right ColumnLimit: 79 PenaltyBreakAssignment: 50 From 5ca74e79330728da7d87b6c5a5e9ed4035e3f99d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 12:23:46 +0100 Subject: [PATCH 005/102] Makefile: add 'test-outputs.tar' target For saving test outputs in GitHub actions. Also add "test-outputs.cpio", although it'll be hardly used. Signed-off-by: Martin Wilck --- Makefile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f06f7faa9..8e46ee732 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,7 @@ clean: @touch config.mk $(Q)$(MAKE) $(BUILDDIRS:=.clean) tests.clean || true $(Q)$(RM) -r abi abi.tar.gz abi-test config.mk + $(Q)$(RM) test-progs.* test-outputs.* install: $(BUILDDIRS:=.install) uninstall: $(BUILDDIRS:=.uninstall) @@ -131,10 +132,16 @@ TEST-ARTIFACTS := config.mk Makefile.inc \ tests/Makefile tests/*.so* tests/lib/* tests/*-test test-progs.cpio: test-progs - @printf "%s\\n" $(TEST-ARTIFACTS) | cpio -o -H crc >$@ + $(Q)printf "%s\\n" $(TEST-ARTIFACTS) | cpio -o -H crc >$@ test-progs.tar: test-progs - @tar cf $@ $(TEST-ARTIFACTS) + $(Q)tar cf $@ $(TEST-ARTIFACTS) + +test-outputs.cpio: $(wildcard tests/*.out) $(wildcard tests/*.vgr) + $(Q)printf "%s\\n" $^ | cpio -o -H crc >$@ + +test-outputs.tar: $(wildcard tests/*.out) $(wildcard tests/*.vgr) + $(Q)tar cf "$@" $^ || touch $@ .PHONY: TAGS TAGS: From d4863c36a7e7eec92a593790220ee8a418bb99f8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 16:08:21 +0100 Subject: [PATCH 006/102] Makefile: include top-level Makefile in test-progs.tar... ... and also the runner test script. Signed-off-by: Martin Wilck --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8e46ee732..cabbe28d2 100644 --- a/Makefile +++ b/Makefile @@ -127,9 +127,9 @@ test: all valgrind-test: all @$(MAKE) -C tests valgrind -TEST-ARTIFACTS := config.mk Makefile.inc \ +TEST-ARTIFACTS := config.mk Makefile Makefile.inc \ $(LIB_BUILDDIRS:%=%/*.so*) $(PLUGIN_BUILDDIRS:%=%/*.so) \ - tests/Makefile tests/*.so* tests/lib/* tests/*-test + tests/Makefile tests/*.so* tests/lib/* tests/*-test tests/runner-test.sh test-progs.cpio: test-progs $(Q)printf "%s\\n" $(TEST-ARTIFACTS) | cpio -o -H crc >$@ From f9fc4750c28732714e93dbea072142af0890c75b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 19:37:28 +0100 Subject: [PATCH 007/102] GitHub workflows: increase --pids-limit This is necessary for the runner-test executable, which may create several thousand threads. Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 2 ++ .github/workflows/multiarch-stable.yaml | 2 +- .github/workflows/multiarch.yaml | 2 +- .github/workflows/native.yaml | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index b232b60c1..a2dbe7f8a 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -89,6 +89,7 @@ jobs: host-dir: ${{ github.workspace }} command: -C tests params: > + --pids-limit 4096 --workdir /__w/multipath-tools/multipath-tools --platform linux/${{ env.CONTAINER_ARCH }} pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" @@ -136,6 +137,7 @@ jobs: command: -C tests dmevents.out params: > --workdir /__w/multipath-tools/multipath-tools + --pids-limit 4096 --platform linux/${{ env.CONTAINER_ARCH }} --privileged -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index b8c770e40..1c985000a 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -60,7 +60,7 @@ jobs: guest-dir: /build host-dir: ${{ github.workspace }} command: test - params: "--platform linux/${{ matrix.arch }}" + params: "--pids-limit 4096 --platform linux/${{ matrix.arch }}" pull-params: "--platform linux/${{ matrix.arch }}" continue-on-error: true - name: create binary archive diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 806275a13..98c13be05 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -63,7 +63,7 @@ jobs: guest-dir: /build host-dir: ${{ github.workspace }} command: test - params: "--platform linux/${{ matrix.arch }}" + params: "--pids-limit 4096 --platform linux/${{ matrix.arch }}" pull-params: "--platform linux/${{ matrix.arch }}" continue-on-error: true - name: create binary archive diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 9e89780b1..d91c01d7f 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -143,7 +143,7 @@ jobs: uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: -e CC=clang + params: --pids-limit 4096 -e CC=clang command: -j$(nproc) -Orecurse test continue-on-error: true - name: clang (jessie) @@ -152,7 +152,7 @@ jobs: uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: -e CC=clang + params: --pids-limit 4096 -e CC=clang command: -j$(nproc) -Orecurse READLINE=libreadline test continue-on-error: true From 37c9d20859adf4e93585f17f5300e6aae4140766 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 12:14:13 +0100 Subject: [PATCH 008/102] GitHub workflows: simplify if: syntax Unless the expression starts with '!', we can omit braces in if: statements [1] [1] https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsif Signed-off-by: Martin Wilck --- .github/workflows/abi-stable.yaml | 14 +++++++------- .github/workflows/abi.yaml | 6 +++--- .github/workflows/build-and-unittest.yaml | 2 +- .github/workflows/codingstyle.yml | 4 ++-- .github/workflows/foreign.yaml | 10 +++++----- .github/workflows/native.yaml | 20 ++++++++++---------- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index d0ee4543f..de8050ab0 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -21,15 +21,15 @@ jobs: run: > echo ${{ github.ref }} | sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV - if: ${{ github.event_name == 'push' }} + if: github.event_name == 'push' - name: get parent tag (PR) run: > echo ${{ github.base_ref }} | sed -E 's,stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV - if: ${{ github.event_name == 'pull_request' }} + if: github.event_name == 'pull_request' - name: assert parent tag run: /bin/false - if: ${{ env.PARENT_TAG == '' }} + if: env.PARENT_TAG == '' - name: try to download ABI for ${{ env.PARENT_TAG }} id: download_abi continue-on-error: true @@ -75,15 +75,15 @@ jobs: run: > echo ${{ github.ref }} | sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV - if: ${{ github.event_name == 'push' }} + if: github.event_name == 'push' - name: get parent tag (PR) run: > echo ${{ github.base_ref }} | sed -E 's,stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV - if: ${{ github.event_name == 'pull_request' }} + if: github.event_name == 'pull_request' - name: assert parent tag run: /bin/false - if: ${{ env.PARENT_TAG == '' }} + if: env.PARENT_TAG == '' - name: checkout ${{ github.ref }} uses: actions/checkout@v4 with: @@ -112,7 +112,7 @@ jobs: run: make -j$(nproc) -Orecurse abi-test continue-on-error: true - name: save differences - if: ${{ steps.check_abi.outcome != 'success' }} + if: steps.check_abi.outcome != 'success' uses: actions/upload-artifact@v4 with: name: abi-test diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml index 3b5f1c1e4..1a7d3e5dd 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: set ABI branch - if: ${{ env.ABI_BRANCH == '' }} + if: env.ABI_BRANCH == '' run: echo "ABI_BRANCH=master" >> $GITHUB_ENV - name: checkout uses: actions/checkout@v4 @@ -55,10 +55,10 @@ jobs: - name: compare ABI against reference id: compare continue-on-error: true - if: ${{ steps.reference.outcome == 'success' }} + if: steps.reference.outcome == 'success' run: make abi-test - name: save differences - if: ${{ steps.compare.outcome == 'failure' }} + if: steps.compare.outcome == 'failure' uses: actions/upload-artifact@v4 with: name: abi-test diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml index fdcc59b73..616a4de1f 100644 --- a/.github/workflows/build-and-unittest.yaml +++ b/.github/workflows/build-and-unittest.yaml @@ -43,7 +43,7 @@ jobs: - name: set optflags # valgrind doesn't support the dwarf-5 format of clang 14 run: echo OPT='-O2 -gdwarf-4 -fstack-protector-strong' >> $GITHUB_ENV - if: ${{ matrix.cc == 'clang' }} + if: matrix.cc == 'clang' - name: build run: > make -Orecurse -j$(nproc) diff --git a/.github/workflows/codingstyle.yml b/.github/workflows/codingstyle.yml index 067f32666..a36b3874e 100644 --- a/.github/workflows/codingstyle.yml +++ b/.github/workflows/codingstyle.yml @@ -33,10 +33,10 @@ jobs: - uses: actions/checkout@v4 - run: >- echo "BASEREF=${{ github.event.pull_request.base.sha }}" >>$GITHUB_ENV - if: ${{ github.event_name == 'pull_request' }} + if: github.event_name == 'pull_request' - run: >- echo "BASEREF=${{ github.event.before }}" >>$GITHUB_ENV - if: ${{ github.event_name == 'push' }} + if: github.event_name == 'push' - run: git fetch --depth=1 origin ${{ env.BASEREF }} - uses: yshui/git-clang-format-lint@master with: diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index a2dbe7f8a..976bf521f 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -67,10 +67,10 @@ jobs: steps: - name: set container arch run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV - if: ${{ matrix.arch != 'armhf' }} + if: matrix.arch != 'armhf' - name: set container arch run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV - if: ${{ matrix.arch == 'armhf' }} + if: matrix.arch == 'armhf' - name: download binary archive uses: actions/download-artifact@v4 with: @@ -114,10 +114,10 @@ jobs: run: sudo modprobe brd rd_nr=1 rd_size=65536 - name: set container arch run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV - if: ${{ matrix.arch != 'armhf' }} + if: matrix.arch != 'armhf' - name: set container arch run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV - if: ${{ matrix.arch == 'armhf' }} + if: matrix.arch == 'armhf' - name: download binary archive uses: actions/download-artifact@v4 with: @@ -148,4 +148,4 @@ jobs: run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - name: fail run: /bin/false - if: ${{ steps.root-test.outcome == 'failure' }} + if: steps.root-test.outcome != 'success' diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index d91c01d7f..315ad75d0 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -51,14 +51,14 @@ jobs: - name: set archive name # Leap containers have cpio but not tar run: echo ARCHIVE_TGT=test-progs.cpio >> $GITHUB_ENV - if: ${{ startswith(matrix.os, 'opensuse-leap-15') }} + if: startswith(matrix.os, 'opensuse-leap-15') - name: set archive name run: echo ARCHIVE_TGT=test-progs.tar >> $GITHUB_ENV if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} - name: build and test id: test - if: ${{ matrix.os != 'debian-jessie' }} + if: matrix.os != 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} @@ -68,7 +68,7 @@ jobs: - name: build and test (jessie) id: test_jessie # On jessie, we use libreadline 5 (no licensing issue) - if: ${{ matrix.os == 'debian-jessie' }} + if: matrix.os == 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} @@ -76,13 +76,13 @@ jobs: continue-on-error: true - name: create ${{ env.ARCHIVE_TGT }} - if: ${{ matrix.os != 'debian-jessie' }} + if: matrix.os != 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} command: ${{ env.ARCHIVE_TGT }} - name: create ${{ env.ARCHIVE_TGT }} (jessie) - if: ${{ matrix.os == 'debian-jessie' }} + if: matrix.os == 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} @@ -132,14 +132,14 @@ jobs: - name: set archive name # Leap containers have cpio but not tar run: echo ARCHIVE_TGT=test-progs.cpio >> $GITHUB_ENV - if: ${{ startswith(matrix.os, 'opensuse-leap-15') }} + if: startswith(matrix.os, 'opensuse-leap-15') - name: set archive name run: echo ARCHIVE_TGT=test-progs.tar >> $GITHUB_ENV if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} - name: clang id: test - if: ${{ matrix.os != 'debian-jessie' }} + if: matrix.os != 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} @@ -148,7 +148,7 @@ jobs: continue-on-error: true - name: clang (jessie) id: test_jessie - if: ${{ matrix.os == 'debian-jessie' }} + if: matrix.os == 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} @@ -232,7 +232,7 @@ jobs: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} - name: unpack binary archive run: cpio -idv < test-progs.cpio - if: ${{ startswith(matrix.os, 'opensuse-leap-15') }} + if: startswith(matrix.os, 'opensuse-leap-15') - name: unpack binary archive run: tar xfmv test-progs.tar if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} @@ -253,4 +253,4 @@ jobs: run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - name: fail run: /bin/false - if: ${{ steps.root-test.outcome == 'failure' }} + if: steps.root-test.outcome == 'failure' From 516571e647b8b29fbcee7710debaafc810e05ba0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 12:21:25 +0100 Subject: [PATCH 009/102] GitHub workflows: upload log files in case of failure Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 51 +++++++++++++++++++++++-- .github/workflows/multiarch-stable.yaml | 13 ++++++- .github/workflows/multiarch.yaml | 19 ++++++++- .github/workflows/rolling.yaml | 9 +++++ 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 976bf521f..91184ecc4 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -93,6 +93,31 @@ jobs: --workdir /__w/multipath-tools/multipath-tools --platform linux/${{ env.CONTAINER_ARCH }} pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + id: test + continue-on-error: true + - name: save test outputs + uses: mosteo-actions/docker-run@v1 + with: + image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} + guest-dir: /__w/multipath-tools/multipath-tools + host-dir: ${{ github.workspace }} + command: test-outputs.tar + params: > + --pids-limit 4096 + --workdir /__w/multipath-tools/multipath-tools + --platform linux/${{ env.CONTAINER_ARCH }} + pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' root-test: runs-on: ubuntu-24.04 @@ -144,8 +169,28 @@ jobs: pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" id: root-test continue-on-error: true - - name: show root test output - run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - - name: fail + - name: save test outputs + uses: mosteo-actions/docker-run@v1 + with: + image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} + guest-dir: /__w/multipath-tools/multipath-tools + host-dir: ${{ github.workspace }} + command: test-outputs.tar + params: > + --workdir /__w/multipath-tools/multipath-tools + --pids-limit 4096 + --platform linux/${{ env.CONTAINER_ARCH }} + --privileged + -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 + pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: root-test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if root test failed run: /bin/false if: steps.root-test.outcome != 'success' diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index 1c985000a..1784af036 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -78,7 +78,18 @@ jobs: with: name: binaries-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar + overwrite: true if: steps.test.outcome != 'success' - - name: fail + - name: save test outputs + run: make test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed run: /bin/false if: steps.test.outcome != 'success' diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 98c13be05..723064e52 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -82,6 +82,23 @@ jobs: name: binaries-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar if: steps.test.outcome != 'success' - - name: fail + - name: create test outputs + uses: mosteo-actions/docker-run@v1 + with: + image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} + guest-dir: /build + host-dir: ${{ github.workspace }} + command: test-outputs.tar + params: "--platform linux/${{ matrix.arch }}" + pull-params: "--platform linux/${{ matrix.arch }}" + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed run: /bin/false if: steps.test.outcome != 'success' diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index a4706e52e..4dc389ff6 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -73,6 +73,15 @@ jobs: name: binaries-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} path: test-progs.tar if: steps.test.outcome != 'success' + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: outputs-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} + path: test-outputs.tar + if: steps.test.outcome != 'success' - name: fail run: /bin/false if: steps.test.outcome != 'success' From f703f5bd937c9998dfee8d5f08ce514d4a2ac6c5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 12:22:16 +0100 Subject: [PATCH 010/102] GitHub workflows: native.yml: always use tar for upload The "tar" package has now been added to the SUSE and openSUSE build containers, too [1]. [1] https://github.com/mwilck/build-multipath/commit/3b68ea51dd54c29806ec47de0d1311848f466562 Signed-off-by: Martin Wilck --- .github/workflows/native.yaml | 43 ++++++++--------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 315ad75d0..1cc552aba 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -48,14 +48,6 @@ jobs: - name: checkout uses: actions/checkout@v4 - - name: set archive name - # Leap containers have cpio but not tar - run: echo ARCHIVE_TGT=test-progs.cpio >> $GITHUB_ENV - if: startswith(matrix.os, 'opensuse-leap-15') - - name: set archive name - run: echo ARCHIVE_TGT=test-progs.tar >> $GITHUB_ENV - if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} - - name: build and test id: test if: matrix.os != 'debian-jessie' @@ -75,24 +67,18 @@ jobs: command: -j$(nproc) -Orecurse READLINE=libreadline test continue-on-error: true - - name: create ${{ env.ARCHIVE_TGT }} + - name: create test-progs.tar if: matrix.os != 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: ${{ env.ARCHIVE_TGT }} - - name: create ${{ env.ARCHIVE_TGT }} (jessie) - if: matrix.os == 'debian-jessie' - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: READLINE=libreadline ${{ env.ARCHIVE_TGT }} + command: test-progs.tar - name: upload binary archive uses: actions/upload-artifact@v4 with: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} - path: ${{ env.ARCHIVE_TGT }} + path: test-progs.tar overwrite: true - name: fail @@ -129,14 +115,6 @@ jobs: - name: checkout uses: actions/checkout@v4 - - name: set archive name - # Leap containers have cpio but not tar - run: echo ARCHIVE_TGT=test-progs.cpio >> $GITHUB_ENV - if: startswith(matrix.os, 'opensuse-leap-15') - - name: set archive name - run: echo ARCHIVE_TGT=test-progs.tar >> $GITHUB_ENV - if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} - - name: clang id: test if: matrix.os != 'debian-jessie' @@ -156,21 +134,21 @@ jobs: command: -j$(nproc) -Orecurse READLINE=libreadline test continue-on-error: true - - name: create ${{ env.ARCHIVE_TGT }} + - name: create test-progs.tar uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: ${{ env.ARCHIVE_TGT }} + command: test-progs.tar if: >- ${{ ( matrix.os == 'debian-jessie' && steps.test_jessie.outcome != 'success' ) || ( matrix.os != 'debian-jessie' && steps.test.outcome != 'success' ) }} - - name: create ${{ env.ARCHIVE_TGT }} (jessie) + - name: create test-progs.tar (jessie) uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: READLINE=libreadline ${{ env.ARCHIVE_TGT }} + command: READLINE=libreadline test-progs.tar if: >- ${{ ( matrix.os == 'debian-jessie' && steps.test_jessie.outcome != 'success' ) || @@ -181,7 +159,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: clang-${{ matrix.os }}-${{ matrix.variant.arch }} - path: ${{ env.ARCHIVE_TGT }} + path: test-progs.tar overwrite: true if: >- ${{ ( matrix.os == 'debian-jessie' && @@ -230,12 +208,9 @@ jobs: uses: actions/download-artifact@v4 with: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} - - name: unpack binary archive - run: cpio -idv < test-progs.cpio - if: startswith(matrix.os, 'opensuse-leap-15') + - name: unpack binary archive run: tar xfmv test-progs.tar - if: ${{ !startswith(matrix.os, 'opensuse-leap-15') }} - name: run root tests uses: mosteo-actions/docker-run@v1 From 0ec42484c17426894430f030781d39dcb73b6e7b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 13:03:41 +0100 Subject: [PATCH 011/102] GitHub workflows: native.yml: simplify libreadline handling Instead of duplicating the build step, simply set an environment variable. --- .github/workflows/native.yaml | 132 ++++++++++++++++------------------ 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 1cc552aba..099f9f6eb 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -48,46 +48,51 @@ jobs: - name: checkout uses: actions/checkout@v4 - - name: build and test - id: test - if: matrix.os != 'debian-jessie' - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: -j$(nproc) -Orecurse test - continue-on-error: true - - - name: build and test (jessie) - id: test_jessie # On jessie, we use libreadline 5 (no licensing issue) + - name: set readline make option (Jessie) + run: echo READLINE=libreadline >> $GITHUB_ENV if: matrix.os == 'debian-jessie' + + - name: build and test + id: test uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: -j$(nproc) -Orecurse READLINE=libreadline test + command: -j$(nproc) -Orecurse V=1 READLINE=$READLINE test continue-on-error: true - name: create test-progs.tar - if: matrix.os != 'debian-jessie' uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} command: test-progs.tar - - name: upload binary archive + - name: upload test-progs.tar uses: actions/upload-artifact@v4 with: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} path: test-progs.tar overwrite: true - - name: fail + - name: create test-outputs.tar + uses: mosteo-actions/docker-run@v1 + with: + image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} + command: test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: gcc-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + + - name: fail if test failed run: /bin/false - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} + if: steps.test.outcome != 'success' + clang: strategy: fail-fast: false @@ -115,65 +120,38 @@ jobs: - name: checkout uses: actions/checkout@v4 - - name: clang - id: test - if: matrix.os != 'debian-jessie' - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: --pids-limit 4096 -e CC=clang - command: -j$(nproc) -Orecurse test - continue-on-error: true - - name: clang (jessie) - id: test_jessie + # On jessie, we use libreadline 5 (no licensing issue) + - name: set readline make option (Jessie) + run: echo READLINE=libreadline >> $GITHUB_ENV if: matrix.os == 'debian-jessie' + + - name: build and test with clang + id: test uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} params: --pids-limit 4096 -e CC=clang - command: -j$(nproc) -Orecurse READLINE=libreadline test + command: -j$(nproc) -Orecurse V=1 READLINE=$READLINE test continue-on-error: true - - name: create test-progs.tar + - name: create test-outputs.tar uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: test-progs.tar - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} - - name: create test-progs.tar (jessie) - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: READLINE=libreadline test-progs.tar - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} - - - name: upload binary archive + command: test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar uses: actions/upload-artifact@v4 with: - name: clang-${{ matrix.os }}-${{ matrix.variant.arch }} - path: test-progs.tar + name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar overwrite: true - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} + if: steps.test.outcome != 'success' - - name: fail + - name: fail if test failed run: /bin/false - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} + if: steps.test.outcome != 'success' root-test: needs: stable @@ -221,11 +199,25 @@ jobs: params: > --workdir /__w/multipath-tools/multipath-tools --privileged -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 - command: -C tests directio.out dmevents.out - id: root-test + command: -C tests V=1 directio.out dmevents.out + id: test continue-on-error: true - - name: show root test output - run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - - name: fail + + - name: create test-outputs.tar + uses: mosteo-actions/docker-run@v1 + with: + image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} + command: test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + + - name: fail if test failed run: /bin/false - if: steps.root-test.outcome == 'failure' + if: steps.test.outcome != 'success' From 8a4c75150a5f0bfc4aefaee059478c0bf83bc7fe Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Mar 2026 16:10:48 +0100 Subject: [PATCH 012/102] GitHub workflows: don't start container to build artifacts For test-progs.tar, starting a container may be necessary. I observed this otherwise: building libdmmp.o because of /usr/include/json-c/json.h /usr/include/json-c/arraylist.h /usr/include/json-c/debug.h /usr/include/json-c/json_c_version.h ... Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 26 ++------------------------ .github/workflows/multiarch.yaml | 14 ++------------ .github/workflows/native.yaml | 15 +++------------ 3 files changed, 7 insertions(+), 48 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 91184ecc4..3dcbdb5c3 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -96,17 +96,7 @@ jobs: id: test continue-on-error: true - name: save test outputs - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} - guest-dir: /__w/multipath-tools/multipath-tools - host-dir: ${{ github.workspace }} - command: test-outputs.tar - params: > - --pids-limit 4096 - --workdir /__w/multipath-tools/multipath-tools - --platform linux/${{ env.CONTAINER_ARCH }} - pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test outputs uses: actions/upload-artifact@v4 @@ -170,19 +160,7 @@ jobs: id: root-test continue-on-error: true - name: save test outputs - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} - guest-dir: /__w/multipath-tools/multipath-tools - host-dir: ${{ github.workspace }} - command: test-outputs.tar - params: > - --workdir /__w/multipath-tools/multipath-tools - --pids-limit 4096 - --platform linux/${{ env.CONTAINER_ARCH }} - --privileged - -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 - pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test outputs uses: actions/upload-artifact@v4 diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 723064e52..fba32f151 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -66,13 +66,13 @@ jobs: params: "--pids-limit 4096 --platform linux/${{ matrix.arch }}" pull-params: "--platform linux/${{ matrix.arch }}" continue-on-error: true - - name: create binary archive + - name: create archives uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} guest-dir: /build host-dir: ${{ github.workspace }} - command: test-progs.tar + command: test-progs.tar test-outputs.tar params: "--platform linux/${{ matrix.arch }}" pull-params: "--platform linux/${{ matrix.arch }}" if: steps.test.outcome != 'success' @@ -82,16 +82,6 @@ jobs: name: binaries-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar if: steps.test.outcome != 'success' - - name: create test outputs - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /build - host-dir: ${{ github.workspace }} - command: test-outputs.tar - params: "--platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" - if: steps.test.outcome != 'success' - name: upload test outputs uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 099f9f6eb..75006db40 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -75,10 +75,7 @@ jobs: overwrite: true - name: create test-outputs.tar - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: test-outputs.tar + run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test-outputs.tar @@ -135,10 +132,7 @@ jobs: continue-on-error: true - name: create test-outputs.tar - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: test-outputs.tar + run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test-outputs.tar @@ -204,10 +198,7 @@ jobs: continue-on-error: true - name: create test-outputs.tar - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: test-outputs.tar + run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test-outputs.tar From 8933b22b82c49f2a240193c4dd20105f7f1df26e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 16:29:52 +0100 Subject: [PATCH 013/102] multipathd: get_new_state: map PATH_TIMEOUT to PATH_DOWN We map PATH_TIMEOUT to PATH_DOWN in pathinfo(), but not in get_new_state(). Do it there, too, to treat the states consistently. This avoids logging "checker timed out" twice in update_path_state(), even if log_checker_err is set to "once". Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- multipathd/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/multipathd/main.c b/multipathd/main.c index d87c7b127..9bce4d6be 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -2503,8 +2503,10 @@ get_new_state(struct path *pp) * Wait for uevent for removed paths; * some LLDDs like zfcp keep paths unavailable * without sending uevents. + * Also, map PATH_TIMEOUT to PATH_DOWN here, like we do in + * pathinfo(). */ - if (newstate == PATH_REMOVED) + if (newstate == PATH_REMOVED || newstate == PATH_TIMEOUT) newstate = PATH_DOWN; /* From e3a03399872c814b9f0d57ecb7254da839e9f9ec Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 16:58:44 +0100 Subject: [PATCH 014/102] libmpathutil: add generic implementation for checker thread runners This code adds a generic, abstract implementation of the kind of threads we need for checkers: detached threads that may time out. The design is such that these threads never access any memory of the calling program, simplifying the management of lifetimes of objects. See the documentation of the API in "runner.h". Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmpathutil/Makefile | 2 +- libmpathutil/libmpathutil.version | 7 +- libmpathutil/runner.c | 230 ++++++++++++++++++++++++++++++ libmpathutil/runner.h | 102 +++++++++++++ 4 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 libmpathutil/runner.c create mode 100644 libmpathutil/runner.h diff --git a/libmpathutil/Makefile b/libmpathutil/Makefile index e1cc2c611..bdd706377 100644 --- a/libmpathutil/Makefile +++ b/libmpathutil/Makefile @@ -13,7 +13,7 @@ LIBDEPS += -lpthread -ldl -ludev -L$(mpathcmddir) -lmpathcmd $(SYSTEMD_LIBDEPS) # other object files OBJS := mt-libudev.o parser.o vector.o util.o debug.o time-util.o \ - uxsock.o log_pthread.o log.o strbuf.o globals.o msort.o + uxsock.o log_pthread.o log.o strbuf.o globals.o msort.o runner.o all: $(DEVLIB) diff --git a/libmpathutil/libmpathutil.version b/libmpathutil/libmpathutil.version index 22ef99458..c69120db3 100644 --- a/libmpathutil/libmpathutil.version +++ b/libmpathutil/libmpathutil.version @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMPATHUTIL_5.0 { +LIBMPATHUTIL_6.0 { global: alloc_bitfield; alloc_strvec; @@ -62,6 +62,7 @@ global: cleanup_vector; cleanup_vector_free; convert_dev; + check_runner; dlog; filepresent; fill_strbuf; @@ -72,6 +73,8 @@ global: free_strvec; get_linux_version_code; get_monotonic_time; + get_persistent_runner; + get_runner; get_strbuf_buf__; get_next_string; get_strbuf_len; @@ -161,7 +164,9 @@ global: process_file; pthread_cond_init_mono; recv_packet; + release_runner; reset_strbuf; + runner_state_name; safe_write; send_packet; set_max_fds; diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c new file mode 100644 index 000000000..da57ff039 --- /dev/null +++ b/libmpathutil/runner.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC +#include +#include +#include +#include +#include "util.h" +#include "debug.h" +#include "time-util.h" +#include "runner.h" + +#define STACK_SIZE (4 * 1024) +#define MILLION 1000000 + +const char *runner_state_name(int state) +{ + // clang-format off + static const char * const state_name_[] = { + [RUNNER_IDLE] = "idle", + [RUNNER_RUNNING] = "running", + [RUNNER_DONE] = "done", + [RUNNER_CANCELLED] = "cancelled", + [RUNNER_DEAD] = "dead" + }; + // clang-format on + + if (state < RUNNER_IDLE || state > RUNNER_DEAD) + return "unknown"; + return state_name_[state]; +} + +struct runner_context { + int refcount; + int status; + struct timespec deadline; + pthread_t thr; + void (*func)(void *data); + /* User data will be copied into this area */ + char __attribute__((aligned(sizeof(void *)))) data[]; +}; + +static void release_context(struct runner_context *rctx) +{ + int n; + + n = uatomic_sub_return(&rctx->refcount, 1); + assert(n >= 0); + + if (n == 0) + free(rctx); +} + +static void cleanup_context(struct runner_context **prctx) +{ + struct runner_context *rctx = *prctx; + int st; + + if (!rctx) + return; + + st = uatomic_cmpxchg(&rctx->status, RUNNER_RUNNING, RUNNER_DONE); + if (st != RUNNER_RUNNING) { + uatomic_cmpxchg(&rctx->status, st, RUNNER_DEAD); + condlog(st == RUNNER_CANCELLED ? 3 : 2, + "%s: runner %p finished in state '%s'", __func__, rctx, + runner_state_name(st)); + } + release_context(rctx); +} + +static void *runner_thread(void *arg) +{ + int st; + /* + * The cleanup function makes sure memory is freed if the thread is + * cancelled (-fexceptions). + */ + struct runner_context *rctx __attribute__((cleanup(cleanup_context))) = arg; + +#ifdef RUNNER_START_DELAY_US + /* + * Compile e.g. with RUNNER_START_DELAY_US=1000 to test races between + * thread start and thread cancellation. + */ + do { + struct timespec slp = {.tv_sec = 0, + .tv_nsec = 1000 * RUNNER_START_DELAY_US}; + + nanosleep(&slp, NULL); + } while (0); +#endif + + st = uatomic_cmpxchg(&rctx->status, RUNNER_IDLE, RUNNER_RUNNING); + if (st != RUNNER_IDLE) + return NULL; + + (*rctx->func)(rctx->data); + return NULL; +} + +static int cancel_runner(struct runner_context *rctx) +{ + int st, st_new; + int level = 4, retry = 1; + +repeat: + st = uatomic_cmpxchg(&rctx->status, RUNNER_RUNNING, RUNNER_CANCELLED); + st_new = st; + switch (st) { + case RUNNER_IDLE: + /* + * Race with thread startup. + * + * If after the following cmpxchg st is still IDLE, the cmpxchg + * in runner_thread() will return CANCELLED, and the context + * will be relased there. Otherwise, the thread has switched + * to RUNNING in the meantime, and we will be able to cancel + * it regularly if we retry. + */ + if (retry--) { + st = uatomic_cmpxchg(&rctx->status, RUNNER_IDLE, + RUNNER_CANCELLED); + if (st == RUNNER_IDLE) + st_new = RUNNER_CANCELLED; + else + goto repeat; + } + break; + case RUNNER_RUNNING: + pthread_cancel(rctx->thr); + st_new = RUNNER_CANCELLED; + /* fallthrough */ + case RUNNER_CANCELLED: + break; + case RUNNER_DONE: + st_new = RUNNER_DEAD; + /* fallthrough */ + case RUNNER_DEAD: + level = 3; + break; + } + condlog(level, "%s: runner %p cancelled in state '%s'", __func__, rctx, + runner_state_name(st)); + return st_new; +} + +void release_runner(struct runner_context *rctx) +{ + cancel_runner(rctx); + release_context(rctx); +} + +int check_runner(struct runner_context *rctx, void *data, unsigned int size) +{ + int st = uatomic_read(&rctx->status); + + switch (st) { + case RUNNER_DONE: + if (data) + /* hand back the data to the caller */ + memcpy(data, rctx->data, size); + /* fallthrough */ + case RUNNER_DEAD: + case RUNNER_CANCELLED: + return st; + case RUNNER_IDLE: + case RUNNER_RUNNING: + if (rctx->deadline.tv_sec != 0 || rctx->deadline.tv_nsec != 0) { + struct timespec now; + + get_monotonic_time(&now); + if (timespeccmp(&rctx->deadline, &now) <= 0) + return cancel_runner(rctx); + } + /* don't bother the caller with RUNNER_IDLE */ + return RUNNER_RUNNING; + default: + condlog(1, "%s: runner in impossible state '%s'", __func__, + runner_state_name(st)); + assert(false); + return st; + } +} + +struct runner_context *get_runner(runner_func func, void *data, + unsigned int size, unsigned long timeout_usec) +{ + static const struct timespec time_zero = {.tv_sec = 0}; + struct runner_context *rctx; + pthread_attr_t attr; + int rc; + + if (!func || !data || size <= 0) { + condlog(0, "%s: illegal arguments", __func__); + return NULL; + } + + rctx = malloc(sizeof(*rctx) + size); + if (!rctx) + return NULL; + + rctx->func = func; + /* + * We have to set the refcount to 2 here. The runner thread may be + * cancelled before it even had the chance to increase the refcount, + * which could result in a use-after-free in cleanup_context(). + */ + uatomic_set(&rctx->refcount, 2); + uatomic_set(&rctx->status, RUNNER_IDLE); + memcpy(rctx->data, data, size); + + if (timeout_usec) { + get_monotonic_time(&rctx->deadline); + rctx->deadline.tv_sec += timeout_usec / MILLION; + rctx->deadline.tv_nsec += (timeout_usec % MILLION) * 1000; + } else + rctx->deadline = time_zero; + + setup_thread_attr(&attr, STACK_SIZE, 1); + rc = pthread_create(&rctx->thr, &attr, runner_thread, rctx); + pthread_attr_destroy(&attr); + + if (rc) { + condlog(1, "%s: pthread_create(): %s", __func__, strerror(rc)); + uatomic_dec(&rctx->refcount); + release_context(rctx); + return NULL; + } + return rctx; +} diff --git a/libmpathutil/runner.h b/libmpathutil/runner.h new file mode 100644 index 000000000..cb2ad94ef --- /dev/null +++ b/libmpathutil/runner.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC +#ifndef RUNNER_H_INCLUDED +#define RUNNER_H_INCLUDED + +enum runner_status { + /** + * Initial state. Runner thread has not started yet. + */ + RUNNER_IDLE, + /** + * The runner thread is running in @runner_func (see below). + */ + RUNNER_RUNNING, + /** + * @runner_func has terminated. This is the only only state + * in which @check_runner can obtain user data in the @data + * parameter. + */ + RUNNER_DONE, + /** + * The runner thread has been cancelled (usually because of a timeout), + * but @runner_func is still running. + */ + RUNNER_CANCELLED, + /** + * The runner thread has terminated without providing user data + * (usually after a timeout). + */ + RUNNER_DEAD, +}; + +typedef void (*runner_func)(void *data); +struct runner_context; + +/** + * runner_state_name(): helper for printing runner states + * + * @param state: a valid @runner_status value + * @returns: a string describing the status + */ +const char *runner_state_name(int state); + +/** + * get_runner(): start a runner thread + * + * This function starts a runner thread that calls @func(@data). + * The thread is created as detached thread. + * @data will be copied to thread-private memory, which will be freed when + * the runner terminates. + * Output values can be retrieved with @check_runner(). + * + * @param func: the worker function to invoke + * This parameter must not be NULL. + * @param data: pointer the data to pass to the function (input and output) + * This parameter must not be NULL. + * @param size: the size (in bytes) of the data passed to the function + * This parameter must be positive. + * @param timeout_usec: timeout (in microseconds) after which to cancel the + * runner. If it is 0, the runner will not time out. + * @returns: a runner context that must be passed to the functions below. + */ +struct runner_context *get_runner(runner_func func, void *data, + unsigned int size, unsigned long timeout_usec); + +/** + * release_runner(): release a runner context + * + * This function should be called when the caller has no interest to + * operate on the given @runner_context any more. + * If necessary, the runner thread will be cancelled. + * Upon return of this function, @rctx becomes stale and shouldn't accessed + * any more. + * + * @param rctx: the context of the runner to be released. + */ +void release_runner(struct runner_context *rctx); + +/** + * check_runner(): query the state of a runner thread and obtain results + * + * Check the state of a runner previously started with @get_runner. If the + * thread function has completed, @RUNNER_DONE will be returned, and the + * user data will be copied into the @data argument. If @check_runner returns + * anything else, @data contains no valid data. The @size argument + * will typically be the same as the @size passed to @get_runner (meaning + * that @data represents an object of the same type as the @data argument + * passed to @get_runner previously). + * + * Side effect: If the runner has timed out, the thread will be cancelled. + * + * @param rctx: the context of the runner to be queried + * @param data: memory pointer that will receive results of the worker + * function. Can be NULL, in which case no data will be copied. + * @param size: size of the memory pointed to by data. It must be no bigger + * than the size of the memory passed to @get_runner for this runner. + * It can be smaller, but no more than @size bytes will be copied. + * @returns: @RUNNER_RUNNING, @RUNNER_DONE, @RUNNER_CANCELLED, or @RUNNER_DEAD. + */ +int check_runner(struct runner_context *rctx, void *data, unsigned int size); + +#endif /* RUNNER_H_INCLUDED */ From 30a6178e1bcf774dce9a9fc36c7a3ae2e1584656 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 17:50:28 +0100 Subject: [PATCH 015/102] multipath-tools tests: add test program for thread runners Add a test program for the "runner" thread implementation from the previous commit. The test program runs simulated "hanging" threads that may time out, and optionally kills all threads at an arbitrary point in time. See the comments at the top of the file for details. Also add a test driver script (runner-test.sh) with a few reasonable combinations of command line arguments. The test program has been used to test the "runner" implementation extensively on different architectures (x86_64, aarch64, ppc64le, s390x), using both valgrind and the gcc address sanitizer (libasan) for detection of memory leaks and use-after-free errors. For valgrind, a suppression file needs to be added, as valgrind doesn't seem to capture the deallocation of thread local storage for detached threads in the test case where the test program is killed. The suppression affects only memory allocated by glibc. This leak has not been seen with libasan, only with valgrind. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/Makefile | 15 +- tests/runner-test.sh | 57 +++++ tests/runner-test.supp | 15 ++ tests/runner.c | 565 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 648 insertions(+), 4 deletions(-) create mode 100755 tests/runner-test.sh create mode 100644 tests/runner-test.supp create mode 100644 tests/runner.c diff --git a/tests/Makefile b/tests/Makefile index 9f1b950f8..f0e5dd356 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,12 +9,12 @@ CFLAGS += $(BIN_CFLAGS) -Wno-unused-parameter -Wno-unused-function $(W_MISSING_I LIBDEPS += -L. -L $(mpathutildir) -L$(mpathcmddir) -lmultipath -lmpathutil -lmpathcmd -lcmocka TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ - alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo + alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo runner HELPERS := test-lib.o test-log.o .PRECIOUS: $(TESTS:%=%-test) -all: $(TESTS:%=%.out) +all: $(TESTS:%=%.out) runner-test.out progs: $(TESTS:%=%-test) lib/libchecktur.so valgrind: $(TESTS:%=%.vgr) @@ -65,6 +65,7 @@ features-test_LIBDEPS := -ludev -lpthread features-test_OBJDEPS := $(mpathutildir)/mt-libudev.o cli-test_OBJDEPS := $(daemondir)/cli.o mapinfo-test_LIBDEPS = -lpthread -ldevmapper +runner-test_LIBDEPS = -lpthread %.o: %.c @echo building $@ because of $? @@ -81,12 +82,18 @@ lib/libchecktur.so: %.vgr: %-test lib/libchecktur.so @echo == running valgrind for $< == @LD_LIBRARY_PATH=.:$(mpathutildir):$(mpathcmddir) \ - valgrind --leak-check=full --error-exitcode=128 ./$< >$@ 2>&1 + valgrind --leak-check=full --error-exitcode=128 --suppressions=./runner-test.supp ./$< >$@ 2>&1 OBJS = $(TESTS:%=%.o) $(HELPERS) +runner-test.out: runner-test + $(Q)./runner-test.sh >$@ 2>&1 + +runner-test.vgr: runner-test + $(Q)VALGRIND=1 ./runner-test.sh >$@ 2>&1 + test_clean: - $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* + $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* runner-test.out runner-test.vgr valgrind_clean: $(Q)$(RM) $(TESTS:%=%.vgr) diff --git a/tests/runner-test.sh b/tests/runner-test.sh new file mode 100755 index 000000000..f9235eefc --- /dev/null +++ b/tests/runner-test.sh @@ -0,0 +1,57 @@ +#! /bin/sh +: "${MPATHTEST_VERBOSITY:=2}" + +export LD_LIBRARY_PATH=../libmultipath:../libmpathutil:../libmpathcmd +export MPATHTEST_VERBOSITY + +RUNNER=./runner-test +if [ "$VALGRIND" ]; then + command -v valgrind >/dev/null && \ + RUNNER="valgrind --leak-check=full --error-exitcode=128 --max-threads=5000 --suppressions=./runner-test.supp ./runner-test" +fi + +# LSAN is not supported on ppc64le +case $(uname -m) in + x86_64|aarch64) + export ASAN_OPTIONS="detect_leaks=1:detect_odr_violation=0" + export LSAN_OPTIONS="report_objects=1" + ;; +esac + +LONG= +while [ $# -gt 0 ]; do + case $1 in + -l) LONG=1;; + esac + shift +done + +if [ "$LONG" ]; then + TIME1=30000 + TIME2=23000 +else + TIME1=7500 + TIME2=4700 +fi + +# Test scenarios +# 1. timeout 1 us - test runner creation / cancellation races +# 2. timeout 1 ms - test runner creation / cancellation races with -DRUNNER_START_DELAY_US=1000 +# 3. "realistic" test, scaled down by a factor 10 in time +# 4./5. Tests with high likelihood of completion / cancellation race +set -- \ + "-N 100 -p 1 -t 0 -n 2 -b 1 -s 1 -i -r 20" \ + "-N 100 -p 1 -t 1 -n 2 -b 1 -s 1 -i -r 20" \ + "-N 1000 -p 100 -t 3000 -n 2999 -b 5 -s 1 -i -r 20 -k $TIME1" \ + "-N 1000 -p 10 -t 3000 -n 1 -b 1 -s 1 -i -r 20 -k $TIME2" \ + "-N 100 -p 1 -t 3000 -n 0 -s 1 -i -r 5" + +errors=0 +for args in "$@"; do + echo "=== $RUNNER $args" + # shellcheck disable=SC2086 + $RUNNER $args || errors=$((errors+1)) +done + +echo "$0: ERRORS: $errors" +[ $errors -eq 0 ] diff --git a/tests/runner-test.supp b/tests/runner-test.supp new file mode 100644 index 000000000..e099ec9a9 --- /dev/null +++ b/tests/runner-test.supp @@ -0,0 +1,15 @@ +{ + glibc TLS for detached threads if program is terminated + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + ... + fun:allocate_dtv + fun:_dl_allocate_tls + fun:allocate_stack + ... + fun:get_runner + fun:start_runner + ... + fun:main +} diff --git a/tests/runner.c b/tests/runner.c new file mode 100644 index 000000000..aa59b43b9 --- /dev/null +++ b/tests/runner.c @@ -0,0 +1,565 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Test reliability of the runner implementation. + * + * This tests simulates "path checkers" being started through the + * runner code. It creates threads that run for a variable amount of time, + * optionally ignoring cancellation signals. The runners have a fixed + * timeout (TIMEOUT_USEC, "-t" option), after which they are considered + * "hanging" and will be cancelled. The actual runtime of the runner is random. + * It varies between (TIMEOUT_USEC - NOISE USEC) and + * (TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC). These noise parameters are + * set with the "-n" and "-b" option, respectively. + * This allows simulating frequent races between cancellation and regular + * completion of threads. Note that because timers in different threads + * aren't started simultaneously, NOISE_USEC = 0 doesn't mean that the + * runners will complete exactly at the point in time when there timer + * expires. + * The runners simulate waiting checkers by simply sleeping. Optionally, + * they can ignore cancellation while sleeping (IGNORE_CANCEL, -i) or + * divide the sleep time in multiple "steps" (-s), between which they can + * be cancelled. + * Like multipathd, the main thread "polls" the status of the runners in + * regular time intervals that are set with POLL_USEC (-p). Because of + * this polling behavior, it can happen that a runner finishes after + * its timeout has expired. The runner code (and this test) treats this case + * as successful completion. + * If a runner completes, the result is checked against the expected value. + * The number of threads that have not finished (either successfully or + * cancelled, plus the number of wrong results of completed runners is + * the error count. + * The N_RUNNERS (-N) option determines how many simultaneous threads are + * started. + * The test runs until all runners have either completed or expired, or + * until a maximum wait time is reached, which is calculated from the + * test parameters (max_wait in run_test()). The REPEAT (-r) parameter + * determines the number of times the entire test is repeated. + * The KILL_TIMEOUT (-k) parameter is for simulating a shutdown of the + * main program (think multipathd). When this timeout expires, all pending + * runners are cancelled and the program terminates. + * + * A "realistic" simulation of multipathd path checkers would use options + * roughly like this: + * + * runner-test -N 1000 -p 1000 -t 30000 -n 29990 -b 5 -s 1 -i -r 20 -k 300000 + * + * (note that time options are in ms, whereas the code uses us), but this + * takes a very long time to run. + * + * Scaled down, it becomes: + * + * runner-test -N 1000 -p 100 -t 3000 -n 2999 -b 5 -s 1 -i -r 20 -k 30000 + * + * A less realistic run with high likelihood of completionc / cancellation races: + * + * runner-test -N 1000 -p 10 -t 3000 -n 1 -b 1 -s 1 -i -r 20 + * + * Here, all runners finish in a +-1ms timer interval around the timeout. + * Even with -n 0 (no noise), with a sufficient number of runners, some runners + * will time out. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "time-util.h" +#include "runner.h" +#include "runner.h" +#include "globals.c" + +#define MILLION 1000000 +#define BILLION 1000000000 + +/* For testing startup races, see libmpathutil/runner.c */ +#ifndef RUNNER_START_DELAY_US +#define RUNNER_START_DELAY_US 0 +#endif + +static int N_RUNNERS = 100; +/* sleep time between runner status polling */ +static long POLL_USEC = 10000; +/* timeout for runner */ +static long TIMEOUT_USEC = 100000; +/* random noise to subtract / add to the sleep time */ +static long NOISE_USEC = 10000; +/* + * Factor to increase noise towards longer sleep times (timeouts). + * The actual sleep time will be in the interval + * [ TIMEOUT_USEC - NOISE_USEC, TIMEOUT_USEC + NOISE_USEC * NOISE_BIAS ] + */ +static int NOISE_BIAS = 5; +/* number of sleep intervals the runner uses */ +static int SLEEP_STEPS = 1; +/* time after which to kill all runners */ +static long KILL_TIMEOUT = 0; +/* number of repeated runs */ +static int REPEAT = 10; +/* whether to ignore cancellation signals */ +static bool IGNORE_CANCEL = false; + +/* gap in the paylod to similate larger size */ +#define PAYLOAD_GAP 128 + +struct payload { + long wait_nsec; + int steps; + bool ignore_cancel; + int start; + char pad[PAYLOAD_GAP]; + int end; +}; + +static void wait_and_add_1(void *arg) +{ + struct payload *t1 = arg; + struct timespec wait; + int i, cancelstate; + + wait.tv_sec = t1->wait_nsec / BILLION; + wait.tv_nsec = t1->wait_nsec % BILLION; + normalize_timespec(&wait); + for (i = 0; i < t1->steps; i++) { + if (t1->ignore_cancel) + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancelstate); + if (nanosleep(&wait, NULL) != 0 && errno != EINTR) + condlog(3, "%s: nanosleep: %s", __func__, strerror(errno)); + if (t1->ignore_cancel) { + pthread_setcancelstate(cancelstate, NULL); + } + pthread_testcancel(); + } + t1->end = t1->start + 1; +} + +static bool payload_error(const struct payload *p) +{ + return p->end != p->start + 1; +} + +static int check_payload(struct runner_context *rctx, bool *error) +{ + struct timespec now; + struct payload t1; + int st = check_runner(rctx, &t1, sizeof(t1)); + + if (st == RUNNER_RUNNING || st == RUNNER_IDLE) + return st; + + clock_gettime(CLOCK_MONOTONIC, &now); + if (st == RUNNER_DONE) { + int level = 4; + + if (error) { + *error = payload_error(&t1); + if (*error) + level = 2; + } + condlog(level, + "runner %p finished in state 'done' at %lld.%06lld, start %d end %d", + rctx, (long long)now.tv_sec, + (long long)now.tv_nsec / 1000, t1.start, t1.end); + } else + condlog(4, "runner %p finished in state '%s' at %lld.%06lld", + rctx, runner_state_name(st), (long long)now.tv_sec, + (long long)now.tv_nsec / 1000); + return st; +} + +static struct runner_context * +start_runner(long usecs, int start, long noise_range_usec, long bias, + int steps, bool ignore_cancel) +{ + + struct payload t1; + struct runner_context *rctx; + long noise; + + if (noise_range_usec > 0) + noise = random() % ((bias + 1) * noise_range_usec) - + noise_range_usec; + else + noise = 0; + t1.start = start; + t1.end = 0; + t1.wait_nsec = (usecs + noise) * 1000 / steps; + t1.wait_nsec = t1.wait_nsec > 0 ? t1.wait_nsec : 0; + t1.steps = steps; + t1.ignore_cancel = ignore_cancel; + + rctx = get_runner(wait_and_add_1, &t1, sizeof(t1), usecs); + + if (rctx) { + struct timespec tmo, finish; + + clock_gettime(CLOCK_MONOTONIC, &tmo); + tmo.tv_sec += usecs / MILLION; + tmo.tv_nsec += (usecs % MILLION) * 1000; + finish = tmo; + normalize_timespec(&tmo); + finish.tv_sec += noise / MILLION; + finish.tv_nsec += (noise % MILLION) * 1000; + normalize_timespec(&finish); + condlog(4, "started runner start %d timeout %lld.%06lld, finish %lld.%06lld (noise %ld), steps %d, %signoring cancellation", + start, (long long)tmo.tv_sec, + (long long)tmo.tv_nsec / 1000, (long long)finish.tv_sec, + (long long)finish.tv_nsec / 1000, noise, steps, + ignore_cancel ? "" : "not "); + return rctx; + } else { + condlog(0, "failed to start runner for start %d", start); + return NULL; + } +} + +static struct runner_context **context; +static volatile bool must_stop = false; +void int_handler(int signal) +{ + must_stop = true; +} + +static void terminate_all(void) +{ + int i, count; + + for (count = 0, i = 0; i < N_RUNNERS; i++) + if (context[i]) { + release_runner(context[i]); + context[i] = NULL; + count++; + } + condlog(3, "%s: %d runners released", __func__, count); + /* give runners a chance to clean up */ + sched_yield(); +} + +static bool test_sleep(const struct timespec *wait) +{ + sigset_t set; + + sigfillset(&set); + sigdelset(&set, SIGTERM); + sigdelset(&set, SIGINT); + sigdelset(&set, SIGQUIT); + + pselect(0, NULL, NULL, NULL, wait, &set); + + if (!must_stop) + return false; + + terminate_all(); + return true; +} + +static int run_test(int n) +{ + int i, running, done, errors; + const struct timespec wait = {.tv_sec = 0, .tv_nsec = 1000 * POLL_USEC}; + struct timespec stop, now; + long max_wait = TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC + 100000 + + RUNNER_START_DELAY_US; + bool killed = false; + + for (i = 0; i < N_RUNNERS; i++) + context[i] = start_runner(TIMEOUT_USEC, i, NOISE_USEC, NOISE_BIAS, + SLEEP_STEPS, IGNORE_CANCEL); + + clock_gettime(CLOCK_MONOTONIC, &stop); + stop.tv_sec += max_wait / MILLION; + stop.tv_nsec += (max_wait % MILLION) * 1000; + normalize_timespec(&stop); + running = N_RUNNERS; + done = 0; + errors = 0; + do { + bool err = false, last = false; + + killed = test_sleep(&wait); + if (killed) + condlog(3, "%s: terminating on signal...", __func__); + + clock_gettime(CLOCK_MONOTONIC, &now); + if (timespeccmp(&stop, &now) <= 0) + last = true; + + for (running = 0, i = 0; i < N_RUNNERS; i++) { + int st; + + if (!context[i]) + continue; + st = check_payload(context[i], &err); + switch (st) { + case RUNNER_DONE: + if (err) + errors++; + done++; + /* fallthrough */ + case RUNNER_DEAD: + release_runner(context[i]); + context[i] = NULL; + break; + case RUNNER_CANCELLED: + if (last) { + condlog(3, "%s: releasing %p", + __func__, context[i]); + release_runner(context[i]); + context[i] = NULL; + } else + running++; + break; + default: + running++; + if (last) + condlog(1, "%s: found thread in state %s, rctx=%p", + __func__, runner_state_name(st), + context[i]); + break; + } + } + if (killed || last) + break; + } while (running); + + condlog(2, "%10d%10d%10d%10d%10d", n, N_RUNNERS, N_RUNNERS - running, + done, errors); + + if (killed) { + condlog(2, "%s: termination signal received", __func__); + exit(0); + } + + if (running > 0) { + condlog(1, "ERROR: %d runners haven't finished", running); + terminate_all(); + } + return running + errors; +} + +static void free_rctxs(struct runner_context ***rctxs) +{ + if (*rctxs) + free(*rctxs); +} + +static int setup_signal_handler(int sig, void (*handler)(int)) +{ + sigset_t set; + sigfillset(&set); + struct sigaction sga = {.sa_handler = NULL}; + + sga.sa_handler = handler; + sga.sa_mask = set; + if (sigaction(sig, &sga, NULL) != 0) { + condlog(1, "%s: failed to install signal handler for %d: %s", + __func__, sig, strerror(errno)); + return -1; + } + return 0; +} + +int run_tests(void) +{ + int errors = 0, i; + struct runner_context **rctxs __attribute__((cleanup(free_rctxs))) = NULL; + + if (setup_signal_handler(SIGINT, int_handler) != 0) + return -1; + + rctxs = calloc(N_RUNNERS, sizeof(*context)); + if (rctxs == NULL) + /* arbitrary number to indicate OOM error */ + return 7000; + context = rctxs; + for (i = 0; i < REPEAT; i++) { + errors += run_test(i + 1); + } + return errors ? 1 : 0; +} + +/* We need to register a dummy handler to avoid system call restarting in + * pselect() below */ +static void dummy_handler(int sig) {} + +static int fork_test(void) +{ + sigset_t set; + pid_t child; + int wstatus; + struct timespec wait_to_kill = {.tv_sec = 0}; + + /* Block all signals. termination signals will be enabled in test_sleep() */ + sigfillset(&set); + pthread_sigmask(SIG_SETMASK, &set, NULL); + + child = fork(); + + if (child < 0) { + condlog(0, "error in fork(), %s", strerror(errno)); + return -1; + } else if (child == 0) { + /* child */ + int rc = run_tests(); + exit(rc ? 1 : 0); + } + + setup_signal_handler(SIGCHLD, dummy_handler); + + /* parent */ + if (KILL_TIMEOUT > 0) { + sigset_t set; + + condlog(3, "%s: == Child %d will be killed with SIGINT after %ld us", + __func__, child, KILL_TIMEOUT); + wait_to_kill.tv_sec = KILL_TIMEOUT / MILLION; + wait_to_kill.tv_nsec = (KILL_TIMEOUT % MILLION) * 1000; + + /* + * Unblock SIGCHLD in case thild terminates + * (child will receive SIGINT) + */ + sigfillset(&set); + sigdelset(&set, SIGCHLD); + if (pselect(0, NULL, NULL, NULL, &wait_to_kill, &set) != 0) { + if (errno == EINTR) + condlog(2, "main: child terminated"); + else + condlog(1, "main: error in pselect: %s", + strerror(errno)); + } else + kill(child, SIGINT); + } + + if (waitpid(child, &wstatus, 0) <= 0) { + condlog(1, "%s: failed to wait for child %d", __func__, child); + return -1; + } + if (WIFEXITED(wstatus)) { + condlog(3, "%s: child %d return code %d", __func__, child, + WEXITSTATUS(wstatus)); + return WEXITSTATUS(wstatus); + } else if (WIFSIGNALED(wstatus)) { + condlog(2, "%s: child %d killed by signal code %d", __func__, + child, WTERMSIG(wstatus)); + return -1; + } else { + condlog(1, "%s: unexpected status of child %d", __func__, child); + return -1; + } +} + +static long parse_number(const char *arg, long factor, long deflt) +{ + char *ep; + long v; + + if (*arg) { + v = strtol(arg, &ep, 10); + if (!*ep && v >= 0) + return factor * v; + } + condlog(1, "invalid argument: %s, using %ld", arg, deflt); + return deflt; +} + +static int usage(const char *cmd, int opt) +{ +#define USAGE_FMT \ + "Usage: %s [options]\n" \ + " -N runners: number of parallel runners\n" \ + " -p msecs: time to sleep between status polls in main thread\n" \ + " -t msecs: timeout for runners\n" \ + " -n msecs: random noise for runner sleep time\n" \ + " -b bias: noise increase factor towards sleeping longer\n" \ + " -s n: number of steps to divide sleep time into\n" \ + " -k msecs: timeout after which to kill all runners (0: don't kill)\n" \ + " -r n: number of times to repeat test\n" \ + " -i: runners ignore cancellation while sleeping\n" \ + " -v n: set verbosity level (default 2)\n" \ + " -h: print this help" + condlog(0, USAGE_FMT, cmd); + return opt == 'h' ? 0 : 1; +} + +int main(int argc, char *argv[]) +{ + int opt; + int total = 0; + const char *optstring = "+:N:p:t:n:b:s:k:r:v:ih"; + + init_test_verbosity(2); + + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + case 'N': + N_RUNNERS = parse_number(optarg, 1L, N_RUNNERS); + break; + case 'p': + POLL_USEC = parse_number(optarg, 1000L, POLL_USEC); + break; + case 't': + TIMEOUT_USEC = parse_number(optarg, 1000L, TIMEOUT_USEC); + break; + case 'n': + NOISE_USEC = parse_number(optarg, 1000L, NOISE_USEC); + break; + case 'b': + NOISE_BIAS = parse_number(optarg, 1L, NOISE_BIAS); + break; + case 's': + SLEEP_STEPS = parse_number(optarg, 1L, SLEEP_STEPS); + break; + case 'k': + KILL_TIMEOUT = parse_number(optarg, 1000L, KILL_TIMEOUT); + break; + case 'r': + REPEAT = parse_number(optarg, 1L, REPEAT); + break; + case 'i': + IGNORE_CANCEL = true; + break; + case 'v': + libmp_verbosity = parse_number(optarg, 1L, libmp_verbosity); + break; + case 'h': + case ':': + case '?': + return usage(argv[0], opt); + break; + } + } + + if (optind != argc) + return usage(argv[0], '?'); + + /* + * For the sake of this test, infinite timeout makes no sense. + * Interpret "-t 0" as "minimal timeout", and use 1us. + */ + if (TIMEOUT_USEC == 0) + TIMEOUT_USEC = 1; + + condlog(2, "Runner: timeout=%ld us, noise interval=[-%ld:%ld] us, steps=%d", + TIMEOUT_USEC, TIMEOUT_USEC - NOISE_USEC, + TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC, SLEEP_STEPS); + condlog(2, "Other : poll interval=%ld us, ignore cancellation=%s, runners=%d, repeat=%d, kill timeout=%ld us", + POLL_USEC, IGNORE_CANCEL ? "YES" : "NO", N_RUNNERS, REPEAT, + KILL_TIMEOUT); + condlog(2, "%10s%10s%10s%10s%10s", "run", "total", "finished", + "completed", "errors"); + + total = fork_test(); + if (total == -1) + return 130; + condlog(2, "== TOTAL NUMBER OF FAILED RUNS: %d", total); + return total ? 1 : 0; +} From d1ebd97751fa7669ed8f394ab81f298c52e0d44f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Mar 2026 17:59:30 +0100 Subject: [PATCH 016/102] libmultipath: TUR checker: use runner threads Use the generic runners to simplify the TUR checker code. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/checkers/tur.c | 363 +++++++++++------------------------- 1 file changed, 112 insertions(+), 251 deletions(-) diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index ba4ca683c..0a312c7c6 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -3,7 +3,6 @@ * * Copyright (c) 2004 Christophe Varoqui */ -#include #include #include #include @@ -14,16 +13,11 @@ #include #include #include -#include -#include -#include #include "checkers.h" - #include "debug.h" #include "sg_include.h" -#include "util.h" -#include "time-util.h" +#include "runner.h" #define TUR_CMD_LEN 6 #define HEAVY_CHECK_COUNT 10 @@ -45,70 +39,47 @@ const char *libcheck_msgtable[] = { NULL, }; -struct tur_checker_context { - dev_t devt; - int state; - int running; /* uatomic access only */ +struct tur_data { int fd; + dev_t devt; unsigned int timeout; - time_t time; - pthread_t thread; - pthread_mutex_t lock; - pthread_cond_t active; - int holders; /* uatomic access only */ - int msgid; - struct checker_context ctx; + int state; + short msgid; +}; + +struct tur_checker_context { + struct checker_context chkr; + int last_runner_state; unsigned int nr_timeouts; - bool checked_state; + struct runner_context *rtx; + struct tur_data tdata; }; -int libcheck_init (struct checker * c) +int libcheck_init(struct checker *c) { - struct tur_checker_context *ct; + struct tur_checker_context *tcc; struct stat sb; - ct = malloc(sizeof(struct tur_checker_context)); - if (!ct) - return 1; - memset(ct, 0, sizeof(struct tur_checker_context)); - - ct->state = PATH_UNCHECKED; - ct->fd = -1; - uatomic_set(&ct->holders, 1); - pthread_cond_init_mono(&ct->active); - pthread_mutex_init(&ct->lock, NULL); + tcc = calloc(1, sizeof(*tcc)); + tcc->tdata.state = PATH_UNCHECKED; + tcc->tdata.fd = -1; if (fstat(c->fd, &sb) == 0) - ct->devt = sb.st_rdev; - ct->ctx.cls = c->cls; - c->context = ct; - + tcc->tdata.devt = sb.st_rdev; + tcc->chkr.cls = c->cls; + c->context = tcc; return 0; } -static void cleanup_context(struct tur_checker_context *ct) -{ - pthread_mutex_destroy(&ct->lock); - pthread_cond_destroy(&ct->active); - free(ct); -} - void libcheck_free (struct checker * c) { - if (c->context) { - struct tur_checker_context *ct = c->context; - int holders; - int running; - - running = uatomic_xchg(&ct->running, 0); - if (running) - pthread_cancel(ct->thread); - ct->thread = 0; - holders = uatomic_sub_return(&ct->holders, 1); - if (!holders) - cleanup_context(ct); - c->context = NULL; - } - return; + struct tur_checker_context *tcc = c->context; + + if (!tcc) + return; + c->context = NULL; + if (tcc->rtx) + release_runner(tcc->rtx); + free(tcc); } static int @@ -216,19 +187,6 @@ tur_check(int fd, unsigned int timeout, short *msgid) return PATH_UP; } -#define tur_thread_cleanup_push(ct) pthread_cleanup_push(cleanup_func, ct) -#define tur_thread_cleanup_pop(ct) pthread_cleanup_pop(1) - -static void cleanup_func(void *data) -{ - int holders; - struct tur_checker_context *ct = data; - - holders = uatomic_sub_return(&ct->holders, 1); - if (!holders) - cleanup_context(ct); -} - /* * Test code for "zombie tur thread" handling. * Compile e.g. with CFLAGS=-DTUR_TEST_MAJOR=8 @@ -249,13 +207,13 @@ static void cleanup_func(void *data) #define TUR_SLEEP_SECS 60 #endif -static void tur_deep_sleep(const struct tur_checker_context *ct) +static void tur_deep_sleep(const struct tur_data *tdata) { static int sleep_cnt; const struct timespec ts = { .tv_sec = TUR_SLEEP_SECS, .tv_nsec = 0 }; int oldstate; - if (ct->devt != makedev(TUR_TEST_MAJOR, TUR_TEST_MINOR) || + if (tdata->devt != makedev(TUR_TEST_MAJOR, TUR_TEST_MINOR) || ++sleep_cnt % TUR_SLEEP_INTERVAL != 0) return; @@ -273,110 +231,92 @@ static void tur_deep_sleep(const struct tur_checker_context *ct) #define tur_deep_sleep(x) do {} while (0) #endif /* TUR_TEST_MAJOR */ -void *libcheck_thread(struct checker_context *ctx) +void runner_callback(void *arg) { - struct tur_checker_context *ct = - container_of(ctx, struct tur_checker_context, ctx); - int state, running; - short msgid; - - /* This thread can be canceled, so setup clean up */ - tur_thread_cleanup_push(ct); + struct tur_data *tdata = arg; + int state; - condlog(4, "%d:%d : tur checker starting up", major(ct->devt), - minor(ct->devt)); + condlog(4, "%d:%d : tur checker starting up", major(tdata->devt), + minor(tdata->devt)); - tur_deep_sleep(ct); - state = tur_check(ct->fd, ct->timeout, &msgid); + tur_deep_sleep(tdata); + state = tur_check(tdata->fd, tdata->timeout, &tdata->msgid); + tdata->state = state; pthread_testcancel(); - - /* TUR checker done */ - pthread_mutex_lock(&ct->lock); - ct->state = state; - ct->msgid = msgid; - pthread_cond_signal(&ct->active); - pthread_mutex_unlock(&ct->lock); - - condlog(4, "%d:%d : tur checker finished, state %s", major(ct->devt), - minor(ct->devt), checker_state_name(state)); - - running = uatomic_xchg(&ct->running, 0); - if (!running) - pause(); - - tur_thread_cleanup_pop(ct); - - return ((void *)0); + condlog(4, "%d:%d : tur checker finished, state %s", major(tdata->devt), + minor(tdata->devt), checker_state_name(state)); } -static void tur_set_async_timeout(struct checker *c) +static int check_runner_state(struct tur_checker_context *tcc) { - struct tur_checker_context *ct = c->context; - struct timespec now; - - get_monotonic_time(&now); - ct->time = now.tv_sec + c->timeout; -} - -static int tur_check_async_timeout(struct checker *c) -{ - struct tur_checker_context *ct = c->context; - struct timespec now; - - get_monotonic_time(&now); - return (now.tv_sec > ct->time); -} - -int check_pending(struct checker *c) -{ - struct tur_checker_context *ct = c->context; - int tur_status = PATH_PENDING; - - pthread_mutex_lock(&ct->lock); - - if (ct->state != PATH_PENDING || ct->msgid != MSG_TUR_RUNNING) - { - tur_status = ct->state; - c->msgid = ct->msgid; - } - pthread_mutex_unlock(&ct->lock); - if (tur_status == PATH_PENDING && c->msgid == MSG_TUR_RUNNING) { + struct runner_context *rtx = tcc->rtx; + int rc; + + rc = check_runner(rtx, &tcc->tdata, sizeof(tcc->tdata)); + switch (rc) { + case RUNNER_DEAD: + tcc->tdata.state = PATH_TIMEOUT; + tcc->tdata.msgid = MSG_TUR_TIMEOUT; + /* fallthrough */ + case RUNNER_DONE: + release_runner(tcc->rtx); + tcc->rtx = NULL; + tcc->last_runner_state = rc; + tcc->nr_timeouts = 0; + condlog(rc == RUNNER_DONE ? 4 : 3, + "%d:%d : tur checker finished, state %s, runner state %s", + major(tcc->tdata.devt), minor(tcc->tdata.devt), + checker_state_name(tcc->tdata.state), + runner_state_name(rc)); + break; + case RUNNER_CANCELLED: + tcc->last_runner_state = rc; + tcc->tdata.state = PATH_TIMEOUT; + tcc->tdata.msgid = MSG_TUR_TIMEOUT; + if (tcc->nr_timeouts < MAX_NR_TIMEOUTS) { + condlog(3, "%d:%d : tur checker timed out, releasing it", + major(tcc->tdata.devt), minor(tcc->tdata.devt)); + tcc->nr_timeouts++; + release_runner(tcc->rtx); + tcc->rtx = NULL; + } else if (tcc->nr_timeouts == MAX_NR_TIMEOUTS) { + tcc->nr_timeouts++; + condlog(3, "%d:%d : tur checker timed out, waiting for it", + major(tcc->tdata.devt), minor(tcc->tdata.devt)); + } + break; + default: condlog(4, "%d:%d : tur checker still running", - major(ct->devt), minor(ct->devt)); - } else { - int running = uatomic_xchg(&ct->running, 0); - if (running) - pthread_cancel(ct->thread); - ct->thread = 0; + major(tcc->tdata.devt), minor(tcc->tdata.devt)); + tcc->tdata.msgid = MSG_TUR_RUNNING; + break; } - - ct->checked_state = true; - return tur_status; + return rc; } bool libcheck_need_wait(struct checker *c) { struct tur_checker_context *ct = c->context; - return (ct && ct->thread && uatomic_read(&ct->running) != 0 && - !ct->checked_state); + + return ct && ct->rtx; } int libcheck_pending(struct checker *c) { struct tur_checker_context *ct = c->context; - /* The if path checker isn't running, just return the exiting value. */ - if (!ct || !ct->thread) + if (!ct || !ct->rtx) return c->path_state; - return check_pending(c); + /* This may nullify ct->rtx */ + check_runner_state(ct); + c->msgid = ct->tdata.msgid; + return ct->tdata.state; } int libcheck_check(struct checker * c) { struct tur_checker_context *ct = c->context; - pthread_attr_t attr; - int tur_status, r; if (!ct) return PATH_UNCHECKED; @@ -384,109 +324,30 @@ int libcheck_check(struct checker * c) if (checker_is_sync(c)) return tur_check(c->fd, c->timeout, &c->msgid); - /* - * Async mode - */ - if (ct->thread) { - ct->checked_state = true; - if (tur_check_async_timeout(c)) { - int running = uatomic_xchg(&ct->running, 0); - if (running) { - pthread_cancel(ct->thread); - condlog(3, "%d:%d : tur checker timeout", - major(ct->devt), minor(ct->devt)); - c->msgid = MSG_TUR_TIMEOUT; - tur_status = PATH_TIMEOUT; - } else { - pthread_mutex_lock(&ct->lock); - tur_status = ct->state; - c->msgid = ct->msgid; - pthread_mutex_unlock(&ct->lock); - } - ct->thread = 0; - } else if (uatomic_read(&ct->running) != 0) { - condlog(3, "%d:%d : tur checker not finished", - major(ct->devt), minor(ct->devt)); - tur_status = PATH_PENDING; - c->msgid = MSG_TUR_RUNNING; - } else { - /* TUR checker done */ - ct->thread = 0; - pthread_mutex_lock(&ct->lock); - tur_status = ct->state; - c->msgid = ct->msgid; - pthread_mutex_unlock(&ct->lock); - } - } else { - if (uatomic_read(&ct->holders) > 1) { - /* The thread has been cancelled but hasn't quit. */ - if (ct->nr_timeouts == MAX_NR_TIMEOUTS) { - condlog(2, "%d:%d : waiting for stalled tur thread to finish", - major(ct->devt), minor(ct->devt)); - ct->nr_timeouts++; - } - /* - * Don't start new threads until the last once has - * finished. - */ - if (ct->nr_timeouts > MAX_NR_TIMEOUTS) { - c->msgid = MSG_TUR_TIMEOUT; - return PATH_TIMEOUT; - } - ct->nr_timeouts++; - /* - * Start a new thread while the old one is stalled. - * We have to prevent it from interfering with the new - * thread. We create a new context and leave the old - * one with the stale thread, hoping it will clean up - * eventually. - */ - condlog(3, "%d:%d : tur thread not responding", - major(ct->devt), minor(ct->devt)); - - /* - * libcheck_init will replace c->context. - * It fails only in OOM situations. In this case, return - * PATH_UNCHECKED to avoid prematurely failing the path. - */ - if (libcheck_init(c) != 0) { - c->msgid = MSG_TUR_FAILED; - return PATH_UNCHECKED; - } - ((struct tur_checker_context *)c->context)->nr_timeouts = ct->nr_timeouts; + /* Handle the case that the checker just completed */ + if (ct->rtx) { + check_runner_state(ct); + c->msgid = ct->tdata.msgid; + return ct->tdata.state; + } - if (!uatomic_sub_return(&ct->holders, 1)) { - /* It did terminate, eventually */ - cleanup_context(ct); - ((struct tur_checker_context *)c->context)->nr_timeouts = 0; - } + /* create new checker thread */ + ct->tdata.fd = c->fd; + ct->tdata.timeout = c->timeout; - ct = c->context; - } else - ct->nr_timeouts = 0; - /* Start new TUR checker */ - pthread_mutex_lock(&ct->lock); - tur_status = ct->state = PATH_PENDING; - c->msgid = ct->msgid = MSG_TUR_RUNNING; - pthread_mutex_unlock(&ct->lock); - ct->fd = c->fd; - ct->timeout = c->timeout; - ct->checked_state = false; - uatomic_add(&ct->holders, 1); - uatomic_set(&ct->running, 1); - tur_set_async_timeout(c); - setup_thread_attr(&attr, 32 * 1024, 1); - r = start_checker_thread(&ct->thread, &attr, &ct->ctx); - pthread_attr_destroy(&attr); - if (r) { - uatomic_sub(&ct->holders, 1); - uatomic_set(&ct->running, 0); - ct->thread = 0; - condlog(3, "%d:%d : failed to start tur thread, using" - " sync mode", major(ct->devt), minor(ct->devt)); - return tur_check(c->fd, c->timeout, &c->msgid); - } - } + ct->tdata.state = PATH_PENDING; + ct->tdata.msgid = MSG_TUR_RUNNING; + condlog(3, "%d:%d : starting checker", major(ct->tdata.devt), + minor(ct->tdata.devt)); + ct->rtx = get_runner(runner_callback, &ct->tdata, sizeof(ct->tdata), + 1000000 * c->timeout); - return tur_status; + if (ct->rtx) { + c->msgid = ct->tdata.msgid; + return ct->tdata.state; + } else { + condlog(3, "%d:%d : failed to start tur thread, using sync mode", + major(ct->tdata.devt), minor(ct->tdata.devt)); + return tur_check(c->fd, c->timeout, &c->msgid); + } } From 920f052678eca8ece429d9f67d7f7b52d5a9e4c7 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 23 Apr 2026 21:53:47 +0200 Subject: [PATCH 017/102] libmultipath: tur checker: improve tur_deep_sleep() test The tur_deep_sleep() test serves to test the behavior of the tur checker when checkers time out and cannot be cancelled. Up to now the checker timed out on every TUR_SLEEP_INTERVALth invocation. That made it impossible to test the MAX_TIMEOUTS logic, which requires multiple consecutive timeouts. To fix it, invert the logic, so that the checker just succeeds on every TUR_SLEEP_INTERVALth call. Also, decrease loglevel of the messages from tur_deep_sleep(). Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/checkers/tur.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index 0a312c7c6..679117246 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -214,15 +214,15 @@ static void tur_deep_sleep(const struct tur_data *tdata) int oldstate; if (tdata->devt != makedev(TUR_TEST_MAJOR, TUR_TEST_MINOR) || - ++sleep_cnt % TUR_SLEEP_INTERVAL != 0) + ++sleep_cnt % TUR_SLEEP_INTERVAL == 0) return; - condlog(1, "tur thread going to sleep for %ld seconds", ts.tv_sec); + condlog(3, "tur thread going to sleep for %ld seconds", ts.tv_sec); if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0) condlog(0, "pthread_setcancelstate: %m"); if (nanosleep(&ts, NULL) != 0) condlog(0, "nanosleep: %m"); - condlog(1, "tur zombie thread woke up"); + condlog(3, "tur zombie thread woke up"); if (pthread_setcancelstate(oldstate, NULL) != 0) condlog(0, "pthread_setcancelstate (2): %m"); pthread_testcancel(); From fa2eb1268bf66332b859259051308030f78001fc Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Sun, 31 May 2026 12:38:27 +0200 Subject: [PATCH 018/102] multipath-tools: align ALUA state strings with kernel scsi_sysfs Update ALUA asymmetric access state descriptions in the prioritizer to match the shorter names defined in the SCSI kernel subsystem: $ grep "{ SCSI_ACCESS_STATE_" linux/drivers/scsi/scsi_sysfs.c { SCSI_ACCESS_STATE_OPTIMAL, "active/optimized" }, { SCSI_ACCESS_STATE_ACTIVE, "active/non-optimized" }, { SCSI_ACCESS_STATE_STANDBY, "standby" }, { SCSI_ACCESS_STATE_UNAVAILABLE, "unavailable" }, { SCSI_ACCESS_STATE_LBA, "lba-dependent" }, { SCSI_ACCESS_STATE_OFFLINE, "offline" }, { SCSI_ACCESS_STATE_TRANSITIONING, "transitioning" }, This unifies diagnostic strings across the stack and replaces the misleading "ARRAY BUG: invalid TPGs state!" message with the standard SPC "reserved" designation. These codes are explicitly reserved for future standard definitions rather than being treated as malformed states. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Martin Wilck --- libmultipath/prioritizers/alua.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libmultipath/prioritizers/alua.c b/libmultipath/prioritizers/alua.c index ec68f3702..3aedbda15 100644 --- a/libmultipath/prioritizers/alua.c +++ b/libmultipath/prioritizers/alua.c @@ -26,16 +26,18 @@ #define ALUA_PRIO_TPGS_FAILED 4 #define ALUA_PRIO_NO_INFORMATION 5 +// clang-format off static const char * aas_string[] = { [AAS_OPTIMIZED] = "active/optimized", [AAS_NON_OPTIMIZED] = "active/non-optimized", [AAS_STANDBY] = "standby", [AAS_UNAVAILABLE] = "unavailable", - [AAS_LBA_DEPENDENT] = "logical block dependent", - [AAS_RESERVED] = "ARRAY BUG: invalid TPGs state!", + [AAS_LBA_DEPENDENT] = "lba-dependent", + [AAS_RESERVED] = "reserved", [AAS_OFFLINE] = "offline", - [AAS_TRANSITIONING] = "transitioning between states", + [AAS_TRANSITIONING] = "transitioning", }; +// clang-format on static const char *aas_print_string(int rc) { From bc50eb994cfe55572ad7ddbcab1f92184777b537 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 19:22:20 +0200 Subject: [PATCH 019/102] libmpathutil: runner: reduce a message loglevel Don't "log runner 0x7b724a07a480 cancelled in state 'done'" at level 3. It's a fairly normal thing to happen. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmpathutil/runner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index da57ff039..8c6d6b91c 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -134,7 +134,7 @@ static int cancel_runner(struct runner_context *rctx) break; case RUNNER_DONE: st_new = RUNNER_DEAD; - /* fallthrough */ + break; case RUNNER_DEAD: level = 3; break; From 2c7a5fcff365edd2687685cb05a4774077d7309e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 16:57:55 +0200 Subject: [PATCH 020/102] libmultipath: checkers: add two generic checker messages Add generic messages for "timed out" and "still running". They will be needed by follow-up patches. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.c | 2 ++ libmultipath/checkers.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index bb6ad1ee6..8a90adeb6 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -365,6 +365,8 @@ static const char *generic_msg[CHECKER_GENERIC_MSGTABLE_SIZE] = { [CHECKER_MSGID_GHOST] = " reports path is ghost", [CHECKER_MSGID_UNSUPPORTED] = " doesn't support this device", [CHECKER_MSGID_DISCONNECTED] = " no access to this device", + [CHECKER_MSGID_TIMEOUT] = " timed out", + [CHECKER_MSGID_RUNNING] = " still running", }; const char *checker_message(const struct checker *c) diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index a969e7d15..2317cd112 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -124,6 +124,8 @@ enum { CHECKER_MSGID_GHOST, CHECKER_MSGID_UNSUPPORTED, CHECKER_MSGID_DISCONNECTED, + CHECKER_MSGID_TIMEOUT, + CHECKER_MSGID_RUNNING, CHECKER_GENERIC_MSGTABLE_SIZE, CHECKER_FIRST_MSGID = 100, /* lowest msgid for checkers */ CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */ From 8b8d0bababa33e7c207a0294fc1dece999464e4b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 17:01:56 +0200 Subject: [PATCH 021/102] libmultipath: checkers: move checker_class definition to checkers.h Make the contents of struct checker_class accessible to other code. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.c | 18 ------------------ libmultipath/checkers.h | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 8a90adeb6..8f18ca6ca 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -14,24 +14,6 @@ static const char * const checker_dir = MULTIPATH_DIR; -struct checker_class { - struct list_head node; - void *handle; - int refcount; - int sync; - char name[CHECKER_NAME_LEN]; - int (*check)(struct checker *); - int (*init)(struct checker *); /* to allocate the context */ - int (*mp_init)(struct checker *); /* to allocate the mpcontext */ - void (*free)(struct checker *); /* to free the context */ - void (*reset)(void); /* to reset the global variables */ - void *(*thread)(void *); /* async thread entry point */ - int (*pending)(struct checker *); /* to recheck pending paths */ - bool (*need_wait)(struct checker *); /* checker needs waiting for */ - const char **msgtable; - short msgtable_size; -}; - static const char *checker_state_names[PATH_MAX_STATE] = { [PATH_WILD] = "wild", [PATH_UNCHECKED] = "unchecked", diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 2317cd112..694dfa37a 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -131,7 +131,25 @@ enum { CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */ }; -struct checker_class; +struct checker; +struct checker_class { + struct list_head node; + void *handle; + int refcount; + int sync; + char name[CHECKER_NAME_LEN]; + int (*check)(struct checker *); + int (*init)(struct checker *); /* to allocate the context */ + int (*mp_init)(struct checker *); /* to allocate the mpcontext */ + void (*free)(struct checker *); /* to free the context */ + void (*reset)(void); /* to reset the global variables */ + void *(*thread)(void *); /* async thread entry point */ + int (*pending)(struct checker *); /* to recheck pending paths */ + bool (*need_wait)(struct checker *); /* checker needs waiting for */ + const char **msgtable; + short msgtable_size; +}; + struct checker { struct checker_class *cls; int fd; From dc29e0899d4ef51ace7b69ac7bb010e915f72bea Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 21:52:32 +0200 Subject: [PATCH 022/102] libmpathutil: add implementation of generic shared pointer Add a set of simple functions to handle refcounted pointers. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmpathutil/libmpathutil.version | 7 +++++ libmpathutil/util.c | 47 ++++++++++++++++++++++++++++--- libmpathutil/util.h | 5 ++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/libmpathutil/libmpathutil.version b/libmpathutil/libmpathutil.version index c69120db3..51594ad47 100644 --- a/libmpathutil/libmpathutil.version +++ b/libmpathutil/libmpathutil.version @@ -194,3 +194,10 @@ global: local: *; }; + +LIBMPATHUTIL_6.1 { +global: + alloc_shared_ptr; + get_shared_ptr; + put_shared_ptr; +} LIBMPATHUTIL_6.0; diff --git a/libmpathutil/util.c b/libmpathutil/util.c index 23a9797ce..a6f279514 100644 --- a/libmpathutil/util.c +++ b/libmpathutil/util.c @@ -12,15 +12,13 @@ #include #include #include +#include #include "mt-udev-wrap.h" #include "util.h" #include "debug.h" -#include "checkers.h" #include "vector.h" -#include "structs.h" -#include "config.h" -#include "log.h" +#include "list.h" size_t strchop(char *str) @@ -384,3 +382,44 @@ void cleanup_udev_device(struct udev_device **udd) if (*udd) udev_device_unref(*udd); } + +struct shared_ptr { + long refcnt; + void (*destructor)(void *); + char __attribute__((aligned(sizeof(void *)))) ptr[]; +}; + +void *alloc_shared_ptr(size_t size, void (*destructor)(void *)) +{ + struct shared_ptr *sp = malloc(sizeof(*sp) + size); + + if (!sp) + return NULL; + uatomic_set(&sp->refcnt, 1); + sp->destructor = destructor; + return sp->ptr; +} + +void get_shared_ptr(void *ptr) +{ + struct shared_ptr *sp; + + assert(ptr != NULL); + sp = container_of(ptr, struct shared_ptr, ptr); + + if (uatomic_add_return(&sp->refcnt, 1) < 0) + condlog(0, "%s: refcount overflow", __func__); +} + +void put_shared_ptr(void *ptr) +{ + struct shared_ptr *sp; + + assert(ptr != NULL); + sp = container_of(ptr, struct shared_ptr, ptr); + if (uatomic_sub_return(&sp->refcnt, 1) == 0) { + if (sp->destructor) + sp->destructor(ptr); + free(sp); + } +} diff --git a/libmpathutil/util.h b/libmpathutil/util.h index aed1bc1ee..ffbb1821b 100644 --- a/libmpathutil/util.h +++ b/libmpathutil/util.h @@ -160,4 +160,9 @@ void cleanup_charp(char **p); void cleanup_ucharp(unsigned char **p); void cleanup_udev_device(struct udev_device **udd); void cleanup_bitfield(union bitfield **p); + +void *alloc_shared_ptr(size_t size, void (*destructor)(void *)); +void get_shared_ptr(void *ptr); +void put_shared_ptr(void *ptr); + #endif /* UTIL_H_INCLUDED */ From 023be40ccb09ae021f20102dfa468f7bdb0f8bf2 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 21:53:13 +0200 Subject: [PATCH 023/102] multipath-tools tests: add tests for shared pointer code Add a few tests (mostly meaningful with valgrind or ASAN) to test the shared pointer code. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- tests/Makefile | 4 +- tests/shared_ptr.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/shared_ptr.c diff --git a/tests/Makefile b/tests/Makefile index f0e5dd356..fc2a7ed7a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,7 +9,8 @@ CFLAGS += $(BIN_CFLAGS) -Wno-unused-parameter -Wno-unused-function $(W_MISSING_I LIBDEPS += -L. -L $(mpathutildir) -L$(mpathcmddir) -lmultipath -lmpathutil -lmpathcmd -lcmocka TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ - alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo runner + alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo runner \ + shared_ptr HELPERS := test-lib.o test-log.o .PRECIOUS: $(TESTS:%=%-test) @@ -66,6 +67,7 @@ features-test_OBJDEPS := $(mpathutildir)/mt-libudev.o cli-test_OBJDEPS := $(daemondir)/cli.o mapinfo-test_LIBDEPS = -lpthread -ldevmapper runner-test_LIBDEPS = -lpthread +shared_ptr-test_LIBDEPS = -lpthread %.o: %.c @echo building $@ because of $? diff --git a/tests/shared_ptr.c b/tests/shared_ptr.c new file mode 100644 index 000000000..2194ddd11 --- /dev/null +++ b/tests/shared_ptr.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "cmocka-compat.h" +#include "util.h" +#include "globals.c" + +struct s_4 { + uint32_t val; +}; + +struct s_8 { + char pad[4]; + uint32_t val; +}; + +struct s_64 { + char pad[60]; + uint32_t val; +}; + +// clang-format off +#define make_destruct(n, size) \ +static void destruct_ ## n ## _ ## size(void *ptr) \ +{ \ + struct s_##size *s = ptr; \ + \ + assert_ptr_not_equal(ptr, NULL); \ + assert_int_equal(s->val, n); \ +} + +#define make_fn(size) \ +static void *thread_##size(void *arg) \ +{ \ + struct s_##size *s = arg; \ + \ + get_shared_ptr(s); \ + uatomic_add(&s->val, 1); \ + put_shared_ptr(s); \ + return NULL; \ +} + +make_fn(4) +make_fn(8) +make_fn(64) + +#define make_test(n, size) \ +make_destruct(n, size) \ + \ +static void test_ ## n ## _ ## size(void **state) \ +{ \ + pthread_attr_t attr; \ + pthread_t threads[n]; \ + struct s_##size *s; \ + int i, rc; \ + \ + rc = pthread_attr_init(&attr); \ + assert_int_equal(rc, 0); \ + rc = pthread_attr_setstacksize(&attr, \ + PTHREAD_STACK_MIN + 4096); \ + assert_int_equal(rc, 0); \ + s = alloc_shared_ptr(size, destruct_ ## n## _ ## size); \ + assert_ptr_not_equal(s, NULL); \ + s->val = 0; \ + for (i = 0; i < n; i++) { \ + rc = pthread_create(&threads[i], &attr, \ + thread_##size, s); \ + assert_int_equal(rc, 0); \ + } \ + pthread_attr_destroy(&attr); \ + for (i = 0; i < n; i++) { \ + rc = pthread_join(threads[i], NULL); \ + assert_int_equal(rc, 0); \ + } \ + assert_int_equal(uatomic_read(&s->val), n); \ + put_shared_ptr(s); \ +} + +make_test(0, 4) +make_test(100, 4) +make_test(1000, 4) +make_test(0, 8) +make_test(100, 8) +make_test(1000, 8) +make_test(0, 64) +make_test(100, 64) +make_test(1000, 64) + // clang-format on + + int test_shared_ptr(void) +{ + // clang-format off + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_0_4), + cmocka_unit_test(test_100_4), + cmocka_unit_test(test_1000_4), + cmocka_unit_test(test_0_8), + cmocka_unit_test(test_100_8), + cmocka_unit_test(test_1000_8), + cmocka_unit_test(test_0_64), + cmocka_unit_test(test_100_64), + cmocka_unit_test(test_1000_64), + }; + // clang-format on + return cmocka_run_group_tests(tests, NULL, NULL); +} + +int main(void) +{ + int ret = 0; + + init_test_verbosity(-1); + ret += test_shared_ptr(); + return ret; +} From cbfc7117c7adbdd46967794ab2a17054fd3dccda Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Sun, 17 May 2026 16:38:44 +0200 Subject: [PATCH 024/102] libmultipath: remove libcheck_thread related code After d1ebd97 ("libmultipath: TUR checker: use runner threads"), this code is not used any more. Remove it. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.c | 40 +------------------------------ libmultipath/checkers.h | 22 +---------------- libmultipath/libmultipath.version | 1 - 3 files changed, 2 insertions(+), 61 deletions(-) diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 8f18ca6ca..5655146ce 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -163,8 +163,7 @@ static struct checker_class *add_checker_class(const char *name) goto out; c->mp_init = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_mp_init"); - c->reset = (void (*)(void)) dlsym(c->handle, "libcheck_reset"); - c->thread = (void *(*)(void*)) dlsym(c->handle, "libcheck_thread"); + c->reset = (void (*)(void))dlsym(c->handle, "libcheck_reset"); c->pending = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_pending"); c->need_wait = (bool (*)(struct checker *)) dlsym(c->handle, "libcheck_need_wait"); /* These 5 functions can be NULL. call dlerror() to clear out any @@ -371,43 +370,6 @@ const char *checker_message(const struct checker *c) return generic_msg[CHECKER_MSGID_NONE]; } -static void checker_cleanup_thread(void *arg) -{ - struct checker_class *cls = arg; - - free_checker_class(cls); - rcu_unregister_thread(); -} - -static void *checker_thread_entry(void *arg) -{ - struct checker_context *ctx = arg; - void *rv; - - rcu_register_thread(); - pthread_cleanup_push(checker_cleanup_thread, ctx->cls); - rv = ctx->cls->thread(ctx); - pthread_cleanup_pop(1); - return rv; -} - -int start_checker_thread(pthread_t *thread, const pthread_attr_t *attr, - struct checker_context *ctx) -{ - int rv; - - assert(ctx && ctx->cls && ctx->cls->thread); - /* Take a ref here, lest the class be freed before the thread starts */ - (void)checker_class_ref(ctx->cls); - rv = pthread_create(thread, attr, checker_thread_entry, ctx); - if (rv != 0) { - condlog(1, "failed to start checker thread for %s: %m", - ctx->cls->name); - checker_class_unref(ctx->cls); - } - return rv; -} - void checker_clear_message (struct checker *c) { if (!c) diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 694dfa37a..e69dfd9c0 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -143,7 +143,6 @@ struct checker_class { int (*mp_init)(struct checker *); /* to allocate the mpcontext */ void (*free)(struct checker *); /* to free the context */ void (*reset)(void); /* to reset the global variables */ - void *(*thread)(void *); /* async thread entry point */ int (*pending)(struct checker *); /* to recheck pending paths */ bool (*need_wait)(struct checker *); /* checker needs waiting for */ const char **msgtable; @@ -179,29 +178,10 @@ void checker_set_sync (struct checker *); void checker_set_async (struct checker *); void checker_set_fd (struct checker *, int); void checker_enable (struct checker *); -void checker_disable (struct checker *); -/* - * start_checker_thread(): start async path checker thread - * - * This function provides a wrapper around pthread_create(). - * The created thread will call the DSO's "libcheck_thread" function with the - * checker context as argument. - * - * Rationale: - * Path checkers that do I/O may hang forever. To avoid blocking, some - * checkers therefore use asynchronous, detached threads for checking - * the paths. These threads may continue hanging if multipathd is stopped. - * In this case, we can't unload the checker DSO at exit. In order to - * avoid race conditions and crashes, the entry point of the thread - * needs to be in libmultipath, not in the DSO itself. - * - * @param arg: pointer to struct checker_context. - */ +void checker_disable(struct checker *); struct checker_context { struct checker_class *cls; }; -int start_checker_thread (pthread_t *thread, const pthread_attr_t *attr, - struct checker_context *ctx); int checker_get_state(struct checker *c); bool checker_need_wait(struct checker *c); void checker_check (struct checker *, int); diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 78fa2d436..5deee32e4 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -221,7 +221,6 @@ global: /* checkers */ checker_is_sync; sg_read; - start_checker_thread; /* prioritizers */ get_asymmetric_access_state; From e032b0261672d863b740597dbab645c33d1572c8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 22:03:23 +0200 Subject: [PATCH 025/102] libmultipath: use shared_ptr for checker classes Utilize the share_ptr code for tracking the refcount of checker classes. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.c | 58 ++++++++++++++--------------------------- libmultipath/checkers.h | 2 -- 2 files changed, 19 insertions(+), 41 deletions(-) diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 5655146ce..a3b9cc8e4 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -39,41 +39,11 @@ const char *checker_state_name(int i) return checker_state_names[i]; } -static struct checker_class *alloc_checker_class(void) -{ - struct checker_class *c; - - c = calloc(1, sizeof(struct checker_class)); - if (c) { - INIT_LIST_HEAD(&c->node); - uatomic_set(&c->refcount, 1); - } - return c; -} - -/* Use uatomic_{sub,add}_return() to ensure proper memory barriers */ -static int checker_class_ref(struct checker_class *cls) -{ - return uatomic_add_return(&cls->refcount, 1); -} - -static int checker_class_unref(struct checker_class *cls) -{ - return uatomic_sub_return(&cls->refcount, 1); -} - -void free_checker_class(struct checker_class *c) +static void free_checker_class(void *ptr) { - int cnt; - + struct checker_class *c = ptr; if (!c) return; - cnt = checker_class_unref(c); - if (cnt != 0) { - condlog(cnt < 0 ? 1 : 4, "%s checker refcount %d", - c->name, cnt); - return; - } condlog(3, "unloading %s checker", c->name); list_del(&c->node); if (c->reset) @@ -84,7 +54,18 @@ void free_checker_class(struct checker_class *c) c->name, dlerror()); } } - free(c); +} + +static struct checker_class *alloc_checker_class(void) +{ + struct checker_class *c; + + c = alloc_shared_ptr(sizeof(*c), free_checker_class); + if (c) { + memset(c, 0, sizeof(*c)); + INIT_LIST_HEAD(&c->node); + } + return c; } void cleanup_checkers (void) @@ -92,9 +73,8 @@ void cleanup_checkers (void) struct checker_class *checker_loop; struct checker_class *checker_temp; - list_for_each_entry_safe(checker_loop, checker_temp, &checkers, node) { - free_checker_class(checker_loop); - } + list_for_each_entry_safe(checker_loop, checker_temp, &checkers, node) + put_shared_ptr(checker_loop); } static struct checker_class *checker_class_lookup(const char *name) @@ -198,7 +178,7 @@ static struct checker_class *add_checker_class(const char *name) list_add(&c->node, &checkers); return c; out: - free_checker_class(c); + put_shared_ptr(c); return NULL; } @@ -284,7 +264,7 @@ void checker_put (struct checker * dst) if (src && src->free) src->free(dst); checker_clear(dst); - free_checker_class(src); + put_shared_ptr(src); } int checker_get_state(struct checker *c) @@ -393,7 +373,7 @@ void checker_get(struct checker *dst, const char *name) if (!src) return; - (void)checker_class_ref(dst->cls); + get_shared_ptr(dst->cls); } int init_checkers(void) diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index e69dfd9c0..7ff8e01b9 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -135,7 +135,6 @@ struct checker; struct checker_class { struct list_head node; void *handle; - int refcount; int sync; char name[CHECKER_NAME_LEN]; int (*check)(struct checker *); @@ -200,7 +199,6 @@ void checker_get(struct checker *, const char *); int libcheck_check(struct checker *); int libcheck_init(struct checker *); void libcheck_free(struct checker *); -void *libcheck_thread(struct checker_context *ctx); void libcheck_reset(void); int libcheck_mp_init(struct checker *); int libcheck_pending(struct checker *c); From ca006b13570cf015e7836e842c2748082a971b33 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 22:53:16 +0200 Subject: [PATCH 026/102] libmultipath: use shared_ptr for prioritizers Replace the hardcoded refcount handling in prio.c with shared_ptr. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/prio.c | 47 +++++++++++++++++++-------------------------- libmultipath/prio.h | 1 - 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/libmultipath/prio.c b/libmultipath/prio.c index 24f825bd8..bf388e9c6 100644 --- a/libmultipath/prio.c +++ b/libmultipath/prio.c @@ -53,28 +53,11 @@ int init_prio(void) return 0; } -static struct prio * alloc_prio (void) -{ - struct prio *p; - - p = calloc(1, sizeof(struct prio)); - if (p) { - INIT_LIST_HEAD(&p->node); - p->refcount = 1; - } - return p; -} - -void free_prio (struct prio * p) +void free_prio(void *ptr) { + struct prio *p = ptr; if (!p) return; - p->refcount--; - if (p->refcount) { - condlog(4, "%s prioritizer refcount %d", - p->name, p->refcount); - return; - } condlog(3, "unloading %s prioritizer", p->name); list_del(&p->node); if (p->handle) { @@ -83,7 +66,18 @@ void free_prio (struct prio * p) p->name, dlerror()); } } - free(p); +} + +static struct prio *alloc_prio(void) +{ + struct prio *p; + + p = alloc_shared_ptr(sizeof(*p), free_prio); + if (p) { + memset(p, 0, sizeof(*p)); + INIT_LIST_HEAD(&p->node); + } + return p; } void cleanup_prio(void) @@ -91,9 +85,8 @@ void cleanup_prio(void) struct prio * prio_loop; struct prio * prio_temp; - list_for_each_entry_safe(prio_loop, prio_temp, &prioritizers, node) { - free_prio(prio_loop); - } + list_for_each_entry_safe(prio_loop, prio_temp, &prioritizers, node) + put_shared_ptr(prio_loop); } static struct prio *prio_lookup(const char *name) @@ -150,7 +143,7 @@ struct prio *add_prio (const char *name) list_add(&p->node, &prioritizers); return p; out: - free_prio(p); + put_shared_ptr(p); return NULL; } @@ -199,17 +192,17 @@ void prio_get(struct prio *dst, const char *name, const char *args) dst->getprio = src->getprio; dst->handle = NULL; - src->refcount++; + get_shared_ptr(src); } void prio_put (struct prio * dst) { - struct prio * src; + struct prio *src; if (!dst || !dst->getprio) return; src = prio_lookup(dst->name); memset(dst, 0x0, sizeof(struct prio)); - free_prio(src); + put_shared_ptr(src); } diff --git a/libmultipath/prio.h b/libmultipath/prio.h index 119b75f25..7ff1a2434 100644 --- a/libmultipath/prio.h +++ b/libmultipath/prio.h @@ -45,7 +45,6 @@ struct path; struct prio { void *handle; - int refcount; struct list_head node; char name[PRIO_NAME_LEN]; char args[PRIO_ARGS_LEN]; From 2e9b23c07ae167b6e927f9b95ccf6eae0607a6b9 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 23:05:48 +0200 Subject: [PATCH 027/102] libmpathutil: runner: use shared_ptr Use shared_ptr to track the runner_context refcount. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmpathutil/runner.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index 8c6d6b91c..56abd03af 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -30,7 +30,6 @@ const char *runner_state_name(int state) } struct runner_context { - int refcount; int status; struct timespec deadline; pthread_t thr; @@ -39,17 +38,6 @@ struct runner_context { char __attribute__((aligned(sizeof(void *)))) data[]; }; -static void release_context(struct runner_context *rctx) -{ - int n; - - n = uatomic_sub_return(&rctx->refcount, 1); - assert(n >= 0); - - if (n == 0) - free(rctx); -} - static void cleanup_context(struct runner_context **prctx) { struct runner_context *rctx = *prctx; @@ -65,7 +53,7 @@ static void cleanup_context(struct runner_context **prctx) "%s: runner %p finished in state '%s'", __func__, rctx, runner_state_name(st)); } - release_context(rctx); + put_shared_ptr(rctx); } static void *runner_thread(void *arg) @@ -147,7 +135,7 @@ static int cancel_runner(struct runner_context *rctx) void release_runner(struct runner_context *rctx) { cancel_runner(rctx); - release_context(rctx); + put_shared_ptr(rctx); } int check_runner(struct runner_context *rctx, void *data, unsigned int size) @@ -195,17 +183,17 @@ struct runner_context *get_runner(runner_func func, void *data, return NULL; } - rctx = malloc(sizeof(*rctx) + size); + rctx = alloc_shared_ptr(sizeof(*rctx) + size, NULL); if (!rctx) return NULL; rctx->func = func; /* - * We have to set the refcount to 2 here. The runner thread may be - * cancelled before it even had the chance to increase the refcount, - * which could result in a use-after-free in cleanup_context(). + * Take an additional reference here. The runner thread may be + * cancelled before it even had the chance to take a reference, which + * could result in a use-after-free in cleanup_context(). */ - uatomic_set(&rctx->refcount, 2); + get_shared_ptr(rctx); uatomic_set(&rctx->status, RUNNER_IDLE); memcpy(rctx->data, data, size); @@ -222,8 +210,8 @@ struct runner_context *get_runner(runner_func func, void *data, if (rc) { condlog(1, "%s: pthread_create(): %s", __func__, strerror(rc)); - uatomic_dec(&rctx->refcount); - release_context(rctx); + put_shared_ptr(rctx); + put_shared_ptr(rctx); return NULL; } return rctx; From 474682edd0c5abcd707214f07745d8815a126b18 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 16:53:01 +0200 Subject: [PATCH 028/102] libmultipath: add generic async path checker code Add a framework for an asynchronous path checker utilizing the recently added runner code for thread handling. This code has been derived from the TUR checker code by removing all references to the actual sending of TUR ioctls. Follow-up patches will convert the current TUR checker into an instance of this async checker class. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/Makefile | 2 +- libmultipath/async_checker.c | 214 +++++++++++++++++++++++++++++++++++ libmultipath/async_checker.h | 35 ++++++ libmultipath/checkers.c | 1 + libmultipath/checkers.h | 2 + 5 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 libmultipath/async_checker.c create mode 100644 libmultipath/async_checker.h diff --git a/libmultipath/Makefile b/libmultipath/Makefile index 85767ab4f..d71a835fe 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -22,7 +22,7 @@ OBJS-O := devmapper.o hwtable.o blacklist.o dmparser.o \ configure.o structs_vec.o sysfs.o \ lock.o file.o wwids.o prioritizers/alua_rtpg.o prkey.o \ io_err_stat.o dm-generic.o generic.o nvme-lib.o \ - libsg.o valid.o + libsg.o valid.o async_checker.o OBJS := $(OBJS-O) $(OBJS-U) diff --git a/libmultipath/async_checker.c b/libmultipath/async_checker.c new file mode 100644 index 000000000..02fe7657c --- /dev/null +++ b/libmultipath/async_checker.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC + +#include +#include +#include +#include +#include +#include "async_checker.h" +#include "checkers.h" +#include "debug.h" +#include "runner.h" + +#define MAX_NR_TIMEOUTS 1 + +struct async_checker_context { + int last_runner_state; + unsigned int nr_timeouts; + struct runner_context *rtx; + struct runner_data rdata; +}; + +#define rdata_size(acc) (sizeof(acc->rdata)) + +int async_check_init(struct checker *c) +{ + struct async_checker_context *acc; + struct stat sb; + + acc = calloc(1, sizeof(*acc)); + if (!acc) + return -1; + acc->rdata.state = PATH_UNCHECKED; + acc->rdata.fd = -1; + if (fstat(c->fd, &sb) == 0) + acc->rdata.devt = sb.st_rdev; + c->context = acc; + return 0; +} + +void async_check_free(struct checker *c) +{ + struct async_checker_context *acc = c->context; + + if (!acc) + return; + c->context = NULL; + if (acc->rtx) + release_runner(acc->rtx); + free(acc); +} + +static void runner_callback(void *arg) +{ + struct runner_data *rdata = arg; + int state; + + condlog(4, "%d:%d : async checker starting up", major(rdata->devt), + minor(rdata->devt)); + + async_deep_sleep(rdata); + state = rdata->afunc(rdata); + rdata->state = state; + pthread_testcancel(); + condlog(4, "%d:%d : async checker finished, state %s", major(rdata->devt), + minor(rdata->devt), checker_state_name(state)); +} + +static int check_runner_state(struct async_checker_context *acc) +{ + struct runner_context *rtx = acc->rtx; + int rc; + + rc = check_runner(rtx, &acc->rdata, rdata_size(acc)); + switch (rc) { + case RUNNER_DEAD: + acc->rdata.state = PATH_TIMEOUT; + acc->rdata.msgid = CHECKER_MSGID_TIMEOUT; + /* fallthrough */ + case RUNNER_DONE: + release_runner(acc->rtx); + acc->rtx = NULL; + acc->last_runner_state = rc; + acc->nr_timeouts = 0; + condlog(rc == RUNNER_DONE ? 4 : 3, + "%d:%d : async checker finished, state %s, runner state %s", + major(acc->rdata.devt), minor(acc->rdata.devt), + checker_state_name(acc->rdata.state), + runner_state_name(rc)); + break; + case RUNNER_CANCELLED: + acc->last_runner_state = rc; + acc->rdata.state = PATH_TIMEOUT; + acc->rdata.msgid = CHECKER_MSGID_TIMEOUT; + if (acc->nr_timeouts < MAX_NR_TIMEOUTS) { + condlog(3, "%d:%d : async checker timed out, releasing it", + major(acc->rdata.devt), minor(acc->rdata.devt)); + acc->nr_timeouts++; + release_runner(acc->rtx); + acc->rtx = NULL; + } else if (acc->nr_timeouts == MAX_NR_TIMEOUTS) { + acc->nr_timeouts++; + condlog(3, "%d:%d : async checker timed out, waiting for it", + major(acc->rdata.devt), minor(acc->rdata.devt)); + } + break; + default: + condlog(4, "%d:%d : async checker still running", + major(acc->rdata.devt), minor(acc->rdata.devt)); + acc->rdata.msgid = CHECKER_MSGID_RUNNING; + break; + } + return rc; +} + +bool async_check_need_wait(struct checker *c) +{ + struct async_checker_context *acc = c->context; + + return acc && acc->rtx; +} + +int async_check_pending(struct checker *c) +{ + struct async_checker_context *acc = c->context; + /* The if path checker isn't running, just return the exiting value. */ + if (!acc || !acc->rtx) + return c->path_state; + + /* This may nullify ct->rtx */ + check_runner_state(acc); + c->msgid = acc->rdata.msgid; + return acc->rdata.state; +} + +int async_check_check(struct checker *c) +{ + struct async_checker_context *acc = c->context; + + if (!acc) + return PATH_UNCHECKED; + + if (checker_is_sync(c)) + return acc->rdata.afunc(&acc->rdata); + + /* Handle the case that the checker just completed */ + if (acc->rtx) { + check_runner_state(acc); + c->msgid = acc->rdata.msgid; + return acc->rdata.state; + } + + /* create new checker thread */ + acc->rdata.fd = c->fd; + acc->rdata.timeout = c->timeout; + + acc->rdata.state = PATH_PENDING; + acc->rdata.msgid = CHECKER_MSGID_RUNNING; + acc->rdata.afunc = c->cls->async_func; + condlog(4, "%d:%d : starting checker", major(acc->rdata.devt), + minor(acc->rdata.devt)); + acc->rtx = get_runner(runner_callback, &acc->rdata, rdata_size(acc), + 1000000 * c->timeout); + + if (acc->rtx) { + c->msgid = acc->rdata.msgid; + return acc->rdata.state; + } else { + condlog(3, "%d:%d : failed to start async thread, using sync mode", + major(acc->rdata.devt), minor(acc->rdata.devt)); + return acc->rdata.afunc(&acc->rdata); + } +} + +/* + * Test code for "zombie tur thread" handling. + * Compile e.g. with CFLAGS=-DASYNC_TEST_MAJOR=8 + * Additional parameters can be configure with the macros below. + * + * Everty nth started thread will hang in non-cancellable state + * for given number of seconds, for device given by major/minor. + */ +#ifdef ASYNC_TEST_MAJOR +#ifndef ASYNC_TEST_MINOR +#define ASYNC_TEST_MINOR 0 +#endif +#ifndef ASYNC_SLEEP_INTERVAL +#define ASYNC_SLEEP_INTERVAL 3 +#endif +#ifndef ASYNC_SLEEP_SECS +#define ASYNC_SLEEP_SECS 60 +#endif + +static void async_deep_sleep(const struct runner_data *rdata) +{ + static int sleep_cnt; + const struct timespec ts = {.tv_sec = ASYNC_SLEEP_SECS, .tv_nsec = 0}; + int oldstate; + + if (rdata->devt != makedev(ASYNC_TEST_MAJOR, ASYNC_TEST_MINOR) || + ++sleep_cnt % ASYNC_SLEEP_INTERVAL == 0) + return; + + condlog(3, "async thread going to sleep for %ld seconds", ts.tv_sec); + if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0) + condlog(0, "pthread_setcancelstate: %m"); + if (nanosleep(&ts, NULL) != 0) + condlog(0, "nanosleep: %m"); + condlog(3, "async zombie thread woke up"); + if (pthread_setcancelstate(oldstate, NULL) != 0) + condlog(0, "pthread_setcancelstate (2): %m"); + pthread_testcancel(); +} +#endif /* ASYNC_TEST_MAJOR */ diff --git a/libmultipath/async_checker.h b/libmultipath/async_checker.h new file mode 100644 index 000000000..fc9632da9 --- /dev/null +++ b/libmultipath/async_checker.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC +#ifndef ASYNC_CHECKER_H_INCLUDED +#define ASYNC_CHECKER_H_INCLUDED + +struct runner_data; +struct checker; +typedef int (*async_checker_func)(struct runner_data *); + +struct runner_data { + int fd; + dev_t devt; + async_checker_func afunc; + unsigned int timeout; + int state; + short msgid; + char __attribute__((aligned(sizeof(void *)))) checker_ctx[]; +}; + +int async_check_init(struct checker *c); +void async_check_free(struct checker *c); +bool async_check_need_wait(struct checker *c); +int async_check_pending(struct checker *c); +int async_check_check(struct checker *c); + +#define CHECKER_MAX_CONTEXT_SIZE 1024 + +/* For testing handling of async checker timeouts */ +#ifdef ASYNC_TEST_MAJOR +static void async_deep_sleep(const struct runner_data *rdata); +#else +#define async_deep_sleep(x) do {} while (0) +#endif + +#endif diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index a3b9cc8e4..3a1663fcd 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -9,6 +9,7 @@ #include "debug.h" #include "checkers.h" +#include "async_checker.h" #include "vector.h" #include "util.h" diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 7ff8e01b9..05dbd7cc4 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -132,6 +132,7 @@ enum { }; struct checker; +struct runner_data; struct checker_class { struct list_head node; void *handle; @@ -144,6 +145,7 @@ struct checker_class { void (*reset)(void); /* to reset the global variables */ int (*pending)(struct checker *); /* to recheck pending paths */ bool (*need_wait)(struct checker *); /* checker needs waiting for */ + int (*async_func)(struct runner_data *); /* callback for async_checker */ const char **msgtable; short msgtable_size; }; From 5712eb40fb8da82075a08ae4a8b74a76f313245d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 17:05:01 +0200 Subject: [PATCH 029/102] libmultipath: checkers: add support for async checker If a checker shared library contains a symbol "libcheck_async_func", use this as a callback for the async checker functions, and do not read any other symbols from the .so file except the message table. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.c | 51 +++++++++++++++++++++---------- libmultipath/libmultipath.version | 2 +- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 3a1663fcd..2f20b250d 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -101,6 +101,18 @@ void reset_checker_classes(void) } } +static struct checker_class *add_async_checker_class(struct checker_class *c) +{ + c->init = async_check_init; + c->check = async_check_check; + c->need_wait = async_check_need_wait; + c->pending = async_check_pending; + c->free = async_check_free; + + list_add(&c->node, &checkers); + return c; +} + static struct checker_class *add_checker_class(const char *name) { char libname[LIB_CHECKER_NAMELEN]; @@ -129,6 +141,29 @@ static struct checker_class *add_checker_class(const char *name) errstr); goto out; } + + c->msgtable_size = 0; + c->msgtable = dlsym(c->handle, "libcheck_msgtable"); + + if (c->msgtable != NULL) { + const char **p; + + for (p = c->msgtable; + *p && (p - c->msgtable) < CHECKER_MSGTABLE_SIZE; p++) + /* nothing */; + + c->msgtable_size = p - c->msgtable; + } else + c->msgtable_size = 0; + condlog(3, "checker %s: message table size = %d", c->name, + c->msgtable_size); + + c->async_func = (int (*)(struct runner_data *)) + dlsym(c->handle, "libcheck_async_func"); + errstr = dlerror(); + if (c->async_func) + return add_async_checker_class(c); + c->check = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_check"); errstr = dlerror(); if (errstr != NULL) @@ -158,22 +193,6 @@ static struct checker_class *add_checker_class(const char *name) if (!c->free) goto out; - c->msgtable_size = 0; - c->msgtable = dlsym(c->handle, "libcheck_msgtable"); - - if (c->msgtable != NULL) { - const char **p; - - for (p = c->msgtable; - *p && (p - c->msgtable) < CHECKER_MSGTABLE_SIZE; p++) - /* nothing */; - - c->msgtable_size = p - c->msgtable; - } else - c->msgtable_size = 0; - condlog(3, "checker %s: message table size = %d", - c->name, c->msgtable_size); - done: c->sync = 1; list_add(&c->node, &checkers); diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 5deee32e4..c091ce268 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMULTIPATH_32.0.0 { +LIBMULTIPATH_33.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; From 9770e6f8338ad3d5e62a8d1e1492535c005d04b2 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 17:06:40 +0200 Subject: [PATCH 030/102] libmultipath: convert TUR checker to an async checker instance The TUR checker now just exports the message table and the "libcheck_async_func" symbol. This converts it into an instance of the generic async checker model. With this change, struct checker_context isn't used any more and can be removed. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers.h | 3 - libmultipath/checkers/tur.c | 253 +++--------------------------------- 2 files changed, 15 insertions(+), 241 deletions(-) diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 05dbd7cc4..4846ea95b 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -180,9 +180,6 @@ void checker_set_async (struct checker *); void checker_set_fd (struct checker *, int); void checker_enable (struct checker *); void checker_disable(struct checker *); -struct checker_context { - struct checker_class *cls; -}; int checker_get_state(struct checker *c); bool checker_need_wait(struct checker *c); void checker_check (struct checker *, int); diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index 679117246..62460cb2c 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -3,87 +3,29 @@ * * Copyright (c) 2004 Christophe Varoqui */ -#include -#include -#include -#include -#include -#include #include -#include +#include + #include -#include #include "checkers.h" -#include "debug.h" +#include "async_checker.h" #include "sg_include.h" -#include "runner.h" #define TUR_CMD_LEN 6 #define HEAVY_CHECK_COUNT 10 -#define MAX_NR_TIMEOUTS 1 enum { - MSG_TUR_RUNNING = CHECKER_FIRST_MSGID, - MSG_TUR_TIMEOUT, - MSG_TUR_FAILED, - MSG_TUR_TRANSITIONING, + MSG_TUR_TRANSITIONING = CHECKER_FIRST_MSGID, }; #define IDX_(x) (MSG_ ## x - CHECKER_FIRST_MSGID) const char *libcheck_msgtable[] = { - [IDX_(TUR_RUNNING)] = " still running", - [IDX_(TUR_TIMEOUT)] = " timed out", - [IDX_(TUR_FAILED)] = " failed to initialize", [IDX_(TUR_TRANSITIONING)] = " reports path is transitioning", NULL, }; -struct tur_data { - int fd; - dev_t devt; - unsigned int timeout; - int state; - short msgid; -}; - -struct tur_checker_context { - struct checker_context chkr; - int last_runner_state; - unsigned int nr_timeouts; - struct runner_context *rtx; - struct tur_data tdata; -}; - -int libcheck_init(struct checker *c) -{ - struct tur_checker_context *tcc; - struct stat sb; - - tcc = calloc(1, sizeof(*tcc)); - tcc->tdata.state = PATH_UNCHECKED; - tcc->tdata.fd = -1; - if (fstat(c->fd, &sb) == 0) - tcc->tdata.devt = sb.st_rdev; - tcc->chkr.cls = c->cls; - c->context = tcc; - return 0; -} - -void libcheck_free (struct checker * c) -{ - struct tur_checker_context *tcc = c->context; - - if (!tcc) - return; - c->context = NULL; - if (tcc->rtx) - release_runner(tcc->rtx); - free(tcc); -} - -static int -tur_check(int fd, unsigned int timeout, short *msgid) +int libcheck_async_func(struct runner_data *rdata) { struct sg_io_hdr io_hdr; unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; @@ -99,14 +41,14 @@ tur_check(int fd, unsigned int timeout, short *msgid) io_hdr.dxfer_direction = SG_DXFER_NONE; io_hdr.cmdp = turCmdBlk; io_hdr.sbp = sense_buffer; - io_hdr.timeout = timeout * 1000; + io_hdr.timeout = rdata->timeout * 1000; io_hdr.pack_id = 0; - if (ioctl(fd, SG_IO, &io_hdr) < 0) { + if (ioctl(rdata->fd, SG_IO, &io_hdr) < 0) { if (errno == ENOTTY) { - *msgid = CHECKER_MSGID_UNSUPPORTED; + rdata->msgid = CHECKER_MSGID_UNSUPPORTED; return PATH_WILD; } - *msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; return PATH_DOWN; } if ((io_hdr.status & 0x7e) == 0x18) { @@ -114,7 +56,7 @@ tur_check(int fd, unsigned int timeout, short *msgid) * SCSI-3 arrays might return * reservation conflict on TUR */ - *msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; return PATH_UP; } if (io_hdr.info & SG_INFO_OK_MASK) { @@ -159,14 +101,14 @@ tur_check(int fd, unsigned int timeout, short *msgid) * LOGICAL UNIT NOT ACCESSIBLE, * TARGET PORT IN STANDBY STATE */ - *msgid = CHECKER_MSGID_GHOST; + rdata->msgid = CHECKER_MSGID_GHOST; return PATH_GHOST; } else if (asc == 0x04 && ascq == 0x0a) { /* * LOGICAL UNIT NOT ACCESSIBLE, * ASYMMETRIC ACCESS STATE TRANSITION */ - *msgid = MSG_TUR_TRANSITIONING; + rdata->msgid = MSG_TUR_TRANSITIONING; return PATH_PENDING; } } else if (key == 0x5) { @@ -176,178 +118,13 @@ tur_check(int fd, unsigned int timeout, short *msgid) * LUN NOT SUPPORTED: unmapped at target. * Signals pp->disconnected, becomes PATH_DOWN. */ - *msgid = CHECKER_MSGID_DISCONNECTED; + rdata->msgid = CHECKER_MSGID_DISCONNECTED; return PATH_DISCONNECTED; } } - *msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; return PATH_DOWN; } - *msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; return PATH_UP; } - -/* - * Test code for "zombie tur thread" handling. - * Compile e.g. with CFLAGS=-DTUR_TEST_MAJOR=8 - * Additional parameters can be configure with the macros below. - * - * Everty nth started TUR thread will hang in non-cancellable state - * for given number of seconds, for device given by major/minor. - */ -#ifdef TUR_TEST_MAJOR - -#ifndef TUR_TEST_MINOR -#define TUR_TEST_MINOR 0 -#endif -#ifndef TUR_SLEEP_INTERVAL -#define TUR_SLEEP_INTERVAL 3 -#endif -#ifndef TUR_SLEEP_SECS -#define TUR_SLEEP_SECS 60 -#endif - -static void tur_deep_sleep(const struct tur_data *tdata) -{ - static int sleep_cnt; - const struct timespec ts = { .tv_sec = TUR_SLEEP_SECS, .tv_nsec = 0 }; - int oldstate; - - if (tdata->devt != makedev(TUR_TEST_MAJOR, TUR_TEST_MINOR) || - ++sleep_cnt % TUR_SLEEP_INTERVAL == 0) - return; - - condlog(3, "tur thread going to sleep for %ld seconds", ts.tv_sec); - if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0) - condlog(0, "pthread_setcancelstate: %m"); - if (nanosleep(&ts, NULL) != 0) - condlog(0, "nanosleep: %m"); - condlog(3, "tur zombie thread woke up"); - if (pthread_setcancelstate(oldstate, NULL) != 0) - condlog(0, "pthread_setcancelstate (2): %m"); - pthread_testcancel(); -} -#else -#define tur_deep_sleep(x) do {} while (0) -#endif /* TUR_TEST_MAJOR */ - -void runner_callback(void *arg) -{ - struct tur_data *tdata = arg; - int state; - - condlog(4, "%d:%d : tur checker starting up", major(tdata->devt), - minor(tdata->devt)); - - tur_deep_sleep(tdata); - state = tur_check(tdata->fd, tdata->timeout, &tdata->msgid); - tdata->state = state; - pthread_testcancel(); - condlog(4, "%d:%d : tur checker finished, state %s", major(tdata->devt), - minor(tdata->devt), checker_state_name(state)); -} - -static int check_runner_state(struct tur_checker_context *tcc) -{ - struct runner_context *rtx = tcc->rtx; - int rc; - - rc = check_runner(rtx, &tcc->tdata, sizeof(tcc->tdata)); - switch (rc) { - case RUNNER_DEAD: - tcc->tdata.state = PATH_TIMEOUT; - tcc->tdata.msgid = MSG_TUR_TIMEOUT; - /* fallthrough */ - case RUNNER_DONE: - release_runner(tcc->rtx); - tcc->rtx = NULL; - tcc->last_runner_state = rc; - tcc->nr_timeouts = 0; - condlog(rc == RUNNER_DONE ? 4 : 3, - "%d:%d : tur checker finished, state %s, runner state %s", - major(tcc->tdata.devt), minor(tcc->tdata.devt), - checker_state_name(tcc->tdata.state), - runner_state_name(rc)); - break; - case RUNNER_CANCELLED: - tcc->last_runner_state = rc; - tcc->tdata.state = PATH_TIMEOUT; - tcc->tdata.msgid = MSG_TUR_TIMEOUT; - if (tcc->nr_timeouts < MAX_NR_TIMEOUTS) { - condlog(3, "%d:%d : tur checker timed out, releasing it", - major(tcc->tdata.devt), minor(tcc->tdata.devt)); - tcc->nr_timeouts++; - release_runner(tcc->rtx); - tcc->rtx = NULL; - } else if (tcc->nr_timeouts == MAX_NR_TIMEOUTS) { - tcc->nr_timeouts++; - condlog(3, "%d:%d : tur checker timed out, waiting for it", - major(tcc->tdata.devt), minor(tcc->tdata.devt)); - } - break; - default: - condlog(4, "%d:%d : tur checker still running", - major(tcc->tdata.devt), minor(tcc->tdata.devt)); - tcc->tdata.msgid = MSG_TUR_RUNNING; - break; - } - return rc; -} - -bool libcheck_need_wait(struct checker *c) -{ - struct tur_checker_context *ct = c->context; - - return ct && ct->rtx; -} - -int libcheck_pending(struct checker *c) -{ - struct tur_checker_context *ct = c->context; - /* The if path checker isn't running, just return the exiting value. */ - if (!ct || !ct->rtx) - return c->path_state; - - /* This may nullify ct->rtx */ - check_runner_state(ct); - c->msgid = ct->tdata.msgid; - return ct->tdata.state; -} - -int libcheck_check(struct checker * c) -{ - struct tur_checker_context *ct = c->context; - - if (!ct) - return PATH_UNCHECKED; - - if (checker_is_sync(c)) - return tur_check(c->fd, c->timeout, &c->msgid); - - /* Handle the case that the checker just completed */ - if (ct->rtx) { - check_runner_state(ct); - c->msgid = ct->tdata.msgid; - return ct->tdata.state; - } - - /* create new checker thread */ - ct->tdata.fd = c->fd; - ct->tdata.timeout = c->timeout; - - ct->tdata.state = PATH_PENDING; - ct->tdata.msgid = MSG_TUR_RUNNING; - condlog(3, "%d:%d : starting checker", major(ct->tdata.devt), - minor(ct->tdata.devt)); - ct->rtx = get_runner(runner_callback, &ct->tdata, sizeof(ct->tdata), - 1000000 * c->timeout); - - if (ct->rtx) { - c->msgid = ct->tdata.msgid; - return ct->tdata.state; - } else { - condlog(3, "%d:%d : failed to start tur thread, using sync mode", - major(ct->tdata.devt), minor(ct->tdata.devt)); - return tur_check(c->fd, c->timeout, &c->msgid); - } -} From 7bb5441034680c082ef7a9397e9494b89d39085e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 20:00:58 +0200 Subject: [PATCH 031/102] libmultipath: convert cciss_tur checker to async checker Make the cciss_tur checker use the async checker framework. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers/cciss_tur.c | 41 ++++++++----------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/libmultipath/checkers/cciss_tur.c b/libmultipath/checkers/cciss_tur.c index c89fe5e78..b859138a2 100644 --- a/libmultipath/checkers/cciss_tur.c +++ b/libmultipath/checkers/cciss_tur.c @@ -24,47 +24,33 @@ #include #include "checkers.h" +#include "async_checker.h" #include "cciss.h" #define TUR_CMD_LEN 6 #define HEAVY_CHECK_COUNT 10 -struct cciss_tur_checker_context { - void * dummy; -}; - -int libcheck_init (__attribute__((unused)) struct checker * c) -{ - return 0; -} - -void libcheck_free (__attribute__((unused)) struct checker * c) -{ - return; -} - -int libcheck_check(struct checker * c) +int libcheck_async_func(struct runner_data *rdata) { int rc; int ret; unsigned int lun = 0; - struct cciss_tur_checker_context * ctxt = NULL; LogvolInfo_struct lvi; // logical "volume" info IOCTL_Command_struct cic; // cciss ioctl command - if ((c->fd) < 0) { - c->msgid = CHECKER_MSGID_NO_FD; + if ((rdata->fd) < 0) { + rdata->msgid = CHECKER_MSGID_NO_FD; ret = -1; goto out; } - rc = ioctl(c->fd, CCISS_GETLUNINFO, &lvi); + rc = ioctl(rdata->fd, CCISS_GETLUNINFO, &lvi); if ( rc != 0) { perror("Error: "); fprintf(stderr, "cciss TUR failed in CCISS_GETLUNINFO: %s\n", strerror(errno)); - c->msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; ret = PATH_DOWN; goto out; } else { @@ -87,31 +73,24 @@ int libcheck_check(struct checker * c) cic.Request.CDB[4] = 0; cic.Request.CDB[5] = 0; - rc = ioctl(c->fd, CCISS_PASSTHRU, &cic); + rc = ioctl(rdata->fd, CCISS_PASSTHRU, &cic); if (rc < 0) { fprintf(stderr, "cciss TUR failed: %s\n", strerror(errno)); - c->msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; ret = PATH_DOWN; goto out; } if ((cic.error_info.CommandStatus | cic.error_info.ScsiStatus )) { - c->msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; ret = PATH_DOWN; goto out; } - c->msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; ret = PATH_UP; out: - /* - * caller told us he doesn't want to keep the context : - * free it - */ - if (!c->context) - free(ctxt); - return(ret); } From f906460a21aa74063a8cc415f08f1012ae6c6c20 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 20:34:55 +0200 Subject: [PATCH 032/102] libmultipath: convert readsector0 checker to async checker Make the readsector0 checker use the async checker framework. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers/readsector0.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/libmultipath/checkers/readsector0.c b/libmultipath/checkers/readsector0.c index b041f1109..fcc4ad184 100644 --- a/libmultipath/checkers/readsector0.c +++ b/libmultipath/checkers/readsector0.c @@ -1,41 +1,27 @@ /* * Copyright (c) 2004, 2005 Christophe Varoqui */ -#include - +#include #include "checkers.h" #include "libsg.h" +#include "async_checker.h" -struct readsector0_checker_context { - void * dummy; -}; - -int libcheck_init (__attribute__((unused)) struct checker * c) -{ - return 0; -} - -void libcheck_free (__attribute__((unused)) struct checker * c) -{ - return; -} - -int libcheck_check (struct checker * c) +int libcheck_async_func(struct runner_data *rdata) { unsigned char buf[4096]; unsigned char sbuf[SENSE_BUFF_LEN]; int ret; - ret = sg_read(c->fd, &buf[0], 4096, &sbuf[0], - SENSE_BUFF_LEN, c->timeout); + ret = sg_read(rdata->fd, &buf[0], 4096, &sbuf[0], SENSE_BUFF_LEN, + rdata->timeout); switch (ret) { case PATH_DOWN: - c->msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; break; case PATH_UP: - c->msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; break; default: break; From 3c79c159a3998d6454472c5fc3cdc2b0f1abf613 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 20:34:55 +0200 Subject: [PATCH 033/102] libmultipath: convert hp_sw checker to async checker Make the hp_sw checker use the async checker framework. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers/hp_sw.c | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/libmultipath/checkers/hp_sw.c b/libmultipath/checkers/hp_sw.c index 1ab7909a6..770221fbd 100644 --- a/libmultipath/checkers/hp_sw.c +++ b/libmultipath/checkers/hp_sw.c @@ -1,8 +1,6 @@ /* * Copyright (c) 2005 Christophe Varoqui */ -#include -#include #include #include #include @@ -12,7 +10,7 @@ #include #include "checkers.h" - +#include "async_checker.h" #include "sg_include.h" #include "unaligned.h" @@ -28,20 +26,6 @@ #define MX_ALLOC_LEN 255 #define HEAVY_CHECK_COUNT 10 -struct sw_checker_context { - void * dummy; -}; - -int libcheck_init (__attribute__((unused)) struct checker * c) -{ - return 0; -} - -void libcheck_free (__attribute__((unused)) struct checker * c) -{ - return; -} - static int do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, void *resp, int mx_resp_len, unsigned int timeout) @@ -127,24 +111,24 @@ do_tur (int fd, unsigned int timeout) return 0; } -int libcheck_check(struct checker * c) +int libcheck_async_func(struct runner_data *rdata) { char buff[MX_ALLOC_LEN]; - int ret = do_inq(c->fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, c->timeout); + int ret = do_inq(rdata->fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, rdata->timeout); if (ret == PATH_WILD) { - c->msgid = CHECKER_MSGID_UNSUPPORTED; + rdata->msgid = CHECKER_MSGID_UNSUPPORTED; return ret; } if (ret != PATH_UP) { - c->msgid = CHECKER_MSGID_DOWN; + rdata->msgid = CHECKER_MSGID_DOWN; return ret; }; - if (do_tur(c->fd, c->timeout)) { - c->msgid = CHECKER_MSGID_GHOST; + if (do_tur(rdata->fd, rdata->timeout)) { + rdata->msgid = CHECKER_MSGID_GHOST; return PATH_GHOST; } - c->msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; return PATH_UP; } From 90c31d3b5f618d018f9c32c1f640923d36ad9ba4 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 5 May 2026 16:25:39 +0200 Subject: [PATCH 034/102] libmultipath: async_checker: add context_size and init symbols Add two symbols that can be read from checker .so files with dlsym(): "libcheck_async_context_size" for defining the size of a checker-private context, and "libcheck_async_init" for initializing this context. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/async_checker.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/libmultipath/async_checker.c b/libmultipath/async_checker.c index 02fe7657c..7a7bfc1a0 100644 --- a/libmultipath/async_checker.c +++ b/libmultipath/async_checker.c @@ -6,35 +6,62 @@ #include #include #include +#include #include "async_checker.h" #include "checkers.h" #include "debug.h" #include "runner.h" #define MAX_NR_TIMEOUTS 1 +typedef int (*async_init_func)(struct runner_data *); struct async_checker_context { int last_runner_state; unsigned int nr_timeouts; struct runner_context *rtx; + async_init_func async_init; + int async_context_size; struct runner_data rdata; }; -#define rdata_size(acc) (sizeof(acc->rdata)) +#define rdata_size(acc) (sizeof(acc->rdata) + acc->async_context_size) int async_check_init(struct checker *c) { struct async_checker_context *acc; struct stat sb; - - acc = calloc(1, sizeof(*acc)); + const int *p_ctx_size; + int ctx_size; + char *errstr __attribute__((unused)); + + p_ctx_size = dlsym(c->cls->handle, "libcheck_async_context_size"); + errstr = dlerror(); + if (p_ctx_size) + ctx_size = *p_ctx_size; + else + ctx_size = 0; + + if (ctx_size < 0 || ctx_size > CHECKER_MAX_CONTEXT_SIZE) { + condlog(0, "%s: invalid context size %d", __func__, ctx_size); + return -1; + } + condlog(3, "%s: extra context size: %d", __func__, ctx_size); + acc = calloc(1, sizeof(*acc) + ctx_size); if (!acc) return -1; + + acc->async_context_size = ctx_size; + acc->async_init = (int (*)(struct runner_data *)) + dlsym(c->cls->handle, "libcheck_async_init"); + errstr = dlerror(); + acc->rdata.state = PATH_UNCHECKED; acc->rdata.fd = -1; if (fstat(c->fd, &sb) == 0) acc->rdata.devt = sb.st_rdev; c->context = acc; + if (acc->async_init) + return acc->async_init(&acc->rdata); return 0; } From c155083b630b04cb6d7256000ccf7810a5360dec Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 19:58:06 +0200 Subject: [PATCH 035/102] libmultipath: convert RDAC checker to async checker Make the RDAC checker use the async checker framework. The function libcheck_init(), which was used to set the TAS bit, is converted to libcheck_async_init(). Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers/rdac.c | 38 +++++++++++++----------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/libmultipath/checkers/rdac.c b/libmultipath/checkers/rdac.c index 87b8872a1..a0f853e5f 100644 --- a/libmultipath/checkers/rdac.c +++ b/libmultipath/checkers/rdac.c @@ -1,8 +1,6 @@ /* * Copyright (c) 2005 Christophe Varoqui */ -#include -#include #include #include #include @@ -15,6 +13,7 @@ #include "debug.h" #include "sg_include.h" +#include "async_checker.h" #define INQUIRY_CMDLEN 6 #define INQUIRY_CMD 0x12 @@ -55,11 +54,7 @@ struct control_mode_page { unsigned char dontcare1[6]; }; -struct rdac_checker_context { - void * dummy; -}; - -int libcheck_init (struct checker * c) +int libcheck_async_init(struct runner_data *rdata) { unsigned char cmd[MODE_SEN_SEL_CMDLEN]; unsigned char sense_b[SENSE_BUFF_LEN]; @@ -85,9 +80,9 @@ int libcheck_init (struct checker * c) io_hdr.dxferp = ¤t; io_hdr.cmdp = cmd; io_hdr.sbp = sense_b; - io_hdr.timeout = c->timeout * 1000; + io_hdr.timeout = rdata->timeout * 1000; - if (ioctl(c->fd, SG_IO, &io_hdr) < 0) + if (ioctl(rdata->fd, SG_IO, &io_hdr) < 0) goto out; /* check the TAS bit to see if it is already set */ @@ -101,7 +96,7 @@ int libcheck_init (struct checker * c) io_hdr.dxferp = &changeable; memset(&changeable, 0, sizeof(struct control_mode_page)); - if (ioctl(c->fd, SG_IO, &io_hdr) < 0) + if (ioctl(rdata->fd, SG_IO, &io_hdr) < 0) goto out; /* if TAS bit is not settable exit */ @@ -122,7 +117,7 @@ int libcheck_init (struct checker * c) io_hdr.dxfer_direction = SG_DXFER_TO_DEV; io_hdr.dxferp = ¤t; - if (ioctl(c->fd, SG_IO, &io_hdr) < 0) + if (ioctl(rdata->fd, SG_IO, &io_hdr) < 0) goto out; /* Success */ @@ -133,11 +128,6 @@ int libcheck_init (struct checker * c) return 0; } -void libcheck_free(__attribute__((unused)) struct checker *c) -{ - return; -} - static int do_inq(int sg_fd, unsigned int pg_op, void *resp, int mx_resp_len, unsigned int timeout) @@ -288,15 +278,15 @@ checker_msg_string(const struct volume_access_inq *inq) } } -int libcheck_check(struct checker * c) +int libcheck_async_func(struct runner_data *rdata) { struct volume_access_inq inq; int ret, inqfail; inqfail = 0; memset(&inq, 0, sizeof(struct volume_access_inq)); - ret = do_inq(c->fd, 0xC9, &inq, sizeof(struct volume_access_inq), - c->timeout); + ret = do_inq(rdata->fd, 0xC9, &inq, sizeof(struct volume_access_inq), + rdata->timeout); if (ret != PATH_UP) { inqfail = 1; goto done; @@ -340,17 +330,17 @@ int libcheck_check(struct checker * c) done: switch (ret) { case PATH_WILD: - c->msgid = CHECKER_MSGID_UNSUPPORTED; + rdata->msgid = CHECKER_MSGID_UNSUPPORTED; break; case PATH_DOWN: - c->msgid = (inqfail ? RDAC_MSGID_INQUIRY_FAILED : - checker_msg_string(&inq)); + rdata->msgid = (inqfail ? RDAC_MSGID_INQUIRY_FAILED + : checker_msg_string(&inq)); break; case PATH_UP: - c->msgid = CHECKER_MSGID_UP; + rdata->msgid = CHECKER_MSGID_UP; break; case PATH_GHOST: - c->msgid = CHECKER_MSGID_GHOST; + rdata->msgid = CHECKER_MSGID_GHOST; break; } From 92eca20898299681b4cf31f2bd5a653e33bd3ad5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 24 Apr 2026 17:29:07 +0200 Subject: [PATCH 036/102] libmultipath: checkers: rework mpcontext passing The emc_clariion path checker requires an "mpcontext" that is shared between the checkers for all paths of a given multipath map. This context is currently represented in struct multipath as a void *, the pointer to which is passed to the checker. The checker allocates the actual memory in the libcheck_mp_init() function, and changes the pointer in struct multipath. This is a dangerous layering violation as the checker operates on memory it doesn't own, and not type-safe at all. This patch changes the mpcontext handling as follows: The void * pointer is replaced by a dedicated union checker_mpcontext. It only contains a long integer element, which is sufficient to store the information needed by emc_clariion. Instead of initializing this value in mp_init, the address of the union is passed to libcheck_check() and libcheck_pending() if the path that is being checked has an associated struct multipath. Passing a union pointer improves compile-time type safety. In order to check whether the value is initialized, instead of testing for a NULL pointer like before, the code defines an INVALID_MPCONTEXT value. This value must obviously be chosen such that it doesn't represent a valid context value, which is the case for emc_clariion. For a synchronous checker (like emc_clariion), libcheck_check() is allowed to write to this memory, because at the time of the call we know that pp->mpp is valid. The same holds for libcheck_pending(). This change requires checker_check() and checker_get_state() to be passed a "struct path *" rather than a "struct checker *". The layer separation happens in these functions now. The async_checker code also gets an implementation of mpcontext handling, in preparation of converting emc_clariion to an async checker. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/async_checker.c | 32 +++++++++++------- libmultipath/async_checker.h | 5 +-- libmultipath/checkers.c | 46 +++++++++++--------------- libmultipath/checkers.h | 44 ++++++++++++++++--------- libmultipath/checkers/directio.c | 6 ++-- libmultipath/checkers/emc_clariion.c | 49 ++++++++++------------------ libmultipath/discovery.c | 8 ++--- libmultipath/libmultipath.version | 2 +- libmultipath/structs.c | 3 +- libmultipath/structs.h | 3 +- tests/directio.c | 2 +- 11 files changed, 100 insertions(+), 100 deletions(-) diff --git a/libmultipath/async_checker.c b/libmultipath/async_checker.c index 7a7bfc1a0..0023c73dc 100644 --- a/libmultipath/async_checker.c +++ b/libmultipath/async_checker.c @@ -7,8 +7,8 @@ #include #include #include -#include "async_checker.h" #include "checkers.h" +#include "async_checker.h" #include "debug.h" #include "runner.h" @@ -59,6 +59,7 @@ int async_check_init(struct checker *c) acc->rdata.fd = -1; if (fstat(c->fd, &sb) == 0) acc->rdata.devt = sb.st_rdev; + SET_INVALID_MPCONTEXT(acc->rdata.mpc); c->context = acc; if (acc->async_init) return acc->async_init(&acc->rdata); @@ -147,35 +148,42 @@ bool async_check_need_wait(struct checker *c) return acc && acc->rtx; } -int async_check_pending(struct checker *c) +int async_check_pending(struct checker *c, union checker_mpcontext *mpc) { struct async_checker_context *acc = c->context; + int rc; /* The if path checker isn't running, just return the exiting value. */ if (!acc || !acc->rtx) return c->path_state; - /* This may nullify ct->rtx */ - check_runner_state(acc); + /* This may nullify acc->rtx */ + rc = check_runner_state(acc); c->msgid = acc->rdata.msgid; + if (rc == RUNNER_DONE && mpc && !IS_INVALID_MPCONTEXT(acc->rdata.mpc)) + *mpc = acc->rdata.mpc; return acc->rdata.state; } -int async_check_check(struct checker *c) +int async_check_check(struct checker *c, union checker_mpcontext *mpc) { struct async_checker_context *acc = c->context; if (!acc) return PATH_UNCHECKED; - if (checker_is_sync(c)) - return acc->rdata.afunc(&acc->rdata); + if (mpc && !IS_INVALID_MPCONTEXT(*mpc)) + acc->rdata.mpc = *mpc; - /* Handle the case that the checker just completed */ - if (acc->rtx) { - check_runner_state(acc); - c->msgid = acc->rdata.msgid; - return acc->rdata.state; + if (checker_is_sync(c)) { + int rc = acc->rdata.afunc(&acc->rdata); + + if (mpc && !IS_INVALID_MPCONTEXT(acc->rdata.mpc)) + *mpc = acc->rdata.mpc; + return rc; } + /* Handle the case that the checker just completed */ + if (acc->rtx) + return async_check_pending(c, mpc); /* create new checker thread */ acc->rdata.fd = c->fd; diff --git a/libmultipath/async_checker.h b/libmultipath/async_checker.h index fc9632da9..d7d02e8e6 100644 --- a/libmultipath/async_checker.h +++ b/libmultipath/async_checker.h @@ -14,14 +14,15 @@ struct runner_data { unsigned int timeout; int state; short msgid; + union checker_mpcontext mpc; char __attribute__((aligned(sizeof(void *)))) checker_ctx[]; }; int async_check_init(struct checker *c); void async_check_free(struct checker *c); bool async_check_need_wait(struct checker *c); -int async_check_pending(struct checker *c); -int async_check_check(struct checker *c); +int async_check_pending(struct checker *c, union checker_mpcontext *mpctx); +int async_check_check(struct checker *c, union checker_mpcontext *mpctx); #define CHECKER_MAX_CONTEXT_SIZE 1024 diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 2f20b250d..12d928a16 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -12,6 +12,7 @@ #include "async_checker.h" #include "vector.h" #include "util.h" +#include "structs.h" static const char * const checker_dir = MULTIPATH_DIR; @@ -164,7 +165,8 @@ static struct checker_class *add_checker_class(const char *name) if (c->async_func) return add_async_checker_class(c); - c->check = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_check"); + c->check = (int (*)(struct checker *, union checker_mpcontext *)) + dlsym(c->handle, "libcheck_check"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); @@ -178,9 +180,9 @@ static struct checker_class *add_checker_class(const char *name) if (!c->init) goto out; - c->mp_init = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_mp_init"); c->reset = (void (*)(void))dlsym(c->handle, "libcheck_reset"); - c->pending = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_pending"); + c->pending = (int (*)(struct checker *, union checker_mpcontext *)) + dlsym(c->handle, "libcheck_pending"); c->need_wait = (bool (*)(struct checker *)) dlsym(c->handle, "libcheck_need_wait"); /* These 5 functions can be NULL. call dlerror() to clear out any * error string */ @@ -239,31 +241,12 @@ void checker_disable (struct checker * c) c->path_state = PATH_UNCHECKED; } -int checker_init (struct checker * c, void ** mpctxt_addr) +int checker_init(struct checker *c) { if (!c || !c->cls) return 1; - c->mpcontext = mpctxt_addr; if (c->cls->init && c->cls->init(c) != 0) return 1; - if (mpctxt_addr && *mpctxt_addr == NULL && c->cls->mp_init && - c->cls->mp_init(c) != 0) /* continue even if mp_init fails */ - c->mpcontext = NULL; - return 0; -} - -int checker_mp_init(struct checker * c, void ** mpctxt_addr) -{ - if (!c || !c->cls) - return 1; - if (c->mpcontext || !mpctxt_addr) - return 0; - c->mpcontext = mpctxt_addr; - if (*mpctxt_addr == NULL && c->cls->mp_init && - c->cls->mp_init(c) != 0) { - c->mpcontext = NULL; - return 1; - } return 0; } @@ -287,13 +270,17 @@ void checker_put (struct checker * dst) put_shared_ptr(src); } -int checker_get_state(struct checker *c) +int checker_get_state(struct path *pp) { + struct checker *c = &pp->checker; + union checker_mpcontext *mpc; + if (!c || !c->cls) return PATH_UNCHECKED; if (c->path_state != PATH_PENDING || !c->cls->pending) return c->path_state; - c->path_state = c->cls->pending(c); + mpc = pp->mpp ? &pp->mpp->mpcontext : NULL; + c->path_state = c->cls->pending(c, mpc); return c->path_state; } @@ -305,8 +292,10 @@ bool checker_need_wait(struct checker *c) return c->cls->need_wait(c); } -void checker_check (struct checker * c, int path_state) +void checker_check(struct path *pp, int path_state) { + struct checker *c = &pp->checker; + if (!c) return; @@ -320,7 +309,10 @@ void checker_check (struct checker * c, int path_state) c->msgid = CHECKER_MSGID_NO_FD; c->path_state = PATH_WILD; } else { - c->path_state = c->cls->check(c); + union checker_mpcontext *mpc; + + mpc = pp->mpp ? &pp->mpp->mpcontext : NULL; + c->path_state = c->cls->check(c, mpc); } } diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 4846ea95b..556613ac4 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -131,6 +131,23 @@ enum { CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */ }; +/* + * Data shared between checkers for a struct multipath. + * Should be large enough to hold the data for all checker types. + * Currently only used by emc_clariion. + */ +union checker_mpcontext { + long long_val; +}; + +#define INVALID_MPCONTEXT__ -1L +#define IS_INVALID_MPCONTEXT(mpc) ((mpc).long_val == INVALID_MPCONTEXT__) +#define SET_INVALID_MPCONTEXT(mpc) \ + do { \ + (mpc).long_val = INVALID_MPCONTEXT__; \ + } while (0) + +struct path; struct checker; struct runner_data; struct checker_class { @@ -138,12 +155,12 @@ struct checker_class { void *handle; int sync; char name[CHECKER_NAME_LEN]; - int (*check)(struct checker *); - int (*init)(struct checker *); /* to allocate the context */ - int (*mp_init)(struct checker *); /* to allocate the mpcontext */ - void (*free)(struct checker *); /* to free the context */ - void (*reset)(void); /* to reset the global variables */ - int (*pending)(struct checker *); /* to recheck pending paths */ + int (*check)(struct checker *, union checker_mpcontext *); + int (*init)(struct checker *); /* to allocate the context */ + void (*free)(struct checker *); /* to free the context */ + void (*reset)(void); /* to reset the global variables */ + int (*pending)(struct checker *, + union checker_mpcontext *); /* to recheck pending paths */ bool (*need_wait)(struct checker *); /* checker needs waiting for */ int (*async_func)(struct runner_data *); /* callback for async_checker */ const char **msgtable; @@ -157,9 +174,7 @@ struct checker { int disable; int path_state; short msgid; /* checker-internal extra status */ - void * context; /* store for persistent data */ - void ** mpcontext; /* store for persistent data shared - multipath-wide. */ + void *context; /* store for persistent data */ }; static inline int checker_selected(const struct checker *c) @@ -170,8 +185,7 @@ static inline int checker_selected(const struct checker *c) const char *checker_state_name(int); int init_checkers(void); void cleanup_checkers (void); -int checker_init (struct checker *, void **); -int checker_mp_init(struct checker *, void **); +int checker_init(struct checker *); void checker_clear (struct checker *); void checker_put (struct checker *); void checker_reset (struct checker *); @@ -180,9 +194,9 @@ void checker_set_async (struct checker *); void checker_set_fd (struct checker *, int); void checker_enable (struct checker *); void checker_disable(struct checker *); -int checker_get_state(struct checker *c); +int checker_get_state(struct path *pp); bool checker_need_wait(struct checker *c); -void checker_check (struct checker *, int); +void checker_check(struct path *, int); int checker_is_sync(const struct checker *); const char *checker_name (const struct checker *); void reset_checker_classes(void); @@ -195,12 +209,12 @@ void checker_clear_message (struct checker *c); void checker_get(struct checker *, const char *); /* Prototypes for symbols exported by path checker dynamic libraries (.so) */ -int libcheck_check(struct checker *); +int libcheck_check(struct checker *, union checker_mpcontext *); int libcheck_init(struct checker *); void libcheck_free(struct checker *); void libcheck_reset(void); int libcheck_mp_init(struct checker *); -int libcheck_pending(struct checker *c); +int libcheck_pending(struct checker *c, union checker_mpcontext *); bool libcheck_need_wait(struct checker *c); /* diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index a7422a870..82b204192 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -405,7 +405,8 @@ bool libcheck_need_wait(struct checker *c) !ct->checked_state); } -int libcheck_pending(struct checker *c) +int libcheck_pending(struct checker *c, + union checker_mpcontext *mpc __attribute__((unused))) { int rc; struct io_event event; @@ -441,7 +442,8 @@ int libcheck_pending(struct checker *c) return rc; } -int libcheck_check (struct checker * c) +int libcheck_check(struct checker *c, + union checker_mpcontext *mpctx __attribute__((unused))) { int ret; struct directio_context * ct = (struct directio_context *)c->context; diff --git a/libmultipath/checkers/emc_clariion.c b/libmultipath/checkers/emc_clariion.c index bf0baabc0..24a386c4d 100644 --- a/libmultipath/checkers/emc_clariion.c +++ b/libmultipath/checkers/emc_clariion.c @@ -15,6 +15,7 @@ #include "libsg.h" #include "checkers.h" #include "debug.h" +#include "util.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 @@ -32,18 +33,20 @@ * simple read test would return 02/04/03 instead * of 05/25/01 sensekey/ASC/ASCQ data. */ -#define IS_INACTIVE_SNAP(c) (c->mpcontext ? \ - ((struct emc_clariion_checker_LU_context *) \ - (*c->mpcontext))->inactive_snap \ - : 0) +#define IS_INACTIVE_SNAP(mpctx) \ + (!mpctx || IS_INVALID_MPCONTEXT(*mpctx) ? 0 : mpctx->long_val) -#define SET_INACTIVE_SNAP(c) if (c->mpcontext) \ - ((struct emc_clariion_checker_LU_context *)\ - (*c->mpcontext))->inactive_snap = 1 +#define SET_INACTIVE_SNAP(mpctx) \ + do { \ + if (mpctx) \ + mpctx->long_val = 1; \ + } while (0); -#define CLR_INACTIVE_SNAP(c) if (c->mpcontext) \ - ((struct emc_clariion_checker_LU_context *)\ - (*c->mpcontext))->inactive_snap = 0 +#define CLR_INACTIVE_SNAP(mpctx) \ + do { \ + if (mpctx) \ + mpctx->long_val = 0; \ + } while (0); enum { MSG_CLARIION_QUERY_FAILED = CHECKER_FIRST_MSGID, @@ -109,28 +112,12 @@ int libcheck_init (struct checker * c) return 0; } -int libcheck_mp_init (struct checker * c) -{ - /* - * Allocate and initialize the multi-path global context. - */ - if (c->mpcontext && *c->mpcontext == NULL) { - void * mpctxt = malloc(sizeof(int)); - if (!mpctxt) - return 1; - *c->mpcontext = mpctxt; - CLR_INACTIVE_SNAP(c); - } - - return 0; -} - -void libcheck_free (struct checker * c) +void libcheck_free(struct checker *c) { free(c->context); } -int libcheck_check (struct checker * c) +int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) { unsigned char sense_buffer[128] = { 0, }; unsigned char sb[SENSE_BUFF_LEN] = { 0, }, *sbb; @@ -282,7 +269,7 @@ int libcheck_check (struct checker * c) * passive paths which will return * 02/04/03 not 05/25/01 on read. */ - SET_INACTIVE_SNAP(c); + SET_INACTIVE_SNAP(mpctx); condlog(3, "emc_clariion_checker: Active " "path to inactive snapshot WWN %s.", wwnstr); @@ -300,10 +287,10 @@ int libcheck_check (struct checker * c) * snapshot LUs if it was in this list since the * snapshot is no longer inactive. */ - CLR_INACTIVE_SNAP(c); + CLR_INACTIVE_SNAP(mpctx); } } else { - if (IS_INACTIVE_SNAP(c)) { + if (IS_INACTIVE_SNAP(mpctx)) { hexadecimal_to_ascii(ct->wwn, wwnstr); condlog(3, "emc_clariion_checker: Passive " "path to inactive snapshot WWN %s.", diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index f59fa6d4a..ceca9fd38 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -1998,20 +1998,18 @@ start_checker (struct path * pp, struct config *conf, int daemon, int oldstate) return -1; } checker_set_fd(c, pp->fd); - if (checker_init(c, pp->mpp?&pp->mpp->mpcontext:NULL)) { + if (checker_init(c)) { checker_clear(c); condlog(3, "%s: checker init failed", pp->dev); return -1; } } - if (pp->mpp && !c->mpcontext) - checker_mp_init(c, &pp->mpp->mpcontext); checker_clear_message(c); if (conf->force_sync == 0) checker_set_async(c); else checker_set_sync(c); - checker_check(c, oldstate); + checker_check(pp, oldstate); return 0; } @@ -2021,7 +2019,7 @@ get_state (struct path * pp) struct checker * c = &pp->checker; int state, lvl; - state = checker_get_state(c); + state = checker_get_state(pp); lvl = state == pp->oldstate || state == PATH_PENDING ? 4 : 3; condlog(lvl, "%s: %s state = %s", pp->dev, diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index c091ce268..f631f4530 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMULTIPATH_33.0.0 { +LIBMULTIPATH_34.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; diff --git a/libmultipath/structs.c b/libmultipath/structs.c index f44131272..c627b2c0d 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -261,7 +261,7 @@ alloc_multipath (void) if (mpp) { mpp->bestpg = 1; - mpp->mpcontext = NULL; + SET_INVALID_MPCONTEXT(mpp->mpcontext); mpp->no_path_retry = NO_PATH_RETRY_UNDEF; dm_multipath_to_gen(mpp)->ops = &dm_gen_multipath_ops; } @@ -330,7 +330,6 @@ void free_multipath(struct multipath *mpp) vector_free(mpp->hwe); mpp->hwe = NULL; } - free(mpp->mpcontext); free(mpp); } diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 9adedde25..8b71aa29c 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -540,8 +540,7 @@ struct multipath { unsigned int stat_queueing_timeouts; unsigned int stat_map_failures; - /* checkers shared data */ - void * mpcontext; + union checker_mpcontext mpcontext; /* persistent management data*/ int prkey_source; diff --git a/tests/directio.c b/tests/directio.c index fa1e553a5..addb7cf26 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -227,7 +227,7 @@ void do_check_state(struct checker *c, int sync, int chk_state) void do_libcheck_pending(struct checker *c, int chk_state) { - assert_int_equal(libcheck_pending(c), chk_state); + assert_int_equal(libcheck_pending(c, NULL), chk_state); assert_int_equal(ev_off, 0); memset(mock_events, 0, sizeof(mock_events)); } From 6f7daba4e38607776596468afeaf18e790979e78 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 29 Apr 2026 21:39:55 +0200 Subject: [PATCH 037/102] libmultipath: convert emc_clariion to async_checker Convert also the emc_clariion checker. This is the only checker that uses a checker-specific context and mptcontext. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/checkers/emc_clariion.c | 79 ++++++++++++---------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/libmultipath/checkers/emc_clariion.c b/libmultipath/checkers/emc_clariion.c index 24a386c4d..5b34d68a9 100644 --- a/libmultipath/checkers/emc_clariion.c +++ b/libmultipath/checkers/emc_clariion.c @@ -1,8 +1,6 @@ /* * Copyright (c) 2004, 2005 Lars Marowsky-Bree */ -#include -#include #include #include #include @@ -14,8 +12,8 @@ #include "sg_include.h" #include "libsg.h" #include "checkers.h" +#include "async_checker.h" #include "debug.h" -#include "util.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 @@ -33,19 +31,17 @@ * simple read test would return 02/04/03 instead * of 05/25/01 sensekey/ASC/ASCQ data. */ -#define IS_INACTIVE_SNAP(mpctx) \ - (!mpctx || IS_INVALID_MPCONTEXT(*mpctx) ? 0 : mpctx->long_val) +#define IS_INACTIVE_SNAP(mpctx) \ + (IS_INVALID_MPCONTEXT(mpctx) ? 0 : mpctx.long_val) #define SET_INACTIVE_SNAP(mpctx) \ do { \ - if (mpctx) \ - mpctx->long_val = 1; \ + mpctx.long_val = 1; \ } while (0); #define CLR_INACTIVE_SNAP(mpctx) \ do { \ - if (mpctx) \ - mpctx->long_val = 0; \ + mpctx.long_val = 0; \ } while (0); enum { @@ -86,7 +82,9 @@ struct emc_clariion_checker_LU_context { int inactive_snap; }; -void hexadecimal_to_ascii(char * wwn, char *wwnstr) +int libcheck_async_context_size = sizeof(struct emc_clariion_checker_path_context); + +static void hexadecimal_to_ascii(char *wwn, char *wwnstr) { int i,j, nbl; @@ -99,33 +97,22 @@ void hexadecimal_to_ascii(char * wwn, char *wwnstr) wwnstr[32]=0; } -int libcheck_init (struct checker * c) +int libcheck_async_init(struct runner_data *rdata) { - /* - * Allocate and initialize the path specific context. - */ - c->context = calloc(1, sizeof(struct emc_clariion_checker_path_context)); - if (!c->context) - return 1; - ((struct emc_clariion_checker_path_context *)c->context)->wwn_set = 0; + ((struct emc_clariion_checker_path_context *)rdata->checker_ctx)->wwn_set = 0; return 0; } -void libcheck_free(struct checker *c) -{ - free(c->context); -} - -int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) +int libcheck_async_func(struct runner_data *rdata) { unsigned char sense_buffer[128] = { 0, }; unsigned char sb[SENSE_BUFF_LEN] = { 0, }, *sbb; unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {INQUIRY_CMD, 1, 0xC0, 0, sizeof(sense_buffer), 0}; struct sg_io_hdr io_hdr; - struct emc_clariion_checker_path_context * ct = - (struct emc_clariion_checker_path_context *)c->context; + struct emc_clariion_checker_path_context *ct = + (struct emc_clariion_checker_path_context *)rdata->checker_ctx; char wwnstr[33]; int ret; int retry_emc = 5; @@ -142,14 +129,14 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; - io_hdr.timeout = c->timeout * 1000; + io_hdr.timeout = rdata->timeout * 1000; io_hdr.pack_id = 0; - if (ioctl(c->fd, SG_IO, &io_hdr) < 0) { + if (ioctl(rdata->fd, SG_IO, &io_hdr) < 0) { if (errno == ENOTTY) { - c->msgid = CHECKER_MSGID_UNSUPPORTED; + rdata->msgid = CHECKER_MSGID_UNSUPPORTED; return PATH_WILD; } - c->msgid = MSG_CLARIION_QUERY_FAILED; + rdata->msgid = MSG_CLARIION_QUERY_FAILED; return PATH_DOWN; } @@ -181,12 +168,12 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) sense_key = sbp[2] & 0xf; if (sense_key == ILLEGAL_REQUEST) { - c->msgid = CHECKER_MSGID_UNSUPPORTED; + rdata->msgid = CHECKER_MSGID_UNSUPPORTED; return PATH_WILD; } else if (sense_key != RECOVERED_ERROR) { condlog(1, "emc_clariion_checker: INQUIRY failed with sense key %02x", sense_key); - c->msgid = MSG_CLARIION_QUERY_ERROR; + rdata->msgid = MSG_CLARIION_QUERY_ERROR; return PATH_DOWN; } } @@ -195,13 +182,13 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) if (io_hdr.info & SG_INFO_OK_MASK) { condlog(1, "emc_clariion_checker: INQUIRY failed without sense, status %02x", io_hdr.status); - c->msgid = MSG_CLARIION_QUERY_ERROR; + rdata->msgid = MSG_CLARIION_QUERY_ERROR; return PATH_DOWN; } if (/* Verify the code page - right page & revision */ sense_buffer[1] != 0xc0 || sense_buffer[9] != 0x00) { - c->msgid = MSG_CLARIION_UNIT_REPORT; + rdata->msgid = MSG_CLARIION_UNIT_REPORT; return PATH_DOWN; } @@ -215,19 +202,19 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) ((sense_buffer[28] & 0x07) != 0x06)) /* Arraycommpath should be set to 1 */ || (sense_buffer[30] & 0x04) != 0x04) { - c->msgid = MSG_CLARIION_PATH_CONFIG; + rdata->msgid = MSG_CLARIION_PATH_CONFIG; return PATH_DOWN; } if ( /* LUN operations should indicate normal operations */ sense_buffer[48] != 0x00) { - c->msgid = MSG_CLARIION_PATH_NOT_AVAIL; + rdata->msgid = MSG_CLARIION_PATH_NOT_AVAIL; return PATH_SHAKY; } if ( /* LUN should at least be bound somewhere and not be LUNZ */ sense_buffer[4] == 0x00) { - c->msgid = MSG_CLARIION_LUN_UNBOUND; + rdata->msgid = MSG_CLARIION_LUN_UNBOUND; return PATH_DOWN; } @@ -238,7 +225,7 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) */ if (ct->wwn_set) { if (memcmp(ct->wwn, &sense_buffer[10], 16) != 0) { - c->msgid = MSG_CLARIION_WWN_CHANGED; + rdata->msgid = MSG_CLARIION_WWN_CHANGED; return PATH_DOWN; } } else { @@ -253,8 +240,8 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) unsigned char buf[4096]; memset(buf, 0, 4096); - ret = sg_read(c->fd, &buf[0], 4096, - sbb = &sb[0], SENSE_BUFF_LEN, c->timeout); + ret = sg_read(rdata->fd, &buf[0], 4096, sbb = &sb[0], + SENSE_BUFF_LEN, rdata->timeout); if (ret == PATH_DOWN) { hexadecimal_to_ascii(ct->wwn, wwnstr); @@ -269,7 +256,7 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) * passive paths which will return * 02/04/03 not 05/25/01 on read. */ - SET_INACTIVE_SNAP(mpctx); + SET_INACTIVE_SNAP(rdata->mpc); condlog(3, "emc_clariion_checker: Active " "path to inactive snapshot WWN %s.", wwnstr); @@ -278,26 +265,26 @@ int libcheck_check(struct checker *c, union checker_mpcontext *mpctx) "error for WWN %s. Sense data are " "0x%x/0x%x/0x%x.", wwnstr, sbb[2]&0xf, sbb[12], sbb[13]); - c->msgid = MSG_CLARIION_READ_ERROR; + rdata->msgid = MSG_CLARIION_READ_ERROR; } } else { - c->msgid = MSG_CLARIION_PASSIVE_GOOD; + rdata->msgid = MSG_CLARIION_PASSIVE_GOOD; /* * Remove the path from the set of paths to inactive * snapshot LUs if it was in this list since the * snapshot is no longer inactive. */ - CLR_INACTIVE_SNAP(mpctx); + CLR_INACTIVE_SNAP(rdata->mpc); } } else { - if (IS_INACTIVE_SNAP(mpctx)) { + if (IS_INACTIVE_SNAP(rdata->mpc)) { hexadecimal_to_ascii(ct->wwn, wwnstr); condlog(3, "emc_clariion_checker: Passive " "path to inactive snapshot WWN %s.", wwnstr); ret = PATH_DOWN; } else { - c->msgid = MSG_CLARIION_PASSIVE_GOOD; + rdata->msgid = MSG_CLARIION_PASSIVE_GOOD; ret = PATH_UP; /* not ghost */ } } From c70991550113d3a0ea97f5f1fa479842087492c9 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 7 May 2026 22:00:51 +0200 Subject: [PATCH 038/102] fixup "GitHub workflows: upload log files in case of failure" actions/upload-artifact@v4 doesn't support slashes in file names. Signed-off-by: Martin Wilck Reviewed-by: Martin Wilck --- .github/workflows/multiarch-stable.yaml | 8 ++++++-- .github/workflows/multiarch.yaml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index 1784af036..a374dfd95 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -52,6 +52,10 @@ jobs: uses: docker/setup-qemu-action@v2 with: image: tonistiigi/binfmt:latest + - name: set architecture name + run: | + __arch="${{ matrix.arch }}" + echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" - name: compile and run unit tests id: test uses: mosteo-actions/docker-run@v1 @@ -76,7 +80,7 @@ jobs: - name: upload binary archive uses: actions/upload-artifact@v4 with: - name: binaries-${{ matrix.os }}-${{ matrix.arch }} + name: binaries-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-progs.tar overwrite: true if: steps.test.outcome != 'success' @@ -86,7 +90,7 @@ jobs: - name: upload test outputs uses: actions/upload-artifact@v4 with: - name: test-${{ matrix.os }}-${{ matrix.arch }} + name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-outputs.tar overwrite: true if: steps.test.outcome != 'success' diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index fba32f151..6bd399407 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -55,6 +55,10 @@ jobs: uses: docker/setup-qemu-action@v2 with: image: tonistiigi/binfmt:latest + - name: set architecture name + run: | + __arch="${{ matrix.arch }}" + echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" - name: compile and run unit tests id: test uses: mosteo-actions/docker-run@v1 @@ -79,13 +83,13 @@ jobs: - name: upload binary archive uses: actions/upload-artifact@v4 with: - name: binaries-${{ matrix.os }}-${{ matrix.arch }} + name: binaries-${{ matrix.os }}w-${{ env.ARCH_NAME }} path: test-progs.tar if: steps.test.outcome != 'success' - name: upload test outputs uses: actions/upload-artifact@v4 with: - name: test-${{ matrix.os }}-${{ matrix.arch }} + name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-outputs.tar overwrite: true if: steps.test.outcome != 'success' From b065c9d61149b34bb8427e64cfcc564c4ec93aff Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 19 Jun 2026 11:15:10 +0200 Subject: [PATCH 039/102] multipath-tools tests: add wrapper for fstat() and fstat64() On some Debian distributions, fstat() and fstat64() are wrappers around __fxstat() and __fxstat64(), respectively. These functions take an extra argument. Create macros to make sure we substitute the correct functions with wrappers. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/wrap64.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/wrap64.h b/tests/wrap64.h index fe985a714..3ea4b0aec 100644 --- a/tests/wrap64.h +++ b/tests/wrap64.h @@ -4,6 +4,7 @@ #include /* The following include is required for LIBAIO_REDIRECT */ #include +#include #include "util.h" /* @@ -87,4 +88,29 @@ * (see https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html). */ #define wrap_will_return(x, y) will_return(x, y) + +/* + * On some glibc versions on older Debian distros (up to bullseye), fstat() + * is a wrapper around __fxstat(), which takes an additional argument. + * Also, MUSL libc redirects fstat to __fstat_time64 on some architectures. + */ +#if defined(__GLIBC__) && defined(_STAT_VER) +#define WRAP_FSTAT_NAME WRAP_NAME(__fxstat) +#define WRAP_FSTAT_FUNC(v, fd, buf) WRAP_FSTAT(v, fd, buf) +#define REAL_FSTAT_FUNC(v, fd, buf) REAL_FSTAT(v, fd, buf) +#else +#if !defined(__GLIBC__) && defined(_REDIR_TIME64) && _REDIR_TIME64 +#define WRAP_FSTAT_NAME WRAP_NAME(__fstat_time64) +#elif defined(__GLIBC__) && __GLIBC_PREREQ(2, 34) && __BITS_PER_LONG == 32 && \ + defined(_TIME_BITS) && _TIME_BITS == 64 +#define WRAP_FSTAT_NAME WRAP_NAME(__fstat64_time) +#else +#define WRAP_FSTAT_NAME WRAP_NAME(fstat) +#endif +#define WRAP_FSTAT_FUNC(v, fd, buf) WRAP_FSTAT(fd, buf) +#define REAL_FSTAT_FUNC(v, fd, buf) REAL_FSTAT(fd, buf) +#endif +#define WRAP_FSTAT CONCAT2(__wrap_, WRAP_FSTAT_NAME) +#define REAL_FSTAT CONCAT2(__real_, WRAP_FSTAT_NAME) + #endif From aeb7cc36a89788d555e6f7d2f11d37881a2529cf Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 22 Jun 2026 12:50:35 +0200 Subject: [PATCH 040/102] libmpathutil: runner: avoid segmentation violation in pthread_cancel() SISEGV has been observed in the runner test in our Gitlab CI with MUSL libc. It can happen that a thread terminates before pthread_cancel() is called, causing the error. Avoid it by waiting until the thread has actually been cancelled. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmpathutil/runner.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index 56abd03af..459af13d8 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright (c) 2026 SUSE LLC #include +#include #include #include #include @@ -47,9 +48,24 @@ static void cleanup_context(struct runner_context **prctx) return; st = uatomic_cmpxchg(&rctx->status, RUNNER_RUNNING, RUNNER_DONE); + /* + * If it finds the thread in RUNNER_RUNNING state, cancel_runner() sets + * the state to RUNNER_CANCELLED before actually cancelling it. + * If the thread terminates between these two points in time, + * pthread_cancel() may access a pthread_t for an already cleaned-up + * thread. Therefore wait here until the thread has actually been + * cancelled, after which cancel_runner() will set the state to + * RUNNER_DEAD. Whether the thread will actually see this value is + * implementation-dependent. + */ + if (st == RUNNER_CANCELLED) { + do + sched_yield(); + while (uatomic_read(&rctx->status) == RUNNER_CANCELLED); + } if (st != RUNNER_RUNNING) { uatomic_cmpxchg(&rctx->status, st, RUNNER_DEAD); - condlog(st == RUNNER_CANCELLED ? 3 : 2, + condlog(st == RUNNER_DEAD || st == RUNNER_CANCELLED ? 3 : 2, "%s: runner %p finished in state '%s'", __func__, rctx, runner_state_name(st)); } @@ -116,6 +132,8 @@ static int cancel_runner(struct runner_context *rctx) break; case RUNNER_RUNNING: pthread_cancel(rctx->thr); + assert(uatomic_cmpxchg(&rctx->status, RUNNER_CANCELLED, + RUNNER_DEAD) == RUNNER_CANCELLED); st_new = RUNNER_CANCELLED; /* fallthrough */ case RUNNER_CANCELLED: From a78777adc716a1f9b99d0cdbf0b9dbace1af4169 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 23 Jun 2026 16:20:38 +0200 Subject: [PATCH 041/102] libmpathutil: runner: pause in cleanup handler if rctx is NULL In this (more or less impossible) case, make sure that pthread_cancel() can't access invalid memory because the thread has exited unexpectedly. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmpathutil/runner.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index 459af13d8..c7adc2001 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "util.h" #include "debug.h" @@ -44,8 +45,10 @@ static void cleanup_context(struct runner_context **prctx) struct runner_context *rctx = *prctx; int st; - if (!rctx) - return; + if (!rctx) { + condlog(0, "ERROR: %s: rctx is NULL", __func__); + pause(); + } st = uatomic_cmpxchg(&rctx->status, RUNNER_RUNNING, RUNNER_DONE); /* From 49eca7e5daaeab7b6045db08f46b551553070bd6 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 10:29:44 +0200 Subject: [PATCH 042/102] libmpathutil: runner: use pthread_cleanup_push() ... instead of relying on -fexceptions and __attribute__((cleanup)). The latter causes sporadic occurences of SIGSEGV on MUSL libc. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmpathutil/runner.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index c7adc2001..694481e9d 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -40,10 +40,10 @@ struct runner_context { char __attribute__((aligned(sizeof(void *)))) data[]; }; -static void cleanup_context(struct runner_context **prctx) +static void cleanup_context(void *arg) { - struct runner_context *rctx = *prctx; int st; + struct runner_context *rctx = arg; if (!rctx) { condlog(0, "ERROR: %s: rctx is NULL", __func__); @@ -82,7 +82,9 @@ static void *runner_thread(void *arg) * The cleanup function makes sure memory is freed if the thread is * cancelled (-fexceptions). */ - struct runner_context *rctx __attribute__((cleanup(cleanup_context))) = arg; + struct runner_context *rctx = arg; + + pthread_cleanup_push(cleanup_context, arg); #ifdef RUNNER_START_DELAY_US /* @@ -98,10 +100,12 @@ static void *runner_thread(void *arg) #endif st = uatomic_cmpxchg(&rctx->status, RUNNER_IDLE, RUNNER_RUNNING); - if (st != RUNNER_IDLE) - return NULL; - (*rctx->func)(rctx->data); + /* Only run the function if we haven't been cancelled */ + if (st == RUNNER_IDLE) + (*rctx->func)(rctx->data); + + pthread_cleanup_pop(1); return NULL; } From fac68b6df61c7254af3458cb85a9b246c378c80c Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 12 Jun 2026 15:26:13 +0200 Subject: [PATCH 043/102] multipath-tools tests: add unit test for reading GPT partition table Add unit tests to verify that we read unusual or broken GPT headers correctly. Co-authored-by: Claude Sonnet 4.6 Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/Makefile | 6 +- tests/gpt.c | 707 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 tests/gpt.c diff --git a/tests/Makefile b/tests/Makefile index fc2a7ed7a..fa1fd186d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,7 +10,7 @@ LIBDEPS += -L. -L $(mpathutildir) -L$(mpathcmddir) -lmultipath -lmpathutil -lmpa TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo runner \ - shared_ptr + shared_ptr gpt HELPERS := test-lib.o test-log.o .PRECIOUS: $(TESTS:%=%-test) @@ -22,6 +22,8 @@ valgrind: $(TESTS:%=%.vgr) # test-specific compiler flags # XYZ-test_FLAGS: Additional compiler flags for this test +kpartxdir := $(TOPDIR)/kpartx +gpt-test_FLAGS := -I$(kpartxdir) mpathvalid-test_FLAGS := -I$(mpathvaliddir) features-test_FLAGS := -I$(multipathdir)/nvme @@ -68,6 +70,8 @@ cli-test_OBJDEPS := $(daemondir)/cli.o mapinfo-test_LIBDEPS = -lpthread -ldevmapper runner-test_LIBDEPS = -lpthread shared_ptr-test_LIBDEPS = -lpthread +gpt-test_OBJDEPS := $(kpartxdir)/gpt.o $(kpartxdir)/crc32.o + %.o: %.c @echo building $@ because of $? diff --git a/tests/gpt.c b/tests/gpt.c new file mode 100644 index 000000000..0d00e3049 --- /dev/null +++ b/tests/gpt.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include /* BLKGETSIZE64, BLKSSZGET */ +#include +#include +#include /* memfd_create */ +#include +#include +#include +#include +#include +#include + +#include "gpt.h" /* gpt_header, gpt_entry, legacy_mbr, read_gpt_pt */ +#include "crc32.h" /* crc32() macro -> crc32_le() */ +#include "kpartx.h" /* struct slice */ +#include "dos.h" /* struct partition (needed by gpt.h -> legacy_mbr) */ +#include "efi.h" /* efi_guid_t */ +#include "wrap64.h" + +/* ------------------------------------------------------------------ */ +/* External symbols required by gpt.o */ +/* ------------------------------------------------------------------ */ + +int force_gpt = 1; + +#define SECTOR_SIZE 512U +int get_sector_size(int fd __attribute__((unused))) +{ + return SECTOR_SIZE; +} + +int aligned_malloc(void **mem_p, size_t align, size_t *size_p) +{ + size_t sz; + + if (!mem_p || !align || (size_p && !*size_p)) + return EINVAL; + if (size_p) + sz = ((*size_p + align - 1) / align) * align; + else + sz = (size_t)getpagesize(); + if (posix_memalign(mem_p, (size_t)getpagesize(), sz)) + return ENOMEM; + if (size_p) + *size_p = sz; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Block-device simulation */ +/* ------------------------------------------------------------------ */ + +static uint64_t test_disk_size_bytes; + +int REAL_FSTAT_FUNC(int v, int fd, struct stat *buf); +int WRAP_FSTAT_FUNC(int v, int fd, struct stat *buf) +{ + int rc = REAL_FSTAT_FUNC(v, fd, buf); + + if (!rc && test_disk_size_bytes) + buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFBLK; + return rc; +} + +int REAL_IOCTL(int fd, unsigned long req, void *p); +int WRAP_IOCTL(int fd, unsigned long req, void *p) +{ + if (test_disk_size_bytes) { + if (req == BLKGETSIZE64) { + *(uint64_t *)p = test_disk_size_bytes; + return 0; + } + if (req == BLKSSZGET) { + *(int *)p = SECTOR_SIZE; + return 0; + } + } + return REAL_IOCTL(fd, req, p); +} + +/* ------------------------------------------------------------------ */ +/* CRC helpers */ +/* ------------------------------------------------------------------ */ + +static uint32_t efi_crc32(const void *buf, size_t len) +{ + return crc32(~0L, buf, len) ^ ~0L; +} + +#define NS 256 + +/* ------------------------------------------------------------------ */ +/* Disk image builder */ +/* ------------------------------------------------------------------ */ + +#define DISK_SECTORS 2048U +#define DISK_BYTES ((size_t)DISK_SECTORS * SECTOR_SIZE) +#define PE_SIZE 128U /* sizeof(gpt_entry) */ + +/* Flags for make_gpt_fd */ +#define FLAG_BAD_PRIMARY_HDR_CRC (1U << 0) +#define FLAG_BAD_ALT_HDR_CRC (1U << 1) +#define FLAG_BAD_PTE_CRC (1U << 2) +#define FLAG_BAD_SIGNATURE (1U << 3) +#define FLAG_WRONG_SIZEOF_PE (1U << 4) +#define FLAG_VALID_PMBR (1U << 5) + +/* Linux data partition type GUID */ +static const efi_guid_t linux_data_guid = + EFI_GUID(0x0FC63DAF, 0x8483, 0x4772, 0x8E, 0x79, 0x3D, 0x69, 0xD8, + 0x47, 0x7D, 0xE4); + +/* + * Build an in-memory GPT disk image and return an open memfd. + * + * Disk layout (DISK_SECTORS = 2048, sector_size = 512): + * LBA 0 Protective MBR + * LBA 1 Primary GPT header + * LBA 2..2+pe-1 Primary partition entry array (pe sectors) + * LBA L-pe..L-1 Backup partition entry array + * LBA L Backup GPT header (L = DISK_SECTORS-1 = 2047) + * + * For very large num_entries (overflow test), pe is capped at 64. + */ +static int make_gpt_fd(uint32_t num_entries, unsigned int num_parts, + const struct slice *parts, unsigned int flags) +{ + uint8_t *disk; + uint64_t pe_bytes = (uint64_t)num_entries * PE_SIZE; + uint32_t pe_sectors; + uint64_t primary_pe_lba, backup_hdr_lba, backup_pe_lba; + uint64_t first_usable, last_usable; + gpt_entry *pte; + uint32_t pte_crc, hdr_pte_crc; + gpt_header *phdr, *ahdr; + uint32_t pcrc, acrc; + int fd; + ssize_t written; + + /* Cap pe_sectors for very large num_entries to keep disk valid */ + if (pe_bytes > (uint64_t)(DISK_SECTORS - 4) * SECTOR_SIZE) + pe_sectors = 64; /* enough for 256 entries */ + else + pe_sectors = (uint32_t)((pe_bytes + SECTOR_SIZE - 1) / SECTOR_SIZE); + + primary_pe_lba = 2; + backup_hdr_lba = DISK_SECTORS - 1; /* LBA 2047 */ + backup_pe_lba = backup_hdr_lba - pe_sectors; /* varies */ + first_usable = primary_pe_lba + pe_sectors; + last_usable = backup_pe_lba > 0 ? backup_pe_lba - 1 : 0; + + disk = calloc(DISK_BYTES, 1); + if (!disk) + return -1; + + /* --- PMBR (LBA 0) --- */ + if (flags & FLAG_VALID_PMBR) { + legacy_mbr *pmbr = (legacy_mbr *)disk; + + pmbr->signature = htole16(MSDOS_MBR_SIGNATURE); + pmbr->partition[0].sys_type = EFI_PMBR_OSTYPE_EFI_GPT; + } + + /* --- Primary partition entry array (LBA 2) --- */ + pte = (gpt_entry *)(disk + primary_pe_lba * SECTOR_SIZE); + for (unsigned int i = 0; i < num_parts; i++) { + pte[i].partition_type_guid = linux_data_guid; + memset(&pte[i].unique_partition_guid, (int)(i + 1), 16); + pte[i].starting_lba = htole64(parts[i].start); + pte[i].ending_lba = htole64(parts[i].start + parts[i].size - 1); + } + + /* + * Compute PTE CRC matching alloc_read_gpt_entries(). + * In the overflow case, compute CRC over one entry, + * causing test failure in test_invalid_overflow_num_entries_1 + * if the overflow test is not correct. + */ + if (num_entries == 0 || pe_bytes > (uint64_t)(DISK_SECTORS - 4) * SECTOR_SIZE) + pte_crc = efi_crc32(disk + primary_pe_lba * SECTOR_SIZE, + sizeof(gpt_entry)); + else + pte_crc = efi_crc32(disk + primary_pe_lba * SECTOR_SIZE, + num_entries * sizeof(gpt_entry)); + + hdr_pte_crc = (flags & FLAG_BAD_PTE_CRC) ? (pte_crc ^ 1) : pte_crc; + + /* --- Primary GPT header (LBA 1) --- */ + phdr = (gpt_header *)(disk + 1 * SECTOR_SIZE); + phdr->signature = htole64(GPT_HEADER_SIGNATURE); + phdr->revision = htole32(GPT_HEADER_REVISION_V1_00); + phdr->header_size = htole32(92); + phdr->my_lba = htole64(1); + phdr->alternate_lba = htole64(backup_hdr_lba); + phdr->first_usable_lba = htole64(first_usable); + phdr->last_usable_lba = htole64(last_usable); + phdr->partition_entry_lba = htole64(primary_pe_lba); + phdr->num_partition_entries = htole32(num_entries); + phdr->sizeof_partition_entry = + htole32((flags & FLAG_WRONG_SIZEOF_PE) ? 256 : PE_SIZE); + phdr->partition_entry_array_crc32 = htole32(hdr_pte_crc); + + if (flags & FLAG_BAD_SIGNATURE) + phdr->signature = 0; + + phdr->header_crc32 = 0; + pcrc = efi_crc32(phdr, 92); + phdr->header_crc32 = htole32(pcrc); + if (flags & FLAG_BAD_PRIMARY_HDR_CRC) + phdr->header_crc32 ^= htole32(0xFF); + + /* --- Copy primary PE to backup PE location --- */ + if (pe_sectors > 0 && + (backup_pe_lba + pe_sectors) * SECTOR_SIZE <= DISK_BYTES) { + memcpy(disk + backup_pe_lba * SECTOR_SIZE, + disk + primary_pe_lba * SECTOR_SIZE, + pe_sectors * SECTOR_SIZE); + } + + /* --- Backup GPT header (LBA 2047) --- */ + ahdr = (gpt_header *)(disk + backup_hdr_lba * SECTOR_SIZE); + *ahdr = *phdr; /* inherit all flags applied to primary */ + ahdr->header_crc32 = 0; + ahdr->my_lba = htole64(backup_hdr_lba); + ahdr->alternate_lba = htole64(1); + ahdr->partition_entry_lba = htole64(backup_pe_lba); + + acrc = efi_crc32(ahdr, 92); + ahdr->header_crc32 = htole32(acrc); + if (flags & FLAG_BAD_ALT_HDR_CRC) + ahdr->header_crc32 ^= htole32(0xFF); + + /* --- Write to memfd --- */ + fd = memfd_create("gpt-test", 0); + if (fd < 0) { + free(disk); + return -1; + } + + written = write(fd, disk, DISK_BYTES); + free(disk); + + if (written != (ssize_t)DISK_BYTES) { + close(fd); + return -1; + } + if (lseek(fd, 0, SEEK_SET) < 0) { + close(fd); + return -1; + } + + test_disk_size_bytes = DISK_BYTES; + return fd; +} + +/* ------------------------------------------------------------------ */ +/* Partition data arrays */ +/* ------------------------------------------------------------------ */ + +static const struct slice parts3[] = { + { .start = 100, .size = 100 }, + { .start = 200, .size = 100 }, + { .start = 300, .size = 100 }, +}; + +static const struct slice parts2[] = { + { .start = 100, .size = 100 }, + { .start = 200, .size = 100 }, +}; + +static const struct slice parts5[] = { + { .start = 100, .size = 50 }, + { .start = 150, .size = 50 }, + { .start = 200, .size = 50 }, + { .start = 250, .size = 50 }, + { .start = 300, .size = 50 }, +}; + +/* ------------------------------------------------------------------ */ +/* Individual test functions */ +/* ------------------------------------------------------------------ */ + +static void test_valid_gpt_128(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 3, parts3, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 3); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); + assert_int_equal((int)sp[2].start, 300); + assert_int_equal((int)sp[2].size, 100); +} + +static void test_valid_gpt_no_partitions(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 0); +} + +static void test_invalid_zero_declared(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(0, 0, NULL, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + /* alloc_read_gpt_entries returns NULL for 0 entries -> failure */ + assert_true(ret <= 0); +} + +static void test_invalid_bad_signature(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, FLAG_BAD_SIGNATURE); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_true(ret <= 0); +} + +static void test_invalid_bad_header_crc(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, + FLAG_BAD_PRIMARY_HDR_CRC | FLAG_BAD_ALT_HDR_CRC); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_true(ret <= 0); +} + +static void test_invalid_bad_pte_crc(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, FLAG_BAD_PTE_CRC); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_true(ret <= 0); +} + +static void test_invalid_wrong_sizeof_pe(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, FLAG_WRONG_SIZEOF_PE); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + /* is_gpt_valid rejects sizeof_partition_entry != PE_SIZE */ + assert_true(ret <= 0); +} + +static void test_invalid_overflow_num_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + /* + * 0x02000000 * 128 = 0x100000000 (overflows uint32_t on 32-bit). + * On 64-bit: alloc_read_gpt_entries sets n_alloc=256, reads 64 + * sectors, then the extra-entries loop tries to read beyond the disk + * and fails. The accumulated CRC will not match the placeholder stored + * in the header, so is_gpt_valid returns 0 for both primary and + * alternate. + */ + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(0x02000000, 0, NULL, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_true(ret <= 0); +} + +static void +test_invalid_overflow_num_entries_1(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(0x02000001, 0, NULL, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_true(ret <= 0); +} + +static void test_fallback_to_alternate(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + /* Only primary header CRC is corrupted; alternate remains valid */ + fd = make_gpt_fd(128, 2, parts2, FLAG_BAD_PRIMARY_HDR_CRC); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + /* find_valid_gpt uses alternate GPT, returns 2 partitions */ + assert_int_equal(ret, 2); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); +} + +static void test_gpt_1024_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(1024, 3, parts3, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + /* 1024 > NS=256: warning printed, but 3 slices returned */ + assert_int_equal(ret, 3); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); + assert_int_equal((int)sp[2].start, 300); + assert_int_equal((int)sp[2].size, 100); +} + +static void test_pmbr_required(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + force_gpt = 0; + memset(sp, 0, sizeof(sp)); + /* No FLAG_VALID_PMBR: PMBR is all zeros, is_pmbr_valid() returns 0 */ + fd = make_gpt_fd(128, 0, NULL, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + force_gpt = 1; + + /* force_gpt=0 and invalid PMBR -> find_valid_gpt fails */ + assert_true(ret <= 0); +} + +static void test_pmbr_accepted(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + force_gpt = 0; + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(128, 0, NULL, FLAG_VALID_PMBR); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + force_gpt = 1; + + /* force_gpt=0 but valid PMBR -> success (0 partitions) */ + assert_int_equal(ret, 0); +} + +/* + * 3 entries (< 4 = entries_per_sector): count_rounded = 512, + * CRC covers the partial sector including zero padding. + * Tests the "num_entries <= ns, count rounds up" path in + * alloc_read_gpt_entries(). + */ +static void test_valid_gpt_3_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(3, 3, parts3, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 3); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); + assert_int_equal((int)sp[2].start, 300); + assert_int_equal((int)sp[2].size, 100); +} + +/* + * 5 entries (5 % 4 != 0): count_rounded = 1024, + * CRC covers two full sectors (5 entries + 3 zero entries of padding). + */ +static void test_valid_gpt_5_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(5, 5, parts5, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 5); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 50); + assert_int_equal((int)sp[1].start, 150); + assert_int_equal((int)sp[1].size, 50); + assert_int_equal((int)sp[2].start, 200); + assert_int_equal((int)sp[2].size, 50); + assert_int_equal((int)sp[3].start, 250); + assert_int_equal((int)sp[3].size, 50); + assert_int_equal((int)sp[4].start, 300); + assert_int_equal((int)sp[4].size, 50); +} + +/* + * 257 entries (257 % 4 != 0): triggers the extra-entries path in + * alloc_read_gpt_entries() with a partial final sector (1 entry, + * CRC over 128 bytes instead of 512). + */ +static void test_gpt_257_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(257, 3, parts3, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 3); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); + assert_int_equal((int)sp[2].start, 300); + assert_int_equal((int)sp[2].size, 100); +} + +/* + * 1025 entries (1025 % 4 != 0): extra-entries path with partial last sector + * (1 entry = 128 bytes) at the very end of a 256-sector PE block. + */ +static void test_gpt_1025_entries(void **state __attribute__((unused))) +{ + struct slice all = { 0 }; + struct slice sp[NS]; + int fd, ret; + + memset(sp, 0, sizeof(sp)); + fd = make_gpt_fd(1025, 3, parts3, 0); + assert_true(fd >= 0); + + ret = read_gpt_pt(fd, all, sp, NS); + close(fd); + test_disk_size_bytes = 0; + + assert_int_equal(ret, 3); + assert_int_equal((int)sp[0].start, 100); + assert_int_equal((int)sp[0].size, 100); + assert_int_equal((int)sp[1].start, 200); + assert_int_equal((int)sp[1].size, 100); + assert_int_equal((int)sp[2].start, 300); + assert_int_equal((int)sp[2].size, 100); +} + +/* ------------------------------------------------------------------ */ +/* Group setup/teardown */ +/* ------------------------------------------------------------------ */ + +static int group_setup(void **state __attribute__((unused))) +{ + return init_crc32() ? -1 : 0; +} + +static int group_teardown(void **state __attribute__((unused))) +{ + cleanup_crc32(); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* main */ +/* ------------------------------------------------------------------ */ + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_valid_gpt_128), + cmocka_unit_test(test_valid_gpt_no_partitions), + cmocka_unit_test(test_invalid_zero_declared), + cmocka_unit_test(test_invalid_bad_signature), + cmocka_unit_test(test_invalid_bad_header_crc), + cmocka_unit_test(test_invalid_bad_pte_crc), + cmocka_unit_test(test_invalid_wrong_sizeof_pe), + cmocka_unit_test(test_invalid_overflow_num_entries), + cmocka_unit_test(test_invalid_overflow_num_entries_1), + cmocka_unit_test(test_fallback_to_alternate), + cmocka_unit_test(test_gpt_1024_entries), + cmocka_unit_test(test_pmbr_required), + cmocka_unit_test(test_pmbr_accepted), + cmocka_unit_test(test_valid_gpt_3_entries), + cmocka_unit_test(test_valid_gpt_5_entries), + cmocka_unit_test(test_gpt_257_entries), + cmocka_unit_test(test_gpt_1025_entries), + }; + return cmocka_run_group_tests(tests, group_setup, group_teardown); +} From 15310a33712447fa740d396fddc1e5ddac466c0a Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 09:25:18 +0200 Subject: [PATCH 044/102] kpartx: Fix integer overflow in GPT size calculation A crafted GPT table with num_partition_entries >= 0x02000000 can trigger an integer overflow in kpartx. Fix it by casting one operand of the multiplication to size_t. Same as kernel commit c5082b70adfe ("partitions/efi: Fix integer overflow in GPT size calculation"). Signed-off-by: Martin Wilck Reported-by: Tristan Reviewed-by: Benjamin Marzinski --- kpartx/gpt.c | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/kpartx/gpt.c b/kpartx/gpt.c index 69ddce99a..df5c42eaa 100644 --- a/kpartx/gpt.c +++ b/kpartx/gpt.c @@ -52,6 +52,12 @@ #define BLKGETSIZE64 _IOR(0x12,114,sizeof(uint64_t)) /* return device size in bytes (u64 *arg) */ #endif +/* + * The max size of a partition table that we will read for calculating the CRC. + * Note that this is much larger than MAX_SLICES. It's 0x02000000 on most systems. + */ +#define MAX_PT_SIZE ((SIZE_MAX <= UINT32_MAX ? SIZE_MAX : UINT32_MAX) / sizeof(gpt_entry)) + struct blkdev_ioctl_param { unsigned int block; size_t content_length; @@ -70,8 +76,7 @@ struct blkdev_ioctl_param { * Note, the EFI Specification, v1.02, has a reference to * Dr. Dobbs Journal, May 1994 (actually it's in May 1992). */ -static inline uint32_t -efi_crc32(const void *buf, unsigned long len) +static inline uint32_t efi_crc32(const void *buf, size_t len) { return (crc32(~0L, buf, len) ^ ~0L); } @@ -226,10 +231,15 @@ static gpt_entry * alloc_read_gpt_entries(int fd, gpt_header * gpt) { gpt_entry *pte; - size_t count = __le32_to_cpu(gpt->num_partition_entries) * - __le32_to_cpu(gpt->sizeof_partition_entry); + size_t num_entries = __le32_to_cpu(gpt->num_partition_entries); + uint32_t sizeof_pe = __le32_to_cpu(gpt->sizeof_partition_entry); + size_t count; - if (!count) return NULL; + if (!sizeof_pe || num_entries > MAX_PT_SIZE) + return NULL; + count = num_entries * sizeof_pe; + if (!count) + return NULL; if (aligned_malloc((void **)&pte, get_sector_size(fd), &count)) return NULL; @@ -284,6 +294,8 @@ is_gpt_valid(int fd, uint64_t lba, { int rc = 0; /* default to not valid */ uint32_t crc, origcrc; + size_t num_pe; + uint32_t sizeof_pe; if (!gpt || !ptes) return 0; @@ -341,20 +353,25 @@ is_gpt_valid(int fd, uint64_t lba, } /* Check the GUID Partition Entry Array CRC */ - crc = efi_crc32(*ptes, - __le32_to_cpu((*gpt)->num_partition_entries) * - __le32_to_cpu((*gpt)->sizeof_partition_entry)); - if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) { - // printf("GUID Partition Entry Array CRC check failed.\n"); - free(*gpt); - *gpt = NULL; - free(*ptes); - *ptes = NULL; - return 0; - } + num_pe = __le32_to_cpu((*gpt)->num_partition_entries); + sizeof_pe = __le32_to_cpu((*gpt)->sizeof_partition_entry); + + if (num_pe > MAX_PT_SIZE) + goto bad_crc; + crc = efi_crc32(*ptes, num_pe * sizeof_pe); + if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) + goto bad_crc; /* We're done, all's well */ return 1; + +bad_crc: + // printf("GUID Partition Entry Array CRC check failed.\n"); + free(*gpt); + *gpt = NULL; + free(*ptes); + *ptes = NULL; + return 0; } /** * compare_gpts() - Search disk for valid GPT headers and PTEs From 8697ea7e35b85dba8a79c9977a0f86f2d28786e6 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 16:36:34 +0200 Subject: [PATCH 045/102] kpartx: use mmap() to implement alloc_read_gpt_entries() A maliciously crafted partition table could cause an unnecessarily large memory allocation. By using mmap(), we can calculate the checksum without needing to allocate space for more than the supported number of partitions. Tested with odd values of MAX_SLICES like 1, 2, 4, 7, 8, 19. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- kpartx/gpt.c | 100 +++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/kpartx/gpt.c b/kpartx/gpt.c index df5c42eaa..1068da2e3 100644 --- a/kpartx/gpt.c +++ b/kpartx/gpt.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "crc32.h" #include "kpartx.h" @@ -227,29 +228,48 @@ read_lba(int fd, uint64_t lba, void *buffer, size_t bytes) * Allocates space for PTEs based on information found in @gpt. * Notes: remember to free pte when you're done! */ -static gpt_entry * -alloc_read_gpt_entries(int fd, gpt_header * gpt) +static gpt_entry *alloc_read_gpt_entries(int fd, gpt_header *gpt, unsigned int ns) { - gpt_entry *pte; + gpt_entry *pte = NULL; size_t num_entries = __le32_to_cpu(gpt->num_partition_entries); - uint32_t sizeof_pe = __le32_to_cpu(gpt->sizeof_partition_entry); - size_t count; - - if (!sizeof_pe || num_entries > MAX_PT_SIZE) - return NULL; - count = num_entries * sizeof_pe; - if (!count) + size_t n_alloc = num_entries <= ns ? num_entries : ns; + long pagesz = sysconf(_SC_PAGESIZE); + off_t ofs = __le64_to_cpu(gpt->partition_entry_lba) * get_sector_size(fd); + off_t ofs_aligned = ofs & ~(pagesz - 1); + uint32_t crc; + size_t arr_len, count; + unsigned char *pe_map; + + if (!num_entries || num_entries > MAX_PT_SIZE) return NULL; - if (aligned_malloc((void **)&pte, get_sector_size(fd), &count)) - return NULL; - memset(pte, 0, count); + arr_len = num_entries * sizeof(gpt_entry); + ofs -= ofs_aligned; + pe_map = mmap(NULL, arr_len + ofs, PROT_READ, MAP_SHARED, fd, ofs_aligned); - if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte, - count)) { - free(pte); + if (pe_map == MAP_FAILED) { + fprintf(stderr, "%s: mmap failed: %s\n", __func__, strerror(errno)); return NULL; } + + crc = efi_crc32(pe_map + ofs, arr_len); + if (crc != __le32_to_cpu(gpt->partition_entry_array_crc32)) { + fprintf(stderr, "%s: CRC32 checksum mismatch", __func__); + goto out; + } + + count = n_alloc * sizeof(gpt_entry); + pte = malloc(count); + if (pte == NULL) + goto out; + + memcpy(pte, pe_map + ofs, count); + if (n_alloc < num_entries) + fprintf(stderr, + "%s: partition table size (%zu) is larger than supported (%zu)\n", + __func__, num_entries, n_alloc); +out: + munmap(pe_map, arr_len + ofs); return pte; } @@ -288,14 +308,11 @@ alloc_read_gpt_header(int fd, uint64_t lba) * Description: returns 1 if valid, 0 on error. * If valid, returns pointers to newly allocated GPT header and PTEs. */ -static int -is_gpt_valid(int fd, uint64_t lba, - gpt_header ** gpt, gpt_entry ** ptes) +static int is_gpt_valid(int fd, uint64_t lba, gpt_header **gpt, + gpt_entry **ptes, unsigned int ns) { int rc = 0; /* default to not valid */ uint32_t crc, origcrc; - size_t num_pe; - uint32_t sizeof_pe; if (!gpt || !ptes) return 0; @@ -346,33 +363,16 @@ is_gpt_valid(int fd, uint64_t lba, return 0; } - if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) { + if (!(*ptes = alloc_read_gpt_entries(fd, *gpt, ns))) { free(*gpt); *gpt = NULL; return 0; } - /* Check the GUID Partition Entry Array CRC */ - num_pe = __le32_to_cpu((*gpt)->num_partition_entries); - sizeof_pe = __le32_to_cpu((*gpt)->sizeof_partition_entry); - - if (num_pe > MAX_PT_SIZE) - goto bad_crc; - crc = efi_crc32(*ptes, num_pe * sizeof_pe); - if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) - goto bad_crc; - /* We're done, all's well */ return 1; - -bad_crc: - // printf("GUID Partition Entry Array CRC check failed.\n"); - free(*gpt); - *gpt = NULL; - free(*ptes); - *ptes = NULL; - return 0; } + /** * compare_gpts() - Search disk for valid GPT headers and PTEs * @pgpt is the primary GPT header @@ -494,8 +494,7 @@ compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba) * Validity depends on finding either the Primary GPT header and PTEs valid, * or the Alternate GPT header and PTEs valid, and the PMBR valid. */ -static int -find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes) +static int find_valid_gpt(int fd, gpt_header **gpt, gpt_entry **ptes, unsigned int ns) { int good_pgpt = 0, good_agpt = 0, good_pmbr = 0; gpt_header *pgpt = NULL, *agpt = NULL; @@ -508,20 +507,17 @@ find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes) if (!(lastlba = last_lba(fd))) return 0; - good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA, - &pgpt, &pptes); + good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA, &pgpt, + &pptes, ns); if (good_pgpt) { - good_agpt = is_gpt_valid(fd, - __le64_to_cpu(pgpt->alternate_lba), - &agpt, &aptes); + good_agpt = is_gpt_valid(fd, __le64_to_cpu(pgpt->alternate_lba), + &agpt, &aptes, ns); if (!good_agpt) { - good_agpt = is_gpt_valid(fd, lastlba, - &agpt, &aptes); + good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes, ns); } } else { - good_agpt = is_gpt_valid(fd, lastlba, - &agpt, &aptes); + good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes, ns); } /* The obviously unsuccessful case */ @@ -614,7 +610,7 @@ read_gpt_pt (int fd, __attribute__((unused)) struct slice all, int last_used_index = -1; int sector_size_mul = get_sector_size(fd)/512; - if (!find_valid_gpt (fd, &gpt, &ptes) || !gpt || !ptes) { + if (!find_valid_gpt(fd, &gpt, &ptes, ns) || !gpt || !ptes) { if (gpt) free (gpt); if (ptes) From d08eefd5150470547d6cc38db597dfa5ede80b01 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 17:23:20 +0200 Subject: [PATCH 046/102] kpartx: dasd: avoid out-of-bounds write in read_dasd_pt() Make sure not to write to the struct slice array sp beyond the upper bound (ns). Signed-off-by: Martin Wilck Reported-by: Tristan Reviewed-by: Benjamin Marzinski --- kpartx/dasd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 337d5f45c..a044ec638 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -53,9 +53,8 @@ typedef unsigned int __attribute__((__may_alias__)) label_ints_t; /* */ -int -read_dasd_pt(int fd, __attribute__((unused)) struct slice all, - struct slice *sp, __attribute__((unused)) unsigned int ns) +int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, + struct slice *sp, unsigned int ns) { int retval = -1; int blocksize; @@ -243,6 +242,8 @@ read_dasd_pt(int fd, __attribute__((unused)) struct slice all, sp[counter].start = sectors512(offset, blocksize); sp[counter].size = sectors512(size, blocksize); counter++; + if ((unsigned int)counter >= ns) + break; blk++; } retval = counter; From cd77a2be8967c2dfa24a2b8d0142d71008d1ef1b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 17:27:09 +0200 Subject: [PATCH 047/102] kpartx: dasd: avoid infinite loop in read_dasd_pt Make sure that the loop terminates if read() encounters EOF and returns 0. Found with the help of Claude Sonnet 4.6. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- kpartx/dasd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index a044ec638..7d8a14ccd 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -216,7 +216,7 @@ int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, if (lseek(fd_dasd, blk * blocksize, SEEK_SET) == -1) goto out; - while (read(fd_dasd, data, blocksize) != -1) { + while (read(fd_dasd, data, blocksize) > 0) { format1_label_t f1; memcpy(&f1, data, sizeof(format1_label_t)); From 6179c2dfb750a269766ab0c211247c3ec82cb7bf Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 17:30:13 +0200 Subject: [PATCH 048/102] kpartx: dasd: double check blocksize bounds after reading it from disk Avoid a crash when a maliciously crafted disk image reports an invalid block size. Allowed blocksizes are between 512 and 4096 bytes [1]. Found withe the help of Claude Sonnet 4.6. [1] https://www.ibm.com/docs/en/zvm/7.4.0?topic=commands-format Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- kpartx/dasd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 7d8a14ccd..108e2303f 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -192,6 +192,8 @@ int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, label_ints_t *label = (label_ints_t *) &vlabel; blocksize = label[4]; + if (blocksize < 512 || blocksize > 4096) + goto out; if (label[14] != 0) { /* disk is reserved minidisk */ offset = label[14]; From c616a95e00d145ae6f97ad03cd00918991c2c997 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 11 Jun 2026 17:33:09 +0200 Subject: [PATCH 049/102] kpartx: dasd: avoid negative partition size Make sure that size - sp[0].start does not result in a negative value. Found withe the help of Claude Sonnet 4.6. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- kpartx/dasd.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 108e2303f..9062e9041 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -59,7 +59,7 @@ int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, int retval = -1; int blocksize; uint64_t disksize; - uint64_t offset, size, fmt_size; + uint64_t offset, size, fmt_size, start; dasd_information_t info; struct hd_geometry geo; char type[5] = {0,}; @@ -202,8 +202,11 @@ int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, offset = info.label_block + 1; size = sectors512(label[8], blocksize); } - sp[0].start = sectors512(offset, blocksize); - sp[0].size = size - sp[0].start; + start = sectors512(offset, blocksize); + if (start >= size) + goto out; + sp[0].start = start; + sp[0].size = size - start; retval = 1; } else if ((strncmp(type, "VOL1", 4) == 0) && (!info.FBA_layout) && (!memcmp(info.type, "ECKD",4))) { @@ -276,8 +279,11 @@ int read_dasd_pt(int fd, __attribute__((unused)) struct slice all, } else size = disksize; - sp[0].start = sectors512(info.label_block + 1, blocksize); - sp[0].size = size - sp[0].start; + start = sectors512(info.label_block + 1, blocksize); + if (start >= size) + goto out; + sp[0].start = start; + sp[0].size = size - start; retval = 1; } From 4e440522868abeabeb70004f7b2c5cdb15e4c9e0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 18 Jun 2026 21:31:45 +0200 Subject: [PATCH 050/102] .clang-format: set Cpp11BracedListStyle: false This avoids reformatting the subsequent patch "libmultipath: deprecate rr_min_io_rq, and make it do nothing". Signed-off-by: Martin Wilck Reviewed-by: Martin Wilck --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index f8b87a2c6..e68fd4d5c 100644 --- a/.clang-format +++ b/.clang-format @@ -30,4 +30,5 @@ ForEachMacros: - vector_foreach_slot - vector_foreach_slot_backwards - vector_foreach_slot_after +Cpp11BracedListStyle: false --- From 2f81014baef47793ea35ae0a81c63f8db1b76a9b Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 15 Jun 2026 19:36:08 -0400 Subject: [PATCH 051/102] libmultipath: deprecate rr_weight, and make it do nothing Since the kernel has required paths to have a minio of 1 for a long time now, rr_weight is completely useless. Remove all the code for it, and deprecate the option. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/config.c | 3 --- libmultipath/config.h | 3 --- libmultipath/configure.c | 1 - libmultipath/defaults.h | 1 - libmultipath/dict.c | 54 ++++------------------------------------ libmultipath/dict.h | 1 - libmultipath/dmparser.c | 16 +----------- libmultipath/hwtable.c | 1 - libmultipath/propsel.c | 22 ---------------- libmultipath/propsel.h | 1 - libmultipath/structs.h | 7 ------ 11 files changed, 6 insertions(+), 104 deletions(-) diff --git a/libmultipath/config.c b/libmultipath/config.c index f9198d605..c0be223a2 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -447,7 +447,6 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) merge_str(bl_product); merge_num(pgpolicy); merge_num(pgfailback); - merge_num(rr_weight); merge_num(no_path_retry); merge_num(minio); merge_num(minio_rq); @@ -505,7 +504,6 @@ merge_mpe(struct mpentry *dst, struct mpentry *src) merge_num(pgpolicy); merge_num(pgfailback); - merge_num(rr_weight); merge_num(no_path_retry); merge_num(minio); merge_num(minio_rq); @@ -616,7 +614,6 @@ store_hwe (vector hwtable, struct hwentry * dhwe) hwe->pgpolicy = dhwe->pgpolicy; hwe->pgfailback = dhwe->pgfailback; - hwe->rr_weight = dhwe->rr_weight; hwe->no_path_retry = dhwe->no_path_retry; hwe->minio = dhwe->minio; hwe->minio_rq = dhwe->minio_rq; diff --git a/libmultipath/config.h b/libmultipath/config.h index 618fa5723..3a08b7014 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -64,7 +64,6 @@ struct hwentry { int pgpolicy; int pgfailback; - int rr_weight; int no_path_retry; int minio; int minio_rq; @@ -114,7 +113,6 @@ struct mpentry { uint8_t sa_flags; int pgpolicy; int pgfailback; - int rr_weight; int no_path_retry; int minio; int minio_rq; @@ -151,7 +149,6 @@ struct config { unsigned int max_checkint; unsigned int adjust_int; int pgfailback; - int rr_weight; int no_path_retry; int user_friendly_names; int bindings_read_only; diff --git a/libmultipath/configure.c b/libmultipath/configure.c index a954ca311..9d6aec4d0 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -336,7 +336,6 @@ int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) else free(save_attr); - select_rr_weight(conf, mpp); select_minio(conf, mpp); select_mode(conf, mpp); select_uid(conf, mpp); diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index d80dd7d44..a6e808db9 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -20,7 +20,6 @@ #define DEFAULT_MINIO_RQ 1 #define DEFAULT_PGPOLICY FAILOVER #define DEFAULT_FAILBACK -FAILBACK_MANUAL -#define DEFAULT_RR_WEIGHT RR_WEIGHT_NONE #define DEFAULT_NO_PATH_RETRY NO_PATH_RETRY_UNDEF #define DEFAULT_VERBOSITY 2 #define DEFAULT_REASSIGN_MAPS 0 diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 797707d9d..502f54b26 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -1350,51 +1350,6 @@ snprint_max_fds (struct config *conf, struct strbuf *buff, const void *data) return print_int(buff, conf->max_fds); } -static int -set_rr_weight(vector strvec, void *ptr, const char *file, int line_nr) -{ - int *int_ptr = (int *)ptr; - char * buff; - - buff = set_value(strvec); - - if (!buff) - return 1; - - if (!strcmp(buff, "priorities")) - *int_ptr = RR_WEIGHT_PRIO; - else if (!strcmp(buff, "uniform")) - *int_ptr = RR_WEIGHT_NONE; - else - condlog(1, "%s line %d, invalid value for rr_weight: \"%s\"", - file, line_nr, buff); - free(buff); - - return 0; -} - -int -print_rr_weight (struct strbuf *buff, long v) -{ - if (!v) - return 0; - if (v == RR_WEIGHT_PRIO) - return append_strbuf_quoted(buff, "priorities"); - if (v == RR_WEIGHT_NONE) - return append_strbuf_quoted(buff, "uniform"); - - return 0; -} - -declare_def_handler(rr_weight, set_rr_weight) -declare_def_snprint_defint(rr_weight, print_rr_weight, DEFAULT_RR_WEIGHT) -declare_ovr_handler(rr_weight, set_rr_weight) -declare_ovr_snprint(rr_weight, print_rr_weight) -declare_hw_handler(rr_weight, set_rr_weight) -declare_hw_snprint(rr_weight, print_rr_weight) -declare_mp_handler(rr_weight, set_rr_weight) -declare_mp_snprint(rr_weight, print_rr_weight) - static int set_pgfailback(vector strvec, void *ptr, const char *file, int line_nr) { @@ -2162,6 +2117,7 @@ declare_deprecated_handler(pg_timeout, "(not set)") declare_deprecated_handler(bindings_file, DEFAULT_BINDINGS_FILE) declare_deprecated_handler(wwids_file, DEFAULT_WWIDS_FILE) declare_deprecated_handler(prkeys_file, DEFAULT_PRKEYS_FILE) +declare_deprecated_handler(rr_weight, "uniform") /* * If you add or remove a keyword also update multipath/multipath.conf.5 @@ -2190,7 +2146,7 @@ init_keywords(vector keywords) install_keyword("rr_min_io", &def_minio_handler, &snprint_def_minio); install_keyword("rr_min_io_rq", &def_minio_rq_handler, &snprint_def_minio_rq); install_keyword("max_fds", &max_fds_handler, &snprint_max_fds); - install_keyword("rr_weight", &def_rr_weight_handler, &snprint_def_rr_weight); + install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &def_no_path_retry_handler, &snprint_def_no_path_retry); install_keyword("queue_without_daemon", &def_queue_without_daemon_handler, &snprint_def_queue_without_daemon); install_keyword("checker_timeout", &def_checker_timeout_handler, &snprint_def_checker_timeout); @@ -2295,7 +2251,7 @@ init_keywords(vector keywords) install_keyword("prio", &hw_prio_name_handler, &snprint_hw_prio_name); install_keyword("prio_args", &hw_prio_args_handler, &snprint_hw_prio_args); install_keyword("failback", &hw_pgfailback_handler, &snprint_hw_pgfailback); - install_keyword("rr_weight", &hw_rr_weight_handler, &snprint_hw_rr_weight); + install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &hw_no_path_retry_handler, &snprint_hw_no_path_retry); install_keyword("rr_min_io", &hw_minio_handler, &snprint_hw_minio); install_keyword("rr_min_io_rq", &hw_minio_rq_handler, &snprint_hw_minio_rq); @@ -2341,7 +2297,7 @@ init_keywords(vector keywords) install_keyword("prio", &ovr_prio_name_handler, &snprint_ovr_prio_name); install_keyword("prio_args", &ovr_prio_args_handler, &snprint_ovr_prio_args); install_keyword("failback", &ovr_pgfailback_handler, &snprint_ovr_pgfailback); - install_keyword("rr_weight", &ovr_rr_weight_handler, &snprint_ovr_rr_weight); + install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &ovr_no_path_retry_handler, &snprint_ovr_no_path_retry); install_keyword("rr_min_io", &ovr_minio_handler, &snprint_ovr_minio); install_keyword("rr_min_io_rq", &ovr_minio_rq_handler, &snprint_ovr_minio_rq); @@ -2390,7 +2346,7 @@ init_keywords(vector keywords) install_keyword("prio", &mp_prio_name_handler, &snprint_mp_prio_name); install_keyword("prio_args", &mp_prio_args_handler, &snprint_mp_prio_args); install_keyword("failback", &mp_pgfailback_handler, &snprint_mp_pgfailback); - install_keyword("rr_weight", &mp_rr_weight_handler, &snprint_mp_rr_weight); + install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &mp_no_path_retry_handler, &snprint_mp_no_path_retry); install_keyword("rr_min_io", &mp_minio_handler, &snprint_mp_minio); install_keyword("rr_min_io_rq", &mp_minio_rq_handler, &snprint_mp_minio_rq); diff --git a/libmultipath/dict.h b/libmultipath/dict.h index 97c91c8e8..3757f1ca1 100644 --- a/libmultipath/dict.h +++ b/libmultipath/dict.h @@ -7,7 +7,6 @@ struct strbuf; void init_keywords(vector keywords); int get_sys_max_fds(int *); -int print_rr_weight(struct strbuf *buff, long v); int print_pgfailback(struct strbuf *buff, long v); int print_pgpolicy(struct strbuf *buff, long v); int print_no_path_retry(struct strbuf *buff, long v); diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c index 3411bd00b..973006682 100644 --- a/libmultipath/dmparser.c +++ b/libmultipath/dmparser.c @@ -89,16 +89,11 @@ int assemble_map(struct multipath *mp, char **params) goto err; vector_foreach_slot (pgp->paths, pp, j) { - int tmp_minio = minio; - - if (mp->rr_weight == RR_WEIGHT_PRIO - && pp->priority > 0) - tmp_minio = minio * pp->priority; if (!strlen(pp->dev_t) ) { condlog(0, "dev_t not set for '%s'", pp->dev); goto err; } - if (print_strbuf(&buff, " %s %d", pp->dev_t, tmp_minio) < 0) + if (print_strbuf(&buff, " %s %d", pp->dev_t, minio) < 0) goto err; } } @@ -315,15 +310,6 @@ int disassemble_map(const struct vector_s *pathvec, def_minio = atoi(word); free(word); - if (!strncmp(mpp->selector, - "round-robin", 11)) { - - if (mpp->rr_weight == RR_WEIGHT_PRIO - && pp->priority > 0) - def_minio /= pp->priority; - - } - if (def_minio != mpp->minio) mpp->minio = def_minio; } diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index 23c549335..949ccb338 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -56,7 +56,6 @@ .prio_name = PRIO_CONST, .prio_args = "", .pgfailback = -FAILBACK_MANUAL, - .rr_weight = RR_WEIGHT_NONE, .no_path_retry = NO_PATH_RETRY_UNDEF, .minio = 1000, .minio_rq = 1, diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 0c10ebc1c..bc5aae7dd 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -211,28 +211,6 @@ int select_gid(struct config *conf, struct multipath *mp) return 0; } -/* - * selectors : - * traverse the configuration layers from most specific to most generic - * stop at first explicit setting found - */ -int select_rr_weight(struct config *conf, struct multipath * mp) -{ - const char *origin; - STRBUF_ON_STACK(buff); - - mp_set_mpe(rr_weight); - mp_set_ovr(rr_weight); - mp_set_hwe(rr_weight); - mp_set_conf(rr_weight); - mp_set_default(rr_weight, DEFAULT_RR_WEIGHT); -out: - print_rr_weight(&buff, mp->rr_weight); - condlog(3, "%s: rr_weight = %s %s", mp->alias, - get_strbuf_str(&buff), origin); - return 0; -} - int select_pgfailback(struct config *conf, struct multipath * mp) { const char *origin; diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h index bd20e39e4..056e841ef 100644 --- a/libmultipath/propsel.h +++ b/libmultipath/propsel.h @@ -1,6 +1,5 @@ #ifndef PROPSEL_H_INCLUDED #define PROPSEL_H_INCLUDED -int select_rr_weight (struct config *conf, struct multipath * mp); int select_pgfailback (struct config *conf, struct multipath * mp); int select_detect_pgpolicy (struct config *conf, struct multipath * mp); int select_detect_pgpolicy_use_tpg (struct config *conf, struct multipath * mp); diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 8b71aa29c..9e36f4c78 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -43,12 +43,6 @@ enum free_path_mode { FREE_PATHS }; -enum rr_weight_mode { - RR_WEIGHT_UNDEF, - RR_WEIGHT_NONE, - RR_WEIGHT_PRIO -}; - enum failback_mode { FAILBACK_UNDEF, FAILBACK_MANUAL, @@ -483,7 +477,6 @@ struct multipath { int uev_wait_tick; int pgfailback; int failback_tick; - int rr_weight; int no_path_retry; /* number of retries after all paths are down */ int retry_tick; /* remaining times for retries */ int disable_queueing; From c2e0dd003720b235e40da64181c403e1116b1e22 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 15 Jun 2026 19:36:09 -0400 Subject: [PATCH 052/102] libmultipath: deprecate rr_min_io_rq, and make it do nothing Since the kernel now requires paths to have a minio of 1, rr_min_io_rq is completely useless. Remove all the code for it, and deprecate the option. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/config.c | 3 --- libmultipath/config.h | 3 --- libmultipath/dict.c | 18 +++++------------- libmultipath/hwtable.c | 1 - libmultipath/propsel.c | 20 ++++---------------- tests/hwtable.c | 13 +++---------- 6 files changed, 12 insertions(+), 46 deletions(-) diff --git a/libmultipath/config.c b/libmultipath/config.c index c0be223a2..0d8774222 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -449,7 +449,6 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) merge_num(pgfailback); merge_num(no_path_retry); merge_num(minio); - merge_num(minio_rq); merge_num(flush_on_last_del); merge_num(fast_io_fail); merge_num(dev_loss); @@ -506,7 +505,6 @@ merge_mpe(struct mpentry *dst, struct mpentry *src) merge_num(pgfailback); merge_num(no_path_retry); merge_num(minio); - merge_num(minio_rq); merge_num(flush_on_last_del); merge_num(attribute_flags); merge_num(user_friendly_names); @@ -616,7 +614,6 @@ store_hwe (vector hwtable, struct hwentry * dhwe) hwe->pgfailback = dhwe->pgfailback; hwe->no_path_retry = dhwe->no_path_retry; hwe->minio = dhwe->minio; - hwe->minio_rq = dhwe->minio_rq; hwe->flush_on_last_del = dhwe->flush_on_last_del; hwe->fast_io_fail = dhwe->fast_io_fail; hwe->dev_loss = dhwe->dev_loss; diff --git a/libmultipath/config.h b/libmultipath/config.h index 3a08b7014..ad3b24233 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -66,7 +66,6 @@ struct hwentry { int pgfailback; int no_path_retry; int minio; - int minio_rq; int flush_on_last_del; int fast_io_fail; unsigned int dev_loss; @@ -115,7 +114,6 @@ struct mpentry { int pgfailback; int no_path_retry; int minio; - int minio_rq; int flush_on_last_del; int attribute_flags; int user_friendly_names; @@ -144,7 +142,6 @@ struct config { int pgpolicy_flag; int pgpolicy; int minio; - int minio_rq; unsigned int checkint; unsigned int max_checkint; unsigned int adjust_int; diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 502f54b26..3b6584eca 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -755,15 +755,6 @@ declare_hw_snprint(minio, print_nonzero) declare_mp_range_handler(minio, 0, INT_MAX) declare_mp_snprint(minio, print_nonzero) -declare_def_range_handler(minio_rq, 0, INT_MAX) -declare_def_snprint_defint(minio_rq, print_int, DEFAULT_MINIO_RQ) -declare_ovr_range_handler(minio_rq, 0, INT_MAX) -declare_ovr_snprint(minio_rq, print_nonzero) -declare_hw_range_handler(minio_rq, 0, INT_MAX) -declare_hw_snprint(minio_rq, print_nonzero) -declare_mp_range_handler(minio_rq, 0, INT_MAX) -declare_mp_snprint(minio_rq, print_nonzero) - declare_def_handler(queue_without_daemon, set_yes_no) static int snprint_def_queue_without_daemon(struct config *conf, struct strbuf *buff, @@ -2117,6 +2108,7 @@ declare_deprecated_handler(pg_timeout, "(not set)") declare_deprecated_handler(bindings_file, DEFAULT_BINDINGS_FILE) declare_deprecated_handler(wwids_file, DEFAULT_WWIDS_FILE) declare_deprecated_handler(prkeys_file, DEFAULT_PRKEYS_FILE) +declare_deprecated_handler(minio_rq, "1") declare_deprecated_handler(rr_weight, "uniform") /* @@ -2144,7 +2136,7 @@ init_keywords(vector keywords) install_keyword("alias_prefix", &def_alias_prefix_handler, &snprint_def_alias_prefix); install_keyword("failback", &def_pgfailback_handler, &snprint_def_pgfailback); install_keyword("rr_min_io", &def_minio_handler, &snprint_def_minio); - install_keyword("rr_min_io_rq", &def_minio_rq_handler, &snprint_def_minio_rq); + install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("max_fds", &max_fds_handler, &snprint_max_fds); install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &def_no_path_retry_handler, &snprint_def_no_path_retry); @@ -2254,7 +2246,7 @@ init_keywords(vector keywords) install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &hw_no_path_retry_handler, &snprint_hw_no_path_retry); install_keyword("rr_min_io", &hw_minio_handler, &snprint_hw_minio); - install_keyword("rr_min_io_rq", &hw_minio_rq_handler, &snprint_hw_minio_rq); + install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("pg_timeout", &deprecated_pg_timeout_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &hw_flush_on_last_del_handler, &snprint_hw_flush_on_last_del); install_keyword("fast_io_fail_tmo", &hw_fast_io_fail_handler, &snprint_hw_fast_io_fail); @@ -2300,7 +2292,7 @@ init_keywords(vector keywords) install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &ovr_no_path_retry_handler, &snprint_ovr_no_path_retry); install_keyword("rr_min_io", &ovr_minio_handler, &snprint_ovr_minio); - install_keyword("rr_min_io_rq", &ovr_minio_rq_handler, &snprint_ovr_minio_rq); + install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &ovr_flush_on_last_del_handler, &snprint_ovr_flush_on_last_del); install_keyword("fast_io_fail_tmo", &ovr_fast_io_fail_handler, &snprint_ovr_fast_io_fail); install_keyword("dev_loss_tmo", &ovr_dev_loss_handler, &snprint_ovr_dev_loss); @@ -2349,7 +2341,7 @@ init_keywords(vector keywords) install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &mp_no_path_retry_handler, &snprint_mp_no_path_retry); install_keyword("rr_min_io", &mp_minio_handler, &snprint_mp_minio); - install_keyword("rr_min_io_rq", &mp_minio_rq_handler, &snprint_mp_minio_rq); + install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("pg_timeout", &deprecated_pg_timeout_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &mp_flush_on_last_del_handler, &snprint_mp_flush_on_last_del); install_keyword("features", &mp_features_handler, &snprint_mp_features); diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index 949ccb338..366465402 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -58,7 +58,6 @@ .pgfailback = -FAILBACK_MANUAL, .no_path_retry = NO_PATH_RETRY_UNDEF, .minio = 1000, - .minio_rq = 1, .flush_on_last_del = FLUSH_UNUSED, .user_friendly_names = USER_FRIENDLY_NAMES_OFF, .fast_io_fail = 5, diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index bc5aae7dd..7f0b66ece 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -848,21 +848,6 @@ int select_no_path_retry(struct config *conf, struct multipath *mp) return 0; } -int -select_minio_rq (struct config *conf, struct multipath * mp) -{ - const char *origin; - - do_set(minio_rq, mp->mpe, mp->minio, multipaths_origin); - do_set(minio_rq, conf->overrides, mp->minio, overrides_origin); - do_set_from_hwe(minio_rq, mp, mp->minio, hwe_origin); - do_set(minio_rq, conf, mp->minio, conf_origin); - do_default(mp->minio, DEFAULT_MINIO_RQ); -out: - condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin); - return 0; -} - int select_minio_bio (struct config *conf, struct multipath * mp) { @@ -880,13 +865,16 @@ select_minio_bio (struct config *conf, struct multipath * mp) int select_minio(struct config *conf, struct multipath *mp) { + const char *origin; unsigned int minv_dmrq[3] = {1, 1, 0}, version[3]; if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) && VERSION_GE(version, minv_dmrq)) - return select_minio_rq(conf, mp); + mp_set_default(minio, DEFAULT_MINIO_RQ); else return select_minio_bio(conf, mp); + condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin); + return 0; } int select_fast_io_fail(struct config *conf, struct path *pp) diff --git a/tests/hwtable.c b/tests/hwtable.c index 6021bfd36..eb135da1f 100644 --- a/tests/hwtable.c +++ b/tests/hwtable.c @@ -406,7 +406,6 @@ static const char _checker[] = "path_checker"; static const char _vpd_vnd[] = "vpd_vendor"; static const char _uid_attr[] = "uid_attribute"; static const char _bl_product[] = "product_blacklist"; -static const char _minio[] = "rr_min_io_rq"; static const char _no_path_retry[] = "no_path_retry"; /* Device identifiers */ @@ -441,7 +440,6 @@ static const struct key_value bl_bar = { _bl_product, "bar" }; static const struct key_value bl_baz = { _bl_product, "baz" }; static const struct key_value bl_barx = { _bl_product, "ba[[rxy]" }; static const struct key_value bl_bazy = { _bl_product, "ba[zy]" }; -static const struct key_value minio_99 = { _minio, "99" }; static const struct key_value npr_37 = { _no_path_retry, "37" }; static const struct key_value npr_queue = { _no_path_retry, "queue" }; @@ -552,7 +550,6 @@ static void test_sanity_globals(void **state) assert_string_not_equal(chk_hp.value, DEFAULT_CHECKER); assert_int_not_equal(MULTIBUS, DEFAULT_PGPOLICY); assert_int_not_equal(NO_PATH_RETRY_QUEUE, DEFAULT_NO_PATH_RETRY); - assert_int_not_equal(atoi(minio_99.value), DEFAULT_MINIO_RQ); assert_int_not_equal(atoi(npr_37.value), DEFAULT_NO_PATH_RETRY); } @@ -1566,7 +1563,6 @@ static void test_multipath_config(const struct hwt_state *hwt) mp = mock_multipath(pp); assert_ptr_not_equal(mp->mpe, NULL); TEST_PROP(prio_name(&pp->prio), prio_rdac.value); - assert_int_equal(mp->minio, atoi(minio_99.value)); TEST_PROP(pp->uid_attribute, uid_baz.value); /* test different wwid */ @@ -1574,14 +1570,13 @@ static void test_multipath_config(const struct hwt_state *hwt) mp = mock_multipath(pp); // assert_ptr_equal(mp->mpe, NULL); TEST_PROP(prio_name(&pp->prio), prio_emc.value); - assert_int_equal(mp->minio, DEFAULT_MINIO_RQ); TEST_PROP(pp->uid_attribute, uid_baz.value); } static int setup_multipath_config(void **state) { struct hwt_state *hwt = CHECK_STATE(state); - const struct key_value kvm[] = { wwid_test, prio_rdac, minio_99 }; + const struct key_value kvm[] = { wwid_test, prio_rdac }; const struct key_value kvp[] = { vnd_foo, prd_bar, prio_emc, uid_baz }; begin_config(hwt); @@ -1612,14 +1607,13 @@ static void test_multipath_config_2(const struct hwt_state *hwt) assert_ptr_not_equal(mp, NULL); assert_ptr_not_equal(mp->mpe, NULL); TEST_PROP(prio_name(&pp->prio), prio_rdac.value); - assert_int_equal(mp->minio, atoi(minio_99.value)); assert_int_equal(mp->no_path_retry, atoi(npr_37.value)); } static int setup_multipath_config_2(void **state) { const struct key_value kv1[] = { wwid_test, prio_rdac, npr_queue }; - const struct key_value kv2[] = { wwid_test, minio_99, npr_37 }; + const struct key_value kv2[] = { wwid_test, npr_37 }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); @@ -1647,14 +1641,13 @@ static void test_multipath_config_3(const struct hwt_state *hwt) assert_ptr_not_equal(mp, NULL); assert_ptr_not_equal(mp->mpe, NULL); TEST_PROP(prio_name(&pp->prio), prio_rdac.value); - assert_int_equal(mp->minio, atoi(minio_99.value)); assert_int_equal(mp->no_path_retry, atoi(npr_37.value)); } static int setup_multipath_config_3(void **state) { const struct key_value kv1[] = { wwid_test, prio_rdac, npr_queue }; - const struct key_value kv2[] = { wwid_test, minio_99, npr_37 }; + const struct key_value kv2[] = { wwid_test, npr_37 }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); From 91f91c7e4e8c27426cbda33d5f12b82f4fb27261 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 15 Jun 2026 19:36:10 -0400 Subject: [PATCH 053/102] libmultipath: deprecate rr_min_io Since the kernel now requires paths to have a minio of 1, rr_min_io is currently useless. However, bio-based multipath performs significantly worse than request-based multipath. Part of that is because bio-based multipath will call the path selector to pick a new path for every bio, making it very unlikely that adjacent bios will be merged. So this patch leaves the bio-based minio code around, since we may revisit this in the future. It does disable setting the value in multipath.conf, and changes the default to 1. Also, it fixes select_minio() to actually check if the multipath device is bio-based or request-based, and it takes out the code for modifying the minio based on the status of the "least-pending" path selector, because that no longer exists. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/defaults.h | 2 +- libmultipath/dict.c | 18 +++++------------- libmultipath/dmparser.c | 12 +----------- libmultipath/hwtable.c | 1 - libmultipath/propsel.c | 8 +++----- tests/test-lib.c | 2 -- 6 files changed, 10 insertions(+), 33 deletions(-) diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index a6e808db9..ea1fce0e7 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -16,7 +16,7 @@ #define DEFAULT_ALIAS_PREFIX "mpath" #define DEFAULT_FEATURES "0" #define DEFAULT_HWHANDLER "0" -#define DEFAULT_MINIO 1000 +#define DEFAULT_MINIO 1 #define DEFAULT_MINIO_RQ 1 #define DEFAULT_PGPOLICY FAILOVER #define DEFAULT_FAILBACK -FAILBACK_MANUAL diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 3b6584eca..0e947b1d9 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -746,15 +746,6 @@ declare_ovr_snprint(checker_name, print_str) declare_hw_handler(checker_name, set_str) declare_hw_snprint(checker_name, print_str) -declare_def_range_handler(minio, 0, INT_MAX) -declare_def_snprint_defint(minio, print_int, DEFAULT_MINIO) -declare_ovr_range_handler(minio, 0, INT_MAX) -declare_ovr_snprint(minio, print_nonzero) -declare_hw_range_handler(minio, 0, INT_MAX) -declare_hw_snprint(minio, print_nonzero) -declare_mp_range_handler(minio, 0, INT_MAX) -declare_mp_snprint(minio, print_nonzero) - declare_def_handler(queue_without_daemon, set_yes_no) static int snprint_def_queue_without_daemon(struct config *conf, struct strbuf *buff, @@ -2108,6 +2099,7 @@ declare_deprecated_handler(pg_timeout, "(not set)") declare_deprecated_handler(bindings_file, DEFAULT_BINDINGS_FILE) declare_deprecated_handler(wwids_file, DEFAULT_WWIDS_FILE) declare_deprecated_handler(prkeys_file, DEFAULT_PRKEYS_FILE) +declare_deprecated_handler(minio, "1") declare_deprecated_handler(minio_rq, "1") declare_deprecated_handler(rr_weight, "uniform") @@ -2135,7 +2127,7 @@ init_keywords(vector keywords) install_keyword("checker", &def_checker_name_handler, NULL); install_keyword("alias_prefix", &def_alias_prefix_handler, &snprint_def_alias_prefix); install_keyword("failback", &def_pgfailback_handler, &snprint_def_pgfailback); - install_keyword("rr_min_io", &def_minio_handler, &snprint_def_minio); + install_keyword("rr_min_io", &deprecated_minio_handler, &snprint_deprecated); install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("max_fds", &max_fds_handler, &snprint_max_fds); install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); @@ -2245,7 +2237,7 @@ init_keywords(vector keywords) install_keyword("failback", &hw_pgfailback_handler, &snprint_hw_pgfailback); install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &hw_no_path_retry_handler, &snprint_hw_no_path_retry); - install_keyword("rr_min_io", &hw_minio_handler, &snprint_hw_minio); + install_keyword("rr_min_io", &deprecated_minio_handler, &snprint_deprecated); install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("pg_timeout", &deprecated_pg_timeout_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &hw_flush_on_last_del_handler, &snprint_hw_flush_on_last_del); @@ -2291,7 +2283,7 @@ init_keywords(vector keywords) install_keyword("failback", &ovr_pgfailback_handler, &snprint_ovr_pgfailback); install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &ovr_no_path_retry_handler, &snprint_ovr_no_path_retry); - install_keyword("rr_min_io", &ovr_minio_handler, &snprint_ovr_minio); + install_keyword("rr_min_io", &deprecated_minio_handler, &snprint_deprecated); install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &ovr_flush_on_last_del_handler, &snprint_ovr_flush_on_last_del); install_keyword("fast_io_fail_tmo", &ovr_fast_io_fail_handler, &snprint_ovr_fast_io_fail); @@ -2340,7 +2332,7 @@ init_keywords(vector keywords) install_keyword("failback", &mp_pgfailback_handler, &snprint_mp_pgfailback); install_keyword("rr_weight", &deprecated_rr_weight_handler, &snprint_deprecated); install_keyword("no_path_retry", &mp_no_path_retry_handler, &snprint_mp_no_path_retry); - install_keyword("rr_min_io", &mp_minio_handler, &snprint_mp_minio); + install_keyword("rr_min_io", &deprecated_minio_handler, &snprint_deprecated); install_keyword("rr_min_io_rq", &deprecated_minio_rq_handler, &snprint_deprecated); install_keyword("pg_timeout", &deprecated_pg_timeout_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &mp_flush_on_last_del_handler, &snprint_mp_flush_on_last_del); diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c index 973006682..83760e30a 100644 --- a/libmultipath/dmparser.c +++ b/libmultipath/dmparser.c @@ -337,7 +337,6 @@ int disassemble_status(const char *params, struct multipath *mpp) int num_pg; int num_pg_args; int num_paths; - int def_minio = 0; struct path * pp; struct pathgroup * pgp; @@ -507,16 +506,7 @@ int disassemble_status(const char *params, struct multipath *mpp) * selector args */ for (k = 0; k < num_pg_args; k++) { - if (!strncmp(mpp->selector, - "least-pending", 13)) { - p += get_word(p, &word); - if (sscanf(word,"%d:*d", - &def_minio) == 1 && - def_minio != mpp->minio) - mpp->minio = def_minio; - free(word); - } else - p += get_word(p, NULL); + p += get_word(p, NULL); } } } diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index 366465402..a8fbfa70a 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -57,7 +57,6 @@ .prio_args = "", .pgfailback = -FAILBACK_MANUAL, .no_path_retry = NO_PATH_RETRY_UNDEF, - .minio = 1000, .flush_on_last_del = FLUSH_UNUSED, .user_friendly_names = USER_FRIENDLY_NAMES_OFF, .fast_io_fail = 5, diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 7f0b66ece..277214f6d 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -866,13 +866,11 @@ select_minio_bio (struct config *conf, struct multipath * mp) int select_minio(struct config *conf, struct multipath *mp) { const char *origin; - unsigned int minv_dmrq[3] = {1, 1, 0}, version[3]; - if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) - && VERSION_GE(version, minv_dmrq)) - mp_set_default(minio, DEFAULT_MINIO_RQ); - else + if (mp->queue_mode == QUEUE_MODE_BIO) return select_minio_bio(conf, mp); + + mp_set_default(minio, DEFAULT_MINIO_RQ); condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin); return 0; } diff --git a/tests/test-lib.c b/tests/test-lib.c index ea99977ff..9a229e477 100644 --- a/tests/test-lib.c +++ b/tests/test-lib.c @@ -398,7 +398,6 @@ struct multipath *mock_multipath__(struct vectors *vecs, struct path *pp) struct multipath *mp; struct config *conf; struct mocked_path mop; - /* pretend new dm, use minio_rq, */ static const unsigned int fake_dm_tgt_version[3] = { 1, 1, 1 }; mocked_path_from_path(&mop, pp); @@ -414,7 +413,6 @@ struct multipath *mock_multipath__(struct vectors *vecs, struct path *pp) select_no_path_retry(conf, mp); will_return(__wrap_libmp_get_version, fake_dm_tgt_version); select_retain_hwhandler(conf, mp); - will_return(__wrap_libmp_get_version, fake_dm_tgt_version); select_minio(conf, mp); put_multipath_config(conf); From b6c7aab40ea83eafd634907794c21b5ba6f68e4d Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 15 Jun 2026 19:36:11 -0400 Subject: [PATCH 054/102] libmultipath: don't set hwhander for bio based devices The kernel prints an error message when a bio-based multipath device sets a hardware handler, since bio-based devices only support using the already attached hardware handler as-is. Change select_hwhandler() to not set a hardware handler for bio-based devices. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/propsel.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 277214f6d..0202aaef3 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -560,6 +560,11 @@ int select_hwhandler(struct config *conf, struct multipath *mp) dh_state = &handler[2]; + if (mp->queue_mode == QUEUE_MODE_BIO) { + mp->hwhandler = DEFAULT_HWHANDLER; + origin = "(setting: disabled due to \"queue_mode bio\")"; + goto set; + } /* * TPGS_UNDEF means that ALUA support couldn't determined either way * yet, probably because the path was always down. @@ -600,6 +605,7 @@ int select_hwhandler(struct config *conf, struct multipath *mp) mp->hwhandler = DEFAULT_HWHANDLER; origin = tpgs_origin; } +set: mp->hwhandler = strdup(mp->hwhandler); condlog(3, "%s: hardware_handler = \"%s\" %s", mp->alias, mp->hwhandler, origin); From 0fbcc6ccaf3967dc4536288692159d8c95c49c57 Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:28:57 +0200 Subject: [PATCH 055/102] multipath-tools: delete obsolete information from multipath.conf.5 repeat_count (rr_min_io, rr_min_io_rq, rr_weight) has been unsupported since kernel 4.6 ( commits 90a4323ccfea and 21136f89d76d ). And also clean up other deprecated entries. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipath/multipath.conf.5.in | 69 +++-------------------------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 84cd1a0ab..e3a9811c9 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -6,7 +6,7 @@ .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . -.TH MULTIPATH.CONF 5 2025-09-12 Linux +.TH MULTIPATH.CONF 5 2026-05-27 Linux . . .\" ---------------------------------------------------------------------------- @@ -198,9 +198,8 @@ kernel multipath target: .TP 12 .I "round-robin 0" Choose the path for the next bunch of I/O by looping through every path in the -path group, sending \fBthe same number of I/O requests\fR to each path. Some -aspects of behavior can be controlled with the attributes: \fIrr_min_io\fR, -\fIrr_min_io_rq\fR and \fIrr_weight\fR. +path group, sending \fBthe same number of I/O requests\fR to each path. + .TP .I "queue-length 0" (Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on \fBthe lowest @@ -598,24 +597,12 @@ The default is: \fBmanual\fR . .TP .B rr_min_io -Number of I/O requests to route to a path before switching to the next in the -same path group. This is only for \fIBlock I/O\fR(BIO) based multipath and -only apply to \fIround-robin\fR path_selector. -.RS -.TP -The default is: \fB1000\fR -.RE +(Deprecated since kernel 4.6) This option is not supported anymore, and will be ignored. . . .TP .B rr_min_io_rq -Number of I/O requests to route to a path before switching to the next in the -same path group. This is only for \fIRequest\fR based multipath and -only apply to \fIround-robin\fR path_selector. -.RS -.TP -The default is: \fB1\fR -.RE +(Deprecated since kernel 4.6) This option is not supported anymore, and will be ignored. . . .TP @@ -634,16 +621,7 @@ The default is: \fBmax\fR . .TP .B rr_weight -If set to \fIpriorities\fR the multipath configurator will assign path weights -as "path prio * rr_min_io". Possible values are -.I priorities -or -.I uniform . -Only apply to \fIround-robin\fR path_selector. -.RS -.TP -The default is: \fBuniform\fR -.RE +(Deprecated since kernel 4.6) This option is not supported anymore, and will be ignored. . . .TP @@ -821,28 +799,16 @@ The default is: \fB\fR .TP .B bindings_file (Deprecated) This option is not supported anymore, and will be ignored. -.RS -.TP -The compiled-in value is: \fB@STATE_DIR@/bindings\fR -.RE . . .TP .B wwids_file (Deprecated) This option is not supported anymore, and will be ignored. -.RS -.TP -The compiled-in value is: \fB@STATE_DIR@/wwids\fR -.RE . . .TP .B prkeys_file (Deprecated) This option is not supported anymore, and will be ignored. -.RS -.TP -The compiled-in value is: \fB@STATE_DIR@/prkeys\fR -.RE . . .TP @@ -1008,10 +974,6 @@ The default is: \fB\fR .TP .B config_dir (Deprecated) This option is not supported anymore, and will be ignored. -.RS -.TP -The compiled-in value is: \fB@CONFIGDIR@\fR -.RE . . .TP @@ -1324,7 +1286,6 @@ The default is: \fBno\fR .TP .B disable_changed_wwids (Deprecated) This option is not supported anymore, and will be ignored. -.RE . . .TP @@ -1580,14 +1541,8 @@ section: .TP .B failback .TP -.B rr_weight -.TP .B no_path_retry .TP -.B rr_min_io -.TP -.B rr_min_io_rq -.TP .B flush_on_last_del .TP .B features @@ -1769,14 +1724,8 @@ section: .TP .B failback .TP -.B rr_weight -.TP .B no_path_retry .TP -.B rr_min_io -.TP -.B rr_min_io_rq -.TP .B fast_io_fail_tmo .TP .B dev_loss_tmo @@ -1855,14 +1804,8 @@ the values are taken from the \fIdevices\fR or \fIdefaults\fR sections: .TP .B failback .TP -.B rr_weight -.TP .B no_path_retry .TP -.B rr_min_io -.TP -.B rr_min_io_rq -.TP .B flush_on_last_del .TP .B fast_io_fail_tmo From ca0d44db1bb48da1d5e398b87c449fe3b552174a Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:28:58 +0200 Subject: [PATCH 056/102] multipath-tools: remove explicit width from .TP macros in multipath.conf.5 This allows man/groff to calculate the width automatically, preventing excessive whitespace and improving readability on various terminal sizes. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipath/multipath.conf.5.in | 56 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index e3a9811c9..ef08ed2b3 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -103,7 +103,7 @@ matches, standard regular expression syntax using the special characters "^" and .LP . The following \fIsection\fP keywords are recognized: -.TP 17 +.TP .B defaults This section defines default values for attributes which are used whenever no values are given in the appropriate device or multipath @@ -142,7 +142,7 @@ device-specific settings for all devices. The \fIdefaults\fR section recognizes the following keywords: . . -.TP 17 +.TP .B verbosity Default verbosity. Higher values increase the verbosity level. Valid levels are between 0 and 6. @@ -195,7 +195,7 @@ The default is: \fBno\fR The default path selector algorithm to use; they are offered by the kernel multipath target: .RS -.TP 12 +.TP .I "round-robin 0" Choose the path for the next bunch of I/O by looping through every path in the path group, sending \fBthe same number of I/O requests\fR to each path. @@ -223,7 +223,7 @@ The default is: \fBservice-time 0\fR (Hardware-dependent) The default path grouping policy to apply to unspecified multipaths. Possible values are: .RS -.TP 12 +.TP .I failover One path per priority group. .TP @@ -333,7 +333,7 @@ of this path. Higher number have a higher priority. \fI"none"\fR is a valid value. Currently the following path priority routines are implemented: .RS -.TP 12 +.TP .I const Return a constant priority of \fI1\fR. .TP @@ -408,12 +408,12 @@ NetAPP E/EF Series, where it is \fBalua\fR). If \fBdetect_prio\fR is Arguments to pass to to the prio function. This only applies to certain prioritizers: .RS -.TP 12 +.TP .I weighted Needs a value of the form \fI" ..."\fR .RS -.TP 8 +.TP .I hbtl Regex can be of SCSI H:B:T:L format. For example: 1:0:.:. , *:0:0:. .TP @@ -430,11 +430,11 @@ Regex can be of the form \fI"host_wwnn:host_wwpn:target_wwnn:target_wwpn"\fR these values can be looked up through sysfs or by running \fImultipathd show paths format "%N:%R:%n:%r"\fR. For example: 0x200100e08ba0aea0:0x210100e08ba0aea0:.*:.* , .*:.*:iqn.2009-10.com.redhat.msp.lab.ask-06:.* .RE -.TP 12 +.TP .I path_latency Needs a value of the form "io_num=\fI<20>\fR base_num=\fI<10>\fR" .RS -.TP 8 +.TP .I io_num The number of read IOs sent to the current path continuously, used to calculate the average path latency. Valid Values: Integer, [20, 200]. @@ -445,7 +445,7 @@ Double-precision floating-point, [1.1, 10]. And Max average latency value is 100 For example: If base_num=10, the paths will be grouped in priority groups with path latency <=1us, (1us, 10us], (10us, 100us], (100us, 1ms], (1ms, 10ms], (10ms, 100ms], (100ms, 1s], (1s, 10s], (10s, 100s], >100s. .RE -.TP 12 +.TP .I alua If \fIexclusive_pref_bit\fR is set, paths with the \fIpreferred path\fR bit set will always be in their own path group. @@ -456,17 +456,17 @@ set will always be in their own path group. .TP .I datacore .RS -.TP 8 +.TP .I preferredsds (Mandatory) The preferred "SDS name". .TP .I timeout (Optional) The timeout for the INQUIRY, in ms. .RE -.TP 12 +.TP .I iet .RS -.TP 8 +.TP .I preferredip=... (Mandatory) Th preferred IP address, in dotted decimal notation, for iSCSI targets. .RE @@ -481,7 +481,7 @@ Specify any device-mapper features to be used. Syntax is \fInum list\fR where \fInum\fR is the number, between 0 and 8, of features in \fIlist\fR. Possible values for the feature list are: .RS -.TP 12 +.TP .I queue_if_no_path (Deprecated, superseded by \fIno_path_retry\fR) Queue I/O if no path is active. Identical to the \fIno_path_retry\fR with \fIqueue\fR value. If both this @@ -520,7 +520,7 @@ to respond. The asynchronous checkers (\fItur\fR and \fIdirectio\fR) will not pause multipathd. Instead, multipathd will check for a response once per second, until \fIchecker_timeout\fR seconds have elapsed. Possible values are: .RS -.TP 12 +.TP .I readsector0 (Deprecated) Read the first sector of the device. This checker is being deprecated, please use \fItur\fR or \fIdirectio\fR instead. @@ -574,7 +574,7 @@ Tell multipathd how to manage path group failback. To select \fIimmediate\fR or a \fIvalue\fR, it's mandatory that the device has support for a working prioritizer. .RS -.TP 12 +.TP .I immediate Immediately failback to the highest priority pathgroup that contains active paths. @@ -628,7 +628,7 @@ The default is: \fBmax\fR .B no_path_retry Specify what to do when all paths are down. Possible values are: .RS -.TP 12 +.TP .I value > 0 Number of retries until disable I/O queueing. .TP @@ -1154,7 +1154,7 @@ blocked from further processing by higher layers - such as LVM - if and only if it\'s considered a valid multipath device path), and b) when multipathd detects a new device. The following values are possible: .RS -.TP 10 +.TP .I strict Both multipath and multipathd treat only such devices as multipath devices which have been part of a multipath map previously, and which are therefore @@ -1421,7 +1421,7 @@ by multipath-tools. . The following keywords are recognized in both sections. The defaults are empty unless explicitly stated. -.TP 17 +.TP .B devnode Regular expression matching the device nodes to be excluded/included. .RS @@ -1511,7 +1511,7 @@ from later entries take precedence. . . The \fImultipath\fR subsection recognizes the following attributes: -.TP 17 +.TP .B wwid (Mandatory) World Wide Identifier. Detected multipath maps are matched against this attribute. Note that, unlike the \fIwwid\fR attribute in the \fIblacklist\fR section, @@ -1530,7 +1530,7 @@ section: .sp 1 .PD .1v .RS -.TP 18 +.TP .B path_grouping_policy .TP .B path_selector @@ -1587,7 +1587,7 @@ section: .SH "devices section" .\" ---------------------------------------------------------------------------- . -.TP 4 +.TP .B Important: The built-in hardware device table of .I multipath-tools @@ -1623,7 +1623,7 @@ for all devices in the system. .LP . The \fIdevice\fR subsection recognizes the following attributes: -.TP 17 +.TP .B vendor (Mandatory) Regular expression to match the vendor name. .RS @@ -1666,7 +1666,7 @@ and \fImultipathd show paths format\fR commands. Currently only the The hardware handler to use for this device type. The following hardware handler are implemented: .RS -.TP 12 +.TP .I 1 emc (Hardware-dependent) Hardware handler for DGC class arrays as CLARiiON CX/AX and EMC VNX families @@ -1707,7 +1707,7 @@ section: .sp 1 .PD .1v .RS -.TP 18 +.TP .B path_grouping_policy .TP .B uid_attribute @@ -1785,7 +1785,7 @@ the values are taken from the \fIdevices\fR or \fIdefaults\fR sections: .sp 1 .PD .1v .RS -.TP 18 +.TP .B path_grouping_policy .TP .B uid_attribute @@ -1899,7 +1899,7 @@ which paths belong to the same device. Each path presenting the same WWID is assumed to point to the same device. .LP The WWID is generated by four methods (in the order of preference): -.TP 17 +.TP .B uid_attrs The WWID is derived from udev attributes by matching the device node name; cf \fIuid_attrs\fR above. @@ -1939,7 +1939,7 @@ pathgroups will not be used until all other pathgroups have been tried. At the time when the path would normally be reinstated, it will be returned to its normal pathgroup. The logic of determining \(dqshaky\(dq condition, as well as the logic when to reinstate, differs between the three methods. -.TP 8 +.TP .B \(dqdelay_checks\(dq failure tracking (Deprecated) This method is \fBdeprecated\fR and mapped to the \(dqsan_path_err\(dq method. See the \fIdelay_watch_checks\fR and \fIdelay_wait_checks\fR options above From 6ddd435c97e5b7fac622675517862ddb9187df13 Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:28:59 +0200 Subject: [PATCH 057/102] multipath-tools: remove explicit widths from macros in the remaining man pages This allows man/groff to calculate the width automatically, preventing excessive whitespace and improving readability on various terminal sizes. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmpathpersist/mpath_persistent_reserve_in.3 | 4 ++-- libmpathpersist/mpath_persistent_reserve_out.3 | 4 ++-- mpathpersist/mpathpersist.8.in | 2 +- multipath/multipath.8.in | 6 +++--- multipath/multipath.conf.5.in | 10 +++++----- multipathd/multipathd.8.in | 9 ++++----- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index 4ac43fa37..871242d56 100644 --- a/libmpathpersist/mpath_persistent_reserve_in.3 +++ b/libmpathpersist/mpath_persistent_reserve_in.3 @@ -35,7 +35,7 @@ the DM device and gets the response. .TP .B Parameters: .RS -.TP 12 +.TP .I fd The file descriptor of a multipath device. Input argument. .TP @@ -57,7 +57,7 @@ Set verbosity level. Input argument. value:[0-3]. 0->Errors, 1->Warnings, 2->Inf .SH RETURNS .\" ---------------------------------------------------------------------------- . -.TP 12 +.TP .B MPATH_PR_SUCCESS If PR command successful. .TP diff --git a/libmpathpersist/mpath_persistent_reserve_out.3 b/libmpathpersist/mpath_persistent_reserve_out.3 index ab1f923e1..bf44777c3 100644 --- a/libmpathpersist/mpath_persistent_reserve_out.3 +++ b/libmpathpersist/mpath_persistent_reserve_out.3 @@ -35,7 +35,7 @@ the DM device and gets the response. .TP .B Parameters: .RS -.TP 12 +.TP .I fd The file descriptor of a multipath device. Input argument. .TP @@ -74,7 +74,7 @@ Set verbosity level. Input argument. value: 0 to 3. 0->Errors, 1->Warnings, 2->I .SH RETURNS .\" ---------------------------------------------------------------------------- . -.TP 12 +.TP .B MPATH_PR_SUCCESS If PR command successful else returns any one of the status mentioned below. .TP diff --git a/mpathpersist/mpathpersist.8.in b/mpathpersist/mpathpersist.8.in index fecef0d63..9a5a716bd 100644 --- a/mpathpersist/mpathpersist.8.in +++ b/mpathpersist/mpathpersist.8.in @@ -50,7 +50,7 @@ various options. .BI \-verbose|\-v " level" Verbosity: .RS -.TP 5 +.TP .I 0 Critical messages. .TP diff --git a/multipath/multipath.8.in b/multipath/multipath.8.in index b88e9a4ca..0b170101b 100644 --- a/multipath/multipath.8.in +++ b/multipath/multipath.8.in @@ -100,7 +100,7 @@ The \fBdevice\fR argument restricts \fBmultipath\fR's operation to devices match expression. The argument may refer either to a multipath map or to its components ("paths"). The expression may be in one of the following formats: . -.TP 1.4i +.TP .B device node file name of a device node, e.g. \fI/dev/dm-10\fR or \fI/dev/sda\fR. If the node refers to an existing device mapper device representing a multipath map, this selects @@ -195,8 +195,8 @@ creating \fI@CONFIGFILE@\fR. .BI \-v " level" Verbosity of information printed to stdout in default and "list" operation modes. The default level is \fI-v 2\fR. -.RS 1.2i -.TP 1.2i +.RS +.TP .I 0 Nothing is printed. .TP diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index ef08ed2b3..6990d2bfa 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -1527,7 +1527,7 @@ the same WWID in the \fIbindings_file\fR. The following attributes are optional; if not set the default values are taken from the \fIoverrides\fR, \fIdevices\fR, or \fIdefaults\fR section: -.sp 1 +.sp .PD .1v .RS .TP @@ -1704,7 +1704,7 @@ has no effect. The following attributes are optional; if not set the default values are taken from the \fIdefaults\fR section: -.sp 1 +.sp .PD .1v .RS .TP @@ -1782,7 +1782,7 @@ section: . The overrides section recognizes the following optional attributes; if not set the values are taken from the \fIdevices\fR or \fIdefaults\fR sections: -.sp 1 +.sp .PD .1v .RS .TP @@ -1878,7 +1878,7 @@ exactly. The protocol that a path is using can be viewed by running .LP The following attributes are optional; if not set, the default values are taken from the \fIoverrides\fR, \fIdevices\fR, or \fIdefaults\fR section: -.sp 1 +.sp .PD .1v .RS .TP @@ -1969,7 +1969,7 @@ increase and the threshold is never reached. Ticks are the time between path checks by multipathd, which is variable and controlled by the \fIpolling_interval\fR and \fImax_polling_interval\fR parameters. . -.RS 8 +.RS .LP This algorithm is superseded by the \(dqmarginal_path\(dq failure tracking, but remains supported for backward compatibility. diff --git a/multipathd/multipathd.8.in b/multipathd/multipathd.8.in index 38a243e3e..d766dc65b 100644 --- a/multipathd/multipathd.8.in +++ b/multipathd/multipathd.8.in @@ -416,7 +416,7 @@ appropriate device information. The following wildcards are supported. .TP .B Multipath format wildcards .RS -.TP 12 +.TP .B %n The device name. .TP @@ -521,7 +521,7 @@ the configured one). .TP .B Path format wildcards .RS -.TP 12 +.TP .B %w The device WWID (uuid). .TP @@ -548,7 +548,7 @@ sysfs states, it shows "running". .B %T The multipathd path checker state of the device. The possible states are: .RS -.TP 12 +.TP .I ready The device is ready to handle IO. .TP @@ -693,8 +693,7 @@ Overrides the \fImax_fds\fR configuration setting. . .BR multipathc (8), .BR multipath (8), -.BR kpartx (8) -.RE +.BR kpartx (8), .BR sd_notify (3), .BR systemd.service (5). . From 17df8d7612e08f05a250fcfd506d57b935d34949 Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:29:00 +0200 Subject: [PATCH 058/102] multipath-tools: remove syntax check comments from man pages Remove the embedded groff/man command lines from the header comments of all man pages. Suggested-by: Martin Wilck Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- kpartx/kpartx.8 | 4 ---- libmpathpersist/mpath_persistent_reserve_in.3 | 4 ---- libmpathpersist/mpath_persistent_reserve_out.3 | 4 ---- mpathpersist/mpathpersist.8.in | 4 ---- multipath/multipath.8.in | 4 ---- multipath/multipath.conf.5.in | 4 ---- multipathd/multipathc.8 | 4 ---- multipathd/multipathd.8.in | 4 ---- 8 files changed, 32 deletions(-) diff --git a/kpartx/kpartx.8 b/kpartx/kpartx.8 index ef8051a50..fbc8f726b 100644 --- a/kpartx/kpartx.8 +++ b/kpartx/kpartx.8 @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t kpartx/kpartx.8 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z kpartx/kpartx.8 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index 871242d56..5109fb033 100644 --- a/libmpathpersist/mpath_persistent_reserve_in.3 +++ b/libmpathpersist/mpath_persistent_reserve_in.3 @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t libmpathpersist/mpath_persistent_reserve_in.3 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z libmpathpersist/mpath_persistent_reserve_in.3 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/libmpathpersist/mpath_persistent_reserve_out.3 b/libmpathpersist/mpath_persistent_reserve_out.3 index bf44777c3..09708b93d 100644 --- a/libmpathpersist/mpath_persistent_reserve_out.3 +++ b/libmpathpersist/mpath_persistent_reserve_out.3 @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t libmpathpersist/mpath_persistent_reserve_out.3 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z libmpathpersist/mpath_persistent_reserve_out.3 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/mpathpersist/mpathpersist.8.in b/mpathpersist/mpathpersist.8.in index 9a5a716bd..d5f5a4526 100644 --- a/mpathpersist/mpathpersist.8.in +++ b/mpathpersist/mpathpersist.8.in @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t mpathpersist/mpathpersist.8 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z mpathpersist/mpathpersist.8 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/multipath/multipath.8.in b/multipath/multipath.8.in index 0b170101b..da5effb07 100644 --- a/multipath/multipath.8.in +++ b/multipath/multipath.8.in @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipath/multipath.8 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z multipath/multipath.8 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 6990d2bfa..422ab1208 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipath/multipath.conf.5 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z multipath/multipath.conf.5 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/multipathd/multipathc.8 b/multipathd/multipathc.8 index cf7ae5be1..5e97fb81a 100644 --- a/multipathd/multipathc.8 +++ b/multipathd/multipathc.8 @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipathd/multipathc.8 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z multipathd/multipathc.8 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . diff --git a/multipathd/multipathd.8.in b/multipathd/multipathd.8.in index d766dc65b..93625a908 100644 --- a/multipathd/multipathd.8.in +++ b/multipathd/multipathd.8.in @@ -1,8 +1,4 @@ .\" ---------------------------------------------------------------------------- -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipathd/multipathd.8 -.\" man --warnings -E UTF-8 -l -Tutf8 -Z multipathd/multipathd.8 > /dev/null -.\" .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . From b9567538b2b9a9f497d96dd406d316a1a60a1f8a Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Thu, 18 Jun 2026 15:45:20 +0200 Subject: [PATCH 059/102] multipath-tools: clarify path selector descriptions in multipath.conf.5 Selectors operate on the next individual I/O request rather than a "bunch" of I/O. Suggested-by: Benjamin Marzinski Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipath/multipath.conf.5.in | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 422ab1208..287441bd8 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -2,7 +2,7 @@ .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . -.TH MULTIPATH.CONF 5 2026-05-27 Linux +.TH MULTIPATH.CONF 5 2026-06-13 Linux . . .\" ---------------------------------------------------------------------------- @@ -193,20 +193,18 @@ kernel multipath target: .RS .TP .I "round-robin 0" -Choose the path for the next bunch of I/O by looping through every path in the -path group, sending \fBthe same number of I/O requests\fR to each path. - +Choose the path for the next I/O request by \fBcycling through the path group\fR. .TP .I "queue-length 0" -(Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on \fBthe lowest +(Since 2.6.31 kernel) Choose the path for the next I/O request based on \fBthe lowest number of outstanding in-flight I/O requests\fR to the path. .TP .I "service-time 0" -(Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on \fBthe +(Since 2.6.31 kernel) Choose the path for the next I/O request based on \fBthe lowest total size (in bytes) of outstanding in-flight I/O requests\fR to the path. .TP .I "historical-service-time 0" -(Since 5.8 kernel) Choose the path for the next bunch of I/O with \fBa dynamic +(Since 5.8 kernel) Choose the path for the next I/O request with \fBa dynamic algorithm based on the historical service time and the number of outstanding in-flight I/O requests\fR to the path. .TP From fbff90a53ca83a1a1f4252b313d145c35403240c Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:29:02 +0200 Subject: [PATCH 060/102] multipath-tools: specify file paths using variables and add FILES section in multipath.conf.5 Replace hardcoded file name references with their dynamic build variables. Also, add a dedicated FILES section at the end of the man page to clearly document configuration files/dirs. Suggested-by: Benjamin Marzinski Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipath/multipath.conf.5.in | 55 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 287441bd8..9289b244d 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -27,7 +27,7 @@ or \fBmultipathd show config\fR command. . .PP Additional configuration can be made in drop-in files under -.B @CONFIGDIR@. +.BR @CONFIGDIR@/ . Files ending in \fI.conf\fR in this directory are read in alphabetical order, after reading \fI@CONFIGFILE@\fR. They use the same syntax as \fI@CONFIGFILE@\fR itself, @@ -831,11 +831,11 @@ to the end of the reservation key. .RS .PP Alternatively, this can be set to \fBfile\fR, which will store the RESERVATION -KEY registered by mpathpersist in the \fIprkeys_file\fR. multipathd will then -use this key to register additional paths as they appear. When the +KEY registered by mpathpersist in the \fI@STATE_DIR@/prkeys\fR file. multipathd +will then use this key to register additional paths as they appear. When the registration is removed, the RESERVATION KEY is removed from the -\fIprkeys_file\fR. The prkeys file will automatically keep track of whether -the key was registered with \fI--param-aptpl\fR. +\fI@STATE_DIR@/prkeys\fR file. The prkeys file will automatically keep track of +whether the key was registered with \fI--param-aptpl\fR. .TP The default is: \fB\fR .RE @@ -1152,9 +1152,9 @@ detects a new device. The following values are possible: .I strict Both multipath and multipathd treat only such devices as multipath devices which have been part of a multipath map previously, and which are therefore -listed in the \fBwwids_file\fR. Users can manually set up multipath maps using the -\fBmultipathd add map\fR command. Once set up manually, the map is -remembered in the wwids file and will be set up automatically in the future. +listed in the \fI@STATE_DIR@/wwids\fR file. Users can manually set up multipath +maps using the \fBmultipathd add map\fR command. Once set up manually, the map is +remembered in the WWIDs file and will be set up automatically in the future. .TP .I off Multipath behaves like \fBstrict\fR. Multipathd behaves like \fBgreedy\fR. @@ -1513,8 +1513,8 @@ this is \fBnot\fR a regular expression or a substring; WWIDs must match exactly inside the multipaths section. .TP .B alias -Symbolic name for the multipath map. This takes precedence over a an entry for -the same WWID in the \fIbindings_file\fR. +Symbolic name for the multipath map. This takes precedence over an entry for +the same WWID in the \fI@STATE_DIR@/bindings\fR file. .LP . . @@ -1598,9 +1598,9 @@ for more than 100 known multipath-capable storage devices. The devices section can be used to override these settings. If there are multiple matches for a given device, the attributes of all matching entries are applied to it. If an attribute is specified in several matching device subsections, -later entries take precedence. Thus, entries in files under \fIconfig_dir\fR (in -reverse alphabetical order) have the highest precedence, followed by entries -in \fI@CONFIGFILE@\fR; the built-in hardware table has the lowest +later entries take precedence. Thus, entries in files under the \fI@CONFIGDIR@/\fR +directory (in reverse alphabetical order) have the highest precedence, followed +by entries in \fI@CONFIGFILE@\fR; the built-in hardware table has the lowest precedence. Inside a configuration file, later entries have higher precedence than earlier ones. .LP @@ -2028,6 +2028,35 @@ specified the order of precedence is \fIno_path_retry, queue_if_no_path, dev_los . . .\" ---------------------------------------------------------------------------- +.SH "FILES" +.\" ---------------------------------------------------------------------------- +. +Default paths for configuration files and directories: +.TP +.I @CONFIGFILE@ +Main configuration file. +.TP +.I @CONFIGDIR@/ +Directory for configuration drop-in files. +.TP +.I @STATE_DIR@/bindings +Bindings file, used when the \fBuser_friendly_names\fR option is set to +\fByes\fR to assign a persistent and unique alias to each multipath device. +.TP +.I @STATE_DIR@/prkeys +Persistent reservation keys file, used by multipathd to keep track of the +reservation key used for a specific WWID when \fBreservation_key\fR is set +to \fBfile\fR. +.TP +.I @STATE_DIR@/wwids +WWIDs file, used by multipath to keep track of the WWIDs of LUNs for which +multipath devices have been created in the past. +.TP +.I @RUNTIME_DIR@/multipathd.pid +PID file. +. +. +.\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . From 175fae217d4a64407d8d3e82639cc56b96c1be97 Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:29:03 +0200 Subject: [PATCH 061/102] multipath-tools: clarify user_friendly_names prefix behavior in multipath.conf.5 Clarify that the alias prefix depends on the alias_prefix setting. Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipath/multipath.conf.5.in | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 9289b244d..107a0f93d 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -704,11 +704,13 @@ The default is: \fBunused\fR .B user_friendly_names If set to .I yes -, using the bindings file \fI@STATE_DIR@/bindings\fR to assign a persistent -and unique alias to the multipath, in the form of mpath. If set to +, the bindings file \fI@STATE_DIR@/bindings\fR is used to assign a persistent +and unique alias to the multipath, in the form of \fIprefix\fR (where +\fIprefix\fR is defined by \fIalias_prefix\fR; by default \fImpath\fR). +If set to .I no -use the WWID as the alias. In either case this be will -be overridden by any specific aliases in the \fImultipaths\fR section. +, the WWID is used as the alias. In either case, this will be overridden +by any specific aliases defined in the \fImultipaths\fR section. .RS .TP The default is: \fBno\fR From f69f8dfe9ce13fe9d255d62a6beb39705a725f1e Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Tue, 16 Jun 2026 14:29:04 +0200 Subject: [PATCH 062/102] multipath-tools: fix mandoc errors/warnings in man pages Fix the following errors and warnings reported by mandoc linting: $ mandoc -W all -T lint man_page.X | grep -v "STYLE: input text line longer than 80 bytes" mandoc: ./libmpathpersist/mpath_persistent_reserve_in.3:22:2: WARNING: skipping paragraph macro: PP empty mandoc: ./libmpathpersist/mpath_persistent_reserve_out.3:22:2: WARNING: skipping paragraph macro: PP empty mandoc: ./mpathpersist/mpathpersist.8.in:172:2: WARNING: skipping paragraph macro: PP after SH mandoc: ./mpathpersist/mpathpersist.8.in:215:2: WARNING: skipping paragraph macro: PP after SH mandoc: ./multipathd/multipathc.8:19:15: STYLE: whitespace at end of input line mandoc: ./multipathd/multipathd.8.in:573:2: WARNING: skipping paragraph macro: PP empty mandoc: ./multipathd/multipathd.8.in:573:2: WARNING: skipping paragraph macro: PP empty mandoc: ./multipath/multipath.conf.5.in:130:2: ERROR: skipping end of block that is not open: RE mandoc: ./multipath/multipath.conf.5.in:1979:2: ERROR: skipping end of block that is not open: RE mandoc: ./multipath/multipath.conf.5.in:89:2: WARNING: skipping paragraph macro: PP empty mandoc: ./multipath/multipath.conf.5.in:131:2: WARNING: skipping paragraph macro: PP empty mandoc: ./multipath/multipath.conf.5.in:130:2: WARNING: skipping paragraph macro: br at the end of SH mandoc: ./multipath/multipath.conf.5.in:1577:2: WARNING: skipping paragraph macro: PP empty mandoc: ./multipath/multipath.conf.5.in:1770:2: WARNING: skipping paragraph macro: PP empty Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM_DEVEL-ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmpathpersist/mpath_persistent_reserve_in.3 | 1 - libmpathpersist/mpath_persistent_reserve_out.3 | 1 - mpathpersist/mpathpersist.8.in | 2 -- multipath/multipath.conf.5.in | 6 ------ multipathd/multipathc.8 | 2 +- multipathd/multipathd.8.in | 1 - 6 files changed, 1 insertion(+), 12 deletions(-) diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index 5109fb033..36cc78151 100644 --- a/libmpathpersist/mpath_persistent_reserve_in.3 +++ b/libmpathpersist/mpath_persistent_reserve_in.3 @@ -19,7 +19,6 @@ mpath_persistent_reserve_in \- send PRIN command to DM device .B #include .P .BI "int mpath_persistent_reserve_in" "(int fd, int rq_servact, struct prin_resp *resp, int noisy, int verbose)" -.P . . .\" ---------------------------------------------------------------------------- diff --git a/libmpathpersist/mpath_persistent_reserve_out.3 b/libmpathpersist/mpath_persistent_reserve_out.3 index 09708b93d..1a2b9511f 100644 --- a/libmpathpersist/mpath_persistent_reserve_out.3 +++ b/libmpathpersist/mpath_persistent_reserve_out.3 @@ -19,7 +19,6 @@ mpath_persistent_reserve_out \- send PROUT command to DM device .B #include .P .BI "int mpath_persistent_reserve_out" "(int fd, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, int verbose)" -.P . . .\" ---------------------------------------------------------------------------- diff --git a/mpathpersist/mpathpersist.8.in b/mpathpersist/mpathpersist.8.in index d5f5a4526..2208008c2 100644 --- a/mpathpersist/mpathpersist.8.in +++ b/mpathpersist/mpathpersist.8.in @@ -169,7 +169,6 @@ PR In: maximum allocation length. LEN is a decimal number between 0 and 8192. .SH EXAMPLE .\" ---------------------------------------------------------------------------- . -.PP Register the key \(dq123abc\(dq for the /dev/mapper/mpath9 device: .RS \fBmpathpersist --out --register --param-sark=\fI123abc /dev/mapper/mpath9\fR @@ -212,7 +211,6 @@ Remove current reservation, and unregister all registered keys from all I_T nexu .SH BATCH FILES .\" ---------------------------------------------------------------------------- . -.PP The option \fI--batch-file\fR (\fI-f\fR) sets an input file to be processed by \fBmpathpersist\fR. Grouping commands in batch files can provide a speed improvement in particular on large installments, because \fBmpathpersist\fR diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 107a0f93d..e6b4fc040 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -88,7 +88,6 @@ the indentation shown in the above example is helpful for human readers but not mandatory. .LP . -.LP .B Note on regular expressions: The \fI@CONFIGFILE@\fR syntax allows many attribute values to be specified as POSIX Extended Regular Expressions (see \fBregex\fR(7)). These regular expressions @@ -127,8 +126,6 @@ vendor, product, and revision. .B overrides This section defines values for attributes that should override the device-specific settings for all devices. -.RE -.LP . . .\" ---------------------------------------------------------------------------- @@ -1576,7 +1573,6 @@ section: .B ghost_delay .RE .PD -.LP . . .\" ---------------------------------------------------------------------------- @@ -1769,7 +1765,6 @@ section: .B all_tg_pt .RE .PD -.LP . . .\" ---------------------------------------------------------------------------- @@ -1978,7 +1973,6 @@ as integrity failures or congestion with so-called Fabric Performance Impact Notifications (FPINs).On receiving the fpin notifications through ELS multipathd will move the affected path and port states to marginal. . -.RE .LP See the documentation of the individual options above for details. diff --git a/multipathd/multipathc.8 b/multipathd/multipathc.8 index 5e97fb81a..2187d1eda 100644 --- a/multipathd/multipathc.8 +++ b/multipathd/multipathc.8 @@ -16,7 +16,7 @@ multipathc \- Interactive client for multipathd .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . -.B multipathc +.B multipathc .RB [\| .IR timeout .RB \|] diff --git a/multipathd/multipathd.8.in b/multipathd/multipathd.8.in index 93625a908..632cbe403 100644 --- a/multipathd/multipathd.8.in +++ b/multipathd/multipathd.8.in @@ -570,7 +570,6 @@ The device appears usable, but it being delayed for marginal path checking. .I undef The device either is not part of a multipath device, or its path checker has not yet run. -.PP .RE .TP .B %s From a92f54a53b245a7ea7477183fcc6716c03839c38 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 22 Jun 2026 12:49:09 +0200 Subject: [PATCH 063/102] Makefile: check for availablity of memfd_create() ... and skip the gpt test otherwise. Signed-off-by: Martin Wilck --- create-config.mk | 18 ++++++++++++++++++ tests/Makefile | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/create-config.mk b/create-config.mk index 712cef05c..8a8b01fea 100644 --- a/create-config.mk +++ b/create-config.mk @@ -60,6 +60,18 @@ check_var = $(shell \ echo "$$found" \ ) +check_compile = $(shell \ + if /bin/echo -e '$2' | gcc -D _GNU_SOURCE -o /dev/null -c -xc -; then \ + found=1; \ + status="yes"; \ + else \ + found=0; \ + status="no"; \ + fi; \ + echo 1>&2 "Compile-checking for $1 ... $$status"; \ + echo "$$found" \ + ) + # Test special behavior of gcc 4.8 with nested initializers # gcc 4.8 compiles blacklist.c only with -Wno-missing-field-initializers TEST_MISSING_INITIALIZERS = $(shell \ @@ -125,6 +137,11 @@ ifneq ($(call check_file,$(kernel_incdir)/linux/nvme_ioctl.h),0) ANA_SUPPORT := 1 endif +HASH = \# +ifneq ($(call check_compile,memfd_create,$(HASH)include \nvoid *c = memfd_create;),0) + MEMFD_SUPPORT := 1 +endif + ENABLE_LIBDMMP := $(call check_cmd,$(PKG_CONFIG) --exists json-c) ifeq ($(ENABLE_DMEVENTS_POLL),0) @@ -201,3 +218,4 @@ $(TOPDIR)/config.mk: $(multipathdir)/autoconfig.h @echo "W_URCU_TYPE_LIMITS := $(call TEST_URCU_TYPE_LIMITS)" >>$@ @echo "ENABLE_LIBDMMP := $(ENABLE_LIBDMMP)" >>$@ @echo "C_STD := $(C_STD)" >>$@ + @echo "MEMFD_SUPPORT := $(MEMFD_SUPPORT)" >>$@ diff --git a/tests/Makefile b/tests/Makefile index fa1fd186d..acb116f66 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,7 +10,7 @@ LIBDEPS += -L. -L $(mpathutildir) -L$(mpathcmddir) -lmultipath -lmpathutil -lmpa TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ alias directio valid devt mpathvalid strbuf sysfs features cli mapinfo runner \ - shared_ptr gpt + shared_ptr $(if $(MEMFD_SUPPORT),gpt) HELPERS := test-lib.o test-log.o .PRECIOUS: $(TESTS:%=%-test) From 42ef330c78c53e2ea81e02e9eefbbde4ce6799f3 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 10:17:50 +0200 Subject: [PATCH 064/102] multipath-tools Makefile: collect core dumps in test-outputs.tar Signed-off-by: Martin Wilck --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cabbe28d2..e8de94935 100644 --- a/Makefile +++ b/Makefile @@ -137,10 +137,10 @@ test-progs.cpio: test-progs test-progs.tar: test-progs $(Q)tar cf $@ $(TEST-ARTIFACTS) -test-outputs.cpio: $(wildcard tests/*.out) $(wildcard tests/*.vgr) +test-outputs.cpio: $(wildcard tests/*.out) $(wildcard tests/*.vgr) $(wildcard tests/core*) $(Q)printf "%s\\n" $^ | cpio -o -H crc >$@ -test-outputs.tar: $(wildcard tests/*.out) $(wildcard tests/*.vgr) +test-outputs.tar: $(wildcard tests/*.out) $(wildcard tests/*.vgr) $(wildcard tests/core*) $(Q)tar cf "$@" $^ || touch $@ .PHONY: TAGS From 43e1827bddfc65875980b1f3542d714a2e1871f4 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 10:18:17 +0200 Subject: [PATCH 065/102] multipath-tools tests: enable coredumps when running tests Signed-off-by: Martin Wilck --- tests/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index acb116f66..23f7d2bb2 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -83,7 +83,8 @@ lib/libchecktur.so: %.out: %-test lib/libchecktur.so @echo == running $< == - @LD_LIBRARY_PATH=.:$(mpathutildir):$(mpathcmddir) ./$< >$@ 2>&1 || { cat "$@"; false; } + $(Q)ulimit -c unlimited; \ + LD_LIBRARY_PATH=.:$(mpathutildir):$(mpathcmddir) ./$< >$@ 2>&1 || { cat "$@"; false; } %.vgr: %-test lib/libchecktur.so @echo == running valgrind for $< == @@ -93,13 +94,14 @@ lib/libchecktur.so: OBJS = $(TESTS:%=%.o) $(HELPERS) runner-test.out: runner-test - $(Q)./runner-test.sh >$@ 2>&1 + $(Q)ulimit -c unlimited; \ + ./runner-test.sh >$@ 2>&1 runner-test.vgr: runner-test $(Q)VALGRIND=1 ./runner-test.sh >$@ 2>&1 test_clean: - $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* runner-test.out runner-test.vgr + $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* runner-test.out runner-test.vgr core* valgrind_clean: $(Q)$(RM) $(TESTS:%=%.vgr) From 2dba696bfcb45f2957bff2b460c86817a9a43e32 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 22 Jun 2026 18:50:02 +0200 Subject: [PATCH 066/102] multipath-tools tests: use ioctl_request_t for all ioctl calls Signed-off-by: Martin Wilck --- tests/directio.c | 6 ------ tests/dmevents.c | 4 ++-- tests/gpt.c | 8 ++++---- tests/vpd.c | 2 +- tests/wrap64.h | 8 ++++++++ 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/directio.c b/tests/directio.c index addb7cf26..dbd794a5f 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -25,12 +25,6 @@ const struct timespec one_sec = { .tv_sec = 1 }; const char *test_dev = NULL; unsigned int test_delay = 10000; -#ifdef __GLIBC__ -#define ioctl_request_t unsigned long -#else -#define ioctl_request_t int -#endif - int REAL_IOCTL(int fd, ioctl_request_t request, void *argp); int WRAP_IOCTL(int fd, ioctl_request_t request, void *argp) diff --git a/tests/dmevents.c b/tests/dmevents.c index c94ab016f..13fdb3085 100644 --- a/tests/dmevents.c +++ b/tests/dmevents.c @@ -240,9 +240,9 @@ int __wrap_dm_geteventnr(const char *name) return -1; } -int WRAP_IOCTL(int fd, unsigned long request, void *argp) +int WRAP_IOCTL(int fd, ioctl_request_t request, void *argp) { - condlog(1, "%s %ld", __func__, request); + condlog(1, "%s " IOCTL_FMT_, __func__, request); assert_int_equal(fd, waiter->fd); assert_uint_equal(request, DM_DEV_ARM_POLL); return mock_type(int); diff --git a/tests/gpt.c b/tests/gpt.c index 0d00e3049..395954042 100644 --- a/tests/gpt.c +++ b/tests/gpt.c @@ -59,7 +59,7 @@ int aligned_malloc(void **mem_p, size_t align, size_t *size_p) /* Block-device simulation */ /* ------------------------------------------------------------------ */ -static uint64_t test_disk_size_bytes; +static ioctl_request_t test_disk_size_bytes; int REAL_FSTAT_FUNC(int v, int fd, struct stat *buf); int WRAP_FSTAT_FUNC(int v, int fd, struct stat *buf) @@ -71,11 +71,11 @@ int WRAP_FSTAT_FUNC(int v, int fd, struct stat *buf) return rc; } -int REAL_IOCTL(int fd, unsigned long req, void *p); -int WRAP_IOCTL(int fd, unsigned long req, void *p) +int REAL_IOCTL(int fd, ioctl_request_t req, void *p); +int WRAP_IOCTL(int fd, ioctl_request_t req, void *p) { if (test_disk_size_bytes) { - if (req == BLKGETSIZE64) { + if (req == (ioctl_request_t)BLKGETSIZE64) { *(uint64_t *)p = test_disk_size_bytes; return 0; } diff --git a/tests/vpd.c b/tests/vpd.c index b02179483..19e759895 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -59,7 +59,7 @@ static const char vendor_id[] = "Linux"; static const char test_id[] = "A123456789AbcDefB123456789AbcDefC123456789AbcDefD123456789AbcDef"; -int WRAP_IOCTL(int fd, unsigned long request, void *param) +int WRAP_IOCTL(int fd, ioctl_request_t request, void *param) { int len; struct sg_io_hdr *io_hdr; diff --git a/tests/wrap64.h b/tests/wrap64.h index 3ea4b0aec..0f0ee1794 100644 --- a/tests/wrap64.h +++ b/tests/wrap64.h @@ -73,6 +73,14 @@ #define WRAP_IOCTL CONCAT2(__wrap_, WRAP_IOCTL_NAME) #define REAL_IOCTL CONCAT2(__real_, WRAP_IOCTL_NAME) +#ifdef __GLIBC__ +#define ioctl_request_t unsigned long +#define IOCTL_FMT_ "%lu" +#else +#define ioctl_request_t int +#define IOCTL_FMT_ "%d" +#endif + #if defined(__GLIBC__) && defined(LIBAIO_REDIRECT) && __BITS_PER_LONG == 32 \ && defined(_TIME_BITS) && _TIME_BITS == 64 #define WRAP_IO_GETEVENTS_NAME io_getevents_time64 From 08832778047917b1cabfdf0c0adbff32e93d9531 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 23 Jun 2026 18:44:23 +0200 Subject: [PATCH 067/102] multipath-tools tests: runner: avoid releasing context twice Signed-off-by: Martin Wilck --- tests/runner.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/runner.c b/tests/runner.c index aa59b43b9..c390812ab 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -231,11 +231,13 @@ void int_handler(int signal) static void terminate_all(void) { int i, count; + struct runner_context *rctx; for (count = 0, i = 0; i < N_RUNNERS; i++) if (context[i]) { - release_runner(context[i]); + rctx = context[i]; context[i] = NULL; + release_runner(rctx); count++; } condlog(3, "%s: %d runners released", __func__, count); @@ -269,6 +271,7 @@ static int run_test(int n) long max_wait = TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC + 100000 + RUNNER_START_DELAY_US; bool killed = false; + struct runner_context *rctx; for (i = 0; i < N_RUNNERS; i++) context[i] = start_runner(TIMEOUT_USEC, i, NOISE_USEC, NOISE_BIAS, @@ -305,15 +308,17 @@ static int run_test(int n) done++; /* fallthrough */ case RUNNER_DEAD: - release_runner(context[i]); + rctx = context[i]; context[i] = NULL; + release_runner(rctx); break; case RUNNER_CANCELLED: if (last) { - condlog(3, "%s: releasing %p", - __func__, context[i]); - release_runner(context[i]); + rctx = context[i]; context[i] = NULL; + condlog(3, "%s: releasing %p", + __func__, rctx); + release_runner(rctx); } else running++; break; From e9b0725336f4742a8a87e5a4dcee9fdb2f670b59 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 13:43:51 +0200 Subject: [PATCH 068/102] multipath-tools tests: runner: fix wrong output for noise interval Signed-off-by: Martin Wilck --- tests/runner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.c b/tests/runner.c index c390812ab..36d608a4c 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -553,7 +553,7 @@ int main(int argc, char *argv[]) if (TIMEOUT_USEC == 0) TIMEOUT_USEC = 1; - condlog(2, "Runner: timeout=%ld us, noise interval=[-%ld:%ld] us, steps=%d", + condlog(2, "Runner: timeout=%ld us, noise interval=[%ld:%ld] us, steps=%d", TIMEOUT_USEC, TIMEOUT_USEC - NOISE_USEC, TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC, SLEEP_STEPS); condlog(2, "Other : poll interval=%ld us, ignore cancellation=%s, runners=%d, repeat=%d, kill timeout=%ld us", From e41906a99de705102f2a5af7a87b49128697efff Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 13:47:46 +0200 Subject: [PATCH 069/102] multipath-tools tests: runner: avoid memory leaks by waiting for threads We use detached threads, and we ignore cancellation signals. Valgrind's thread creation can be quite slow. If some threads haven't terminated when the main program exits, valgrind will consider their runner_context "possibly lost", because the rctx pointer is not identical to the shared_ptr address that was allocated [1]. While this is non-fatal, and the memcheck option "--errors-for-leak-kinds=definite,indirect" could be used to avoid it being reported as error, it would be nicer for the CI to avoid this sort of problem in the first place. Add some wait time if leak checking is enabled, so that we can reasonably expect that all threads have terminated when the main program exits. This not a 100% fail-safe remedy. [1] https://valgrind.org/docs/manual/mc-manual.html#mc-manual.leaks Signed-off-by: Martin Wilck --- tests/runner.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/runner.c b/tests/runner.c index 36d608a4c..0e728b1b0 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -76,6 +76,7 @@ #include "runner.h" #include "runner.h" #include "globals.c" +#include "../third-party/valgrind/valgrind.h" #define MILLION 1000000 #define BILLION 1000000000 @@ -110,6 +111,14 @@ static bool IGNORE_CANCEL = false; /* gap in the paylod to similate larger size */ #define PAYLOAD_GAP 128 +/* Max time to wait for threads to finish on exit */ +#define MAX_SECS_WAIT_ON_EXIT 30 + +/* Min time to wait for threads to finish on exit */ +#define MIN_USECS_WAIT_ON_EXIT 100000 + +static bool MEMCHECK_ACTIVE = false; + struct payload { long wait_nsec; int steps; @@ -338,6 +347,27 @@ static int run_test(int n) condlog(2, "%10d%10d%10d%10d%10d", n, N_RUNNERS, N_RUNNERS - running, done, errors); + if (MEMCHECK_ACTIVE && IGNORE_CANCEL) { + const struct timespec high = { MAX_SECS_WAIT_ON_EXIT, 0 }; + const struct timespec low = { 0, 1000 * MIN_USECS_WAIT_ON_EXIT }; + + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&stop, &now, &stop); + if (timespeccmp(&stop, &low) < 0) + stop = low; + if (timespeccmp(&stop, &high) > 0) { + condlog(2, "%s: capping wait time (%lld.%06llds), valgrind may show \"possibly lost\" memory", + __func__, (long long)stop.tv_sec, + (long long)stop.tv_nsec / 1000); + stop = high; + } + if (stop.tv_sec >= 0) { + condlog(3, "%s: waiting %lld.%06llds for threads to finish", + __func__, (long long)stop.tv_sec, + (long long)stop.tv_nsec / 1000); + nanosleep(&stop, NULL); + } + } if (killed) { condlog(2, "%s: termination signal received", __func__); exit(0); @@ -372,6 +402,28 @@ static int setup_signal_handler(int sig, void (*handler)(int)) return 0; } +/* https://stackoverflow.com/questions/34813412/how-to-detect-if-building-with-address-sanitizer-when-building-with-gcc-4-8 + */ +#if defined(__has_feature) +#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) // for clang +#define __SANITIZE_ADDRESS__ // GCC already sets this +#endif +#endif + +static bool detect_memcheck(void) +{ +#if defined(__SANITIZE_ADDRESS__) + const char *asan; + + asan = getenv("ASAN_OPTIONS"); + if (asan && strstr(asan, "detect_leaks=1")) + return true; +#endif + if (RUNNING_ON_VALGRIND) + return true; + return false; +} + int run_tests(void) { int errors = 0, i; @@ -380,6 +432,7 @@ int run_tests(void) if (setup_signal_handler(SIGINT, int_handler) != 0) return -1; + MEMCHECK_ACTIVE = detect_memcheck(); rctxs = calloc(N_RUNNERS, sizeof(*context)); if (rctxs == NULL) /* arbitrary number to indicate OOM error */ From 7700e25b1bb8cf4ceac778e5127e42af98e6204f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 13:43:01 +0200 Subject: [PATCH 070/102] multipath-tools tests: runner-test.sh: Check if libasan is enabled ... and only set ASAN_OPTIONS in this case. Signed-off-by: Martin Wilck --- tests/runner-test.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/runner-test.sh b/tests/runner-test.sh index f9235eefc..57d04c2fc 100755 --- a/tests/runner-test.sh +++ b/tests/runner-test.sh @@ -8,16 +8,16 @@ RUNNER=./runner-test if [ "$VALGRIND" ]; then command -v valgrind >/dev/null && \ RUNNER="valgrind --leak-check=full --error-exitcode=128 --max-threads=5000 --suppressions=./runner-test.supp ./runner-test" +elif ldd ./runner-test | grep -q libasan; then + # LSAN is not supported on ppc64le + case $(uname -m) in + x86_64|aarch64) + export ASAN_OPTIONS="detect_leaks=1:detect_odr_violation=0" + export LSAN_OPTIONS="report_objects=1" + ;; + esac fi -# LSAN is not supported on ppc64le -case $(uname -m) in - x86_64|aarch64) - export ASAN_OPTIONS="detect_leaks=1:detect_odr_violation=0" - export LSAN_OPTIONS="report_objects=1" - ;; -esac - LONG= while [ $# -gt 0 ]; do case $1 in From 5f61295c8bb939ae2c30b7605b7c0615be8ae047 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 13:52:28 +0200 Subject: [PATCH 071/102] multipath-tools tests: runner-test.sh: further shorten test runtime The previous patch "avoid memory leaks by waiting for threads" increases the test runtime by several minutes if a memory-leak checker is enabled. The tests are also effective when runtimes are reduced some more. Do so, in order to make sure that GitHub CI doesn't run for an excessively long time. Signed-off-by: Martin Wilck --- tests/runner-test.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/runner-test.sh b/tests/runner-test.sh index 57d04c2fc..819e1871e 100755 --- a/tests/runner-test.sh +++ b/tests/runner-test.sh @@ -37,14 +37,14 @@ fi # Test scenarios # 1. timeout 1 us - test runner creation / cancellation races # 2. timeout 1 ms - test runner creation / cancellation races with -DRUNNER_START_DELAY_US=1000 -# 3. "realistic" test, scaled down by a factor 10 in time +# 3. "realistic" test, scaled down by a factor 30 in time # 4./5. Tests with high likelihood of completion / cancellation race set -- \ "-N 100 -p 1 -t 0 -n 2 -b 1 -s 1 -i -r 20" \ "-N 100 -p 1 -t 1 -n 2 -b 1 -s 1 -i -r 20" \ - "-N 1000 -p 100 -t 3000 -n 2999 -b 5 -s 1 -i -r 20 -k $TIME1" \ - "-N 1000 -p 10 -t 3000 -n 1 -b 1 -s 1 -i -r 20 -k $TIME2" \ - "-N 100 -p 1 -t 3000 -n 0 -s 1 -i -r 5" + "-N 1000 -p 100 -t 1000 -n 999 -b 5 -s 1 -i -r 20 -k $TIME1" \ + "-N 1000 -p 10 -t 1000 -n 1 -b 1 -s 1 -i -r 20 -k $TIME2" \ + "-N 100 -p 1 -t 1000 -n 0 -s 1 -i -r 5" errors=0 for args in "$@"; do From ff51c8463d2395cdc118b729fa7f6dcc800ffabd Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 18 Jun 2026 21:54:45 +0200 Subject: [PATCH 072/102] GitHub workflows: remove spelling action The check_spelling action seems to have been compromised. Disabling it. https://github.com/jsoref/2026-06-16-credential-leak/blob/main/README.md Signed-off-by: Martin Wilck --- .github/actions/spelling/expect.txt | 278 -------------------------- .github/actions/spelling/only.txt | 18 -- .github/actions/spelling/patterns.txt | 119 ----------- .github/workflows/spelling.yml | 197 ------------------ 4 files changed, 612 deletions(-) delete mode 100644 .github/actions/spelling/expect.txt delete mode 100644 .github/actions/spelling/only.txt delete mode 100644 .github/actions/spelling/patterns.txt delete mode 100644 .github/workflows/spelling.yml diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt deleted file mode 100644 index dd39ed672..000000000 --- a/.github/actions/spelling/expect.txt +++ /dev/null @@ -1,278 +0,0 @@ -abi -adt -aio -Alletra -alloc -alltgpt -alua -aptpl -ASAN -ascq -ata -autoconfig -autodetected -autoresize -barbie -BINDIR -bitfield -blkid -bmarzins -Bsymbolic -cciss -CFLAGS -cgroups -christophe -clangd -clariion -cmdline -cmocka -coldplug -commandline -COMPAQ -configdir -configfile -configurator -coverity -cplusplus -CPPFLAGS -ctx -dasd -datacore -ddf -Debian -DESTDIR -devmaps -devname -devnode -devpath -DEVTYPE -DGC -DIO -directio -disablequeueing -dmevent -dmi -dmmp -dmraid -dmsetup -dracut -EAGAIN -ECKD -emc -Engenio -EVPD -Exos -failback -failover -fds -fexceptions -FFFFFFFF -fge -fno -followover -forcequeueing -fpin -fulldescr -gcc -getprkey -getprstatus -getrlimit -getuid -github -gitlab -google -GPT -hbtl -hds -HITACHI -hotplug -HPE -HSG -HSV -Huawei -hwhandler -hwtable -iet -ifdef -ifndef -igroup -img -Infini -Infinidat -inotify -inttypes -ioctls -iscsi -isw -kpartx -LDFLAGS -len -libaio -libc -LIBDEPS -libdevmapper -libdmmp -libedit -libjson -libmpathcmd -libmpathpersist -libmpathutil -libmpathvalid -libmultipath -libreadline -libsystemd -libudev -libudevdir -liburcu -linux -LIO -lld -lpthread -Lun -lvm -lvmteam -Lyve -Marzinski -misdetection -mpath -mpathb -mpathpersist -mpathvalid -msecs -multipathc -multipathd -multipathed -multipathing -multipaths -multiqueue -mwilck -NFINIDAT -NOLOG -nompath -NOSCAN -Nosync -nvme -Nytro -OBJDEPS -oneshot -ontap -OOM -opensvc -OPTFLAGS -paramp -partx -pathgroup -pathlist -petabytes -pgpolicy -pgvec -plugindir -PNR -ppc -preferredip -preferredsds -prefixdir -prgeneration -Primera -prioritizer -prkeys -PROUT -pthreads -QSAN -rdac -rdma -readcap -readdescr -readfd -readkeys -readline -readsector -rebranded -reconfig -redhat -restorequeueing -retrigger -retryable -rhabarber -rootprefix -rootprefixdir -rpmbuild -rport -rtpi -rtprio -sanitizers -sas -sbp -scsi -SCST -sda -Seagate -setmarginal -setprkey -setprstatus -shm -shu -SIGHUP -softdep -spi -srp -ssa -standalone -statedir -stdarg -stdint -STRERR -strerror -suse -svg -switchgroup -sys -SYSDIR -sysfs -sysinit -tcp -terabytes -TESTDEPS -testname -tgill -TGTDIR -TIDS -tmo -tpg -transitioning -transportid -trnptid -udev -udevadm -udevd -uevent -uid -unitdir -unsetmarginal -unsetprkey -unsetprstatus -unspec -usb -userdata -userspace -usr -uuid -valgrind -varoqui -versioning -Vess -vgr -VNX -vpd -VSN -wakka -weightedpath -wholedisk -Wilck -wildcards -workflows -wrt -wwid -wwn -wwnn -wwpn diff --git a/.github/actions/spelling/only.txt b/.github/actions/spelling/only.txt deleted file mode 100644 index a1750d935..000000000 --- a/.github/actions/spelling/only.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Files to check - see on.push.paths in spelling.yml -# public header files -libdmmp\.h -mpath_valid\.h -mpath_cmd\.h -mpath_persist\.h -# udev rules -\.rules -\.rules\.in -# systemd unit files -\.service -\.service\.in -\.socket -# man pages -\.[358] -\.[358]\.in -README\.md -NEWS\.md diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt deleted file mode 100644 index b6a7aaeb7..000000000 --- a/.github/actions/spelling/patterns.txt +++ /dev/null @@ -1,119 +0,0 @@ -# https://www.gnu.org/software/groff/manual/groff.html -# man troff content -\\f[BCIPR] -# '/" -\\\([ad]q - -# uuid: -\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b - -#commit -\b[0-9a-f]{7}\b - -# Generic Hex numbers -\b0x[0-9a-f]{4}\b -\b0x[0-9a-f]{8}\b -\b0x[0-9a-f]{16}\b - -# Commit SHAs -\b[0-9a-f]{7}\b - -# WWNN/WWPN (NAA identifiers) -\b(?:0x)?10[0-9a-f]{14}\b -\b(?:0x|3)?[25][0-9a-f]{15}\b -\b(?:0x|3)?6[0-9a-f]{31}\b - -# iSCSI iqn (approximate regex) -\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b - -# identifiers -\bCODESET_UTF8\b -\bdev_loss_tmo\b -\bdmmp_mps\b -\bdmmp_pgs\b -\bdmmp_mpath_kdev_name_get\b -\bfast_io_fail_tmo\b -\blibmp_mapinfo\b -\bLimitRTPRIO=?\b -\bmax_fds\b -\bmissing_uev_wait_timeout\b -\bMPATH_MAX_PARAM_LEN\b -\bMPATH_MX_TIDS\b -\bMPATH_MX_TID_LEN\b -\bMPATH_PRIN_RKEY_SA\b -\bMPATH_PRIN_RRES_SA\b -\bMPATH_PRIN_RCAP_SA\b -\bMPATH_PRIN_RFSTAT_SA\b -\bMPATH_PROUT_REG_SA\b -\bMPATH_PROUT_RES_SA\b -\bMPATH_PROUT_REL_SA\b -\bMPATH_PROUT_CLEAR_SA\b -\bMPATH_PROUT_PREE_SA\b -\bMPATH_PROUT_PREE_AB_SA\b -\bMPATH_PROUT_REG_IGN_SA\b -\bMPATH_PROUT_REG_MOV_SA\b -\bMPATH_LU_SCOPE\b -\bMPATH_PRTPE_WE\b -\bMPATH_PRTPE_EA\b -\bMPATH_PRTPE_WE_RO\b -\bMPATH_PRTPE_EA_RO\b -\bMPATH_PRTPE_WE_AR\b -\bMPATH_PRTPE_EA_AR\b -\bMPATH_PR_SKIP\b -\bMPATH_PR_SUCCESS\b -\bMPATH_PR_SYNTAX_ERROR\b -\bMPATH_PR_SENSE_NOT_READY\b -\bMPATH_PR_SENSE_MEDIUM_ERROR\b -\bMPATH_PR_SENSE_HARDWARE_ERROR\b -\bMPATH_PR_ILLEGAL_REQ\b -\bMPATH_PR_SENSE_UNIT_ATTENTION\b -\bMPATH_PR_SENSE_INVALID_OP\b -\bMPATH_PR_SENSE_ABORTED_COMMAND\b -\bMPATH_PR_NO_SENSE\b -\bMPATH_PR_SENSE_MALFORMED\b -\bMPATH_PR_RESERV_CONFLICT\b -\bMPATH_PR_FILE_ERROR\b -\bMPATH_PR_DMMP_ERROR\b -\bMPATH_PR_THREAD_ERROR\b -\bMPATH_PR_OTHER\b -\bMPATH_F_APTPL_MASK\b -\bMPATH_F_ALL_TG_PT_MASK\b -\bMPATH_F_SPEC_I_PT_MASK\b -\bMPATH_PR_TYPE_MASK\b -\bMPATH_PR_SCOPE_MASK\b -\bMPATH_PROTOCOL_ID_FC\b -\bMPATH_PROTOCOL_ID_ISCSI\b -\bMPATH_PROTOCOL_ID_SAS\b -\bMPATH_WWUI_DEVICE_NAME\b -\bMPATH_WWUI_PORT_IDENTIFIER\b -\bmpath_persistent_reserve_init_vecs\b -\bmpath_persistent_reserve_free_vecs\b -\bmpath_recv_reply\b -\bmpath_recv_reply_len\b -\bmpath_recv_reply_data\b -\bMPATHTEST_VERBOSITY\b -\bprkeys_file\b -\bprout-type\b -\bprin_capdescr\b -\bprin_readresv\b -\bprin_resvdescr\b -\bprout_param_descriptor\b -\brq_servact\b -\bRLIMIT_RTPRIO\b -\bSCHED_RT_PRIO\b -\bssize_t\b -\btrnptid_list\b -\buxsock_timeout\b - -# Other -\bTutf8\b -\bUTF-8\b -\bCLARiiON\b -\bGPLv2\b -\bHBAs\b -\bSANtricity\b -\bVTrak\b -\bXSG1\b - - - diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml deleted file mode 100644 index bfce4e662..000000000 --- a/.github/workflows/spelling.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: Check Spelling - -# Comment management is handled through a secondary job, for details see: -# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions -# -# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment -# (in odd cases, it might actually run just to collapse a comment, but that's fairly rare) -# it needs `contents: write` in order to add a comment. -# -# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment -# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment) -# it needs `pull-requests: write` in order to manipulate those comments. - -# Updating pull request branches is managed via comment handling. -# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list -# -# These elements work together to make it happen: -# -# `on.issue_comment` -# This event listens to comments by users asking to update the metadata. -# -# `jobs.update` -# This job runs in response to an issue_comment and will push a new commit -# to update the spelling metadata. -# -# `with.experimental_apply_changes_via_bot` -# Tells the action to support and generate messages that enable it -# to make a commit to update the spelling metadata. -# -# `with.ssh_key` -# In order to trigger workflows when the commit is made, you can provide a -# secret (typically, a write-enabled github deploy key). -# -# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key - -# Sarif reporting -# -# Access to Sarif reports is generally restricted (by GitHub) to members of the repository. -# -# Requires enabling `security-events: write` -# and configuring the action with `use_sarif: 1` -# -# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Sarif-output - -# Minimal workflow structure: -# -# on: -# push: -# ... -# pull_request_target: -# ... -# jobs: -# # you only want the spelling job, all others should be omitted -# spelling: -# # remove `security-events: write` and `use_sarif: 1` -# # remove `experimental_apply_changes_via_bot: 1` -# ... otherwise adjust the `with:` as you wish - -on: - push: - branches: - - "**" - tags-ignore: - - "**" - paths: - - '.github/workflows/spelling.yml' - - 'README*' - - 'NEWS.md' - - '**.3' - - '**.5' - - '**.8' - - '**.8' - - '**.in' - - '**.service' - - '**.socket' - - '**.rules' - - '**/libdmmp.h' - - '**/mpath_valid.h' - - '**/mpath_cmd.h' - - '**/mpath_persist.h' - - '.github/actions/spelling/*' - pull_request_target: - branches: - - "**" - types: - - 'opened' - - 'reopened' - - 'synchronize' - paths: - - '.github/workflows/spelling.yml' - - 'README*' - - 'NEWS.md' - - '**.3' - - '**.5' - - '**.8' - - '**.8' - - '**.in' - - '**.service' - - '**.socket' - - '**.rules' - - '**/libdmmp.h' - - '**/mpath_valid.h' - - '**/mpath_cmd.h' - - '**/mpath_persist.h' - - '.github/actions/spelling/*' - -jobs: - spelling: - name: Check Spelling - permissions: - contents: read - pull-requests: read - actions: read - security-events: write - outputs: - followup: ${{ steps.spelling.outputs.followup }} - runs-on: ubuntu-latest - if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }} - concurrency: - group: spelling-${{ github.event.pull_request.number || github.ref }} - # note: If you use only_check_changed_files, you do not want cancel-in-progress - cancel-in-progress: true - steps: - - name: check-spelling - id: spelling - uses: check-spelling/check-spelling@main - with: - suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} - checkout: true - spell_check_this: opensvc/multipath-tools@master - post_comment: 0 - use_magic_file: 1 - report-timing: 0 - warnings: bad-regex,binary-file,deprecated-feature,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check - experimental_apply_changes_via_bot: 1 - use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} - extra_dictionary_limit: 20 - extra_dictionaries: - cspell:software-terms/dict/softwareTerms.txt - cspell:cpp/src/stdlib-c.txt - - comment-push: - name: Report (Push) - # If your workflow isn't running on push, you can remove this job - runs-on: ubuntu-latest - needs: spelling - permissions: - contents: write - if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' - steps: - - name: comment - uses: check-spelling/check-spelling@main - with: - checkout: true - spell_check_this: opensvc/multipath-tools@master - task: ${{ needs.spelling.outputs.followup }} - - comment-pr: - name: Report (PR) - # If you workflow isn't running on pull_request*, you can remove this job - runs-on: ubuntu-latest - needs: spelling - permissions: - contents: read - pull-requests: write - if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') - steps: - - name: comment - uses: check-spelling/check-spelling@main - with: - checkout: true - spell_check_this: opensvc/multipath-tools@master - task: ${{ needs.spelling.outputs.followup }} - experimental_apply_changes_via_bot: 1 - - update: - name: Update PR - permissions: - contents: write - pull-requests: write - actions: read - runs-on: ubuntu-latest - if: ${{ - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - contains(github.event.comment.body, '@check-spelling-bot apply') - }} - concurrency: - group: spelling-update-${{ github.event.issue.number }} - cancel-in-progress: false - steps: - - name: apply spelling updates - uses: check-spelling/check-spelling@main - with: - experimental_apply_changes_via_bot: 1 - checkout: true - ssh_key: "${{ secrets.CHECK_SPELLING }}" From 20090b09147d035ffa71808b7b8cf550f6a5f1bf Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 18 Jun 2026 22:57:07 +0200 Subject: [PATCH 073/102] GitHub workflows: replace @mosteo-actions/docker-run with docker commands @mosteo-actions/docker-run currently fails. But it can be replaced easily by a simple docker command line. Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 43 +++++++++++-------------- .github/workflows/multiarch-stable.yaml | 24 +++++--------- .github/workflows/multiarch.yaml | 24 +++++--------- .github/workflows/native.yaml | 42 ++++++++++++------------ 4 files changed, 55 insertions(+), 78 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 3dcbdb5c3..e5f1b5b45 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -82,18 +82,15 @@ jobs: with: image: tonistiigi/binfmt:latest - name: run tests - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} - guest-dir: /__w/multipath-tools/multipath-tools - host-dir: ${{ github.workspace }} - command: -C tests - params: > - --pids-limit 4096 - --workdir /__w/multipath-tools/multipath-tools - --platform linux/${{ env.CONTAINER_ARCH }} - pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" id: test + run: | + docker run \ + --pids-limit 4096 \ + --workdir /__w/multipath-tools/multipath-tools \ + --platform linux/${{ env.CONTAINER_ARCH }} \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} -C tests continue-on-error: true - name: save test outputs run: make test-outputs.tar @@ -144,20 +141,18 @@ jobs: with: image: tonistiigi/binfmt:latest - name: run tests - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} - guest-dir: /__w/multipath-tools/multipath-tools - host-dir: ${{ github.workspace }} - command: -C tests dmevents.out - params: > - --workdir /__w/multipath-tools/multipath-tools - --pids-limit 4096 - --platform linux/${{ env.CONTAINER_ARCH }} - --privileged - -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 - pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" id: root-test + run: | + docker run \ + --workdir /__w/multipath-tools/multipath-tools \ + --pids-limit 4096 \ + --platform linux/${{ env.CONTAINER_ARCH }} \ + --privileged \ + -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} \ + -C tests dmevents.out continue-on-error: true - name: save test outputs run: make test-outputs.tar diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index a374dfd95..ac2d821d6 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -58,24 +58,16 @@ jobs: echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" - name: compile and run unit tests id: test - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /build - host-dir: ${{ github.workspace }} - command: test - params: "--pids-limit 4096 --platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" + run: | + docker run --pids-limit 4096 --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test continue-on-error: true - name: create binary archive - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /build - host-dir: ${{ github.workspace }} - command: test-progs.tar - params: "--platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" + run: | + docker run --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar if: steps.test.outcome != 'success' - name: upload binary archive uses: actions/upload-artifact@v4 diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 6bd399407..ad989a143 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -61,24 +61,16 @@ jobs: echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" - name: compile and run unit tests id: test - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /build - host-dir: ${{ github.workspace }} - command: test - params: "--pids-limit 4096 --platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" + run: | + docker run --pids-limit 4096 --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test continue-on-error: true - name: create archives - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /build - host-dir: ${{ github.workspace }} - command: test-progs.tar test-outputs.tar - params: "--platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" + run: | + docker run --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar test-outputs.tar if: steps.test.outcome != 'success' - name: upload binary archive uses: actions/upload-artifact@v4 diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 75006db40..6dec9ead5 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -55,17 +55,16 @@ jobs: - name: build and test id: test - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: -j$(nproc) -Orecurse V=1 READLINE=$READLINE test + run: | + docker run -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -j$(nproc) -Orecurse V=1 READLINE=$READLINE test continue-on-error: true - name: create test-progs.tar - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: test-progs.tar + run: | + docker run -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar - name: upload test-progs.tar uses: actions/upload-artifact@v4 @@ -124,11 +123,11 @@ jobs: - name: build and test with clang id: test - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: --pids-limit 4096 -e CC=clang - command: -j$(nproc) -Orecurse V=1 READLINE=$READLINE test + run: | + docker run --pids-limit 4096 -e CC=clang \ + -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -j$(nproc) -Orecurse V=1 READLINE=$READLINE test continue-on-error: true - name: create test-outputs.tar @@ -185,16 +184,15 @@ jobs: run: tar xfmv test-progs.tar - name: run root tests - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - guest-dir: /__w/multipath-tools/multipath-tools - host-dir: ${{ github.workspace }} - params: > - --workdir /__w/multipath-tools/multipath-tools --privileged - -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 - command: -C tests V=1 directio.out dmevents.out id: test + run: | + docker run \ + --workdir /__w/multipath-tools/multipath-tools --privileged \ + -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -C tests V=1 directio.out dmevents.out continue-on-error: true - name: create test-outputs.tar From b89d42a67039736ec627456fe71d60ccc497fc1f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 10:18:55 +0200 Subject: [PATCH 074/102] GitHub workflows: set coredump pattern Set the coredump pattern to a relative file name, so that we can collect possible cores in test-outputs.tar. Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 6 ++++++ .github/workflows/multiarch-stable.yaml | 3 +++ .github/workflows/multiarch.yaml | 3 +++ .github/workflows/native.yaml | 12 ++++++++++++ .github/workflows/rolling.yaml | 3 +++ 5 files changed, 27 insertions(+) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index e5f1b5b45..d48406d02 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -81,6 +81,9 @@ jobs: uses: docker/setup-qemu-action@v2 with: image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: run tests id: test run: | @@ -140,6 +143,9 @@ jobs: uses: docker/setup-qemu-action@v2 with: image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: run tests id: root-test run: | diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index ac2d821d6..4f9f0a567 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -56,6 +56,9 @@ jobs: run: | __arch="${{ matrix.arch }}" echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: compile and run unit tests id: test run: | diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index ad989a143..abe52d00e 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -59,6 +59,9 @@ jobs: run: | __arch="${{ matrix.arch }}" echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: compile and run unit tests id: test run: | diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 6dec9ead5..93b9d5a33 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -53,6 +53,10 @@ jobs: run: echo READLINE=libreadline >> $GITHUB_ENV if: matrix.os == 'debian-jessie' + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: build and test id: test run: | @@ -121,6 +125,10 @@ jobs: run: echo READLINE=libreadline >> $GITHUB_ENV if: matrix.os == 'debian-jessie' + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: build and test with clang id: test run: | @@ -183,6 +191,10 @@ jobs: - name: unpack binary archive run: tar xfmv test-progs.tar + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: run root tests id: test run: | diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index 4dc389ff6..f530b107a 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -60,6 +60,9 @@ jobs: steps: - name: checkout uses: actions/checkout@v4 + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: build and test id: test run: make CC=${{ matrix.compiler }} READLINE=libreadline -j$(nproc) -Orecurse test From 681185d79f671494f19a440a76db33d7730875ca Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 12:37:44 +0200 Subject: [PATCH 075/102] GitHub workflows: make sure core dumps can be uploaded Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 8 ++++++++ .github/workflows/multiarch-stable.yaml | 4 ++++ .github/workflows/multiarch.yaml | 4 ++++ .github/workflows/native.yaml | 15 +++++++++++++++ .github/workflows/rolling.yaml | 4 ++++ 5 files changed, 35 insertions(+) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index d48406d02..41b91b746 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -95,6 +95,10 @@ jobs: -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} -C tests continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.test.outcome != 'success' - name: save test outputs run: make test-outputs.tar if: steps.test.outcome != 'success' @@ -160,6 +164,10 @@ jobs: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} \ -C tests dmevents.out continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.test.outcome != 'success' - name: save test outputs run: make test-outputs.tar if: steps.test.outcome != 'success' diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index 4f9f0a567..7dbcc8f6d 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -79,6 +79,10 @@ jobs: path: test-progs.tar overwrite: true if: steps.test.outcome != 'success' + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' - name: save test outputs run: make test-outputs.tar if: steps.test.outcome != 'success' diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index abe52d00e..cb62dc84b 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -69,6 +69,10 @@ jobs: -w /build -v${{ github.workspace }}:/build \ ghcr.io/mwilck/multipath-build-${{ matrix.os }} test continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.test.outcome != 'success' - name: create archives run: | docker run --platform linux/${{ matrix.arch }} \ diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 93b9d5a33..9c4fa2e82 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -77,6 +77,11 @@ jobs: path: test-progs.tar overwrite: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + - name: create test-outputs.tar run: make test-outputs.tar if: steps.test.outcome != 'success' @@ -138,6 +143,11 @@ jobs: -j$(nproc) -Orecurse V=1 READLINE=$READLINE test continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + - name: create test-outputs.tar run: make test-outputs.tar if: steps.test.outcome != 'success' @@ -207,6 +217,11 @@ jobs: -C tests V=1 directio.out dmevents.out continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + - name: create test-outputs.tar run: make test-outputs.tar if: steps.test.outcome != 'success' diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index f530b107a..2fd5915af 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -76,6 +76,10 @@ jobs: name: binaries-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} path: test-progs.tar if: steps.test.outcome != 'success' + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' - name: create test-outputs.tar run: make test-outputs.tar if: steps.test.outcome != 'success' From 33d67c4cc703a6b66f8a156f18e275417eb23787 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 24 Jun 2026 12:38:01 +0200 Subject: [PATCH 076/102] GitHub workflows: rolling: use run: docker instead of container workflow The matrix context doesn't seem to be available in "uses" clauses. Signed-off-by: Martin Wilck --- .github/workflows/rolling.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index 2fd5915af..9f178f776 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -56,7 +56,6 @@ jobs: runner: ubuntu-24.04 compiler: clang runs-on: ${{ matrix.variant.runner }} - container: ghcr.io/mwilck/multipath-build-${{ matrix.os }} steps: - name: checkout uses: actions/checkout@v4 @@ -65,7 +64,10 @@ jobs: echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: build and test id: test - run: make CC=${{ matrix.compiler }} READLINE=libreadline -j$(nproc) -Orecurse test + run: | + docker run -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + CC=${{ matrix.compiler }} READLINE=libreadline -j$(nproc) -Orecurse test continue-on-error: true - name: create binary archive run: make test-progs.tar From 7d775bda53f7ed506a69d60294e0eade60a0b9d0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 16:02:48 +0200 Subject: [PATCH 077/102] GitHub workflows: multiarch: use ARM runner for ARM arches We are observing strange errors with the openSUSE tumbleweed test on arm/v7. Try to use the arm4 runner for that. Signed-off-by: Martin Wilck --- .github/workflows/multiarch.yaml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index cb62dc84b..589246919 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -30,7 +30,6 @@ on: jobs: build-current: - runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -40,14 +39,30 @@ jobs: - fedora-rawhide - opensuse-tumbleweed arch: [ppc64le, s390x, 386, arm/v7] + variant: + - arch: ppc64le + runner: ubuntu-24.04 + - arch: s390x + runner: ubuntu-24.04 + - arch: 386 + runner: ubuntu-24.04 + - arch: arm/v7 + runner: ubuntu-24.04-arm exclude: - os: fedora-rawhide - arch: 386 + variant: + arch: 386 + runner: ubuntu-24.04 - os: fedora-rawhide - arch: arm/v7 + variant: + arch: arm/v7 + runner: ubuntu-24.04-arm include: - os: alpine - arch: aarch64 + variant: + arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.variant.runner }} steps: - name: checkout uses: actions/checkout@v4 @@ -57,7 +72,7 @@ jobs: image: tonistiigi/binfmt:latest - name: set architecture name run: | - __arch="${{ matrix.arch }}" + __arch="${{ matrix.variant.arch }}" echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" - name: Set coredump pattern run: | @@ -65,7 +80,7 @@ jobs: - name: compile and run unit tests id: test run: | - docker run --pids-limit 4096 --platform linux/${{ matrix.arch }} \ + docker run --pids-limit 4096 --platform linux/${{ matrix.variant.arch }} \ -w /build -v${{ github.workspace }}:/build \ ghcr.io/mwilck/multipath-build-${{ matrix.os }} test continue-on-error: true @@ -75,7 +90,7 @@ jobs: if: steps.test.outcome != 'success' - name: create archives run: | - docker run --platform linux/${{ matrix.arch }} \ + docker run --platform linux/${{ matrix.variant.arch }} \ -w /build -v${{ github.workspace }}:/build \ ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar test-outputs.tar if: steps.test.outcome != 'success' From d20bbb60030fd2c4d01ba372580c673212cc15a5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 25 Jun 2026 21:05:07 +0200 Subject: [PATCH 078/102] GitHub Workflows: run multiarch test on "musl" branch Signed-off-by: Martin Wilck --- .github/workflows/multiarch.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 589246919..3aee3b028 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -6,6 +6,7 @@ on: - queue - tip - 'stable-*' + - musl paths: - '.github/workflows/multiarch.yaml' - '**.h' From 5dea09f2d7bffefac15f3242d5cb9c5c02fa2141 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 6 Jul 2026 15:45:40 +0200 Subject: [PATCH 079/102] multipath.conf.in: Update path_checker and force_sync documentation Document the fact that all path checkers are now asynchronous by default. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- multipath/multipath.conf.5.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index e6b4fc040..2ea78fc77 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -504,12 +504,11 @@ The default is: \fB0\fR . .TP .B path_checker -The default method used to determine the path's state. The synchronous -checkers (all except \fItur\fR and \fIdirectio\fR) will cause multipathd to -pause most activity, waiting up to \fIchecker_timeout\fR seconds for the path -to respond. The asynchronous checkers (\fItur\fR and \fIdirectio\fR) will not -pause multipathd. Instead, multipathd will check for a response once per -second, until \fIchecker_timeout\fR seconds have elapsed. Possible values are: +The default method used to determine the path's state. Since multipath-tools +0.15.0, all path checkers run in asynchronous mode by default, so that +unresponsive devices don't pause multipathd. Use the \fIforce_sync\fR parameter +to enforce synchronous path checking instead. +Possible values are: .RS .TP .I readsector0 @@ -905,7 +904,8 @@ The default is: \fByes\fR If set to .I yes , multipathd will call the path checkers in sync mode only. This means that -only one checker will run at a time. This is useful in the case where many +only one checker will run at a time, and that multipathd may pause operating +while it is waiting for a device to respond. This is useful in the case where many multipathd checkers running in parallel causes significant CPU pressure. .RS .TP From 8f9c225dba10a140516c557924dfd89159fee51d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 6 Jul 2026 12:38:30 +0000 Subject: [PATCH 080/102] multipath-tools: update NEWS.md Signed-off-by: Martin Wilck Co-Authored-By: Claude Sonnet 4.6 --- NEWS.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1fd4766ab..aae061220 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,53 @@ release. These bug fixes will be tracked in stable branches. See [README.md](README.md) for additional information. +## multipath-tools 0.15.0, 2026/07 + +### User-visible changes + +* All path checkers run in asynchronous mode now by default, using a new + generic asynchronous checker framework. This improves the stability of + multipathd in the presence of non-responsive devices when using path + checkers other than `tur` and `directio`. Use the `force_sync` + parameter in `multipath.conf` to switch back to the previous, synchronous + behavior, especially if you observe strong spikes of CPU load on systems + with a lot of path devices. Note that the likelihood of such spikes should + be strongly reduced since multipath-tools 0.10.0. Commit 6f7daba ff. +* The configuration options `rr_min_io`, `rr_min_io_rq`, and `rr_weight` are now + deprecated and have no effect. These options have not been supported by the + kernel since version 4.6. Users should remove them from `multipath.conf`. + Commits 91f91c7 ff. +* Fix ALUA asymmetric access state descriptions in multipathd logs, so that + the same terms are used as by the kernel ("lba-dependent", "transitioning"). + Commit fa2eb12. +* Don't set a hardware handler for bio-based multipath devices. The kernel + rejects this anyway. Commit b6c7aab. + +### Bug fixes + +* Fix WWID detection for legacy devices that use the older SCSI-2 VPD page + 0x83 format for their device identifier. Commit ed6f6ae. +* kpartx: Fix an integer overflow in the GPT partition table size calculation. + A crafted partition table with an extremely large number of partition entries + could trigger the overflow. Commit 8697ea7, 15310a3. +* kpartx: Fix several issues in the DASD partition table reader that could be + triggered by a maliciously crafted disk image. Commits c616a95 ff. +* Fix duplicate "checker timed out" log messages when `log_checker_err` is + set to `once`. Commit 8933b22. + +### Other changes + +* Man page improvements + +### CI + +* Removed the `check_spelling` GitHub Action after a + [security incident](https://github.com/jsoref/2026-06-16-credential-leak) + reported against the action's source repository. +* Added coredump collection in the CI. +* GitHub action updates related to node 20 depreciation on GitHub. +* Fixes for various errors in the CI. + ## multipath-tools 0.14.3, 2026/02 * Fix boot failures with multipath introduced in 0.14.1. Commit 01cc89c. From 4611f971c514f0f6a1fdaa1146d8ecd006cc98a7 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 6 Jul 2026 20:48:16 -0400 Subject: [PATCH 081/102] libmultipath: make sure sscanf sets a max field width for strings In the iet and datacore prioritizers, multipath was using sscanf to get a string for a 255 byte buffer, without limiting the size of the string. This could result in a buffer overflow, if there was a bad value in multipath.conf. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/prioritizers/datacore.c | 6 +++--- libmultipath/prioritizers/iet.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libmultipath/prioritizers/datacore.c b/libmultipath/prioritizers/datacore.c index ab813a0ea..a7c2ae3e8 100644 --- a/libmultipath/prioritizers/datacore.c +++ b/libmultipath/prioritizers/datacore.c @@ -49,11 +49,11 @@ int datacore_prio (const char *dev, int sg_fd, char * args, return 0; } - if (sscanf(args, "timeout=%i preferredsds=%s", + if (sscanf(args, "timeout=%i preferredsds=%254s", (int *)&timeout_ms, preferredsds) == 2) {} - else if (sscanf(args, "preferredsds=%s timeout=%i", + else if (sscanf(args, "preferredsds=%254s timeout=%i", preferredsds, (int *)&timeout_ms) == 2) {} - else if (sscanf(args, "preferredsds=%s", + else if (sscanf(args, "preferredsds=%254s", preferredsds) == 1) {} else { dc_log(0, "unexpected prio_args format"); diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index f5fdd42aa..3aefaa32c 100644 --- a/libmultipath/prioritizers/iet.c +++ b/libmultipath/prioritizers/iet.c @@ -84,7 +84,7 @@ int iet_prio(const char *dev, char * args) return 0; } // check if args format is OK - if (sscanf(args, "preferredip=%s", preferredip) ==1) {} + if (sscanf(args, "preferredip=%254s", preferredip) == 1) {} else { dc_log(0, "unexpected prio_args format"); return 0; From 95b808bd12811c10087d63e1a61374e74ef6f472 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 6 Jul 2026 20:48:17 -0400 Subject: [PATCH 082/102] Remove unsed DEFAULT_DISABLE_CHANGED_WWIDS define disable_changed_wwids is deprecated and unused. Suggested-by: Xose Vazquez Perez Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/defaults.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index ea1fce0e7..c3ae42e92 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -49,7 +49,6 @@ #define UNSET_PARTITION_DELIM "/UNSET/" #define DEFAULT_PARTITION_DELIM NULL #define DEFAULT_SKIP_KPARTX SKIP_KPARTX_OFF -#define DEFAULT_DISABLE_CHANGED_WWIDS 1 #define DEFAULT_MAX_SECTORS_KB MAX_SECTORS_KB_UNDEF #define DEFAULT_GHOST_DELAY GHOST_DELAY_OFF #define DEFAULT_FIND_MULTIPATHS_TIMEOUT -10 From a048f055f79121852130e99837e3aef7cf59f45c Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 9 Jul 2026 17:07:32 +0200 Subject: [PATCH 083/102] .clang-format: avoid reformatting comments Signed-off-by: Martin Wilck --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index e68fd4d5c..927b8e0dd 100644 --- a/.clang-format +++ b/.clang-format @@ -31,4 +31,5 @@ ForEachMacros: - vector_foreach_slot_backwards - vector_foreach_slot_after Cpp11BracedListStyle: false +ReflowComments: Never --- From 322353d1496fed3033795707dc96ef19b28f2e08 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 9 Jul 2026 16:26:54 +0200 Subject: [PATCH 084/102] multipath-tools tests: fix code formatting in vpd.c Avoid that "git clang-format" wrongly indents the invocations of the macros make_test_vpd_str() and make_test_vpd_prespc3 in tests/vpd.c, by appending semicolons to the macro invocation as we did for other macros in the same file. Note: The indentation issue could also be fixed by adding these macros to the "StatementMacros:" directive in .clang-format. Signed-off-by: Martin Wilck --- tests/vpd.c | 58 ++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/vpd.c b/tests/vpd.c index 19e759895..524b67ff2 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -825,47 +825,47 @@ make_test_vpd_naa(2, 17); make_test_vpd_naa(2, 16); /* SCSI Name string: EUI64, WWID size: 17 */ -make_test_vpd_str(0, 20, 18) -make_test_vpd_str(0, 20, 17) -make_test_vpd_str(0, 20, 16) -make_test_vpd_str(0, 20, 15) +make_test_vpd_str(0, 20, 18); +make_test_vpd_str(0, 20, 17); +make_test_vpd_str(0, 20, 16); +make_test_vpd_str(0, 20, 15); /* SCSI Name string: EUI64, zero padded, WWID size: 16 */ -make_test_vpd_str(16, 20, 18) -make_test_vpd_str(16, 20, 17) -make_test_vpd_str(16, 20, 16) -make_test_vpd_str(16, 20, 15) +make_test_vpd_str(16, 20, 18); +make_test_vpd_str(16, 20, 17); +make_test_vpd_str(16, 20, 16); +make_test_vpd_str(16, 20, 15); /* SCSI Name string: NAA, WWID size: 17 */ -make_test_vpd_str(1, 20, 18) -make_test_vpd_str(1, 20, 17) -make_test_vpd_str(1, 20, 16) -make_test_vpd_str(1, 20, 15) +make_test_vpd_str(1, 20, 18); +make_test_vpd_str(1, 20, 17); +make_test_vpd_str(1, 20, 16); +make_test_vpd_str(1, 20, 15); /* SCSI Name string: NAA, zero padded, WWID size: 16 */ -make_test_vpd_str(17, 20, 18) -make_test_vpd_str(17, 20, 17) -make_test_vpd_str(17, 20, 16) -make_test_vpd_str(17, 20, 15) +make_test_vpd_str(17, 20, 18); +make_test_vpd_str(17, 20, 17); +make_test_vpd_str(17, 20, 16); +make_test_vpd_str(17, 20, 15); /* SCSI Name string: IQN, WWID size: 17 */ -make_test_vpd_str(2, 20, 18) -make_test_vpd_str(2, 20, 17) -make_test_vpd_str(2, 20, 16) -make_test_vpd_str(2, 20, 15) +make_test_vpd_str(2, 20, 18); +make_test_vpd_str(2, 20, 17); +make_test_vpd_str(2, 20, 16); +make_test_vpd_str(2, 20, 15); /* SCSI Name string: IQN, zero padded, WWID size: 16 */ -make_test_vpd_str(18, 20, 18) -make_test_vpd_str(18, 20, 17) -make_test_vpd_str(18, 20, 16) -make_test_vpd_str(18, 20, 15) +make_test_vpd_str(18, 20, 18); +make_test_vpd_str(18, 20, 17); +make_test_vpd_str(18, 20, 16); +make_test_vpd_str(18, 20, 15); /* PRE-SPC3, WWID size: 34 */ -make_test_vpd_prespc3(40) -make_test_vpd_prespc3(34) -make_test_vpd_prespc3(33) -make_test_vpd_prespc3(32) -make_test_vpd_prespc3(20) +make_test_vpd_prespc3(40); +make_test_vpd_prespc3(34); +make_test_vpd_prespc3(33); +make_test_vpd_prespc3(32); +make_test_vpd_prespc3(20); static int test_vpd(void) { From 91141285a503a6096f4243661895f679840b1e68 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 9 Jul 2026 16:33:28 +0200 Subject: [PATCH 085/102] Fix minor code formatting issues in latest commits With these changes, "git clang-format --commit 0.14.0" doesn't change any code. Signed-off-by: Martin Wilck --- libmpathutil/runner.c | 6 +++--- libmultipath/async_checker.c | 2 +- libmultipath/prioritizers/datacore.c | 13 ++++++------- libmultipath/prioritizers/iet.c | 4 ++-- libmultipath/propsel.c | 4 ++-- tests/runner.c | 6 +++--- tests/vpd.c | 3 +-- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/libmpathutil/runner.c b/libmpathutil/runner.c index 694481e9d..31a534946 100644 --- a/libmpathutil/runner.c +++ b/libmpathutil/runner.c @@ -92,8 +92,8 @@ static void *runner_thread(void *arg) * thread start and thread cancellation. */ do { - struct timespec slp = {.tv_sec = 0, - .tv_nsec = 1000 * RUNNER_START_DELAY_US}; + struct timespec slp = { .tv_sec = 0, + .tv_nsec = 1000 * RUNNER_START_DELAY_US }; nanosleep(&slp, NULL); } while (0); @@ -198,7 +198,7 @@ int check_runner(struct runner_context *rctx, void *data, unsigned int size) struct runner_context *get_runner(runner_func func, void *data, unsigned int size, unsigned long timeout_usec) { - static const struct timespec time_zero = {.tv_sec = 0}; + static const struct timespec time_zero = { .tv_sec = 0 }; struct runner_context *rctx; pthread_attr_t attr; int rc; diff --git a/libmultipath/async_checker.c b/libmultipath/async_checker.c index 0023c73dc..89b724112 100644 --- a/libmultipath/async_checker.c +++ b/libmultipath/async_checker.c @@ -229,7 +229,7 @@ int async_check_check(struct checker *c, union checker_mpcontext *mpc) static void async_deep_sleep(const struct runner_data *rdata) { static int sleep_cnt; - const struct timespec ts = {.tv_sec = ASYNC_SLEEP_SECS, .tv_nsec = 0}; + const struct timespec ts = { .tv_sec = ASYNC_SLEEP_SECS, .tv_nsec = 0 }; int oldstate; if (rdata->devt != makedev(ASYNC_TEST_MAJOR, ASYNC_TEST_MINOR) || diff --git a/libmultipath/prioritizers/datacore.c b/libmultipath/prioritizers/datacore.c index a7c2ae3e8..b03d14731 100644 --- a/libmultipath/prioritizers/datacore.c +++ b/libmultipath/prioritizers/datacore.c @@ -49,13 +49,12 @@ int datacore_prio (const char *dev, int sg_fd, char * args, return 0; } - if (sscanf(args, "timeout=%i preferredsds=%254s", - (int *)&timeout_ms, preferredsds) == 2) {} - else if (sscanf(args, "preferredsds=%254s timeout=%i", - preferredsds, (int *)&timeout_ms) == 2) {} - else if (sscanf(args, "preferredsds=%254s", - preferredsds) == 1) {} - else { + if (sscanf(args, "timeout=%i preferredsds=%254s", (int *)&timeout_ms, + preferredsds) == 2) { + } else if (sscanf(args, "preferredsds=%254s timeout=%i", preferredsds, + (int *)&timeout_ms) == 2) { + } else if (sscanf(args, "preferredsds=%254s", preferredsds) == 1) { + } else { dc_log(0, "unexpected prio_args format"); return 0; } diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index 3aefaa32c..1e7f1e895 100644 --- a/libmultipath/prioritizers/iet.c +++ b/libmultipath/prioritizers/iet.c @@ -84,8 +84,8 @@ int iet_prio(const char *dev, char * args) return 0; } // check if args format is OK - if (sscanf(args, "preferredip=%254s", preferredip) == 1) {} - else { + if (sscanf(args, "preferredip=%254s", preferredip) == 1) { + } else { dc_log(0, "unexpected prio_args format"); return 0; } diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 0202aaef3..162e7af21 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -624,8 +624,8 @@ int select_checker_timeout(struct config *conf, struct path *pp) } pp_set_default(checker_timeout, DEF_TIMEOUT); out: - condlog(3, "%s: checker timeout = %u s %s", pp->dev, pp->checker_timeout, - origin); + condlog(3, "%s: checker timeout = %u s %s", pp->dev, + pp->checker_timeout, origin); return 0; } diff --git a/tests/runner.c b/tests/runner.c index 0e728b1b0..eeffe08c7 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -275,7 +275,7 @@ static bool test_sleep(const struct timespec *wait) static int run_test(int n) { int i, running, done, errors; - const struct timespec wait = {.tv_sec = 0, .tv_nsec = 1000 * POLL_USEC}; + const struct timespec wait = { .tv_sec = 0, .tv_nsec = 1000 * POLL_USEC }; struct timespec stop, now; long max_wait = TIMEOUT_USEC + NOISE_BIAS * NOISE_USEC + 100000 + RUNNER_START_DELAY_US; @@ -390,7 +390,7 @@ static int setup_signal_handler(int sig, void (*handler)(int)) { sigset_t set; sigfillset(&set); - struct sigaction sga = {.sa_handler = NULL}; + struct sigaction sga = { .sa_handler = NULL }; sga.sa_handler = handler; sga.sa_mask = set; @@ -453,7 +453,7 @@ static int fork_test(void) sigset_t set; pid_t child; int wstatus; - struct timespec wait_to_kill = {.tv_sec = 0}; + struct timespec wait_to_kill = { .tv_sec = 0 }; /* Block all signals. termination signals will be enabled in test_sleep() */ sigfillset(&set); diff --git a/tests/vpd.c b/tests/vpd.c index 524b67ff2..8b07df16b 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -342,8 +342,7 @@ static int create_vpd83(unsigned char *buf, size_t bufsiz, const char *id, * * Return: VPD page length. */ -static int create_pre_spc3_vpd83(unsigned char *buf, size_t bufsize, - const char *id) +static int create_pre_spc3_vpd83(unsigned char *buf, size_t bufsize, const char *id) { memset(buf, 0, bufsize); buf[1] = 0x83; From 72af830383cbc0ea92b2abc57d12a166b3437f25 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 9 Jul 2026 17:17:42 +0200 Subject: [PATCH 086/102] Update NEWS.md Signed-off-by: Martin Wilck --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index aae061220..e1566af83 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,6 +42,8 @@ See [README.md](README.md) for additional information. triggered by a maliciously crafted disk image. Commits c616a95 ff. * Fix duplicate "checker timed out" log messages when `log_checker_err` is set to `once`. Commit 8933b22. +* Avoid potential buffer overflows in the iet and datacore prioritizers. + Commit 4611f97. ### Other changes From 3eb8504d5354fb6d82b4b7287e5a9ede57d4f149 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 11:20:04 +0200 Subject: [PATCH 087/102] GitHub workflows: fix conditions in foreign.yaml Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 41b91b746..42e21f580 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -167,17 +167,17 @@ jobs: - name: Make core dumps accessible run: | find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x - if: steps.test.outcome != 'success' + if: steps.root-test.outcome != 'success' - name: save test outputs run: make test-outputs.tar - if: steps.test.outcome != 'success' + if: steps.root-test.outcome != 'success' - name: upload test outputs uses: actions/upload-artifact@v4 with: name: root-test-${{ matrix.os }}-${{ matrix.arch }} path: test-outputs.tar overwrite: true - if: steps.test.outcome != 'success' + if: steps.root-test.outcome != 'success' - name: fail if root test failed run: /bin/false if: steps.root-test.outcome != 'success' From 60183a1773ce72dc1774747bdfaf7c634c1b141e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 11:25:57 +0200 Subject: [PATCH 088/102] Add github workflows (pre-0.15) on new orphan branch We will use this branch to maintain the workflows separately from the actual code. That makes maintenance of the workflows easier for the stable branches. Signed-off-by: Martin Wilck --- .github/workflows/abi-stable.yaml | 122 +++++++++++ .github/workflows/abi.yaml | 76 +++++++ .github/workflows/build-and-unittest.yaml | 114 +++++++++++ .github/workflows/codingstyle.yml | 43 ++++ .github/workflows/coverity.yaml | 52 +++++ .github/workflows/foreign.yaml | 183 +++++++++++++++++ .github/workflows/multiarch-stable.yaml | 98 +++++++++ .github/workflows/multiarch.yaml | 113 ++++++++++ .github/workflows/native.yaml | 239 ++++++++++++++++++++++ .github/workflows/rolling.yaml | 96 +++++++++ 10 files changed, 1136 insertions(+) create mode 100644 .github/workflows/abi-stable.yaml create mode 100644 .github/workflows/abi.yaml create mode 100644 .github/workflows/build-and-unittest.yaml create mode 100644 .github/workflows/codingstyle.yml create mode 100644 .github/workflows/coverity.yaml create mode 100644 .github/workflows/foreign.yaml create mode 100644 .github/workflows/multiarch-stable.yaml create mode 100644 .github/workflows/multiarch.yaml create mode 100644 .github/workflows/native.yaml create mode 100644 .github/workflows/rolling.yaml diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml new file mode 100644 index 000000000..de8050ab0 --- /dev/null +++ b/.github/workflows/abi-stable.yaml @@ -0,0 +1,122 @@ +name: check-abi for stable branch +on: + push: + branches: + - 'stable-*' + paths: + - '.github/workflows/abi-stable.yaml' + - '**.h' + - '**.c' + - '**.version' + pull_request: + branches: + - 'stable-*' + workflow_dispatch: + +jobs: + reference-abi: + runs-on: ubuntu-24.04 + steps: + - name: get parent tag (push) + run: > + echo ${{ github.ref }} | + sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV + if: github.event_name == 'push' + - name: get parent tag (PR) + run: > + echo ${{ github.base_ref }} | + sed -E 's,stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV + if: github.event_name == 'pull_request' + - 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 + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + 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 }} + path: abi + + check-abi: + runs-on: ubuntu-24.04 + needs: reference-abi + steps: + - name: get parent tag (push) + run: > + echo ${{ github.ref }} | + sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV + if: github.event_name == 'push' + - name: get parent tag (PR) + run: > + echo ${{ github.base_ref }} | + sed -E 's,stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV + if: github.event_name == 'pull_request' + - name: assert parent tag + run: /bin/false + if: env.PARENT_TAG == '' + - name: checkout ${{ github.ref }} + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: download ABI for ${{ env.PARENT_TAG }} + id: download_abi + 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 + - name: dependencies + run: > + sudo apt-get install --yes gcc + gcc make pkg-config abigail-tools + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + libmount-dev + - name: check ABI of ${{ github.ref }} against ${{ env.PARENT_TAG }} + id: check_abi + run: make -j$(nproc) -Orecurse abi-test + continue-on-error: true + - name: save differences + if: steps.check_abi.outcome != 'success' + uses: actions/upload-artifact@v4 + with: + name: abi-test + path: abi-test + - name: fail + run: /bin/false + if: steps.check_abi.outcome != 'success' diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml new file mode 100644 index 000000000..1a7d3e5dd --- /dev/null +++ b/.github/workflows/abi.yaml @@ -0,0 +1,76 @@ +name: check-abi +on: + push: + branches: + - master + - queue + - abi + paths: + - '.github/workflows/abi.yaml' + - '**.h' + - '**.c' + pull_request: + branches: + - master + - queue + workflow_dispatch: +env: + ABI_BRANCH: ${{ secrets.ABI_BRANCH }} + +jobs: + save-and-test-ABI: + runs-on: ubuntu-24.04 + steps: + - name: set ABI branch + if: env.ABI_BRANCH == '' + run: echo "ABI_BRANCH=master" >> $GITHUB_ENV + - name: checkout + uses: actions/checkout@v4 + - name: get reference ABI + id: reference + continue-on-error: true + uses: dawidd6/action-download-artifact@v6 + with: + workflow: abi.yaml + branch: ${{ env.ABI_BRANCH }} + name: abi + path: reference-abi + - name: update + run: sudo apt-get update + - name: dependencies + run: > + sudo apt-get install --yes gcc + gcc make pkg-config abigail-tools + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + libmount-dev + - name: create ABI + run: make -Orecurse -j$(nproc) abi.tar.gz + - name: save ABI + uses: actions/upload-artifact@v4 + with: + name: abi + path: abi + overwrite: true + - name: compare ABI against reference + id: compare + continue-on-error: true + if: steps.reference.outcome == 'success' + run: make abi-test + - name: save differences + if: steps.compare.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: abi-test + path: abi-test + overwrite: true + + - name: fail + # MUST use >- here, otherwise the condition always evaluates to true + if: >- + ${{ + env.ABI_BRANCH != github.ref_name && + (steps.reference.outcome == 'failure' || + steps.compare.outcome == 'failure') + }} + run: false diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml new file mode 100644 index 000000000..616a4de1f --- /dev/null +++ b/.github/workflows/build-and-unittest.yaml @@ -0,0 +1,114 @@ +name: basic-build-and-ci +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + pull_request: + branches: + - master + - queue + - 'stable-*' +jobs: + jammy: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + rl: ['', 'libreadline', 'libedit'] + cc: [ gcc, clang ] + steps: + - uses: actions/checkout@v4 + - name: update + run: sudo apt-get update + - name: dependencies + run: > + sudo apt-get install --yes gcc + make pkg-config valgrind + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + libmount-dev linux-modules-extra-$(uname -r) + - name: mpath + run: sudo modprobe dm_multipath + - name: zram + run: sudo modprobe zram num_devices=0 + - name: zram-device + run: echo ZRAM=$(sudo cat /sys/class/zram-control/hot_add) >> $GITHUB_ENV + - name: set-zram-size + run: echo 1G | sudo tee /sys/block/zram$ZRAM/disksize + - name: set CC + run: echo CC=${{ matrix.cc }} >> $GITHUB_ENV + - name: set optflags + # valgrind doesn't support the dwarf-5 format of clang 14 + run: echo OPT='-O2 -gdwarf-4 -fstack-protector-strong' >> $GITHUB_ENV + if: matrix.cc == 'clang' + - name: build + run: > + make -Orecurse -j$(nproc) + READLINE=${{ matrix.rl }} OPTFLAGS="$OPT" + - name: test + run: > + make -Orecurse -j$(nproc) + OPTFLAGS="$OPT" test + - name: valgrind-test + id: valgrind + run: > + make -Orecurse -j$(nproc) + OPTFLAGS="$OPT" valgrind-test + continue-on-error: true + - name: valgrind-results + run: cat tests/*.vgr + - name: fail if valgrind failed + run: /bin/false + if: steps.valgrind.outcome != 'success' + - name: clean-nonroot-artifacts + 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: + fail-fast: false + matrix: + rl: ['', 'libreadline', 'libedit'] + cc: [ gcc, clang ] + steps: + - uses: actions/checkout@v4 + - name: mpath + run: sudo modprobe dm_multipath + - name: brd + run: sudo modprobe brd rd_nr=1 rd_size=65536 + - name: update + run: sudo apt-get update + - name: dependencies + run: > + sudo apt-get install --yes gcc-10 + make pkg-config valgrind + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + libmount-dev linux-modules-extra-$(uname -r) + - name: set CC + run: echo CC=${{ matrix.cc }} >> $GITHUB_ENV + - name: build + run: make -Orecurse -j$(nproc) READLINE=${{ matrix.rl }} + - name: test + run: make -Orecurse -j$(nproc) test + - name: valgrind-test + id: valgrind + run: make -Orecurse -j$(nproc) valgrind-test + continue-on-error: true + - name: valgrind-results + run: cat tests/*.vgr + - name: fail if valgrind failed + run: /bin/false + if: steps.valgrind.outcome != 'success' + - name: clean-nonroot-artifacts + 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 diff --git a/.github/workflows/codingstyle.yml b/.github/workflows/codingstyle.yml new file mode 100644 index 000000000..a36b3874e --- /dev/null +++ b/.github/workflows/codingstyle.yml @@ -0,0 +1,43 @@ +name: Check coding style + +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + paths: + - '.clang-format' + - '.github/workflows/codingstyle.yaml' + - '**.h' + - '**.c' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.clang-format' + - '.github/workflows/codingstyle.yaml' + - '**.h' + - '**.c' + +permissions: + contents: read + +jobs: + stylecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: >- + echo "BASEREF=${{ github.event.pull_request.base.sha }}" >>$GITHUB_ENV + if: github.event_name == 'pull_request' + - run: >- + echo "BASEREF=${{ github.event.before }}" >>$GITHUB_ENV + if: github.event_name == 'push' + - run: git fetch --depth=1 origin ${{ env.BASEREF }} + - uses: yshui/git-clang-format-lint@master + with: + base: ${{ env.BASEREF }} diff --git a/.github/workflows/coverity.yaml b/.github/workflows/coverity.yaml new file mode 100644 index 000000000..30e5471d3 --- /dev/null +++ b/.github/workflows/coverity.yaml @@ -0,0 +1,52 @@ +name: coverity +on: + push: + branches: + - coverity + +jobs: + upload-coverity-scan: + runs-on: ubuntu-24.04 + steps: + - name: checkout + uses: actions/checkout@v4 + - name: dependencies + run: > + sudo apt-get install --yes + gcc make pkg-config + libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev + libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + libmount-dev + - name: download coverity + run: > + curl -o cov-analysis-linux64.tar.gz + --form token="$COV_TOKEN" + --form project="$COV_PROJECT" + https://scan.coverity.com/download/cxx/linux64 + env: + COV_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + COV_PROJECT: ${{ secrets.COVERITY_SCAN_PROJECT }} + - name: unpack coverity + run: | + mkdir -p coverity + tar xfz cov-analysis-linux64.tar.gz --strip 1 -C coverity + - name: build with cov-build + run: > + PATH="$PWD/coverity/bin:$PATH" + cov-build --dir cov-int make -Orecurse -j"$(nproc)" + - name: pack results + run: tar cfz multipath-tools.tgz cov-int + - name: submit results + run: > + curl + --form token="$COV_TOKEN" + --form email="$COV_EMAIL" + --form file="@multipath-tools.tgz" + --form version="${{ github.ref_name }}" + --form description="$(git describe --tags --match "0.*")" + --form project="$COV_PROJECT" + https://scan.coverity.com/builds + env: + COV_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + COV_PROJECT: ${{ secrets.COVERITY_SCAN_PROJECT }} + COV_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }} diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml new file mode 100644 index 000000000..42e21f580 --- /dev/null +++ b/.github/workflows/foreign.yaml @@ -0,0 +1,183 @@ +name: compile and unit test on foreign arch +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + paths: + - '.github/workflows/foreign.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.github/workflows/foreign.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + +jobs: + + cross-build: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + os: [bullseye, bookworm, trixie, sid] + arch: [ppc64le, arm64, s390x] + exclude: + - os: bullseye + arch: ppc64le + - os: bullseye + arch: s390x + container: ghcr.io/mwilck/multipath-cross-debian_cross-${{ matrix.os }}-${{ matrix.arch }} + steps: + - name: checkout + uses: actions/checkout@v4 + - name: build + run: make -j$(nproc) -Orecurse test-progs.tar + - name: upload binary archive + uses: actions/upload-artifact@v4 + with: + name: cross-${{ matrix.os }}-${{ matrix.arch }} + path: test-progs.tar + overwrite: true + + test: + runs-on: ubuntu-24.04 + needs: cross-build + strategy: + fail-fast: false + matrix: + os: [bullseye, bookworm, trixie, sid] + arch: [ppc64le, arm64, s390x] + exclude: + - os: bullseye + arch: ppc64le + - os: bullseye + arch: s390x + steps: + - name: set container arch + run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV + if: matrix.arch != 'armhf' + - name: set container arch + run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV + if: matrix.arch == 'armhf' + - name: download binary archive + uses: actions/download-artifact@v4 + with: + name: cross-${{ matrix.os }}-${{ matrix.arch }} + - name: unpack binary archive + run: tar xfv test-progs.tar + - name: enable foreign arch + uses: docker/setup-qemu-action@v2 + with: + image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: run tests + id: test + run: | + docker run \ + --pids-limit 4096 \ + --workdir /__w/multipath-tools/multipath-tools \ + --platform linux/${{ env.CONTAINER_ARCH }} \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} -C tests + continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.test.outcome != 'success' + - name: save test outputs + run: make test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' + + root-test: + runs-on: ubuntu-24.04 + needs: cross-build + strategy: + fail-fast: false + matrix: + os: [bullseye, bookworm, trixie, sid] + arch: [ppc64le, arm64, s390x] + exclude: + - os: bullseye + arch: ppc64le + - os: bullseye + arch: s390x + steps: + - name: mpath + run: sudo modprobe dm_multipath + - name: brd + run: sudo modprobe brd rd_nr=1 rd_size=65536 + - name: set container arch + run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV + if: matrix.arch != 'armhf' + - name: set container arch + run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV + if: matrix.arch == 'armhf' + - name: download binary archive + uses: actions/download-artifact@v4 + with: + name: cross-${{ matrix.os }}-${{ matrix.arch }} + - name: unpack binary archive + run: tar xfv test-progs.tar + - name: enable foreign arch + uses: docker/setup-qemu-action@v2 + with: + image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: run tests + id: root-test + run: | + docker run \ + --workdir /__w/multipath-tools/multipath-tools \ + --pids-limit 4096 \ + --platform linux/${{ env.CONTAINER_ARCH }} \ + --privileged \ + -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} \ + -C tests dmevents.out + continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.root-test.outcome != 'success' + - name: save test outputs + run: make test-outputs.tar + if: steps.root-test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: root-test-${{ matrix.os }}-${{ matrix.arch }} + path: test-outputs.tar + overwrite: true + if: steps.root-test.outcome != 'success' + - name: fail if root test failed + run: /bin/false + if: steps.root-test.outcome != 'success' diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml new file mode 100644 index 000000000..7dbcc8f6d --- /dev/null +++ b/.github/workflows/multiarch-stable.yaml @@ -0,0 +1,98 @@ +name: multiarch test for stable distros +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + paths: + - '.github/workflows/multiarch-stable.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.github/workflows/multiarch-stable.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' +jobs: + + build-old: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + os: + - debian-trixie + - debian-bookworm + - debian-buster + - ubuntu-trusty + arch: [386, arm/v7] + include: + - os: debian-trixie + arch: s390x + - os: debian-trixie + arch: ppc64le + - os: debian-bookworm + arch: s390x + - os: debian-bookworm + arch: ppc64le + steps: + - name: checkout + uses: actions/checkout@v4 + - name: enable foreign arch + uses: docker/setup-qemu-action@v2 + with: + image: tonistiigi/binfmt:latest + - name: set architecture name + run: | + __arch="${{ matrix.arch }}" + echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: compile and run unit tests + id: test + run: | + docker run --pids-limit 4096 --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test + continue-on-error: true + - name: create binary archive + run: | + docker run --platform linux/${{ matrix.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar + if: steps.test.outcome != 'success' + - name: upload binary archive + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }}-${{ env.ARCH_NAME }} + path: test-progs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + - name: save test outputs + run: make test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml new file mode 100644 index 000000000..3aee3b028 --- /dev/null +++ b/.github/workflows/multiarch.yaml @@ -0,0 +1,113 @@ +name: multiarch test for rolling distros +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + - musl + paths: + - '.github/workflows/multiarch.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.github/workflows/multiarch.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + # run monthly to catch rolling distro changes + schedule: + - cron: '45 02 1 * *' + +jobs: + + build-current: + strategy: + fail-fast: false + matrix: + os: + - alpine + - debian-sid + - fedora-rawhide + - opensuse-tumbleweed + arch: [ppc64le, s390x, 386, arm/v7] + variant: + - arch: ppc64le + runner: ubuntu-24.04 + - arch: s390x + runner: ubuntu-24.04 + - arch: 386 + runner: ubuntu-24.04 + - arch: arm/v7 + runner: ubuntu-24.04-arm + exclude: + - os: fedora-rawhide + variant: + arch: 386 + runner: ubuntu-24.04 + - os: fedora-rawhide + variant: + arch: arm/v7 + runner: ubuntu-24.04-arm + include: + - os: alpine + variant: + arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.variant.runner }} + steps: + - name: checkout + uses: actions/checkout@v4 + - name: enable foreign arch + uses: docker/setup-qemu-action@v2 + with: + image: tonistiigi/binfmt:latest + - name: set architecture name + run: | + __arch="${{ matrix.variant.arch }}" + echo "ARCH_NAME=${__arch//\//-}" >>"$GITHUB_ENV" + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: compile and run unit tests + id: test + run: | + docker run --pids-limit 4096 --platform linux/${{ matrix.variant.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test + continue-on-error: true + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+x + if: steps.test.outcome != 'success' + - name: create archives + run: | + docker run --platform linux/${{ matrix.variant.arch }} \ + -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload binary archive + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }}w-${{ env.ARCH_NAME }} + path: test-progs.tar + if: steps.test.outcome != 'success' + - name: upload test outputs + uses: actions/upload-artifact@v4 + with: + name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml new file mode 100644 index 000000000..9c4fa2e82 --- /dev/null +++ b/.github/workflows/native.yaml @@ -0,0 +1,239 @@ +name: compile and unit test on native arch +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + paths: + - '.github/workflows/native.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.github/workflows/native.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + +jobs: + stable: + strategy: + fail-fast: false + matrix: + os: + - debian-jessie + - debian-buster + - debian-bullseye + - debian-bookworm + - debian-trixie + - fedora-43 + - opensuse-leap-15.6 + - opensuse-leap-16.0 + variant: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.variant.runner }} + steps: + - name: checkout + uses: actions/checkout@v4 + + # On jessie, we use libreadline 5 (no licensing issue) + - name: set readline make option (Jessie) + run: echo READLINE=libreadline >> $GITHUB_ENV + if: matrix.os == 'debian-jessie' + + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + + - name: build and test + id: test + run: | + docker run -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -j$(nproc) -Orecurse V=1 READLINE=$READLINE test + continue-on-error: true + + - name: create test-progs.tar + run: | + docker run -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar + + - name: upload test-progs.tar + uses: actions/upload-artifact@v4 + with: + name: native-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-progs.tar + overwrite: true + + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: gcc-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' + + clang: + strategy: + fail-fast: false + matrix: + os: + - debian-buster + - debian-bullseye + - debian-bookworm + - debian-trixie + - fedora-43 + - opensuse-leap-15.6 + - opensuse-leap-16.0 + variant: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + include: + - os: debian-jessie + variant: + arch: x86_64 + runner: ubuntu-24.04 + runs-on: ${{ matrix.variant.runner }} + steps: + - name: checkout + uses: actions/checkout@v4 + + # On jessie, we use libreadline 5 (no licensing issue) + - name: set readline make option (Jessie) + run: echo READLINE=libreadline >> $GITHUB_ENV + if: matrix.os == 'debian-jessie' + + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + + - name: build and test with clang + id: test + run: | + docker run --pids-limit 4096 -e CC=clang \ + -w ${{ github.workspace }} -v${{ github.workspace }}:${{ github.workspace }} \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -j$(nproc) -Orecurse V=1 READLINE=$READLINE test + continue-on-error: true + + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' + + root-test: + needs: stable + strategy: + fail-fast: false + matrix: + os: + - debian-jessie + - debian-buster + - debian-bullseye + - debian-bookworm + - debian-trixie + - fedora-43 + - opensuse-leap-15.6 + - opensuse-leap-16.0 + variant: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.variant.runner }} + steps: + - name: mpath + run: sudo modprobe dm_multipath + - name: brd + run: sudo modprobe brd rd_nr=1 rd_size=65536 + + - name: checkout + uses: actions/checkout@v4 + + - name: download binary archive + uses: actions/download-artifact@v4 + with: + name: native-${{ matrix.os }}-${{ matrix.variant.arch }} + + - name: unpack binary archive + run: tar xfmv test-progs.tar + + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + + - name: run root tests + id: test + run: | + docker run \ + --workdir /__w/multipath-tools/multipath-tools --privileged \ + -v /dev/ram0:/dev/ram0 -e DIO_TEST_DEV=/dev/ram0 \ + -w /__w/multipath-tools/multipath-tools \ + -v${{ github.workspace }}:/__w/multipath-tools/multipath-tools \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + -C tests V=1 directio.out dmevents.out + continue-on-error: true + + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar + overwrite: true + if: steps.test.outcome != 'success' + + - name: fail if test failed + run: /bin/false + if: steps.test.outcome != 'success' diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml new file mode 100644 index 000000000..9f178f776 --- /dev/null +++ b/.github/workflows/rolling.yaml @@ -0,0 +1,96 @@ +name: compile and unit test on rolling distros +on: + push: + branches: + - master + - queue + - tip + - 'stable-*' + paths: + - '.github/workflows/rolling.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + pull_request: + branches: + - master + - queue + - 'stable-*' + paths: + - '.github/workflows/rolling.yaml' + - '**.h' + - '**.c' + - '**Makefile*' + - '**.mk' + # run weekly to catch rolling distro changes + schedule: + - cron: '30 06 * * 1' + +jobs: + rolling: + strategy: + fail-fast: false + matrix: + os: + - debian-sid + - opensuse-tumbleweed + - fedora-rawhide + variant: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + compiler: + - gcc + - clang + include: + - os: alpine + variant: + arch: x86_64 + runner: ubuntu-24.04 + compiler: gcc + - os: alpine + variant: + arch: x86_64 + runner: ubuntu-24.04 + compiler: clang + runs-on: ${{ matrix.variant.runner }} + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern + - name: build and test + id: test + run: | + docker run -w /build -v${{ github.workspace }}:/build \ + ghcr.io/mwilck/multipath-build-${{ matrix.os }} \ + CC=${{ matrix.compiler }} READLINE=libreadline -j$(nproc) -Orecurse test + continue-on-error: true + - name: create binary archive + run: make test-progs.tar + if: steps.test.outcome != 'success' + - name: upload binary archive + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} + path: test-progs.tar + if: steps.test.outcome != 'success' + - name: Make core dumps accessible + run: | + find . \( -name 'core*' \) -print0 | xargs -0 -r sudo chmod a+r + if: steps.test.outcome != 'success' + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + - name: upload test-outputs.tar + uses: actions/upload-artifact@v4 + with: + name: outputs-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} + path: test-outputs.tar + if: steps.test.outcome != 'success' + - name: fail + run: /bin/false + if: steps.test.outcome != 'success' From b420ed1fd1092c801ba53e98546aa04337528164 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 11:44:42 +0200 Subject: [PATCH 089/102] GitHub workflows: replace actions/checkout@v4 with @v7 actions/checkout@v4 uses the deprecated node 20. Signed-off-by: Martin Wilck --- .github/workflows/abi-stable.yaml | 4 ++-- .github/workflows/abi.yaml | 2 +- .github/workflows/build-and-unittest.yaml | 4 ++-- .github/workflows/coverity.yaml | 2 +- .github/workflows/foreign.yaml | 2 +- .github/workflows/multiarch-stable.yaml | 2 +- .github/workflows/multiarch.yaml | 2 +- .github/workflows/native.yaml | 6 +++--- .github/workflows/rolling.yaml | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index de8050ab0..b41a00dca 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -54,7 +54,7 @@ jobs: libmount-dev - name: checkout ${{ env.PARENT_TAG }} if: steps.download_abi.outcome != 'success' - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ env.PARENT_TAG }} - name: build ABI for ${{ env.PARENT_TAG }} @@ -85,7 +85,7 @@ jobs: run: /bin/false if: env.PARENT_TAG == '' - name: checkout ${{ github.ref }} - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ github.ref }} - name: download ABI for ${{ env.PARENT_TAG }} diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml index 1a7d3e5dd..4810737a5 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -25,7 +25,7 @@ jobs: if: env.ABI_BRANCH == '' run: echo "ABI_BRANCH=master" >> $GITHUB_ENV - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: get reference ABI id: reference continue-on-error: true diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml index 616a4de1f..386b01db8 100644 --- a/.github/workflows/build-and-unittest.yaml +++ b/.github/workflows/build-and-unittest.yaml @@ -20,7 +20,7 @@ jobs: rl: ['', 'libreadline', 'libedit'] cc: [ gcc, clang ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: update run: sudo apt-get update - name: dependencies @@ -77,7 +77,7 @@ jobs: rl: ['', 'libreadline', 'libedit'] cc: [ gcc, clang ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: mpath run: sudo modprobe dm_multipath - name: brd diff --git a/.github/workflows/coverity.yaml b/.github/workflows/coverity.yaml index 30e5471d3..2e981f8ae 100644 --- a/.github/workflows/coverity.yaml +++ b/.github/workflows/coverity.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: dependencies run: > sudo apt-get install --yes diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 42e21f580..9d0c99de9 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -41,7 +41,7 @@ jobs: container: ghcr.io/mwilck/multipath-cross-debian_cross-${{ matrix.os }}-${{ matrix.arch }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: build run: make -j$(nproc) -Orecurse test-progs.tar - name: upload binary archive diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index 7dbcc8f6d..cf1b016a4 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -47,7 +47,7 @@ jobs: arch: ppc64le steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: enable foreign arch uses: docker/setup-qemu-action@v2 with: diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 3aee3b028..c187e9cda 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -66,7 +66,7 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: enable foreign arch uses: docker/setup-qemu-action@v2 with: diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 9c4fa2e82..218be91cf 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -46,7 +46,7 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 # On jessie, we use libreadline 5 (no licensing issue) - name: set readline make option (Jessie) @@ -123,7 +123,7 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 # On jessie, we use libreadline 5 (no licensing issue) - name: set readline make option (Jessie) @@ -191,7 +191,7 @@ jobs: run: sudo modprobe brd rd_nr=1 rd_size=65536 - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: download binary archive uses: actions/download-artifact@v4 diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index 9f178f776..5f650e7a5 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -58,7 +58,7 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set coredump pattern run: | echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern From 0c76a5485adeefc0d375823e9b72e6b109657207 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 11:48:05 +0200 Subject: [PATCH 090/102] GitHub workflows: replace setup-qemu-action@v2 with @v4 setup-qemu-action@v2 uses node 20. Signed-off-by: Martin Wilck --- .github/workflows/foreign.yaml | 4 ++-- .github/workflows/multiarch-stable.yaml | 2 +- .github/workflows/multiarch.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 9d0c99de9..4f187403b 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -78,7 +78,7 @@ jobs: - name: unpack binary archive run: tar xfv test-progs.tar - name: enable foreign arch - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest - name: Set coredump pattern @@ -144,7 +144,7 @@ jobs: - name: unpack binary archive run: tar xfv test-progs.tar - name: enable foreign arch - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest - name: Set coredump pattern diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index cf1b016a4..947ea0cab 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -49,7 +49,7 @@ jobs: - name: checkout uses: actions/checkout@v7 - name: enable foreign arch - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest - name: set architecture name diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index c187e9cda..4f9e68cdb 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -68,7 +68,7 @@ jobs: - name: checkout uses: actions/checkout@v7 - name: enable foreign arch - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest - name: set architecture name From 2a92d133d906f95f7baf4488794d6a44e471aa8f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 12:34:55 +0200 Subject: [PATCH 091/102] Add dependabot.yml for tracking github action updates Use the special branch "workflows" for this purpose. It contains only files related to GitHub workflows. Signed-off-by: Martin Wilck --- dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 dependabot.yml diff --git a/dependabot.yml b/dependabot.yml new file mode 100644 index 000000000..57dff7fa7 --- /dev/null +++ b/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + target-branch: "workflows" + commit-message: + prefix: "GitHub workflows [dependabot]: " From 914a517fbe437f2e1c596983b6b5e41682c2a941 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 10 Jul 2026 12:34:55 +0200 Subject: [PATCH 092/102] Add dependabot.yml for tracking github action updates Use the special branch "workflows" for this purpose. It contains only files related to GitHub workflows. Signed-off-by: Martin Wilck --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..57dff7fa7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + target-branch: "workflows" + commit-message: + prefix: "GitHub workflows [dependabot]: " From 9dfa719f0f91e2c6ef335b0447a3bd8dbdadf7ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:59:40 +0000 Subject: [PATCH 093/102] GitHub workflows [dependabot]: Bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/abi-stable.yaml | 4 ++-- .github/workflows/abi.yaml | 4 ++-- .github/workflows/foreign.yaml | 6 +++--- .github/workflows/multiarch-stable.yaml | 4 ++-- .github/workflows/multiarch.yaml | 4 ++-- .github/workflows/native.yaml | 8 ++++---- .github/workflows/rolling.yaml | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index b41a00dca..61e60d8b6 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -62,7 +62,7 @@ jobs: run: make -j$(nproc) -Orecurse abi - name: save ABI if: steps.download_abi.outcome != 'success' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: multipath-abi-${{ env.PARENT_TAG }} path: abi @@ -113,7 +113,7 @@ jobs: continue-on-error: true - name: save differences if: steps.check_abi.outcome != 'success' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: abi-test path: abi-test diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml index 4810737a5..0d679bb4f 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -47,7 +47,7 @@ jobs: - name: create ABI run: make -Orecurse -j$(nproc) abi.tar.gz - name: save ABI - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: abi path: abi @@ -59,7 +59,7 @@ jobs: run: make abi-test - name: save differences if: steps.compare.outcome == 'failure' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: abi-test path: abi-test diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 4f187403b..310fbb650 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -45,7 +45,7 @@ jobs: - name: build run: make -j$(nproc) -Orecurse test-progs.tar - name: upload binary archive - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar @@ -103,7 +103,7 @@ jobs: run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test outputs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-${{ matrix.os }}-${{ matrix.arch }} path: test-outputs.tar @@ -172,7 +172,7 @@ jobs: run: make test-outputs.tar if: steps.root-test.outcome != 'success' - name: upload test outputs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: root-test-${{ matrix.os }}-${{ matrix.arch }} path: test-outputs.tar diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index 947ea0cab..e69e371c8 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -73,7 +73,7 @@ jobs: ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar if: steps.test.outcome != 'success' - name: upload binary archive - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: binaries-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-progs.tar @@ -87,7 +87,7 @@ jobs: run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test outputs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-outputs.tar diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 4f9e68cdb..f0fb4b6a4 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -96,13 +96,13 @@ jobs: ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar test-outputs.tar if: steps.test.outcome != 'success' - name: upload binary archive - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: binaries-${{ matrix.os }}w-${{ env.ARCH_NAME }} path: test-progs.tar if: steps.test.outcome != 'success' - name: upload test outputs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} path: test-outputs.tar diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 218be91cf..06f830fad 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -71,7 +71,7 @@ jobs: ghcr.io/mwilck/multipath-build-${{ matrix.os }} test-progs.tar - name: upload test-progs.tar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} path: test-progs.tar @@ -87,7 +87,7 @@ jobs: if: steps.test.outcome != 'success' - name: upload test-outputs.tar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: gcc-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} path: test-outputs.tar @@ -153,7 +153,7 @@ jobs: if: steps.test.outcome != 'success' - name: upload test-outputs.tar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} path: test-outputs.tar @@ -227,7 +227,7 @@ jobs: if: steps.test.outcome != 'success' - name: upload test-outputs.tar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: clang-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} path: test-outputs.tar diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index 5f650e7a5..43f5c4c72 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -73,7 +73,7 @@ jobs: run: make test-progs.tar if: steps.test.outcome != 'success' - name: upload binary archive - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: binaries-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} path: test-progs.tar @@ -86,7 +86,7 @@ jobs: run: make test-outputs.tar if: steps.test.outcome != 'success' - name: upload test-outputs.tar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: outputs-${{ matrix.os }}-${{ matrix.variant.arch }}-${{ matrix.compiler }} path: test-outputs.tar From 73be7841798da3c06a93d41776b332dc9f5978b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:59:59 +0000 Subject: [PATCH 094/102] GitHub workflows [dependabot]: Bump actions/checkout from 4 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codingstyle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codingstyle.yml b/.github/workflows/codingstyle.yml index a36b3874e..75d9a8a2f 100644 --- a/.github/workflows/codingstyle.yml +++ b/.github/workflows/codingstyle.yml @@ -30,7 +30,7 @@ jobs: stylecheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - run: >- echo "BASEREF=${{ github.event.pull_request.base.sha }}" >>$GITHUB_ENV if: github.event_name == 'pull_request' From 5ad8ce72986ff438822e6f728c152185779d04c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:00:08 +0000 Subject: [PATCH 095/102] GitHub workflows [dependabot]: Bump actions/download-artifact Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/foreign.yaml | 4 ++-- .github/workflows/native.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 310fbb650..5e6cf0bfd 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -72,7 +72,7 @@ jobs: run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV if: matrix.arch == 'armhf' - name: download binary archive - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} - name: unpack binary archive @@ -138,7 +138,7 @@ jobs: run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV if: matrix.arch == 'armhf' - name: download binary archive - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} - name: unpack binary archive diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 06f830fad..e9a16664a 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -194,7 +194,7 @@ jobs: uses: actions/checkout@v7 - name: download binary archive - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: native-${{ matrix.os }}-${{ matrix.variant.arch }} From 7177fa065b7610128981aa27b9ef3d4a7df41653 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:59:48 +0000 Subject: [PATCH 096/102] GitHub workflows [dependabot]: Bump dawidd6/action-download-artifact Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 6 to 21. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v6...v21) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-version: '21' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/abi-stable.yaml | 4 ++-- .github/workflows/abi.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index 61e60d8b6..7d197f49d 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -33,7 +33,7 @@ jobs: - name: try to download ABI for ${{ env.PARENT_TAG }} id: download_abi continue-on-error: true - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v21 with: workflow: abi-stable.yaml workflow_conclusion: '' @@ -90,7 +90,7 @@ jobs: ref: ${{ github.ref }} - name: download ABI for ${{ env.PARENT_TAG }} id: download_abi - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v21 with: workflow: abi-stable.yaml workflow_conclusion: '' diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml index 0d679bb4f..82af1e498 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -29,7 +29,7 @@ jobs: - name: get reference ABI id: reference continue-on-error: true - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v21 with: workflow: abi.yaml branch: ${{ env.ABI_BRANCH }} From d8b5f6702a1922fc4532843a83482b39c92cce9b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 13 Jul 2026 16:11:01 +0200 Subject: [PATCH 097/102] libmultipath: bump version to 0.15.0 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 8d25955ce..b0009275f 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -11,9 +11,9 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION_CODE 0x000E03 +#define VERSION_CODE 0x000F00 /* MMDDYY, in hex */ -#define DATE_CODE 0x02051A +#define DATE_CODE 0x070D1A #define PROG "multipath-tools" From c493f1fd84a5f9c3d5207ec4cc4f03c1aae74046 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 6 Jul 2026 17:44:54 +0200 Subject: [PATCH 098/102] libmultipath: iet prioritizer: obtain PATH_ID from udev Instead of reading /dev/disk/by-id, use the udev PATH_ID property to derive the device path. Signed-off-by: Martin Wilck Tested-by: Arnaldo Viegas de Lima --- libmultipath/prioritizers/Makefile | 2 +- libmultipath/prioritizers/iet.c | 98 ++++++++++++++---------------- 2 files changed, 45 insertions(+), 55 deletions(-) diff --git a/libmultipath/prioritizers/Makefile b/libmultipath/prioritizers/Makefile index ff2524c26..78f69ff76 100644 --- a/libmultipath/prioritizers/Makefile +++ b/libmultipath/prioritizers/Makefile @@ -8,7 +8,7 @@ include ../../Makefile.inc CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) CFLAGS += $(LIB_CFLAGS) LDFLAGS += -L$(multipathdir) -L$(mpathutildir) -LIBDEPS = -lmultipath -lmpathutil -lm -lpthread -lrt +LIBDEPS = -lmultipath -lmpathutil -ludev -lm -lpthread -lrt # If you add or remove a prioritizer also update multipath/multipath.conf.5 LIBS = \ diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index 1e7f1e895..9ca2877fe 100644 --- a/libmultipath/prioritizers/iet.c +++ b/libmultipath/prioritizers/iet.c @@ -1,10 +1,11 @@ -#include #include +#include #include #include #include #include #include +#include #include "prio.h" #include "debug.h" #include @@ -20,19 +21,19 @@ // prio "iet" // prio_args "preferredip=10.11.12.13" // -// Uses /dev/disk/by-path to find the IP of the device. // Assigns prio 20 (high) to the preferred IP and prio 10 (low) to the rest. // // by Olivier Lambert // -#define dc_log(prio, msg) condlog(prio, "%s: iet prio: " msg, dev) +#define dc_log(prio, msg) condlog(prio, "%s: iet prio: " msg, sysname) + // // name: find_regex // @param string: string you want to search into // @param regex: the pattern used // @return result: string found in string with regex, "none" if none -char *find_regex(char * string, char * regex) +char *find_regex(const char *string, const char *regex) { int err; regex_t preg; @@ -73,75 +74,64 @@ char *find_regex(char * string, char * regex) // name: inet_prio // @param // @return prio -int iet_prio(const char *dev, char * args) +int iet_prio(struct udev_device *udev, char *args) { char preferredip_buff[255] = ""; char *preferredip = &preferredip_buff[0]; + const char *by_path; + char *ip; + static bool arg_logged = false; + int prio = 10; + const char *sysname; + + if (!udev) + return 0; + sysname = udev_device_get_sysname(udev); + if (!sysname) + sysname = "UNKNOWN"; + // Phase 1 : checks. If anyone fails, return prio 0. // check if args exists if (!args) { - dc_log(0, "need prio_args with preferredip set"); + if (!arg_logged) { + dc_log(2, "need prio_args with preferredip set"); + arg_logged = true; + } return 0; } // check if args format is OK - if (sscanf(args, "preferredip=%254s", preferredip) == 1) { - } else { - dc_log(0, "unexpected prio_args format"); + if (sscanf(args, "preferredip=%254s", preferredip) != 1) { + if (!arg_logged) { + dc_log(2, "unexpected prio_args format"); + arg_logged = true; + } return 0; } // check if ip is not too short if (strlen(preferredip) <= 7) { - dc_log(0, "prio args: preferredip too short"); + if (!arg_logged) { + dc_log(2, "prio args: preferredip too short"); + arg_logged = true; + } return 0; } - // Phase 2 : find device in /dev/disk/by-path to match device/ip - DIR *dir_p; - struct dirent *dir_entry_p; - enum { BUFFERSIZE = 1024 }; - char buffer[BUFFERSIZE]; - char fullpath[BUFFERSIZE] = "/dev/disk/by-path/"; - dir_p = opendir(fullpath); - if (!dir_p) - goto out; - // loop to find device in /dev/disk/by-path - while( NULL != (dir_entry_p = readdir(dir_p))) { - if (dir_entry_p->d_name[0] != '.') { - char path[BUFFERSIZE] = "/dev/disk/by-path/"; - strcat(path,dir_entry_p->d_name); - ssize_t nchars = readlink(path, buffer, sizeof(buffer)-1); - if (nchars != -1) { - char *device; - buffer[nchars] = '\0'; - device = find_regex(buffer,"(sd[a-z]+)"); - // if device parsed is the right one - if (device!=NULL && strncmp(device, dev, strlen(device)) == 0) { - char *ip; - ip = find_regex(dir_entry_p->d_name,"([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"); - // if prefferedip and ip fetched matches - if (ip!=NULL && strncmp(ip, preferredip, strlen(ip)) == 0) { - // high prio - free(ip); - free(device); - closedir(dir_p); - return 20; - } - free(ip); - } - free(device); - } - else { - printf("error\n"); - } - } + by_path = udev_device_get_property_value(udev, "ID_PATH"); + if (by_path == NULL) { + dc_log(2, "failed to get BY_PATH property"); + return 0; } - // nothing found, low prio - closedir(dir_p); -out: - return 10; + condlog(3, "%s: iet prio: by_path=%s", sysname, by_path); + ip = find_regex(by_path, + "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"); + if (ip != NULL && strncmp(ip, preferredip, strlen(ip)) == 0) + prio = 20; + + free(ip); + return prio; } int getprio(struct path * pp, char * args) { - return iet_prio(pp->dev, args); + return iet_prio(pp->udev, args); } From 2c38dfcadbfd53a51d18e486eb1f6d8750f1f095 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 13 Jul 2026 17:32:40 +0200 Subject: [PATCH 099/102] Update NEWS.md Signed-off-by: Martin Wilck --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index e1566af83..5ae6c8d35 100644 --- a/NEWS.md +++ b/NEWS.md @@ -44,6 +44,9 @@ See [README.md](README.md) for additional information. set to `once`. Commit 8933b22. * Avoid potential buffer overflows in the iet and datacore prioritizers. Commit 4611f97. +* iet prioritizer: avoid misleading error message with systemd 256 and + newer, and properly use udev to derive path parameters. Commit c493f1f. + Fixes [#145](https://github.com/opensvc/multipath-tools/issues/145). ### Other changes From f474bdf9d016e580baea54f1c3d7282b6623ffdd Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 17 Jul 2026 12:05:31 +0200 Subject: [PATCH 100/102] test-kpartx: add test for over-long partition separator kpartx can crash if an over-long partition separator is used. Add a test case for that. This test case will fail without the subsequent commit. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 32a5aef01..6e93302ae 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -460,4 +460,15 @@ $KPARTX -d $KPARTX_OPTS "$FILE4" # /dev/$loop is _not_ automatically deleted [[ -b "/dev/${loop}" ]] +step "test kpartx creation with very long partition separator" +# Overlong, invalid partition separator +LONG_SEP="part---01.part---02.part---03.part---04.part---05.part---06.part---07.part---08.part---09.part---10.part---11.part---12.part---13.part---14.part---15.part---16.part---17.part---18.part---19.part---20." +ERR=0 +# This kpartx command should return with error 1 (for a single partitition!) +# and print the error message below. +# Without the fix for overlong partition separator, kpartx crashes. +$KPARTX -a -p "$LONG_SEP" "$LO1" 2>"$WORKDIR/long_sep.out" || ERR=$? +[[ $ERR = 1 ]] +grep -q 'partition name too long for partition 1' "$WORKDIR/long_sep.out" + OK=yes From 030710318a9d991724cdb908c8519702cd9cbc35 Mon Sep 17 00:00:00 2001 From: Kou Wenqi Date: Fri, 17 Jul 2026 11:34:43 +0800 Subject: [PATCH 101/102] kpartx: fix crash and truncated device creation with long -p delimiter When the -p delimiter is long enough to make the formatted partition name exceed PARTNAME_SIZE (128 bytes), three issues occur: 1. format_partname() fails but snprintf has already written a truncated name into the buffer. dm_find_part() returns 0 and the caller proceeds to dm_addmap() with the truncated name, creating a device that was never intended. 2. dm_find_part() returns early without setting *part_uuid. The uninitialized local variable part_uuid then gets passed to check_uuid() -> strchr(), causing a SIGSEGV. 3. The callers cannot distinguish between 'partition not found, create new' and 'name construction failed' since both return 0. Fix by: - Having dm_find_part() return DFP_ERR when format_partname() fails, with an error message printed by dm_find_part() itself - Defining an enum to represent the return values of dm_find_part(): enum { DFP_DEVICE_CREATE = DM_DEVICE_CREATE, DFP_DEVICE_RELOAD = DM_DEVICE_RELOAD, DFP_ERR = -1 }; This enum is placed in kpartx/devmapper.h, which now also includes directly to make the header self-contained. dm_find_part() uses these enum values explicitly in all return statements. - In the ADD/UPDATE loops (both main and container), assigning the return value directly to 'op' and checking for DFP_ERR to handle errors - In the DELETE loop, checking the return value against DFP_DEVICE_RELOAD to proceed with removal, skipping on error or not-found - Initializing part_uuid to NULL in all three partition loop bodies (ADD/UPDATE main loop, container partition loop, DELETE loop) so that the 'if (part_uuid && uuid)' guard correctly skips the UUID check when dm_find_part() returns early Reproduce steps (now also in the test-kpartx script): # Create test image dd if=/dev/zero of=/tmp/vhlg-test.img bs=1M count=10 parted /tmp/vhlg-test.img mklabel msdos parted /tmp/vhlg-test.img mkpart primary ext4 1MiB 5MiB # Reproduce kpartx -a -p $(python3 -c "print('A'*200)") /tmp/vhlg-test.img # Cleanup kpartx -d /tmp/vhlg-test.img rm -f /tmp/vhlg-test.img [mwilck: removed version history from commit message] Signed-off-by: Kou Wenqi Reviewed-by: Martin Wilck --- kpartx/devmapper.c | 17 +++++++++-------- kpartx/devmapper.h | 9 +++++++++ kpartx/kpartx.c | 38 ++++++++++++++++++++++---------------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/kpartx/devmapper.c b/kpartx/devmapper.c index 45dac585e..091d2a490 100644 --- a/kpartx/devmapper.c +++ b/kpartx/devmapper.c @@ -644,18 +644,19 @@ int dm_find_part(const char *parent, const char *delim, int part, char dev_t[32]; if (!format_partname(name, namesiz, parent, delim, part)) { - if (verbose) - fprintf(stderr, "partname too small\n"); - return 0; + fprintf(stderr, "partition name too long for partition %d\n", part); + return DFP_ERR; } r = dm_map_present(name, part_uuid); - if (r == 1 || parent_uuid == NULL || *parent_uuid == '\0') - return r; + if (r == 1) + return DFP_DEVICE_RELOAD; + if (parent_uuid == NULL || *parent_uuid == '\0') + return DFP_DEVICE_CREATE; uuid = make_prefixed_uuid(part, parent_uuid); if (!uuid) - return 0; + return DFP_DEVICE_CREATE; tmp = dm_find_uuid(uuid); if (tmp == NULL) @@ -688,14 +689,14 @@ int dm_find_part(const char *parent, const char *delim, int part, if (r == 1) { free(tmp); *part_uuid = uuid; - return 1; + return DFP_DEVICE_RELOAD; } if (verbose) fprintf(stderr, "renaming %s->%s failed\n", tmp, name); out: free(uuid); free(tmp); - return r; + return DFP_DEVICE_CREATE; } char *nondm_create_uuid(dev_t devt) diff --git a/kpartx/devmapper.h b/kpartx/devmapper.h index e4db86213..99bab2210 100644 --- a/kpartx/devmapper.h +++ b/kpartx/devmapper.h @@ -1,6 +1,8 @@ #ifndef KPARTX_DEVMAPPER_H_INCLUDED #define KPARTX_DEVMAPPER_H_INCLUDED +#include + #ifdef DM_SUBSYSTEM_UDEV_FLAG0 #define MPATH_UDEV_RELOAD_FLAG DM_SUBSYSTEM_UDEV_FLAG0 #else @@ -18,6 +20,13 @@ dev_t dm_get_first_dep(char *devname); char * dm_mapuuid(const char *mapname); int dm_devn (const char * mapname, unsigned int *major, unsigned int *minor); int dm_remove_partmaps (char * mapname, char *uuid, dev_t devt, int verbose); + +enum { + DFP_DEVICE_CREATE = DM_DEVICE_CREATE, + DFP_DEVICE_RELOAD = DM_DEVICE_RELOAD, + DFP_ERR = -1 +}; + int dm_find_part(const char *parent, const char *delim, int part, const char *parent_uuid, char *name, size_t namesiz, char **part_uuid, int verbose); diff --git a/kpartx/kpartx.c b/kpartx/kpartx.c index cfd821284..fc8f9e5eb 100644 --- a/kpartx/kpartx.c +++ b/kpartx/kpartx.c @@ -437,7 +437,7 @@ main(int argc, char **argv){ case UPDATE: /* ADD and UPDATE share the same code that adds new partitions. */ for (j = 0, c = 0; j < n; j++) { - char *part_uuid, *reason; + char *part_uuid = NULL, *reason; if (slices[j].size == 0) continue; @@ -454,10 +454,13 @@ main(int argc, char **argv){ exit(1); } - op = (dm_find_part(mapname, delim, j + 1, uuid, - partname, sizeof(partname), - &part_uuid, verbose) ? - DM_DEVICE_RELOAD : DM_DEVICE_CREATE); + op = dm_find_part(mapname, delim, j + 1, uuid, + partname, sizeof(partname), + &part_uuid, verbose); + if (op == DFP_ERR) { + r++; + continue; + } if (part_uuid && uuid) { if (check_uuid(uuid, part_uuid, &reason) != 0) { @@ -500,7 +503,7 @@ main(int argc, char **argv){ d = c; while (c) { for (j = 0; j < n; j++) { - char *part_uuid, *reason; + char *part_uuid = NULL, *reason; int k = slices[j].container - 1; if (slices[j].size == 0) @@ -526,11 +529,14 @@ main(int argc, char **argv){ exit(1); } - op = (dm_find_part(mapname, delim, j + 1, uuid, - partname, - sizeof(partname), - &part_uuid, verbose) ? - DM_DEVICE_RELOAD : DM_DEVICE_CREATE); + op = dm_find_part(mapname, delim, + j + 1, uuid, partname, + sizeof(partname), + &part_uuid, verbose); + if (op == DFP_ERR) { + r++; + continue; + } if (part_uuid && uuid) { if (check_uuid(uuid, part_uuid, &reason) != 0) { @@ -570,11 +576,11 @@ main(int argc, char **argv){ } for (j = MAXSLICES-1; j >= 0; j--) { - char *part_uuid, *reason; - if (slices[j].size || - !dm_find_part(mapname, delim, j + 1, uuid, - partname, sizeof(partname), - &part_uuid, verbose)) + char *part_uuid = NULL, *reason; + int res = dm_find_part(mapname, delim, j + 1, uuid, + partname, sizeof(partname), + &part_uuid, verbose); + if (slices[j].size || res != DFP_DEVICE_RELOAD) continue; if (part_uuid && uuid) { From a038ae0d67e15cf59e0c47ddf9cca60240afbcd0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 17 Jul 2026 21:12:17 +0200 Subject: [PATCH 102/102] Update NEWS.md Signed-off-by: Martin Wilck --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 5ae6c8d35..01b09c330 100644 --- a/NEWS.md +++ b/NEWS.md @@ -47,6 +47,8 @@ See [README.md](README.md) for additional information. * iet prioritizer: avoid misleading error message with systemd 256 and newer, and properly use udev to derive path parameters. Commit c493f1f. Fixes [#145](https://github.com/opensvc/multipath-tools/issues/145). +* An overlong partition delimiter (-p option) could cause kpartx to crash. + Fix it. Commit a2f344a. ### Other changes