diff --git a/.clang-format b/.clang-format index 5c762eba5..927b8e0dd 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 @@ -29,4 +30,6 @@ ForEachMacros: - vector_foreach_slot - vector_foreach_slot_backwards - vector_foreach_slot_after +Cpp11BracedListStyle: false +ReflowComments: Never --- 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/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]: " diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index d0ee4543f..7d197f49d 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -21,19 +21,19 @@ 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 - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v21 with: workflow: abi-stable.yaml workflow_conclusion: '' @@ -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 }} @@ -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 @@ -75,22 +75,22 @@ 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 + uses: actions/checkout@v7 with: 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: '' @@ -112,8 +112,8 @@ jobs: 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 + if: steps.check_abi.outcome != 'success' + 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 3b5f1c1e4..82af1e498 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -22,14 +22,14 @@ 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 + uses: actions/checkout@v7 - 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 }} @@ -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 @@ -55,11 +55,11 @@ 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' }} - uses: actions/upload-artifact@v4 + if: steps.compare.outcome == 'failure' + uses: actions/upload-artifact@v7 with: name: abi-test path: abi-test diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml index fdcc59b73..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 @@ -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) @@ -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/codingstyle.yml b/.github/workflows/codingstyle.yml index 067f32666..75d9a8a2f 100644 --- a/.github/workflows/codingstyle.yml +++ b/.github/workflows/codingstyle.yml @@ -30,13 +30,13 @@ 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' }} + 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/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 b232b60c1..5e6cf0bfd 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -41,11 +41,11 @@ 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 - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar @@ -67,31 +67,51 @@ 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 + uses: actions/download-artifact@v8 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 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - name: run tests - uses: mosteo-actions/docker-run@v1 + 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@v7 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: > - --workdir /__w/multipath-tools/multipath-tools - --platform linux/${{ env.CONTAINER_ARCH }} - pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" + 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 @@ -113,37 +133,51 @@ 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 + uses: actions/download-artifact@v8 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 + uses: docker/setup-qemu-action@v4 with: image: tonistiigi/binfmt:latest + - name: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - 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 - --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: show root test output - run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - - name: fail + - 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@v7 + 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 == 'failure' }} + if: steps.root-test.outcome != 'success' diff --git a/.github/workflows/multiarch-stable.yaml b/.github/workflows/multiarch-stable.yaml index b8c770e40..e69e371c8 100644 --- a/.github/workflows/multiarch-stable.yaml +++ b/.github/workflows/multiarch-stable.yaml @@ -47,38 +47,52 @@ jobs: arch: ppc64le steps: - name: checkout - uses: actions/checkout@v4 + 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 + 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 - 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: "--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 + uses: actions/upload-artifact@v7 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' + - 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@v7 + with: + name: test-${{ matrix.os }}-${{ env.ARCH_NAME }} + path: test-outputs.tar + overwrite: true if: steps.test.outcome != 'success' - - name: fail + - 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 806275a13..f0fb4b6a4 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' @@ -30,7 +31,6 @@ on: jobs: build-current: - runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -40,48 +40,74 @@ 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 + 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 + 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 - 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: "--platform linux/${{ matrix.arch }}" - pull-params: "--platform linux/${{ matrix.arch }}" + 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: 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 }}" + - 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 + uses: actions/upload-artifact@v7 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: fail + - name: upload test outputs + uses: actions/upload-artifact@v7 + 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 index 9e89780b1..e9a16664a 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -46,62 +46,58 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - - 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') }} + # 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 - 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 + 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: build and test (jessie) - id: test_jessie - # On jessie, we use libreadline 5 (no licensing issue) - if: ${{ matrix.os == 'debian-jessie' }} - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: -j$(nproc) -Orecurse READLINE=libreadline 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: create ${{ env.ARCHIVE_TGT }} - if: ${{ matrix.os != 'debian-jessie' }} - uses: mosteo-actions/docker-run@v1 + - name: upload test-progs.tar + uses: actions/upload-artifact@v7 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 }} + 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: upload binary archive - uses: actions/upload-artifact@v4 + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v7 with: - name: native-${{ matrix.os }}-${{ matrix.variant.arch }} - path: ${{ env.ARCHIVE_TGT }} + name: gcc-outputs-${{ matrix.os }}-${{ matrix.variant.arch }} + path: test-outputs.tar overwrite: true + 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' + clang: strategy: fail-fast: false @@ -127,75 +123,46 @@ 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) + run: echo READLINE=libreadline >> $GITHUB_ENV + if: matrix.os == 'debian-jessie' - - 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: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - - name: clang + - name: build and test with clang id: test - if: ${{ matrix.os != 'debian-jessie' }} - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: -e CC=clang - command: -j$(nproc) -Orecurse test - continue-on-error: true - - name: clang (jessie) - id: test_jessie - if: ${{ matrix.os == 'debian-jessie' }} - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - params: -e CC=clang - command: -j$(nproc) -Orecurse READLINE=libreadline 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 ${{ env.ARCHIVE_TGT }} - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: ${{ env.ARCHIVE_TGT }} - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - steps.test.outcome != 'success' ) }} - - name: create ${{ env.ARCHIVE_TGT }} (jessie) - uses: mosteo-actions/docker-run@v1 - with: - image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} - command: READLINE=libreadline ${{ env.ARCHIVE_TGT }} - if: >- - ${{ ( matrix.os == 'debian-jessie' && - steps.test_jessie.outcome != 'success' ) || - ( matrix.os != 'debian-jessie' && - 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: upload binary archive - uses: actions/upload-artifact@v4 + - name: create test-outputs.tar + run: make test-outputs.tar + if: steps.test.outcome != 'success' + + - name: upload test-outputs.tar + uses: actions/upload-artifact@v7 with: - name: clang-${{ matrix.os }}-${{ matrix.variant.arch }} - path: ${{ env.ARCHIVE_TGT }} + 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 @@ -224,33 +191,49 @@ 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 + uses: actions/download-artifact@v8 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: Set coredump pattern + run: | + echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern - 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 directio.out dmevents.out - id: root-test + 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: show root test output - run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - - name: fail + + - 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@v7 + 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' diff --git a/.github/workflows/rolling.yaml b/.github/workflows/rolling.yaml index a4706e52e..43f5c4c72 100644 --- a/.github/workflows/rolling.yaml +++ b/.github/workflows/rolling.yaml @@ -56,23 +56,41 @@ 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 + uses: actions/checkout@v7 + - 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 + 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 + uses: actions/upload-artifact@v7 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@v7 + 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' 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 }}" diff --git a/Makefile b/Makefile index f06f7faa9..e8de94935 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) @@ -126,15 +127,21 @@ 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 - @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) $(wildcard tests/core*) + $(Q)printf "%s\\n" $^ | cpio -o -H crc >$@ + +test-outputs.tar: $(wildcard tests/*.out) $(wildcard tests/*.vgr) $(wildcard tests/core*) + $(Q)tar cf "$@" $^ || touch $@ .PHONY: TAGS TAGS: diff --git a/NEWS.md b/NEWS.md index 1fd4766ab..01b09c330 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,60 @@ 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. +* 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). +* An overlong partition delimiter (-p option) could cause kpartx to crash. + Fix it. Commit a2f344a. + +### 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. 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/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]: " diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 337d5f45c..9062e9041 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -53,14 +53,13 @@ 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; 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,}; @@ -193,6 +192,8 @@ 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]; @@ -201,8 +202,11 @@ 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))) { @@ -217,7 +221,7 @@ 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)); @@ -243,6 +247,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; @@ -273,8 +279,11 @@ 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; } 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/gpt.c b/kpartx/gpt.c index 69ddce99a..1068da2e3 100644 --- a/kpartx/gpt.c +++ b/kpartx/gpt.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "crc32.h" #include "kpartx.h" @@ -52,6 +53,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 +77,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); } @@ -222,24 +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; - size_t count = __le32_to_cpu(gpt->num_partition_entries) * - __le32_to_cpu(gpt->sizeof_partition_entry); + gpt_entry *pte = NULL; + size_t num_entries = __le32_to_cpu(gpt->num_partition_entries); + 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 (!count) return NULL; + 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 (aligned_malloc((void **)&pte, get_sector_size(fd), &count)) + if (pe_map == MAP_FAILED) { + fprintf(stderr, "%s: mmap failed: %s\n", __func__, strerror(errno)); return NULL; - memset(pte, 0, count); + } - if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte, - count)) { - free(pte); - 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; } @@ -278,9 +308,8 @@ 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; @@ -334,28 +363,16 @@ is_gpt_valid(int fd, uint64_t lba, return 0; } - if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) { - free(*gpt); - *gpt = NULL; - return 0; - } - - /* 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"); + if (!(*ptes = alloc_read_gpt_entries(fd, *gpt, ns))) { free(*gpt); *gpt = NULL; - free(*ptes); - *ptes = NULL; return 0; } /* We're done, all's well */ return 1; } + /** * compare_gpts() - Search disk for valid GPT headers and PTEs * @pgpt is the primary GPT header @@ -477,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; @@ -491,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 */ @@ -597,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) 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/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) { 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 diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index 4ac43fa37..36cc78151 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. .\" ---------------------------------------------------------------------------- . @@ -23,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 . . .\" ---------------------------------------------------------------------------- @@ -35,7 +30,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 +52,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..1a2b9511f 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. .\" ---------------------------------------------------------------------------- . @@ -23,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 . . .\" ---------------------------------------------------------------------------- @@ -35,7 +30,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 +69,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/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..51594ad47 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; @@ -189,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/runner.c b/libmpathutil/runner.c new file mode 100644 index 000000000..31a534946 --- /dev/null +++ b/libmpathutil/runner.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC +#include +#include +#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 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 cleanup_context(void *arg) +{ + int st; + struct runner_context *rctx = arg; + + if (!rctx) { + condlog(0, "ERROR: %s: rctx is NULL", __func__); + pause(); + } + + 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_DEAD || st == RUNNER_CANCELLED ? 3 : 2, + "%s: runner %p finished in state '%s'", __func__, rctx, + runner_state_name(st)); + } + put_shared_ptr(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 = arg; + + pthread_cleanup_push(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); + + /* Only run the function if we haven't been cancelled */ + if (st == RUNNER_IDLE) + (*rctx->func)(rctx->data); + + pthread_cleanup_pop(1); + 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); + assert(uatomic_cmpxchg(&rctx->status, RUNNER_CANCELLED, + RUNNER_DEAD) == RUNNER_CANCELLED); + st_new = RUNNER_CANCELLED; + /* fallthrough */ + case RUNNER_CANCELLED: + break; + case RUNNER_DONE: + st_new = RUNNER_DEAD; + break; + 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); + put_shared_ptr(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 = alloc_shared_ptr(sizeof(*rctx) + size, NULL); + if (!rctx) + return NULL; + + rctx->func = func; + /* + * 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(). + */ + get_shared_ptr(rctx); + 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)); + put_shared_ptr(rctx); + put_shared_ptr(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 */ 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 */ 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..89b724112 --- /dev/null +++ b/libmultipath/async_checker.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 SUSE LLC + +#include +#include +#include +#include +#include +#include +#include "checkers.h" +#include "async_checker.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) + acc->async_context_size) + +int async_check_init(struct checker *c) +{ + struct async_checker_context *acc; + struct stat sb; + 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; + SET_INVALID_MPCONTEXT(acc->rdata.mpc); + c->context = acc; + if (acc->async_init) + return acc->async_init(&acc->rdata); + 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, 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 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, union checker_mpcontext *mpc) +{ + struct async_checker_context *acc = c->context; + + if (!acc) + return PATH_UNCHECKED; + + if (mpc && !IS_INVALID_MPCONTEXT(*mpc)) + acc->rdata.mpc = *mpc; + + 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; + 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..d7d02e8e6 --- /dev/null +++ b/libmultipath/async_checker.h @@ -0,0 +1,36 @@ +// 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; + 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, union checker_mpcontext *mpctx); +int async_check_check(struct checker *c, union checker_mpcontext *mpctx); + +#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 bb6ad1ee6..12d928a16 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -9,29 +9,13 @@ #include "debug.h" #include "checkers.h" +#include "async_checker.h" #include "vector.h" #include "util.h" +#include "structs.h" 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", @@ -57,41 +41,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) +static void free_checker_class(void *ptr) { - 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) -{ - 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) @@ -102,7 +56,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) @@ -110,9 +75,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) @@ -138,6 +102,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]; @@ -166,7 +142,31 @@ static struct checker_class *add_checker_class(const char *name) errstr); goto out; } - c->check = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_check"); + + 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 *, union checker_mpcontext *)) + dlsym(c->handle, "libcheck_check"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); @@ -180,10 +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->thread = (void *(*)(void*)) dlsym(c->handle, "libcheck_thread"); - c->pending = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_pending"); + c->reset = (void (*)(void))dlsym(c->handle, "libcheck_reset"); + 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 */ @@ -196,28 +195,12 @@ 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); return c; out: - free_checker_class(c); + put_shared_ptr(c); return NULL; } @@ -258,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; } @@ -303,16 +267,20 @@ 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) +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; } @@ -324,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; @@ -339,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); } } @@ -365,6 +338,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) @@ -387,43 +362,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) @@ -447,7 +385,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 a969e7d15..556613ac4 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -124,12 +124,49 @@ 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 */ }; -struct checker_class; +/* + * 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 { + struct list_head node; + void *handle; + int sync; + char name[CHECKER_NAME_LEN]; + 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; + short msgtable_size; +}; + struct checker { struct checker_class *cls; int fd; @@ -137,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) @@ -150,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 *); @@ -159,32 +193,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. - */ -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); +void checker_disable(struct checker *); +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); @@ -197,13 +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_thread(struct checker_context *ctx); 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/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); } 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..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,6 +12,7 @@ #include "sg_include.h" #include "libsg.h" #include "checkers.h" +#include "async_checker.h" #include "debug.h" #define INQUIRY_CMD 0x12 @@ -32,18 +31,18 @@ * 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) \ + (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 { \ + 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 { \ + mpctx.long_val = 0; \ + } while (0); enum { MSG_CLARIION_QUERY_FAILED = CHECKER_FIRST_MSGID, @@ -83,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; @@ -96,49 +97,22 @@ void hexadecimal_to_ascii(char * wwn, char *wwnstr) wwnstr[32]=0; } -int libcheck_init (struct checker * c) -{ - /* - * 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; - - return 0; -} - -int libcheck_mp_init (struct checker * c) +int libcheck_async_init(struct runner_data *rdata) { - /* - * 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); - } + ((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) +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; @@ -155,14 +129,14 @@ int libcheck_check (struct checker * c) 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; } @@ -194,12 +168,12 @@ int libcheck_check (struct checker * c) 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; } } @@ -208,13 +182,13 @@ int libcheck_check (struct checker * c) 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; } @@ -228,19 +202,19 @@ int libcheck_check (struct checker * c) ((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; } @@ -251,7 +225,7 @@ int libcheck_check (struct checker * c) */ 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 { @@ -266,8 +240,8 @@ int libcheck_check (struct checker * c) 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); @@ -282,7 +256,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(rdata->mpc); condlog(3, "emc_clariion_checker: Active " "path to inactive snapshot WWN %s.", wwnstr); @@ -291,26 +265,26 @@ int libcheck_check (struct checker * c) "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(c); + CLR_INACTIVE_SNAP(rdata->mpc); } } else { - if (IS_INACTIVE_SNAP(c)) { + 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 */ } } 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; } 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; } 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; diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index ba4ca683c..62460cb2c 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -3,116 +3,29 @@ * * Copyright (c) 2004 Christophe Varoqui */ -#include -#include -#include -#include -#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 "util.h" -#include "time-util.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_checker_context { - dev_t devt; - int state; - int running; /* uatomic access only */ - int fd; - 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; - unsigned int nr_timeouts; - bool checked_state; -}; - -int libcheck_init (struct checker * c) -{ - struct tur_checker_context *ct; - 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); - if (fstat(c->fd, &sb) == 0) - ct->devt = sb.st_rdev; - ct->ctx.cls = c->cls; - c->context = ct; - - 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; -} - -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 }; @@ -128,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) { @@ -143,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) { @@ -188,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) { @@ -205,288 +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; } - -#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 - * 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_checker_context *ct) -{ - 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) || - ++sleep_cnt % TUR_SLEEP_INTERVAL != 0) - return; - - condlog(1, "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"); - 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 *libcheck_thread(struct checker_context *ctx) -{ - 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); - - condlog(4, "%d:%d : tur checker starting up", major(ct->devt), - minor(ct->devt)); - - tur_deep_sleep(ct); - state = tur_check(ct->fd, ct->timeout, &msgid); - 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); -} - -static void tur_set_async_timeout(struct checker *c) -{ - 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) { - 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; - } - - ct->checked_state = true; - return tur_status; -} - -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); -} - -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) - return c->path_state; - - return check_pending(c); -} - -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; - - 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; - - if (!uatomic_sub_return(&ct->holders, 1)) { - /* It did terminate, eventually */ - cleanup_context(ct); - ((struct tur_checker_context *)c->context)->nr_timeouts = 0; - } - - 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); - } - } - - return tur_status; -} diff --git a/libmultipath/config.c b/libmultipath/config.c index f9198d605..0d8774222 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -447,10 +447,8 @@ 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); merge_num(flush_on_last_del); merge_num(fast_io_fail); merge_num(dev_loss); @@ -505,10 +503,8 @@ 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); merge_num(flush_on_last_del); merge_num(attribute_flags); merge_num(user_friendly_names); @@ -616,10 +612,8 @@ 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; 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 618fa5723..ad3b24233 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -64,10 +64,8 @@ struct hwentry { int pgpolicy; int pgfailback; - int rr_weight; int no_path_retry; int minio; - int minio_rq; int flush_on_last_del; int fast_io_fail; unsigned int dev_loss; @@ -114,10 +112,8 @@ struct mpentry { uint8_t sa_flags; int pgpolicy; int pgfailback; - int rr_weight; int no_path_retry; int minio; - int minio_rq; int flush_on_last_del; int attribute_flags; int user_friendly_names; @@ -146,12 +142,10 @@ struct config { int pgpolicy_flag; int pgpolicy; int minio; - int minio_rq; unsigned int checkint; 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..c3ae42e92 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -16,11 +16,10 @@ #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 -#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 @@ -50,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 diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 797707d9d..0e947b1d9 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -746,24 +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_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, @@ -1350,51 +1332,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 +2099,9 @@ 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") /* * If you add or remove a keyword also update multipath/multipath.conf.5 @@ -2187,10 +2127,10 @@ 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_rq", &def_minio_rq_handler, &snprint_def_minio_rq); + 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", &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,10 +2235,10 @@ 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); + 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); install_keyword("fast_io_fail_tmo", &hw_fast_io_fail_handler, &snprint_hw_fast_io_fail); @@ -2341,10 +2281,10 @@ 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); + 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); install_keyword("dev_loss_tmo", &ovr_dev_loss_handler, &snprint_ovr_dev_loss); @@ -2390,10 +2330,10 @@ 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); + 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); install_keyword("features", &mp_features_handler, &snprint_mp_features); 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/discovery.c b/libmultipath/discovery.c index 0efb8213d..ceca9fd38 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", @@ -1985,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; } @@ -2008,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/dmparser.c b/libmultipath/dmparser.c index 3411bd00b..83760e30a 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; } @@ -351,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; @@ -521,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 23c549335..a8fbfa70a 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -56,10 +56,7 @@ .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, .flush_on_last_del = FLUSH_UNUSED, .user_friendly_names = USER_FRIENDLY_NAMES_OFF, .fast_io_fail = 5, diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 78fa2d436..f631f4530 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_34.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; @@ -221,7 +221,6 @@ global: /* checkers */ checker_is_sync; sg_read; - start_checker_thread; /* prioritizers */ get_asymmetric_access_state; 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]; 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/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) { diff --git a/libmultipath/prioritizers/datacore.c b/libmultipath/prioritizers/datacore.c index ab813a0ea..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=%s", - (int *)&timeout_ms, preferredsds) == 2) {} - else if (sscanf(args, "preferredsds=%s timeout=%i", - preferredsds, (int *)&timeout_ms) == 2) {} - else if (sscanf(args, "preferredsds=%s", - 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/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 diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index f5fdd42aa..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=%s", 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); } diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 56895c4bb..162e7af21 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; @@ -582,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. @@ -622,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); @@ -640,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; } @@ -870,21 +854,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) { @@ -902,13 +871,14 @@ select_minio_bio (struct config *conf, struct multipath * mp) int select_minio(struct config *conf, struct multipath *mp) { - unsigned int minv_dmrq[3] = {1, 1, 0}, version[3]; + const char *origin; - if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) - && VERSION_GE(version, minv_dmrq)) - return select_minio_rq(conf, mp); - 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; } int select_fast_io_fail(struct config *conf, struct path *pp) 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.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..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; @@ -540,8 +533,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/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" diff --git a/mpathpersist/mpathpersist.8.in b/mpathpersist/mpathpersist.8.in index fecef0d63..2208008c2 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. .\" ---------------------------------------------------------------------------- . @@ -50,7 +46,7 @@ various options. .BI \-verbose|\-v " level" Verbosity: .RS -.TP 5 +.TP .I 0 Critical messages. .TP @@ -173,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 @@ -216,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.8.in b/multipath/multipath.8.in index b88e9a4ca..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. .\" ---------------------------------------------------------------------------- . @@ -100,7 +96,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 +191,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 84cd1a0ab..2ea78fc77 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -1,12 +1,8 @@ .\" ---------------------------------------------------------------------------- -.\" 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. .\" ---------------------------------------------------------------------------- . -.TH MULTIPATH.CONF 5 2025-09-12 Linux +.TH MULTIPATH.CONF 5 2026-06-13 Linux . . .\" ---------------------------------------------------------------------------- @@ -31,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, @@ -92,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 @@ -103,7 +98,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 @@ -131,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 . . .\" ---------------------------------------------------------------------------- @@ -142,7 +135,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,23 +188,20 @@ 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. Some -aspects of behavior can be controlled with the attributes: \fIrr_min_io\fR, -\fIrr_min_io_rq\fR and \fIrr_weight\fR. +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 @@ -224,7 +214,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 @@ -334,7 +324,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 @@ -409,12 +399,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 @@ -431,11 +421,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]. @@ -446,7 +436,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. @@ -457,17 +447,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 @@ -482,7 +472,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 @@ -514,14 +504,13 @@ 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: -.RS -.TP 12 +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 (Deprecated) Read the first sector of the device. This checker is being deprecated, please use \fItur\fR or \fIdirectio\fR instead. @@ -575,7 +564,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. @@ -598,24 +587,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,23 +611,14 @@ 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 .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 @@ -732,11 +700,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 @@ -821,28 +791,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 @@ -871,11 +829,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 @@ -946,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 @@ -1008,10 +967,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 @@ -1192,13 +1147,13 @@ 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 -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. @@ -1324,7 +1279,6 @@ The default is: \fBno\fR .TP .B disable_changed_wwids (Deprecated) This option is not supported anymore, and will be ignored. -.RE . . .TP @@ -1460,7 +1414,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 @@ -1550,7 +1504,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, @@ -1558,18 +1512,18 @@ 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 . . 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 18 +.TP .B path_grouping_policy .TP .B path_selector @@ -1580,14 +1534,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 @@ -1625,14 +1573,13 @@ section: .B ghost_delay .RE .PD -.LP . . .\" ---------------------------------------------------------------------------- .SH "devices section" .\" ---------------------------------------------------------------------------- . -.TP 4 +.TP .B Important: The built-in hardware device table of .I multipath-tools @@ -1649,9 +1596,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 @@ -1668,7 +1615,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 @@ -1711,7 +1658,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 @@ -1749,10 +1696,10 @@ 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 18 +.TP .B path_grouping_policy .TP .B uid_attribute @@ -1769,14 +1716,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 @@ -1824,7 +1765,6 @@ section: .B all_tg_pt .RE .PD -.LP . . .\" ---------------------------------------------------------------------------- @@ -1833,10 +1773,10 @@ 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 18 +.TP .B path_grouping_policy .TP .B uid_attribute @@ -1855,14 +1795,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 @@ -1935,7 +1869,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 @@ -1956,7 +1890,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. @@ -1996,7 +1930,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 @@ -2026,7 +1960,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. @@ -2039,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. @@ -2091,6 +2024,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" .\" ---------------------------------------------------------------------------- . 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; /* diff --git a/multipathd/multipathc.8 b/multipathd/multipathc.8 index cf7ae5be1..2187d1eda 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. .\" ---------------------------------------------------------------------------- . @@ -20,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 38a243e3e..632cbe403 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. .\" ---------------------------------------------------------------------------- . @@ -416,7 +412,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 +517,7 @@ the configured one). .TP .B Path format wildcards .RS -.TP 12 +.TP .B %w The device WWID (uuid). .TP @@ -548,7 +544,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 @@ -574,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 @@ -693,8 +688,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). . diff --git a/tests/Makefile b/tests/Makefile index 9f1b950f8..23f7d2bb2 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,18 +9,21 @@ 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 \ + shared_ptr $(if $(MEMFD_SUPPORT),gpt) 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) # 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 @@ -65,6 +68,10 @@ 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 +shared_ptr-test_LIBDEPS = -lpthread +gpt-test_OBJDEPS := $(kpartxdir)/gpt.o $(kpartxdir)/crc32.o + %.o: %.c @echo building $@ because of $? @@ -76,17 +83,25 @@ 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 $< == @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)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* + $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* runner-test.out runner-test.vgr core* valgrind_clean: $(Q)$(RM) $(TESTS:%=%.vgr) diff --git a/tests/directio.c b/tests/directio.c index fa1e553a5..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) @@ -227,7 +221,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)); } 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 new file mode 100644 index 000000000..395954042 --- /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 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) +{ + 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, ioctl_request_t req, void *p); +int WRAP_IOCTL(int fd, ioctl_request_t req, void *p) +{ + if (test_disk_size_bytes) { + if (req == (ioctl_request_t)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); +} 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); diff --git a/tests/runner-test.sh b/tests/runner-test.sh new file mode 100755 index 000000000..819e1871e --- /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" +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 + +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 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 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 + 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..eeffe08c7 --- /dev/null +++ b/tests/runner.c @@ -0,0 +1,623 @@ +// 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" +#include "../third-party/valgrind/valgrind.h" + +#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 + +/* 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; + 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; + struct runner_context *rctx; + + for (count = 0, i = 0; i < N_RUNNERS; i++) + if (context[i]) { + rctx = context[i]; + context[i] = NULL; + release_runner(rctx); + 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; + struct runner_context *rctx; + + 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: + rctx = context[i]; + context[i] = NULL; + release_runner(rctx); + break; + case RUNNER_CANCELLED: + if (last) { + rctx = context[i]; + context[i] = NULL; + condlog(3, "%s: releasing %p", + __func__, rctx); + release_runner(rctx); + } 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 (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); + } + + 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; +} + +/* 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; + struct runner_context **rctxs __attribute__((cleanup(free_rctxs))) = NULL; + + 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 */ + 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; +} 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; +} 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); diff --git a/tests/vpd.c b/tests/vpd.c index f9e22cd87..8b07df16b 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; @@ -331,6 +331,27 @@ 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 +500,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) @@ -779,40 +824,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); static int test_vpd(void) { @@ -945,6 +997,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); } diff --git a/tests/wrap64.h b/tests/wrap64.h index fe985a714..0f0ee1794 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" /* @@ -72,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 @@ -87,4 +96,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