pax_global_header00006660000000000000000000000064147524630240014521gustar00rootroot0000000000000052 comment=cb8de98b2cc3b62fd1a578ba479c0820778e9c02 multipath-tools-0.11.1/000077500000000000000000000000001475246302400147465ustar00rootroot00000000000000multipath-tools-0.11.1/.github/000077500000000000000000000000001475246302400163065ustar00rootroot00000000000000multipath-tools-0.11.1/.github/actions/000077500000000000000000000000001475246302400177465ustar00rootroot00000000000000multipath-tools-0.11.1/.github/actions/spelling/000077500000000000000000000000001475246302400215635ustar00rootroot00000000000000multipath-tools-0.11.1/.github/actions/spelling/expect.txt000066400000000000000000000037741475246302400236270ustar00rootroot00000000000000abi adt aio Alletra alloc alltgpt alua aptpl ascq ata autoconfig autodetected autoresize backported barbie BINDIR 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 emc Engenio EVPD failback failover fds fexceptions FFFFFFFF fge followover forcequeueing fpin fulldescr gcc getprkey getprstatus getrlimit getuid github gitlab GPT hbtl hds HITACHI hotplug HPE HSG HSV Huawei hwhandler hwtable iet ifdef ifndef igroup img 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 lpthread Lun lvm lvmteam Marzinski misdetection mpath mpathb mpathpersist mpathvalid msecs multipathc multipathd multipathed multipathing multipaths multiqueue mwilck NOLOG nompath NOSCAN Nosync nvme OBJDEPS ontap OOM opensvc OPTFLAGS paramp partx pathgroup petabytes pgpolicy plugindir PNR preferredip preferredsds prefixdir prgeneration Primera prioritizer prkeys PROUT pthreads rdac rdma readcap readdescr readfd readkeys readline readsector rebranded reconfig redhat restorequeueing retrigger rhabarber rootprefix rootprefixdir rpmbuild rport rtpi rtprio sas sbp scsi SCST sda sdc setmarginal setprkey setprstatus shm shu SIGHUP softdep spi srp ssa standalone statedir stdarg stdint STRERR strerror suse svg switchgroup sys sysfs sysinit systemd tcp terabytes SYSDIR 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 watchdogsec weightedpath wholedisk Wilck wildcards workflows wrt wwid wwn wwnn wwpn multipath-tools-0.11.1/.github/actions/spelling/only.txt000066400000000000000000000004161475246302400233060ustar00rootroot00000000000000# 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 multipath-tools-0.11.1/.github/actions/spelling/patterns.txt000066400000000000000000000043371475246302400241730ustar00rootroot00000000000000# 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 # 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 multipath-tools-0.11.1/.github/workflows/000077500000000000000000000000001475246302400203435ustar00rootroot00000000000000multipath-tools-0.11.1/.github/workflows/abi-stable.yaml000066400000000000000000000062671475246302400232450ustar00rootroot00000000000000name: check-abi for stable branch on: push: branches: - 'stable-*' paths: - '.github/workflows/abi-stable.yaml' - '**.h' - '**.c' - '**.version' pull_request: branches: - 'stable-*' workflow_dispatch: jobs: reference-abi: runs-on: ubuntu-22.04 steps: - name: get parent tag run: > echo ${{ github.ref }} | sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV if: ${{ github.event_name == 'push' }} - name: get parent tag run: echo PARENT_TAG=${{ github.base_ref }} >> $GITHUB_ENV if: ${{ github.event_name == 'pull_request' }} - name: assert parent tag run: /bin/false if: ${{ env.PARENT_TAG == '' }} - name: update run: sudo apt-get update - name: dependencies run: > sudo apt-get install --yes gcc gcc make pkg-config abigail-tools libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - name: checkout ${{ env.PARENT_TAG }} uses: actions/checkout@v4 with: ref: ${{ env.PARENT_TAG }} - name: build ABI for ${{ env.PARENT_TAG }} run: make -j$(grep -c ^processor /proc/cpuinfo) -Orecurse abi - name: save ABI uses: actions/upload-artifact@v4 with: name: multipath-abi-${{ env.PARENT_TAG }} path: abi check-abi: runs-on: ubuntu-22.04 needs: reference-abi steps: - name: get parent tag run: > echo ${{ github.ref }} | sed -E 's,refs/heads/stable-([0-9]\.[0-9]*)\.y,PARENT_TAG=\1.0,' >> $GITHUB_ENV if: ${{ github.event_name == 'push' }} - name: get parent tag run: echo PARENT_TAG=${{ github.base_ref }} >> $GITHUB_ENV if: ${{ github.event_name == 'pull_request' }} - name: assert parent tag run: /bin/false if: ${{ env.PARENT_TAG == '' }} - name: checkout ${{ github.base_ref }} uses: actions/checkout@v4 with: ref: ${{ github.base_ref }} - name: download ABI for ${{ env.PARENT_TAG }} id: download_abi uses: actions/download-artifact@v4 with: name: multipath-abi-${{ env.PARENT_TAG }} path: reference-abi - name: update run: sudo apt-get update if: steps.download_abi.outcome != 'success' - name: dependencies run: > sudo apt-get install --yes gcc gcc make pkg-config abigail-tools libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - name: check ABI of ${{ github.ref }} against ${{ env.PARENT_TAG }} id: check_abi run: make -j$(grep -c ^processor /proc/cpuinfo) -Orecurse abi-test continue-on-error: true - name: save differences if: ${{ steps.check_abi.outcome != 'success' }} uses: actions/upload-artifact@v4 with: name: abi-test path: abi-test - name: fail run: /bin/false if: steps.check_abi.outcome != 'success' multipath-tools-0.11.1/.github/workflows/abi.yaml000066400000000000000000000040201475246302400217560ustar00rootroot00000000000000name: check-abi on: push: branches: - queue - abi paths: - '.github/workflows/abi.yaml' - '**.h' - '**.c' pull_request: branches: - master - queue workflow_dispatch: env: ABI_BRANCH: ${{ secrets.ABI_BRANCH }} jobs: save-and-test-ABI: runs-on: ubuntu-20.04 steps: - name: set ABI branch if: ${{ env.ABI_BRANCH == '' }} run: echo "ABI_BRANCH=master" >> $GITHUB_ENV - name: checkout uses: actions/checkout@v4 - name: get reference ABI id: reference continue-on-error: true uses: dawidd6/action-download-artifact@v6 with: workflow: abi.yaml branch: ${{ env.ABI_BRANCH }} name: abi path: reference-abi - name: update run: sudo apt-get update - name: dependencies run: > sudo apt-get install --yes gcc gcc make pkg-config abigail-tools libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - name: create ABI run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) abi.tar.gz - name: save ABI uses: actions/upload-artifact@v4 with: name: abi path: abi overwrite: true - name: compare ABI against reference id: compare continue-on-error: true if: ${{ steps.reference.outcome == 'success' }} run: make abi-test - name: save differences if: ${{ steps.compare.outcome == 'failure' }} uses: actions/upload-artifact@v4 with: name: abi-test path: abi-test overwrite: true - name: fail # MUST use >- here, otherwise the condition always evaluates to true if: >- ${{ env.ABI_BRANCH != github.ref_name && (steps.reference.outcome == 'failure' || steps.compare.outcome == 'failure') }} run: false multipath-tools-0.11.1/.github/workflows/build-and-unittest.yaml000066400000000000000000000070471475246302400247530ustar00rootroot00000000000000name: basic-build-and-ci on: push: branches: - master - queue - tip - 'stable-*' pull_request: branches: - master - queue - 'stable-*' jobs: jammy: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: rl: ['', 'libreadline', 'libedit'] cc: [ gcc, clang ] steps: - uses: actions/checkout@v4 - name: update run: sudo apt-get update - name: dependencies run: > sudo apt-get install --yes gcc make pkg-config valgrind libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev libmount-dev linux-modules-extra-$(uname -r) - name: mpath run: sudo modprobe dm_multipath - name: zram run: sudo modprobe zram num_devices=0 - name: zram-device run: echo ZRAM=$(sudo cat /sys/class/zram-control/hot_add) >> $GITHUB_ENV - name: set-zram-size run: echo 1G | sudo tee /sys/block/zram$ZRAM/disksize - name: set CC run: echo CC=${{ matrix.cc }} >> $GITHUB_ENV - name: set optflags # valgrind doesn't support the dwarf-5 format of clang 14 run: echo OPT='-O2 -gdwarf-4 -fstack-protector-strong' >> $GITHUB_ENV if: ${{ matrix.cc == 'clang' }} - name: build run: > make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) READLINE=${{ matrix.rl }} OPTFLAGS="$OPT" - name: test run: > make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) OPTFLAGS="$OPT" test - name: valgrind-test id: valgrind run: > make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) OPTFLAGS="$OPT" valgrind-test continue-on-error: true - name: valgrind-results run: cat tests/*.vgr - name: fail if valgrind failed run: /bin/false if: steps.valgrind.outcome != 'success' - name: clean-nonroot-artifacts run: rm -f tests/dmevents.out tests/directio.out - name: root-test run: sudo make DIO_TEST_DEV=/dev/zram$ZRAM test focal: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: rl: ['', 'libreadline', 'libedit'] cc: [ gcc, clang ] steps: - uses: actions/checkout@v4 - name: mpath run: sudo modprobe dm_multipath - name: brd run: sudo modprobe brd rd_nr=1 rd_size=65536 - name: update run: sudo apt-get update - name: dependencies run: > sudo apt-get install --yes gcc-10 make pkg-config valgrind libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - name: set CC run: echo CC=${{ matrix.cc }} >> $GITHUB_ENV - name: build run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) READLINE=${{ matrix.rl }} - name: test run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) test - name: valgrind-test id: valgrind run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) valgrind-test continue-on-error: true - name: valgrind-results run: cat tests/*.vgr - name: fail if valgrind failed run: /bin/false if: steps.valgrind.outcome != 'success' - name: clean-nonroot-artifacts run: rm -f tests/dmevents.out tests/directio.out - name: root-test run: sudo make DIO_TEST_DEV=/dev/ram0 test multipath-tools-0.11.1/.github/workflows/coverity.yaml000066400000000000000000000033271475246302400231000ustar00rootroot00000000000000name: coverity on: push: branches: - coverity jobs: upload-coverity-scan: runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: dependencies run: > sudo apt-get install --yes gcc make pkg-config libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - name: download coverity run: > curl -o cov-analysis-linux64.tar.gz --form token="$COV_TOKEN" --form project="$COV_PROJECT" https://scan.coverity.com/download/cxx/linux64 env: COV_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} COV_PROJECT: ${{ secrets.COVERITY_SCAN_PROJECT }} - name: unpack coverity run: | mkdir -p coverity tar xfz cov-analysis-linux64.tar.gz --strip 1 -C coverity - name: build with cov-build run: > PATH="$PWD/coverity/bin:$PATH" cov-build --dir cov-int make -Orecurse -j"$(grep -c ^processor /proc/cpuinfo)" - name: pack results run: tar cfz multipath-tools.tgz cov-int - name: submit results run: > curl --form token="$COV_TOKEN" --form email="$COV_EMAIL" --form file="@multipath-tools.tgz" --form version="${{ github.ref_name }}" --form description="$(git describe --tags --match "0.*")" --form project="$COV_PROJECT" https://scan.coverity.com/builds env: COV_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} COV_PROJECT: ${{ secrets.COVERITY_SCAN_PROJECT }} COV_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }} multipath-tools-0.11.1/.github/workflows/foreign.yaml000066400000000000000000000076731475246302400226750ustar00rootroot00000000000000name: compile and unit test on foreign arch on: push: branches: - master - queue - tip - 'stable-*' paths: - '.github/workflows/foreign.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' pull_request: branches: - master - queue - 'stable-*' paths: - '.github/workflows/foreign.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' jobs: cross-build: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: [bookworm, sid] arch: [ppc64le, arm64, s390x] container: ghcr.io/mwilck/multipath-cross-debian_cross-${{ matrix.os }}-${{ matrix.arch }} steps: - name: checkout uses: actions/checkout@v4 - name: build run: make -j -Orecurse test-progs.tar - name: upload binary archive uses: actions/upload-artifact@v4 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} path: test-progs.tar overwrite: true test: runs-on: ubuntu-22.04 needs: cross-build strategy: fail-fast: false matrix: os: [bookworm, sid] arch: [ppc64le, arm64, s390x] steps: - name: set container arch run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV if: ${{ matrix.arch != 'armhf' }} - name: set container arch run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV if: ${{ matrix.arch == 'armhf' }} - name: download binary archive uses: actions/download-artifact@v4 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} - name: unpack binary archive run: tar xfv test-progs.tar - name: enable foreign arch uses: dbhi/qus/action@main - name: run tests uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-run-debian-${{ matrix.os }} guest-dir: /__w/multipath-tools/multipath-tools host-dir: ${{ github.workspace }} command: -C tests params: > --workdir /__w/multipath-tools/multipath-tools --platform linux/${{ env.CONTAINER_ARCH }} pull-params: "--platform linux/${{ env.CONTAINER_ARCH }}" root-test: runs-on: ubuntu-22.04 needs: cross-build strategy: fail-fast: false matrix: os: [bookworm, sid] arch: [ppc64le, arm64, s390x] steps: - name: mpath run: sudo modprobe dm_multipath - name: brd run: sudo modprobe brd rd_nr=1 rd_size=65536 - name: set container arch run: echo CONTAINER_ARCH="${{ matrix.arch }}" >> $GITHUB_ENV if: ${{ matrix.arch != 'armhf' }} - name: set container arch run: echo CONTAINER_ARCH="arm/v7" >> $GITHUB_ENV if: ${{ matrix.arch == 'armhf' }} - name: download binary archive uses: actions/download-artifact@v4 with: name: cross-${{ matrix.os }}-${{ matrix.arch }} - name: unpack binary archive run: tar xfv test-progs.tar - name: enable foreign arch uses: dbhi/qus/action@main - 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 continue-on-error: true - name: show root test output run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - name: fail run: /bin/false if: ${{ steps.root-test.outcome == 'failure' }} multipath-tools-0.11.1/.github/workflows/multiarch-stable.yaml000066400000000000000000000025451475246302400244750ustar00rootroot00000000000000name: multiarch test for stable distros on: push: branches: - master - queue - tip - 'stable-*' paths: - '.github/workflows/multiarch-stable.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' pull_request: branches: - master - queue - 'stable-*' paths: - '.github/workflows/multiarch-stable.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' jobs: build-old: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - debian-bookworm - debian-buster - ubuntu-trusty arch: [386, arm/v7] include: - os: debian-bookworm arch: aarch64 - os: debian-bookworm arch: s390x - os: debian-bookworm arch: ppc64le steps: - name: checkout uses: actions/checkout@v4 - name: enable foreign arch uses: dbhi/qus/action@main - name: compile and run unit tests 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 }}" multipath-tools-0.11.1/.github/workflows/multiarch.yaml000066400000000000000000000026521475246302400232240ustar00rootroot00000000000000name: multiarch test for rolling distros on: push: branches: - master - queue - tip - 'stable-*' paths: - '.github/workflows/multiarch.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' pull_request: branches: - master - queue - 'stable-*' paths: - '.github/workflows/multiarch.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' # run monthly to catch rolling distro changes schedule: - cron: '45 02 1 * *' jobs: build-current: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - alpine - debian-sid - fedora-rawhide - opensuse-tumbleweed arch: [amd64, ppc64le, aarch64, s390x, 386, arm/v7] exclude: - os: fedora-rawhide arch: 386 - os: fedora-rawhide arch: arm/v7 steps: - name: checkout uses: actions/checkout@v4 - name: enable foreign arch uses: dbhi/qus/action@main - name: compile and run unit tests 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 }}" multipath-tools-0.11.1/.github/workflows/native.yaml000066400000000000000000000113761475246302400225250ustar00rootroot00000000000000name: compile and unit test on native arch on: push: branches: - master - queue - tip - 'stable-*' paths: - '.github/workflows/native.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' pull_request: branches: - master - queue - 'stable-*' paths: - '.github/workflows/native.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' jobs: stable: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - debian-jessie - debian-buster - debian-bullseye - debian-bookworm - fedora-40 - opensuse-leap steps: - name: checkout uses: actions/checkout@v4 - name: set archive name # Leap containers have cpio but not tar run: echo ARCHIVE_TGT=test-progs.cpio >> $GITHUB_ENV if: ${{ matrix.os == 'opensuse-leap' }} - name: set archive name run: echo ARCHIVE_TGT=test-progs.tar >> $GITHUB_ENV if: ${{ matrix.os != 'opensuse-leap' }} - name: build and test if: ${{ matrix.os != 'debian-jessie' }} uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} command: -j -Orecurse test - name: build and 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 -Orecurse READLINE=libreadline test - name: create ${{ env.ARCHIVE_TGT }} if: ${{ matrix.os != 'debian-jessie' }} uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} command: ${{ env.ARCHIVE_TGT }} - name: create ${{ env.ARCHIVE_TGT }} (jessie) if: ${{ matrix.os == 'debian-jessie' }} uses: mosteo-actions/docker-run@v1 with: image: ghcr.io/mwilck/multipath-build-${{ matrix.os }} command: READLINE=libreadline ${{ env.ARCHIVE_TGT }} - name: upload binary archive uses: actions/upload-artifact@v4 with: name: native-${{ matrix.os }} path: ${{ env.ARCHIVE_TGT }} overwrite: true clang: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - debian-jessie - debian-buster - debian-bullseye - debian-bookworm - fedora-40 - opensuse-leap steps: - name: checkout uses: actions/checkout@v4 - name: clang 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 -Orecurse test - name: clang (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 -Orecurse READLINE=libreadline test root-test: runs-on: ubuntu-22.04 needs: stable strategy: fail-fast: false matrix: os: - debian-jessie - debian-buster - debian-bullseye - debian-bookworm - fedora-40 - opensuse-leap steps: - name: mpath run: sudo modprobe dm_multipath - name: brd run: sudo modprobe brd rd_nr=1 rd_size=65536 - name: checkout uses: actions/checkout@v4 - name: download binary archive uses: actions/download-artifact@v4 with: name: native-${{ matrix.os }} - name: unpack binary archive run: cpio -idv < test-progs.cpio if: ${{ matrix.os == 'opensuse-leap' }} - name: unpack binary archive run: tar xfmv test-progs.tar if: ${{ matrix.os != 'opensuse-leap' }} - 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 continue-on-error: true - name: show root test output run: for o in tests/*.out; do echo "===== $o ====="; cat "$o"; done - name: fail run: /bin/false if: ${{ steps.root-test.outcome == 'failure' }} multipath-tools-0.11.1/.github/workflows/rolling.yaml000066400000000000000000000021441475246302400226760ustar00rootroot00000000000000name: compile and unit test on rolling distros on: push: branches: - master - queue - tip - 'stable-*' paths: - '.github/workflows/rolling.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' pull_request: branches: - master - queue - 'stable-*' paths: - '.github/workflows/rolling.yaml' - '**.h' - '**.c' - '**Makefile*' - '**.mk' # run weekly to catch rolling distro changes schedule: - cron: '30 06 * * 1' jobs: rolling: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - debian-sid - alpine - opensuse-tumbleweed - fedora-rawhide container: ghcr.io/mwilck/multipath-build-${{ matrix.os }} steps: - name: checkout uses: actions/checkout@v4 - name: build and test run: make READLINE=libreadline -j -Orecurse test - name: clean run: make -j -Orecurse clean - name: clang env: CC: clang run: make READLINE=libedit -j -Orecurse test multipath-tools-0.11.1/.github/workflows/spelling.yml000066400000000000000000000140131475246302400227020ustar00rootroot00000000000000name: 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' 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 }}" multipath-tools-0.11.1/.gitignore000066400000000000000000000014431475246302400167400ustar00rootroot00000000000000*.o .dotest *~ *.so *.so.0 *.abi *.a *.gz *.d \#* compile_commands.json config.mk cscope.files cscope.out kpartx/kpartx kpartx/kpartx.rules multipath/multipath multipath/multipath.8 multipath/multipath.conf.5 multipath/multipath.rules multipath/11-dm-mpath.rules multipath/tmpfiles.conf multipathd/multipathd multipathd/multipathd.8 multipathd/multipathc multipathd/multipathd.service mpathpersist/mpathpersist mpathpersist/mpathpersist.8 abi.tar.gz abi abi-test compile_commands.json .nfs* *.swp *.patch *.rej *.orig libdmmp/docs/man/*.3.gz libdmmp/*.so.* libdmmp/test/libdmmp_test libdmmp/test/libdmmp_speed_test tests/*-test tests/*.out tests/*.vgr tests/test-lib.o.wrap tests/test-log.o.wrap libmultipath/nvme-ioctl.c libmultipath/nvme-ioctl.h libmultipath/autoconfig.h */*-nv.version reference-abi multipath-tools-0.11.1/.mailmap000066400000000000000000000034671475246302400164010ustar00rootroot00000000000000# # This list is used by git-shortlog to fix a few botched name translations # in the git archive, either because the author's full name was messed up # and/or not always written the same way, making contributions from the # same person appearing not to be so or badly displayed. Also allows for # old email addresses to map to new email addresses. # # For format details, see "MAPPING AUTHORS" in "man git-shortlog". # # Please keep this list dictionary sorted. # Bart Van Assche Bart Van Assche Benjamin Marzinski Benjamin Marzinski Benjamin Marzinski bmarzins@sourceware.org Chongyun Wu Wuchongyun Chongyun Wu Christophe Varoqui Christophe Varoqui Christophe Varoqui Christophe Varoqui Christophe Varoqui root Christophe Varoqui root Christophe Varoqui root Christophe Varoqui Hannes Reinecke Hannes Reinecke Martin Wilck Martin Wilck wei huang wei.huang multipath-tools-0.11.1/COPYING000077700000000000000000000000001475246302400201572LICENSES/LGPL-2.0ustar00rootroot00000000000000multipath-tools-0.11.1/LICENSES/000077500000000000000000000000001475246302400161535ustar00rootroot00000000000000multipath-tools-0.11.1/LICENSES/GPL-2.0000066400000000000000000000431001475246302400170130ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Moe Ghoul, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. multipath-tools-0.11.1/LICENSES/GPL-3.0000066400000000000000000001045151475246302400170240ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . multipath-tools-0.11.1/LICENSES/LGPL-2.0000066400000000000000000000612661475246302400171440ustar00rootroot00000000000000 GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, see . Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Moe Ghoul, President of Vice That's all there is to it! multipath-tools-0.11.1/LICENSES/LGPL-2.1000066400000000000000000000634631475246302400171460ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Moe Ghoul, President of Vice That's all there is to it! multipath-tools-0.11.1/Makefile000066400000000000000000000066201475246302400164120ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # TOPDIR := . LIB_BUILDDIRS := \ libmpathcmd \ libmpathutil \ libmultipath \ libmpathpersist \ libmpathvalid ifneq ($(ENABLE_LIBDMMP),0) LIB_BUILDDIRS += \ libdmmp endif PLUGIN_BUILDDIRS := \ libmultipath/prioritizers \ libmultipath/checkers \ libmultipath/foreign \ BUILDDIRS := $(LIB_BUILDDIRS) \ $(PLUGIN_BUILDDIRS) \ multipath \ multipathd \ mpathpersist \ kpartx BUILDDIRS.clean := $(BUILDDIRS:=.clean) tests.clean .PHONY: $(BUILDDIRS) all: $(BUILDDIRS) config.mk libmultipath/autoconfig.h: @$(MAKE) -f create-config.mk ifeq ($(V),1) @echo ==== config.mk ==== @cat config.mk @echo ==== autoconfig.h ==== @cat libmultipath/autoconfig.h endif $(BUILDDIRS): config.mk @$(MAKE) -C $@ $(LIB_BUILDDIRS:=.abi): $(LIB_BUILDDIRS) @$(MAKE) -C ${@:.abi=} abi # Create formal representation of the ABI # Useful for verifying ABI compatibility # Requires abidw from the abigail suite (https://sourceware.org/libabigail/) .PHONY: abi abi: $(LIB_BUILDDIRS:=.abi) @echo creating abi @mkdir -p $@ $(Q)ln -ft $@ $(LIB_BUILDDIRS:=/*.abi) abi.tar.gz: abi $(Q)tar cfz $@ abi # Check the ABI against a reference. # This requires the ABI from a previous run to be present # in the directory "reference-abi" # Requires abidiff from the abigail suite abi-test: abi reference-abi $(wildcard abi/*.abi) @err=0; \ for lib in abi/*.abi; do \ diff=$$(abidiff --redundant "reference-$$lib" "$$lib") || { \ err=1; \ echo "==== ABI differences in for $$lib ===="; \ echo "$$diff"; \ }; \ done >$@; \ if [ $$err -eq 0 ]; then \ echo "*** OK, ABI unchanged ***"; \ else \ echo "*** WARNING: ABI has changed, see file $@ ***"; \ fi; \ [ $$err -eq 0 ] # Create compile_commands.json, useful for using clangd with an IDE # Requires bear (https://github.com/rizsotto/Bear) compile_commands.json: Makefile Makefile.inc $(BUILDDIRS:=/Makefile) $(Q)$(MAKE) clean $(Q)bear -- $(MAKE) WARN_ONLY=1 test-progs || rm $@ libmpathutil libdmmp: libmpathcmd libmultipath: libmpathutil libmpathpersist libmpathvalid multipath multipathd: libmultipath libmultipath/prioritizers libmultipath/checkers libmultipath/foreign: libmultipath mpathpersist multipathd: libmpathpersist libmultipath/checkers.install \ libmultipath/prioritizers.install \ libmultipath/foreign.install: libmultipath.install %.clean: @$(MAKE) -C ${@:.clean=} clean %.install: % @$(MAKE) -C ${@:.install=} install $(BUILDDIRS:=.uninstall): @$(MAKE) -C ${@:.uninstall=} uninstall # If config.mk is missing, "make clean" in subdir either fails, or tries to # build it. Both is undesirable. Avoid it by creating config.mk temporarily. clean: @touch config.mk $(Q)$(MAKE) $(BUILDDIRS:=.clean) tests.clean || true $(Q)$(RM) -r abi abi.tar.gz abi-test config.mk install: $(BUILDDIRS:=.install) uninstall: $(BUILDDIRS:=.uninstall) test-progs: all @$(MAKE) -C tests progs test: all @$(MAKE) -C tests all valgrind-test: all @$(MAKE) -C tests valgrind TEST-ARTIFACTS := config.mk Makefile.inc \ $(LIB_BUILDDIRS:%=%/*.so*) $(PLUGIN_BUILDDIRS:%=%/*.so) \ tests/Makefile tests/*.so* tests/lib/* tests/*-test test-progs.cpio: test-progs @printf "%s\\n" $(TEST-ARTIFACTS) | cpio -o -H crc >$@ test-progs.tar: test-progs @tar cf $@ $(TEST-ARTIFACTS) .PHONY: TAGS TAGS: @etags -a libmultipath/*.c @etags -a libmultipath/*.h @etags -a multipathd/*.c @etags -a multipathd/*.h multipath-tools-0.11.1/Makefile.inc000066400000000000000000000141451475246302400171630ustar00rootroot00000000000000# -*- Makefile -*- # Copyright (C) 2004 Christophe Varoqui, # # # Uncomment to disable libdmmp support # ENABLE_LIBDMMP = 0 # # Uncomment to disable dmevents polling support # ENABLE_DMEVENTS_POLL = 0 # # Readline library to use, libedit, libreadline, or empty # Caution: Using libreadline may make the multipathd binary undistributable, # see https://github.com/opensvc/multipath-tools/issues/36 READLINE := # List of scsi device handler modules to load on boot, e.g. # SCSI_DH_MODULES_PRELOAD := scsi_dh_alua scsi_dh_rdac SCSI_DH_MODULES_PRELOAD := EXTRAVERSION := $(shell rev=$$(git rev-parse --short=7 HEAD 2>/dev/null); echo $${rev:+-g$$rev}) # PKG_CONFIG must be read from the environment to enable compilation # in Debian multiarch setups PKG_CONFIG ?= pkg-config ifeq ($(TOPDIR),) TOPDIR = .. endif ifneq ($(CREATE_CONFIG),1) include $(TOPDIR)/config.mk endif # Paths. All these can be overridden on the "make" command line. prefix := # Prefix for binaries exec_prefix := $(prefix) # Prefix for non-essential libraries (libdmmp) usr_prefix := $(if $(prefix),$(prefix),/usr) # Prefix for configuration files (multipath.conf) etc_prefix := $(prefix) # Where to install systemd-related files. systemd is usually installed under /usr # Note: systemd installations with "split-usr=true" use separate "prefixdir" and # "rootprefixdir". Our systemd_prefix corresponds to "prefixdir". # In this case, override only unitdir, libudevdir and sys_execprefix below # to use systemd's "rootprefixdir" instead of $(systemd_prefix) systemd_prefix := /usr # Prefix for binaries that are owned by other packages (including systemd) sys_execprefix := /usr # Make sure all prefix variables end in "/" append-slash = $(1)$(if $(filter %/,$(1)),,/) override prefix := $(call append-slash,$(prefix)) override exec_prefix := $(call append-slash,$(exec_prefix)) override usr_prefix := $(call append-slash,$(usr_prefix)) override etc_prefix := $(call append-slash,$(etc_prefix)) override systemd_prefix := $(call append-slash,$(systemd_prefix)) override sys_execprefix := $(call append-slash,$(sys_execprefix)) unitdir := $(systemd_prefix)lib/systemd/system tmpfilesdir := $(systemd_prefix)lib/tmpfiles.d modulesloaddir := $(systemd_prefix)lib/modules-load.d libudevdir := $(systemd_prefix)lib/udev udevrulesdir := $(libudevdir)/rules.d bindir := $(exec_prefix)sbin mandir := $(usr_prefix)share/man LIB := $(if $(shell test -d /lib64 && echo 1),lib64,lib) syslibdir := $(prefix)$(LIB) usrlibdir := $(usr_prefix)$(LIB) includedir := $(usr_prefix)include pkgconfdir := $(usrlibdir)/pkgconfig plugindir := $(prefix)$(LIB)/multipath configdir := $(etc_prefix)etc/multipath/conf.d configfile := $(etc_prefix)etc/multipath.conf statedir := $(etc_prefix)etc/multipath runtimedir := $(if $(shell test -L /var/run -o ! -d /var/run && echo 1),/run,/var/run) devmapper_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir devmapper),/usr/include) libudev_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir libudev),/usr/include) kernel_incdir := /usr/include sysdir_bin := $(sys_execprefix)bin ifeq ($(V),) Q := @ # make's "Entering directory" messages are confusing in parallel mode #MAKEFLAGS = --no-print-directory endif GZIP_PROG := gzip -9 -c RM := rm -f LN := ln -sf INSTALL_PROGRAM := install ORIG_CPPFLAGS := $(CPPFLAGS) ORIG_CFLAGS := $(CFLAGS) ORIG_LDFLAGS := $(LDFLAGS) SYSTEMD_CPPFLAGS := $(if $(SYSTEMD),-DUSE_SYSTEMD=$(SYSTEMD)) SYSTEMD_LIBDEPS := $(if $(SYSTEMD),$(if $(shell test $(SYSTEMD) -gt 209 && echo 1),-lsystemd,-lsystemd-daemon)) MODPROBE_UNIT := $(shell test "0$(SYSTEMD)" -lt 245 2>/dev/null || \ echo "modprobe@dm_multipath.service") OPTFLAGS := -O2 -g $(STACKPROT) --param=ssp-buffer-size=4 # Set WARN_ONLY=1 to avoid compilation erroring out due to warnings. Useful during development. WARN_ONLY := ERROR := $(if $(WARN_ONLY),,error=) WERROR := $(if $(WARN_ONLY),,-Werror) WARNFLAGS := $(WERROR) -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -W$(ERROR)implicit-int \ -W$(ERROR)implicit-function-declaration -W$(ERROR)format-security \ $(WNOCLOBBERED) -W$(ERROR)cast-qual $(ERROR_DISCARDED_QUALIFIERS) $(W_URCU_TYPE_LIMITS) CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) $(D_URCU_VERSION) \ -D_FILE_OFFSET_BITS=64 \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(TGTDIR)$(plugindir)\" \ -DRUNTIME_DIR=\"$(runtimedir)\" -DCONFIG_DIR=\"$(TGTDIR)$(configdir)\" \ -DDEFAULT_CONFIGFILE=\"$(TGTDIR)$(configfile)\" -DSTATE_DIR=\"$(TGTDIR)$(statedir)\" \ -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP CFLAGS := -std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe \ -fexceptions BIN_CFLAGS := -fPIE -DPIE LIB_CFLAGS := -fPIC SHARED_FLAGS := -shared LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,now -Wl,-z,defs BIN_LDFLAGS := -pie # Source code directories. Don't modify. multipathdir := $(TOPDIR)/libmultipath daemondir := $(TOPDIR)/multipathd mpathutildir := $(TOPDIR)/libmpathutil mpathpersistdir := $(TOPDIR)/libmpathpersist mpathcmddir := $(TOPDIR)/libmpathcmd mpathvaliddir := $(TOPDIR)/libmpathvalid thirdpartydir := $(TOPDIR)/third-party libdmmpdir := $(TOPDIR)/libdmmp nvmedir := $(TOPDIR)/libmultipath/nvme # Common code for libraries - library Makefiles just set DEVLIB # SONAME defaults to 0 (we use version scripts) SONAME := 0 LIBS = $(DEVLIB).$(SONAME) VERSION_SCRIPT = $(DEVLIB:%.so=%.version) NV_VERSION_SCRIPT = $(DEVLIB:%.so=%-nv.version) %.o: %.c @echo building $@ because of $? $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< %.abi: %.so.0 $(Q)abidw $< >$@ %.abi: %.so $(Q)abidw $< >$@ %-nv.version: %.version @echo creating $@ from $< @printf 'NOVERSION {\nglobal:\n' >$@ @grep -P '^[ \t]+[a-zA-Z_][a-zA-Z0-9_]*;' $< >>$@ @printf 'local:\n\t*;\n};\n' >>$@ %: %.in @echo creating $@ $(Q)sed -e 's:@CONFIGFILE@:'$(TGTDIR)$(configfile)':g' \ -e 's:@CONFIGDIR@:'$(TGTDIR)$(configdir)':g' \ -e 's:@STATE_DIR@:'$(TGTDIR)$(statedir)':g' \ -e 's:@BINDIR@:'$(TGTDIR)$(bindir)':g' \ -e 's:@SYSDIR_BIN@:'$(sysdir_bin)': g' \ -e 's:@RUNTIME_DIR@:'$(runtimedir)':g' \ -e 's/@MODPROBE_UNIT@/'$(MODPROBE_UNIT)'/g' \ $< >$@ multipath-tools-0.11.1/NEWS.md000066400000000000000000000573331475246302400160570ustar00rootroot00000000000000# multipath-tools Release Notes ## Stable branches and new versioning scheme Beginning with 0.11, the second digit in the multipath-tools version will be used for upstream "major" releases. The 3rd and last digit will denote stable releases in the future, containing only bug fixes on top of the last major release. These bug fixes will be tracked in stable branches. See [README.md](README.md) for additional information. ## multipath-tools 0.11.1, 2025/02 This release contains backported bug fixes from the master branch up to 0.12. ### Bug fixes * Fix multipathd crash because of invalid path group index value, for example if an invalid path device was removed from a map. Fixes [#105](https://github.com/opensvc/multipath-tools/issues/105). * Make sure maps are reloaded in the path checker loop after detecting an inconsistent or wrong kernel state (e.g. missing or falsely mapped path device). Wrongly mapped paths will be unmapped and released to the system. Fixes another issue reported in [#105](https://github.com/opensvc/multipath-tools/issues/105). * Fix the problem that `group_by_tpg` might be disabled if one or more paths were offline during initial configuration. * Fix possible misdetection of changed pathgroups in a map. * Fix the problem that if a map was scheduled to be reloaded already, `max_sectors_kb` might not be set on a path device that was being added to a multipath map. This problem was introduced in 0.9.9. ## multipath-tools 0.11.0, 2024/11 ### User-visible changes * Modified the systemd unit `multipathd.service` such that multipathd will now restart after a failure or crash. Fixes [#100](https://github.com/opensvc/multipath-tools/issues/100). * Logging changes for verbosity level 3: - silenced logging of path status if the status is unchanged - silenced some unhelpful messages from scanning of existing maps - added a message when partition mappings are removed. ### Other major changes #### Rework of the path checking algorithm This is a continuation of the checker-related work that went into 0.10.0. For asynchronous checker algorithms (i.e. tur and directio), the start of the check and the retrieval of the results are done at separate points in time, which reduces the time for waiting for the checker results of individual paths and thus strongly improves the performance of the checker algorithm, in particular on systems with a large a amount of paths. The algorithm has the following additional properties: 1. multipath devices get synchronized with the kernel occasionally, even if they have not paths 2. If multiple paths from a multipath device are checked in the same loop, the multipath device will only get synchronized with the kernel once. 3. All the paths of a multipath device will converge to being checked at the same time (at least as much as their differing checker intervals will allow). 4. The times for checking the paths of each multipath device will spread out as much as possible so multipathd doesn't do all of it's checking in a burst. 5. path checking is done by multipath device (for initialized paths, the uninitialized paths are handled after all the adopted paths are handled). ### Bug fixes * Fixed the problem that multipathd wouldn't start on systems with certain types of device mapper devices, in particular devices with multiple DM targets. The problem was introduced in 0.10.0. Fixes [#102](https://github.com/opensvc/multipath-tools/issues/102). * Fixed a corner case in the udev rules which could cause a device not to be activated during boot if a cold plug uevent is processed for a previously not configured multipath map while this map was suspended. This problem existed since 0.9.8. * Fixed the problem that devices with `no_path_retry fail` and no setting for `dev_loss_tmo` might get the `dev_loss_tmo` set to 0, causing the device to be deleted immediately in the event of a transport disruption. This bug was introduced in 0.9.6. * Fixed the problem that, if there were multiple maps with deferred failback (`failback` value > 0 in `multipath.conf`), some maps might fail back later than configured. The problem existed since 0.9.6. * Fixed handling of empty maps, i.e. multipath maps that have a multipath UUID but don't contain a device-mapper table. Such maps can occur in very rare cases if some device-mapper operation has failed, or if a tool has been killed in the process of map creation. multipathd will now detect such cases, and either remove these maps or reload them as appropriate. * During map creation, fixed the case where a map with different name, but matching UUID and matching type was already present. multipathd previously failed to set up such maps. Now it will reload them with the correct content. * Fixed the logic for enabling the systemd watchdog (`WatchdogSec=` in the systemd unit file for multipathd). * Fixed a memory leak in the nvme foreign library. The bug existed since 0.7.8. * Fixed a problem in the marginal path detection algorithm that could cause the io error check for a recently failed path to be delayed. This bug existed since 0.7.4. ### Other * Default settings for `hardware_handler` have been removed from the internal hardware table. These settings have been obsoleted by the Linux kernel 4.3 and later, which automatically selects hardware handlers when SCSI devices are added. See the notes about `SCSI_DH_MODULES_PRELOAD` in [README.md](README.md). * Added a hardware table entry for the SCSI Target Subsystem for Linux (SCST). * The text of the licenses has been updated to the latest versions from the Free Software Foundation. ### Internal * `libmp_mapinfo()` now fills in the `name`, `uuid`, and `dmi` fields if requested by the caller, even if it encounters an error or an empty map. ## multipath-tools 0.10.0, 2024/08 ### User-Visible Changes * The `multipathd show daemon` command now shows `(reconfigure pending)` if a reconfiguration has been triggered but not finished yet. ### Other major changes * Refactored the path checker loop. Paths are now checked for each multipath map in turn, rather than walking linearly through the list of paths. Paths for different multipath maps will be checked at different time offsets in the `polling_interval` time span, which distributes the load caused by path checking more evenly over time. * Refactored a significant part of the libmultipath / libdevmapper interface. All functions that retrieve information about DM maps have been converted to use just one worker function, libmp_mapinfo(). This reduces code size while providing more flexibility and efficiency (less device-mapper ioctls). Also, cleanup attributes are used consistently in the libdevmapper-related code. * Renamed public functions, variables, and macros to comply with the glibc [policy for reserved names](https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html). For backward compatibility reasons, the exported functions from `libmpathcmd` and `libmpathpersist` that start with double underscore are kept as weak symbols. Fixes [#91](https://github.com/opensvc/multipath-tools/issues/91). ### Bug fixes * Fixed bug that caused queueing to be always disabled if flushing a map failed (bug introduced in 0.9.8). * Fixed failure to remove maps even with `deferred_remove` (bug introduced in 0.9.9). * Fixed old mpathpersist bug leading to the error message "configured reservation key doesn't match: 0x0" when `reservation_key` was configured in the multipaths section of `multipath.conf` (Fixes [#92](https://github.com/opensvc/multipath-tools/issues/92)). * Fixed output of `multipath -t` and `multipath -T` for the options `force_sync` and `retrigger_tries`. (Fixes [#88](https://github.com/opensvc/multipath-tools/pull/88)) * Fixed adding maps by WWID in CLI (command `add map $WWID`). ### Other * Removed hardcoded paths and make them configurable instead. This should improve compatibility e.g. with NixOS. * Improved handling of paths with changed WWIDs. * Improved synchronization between kernel state and multipathd's internal state. * Made map removal more efficient by avoiding unnecessary recursion. * Added hardware defaults for Huawei storage arrays and XSG1 vendors. * Use `-fexceptions` during compilation to make sure cleanup code is executed when threads are cancelled * Use `weak` attribute for `get_multipath_config()` and `put_multipath_config()` in order to enable linking with `-Bsymbolic-non-weak-functions` (Fixes [#86](https://github.com/opensvc/multipath-tools/pull/86)). * Fixed CI for ARM/v7 * Fixed directio CI test for real devices, run more "real" tests in CI * Fixed minor issues detected by coverity. * Fixed a minor bug in the config file parser (Fixes [#93](https://github.com/opensvc/multipath-tools/pull/93)). * Minor documentation fixes (Fixes [#87](https://github.com/opensvc/multipath-tools/pull/87)). ## multipath-tools 0.9.9, 2024/05 ### Important note It is not recommended to use *lvm2* 2.03.24 and newer with multipath-tools versions earlier than 0.9.9. See "Other major changes" below. ### User-Visible Changes * *Changed realtime scheduling:* multipathd used to run at the highest possible realtime priority, 99. This has always been excessive, and on some distributions (e.g. RHEL 8), it hasn't worked at all. It is now possible to set multipathd's real time scheduling by setting the hard limit for `RLIMIT_RTPRIO` (see getrlimit(2)), which corresponds to the `rtprio` setting in limits.conf and to `LimitRTPRIO=` in the systemd unit file. The default in the systemd unit file has been set to 10. If the limit is set to 0, multipathd doesn't attempt to enable real-time scheduling. Otherwise, it will try to set the scheduling priority to the given value. Fixes [#82](https://github.com/opensvc/multipath-tools/issues/82). * *Changed normal scheduling:* In order to make sure that multipathd has sufficient priority even if real time scheduling is switched off, the `CPUWeight=` setting in the unit file is set to 1000. This is necessary because regular nice(2) values have no effect in systems with cgroups enabled. * *Changed handling of `max_sectors_kb` configuration:* multipathd applies the `max_sectors_kb` setting only during map creation, or when a new path is added to an existing map. The kernel makes sure that the multipath device never has a larger `max_sectors_kb` value than any of its configured path devices. The reason for this change is that applying `max_sectors_kb` on live maps can cause IO errors and data loss in rare situations. It can now happen that some path devices have a higher `max_sectors_kb` value than the map; this is not an error. It is not possible any more to decrease `max_sectors_kb` in `multipath.conf` and run `multipathd reconfigure` to "apply" this setting to the map and its paths. If decreasing the IO size is necessary, either destroy and recreate the map, or remove one path with `multipathd del path $PATH`, run `multipathd reconfigure`, and re-add the path with `multipathd add path $PATH`. * *New wildcard %k:* The wildcard `%k` for `max_sectors_kb` has been added to the `multipathd show paths format` and `multipathd show maps format` commands. * *Changed semantics of flush_on_last_del:* The `flush_on_last_del` option now takes the values `always` , `unused`, or `never`. `yes` and `no` are still accepted as aliases for `always` and `unused`, respectively. `always` means that when all paths for a multipath map have been removed, *outstanding IO will be failed* and the map will be deleted. `unused` means that this will only happen when the map is not mounted or otherwise opened. `never` means the map will only be removed if the `queue_if_no_path` feature is off. This fixes a problem where multipathd could hang when the last path of a queueing map was deleted. * *Better parsing of `$map` arguments in multipathd interactive shell*: The `$map` argument in commands like `resize map $map` now accepts a WWID, and poorly chosen map aliases that could be mistaken for device names. * *Added documentation for CLI wildcards*. The wildcards used in the `show maps format` and `show paths format` commands are documented in the *multipathd(8)* man page now. * *`%s` wildcard for paths:* this wildcard in `show paths format` now prints the product revision, too. ### Other major changes * Adapted the dm-mpath udev rules such that they will work with the modified device mapper udev rules (`DM_UDEV_RULES_VSN==3`, lvm2 >= 2.03.24). They are still compatible with older versions of the device-mapper udev rules (lvm2 < 2.03.24). If lvm2 2.03.24 or newer is installed, it is recommended to update multipath-tools to 0.9.9 or newer. See also [LVM2 2.03.24 release notes](https://gitlab.com/lvmteam/lvm2/-/tags/v2_03_24). ### Bug fixes * Fixed misspelled DM_UDEV_DISABLE_OTHER_RULES_FLAG in 11-dm-mpath.rules * Always use `glibc_basename()` to avoid some issues with MUSL libc. Fixes [#84](https://github.com/opensvc/multipath-tools/pull/84). * Fixed map failure counting for `no_path_retry > 0`. * The wildcards for path groups are not available for actual commands and have thus been removed from the `show wildcards` command output. ### Other * Build: added `TGTDIR` option to simplify building for a different target host (see README.md). ## multipath-tools 0.9.8, 2024/02 ### User-Visible Changes * Socket activation via multipathd.socket has been disabled by default because it has undesirable side effects (fixes [#76](https://github.com/opensvc/multipath-tools/issues/76), at least partially). * The restorequeueing CLI command now only enables queueing if disablequeueing had been sent before. * Error messages sent from multipathd to the command line client have been improved. The user will now see messages like "map or partition in use" or "device not found" instead of just "fail". ### Other Major Changes * multipathd now tracks the queueing mode of maps in its internal features string. This is helpful to ensure that maps have the desired queuing status. Without this, it could happen that a map remains in queueing state even after the `no_path_retry` timeout has expired. * multipathd's map flushing code has been reworked to avoid hangs if there are no paths but outstanding IO. Thus, if multipathd is running, `multipath -F` can now retry map flushing using the daemon, rather than locally. ### Bug Fixes * A segmentation fault in the 0.9.7 autoresize code has been fixed. * Fixed a bug introduced in 0.9.6 that had caused map reloads being omitted when path priorities changed. * Fixed compilation with gcc 14. (Fixes [#80](https://github.com/opensvc/multipath-tools/issues/80)) * Minor fixes for issues detected by coverity. * Spelling fixes and other minor fixes. ### CI * Enabled `-D_FILE_OFFSET_BITS=64` to fix issues with emulated 32-bit environments in the GitHub CI, so that we can now run our CI in arm/v7. * Added the check-spelling GitHub action. * Various improvements and updates for the GitHub CI workflows. ## multipath-tools 0.9.7, 2023/11 ### User-Visible Changes * The options `bindings_file`, `wwids_file` and `prkeys_file`, which were deprecated since 0.8.8, have been removed. The path to these files is now hard-coded to `$(statedir)` (see below). * Added `max_retries` config option to limit SCSI retries. * Added `auto_resize` config option to enable resizing multipath maps automatically. * Added support for handling FPIN-Li events for FC-NVMe. ### Other Major Changes * Rework of alias selection code: - strictly avoid using an alias that is already taken. - cache bindings table in memory. - write bindings file only if changes have been applied, and watch it with inotify. - sort aliases in "alias order" by using length+alphabetical sort, allowing more efficient allocation of new aliases ### Bug Fixes * Avoid that `multipath -d` changes sysfs settings. * Fix memory and error handling of code using aio in the marginal paths code. and the directio checker (fixes [#73](https://github.com/opensvc/multipath-tools/issues/73)). * Insert compile time settings for paths in man pages correctly. ### Other * Add new compile-time variable `statedir` which defaults to `/etc/multipath`. * Add new compile-time variable `etc_prefix` as prefix for config file and config dir. * Compile-time variable `usr_prefix` now defaults to `/usr` if `prefix` is empty. * Remove check whether multipath is enabled in systemd `.wants` directories. * README improvements. ## multipath-tools 0.9.6, 2023/09 ### User-Visible Changes * Added new path grouping policy `group_by_tpg` to group paths by their ALUA target port group (TPG). * Added new configuration parameters `detect_pgpolicy` (default: yes) and `detect_pgpolicy_use_tpg` (default: no). * Add new wildcard `%A` to print target port group in `list paths format` command. * NVMe devices are now ignored if NVMe native multipath is enabled in the kernel. ### Other Major Changes * Prioritizers now use the same timeout logic as path checkers. * Reload maps if the path groups aren't properly ordered by priority. * Improve logic for updating path priorities. * Avoid paths with unknown priority affecting the priority of their path group. ### Bug Fixes * Fix `max_sectors_kb` for cases where a path is deleted and re-added (Fixes [#66](https://github.com/opensvc/multipath-tools/pull/66)). * Fix handling of `dev_loss_tmo` in cases where it wasn't explicitly configured. * Syntax fixes in udev rules (Fixes [#69](https://github.com/opensvc/multipath-tools/pull/69)). ### Other * Adapt HITACHI/OPEN- config to work with alua and multibus. * Build system fixes. ## multipath-tools 0.9.5, 2023/04 ### User-Visible Changes * Always use directio path checker for Linux TCM (LIO) targets (Fixes [#54](https://github.com/opensvc/multipath-tools/issues/54). * `multipath -u` now checks if path devices are already in use (e.g. mounted), and if so, makes them available to systemd immediately. ### Other Major Changes * Persistent reservations are now handled consistently. Previously, whether a PR key was registered for a path depended on the situation in which the path had been first detected by multipathd. ### Bug Fixes * Make sure that if a map device must be renamed and reloaded, both actions actually take place (previously, the map would only be renamed). * Make sure to always flush IO if a map is resized. * Avoid incorrectly claiming path devices in `find_multipaths smart` case for paths that have no valid WWID or for which `multipath -u` failed. * Avoid paths failures for ALUA transitioning state (fixes [#60]( https://github.com/opensvc/multipath-tools/pull/60). * Handle persistent reservations correctly even if there are no active paths during map creation. * Make sure all paths are orphaned if maps are removed. * Avoid error messages for unsupported device designators in VPD pages. * Fix a memory leak. * Honor the global option `uxsock_timeout` in libmpathpersist (fixes [#45](https://github.com/opensvc/multipath-tools/issues/45)). * Don't fail for devices lacking INQUIRY properties such as "vendor" (fixes [#56](https://github.com/opensvc/multipath-tools/issues/56)). * Remove `Also=` in `multipathd.socket` (fixes [#65](https://github.com/opensvc/multipath-tools/issues/65)). ### CI * Use Ubuntu 22.04 instead of 18.04. ## multipath-tools 0.9.4, 2022/12 ### Bug Fixes * Verify device-mapper table configuration strings before passing them to the kernel. * Fix failure of `setprstatus`, `unsetprstatus` and `unsetprkey` commands sent from libmpathpersist introduced in 0.9.2. * Fix a memory leak. * Compilation fixes for some architectures, older compilers, and MUSL libc. * Fix `show paths format %c` failure for orphan paths (fixes [#49](https://github.com/opensvc/multipath-tools/pull/49)) ### Build system changes * Added a simple `autoconf`-like mechanism. * Use "quiet build" by default, verbose build can be enabled using `make V=1`. * Reworked the Makefile variables for configuring paths. * Don't require perl just for installation of man pages. ### CI * True "multi-architecture" workflows are now possible on GitHub workflows, to test compilation and run unit tests on different architectures. * Containers for test builds are now pulled from ghcr.io rather than from docker hub. ### Other * Updates for the hardware table: PowerMax NVMe, Alletra 5000, FAS/AFF and E/EF. * Documentation fixes. ## multipath-tools 0.9.3, 2022/10 ### Bug fixes * Fix segmentation violation caused by different symbol version numbers in libmultipath and libmpathutil (fixes [47](https://github.com/opensvc/multipath-tools/issues/47). ## multipath-tools 0.9.2, 2022/10 ### User-Visible Changes * Fix handling of device-mapper `queue_mode` parameter. * Enforce `queue_mode bio` for NVMe/TCP paths. ### Other Major Changes * Rework the command parsing logic in multipathd (CVE-2022-41974). * Use `/run` rather than `/dev/shm` (CVE-2022-41973). * Check transport protocol for NVMe devices. ### Bug Fixes * Rework feature string handling, fixing bugs for corner cases. * Fix a race in kpartx partition device creation. * Fix memory leak in the unix socket listener code. * Fix a read past end of buffer in the unix socket listener code. * Fix compilation error with clang 15. ## multipath-tools 0.9.1, 2022/09 ### User-Visible Changes * multipathd doesn't use libreadline any more due to licensing conflicts, because readline has changed its license to GPL 3.0, which is incompatible with the GPL-2.0-only license of parts of the multipath-tools code base. Therefore the command line editing feature in multipathd is disabled by default. libedit can be used instead of libreadline by setting `READLINE=libedit` during compilation. `READLINE=libreadline` can also still be set. Only the new helper program *multipathc*, which does not contain GPL-2.0 code, is linked with libreadline or libedit. `multipathd -k` now executes `multipathc`. Fixes [36](https://github.com/opensvc/multipath-tools/issues/36). * As part of the work separating code of conflicting licenses, the multipath library has been split into `libmultipath` and `libmpathutil`. The latter can be linked with GPL-3.0 code without licensing conflicts. * Speed up start of `multipath -u` and `multipath -U`. * Speed up seeking for aliases in systems with lots of alias entries. * Always use the `emc_clariion` checker for Clariion/Unity storage arrays. ### Bug Fixes * Avoid checker thread blocking uevents or other requests for an extended amount of time with a huge amount of path devices, by occasionally interrupting the checker loop. * Fix handling the case where a map ended up with no paths while being updated. * Fix a segmentation violation in `list map format` code. * Fix use-after-free in code handling path WWID changes by sorting the alias table. * Fix timeout handling in unix socket listener code. * Fix systemd timers in the initramfs. * Fix `find_multipaths_timeout` for unknown hardware. * Fix `multipath -ll` output for native NVMe. ### Other * Cleanup code for sysfs access, and sanitize error handling. * Separation of public and internal APIs in libmpathpersist. * Build system fixes. * Spelling fixes. ## multipath-tools 0.9.0, 2022/06 ### User-Visible Changes * The properties `dev_loss_tmo`, `eh_deadline`, and `fast_io_fail_tmo` can now be set *by protocol*, in the `overrides` → `protocol` section of `multipath.conf`. * The `config_dir` and `multipath_dir` run-time options, marked deprecated since 0.8.8, have been replaced by the build-time options `configdir=` and `plugindir=`, respectively. * `getuid_callout` is not supported any more. ### Other Major Changes * The uevent filtering and merging code has been re-written to avoid artificial delays in uevent processing. ### Bug fixes * The `delayed_reconfigure` logic has been fixed. ### Other * hardware table updates. multipath-tools-0.11.1/README.md000066400000000000000000000342651475246302400162370ustar00rootroot00000000000000[![basic-build-and-ci](https://github.com/openSUSE/multipath-tools/actions/workflows/build-and-unittest.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/build-and-unittest.yaml) [![compile and unit test on native arch](https://github.com/openSUSE/multipath-tools/actions/workflows/native.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/native.yaml) [![compile and unit test on foreign arch](https://github.com/openSUSE/multipath-tools/actions/workflows/foreign.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/foreign.yaml) multipath-tools for Linux ========================= https://github.com/opensvc/multipath-tools This package provides the following binaries to drive the Device Mapper multipathing driver: * multipath - Device mapper target autoconfig. * multipathc - Interactive client for multipathd. * multipathd - Multipath daemon. * mpathpersist - Manages SCSI persistent reservations on dm multipath devices. * kpartx - Create device maps from partition tables. Code Repository, Branches, and Maintenance ========================================== The code is maintained on GitHub in the [opensvc/multipath-tools](https://github.com/opensvc/multipath-tools) project. The latest major release is always found in the `master` branch in this project. Staging area ------------ Between releases, the latest reviewed code can be obtained from [the queue branch](https://github.com/openSUSE/multipath-tools/tree/queue) in the openSUSE/multipath-tools repository on GitHub. From there, pull requests for new releases in the master repository are created roughly every 3 months. Stable branches --------------- Beginning with 0.10, there will be stable branches `stable-0.x.y` on opensvc/multipath-tools. Small bug fixes with low regression risk from will be cherry-picked to these branches from the staging area. These branches are maintained by the multipath-tools maintainers on a best-effort basis. From time to time, minor releases will be made on these branches. Releases ======== The project uses git tags like `0.10.0` for releases. It is not planned to use the GitHub release mechanism. To get a specific X.Y.Z release, use one of the following method: Git --- git clone https://github.com/opensvc/multipath-tools.git cd multipath-tools git tag git archive --format=tar.gz --prefix=multipath-tools-X.Y.Z/ X.Y.Z > ../multipath-tools-X.Y.Z.tar.gz Direct download --------------- wget "https://github.com/opensvc/multipath-tools/archive/X.Y.Z.tar.gz" -O multipath-tools-X.Y.Z.tar.gz Browser ------- Go to: https://github.com/opensvc/multipath-tools/tags Select a release-tag and then click on "zip" or "tar.gz". Building multipath-tools ======================== Prerequisites: development packages of for `libdevmapper`, `libaio`, `libudev`, `libjson-c`, `liburcu`, and `libsystemd`. If commandline editing is enabled (see below), the development package for either `libedit` or `libreadline` is required as well. Then, build and install multipath-tools with: make make DESTDIR="/my/target/dir" install To uninstall, type: make uninstall By default, the build will run quietly, just printing one-line messages about the files being built. To enable more verbose output, run `make V=1`. Customizing the build --------------------- **Note**: With very few exceptions, the build process does not read configuration from the environment. So using syntax like SOME_VAR=some_value make will **not** have the intended effect. Use the following instead: make SOME_VAR=some_value See "Passing standard compiler flags" below for an exception. The following variables can be passed to the `make` command line: * `V=1`: enable verbose build. * `plugindir="/some/path"`: directory where libmultipath plugins (path checkers, prioritizers, and foreign multipath support) will be looked up. This used to be the run-time option `multipath_dir` in earlier versions. The default is `$(prefix)/$(LIB)/multipath`, where `$(LIB)` is `lib64` on systems that have `/lib64`, and `lib` otherwise. * `configfile="/some/path`": The path to the main configuration file. The default is `$(etc_prefix)/etc/multipath.conf`. * `configdir="/some/path"` : directory to search for additional configuration files. This used to be the run-time option `config_dir` in earlier versions. The default is `$(etc_prefix)/etc/multipath/conf.d`. * `statedir="/some/path"`: The path of the directory where multipath-tools stores run-time settings that need persist between reboots, such as known WWIDs, user-friendly names, and persistent reservation keys. The default is `$(etc_prefix)/etc/multipath`. * `READLINE=libedit` or `READLINE=libreadline`: enable command line history and TAB completion in the interactive mode *(which is entered with `multipathd -k` or `multipathc`)*. The respective development package will be required for building. By default, command line editing is disabled. Note that using libreadline may [make binary indistributable due to license incompatibility](https://github.com/opensvc/multipath-tools/issues/36). * `ENABLE_LIBDMMP=0`: disable building libdmmp * `ENABLE_DMEVENTS_POLL=0`: disable support for the device-mapper event polling API. For use with pre-5.0 kernels that don't support dmevent polling (but even if you don't use this option, multipath-tools will work with these kernels). * `SYSTEMD`: The version number of systemd (e.g. "244") to compile the code for. The default is autodetected, assuming that the systemd version in the build environment is the same as on the target system. Override the value to build for a different systemd version, or set it to `""` to build for a system without systemd. **Caution:** multipathd without systemd has been largely untested by the upstream maintainers since at least 2020. * `SCSI_DH_MODULES_PRELOAD="(list)"`: specify a space-separated list of SCSI device handler kernel modules to load early during boot. Some multipath-tools functionality depends on these modules being loaded early. This option causes a *modules-load.d(5)* configuration file to be created, thus it depends on functionality provided by *systemd*. This variable only matters for `make install`. **Note**: The usefulness of the preload list depends on the kernel configuration. It's especially useful if `scsi_mod` is builtin but `scsi_dh_alua` and other device handler modules are built as modules. If `scsi_mod` itself is compiled as a module, it might make more sense to use a module softdep for the same purpose by creating a `modprobe.d` file like this: softdep scsi_mod post: scsi_dh_alua scsi_dh_rdac ### Installation Paths * `prefix`: The directory prefix for (almost) all files to be installed. "Usr-merged" distributions[^systemd] may want to set this to `/usr`. The default is empty (`""`). * `usr_prefix`: where to install those parts of the code that aren't necessary for booting. The default is `/usr` if `$(prefix)` is empty, and `$(prefix)` otherwise. * `systemd_prefix`: Prefix for systemd-related files[^systemd]. The default is `/usr`. * `etc_prefix`: The prefix for configuration files. "usr-merged" distributions with immutable `/usr`[^systemd] may want to set this to `""`. The default is `$(prefix)`. * `LIB`: the subdirectory under `prefix` where shared libraries will be installed. By default, the makefile uses `/lib64` if this directory is found on the build system, and `/lib` otherwise. The options `configdir`, `plugindir`, `configfile`, and `statedir` above can be used for setting individual paths where the `prefix` variables don't provide sufficient control. See `Makefile.inc` for even more fine-grained control. [^systemd]: systemd installations up to v254 which have been built with `split-usr=true` may use separate `prefixdir` and `rootprefixdir` directories, where `prefixdir` is a subdirectory of `rootprefixdir`. multipath-tools' `systemd_prefix` corresponds to systemd's `prefixdir`. On such distributions, override `unitdir` and `libudevdir` to use systemd's `rootprefix`: `make libudevdir=/lib/udev unitdir=/lib/systemd/system` ### prefix, DESTDIR and TGTDIR `prefix` and related variables are included in compiled-in paths like `plugindir` and are used by `make install`. Using `prefix` is useful if multipath-tools is built locally on the same host where it's supposed to be installed. By convention, the `DESTDIR` variable is prepended to all paths by `make install`, but not to any compiled-in paths. It is useful if the software is built on one system (build host) but intended to be run on another system (installation host). This is typically used in build systems like *rpmbuild* to set a root directory for all the installed files. On the contrary, the `TGTDIR` variable is used for compiled-in paths only, and ignored by `make install`. It is useful for running multipath-tools in a separate subdirectory in the installation host, mostly for testing / development purposes. For example, make prefix=/opt DESTDIR=/build TGTDIR=/test install will install plugins into `/build/opt/lib64/multipath` on the build host. On the installation host, the plugins will be expected to be found under `/test/opt/lib64/multipath`. If the developer runs rsync -a $BUILD_HOST:$DESTDIR/ $INSTALL_HOST:$TGTDIR/ and adds `$TGTDIR/lib64` to `LD_LIBRARY_PATH` on the installation host, the multipath binaries installed under `$TGTDIR` will find their plugins and configuration files in the expected compiled-in paths. ### Compiler Options Use `OPTFLAGS` to change optimization-related compiler options; e.g. `OPTFLAGS="-g -O0"` to disable all optimizations. ### Passing standard compiler flags Contrary to most other variables, the standard variables `CFLAGS`, `CPPFLAGS`, and `LDFLAGS` **must** be passed to **make** via the environment if they need to be customized: CPPFLAGS="-D_SECRET_=secret" make Special Makefile targets ------------------------ The following targets are intended for developers only. * `make test` to build and run the unit tests * `make valgrind-test` to run the unit tests under valgrind * `make abi` to create an XML representation of the ABI of the libraries in the `abi/` subdirectory * `make abi-test` to compare the ABI of a different multipath-tools version, which must be stored in the `reference-abi/` subdirectory. If this test fails, the ABI has changed wrt the reference. * `make compile-commands.json` to create input for [clangd](https://clangd.llvm.org/). Contributing ============ Please send patches or contributions for general discussion about multipath tools to the mailing list (see below). You can also create issues or pull requests on [GitHub](https://github.com/opensvc/multipath-tools). You will be asked to send your patches to the mailing list unless your patch is trivial. Mailing list ------------ The mailing list for multipath-tools is `dm-devel@lists.linux.dev`. To subscribe, send an email to `dm-devel+subscribe@lists.linux.dev`. Mailing list archives are available on [lore.kernel.org](https://lore.kernel.org/dm-devel/) and [marc.info](https://marc.info/?l=dm-devel). See also the [lists.linux.dev home page](https://subspace.kernel.org/lists.linux.dev.html). When sending patches to the mailing list, please add a `Signed-off-by:` tag, and add Benjamin Marzinski and Martin Wilck to the Cc list. Adding new storage devices -------------------------- If you want to add special settings for a storage device which is new on the market, follow the instructions at the top of the file `libmultipath/hwtable.c`. Changelog ========= * Since multipath-tools 0.9.1, changs are tracked in [NEWS.md](NEWS.md). * post-0.4.5: https://github.com/opensvc/multipath-tools/commits/master * pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog Maintainer ========== Christophe Varoqui Device-mapper development mailing list Licence ======= The multipath-tools source code is covered by several different licences. Refer to the individual source files for details. Source files which do not specify a licence are shipped under LGPL-2.0 (see `LICENSES/LGPL-2.0`). ALUA ==== This is a rough guide, consult your storage device manufacturer documentation. ALUA is supported in some devices, but usually it's disabled by default. To enable ALUA, the following options should be changed: - EMC CLARiiON/VNX: "Failover Mode" should be changed to "4" or "Active-Active mode(ALUA)-failover mode 4" - HPE 3PAR, Primera, and Alletra 9000: "Host:" should be changed to "Generic-ALUA Persona 2 (UARepLun, SESLun, ALUA)". - Promise VTrak/Vess: "LUN Affinity" and "ALUA" should be changed to "Enable", "Redundancy Type" must be "Active-Active". - LSI/Engenio/NetApp RDAC class, as NetApp SANtricity E/EF Series and rebranded arrays: "Select operating system:" should be changed to "Linux DM-MP (Kernel 3.10 or later)". - NetApp ONTAP FAS/AFF Series: To check ALUA state: "igroup show -v ", and to enable ALUA: "igroup set alua yes". - Huawei OceanStor: "Host Access Mode" should be changed to "Asymmetric". NVMe ==== Using dm-multipath with NVMe ---------------------------- NVMe multipath is natively supported by the Linux kernel. If for some reason you prefer using device mapper multipath with NVMe devices, you need to disable native multipathing first: echo "options nvme_core multipath=N" > /etc/modprobe.d/01-nvme_core-mp.conf Afterwards, regenerate the initramfs (`dracut -f` or `update-initramfs`) and reboot. Using multipath-tools with native NVMe multipath ------------------------------------------------ If native NVMe multipathing is enabled, you can still use multipath-tools for displaying the topology and some other information about native NVMe multipath setups. This feature is disabled by default. To enable it, set `enable_foreign nvme` in the `defaults` section of `multipath.conf`. Commands like `multipath -ll` will then display information about NVMe native multipath. This support is read-only; modifying the native multipath configuration is not supported. multipath-tools-0.11.1/create-config.mk000066400000000000000000000134341475246302400200120ustar00rootroot00000000000000# Copyright (c) SUSE LLC # SPDX-License-Identifier: GPL-2.0-or-later TOPDIR := . CREATE_CONFIG := 1 include $(TOPDIR)/Makefile.inc # "make" on some distros will fail on explicit '#' or '\#' in the program text below __HASH__ := \# # Check whether a given shell command succeeds check_cmd = $(shell \ if $1; then \ found=1; \ status="yes"; \ else \ found=0; \ status="no"; \ fi; \ echo 1>&2 "Checking $(if $3,$3,command \"$1\") ... $$status"; \ echo "$$found" \ ) # Check whether a function with name $1 has been declared in header file $2. check_func = $(shell \ if grep -Eq "^(extern[[:blank:]]+)?[^[:blank:]]+[[:blank:]]+$1[[:blank:]]*(.*)*" "$2"; then \ found=1; \ status="yes"; \ else \ found=0; \ status="no"; \ fi; \ echo 1>&2 "Checking for $1 in $2 ... $$status"; \ echo "$$found" \ ) # Checker whether a file with name $1 exists check_file = $(shell \ if [ -f "$1" ]; then \ found=1; \ status="yes"; \ else \ found=0; \ status="no"; \ fi; \ echo 1>&2 "Checking if $1 exists ... $$status"; \ echo "$$found" \ ) # Check whether a file contains a variable with name $1 in header file $2 check_var = $(shell \ if grep -Eq "(^|[[:blank:]])$1([[:blank:]]|=|$$)" "$2"; then \ found=1; \ status="yes"; \ else \ found=0; \ status="no"; \ fi; \ echo 1>&2 "Checking for $1 in $2 ... $$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 \ echo 'struct A {int a, b;}; struct B {struct A a; int b;} b = {.a.a=1};' | \ $(CC) -c -Werror -Wmissing-field-initializers -o /dev/null -xc - >/dev/null 2>&1 \ || echo -Wno-missing-field-initializers) # gcc 4.8.4 and certain versions of liburcu fail to compile this with -Werror=type-limits TEST_URCU_TYPE_LIMITS = $(shell \ echo -e '$(__HASH__)include \nint main() { int z=8; return uatomic_sub_return(&z, 1); }' | \ $(CC) -c -Werror=type-limits -o /dev/null -xc - 2>/dev/null \ || echo -Wno-type-limits ) URCU_VERSION = $(shell \ $(PKG_CONFIG) --modversion liburcu 2>/dev/null | \ awk -F. '{ printf("-DURCU_VERSION=0x%06x", 256 * ( 256 * $$1 + $$2) + $$3); }') DEFINES := ifneq ($(call check_func,dm_task_no_flush,$(devmapper_incdir)/libdevmapper.h),0) DEFINES += LIBDM_API_FLUSH endif ifneq ($(call check_func,dm_task_get_errno,$(devmapper_incdir)/libdevmapper.h),0) DEFINES += LIBDM_API_GET_ERRNO endif ifneq ($(call check_func,dm_task_set_cookie,$(devmapper_incdir)/libdevmapper.h),0) DEFINES += LIBDM_API_COOKIE endif ifneq ($(call check_func,udev_monitor_set_receive_buffer_size,$(libudev_incdir)/libudev.h),0) DEFINES += LIBUDEV_API_RECVBUF endif ifneq ($(call check_func,dm_task_deferred_remove,$(devmapper_incdir)/libdevmapper.h),0) DEFINES += LIBDM_API_DEFERRED endif ifneq ($(call check_func,dm_hold_control_dev,$(devmapper_incdir)/libdevmapper.h),0) DEFINES += LIBDM_API_HOLD_CONTROL endif ifneq ($(call check_var,ELS_DTAG_LNK_INTEGRITY,$(kernel_incdir)/scsi/fc/fc_els.h),0) DEFINES += FPIN_EVENT_HANDLER FPIN_SUPPORT = 1 endif libmount_h := $(shell $(PKG_CONFIG) --variable=includedir mount)/libmount/libmount.h ifneq ($(call check_func,mnt_unref_cache,$(libmount_h)),0) DEFINES += LIBMOUNT_HAS_MNT_UNREF_CACHE endif ifneq ($(call check_func,mnt_table_parse_swaps,$(libmount_h)),0) DEFINES += LIBMOUNT_SUPPORTS_SWAP endif ifneq ($(call check_file,$(kernel_incdir)/linux/nvme_ioctl.h),0) ANA_SUPPORT := 1 endif ENABLE_LIBDMMP := $(call check_cmd,$(PKG_CONFIG) --exists json-c) ifeq ($(ENABLE_DMEVENTS_POLL),0) DEFINES += -DNO_DMEVENTS_POLL endif SYSTEMD := $(strip $(or $(shell $(PKG_CONFIG) --modversion libsystemd 2>/dev/null | awk '{print $$1}'), \ $(shell systemctl --version 2>/dev/null | sed -n 's/systemd \([0-9]*\).*/\1/p'))) # $(call TEST_CC_OPTION,option,fallback) # Test if the C compiler supports the option. # Evaluates to "option" if yes, and "fallback" otherwise. TEST_CC_OPTION = $(shell \ if echo 'int main(void){return 0;}' | \ $(CC) -o /dev/null -c -Werror "$(1)" -xc - >/dev/null 2>&1; \ then \ echo "$(1)"; \ else \ echo "$(2)"; \ fi) # Check if _DFORTIFY_SOURCE=3 is supported. # On some distros (e.g. Debian Buster) it will be falsely reported as supported # but it doesn't seem to make a difference wrt the compilation result. FORTIFY_OPT := $(shell \ if printf '$(__HASH__)include \nint main(void) { return 0; }\n' | \ $(CC) -o /dev/null $(OPTFLAGS) -c -Werror -D_FORTIFY_SOURCE=3 -xc - 2>/dev/null; \ then \ echo "-D_FORTIFY_SOURCE=3"; \ elif printf '$(__HASH__)include \nint main(void) { return 0; }\n' | \ $(CC) -o /dev/null $(OPTFLAGS) -c -Werror -D_FORTIFY_SOURCE=2 -xc - 2>/dev/null; \ then \ echo "-D_FORTIFY_SOURCE=2"; \ fi) STACKPROT := all: $(TOPDIR)/config.mk $(multipathdir)/autoconfig.h: @echo creating $@ @echo '#ifndef AUTOCONFIG_H_INCLUDED' >$@ @echo '#define AUTOCONFIG_H_INCLUDED' >>$@ @for x in $(DEFINES); do echo "#define $$x" >>$@; done @echo '#endif' >>$@ $(TOPDIR)/config.mk: $(multipathdir)/autoconfig.h @echo creating $@ @echo "FPIN_SUPPORT := $(FPIN_SUPPORT)" >$@ @echo "FORTIFY_OPT := $(FORTIFY_OPT)" >>$@ @echo "D_URCU_VERSION := $(call URCU_VERSION)" >>$@ @echo "SYSTEMD := $(SYSTEMD)" >>$@ @echo "ANA_SUPPORT := $(ANA_SUPPORT)" >>$@ @echo "STACKPROT := $(call TEST_CC_OPTION,-fstack-protector-strong,-fstack-protector)" >>$@ @echo "ERROR_DISCARDED_QUALIFIERS := $(call TEST_CC_OPTION,-Werror=discarded-qualifiers,)" >>$@ @echo "WNOCLOBBERED := $(call TEST_CC_OPTION,-Wno-clobbered -Wno-error=clobbered,)" >>$@ @echo "WFORMATOVERFLOW := $(call TEST_CC_OPTION,-Wformat-overflow=2,)" >>$@ @echo "W_MISSING_INITIALIZERS := $(call TEST_MISSING_INITIALIZERS)" >>$@ @echo "W_URCU_TYPE_LIMITS := $(call TEST_URCU_TYPE_LIMITS)" >>$@ @echo "ENABLE_LIBDMMP := $(ENABLE_LIBDMMP)" >>$@ multipath-tools-0.11.1/kpartx/000077500000000000000000000000001475246302400162575ustar00rootroot00000000000000multipath-tools-0.11.1/kpartx/Makefile000066400000000000000000000034061475246302400177220ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # include ../Makefile.inc EXEC := kpartx CPPFLAGS += -I. -I$(multipathdir) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 CFLAGS += $(BIN_CFLAGS) LDFLAGS += $(BIN_LDFLAGS) LIBDEPS += -ldevmapper OBJS := bsd.o dos.o kpartx.o solaris.o unixware.o dasd.o sun.o \ gpt.o mac.o ps3.o crc32.o lopart.o xstrncpy.o devmapper.o all: $(EXEC) kpartx.rules $(EXEC): $(OBJS) @echo building $@ because of $? $(Q)$(CC) $(CFLAGS) $(OBJS) -o $(EXEC) $(LDFLAGS) $(LIBDEPS) install: $(EXEC) $(EXEC).8 kpartx.rules $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir) $(Q)$(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir) $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(libudevdir) $(Q)$(INSTALL_PROGRAM) -m 755 kpartx_id $(DESTDIR)$(libudevdir) $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(libudevdir)/rules.d $(Q)$(INSTALL_PROGRAM) -m 644 dm-parts.rules $(DESTDIR)$(libudevdir)/rules.d/11-dm-parts.rules $(Q)$(INSTALL_PROGRAM) -m 644 kpartx.rules $(DESTDIR)$(libudevdir)/rules.d/66-kpartx.rules $(Q)$(INSTALL_PROGRAM) -m 644 del-part-nodes.rules $(DESTDIR)$(libudevdir)/rules.d/68-del-part-nodes.rules $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 uninstall: $(Q)$(RM) $(DESTDIR)$(bindir)/$(EXEC) $(Q)$(RM) $(DESTDIR)$(mandir)/man8/$(EXEC).8 $(Q)$(RM) $(DESTDIR)$(libudevdir)/kpartx_id $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/11-dm-parts.rules $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/66-kpartx.rules $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/67-kpartx-compat.rules $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/68-del-part-nodes.rules clean: dep_clean $(Q)$(RM) core *.o $(EXEC) kpartx.rules include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/kpartx/bsd.c000066400000000000000000000100241475246302400171700ustar00rootroot00000000000000#include "kpartx.h" #include #define BSD_LABEL_OFFSET 64 #define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */ #define XBSD_MAXPARTITIONS 16 #define BSD_FS_UNUSED 0 struct bsd_disklabel { unsigned int d_magic; /* the magic number */ short int d_type; /* drive type */ short int d_subtype; /* controller/d_type specific */ char d_typename[16]; /* type name, e.g. "eagle" */ char d_packname[16]; /* pack identifier */ unsigned int d_secsize; /* # of bytes per sector */ unsigned int d_nsectors; /* # of data sectors per track */ unsigned int d_ntracks; /* # of tracks per cylinder */ unsigned int d_ncylinders; /* # of data cylinders per unit */ unsigned int d_secpercyl; /* # of data sectors per cylinder */ unsigned int d_secperunit; /* # of data sectors per unit */ unsigned short d_sparespertrack;/* # of spare sectors per track */ unsigned short d_sparespercyl; /* # of spare sectors per cylinder */ unsigned int d_acylinders; /* # of alt. cylinders per unit */ unsigned short d_rpm; /* rotational speed */ unsigned short d_interleave; /* hardware sector interleave */ unsigned short d_trackskew; /* sector 0 skew, per track */ unsigned short d_cylskew; /* sector 0 skew, per cylinder */ unsigned int d_headswitch; /* head switch time, usec */ unsigned int d_trkseek; /* track-to-track seek, usec */ unsigned int d_flags; /* generic flags */ unsigned int d_drivedata[5]; /* drive-type specific information */ unsigned int d_spare[5]; /* reserved for future use */ unsigned int d_magic2; /* the magic number (again) */ unsigned short d_checksum; /* xor of data incl. partitions */ /* filesystem and partition information: */ unsigned short d_npartitions; /* number of partitions in following */ unsigned int d_bbsize; /* size of boot area at sn0, bytes */ unsigned int d_sbsize; /* max size of fs superblock, bytes */ struct bsd_partition { /* the partition table */ unsigned int p_size; /* number of sectors in partition */ unsigned int p_offset; /* starting sector */ unsigned int p_fsize; /* filesystem basic fragment size */ unsigned char p_fstype; /* filesystem type, see below */ unsigned char p_frag; /* filesystem fragments per block */ unsigned short p_cpg; /* filesystem cylinders per group */ } d_partitions[XBSD_MAXPARTITIONS];/* actually may be more */ }; int read_bsd_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct bsd_disklabel *l; struct bsd_partition *p; unsigned int offset = all.start, end; int max_partitions; char *bp; unsigned int n = 0, i, j; bp = getblock(fd, offset+1); /* 1 sector suffices */ if (bp == NULL) return -1; l = (struct bsd_disklabel *) bp; if (l->d_magic != BSD_DISKMAGIC) { /* * BSD disklabels can also start 64 bytes offset from the * start of the first sector */ bp = getblock(fd, offset); if (bp == NULL) return -1; l = (struct bsd_disklabel *)(bp + 64); if (l->d_magic != BSD_DISKMAGIC) return -1; } max_partitions = 16; if (l->d_npartitions < max_partitions) max_partitions = l->d_npartitions; for (p = l->d_partitions; p - l->d_partitions < max_partitions; p++) { if (p->p_fstype == BSD_FS_UNUSED) /* nothing */; else if (n < ns) { sp[n].start = p->p_offset; sp[n].size = p->p_size; n++; } else { fprintf(stderr, "bsd_partition: too many slices\n"); break; } } /* * Convention has it that the bsd disklabel will always have * the 'c' partition spanning the entire disk. * So we have to check for contained slices. */ for(i = 0; i < n; i++) { if (sp[i].size == 0) continue; end = sp[i].start + sp[i].size; for(j = 0; j < n; j ++) { if ( i == j ) continue; if (sp[j].size == 0) continue; if (sp[i].start < sp[j].start) { if (end > sp[j].start && end < sp[j].start + sp[j].size) { /* Invalid slice */ fprintf(stderr, "bsd_disklabel: slice %d overlaps with %d\n", i , j); sp[i].size = 0; } } else { if (end <= sp[j].start + sp[j].size) { sp[i].container = j + 1; } } } } return n; } multipath-tools-0.11.1/kpartx/byteorder.h000066400000000000000000000013021475246302400204230ustar00rootroot00000000000000#ifndef KPARTX_BYTEORDER_H_INCLUDED #define KPARTX_BYTEORDER_H_INCLUDED #ifdef __linux__ # include # include #else # error unsupported #endif #if BYTE_ORDER == LITTLE_ENDIAN # define le16_to_cpu(x) (x) # define be16_to_cpu(x) bswap_16(x) # define le32_to_cpu(x) (x) # define le64_to_cpu(x) (x) # define be32_to_cpu(x) bswap_32(x) # define be64_to_cpu(x) bswap_64(x) #elif BYTE_ORDER == BIG_ENDIAN # define le16_to_cpu(x) bswap_16(x) # define be16_to_cpu(x) (x) # define le32_to_cpu(x) bswap_32(x) # define le64_to_cpu(x) bswap_64(x) # define be32_to_cpu(x) (x) # define be64_to_cpu(x) (x) #else # error unsupported #endif #endif /* KPARTX_BYTEORDER_H_INCLUDED */ multipath-tools-0.11.1/kpartx/crc32.c000066400000000000000000000315071475246302400173450ustar00rootroot00000000000000/* * crc32.c * This code is in the public domain; copyright abandoned. * Liability for non-performance of this code is limited to the amount * you paid for it. Since it is distributed for free, your refund will * be very very small. If it breaks, you get to keep both pieces. */ #include "crc32.h" #if __GNUC__ >= 3 /* 2.x has "attribute", but only 3.0 has "pure */ #define attribute(x) __attribute__(x) #else #define attribute(x) #endif /* * There are multiple 16-bit CRC polynomials in common use, but this is * *the* standard CRC-32 polynomial, first popularized by Ethernet. * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 */ #define CRCPOLY_LE 0xedb88320 #define CRCPOLY_BE 0x04c11db7 /* How many bits at a time to use. Requires a table of 4< 8 || CRC_LE_BITS < 1 || CRC_LE_BITS & CRC_LE_BITS-1 # error CRC_LE_BITS must be a power of 2 between 1 and 8 #endif #if CRC_LE_BITS == 1 /* * In fact, the table-based code will work in this case, but it can be * simplified by inlining the table in ?: form. */ #define crc32init_le() #define crc32cleanup_le() /** * crc32_le() - Calculate bitwise little-endian Ethernet AUTODIN II CRC32 * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for * other uses, or the previous crc32 value if computing incrementally. * @p - pointer to buffer over which CRC is run * @len - length of buffer @p * */ uint32_t attribute((pure)) crc32_le(uint32_t crc, unsigned char const *p, size_t len) { int i; while (len--) { crc ^= *p++; for (i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); } return crc; } #else /* Table-based approach */ static uint32_t *crc32table_le; /** * crc32init_le() - allocate and initialize LE table data * * crc is the crc of the byte i; other entries are filled in based on the * fact that crctable[i^j] = crctable[i] ^ crctable[j]. * */ static int crc32init_le(void) { unsigned i, j; uint32_t crc = 1; crc32table_le = malloc((1 << CRC_LE_BITS) * sizeof(uint32_t)); if (!crc32table_le) return 1; crc32table_le[0] = 0; for (i = 1 << (CRC_LE_BITS - 1); i; i >>= 1) { crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); for (j = 0; j < 1 << CRC_LE_BITS; j += 2 * i) crc32table_le[i + j] = crc ^ crc32table_le[j]; } return 0; } /** * crc32cleanup_le(): free LE table data */ static void crc32cleanup_le(void) { if (crc32table_le) free(crc32table_le); crc32table_le = NULL; } /** * crc32_le() - Calculate bitwise little-endian Ethernet AUTODIN II CRC32 * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for * other uses, or the previous crc32 value if computing incrementally. * @p - pointer to buffer over which CRC is run * @len - length of buffer @p * */ uint32_t attribute((pure)) crc32_le(uint32_t crc, unsigned char const *p, size_t len) { while (len--) { # if CRC_LE_BITS == 8 crc = (crc >> 8) ^ crc32table_le[(crc ^ *p++) & 255]; # elif CRC_LE_BITS == 4 crc ^= *p++; crc = (crc >> 4) ^ crc32table_le[crc & 15]; crc = (crc >> 4) ^ crc32table_le[crc & 15]; # elif CRC_LE_BITS == 2 crc ^= *p++; crc = (crc >> 2) ^ crc32table_le[crc & 3]; crc = (crc >> 2) ^ crc32table_le[crc & 3]; crc = (crc >> 2) ^ crc32table_le[crc & 3]; crc = (crc >> 2) ^ crc32table_le[crc & 3]; # endif } return crc; } #endif /* * Big-endian CRC computation. Used with serial bit streams sent * msbit-first. Be sure to use cpu_to_be32() to append the computed CRC. */ #if CRC_BE_BITS > 8 || CRC_BE_BITS < 1 || CRC_BE_BITS & CRC_BE_BITS-1 # error CRC_BE_BITS must be a power of 2 between 1 and 8 #endif #if CRC_BE_BITS == 1 /* * In fact, the table-based code will work in this case, but it can be * simplified by inlining the table in ?: form. */ #define crc32init_be() #define crc32cleanup_be() /** * crc32_be() - Calculate bitwise big-endian Ethernet AUTODIN II CRC32 * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for * other uses, or the previous crc32 value if computing incrementally. * @p - pointer to buffer over which CRC is run * @len - length of buffer @p * */ uint32_t attribute((pure)) crc32_be(uint32_t crc, unsigned char const *p, size_t len) { int i; while (len--) { crc ^= *p++ << 24; for (i = 0; i < 8; i++) crc = (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : 0); } return crc; } #else /* Table-based approach */ static uint32_t *crc32table_be; /** * crc32init_be() - allocate and initialize BE table data */ static int crc32init_be(void) { unsigned i, j; uint32_t crc = 0x80000000; crc32table_be = malloc((1 << CRC_BE_BITS) * sizeof(uint32_t)); if (!crc32table_be) return 1; crc32table_be[0] = 0; for (i = 1; i < 1 << CRC_BE_BITS; i <<= 1) { crc = (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : 0); for (j = 0; j < i; j++) crc32table_be[i + j] = crc ^ crc32table_be[j]; } return 0; } /** * crc32cleanup_be(): free BE table data */ static void crc32cleanup_be(void) { if (crc32table_be) free(crc32table_be); crc32table_be = NULL; } /** * crc32_be() - Calculate bitwise big-endian Ethernet AUTODIN II CRC32 * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for * other uses, or the previous crc32 value if computing incrementally. * @p - pointer to buffer over which CRC is run * @len - length of buffer @p * */ uint32_t attribute((pure)) crc32_be(uint32_t crc, unsigned char const *p, size_t len) { while (len--) { # if CRC_BE_BITS == 8 crc = (crc << 8) ^ crc32table_be[(crc >> 24) ^ *p++]; # elif CRC_BE_BITS == 4 crc ^= *p++ << 24; crc = (crc << 4) ^ crc32table_be[crc >> 28]; crc = (crc << 4) ^ crc32table_be[crc >> 28]; # elif CRC_BE_BITS == 2 crc ^= *p++ << 24; crc = (crc << 2) ^ crc32table_be[crc >> 30]; crc = (crc << 2) ^ crc32table_be[crc >> 30]; crc = (crc << 2) ^ crc32table_be[crc >> 30]; crc = (crc << 2) ^ crc32table_be[crc >> 30]; # endif } return crc; } #endif /* * A brief CRC tutorial. * * A CRC is a long-division remainder. You add the CRC to the message, * and the whole thing (message+CRC) is a multiple of the given * CRC polynomial. To check the CRC, you can either check that the * CRC matches the recomputed value, *or* you can check that the * remainder computed on the message+CRC is 0. This latter approach * is used by a lot of hardware implementations, and is why so many * protocols put the end-of-frame flag after the CRC. * * It's actually the same long division you learned in school, except that * - We're working in binary, so the digits are only 0 and 1, and * - When dividing polynomials, there are no carries. Rather than add and * subtract, we just xor. Thus, we tend to get a bit sloppy about * the difference between adding and subtracting. * * A 32-bit CRC polynomial is actually 33 bits long. But since it's * 33 bits long, bit 32 is always going to be set, so usually the CRC * is written in hex with the most significant bit omitted. (If you're * familiar with the IEEE 754 floating-point format, it's the same idea.) * * Note that a CRC is computed over a string of *bits*, so you have * to decide on the endianness of the bits within each byte. To get * the best error-detecting properties, this should correspond to the * order they're actually sent. For example, standard RS-232 serial is * little-endian; the most significant bit (sometimes used for parity) * is sent last. And when appending a CRC word to a message, you should * do it in the right order, matching the endianness. * * Just like with ordinary division, the remainder is always smaller than * the divisor (the CRC polynomial) you're dividing by. Each step of the * division, you take one more digit (bit) of the dividend and append it * to the current remainder. Then you figure out the appropriate multiple * of the divisor to subtract to being the remainder back into range. * In binary, it's easy - it has to be either 0 or 1, and to make the * XOR cancel, it's just a copy of bit 32 of the remainder. * * When computing a CRC, we don't care about the quotient, so we can * throw the quotient bit away, but subtract the appropriate multiple of * the polynomial from the remainder and we're back to where we started, * ready to process the next bit. * * A big-endian CRC written this way would be coded like: * for (i = 0; i < input_bits; i++) { * multiple = remainder & 0x80000000 ? CRCPOLY : 0; * remainder = (remainder << 1 | next_input_bit()) ^ multiple; * } * Notice how, to get at bit 32 of the shifted remainder, we look * at bit 31 of the remainder *before* shifting it. * * But also notice how the next_input_bit() bits we're shifting into * the remainder don't actually affect any decision-making until * 32 bits later. Thus, the first 32 cycles of this are pretty boring. * Also, to add the CRC to a message, we need a 32-bit-long hole for it at * the end, so we have to add 32 extra cycles shifting in zeros at the * end of every message, * * So the standard trick is to rearrange merging in the next_input_bit() * until the moment it's needed. Then the first 32 cycles can be precomputed, * and merging in the final 32 zero bits to make room for the CRC can be * skipped entirely. * This changes the code to: * for (i = 0; i < input_bits; i++) { * remainder ^= next_input_bit() << 31; * multiple = (remainder & 0x80000000) ? CRCPOLY : 0; * remainder = (remainder << 1) ^ multiple; * } * With this optimization, the little-endian code is simpler: * for (i = 0; i < input_bits; i++) { * remainder ^= next_input_bit(); * multiple = (remainder & 1) ? CRCPOLY : 0; * remainder = (remainder >> 1) ^ multiple; * } * * Note that the other details of endianness have been hidden in CRCPOLY * (which must be bit-reversed) and next_input_bit(). * * However, as long as next_input_bit is returning the bits in a sensible * order, we can actually do the merging 8 or more bits at a time rather * than one bit at a time: * for (i = 0; i < input_bytes; i++) { * remainder ^= next_input_byte() << 24; * for (j = 0; j < 8; j++) { * multiple = (remainder & 0x80000000) ? CRCPOLY : 0; * remainder = (remainder << 1) ^ multiple; * } * } * Or in little-endian: * for (i = 0; i < input_bytes; i++) { * remainder ^= next_input_byte(); * for (j = 0; j < 8; j++) { * multiple = (remainder & 1) ? CRCPOLY : 0; * remainder = (remainder << 1) ^ multiple; * } * } * If the input is a multiple of 32 bits, you can even XOR in a 32-bit * word at a time and increase the inner loop count to 32. * * You can also mix and match the two loop styles, for example doing the * bulk of a message byte-at-a-time and adding bit-at-a-time processing * for any fractional bytes at the end. * * The only remaining optimization is to the byte-at-a-time table method. * Here, rather than just shifting one bit of the remainder to decide * in the correct multiple to subtract, we can shift a byte at a time. * This produces a 40-bit (rather than a 33-bit) intermediate remainder, * but again the multiple of the polynomial to subtract depends only on * the high bits, the high 8 bits in this case. * * The multiple we need in that case is the low 32 bits of a 40-bit * value whose high 8 bits are given, and which is a multiple of the * generator polynomial. This is simply the CRC-32 of the given * one-byte message. * * Two more details: normally, appending zero bits to a message which * is already a multiple of a polynomial produces a larger multiple of that * polynomial. To enable a CRC to detect this condition, it's common to * invert the CRC before appending it. This makes the remainder of the * message+crc come out not as zero, but some fixed non-zero value. * * The same problem applies to zero bits prepended to the message, and * a similar solution is used. Instead of starting with a remainder of * 0, an initial remainder of all ones is used. As long as you start * the same way on decoding, it doesn't make a difference. */ /** * init_crc32(): generates CRC32 tables * * On successful initialization, use count is increased. * This guarantees that the library functions will stay resident * in memory, and prevents someone from 'rmmod crc32' while * a driver that needs it is still loaded. * This also greatly simplifies drivers, as there's no need * to call an initialization/cleanup function from each driver. * Since crc32.o is a library module, there's no requirement * that the user can unload it. */ int init_crc32(void) { int rc1, rc2, rc; rc1 = crc32init_le(); rc2 = crc32init_be(); rc = rc1 || rc2; return rc; } /** * cleanup_crc32(): frees crc32 data when no longer needed */ void cleanup_crc32(void) { crc32cleanup_le(); crc32cleanup_be(); } multipath-tools-0.11.1/kpartx/crc32.h000066400000000000000000000010731475246302400173450ustar00rootroot00000000000000/* * crc32.h */ #ifndef CRC32_H_INCLUDED #define CRC32_H_INCLUDED #include #include extern int init_crc32(void); extern void cleanup_crc32(void); extern uint32_t crc32_le(uint32_t crc, unsigned char const *p, size_t len); extern uint32_t crc32_be(uint32_t crc, unsigned char const *p, size_t len); #define crc32(seed, data, length) crc32_le(seed, (unsigned char const *)data, length) #define ether_crc_le(length, data) crc32_le(~0, data, length) #define ether_crc(length, data) crc32_be(~0, data, length) #endif /* CRC32_H_INCLUDED */ multipath-tools-0.11.1/kpartx/dasd.c000066400000000000000000000161231475246302400173410ustar00rootroot00000000000000/* * dasd.c * * IBM DASD partition table handling. * * Mostly taken from drivers/s390/block/dasd.c * * Copyright (c) 2005, Hannes Reinecke, SUSE Linux Products GmbH * Copyright IBM Corporation, 2009 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "devmapper.h" #include "kpartx.h" #include "byteorder.h" #include "dasd.h" unsigned long long sectors512(unsigned long long sectors, int blocksize) { return sectors * (blocksize >> 9); } /* * Magic records per track calculation, copied from fdasd.c */ static unsigned int ceil_quot(unsigned int d1, unsigned int d2) { return (d1 + (d2 - 1)) / d2; } unsigned int recs_per_track(unsigned int dl) { int dn = ceil_quot(dl + 6, 232) + 1; return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34)); } 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 retval = -1; int blocksize; uint64_t disksize; uint64_t offset, size, fmt_size; dasd_information_t info; struct hd_geometry geo; char type[5] = {0,}; volume_label_t vlabel; unsigned char *data = NULL; uint64_t blk; int fd_dasd = -1; struct stat sbuf; dev_t dev; char *devname; char pathname[256]; if (fd < 0) { return -1; } if (fstat(fd, &sbuf) == -1) { return -1; } devname = dm_mapname(major(sbuf.st_rdev), minor(sbuf.st_rdev)); if (devname != NULL) { /* We were passed a handle to a dm device. * Get the first target and operate on that instead. */ if (!(dev = dm_get_first_dep(devname))) { free(devname); return -1; } free(devname); if ((unsigned int)major(dev) != 94) { /* Not a DASD */ return -1; } /* * Hard to believe, but there's no simple way to translate * major/minor into an openable device file, so we have * to create one for ourselves. */ sprintf(pathname, "/dev/.kpartx-node-%u-%u", (unsigned int)major(dev), (unsigned int)minor(dev)); if ((fd_dasd = open(pathname, O_RDONLY | O_DIRECT)) == -1) { /* Devicenode does not exist. Try to create one */ if (mknod(pathname, 0600 | S_IFBLK, dev) == -1) { /* Couldn't create a device node */ return -1; } fd_dasd = open(pathname, O_RDONLY | O_DIRECT); /* * The file will vanish when the last process (we) * has ceased to access it. */ unlink(pathname); } if (fd_dasd < 0) { /* Couldn't open the device */ return -1; } } else { fd_dasd = dup(fd); if (fd_dasd < 0) return -1; } if (ioctl(fd_dasd, BIODASDINFO, (unsigned long)&info) != 0) { info.label_block = 2; info.FBA_layout = 0; memcpy(info.type, "ECKD", sizeof(info.type)); } if (ioctl(fd_dasd, BLKSSZGET, &blocksize) != 0) goto out; if (ioctl(fd_dasd, BLKGETSIZE64, &disksize) != 0) goto out; if (ioctl(fd_dasd, HDIO_GETGEO, (unsigned long)&geo) != 0) { unsigned int cyl; geo.heads = 15; geo.sectors = recs_per_track(blocksize); cyl = disksize / ((uint64_t)blocksize * geo.heads * geo.sectors); if (cyl < LV_COMPAT_CYL) geo.cylinders = cyl; else geo.cylinders = LV_COMPAT_CYL; geo.start = 0; } disksize >>= 9; if (blocksize < 512 || blocksize > 4096) goto out; /* * Get volume label, extract name and type. */ if (aligned_malloc((void **)&data, blocksize, NULL)) goto out; if (lseek(fd_dasd, info.label_block * blocksize, SEEK_SET) == -1) goto out; if (read(fd_dasd, data, blocksize) == -1) { perror("read"); goto out; } if ((!info.FBA_layout) && (!memcmp(info.type, "ECKD", 4))) memcpy (&vlabel, data, sizeof(vlabel)); else { bzero(&vlabel,4); memcpy ((char *)&vlabel + 4, data, sizeof(vlabel) - 4); } vtoc_ebcdic_dec(vlabel.vollbl, type, 4); /* * Three different types: CMS1, VOL1 and LNX1/unlabeled */ if (strncmp(type, "CMS1", 4) == 0) { /* * VM style CMS1 labeled disk */ label_ints_t *label = (label_ints_t *) &vlabel; blocksize = label[4]; if (label[14] != 0) { /* disk is reserved minidisk */ offset = label[14]; size = sectors512(label[8] - 1, blocksize); } else { offset = info.label_block + 1; size = sectors512(label[8], blocksize); } sp[0].start = sectors512(offset, blocksize); sp[0].size = size - sp[0].start; retval = 1; } else if ((strncmp(type, "VOL1", 4) == 0) && (!info.FBA_layout) && (!memcmp(info.type, "ECKD",4))) { /* * New style VOL1 labeled disk */ int counter; /* get block number and read then go through format1 labels */ blk = cchhb2blk(&vlabel.vtoc, &geo) + 1; counter = 0; if (lseek(fd_dasd, blk * blocksize, SEEK_SET) == -1) goto out; while (read(fd_dasd, data, blocksize) != -1) { format1_label_t f1; memcpy(&f1, data, sizeof(format1_label_t)); /* skip FMT4 / FMT5 / FMT7 labels */ if (EBCtoASC[f1.DS1FMTID] == '4' || EBCtoASC[f1.DS1FMTID] == '5' || EBCtoASC[f1.DS1FMTID] == '7' || EBCtoASC[f1.DS1FMTID] == '9') { blk++; continue; } /* only FMT1 and FMT8 valid at this point */ if (EBCtoASC[f1.DS1FMTID] != '1' && EBCtoASC[f1.DS1FMTID] != '8') break; /* OK, we got valid partition data */ offset = cchh2blk(&f1.DS1EXT1.llimit, &geo); size = cchh2blk(&f1.DS1EXT1.ulimit, &geo) - offset + geo.sectors; sp[counter].start = sectors512(offset, blocksize); sp[counter].size = sectors512(size, blocksize); counter++; blk++; } retval = counter; } else { /* * Old style LNX1 or unlabeled disk */ if (strncmp(type, "LNX1", 4) == 0) { if (vlabel.ldl_version == 0xf2) { fmt_size = sectors512(vlabel.formatted_blocks, blocksize); } else if (!memcmp(info.type, "ECKD",4)) { /* formatted w/o large volume support */ fmt_size = geo.cylinders * geo.heads * geo.sectors * (blocksize >> 9); } else { /* old label and no usable disk geometry * (e.g. DIAG) */ fmt_size = disksize; } size = disksize; if (fmt_size < size) size = fmt_size; } else if ((unsigned int)major(sbuf.st_rdev) != 94) { /* Not a DASD */ retval = -1; goto out; } else size = disksize; sp[0].start = sectors512(info.label_block + 1, blocksize); sp[0].size = size - sp[0].start; retval = 1; } out: if (data != NULL) free(data); close(fd_dasd); return retval; } multipath-tools-0.11.1/kpartx/dasd.h000066400000000000000000000274451475246302400173570ustar00rootroot00000000000000/* * dasd.h * * IBM DASD partition table handling. * * Mostly taken from drivers/s390/block/dasd.c * * Copyright (c) 2005, Hannes Reinecke, SUSE Linux Products GmbH * Copyright IBM Corporation, 2009 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef DASD_H_INCLUDED #define DASD_H_INCLUDED typedef struct ttr { uint16_t tt; uint8_t r; } __attribute__ ((packed)) ttr_t; typedef struct cchhb { uint16_t cc; uint16_t hh; uint8_t b; } __attribute__ ((packed)) cchhb_t; typedef struct cchh { uint16_t cc; uint16_t hh; } __attribute__ ((packed)) cchh_t; typedef struct labeldate { uint8_t year; uint16_t day; } __attribute__ ((packed)) labeldate_t; typedef struct volume_label { char volkey[4]; /* volume key = volume label */ char vollbl[4]; /* volume label */ char volid[6]; /* volume identifier */ uint8_t security; /* security byte */ cchhb_t vtoc; /* VTOC address */ char res1[5]; /* reserved */ char cisize[4]; /* CI-size for FBA,... */ /* ...blanks for CKD */ char blkperci[4]; /* no of blocks per CI (FBA), blanks for CKD */ char labperci[4]; /* no of labels per CI (FBA), blanks for CKD */ char res2[4]; /* reserved */ char lvtoc[14]; /* owner code for LVTOC */ char res3[28]; /* reserved */ uint8_t ldl_version; /* version number, valid for ldl format */ uint64_t formatted_blocks; /* valid when ldl_version >= f2 */ } __attribute__ ((packed, aligned(__alignof__(int)))) volume_label_t; typedef struct extent { uint8_t typeind; /* extent type indicator */ uint8_t seqno; /* extent sequence number */ cchh_t llimit; /* starting point of this extent */ cchh_t ulimit; /* ending point of this extent */ } __attribute__ ((packed)) extent_t; typedef struct dev_const { uint16_t DS4DSCYL; /* number of logical cyls */ uint16_t DS4DSTRK; /* number of tracks in a logical cylinder */ uint16_t DS4DEVTK; /* device track length */ uint8_t DS4DEVI; /* non-last keyed record overhead */ uint8_t DS4DEVL; /* last keyed record overhead */ uint8_t DS4DEVK; /* non-keyed record overhead differential */ uint8_t DS4DEVFG; /* flag byte */ uint16_t DS4DEVTL; /* device tolerance */ uint8_t DS4DEVDT; /* number of DSCB's per track */ uint8_t DS4DEVDB; /* number of directory blocks per track */ } __attribute__ ((packed)) dev_const_t; typedef struct format1_label { char DS1DSNAM[44]; /* data set name */ uint8_t DS1FMTID; /* format identifier */ char DS1DSSN[6]; /* data set serial number */ uint16_t DS1VOLSQ; /* volume sequence number */ labeldate_t DS1CREDT; /* creation date: ydd */ labeldate_t DS1EXPDT; /* expiration date */ uint8_t DS1NOEPV; /* number of extents on volume */ uint8_t DS1NOBDB; /* no. of bytes used in last direction blk */ uint8_t DS1FLAG1; /* flag 1 */ char DS1SYSCD[13]; /* system code */ labeldate_t DS1REFD; /* date last referenced */ uint8_t DS1SMSFG; /* system managed storage indicators */ uint8_t DS1SCXTF; /* sec. space extension flag byte */ uint16_t DS1SCXTV; /* secondary space extension value */ uint8_t DS1DSRG1; /* data set organisation byte 1 */ uint8_t DS1DSRG2; /* data set organisation byte 2 */ uint8_t DS1RECFM; /* record format */ uint8_t DS1OPTCD; /* option code */ uint16_t DS1BLKL; /* block length */ uint16_t DS1LRECL; /* record length */ uint8_t DS1KEYL; /* key length */ uint16_t DS1RKP; /* relative key position */ uint8_t DS1DSIND; /* data set indicators */ uint8_t DS1SCAL1; /* secondary allocation flag byte */ char DS1SCAL3[3]; /* secondary allocation quantity */ ttr_t DS1LSTAR; /* last used track and block on track */ uint16_t DS1TRBAL; /* space remaining on last used track */ uint16_t res1; /* reserved */ extent_t DS1EXT1; /* first extent description */ extent_t DS1EXT2; /* second extent description */ extent_t DS1EXT3; /* third extent description */ cchhb_t DS1PTRDS; /* possible pointer to f2 or f3 DSCB */ } __attribute__ ((packed)) format1_label_t; /* * struct dasd_information_t * represents any data about the data, which is visible to userspace */ typedef struct dasd_information_t { unsigned int devno; /* S/390 devno */ unsigned int real_devno; /* for aliases */ unsigned int schid; /* S/390 subchannel identifier */ unsigned int cu_type : 16; /* from SenseID */ unsigned int cu_model : 8; /* from SenseID */ unsigned int dev_type : 16; /* from SenseID */ unsigned int dev_model : 8; /* from SenseID */ unsigned int open_count; unsigned int req_queue_len; unsigned int chanq_len; /* length of chanq */ char type[4]; /* from discipline.name, 'none' for unknown */ unsigned int status; /* current device level */ unsigned int label_block; /* where to find the VOLSER */ unsigned int FBA_layout; /* fixed block size (like AIXVOL) */ unsigned int characteristics_size; unsigned int confdata_size; char characteristics[64]; /* from read_device_characteristics */ char configuration_data[256]; /* from read_configuration_data */ } dasd_information_t; #define DASD_IOCTL_LETTER 'D' #define BIODASDINFO _IOR(DASD_IOCTL_LETTER,1,dasd_information_t) #define BLKGETSIZE _IO(0x12,96) #define BLKSSZGET _IO(0x12,104) #define BLKGETSIZE64 _IOR(0x12,114,size_t) /* device size in bytes (u64 *arg)*/ #define LV_COMPAT_CYL 0xFFFE /* * Only compile this on S/390. Doesn't make any sense * for other architectures. */ static unsigned char EBCtoASC[256] = { /* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, /* 0x08 -GE -SPS -RPT VT FF CR SO SI */ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC -ENP ->LF */ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, /* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB -IUS */ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC -INP */ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, /* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL -SW */ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, /* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, /* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, /* 0x40 SP RSP ä ---- */ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, /* 0x48 . < ( + | */ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* 0x50 & ---- */ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, /* 0x58 ß ! $ * ) ; */ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, /* 0x60 - / ---- Ä ---- ---- ---- */ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, /* 0x68 ---- , % _ > ? */ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* 0x70 --- ---- ---- ---- ---- ---- ---- */ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x78 * ` : # @ ' = " */ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* 0x80 * a b c d e f g */ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* 0x88 h i ---- ---- ---- */ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, /* 0x90 ° j k l m n o p */ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, /* 0x98 q r ---- ---- */ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, /* 0xA0 ~ s t u v w x */ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, /* 0xA8 y z ---- ---- ---- ---- */ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, /* 0xB0 ^ ---- § ---- */ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, /* 0xB8 ---- [ ] ---- ---- ---- ---- */ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, /* 0xC0 { A B C D E F G */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0xC8 H I ---- ö ---- */ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, /* 0xD0 } J K L M N O P */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, /* 0xD8 Q R ---- ü */ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, /* 0xE0 \ S T U V W X */ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, /* 0xE8 Y Z ---- Ö ---- ---- ---- */ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, /* 0xF0 0 1 2 3 4 5 6 7 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 }; static inline void vtoc_ebcdic_dec (const char *source, char *target, int l) { int i; for (i = 0; i < l; i++) target[i]=(char)EBCtoASC[(unsigned char)(source[i])]; } /* * compute the block number from a * cyl-cyl-head-head structure */ static inline uint64_t cchh2blk (cchh_t *ptr, struct hd_geometry *geo) { uint64_t cyl; uint16_t head; /*decode cylinder and heads for large volumes */ cyl = ptr->hh & 0xFFF0; cyl <<= 12; cyl |= ptr->cc; head = ptr->hh & 0x000F; return cyl * geo->heads * geo->sectors + head * geo->sectors; } /* * compute the block number from a * cyl-cyl-head-head-block structure */ static inline uint64_t cchhb2blk (cchhb_t *ptr, struct hd_geometry *geo) { uint64_t cyl; uint16_t head; /*decode cylinder and heads for large volumes */ cyl = ptr->hh & 0xFFF0; cyl <<= 12; cyl |= ptr->cc; head = ptr->hh & 0x000F; return cyl * geo->heads * geo->sectors + head * geo->sectors + ptr->b; } #endif /* DASD_H_INCLUDED */ multipath-tools-0.11.1/kpartx/del-part-nodes.rules000066400000000000000000000021661475246302400221560ustar00rootroot00000000000000# These rules can delete partitions devnodes for slave devices # for certain aggregate devices such as multipath. # This is desirable to avoid confusion and keep the number # of device nodes and symlinks within limits. # # This is done only once on the first "add" or "change" event for # any given device. # # To suppress this, use the kernel parameter "dont_del_part_nodes", # or create an udev rule file that sets ENV{DONT_DEL_PART_NODES}="1". SUBSYSTEM!="block", GOTO="end_del_part_nodes" KERNEL!="sd*|dasd*", GOTO="end_del_part_nodes" ACTION!="add|change", GOTO="end_del_part_nodes" ENV{DEVTYPE}=="partition", GOTO="end_del_part_nodes" IMPORT{cmdline}="dont_del_part_nodes" ENV{dont_del_part_nodes}=="1", GOTO="end_del_part_nodes" ENV{DONT_DEL_PART_NODES}=="1", GOTO="end_del_part_nodes" # dm-multipath ENV{DM_MULTIPATH_DEVICE_PATH}=="1", GOTO="del_part_nodes" # Other aggregate device types can be added here. GOTO="end_del_part_nodes" LABEL="del_part_nodes" IMPORT{db}="DM_DEL_PART_NODES" ENV{DM_DEL_PART_NODES}!="1", ENV{DM_DEL_PART_NODES}="1", \ RUN+="/usr/sbin/partx -d --nr 1-1024 $env{DEVNAME}" LABEL="end_del_part_nodes" multipath-tools-0.11.1/kpartx/devmapper.c000066400000000000000000000336741475246302400204230ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include "autoconfig.h" #include "devmapper.h" #include "kpartx.h" #define UUID_PREFIX_ "part" #define UUID_PREFIX UUID_PREFIX_ "%d-" #define UUID_PREFIX_LEN (sizeof(UUID_PREFIX_) - 1) #define MAX_PREFIX_LEN (UUID_PREFIX_LEN + 4) #define PARAMS_SIZE 1024 #ifdef LIBDM_API_COOKIE # define DM_API_COOKIE_UNUSED__ /* empty */ #else # define DM_API_COOKIE_UNUSED__ __attribute__((unused)) #endif int dm_prereq(char * str, uint32_t x, uint32_t y, uint32_t z) { int r = 1; struct dm_task *dmt; struct dm_versions *target; struct dm_versions *last_target; if (!(dmt = dm_task_create(DM_DEVICE_LIST_VERSIONS))) return 1; if (!dm_task_run(dmt)) goto out; target = dm_task_get_versions(dmt); /* Fetch targets and print 'em */ do { last_target = target; if (!strncmp(str, target->name, strlen(str)) && /* dummy prereq on multipath version */ target->version[0] >= x && target->version[1] >= y && target->version[2] >= z ) r = 0; target = (void *) target + target->next; } while (last_target != target); out: dm_task_destroy(dmt); return r; } int dm_simplecmd(int task, const char *name, int no_flush, DM_API_COOKIE_UNUSED__ uint16_t udev_flags) { int r = 0; #ifdef LIBDM_API_COOKIE int udev_wait_flag = (task == DM_DEVICE_RESUME || task == DM_DEVICE_REMOVE); uint32_t cookie = 0; #endif struct dm_task *dmt; if (!(dmt = dm_task_create(task))) return 0; if (!dm_task_set_name(dmt, name)) goto out; dm_task_skip_lockfs(dmt); if (no_flush) dm_task_no_flush(dmt); #ifdef LIBDM_API_COOKIE if (!udev_sync) udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (udev_wait_flag && !dm_task_set_cookie(dmt, &cookie, udev_flags)) goto out; #endif r = dm_task_run(dmt); #ifdef LIBDM_API_COOKIE if (udev_wait_flag) dm_udev_wait(cookie); #endif out: dm_task_destroy(dmt); return r; } static void strip_slash (char * device) { char * p = device; while (*(p++) != 0x0) { if (*p == '/') *p = '!'; } } static int format_partname(char *buf, size_t bufsiz, const char *mapname, const char *delim, int part) { if (safe_snprintf(buf, bufsiz, "%s%s%d", mapname, delim, part)) return 0; strip_slash(buf); return 1; } static char *make_prefixed_uuid(int part, const char *uuid) { char *prefixed_uuid; int len = MAX_PREFIX_LEN + strlen(uuid) + 1; prefixed_uuid = malloc(len); if (!prefixed_uuid) { fprintf(stderr, "cannot create prefixed uuid : %s\n", strerror(errno)); return NULL; } snprintf(prefixed_uuid, len, UUID_PREFIX "%s", part, uuid); return prefixed_uuid; } int dm_addmap(int task, const char *name, const char *target, const char *params, uint64_t size, int ro, const char *uuid, int part, mode_t mode, uid_t uid, gid_t gid) { int r = 0; struct dm_task *dmt; char *prefixed_uuid = NULL; #ifdef LIBDM_API_COOKIE uint32_t cookie = 0; uint16_t udev_flags = 0; #endif if (!(dmt = dm_task_create (task))) return 0; if (!dm_task_set_name (dmt, name)) goto addout; if (!dm_task_add_target (dmt, 0, size, target, params)) goto addout; if (ro && !dm_task_set_ro (dmt)) goto addout; if (task == DM_DEVICE_CREATE && uuid) { prefixed_uuid = make_prefixed_uuid(part, uuid); if (prefixed_uuid == NULL) goto addout; if (!dm_task_set_uuid(dmt, prefixed_uuid)) goto addout; } if (!dm_task_set_mode(dmt, mode)) goto addout; if (!dm_task_set_uid(dmt, uid)) goto addout; if (!dm_task_set_gid(dmt, gid)) goto addout; #ifdef LIBDM_API_COOKIE if (!udev_sync) udev_flags = DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (task == DM_DEVICE_CREATE && !dm_task_set_cookie(dmt, &cookie, udev_flags)) goto addout; #endif r = dm_task_run (dmt); #ifdef LIBDM_API_COOKIE if (task == DM_DEVICE_CREATE) dm_udev_wait(cookie); #endif addout: dm_task_destroy (dmt); free(prefixed_uuid); return r; } static int dm_map_present(char *str, char **uuid) { int r = 0; struct dm_task *dmt; const char *uuidtmp; struct dm_info info; if (uuid) *uuid = NULL; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return 0; if (!dm_task_set_name(dmt, str)) goto out; if (!dm_task_run(dmt)) goto out; if (!dm_task_get_info(dmt, &info)) goto out; if (!info.exists) goto out; r = 1; if (uuid) { uuidtmp = dm_task_get_uuid(dmt); if (uuidtmp && strlen(uuidtmp)) *uuid = strdup(uuidtmp); } out: dm_task_destroy(dmt); return r; } static int dm_rename (const char *old, const char *new) { int r = 0; struct dm_task *dmt; uint16_t udev_flags = DM_UDEV_DISABLE_LIBRARY_FALLBACK; uint32_t cookie = 0; dmt = dm_task_create(DM_DEVICE_RENAME); if (!dmt) return r; if (!dm_task_set_name(dmt, old) || !dm_task_set_newname(dmt, new) || !dm_task_set_cookie(dmt, &cookie, udev_flags)) goto out; r = dm_task_run(dmt); dm_udev_wait(cookie); out: dm_task_destroy(dmt); return r; } static char *dm_find_uuid(const char *uuid) { struct dm_task *dmt; char *name = NULL; const char *tmp; if ((dmt = dm_task_create(DM_DEVICE_INFO)) == NULL) return NULL; if (!dm_task_set_uuid(dmt, uuid) || !dm_task_run(dmt)) goto out; tmp = dm_task_get_name(dmt); if (tmp != NULL && *tmp != '\0') name = strdup(tmp); out: dm_task_destroy(dmt); return name; } char * dm_mapname(int major, int minor) { struct dm_task *dmt; char *mapname = NULL; const char *map; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return NULL; dm_task_set_major(dmt, major); dm_task_set_minor(dmt, minor); if (!dm_task_run(dmt)) goto out; map = dm_task_get_name(dmt); if (map && strlen(map)) mapname = strdup(map); out: dm_task_destroy(dmt); return mapname; } /* * dm_get_first_dep * * Return the device number of the first dependent device * for a given target. */ dev_t dm_get_first_dep(char *devname) { struct dm_task *dmt; struct dm_deps *dm_deps; dev_t ret = 0; if ((dmt = dm_task_create(DM_DEVICE_DEPS)) == NULL) { return ret; } if (!dm_task_set_name(dmt, devname)) { goto out; } if (!dm_task_run(dmt)) { goto out; } if ((dm_deps = dm_task_get_deps(dmt)) == NULL) { goto out; } if (dm_deps->count > 0) { ret = dm_deps->device[0]; } out: dm_task_destroy(dmt); return ret; } char * dm_mapuuid(const char *mapname) { struct dm_task *dmt; const char *tmp; char *uuid = NULL; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return NULL; if (!dm_task_set_name(dmt, mapname)) goto out; if (!dm_task_run(dmt)) goto out; tmp = dm_task_get_uuid(dmt); if (tmp[0] != '\0') uuid = strdup(tmp); out: dm_task_destroy(dmt); return uuid; } int dm_devn (const char * mapname, unsigned int *major, unsigned int *minor) { int r = 1; struct dm_task *dmt; struct dm_info info; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return 1; if (!dm_task_set_name(dmt, mapname)) goto out; if (!dm_task_run(dmt)) goto out; if (!dm_task_get_info(dmt, &info) || info.exists == 0) goto out; *major = info.major; *minor = info.minor; r = 0; out: dm_task_destroy(dmt); return r; } static int dm_get_map(const char *mapname, char * outparams) { int r = 1; struct dm_task *dmt; uint64_t start, length; char *target_type = NULL; char *params = NULL; if (!(dmt = dm_task_create(DM_DEVICE_TABLE))) return 1; if (!dm_task_set_name(dmt, mapname)) goto out; if (!dm_task_run(dmt)) goto out; /* Fetch 1st target */ if (dm_get_next_target(dmt, NULL, &start, &length, &target_type, ¶ms) != NULL || !params) /* more than one target or not found target */ goto out; if (snprintf(outparams, PARAMS_SIZE, "%s", params) <= PARAMS_SIZE) r = 0; out: dm_task_destroy(dmt); return r; } static int dm_get_opencount (const char * mapname) { int r = -1; struct dm_task *dmt; struct dm_info info; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return 0; if (!dm_task_set_name(dmt, mapname)) goto out; if (!dm_task_run(dmt)) goto out; if (!dm_task_get_info(dmt, &info)) goto out; if (!info.exists) goto out; r = info.open_count; out: dm_task_destroy(dmt); return r; } /* * returns: * 1 : match * 0 : no match * -1 : empty map */ static int dm_type(const char * name, char * type) { int r = 0; struct dm_task *dmt; uint64_t start, length; char *target_type = NULL; char *params; if (!(dmt = dm_task_create(DM_DEVICE_TABLE))) return 0; if (!dm_task_set_name(dmt, name)) goto out; if (!dm_task_run(dmt)) goto out; /* Fetch 1st target */ if (dm_get_next_target(dmt, NULL, &start, &length, &target_type, ¶ms) != NULL) /* more than one target */ r = -1; else if (!target_type) r = -1; else if (!strcmp(target_type, type)) r = 1; out: dm_task_destroy(dmt); return r; } /* * returns: * 0 : if both uuids end with same suffix which starts with UUID_PREFIX * 1 : otherwise */ int dm_compare_uuid(const char *mapuuid, const char *partname) { char *partuuid; int r = 1; partuuid = dm_mapuuid(partname); if (!partuuid) return 1; if (!strncmp(partuuid, UUID_PREFIX_, UUID_PREFIX_LEN)) { char *p = partuuid + UUID_PREFIX_LEN; /* skip partition number */ while (isdigit(*p)) p++; if (p != partuuid + UUID_PREFIX_LEN && *p == '-' && !strcmp(mapuuid, p + 1)) r = 0; } free(partuuid); return r; } struct remove_data { int verbose; }; static int do_foreach_partmaps (const char * mapname, const char *uuid, dev_t devt, int (*partmap_func)(const char *, void *), void *data) { struct dm_task *dmt; struct dm_names *names; struct remove_data *rd = data; unsigned next = 0; char params[PARAMS_SIZE]; unsigned int major, minor; char dev_t[32]; int r = 1; int is_dmdev = 1; if (!(dmt = dm_task_create(DM_DEVICE_LIST))) return 1; if (!dm_task_run(dmt)) goto out; if (!(names = dm_task_get_names(dmt))) goto out; if (!names->dev) { r = 0; /* this is perfectly valid */ goto out; } if (dm_devn(mapname, &major, &minor) || (major != major(devt) || minor != minor(devt))) /* * The latter could happen if a dm device "/dev/mapper/loop0" * exits while kpartx is called on "/dev/loop0". */ is_dmdev = 0; sprintf(dev_t, "%d:%d", major(devt), minor(devt)); do { /* * skip our devmap */ if (is_dmdev && !strcmp(names->name, mapname)) goto next; /* * skip if we cannot fetch the map table from the kernel */ if (dm_get_map(names->name, ¶ms[0])) goto next; /* * skip if the table does not map over the multipath map */ if (!strstr(params, dev_t)) goto next; /* * skip if devmap target is not "linear" */ if (dm_type(names->name, "linear") != 1) { if (rd->verbose) printf("%s: is not a linear target. Not removing\n", names->name); goto next; } /* * skip if uuids don't match */ if (uuid && dm_compare_uuid(uuid, names->name)) { if (rd->verbose) printf("%s: is not a kpartx partition. Not removing\n", names->name); goto next; } if (partmap_func(names->name, data) != 0) goto out; next: next = names->next; names = (void *) names + next; } while (next); r = 0; out: dm_task_destroy (dmt); return r; } static int remove_partmap(const char *name, void *data) { struct remove_data *rd = (struct remove_data *)data; int r = 0; if (dm_get_opencount(name)) { if (rd->verbose) printf("%s is in use. Not removing\n", name); return 1; } if (!dm_simplecmd(DM_DEVICE_REMOVE, name, 0, 0)) { if (rd->verbose) printf("%s: failed to remove\n", name); r = 1; } else if (rd->verbose) printf("del devmap : %s\n", name); return r; } int dm_remove_partmaps (char * mapname, char *uuid, dev_t devt, int verbose) { struct remove_data rd = { verbose }; return do_foreach_partmaps(mapname, uuid, devt, remove_partmap, &rd); } 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) { int r; char params[PARAMS_SIZE]; char *tmp; char *uuid; unsigned int major, minor; char dev_t[32]; if (!format_partname(name, namesiz, parent, delim, part)) { if (verbose) fprintf(stderr, "partname too small\n"); return 0; } r = dm_map_present(name, part_uuid); if (r == 1 || parent_uuid == NULL || *parent_uuid == '\0') return r; uuid = make_prefixed_uuid(part, parent_uuid); if (!uuid) return 0; tmp = dm_find_uuid(uuid); if (tmp == NULL) goto out; /* Sanity check on partition, see dm_foreach_partmaps */ if (dm_type(tmp, "linear") != 1) goto out; /* * Try nondm uuid first. That way we avoid confusing * a device name with a device mapper name. */ if (!nondm_parse_uuid(parent_uuid, &major, &minor) && dm_devn(parent, &major, &minor)) goto out; snprintf(dev_t, sizeof(dev_t), "%d:%d", major, minor); if (dm_get_map(tmp, params)) goto out; if (!strstr(params, dev_t)) goto out; if (verbose) fprintf(stderr, "found map %s for uuid %s, renaming to %s\n", tmp, uuid, name); r = dm_rename(tmp, name); if (r == 1) { free(tmp); *part_uuid = uuid; return 1; } if (verbose) fprintf(stderr, "renaming %s->%s failed\n", tmp, name); out: free(uuid); free(tmp); return r; } char *nondm_create_uuid(dev_t devt) { #define NONDM_UUID_BUFLEN (34 + sizeof(NONDM_UUID_PREFIX) + \ sizeof(NONDM_UUID_SUFFIX)) static char uuid_buf[NONDM_UUID_BUFLEN]; snprintf(uuid_buf, sizeof(uuid_buf), "%s_%u:%u_%s", NONDM_UUID_PREFIX, major(devt), minor(devt), NONDM_UUID_SUFFIX); uuid_buf[NONDM_UUID_BUFLEN-1] = '\0'; return uuid_buf; } int nondm_parse_uuid(const char *uuid, unsigned int *major, unsigned int *minor) { const char *p; char *e; int ma, mi; if (strncmp(uuid, NONDM_UUID_PREFIX "_", sizeof(NONDM_UUID_PREFIX))) return 0; p = uuid + sizeof(NONDM_UUID_PREFIX); ma = strtoul(p, &e, 10); if (e == p || *e != ':') return 0; p = e + 1; mi = strtoul(p, &e, 10); if (e == p || *e != '_') return 0; p = e + 1; if (strcmp(p, NONDM_UUID_SUFFIX)) return 0; *major = ma; *minor = mi; return 1; } multipath-tools-0.11.1/kpartx/devmapper.h000066400000000000000000000027641475246302400204240ustar00rootroot00000000000000#ifndef KPARTX_DEVMAPPER_H_INCLUDED #define KPARTX_DEVMAPPER_H_INCLUDED #ifdef DM_SUBSYSTEM_UDEV_FLAG0 #define MPATH_UDEV_RELOAD_FLAG DM_SUBSYSTEM_UDEV_FLAG0 #else #define MPATH_UDEV_RELOAD_FLAG 0 #endif extern int udev_sync; int dm_prereq (char *, uint32_t, uint32_t, uint32_t); int dm_simplecmd (int, const char *, int, uint16_t); int dm_addmap (int, const char *, const char *, const char *, uint64_t, int, const char *, int, mode_t, uid_t, gid_t); char * dm_mapname(int major, int minor); 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); 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); /* * UUID format for partitions created on non-DM devices * ${UUID_PREFIX}devnode_${MAJOR}:${MINOR}_${NONDM_UUID_SUFFIX}" * where ${UUID_PREFIX} is "part${PARTNO}-" (see devmapper.c). * * The suffix should be sufficiently unique to avoid incidental conflicts; * the value below is a base64-encoded random number. * The UUID format shouldn't be changed between kpartx releases. */ #define NONDM_UUID_PREFIX "devnode" #define NONDM_UUID_SUFFIX "Wh5pYvM" char *nondm_create_uuid(dev_t devt); int nondm_parse_uuid(const char *uuid, unsigned int *major, unsigned int *minor); #endif /* KPARTX_DEVMAPPER_H_INCLUDED */ multipath-tools-0.11.1/kpartx/dm-parts.rules000066400000000000000000000026431475246302400210670ustar00rootroot00000000000000# Rules for partitions created by kpartx KERNEL!="dm-*", GOTO="dm_parts_end" ACTION!="add|change", GOTO="dm_parts_end" ENV{DM_UUID}!="part[0-9]*", GOTO="dm_parts_end" # We must take care that symlinks don't get lost, # even if blkid fails in 13-dm-disk.rules later. # # Fixme: we have currently no way to avoid calling blkid on # partitions of broken mpath maps such as DM_NOSCAN. # But when partition devices appear, kpartx has likely read # the partition table shortly before, so odds are not bad # that blkid will also succeed. IMPORT{db}="ID_FS_USAGE" IMPORT{db}="ID_FS_UUID_ENC" IMPORT{db}="ID_FS_LABEL_ENC" IMPORT{db}="ID_PART_ENTRY_NAME" IMPORT{db}="ID_PART_ENTRY_UUID" IMPORT{db}="ID_PART_ENTRY_SCHEME" # Maps should take precedence over their members. ENV{DM_UDEV_LOW_PRIORITY_FLAG}!="1", OPTIONS+="link_priority=50" # Set some additional symlinks that typically exist for mpath # path members, too, and should be overridden. # # kpartx_id is very robust, it works for suspended maps and maps # with 0 dependencies. It sets DM_TYPE, DM_PART, DM_WWN IMPORT{program}=="kpartx_id %M %m $env{DM_UUID}" # DM_TYPE only has a reasonable value for partitions on multipath. ENV{DM_UUID}=="*-mpath-*", ENV{DM_TYPE}=="?*", ENV{DM_SERIAL}=="?*", \ SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_SERIAL}-part$env{DM_PART}" ENV{DM_WWN}=="?*", ENV{DM_PART}=="?*", \ SYMLINK+="disk/by-id/wwn-$env{DM_WWN}-part$env{DM_PART}" LABEL="dm_parts_end" multipath-tools-0.11.1/kpartx/dos.c000066400000000000000000000050141475246302400172100ustar00rootroot00000000000000/* * Source: copy of util-linux' partx dos.c * * Copyrights of the original file apply * Copyright (c) 2005 Bastian Blank */ #include "kpartx.h" #include "byteorder.h" #include #include #include "dos.h" static int is_extended(int type) { return (type == 5 || type == 0xf || type == 0x85); } static int read_extended_partition(int fd, struct partition *ep, int en, struct slice *sp, int ns) { struct partition p; unsigned long start, here, next; unsigned char *bp; int loopct = 0; int moretodo = 1; int i, n=0; int sector_size_mul = get_sector_size(fd)/512; next = start = sector_size_mul * le32_to_cpu(ep->start_sect); while (moretodo) { here = next; moretodo = 0; if (++loopct > 100) return n; bp = (unsigned char *)getblock(fd, here); if (bp == NULL) return n; if (bp[510] != 0x55 || bp[511] != 0xaa) return n; for (i=0; i<2; i++) { memcpy(&p, bp + 0x1be + i * sizeof (p), sizeof (p)); if (is_extended(p.sys_type)) { if (p.start_sect && p.nr_sects && !moretodo) { next = start + sector_size_mul * le32_to_cpu(p.start_sect); moretodo = 1; } continue; } if (n < ns) { sp[n].start = here + sector_size_mul * le32_to_cpu(p.start_sect); sp[n].size = sector_size_mul * le32_to_cpu(p.nr_sects); sp[n].container = en + 1; n++; } else { fprintf(stderr, "dos_extd_partition: too many slices\n"); return n; } loopct = 0; } } return n; } static int is_gpt(int type) { return (type == 0xEE); } int read_dos_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct partition p; unsigned long offset = all.start; unsigned int i, n=4; unsigned char *bp; uint64_t sector_size_mul = get_sector_size(fd)/512; bp = (unsigned char *)getblock(fd, offset); if (bp == NULL) return -1; if (bp[510] != 0x55 || bp[511] != 0xaa) return -1; for (i=0; i<4; i++) { memcpy(&p, bp + 0x1be + i * sizeof (p), sizeof (p)); if (is_gpt(p.sys_type)) return 0; if (i < ns) { sp[i].start = sector_size_mul * le32_to_cpu(p.start_sect); sp[i].size = sector_size_mul * le32_to_cpu(p.nr_sects); } else { fprintf(stderr, "dos_partition: too many slices\n"); break; } if (is_extended(p.sys_type)) { /* extended partitions only get one or two sectors mapped for LILO to install, whichever is needed to have 1kb of space */ if (sector_size_mul == 1) sp[i].size = 2; else sp[i].size = sector_size_mul; n += read_extended_partition(fd, &p, i, sp+n, ns-n); } } return n; } multipath-tools-0.11.1/kpartx/dos.h000066400000000000000000000004501475246302400172140ustar00rootroot00000000000000#ifndef DOS_H_INCLUDED #define DOS_H_INCLUDED struct partition { unsigned char boot_ind; /* 0x80 - active */ unsigned char bh, bs, bc; unsigned char sys_type; unsigned char eh, es, ec; unsigned int start_sect; unsigned int nr_sects; } __attribute__((packed)); #endif /* DOS_H_INCLUDED */ multipath-tools-0.11.1/kpartx/efi.h000066400000000000000000000033631475246302400172000ustar00rootroot00000000000000/* efi.[ch] - Manipulates EFI variables as exported in /proc/efi/vars Copyright (C) 2001 Dell Computer Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef EFI_H_INCLUDED #define EFI_H_INCLUDED /* * Extensible Firmware Interface * Based on 'Extensible Firmware Interface Specification' * version 1.02, 12 December, 2000 */ #include #include typedef struct { uint8_t b[16]; } efi_guid_t; #define EFI_GUID(a,b,c,d0,d1,d2,d3,d4,d5,d6,d7) \ ((efi_guid_t) \ {{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \ (b) & 0xff, ((b) >> 8) & 0xff, \ (c) & 0xff, ((c) >> 8) & 0xff, \ (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }}) /****************************************************** * GUIDs ******************************************************/ #define NULL_GUID \ EFI_GUID( 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) static inline int efi_guidcmp(efi_guid_t left, efi_guid_t right) { return memcmp(&left, &right, sizeof (efi_guid_t)); } typedef uint16_t efi_char16_t; /* UNICODE character */ #endif /* EFI_H_INCLUDED */ multipath-tools-0.11.1/kpartx/gpt.c000066400000000000000000000425451475246302400172270ustar00rootroot00000000000000/* gpt.[ch] Copyright (C) 2000-2001 Dell Computer Corporation EFI GUID Partition Table handling Per Intel EFI Specification v1.02 http://developer.intel.com/technology/efi/efi.htm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _FILE_OFFSET_BITS 64 #include "gpt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "crc32.h" #include "kpartx.h" #if BYTE_ORDER == LITTLE_ENDIAN # define __le16_to_cpu(x) (uint16_t)(x) # define __le32_to_cpu(x) (uint32_t)(x) # define __le64_to_cpu(x) (uint64_t)(x) # define __cpu_to_le32(x) (x) #elif BYTE_ORDER == BIG_ENDIAN # define __le16_to_cpu(x) bswap_16(x) # define __le32_to_cpu(x) bswap_32(x) # define __le64_to_cpu(x) bswap_64(x) # define __cpu_to_le32(x) bswap_32(x) #endif #ifndef BLKGETLASTSECT #define BLKGETLASTSECT _IO(0x12,108) /* get last sector of block device */ #endif #ifndef BLKGETSIZE #define BLKGETSIZE _IO(0x12,96) /* return device size */ #endif #ifndef BLKSSZGET #define BLKSSZGET _IO(0x12,104) /* get block device sector size */ #endif #ifndef BLKGETSIZE64 #define BLKGETSIZE64 _IOR(0x12,114,sizeof(uint64_t)) /* return device size in bytes (u64 *arg) */ #endif struct blkdev_ioctl_param { unsigned int block; size_t content_length; char * block_contents; }; /** * efi_crc32() - EFI version of crc32 function * @buf: buffer to calculate crc32 of * @len - length of buf * * Description: Returns EFI-style CRC32 value for @buf * * This function uses the little endian Ethernet polynomial * but seeds the function with ~0, and xor's with ~0 at the end. * 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) { return (crc32(~0L, buf, len) ^ ~0L); } /** * is_pmbr_valid(): test Protective MBR for validity * @mbr: pointer to a legacy mbr structure * * Description: Returns 1 if PMBR is valid, 0 otherwise. * Validity depends on two things: * 1) MS-DOS signature is in the last two bytes of the MBR * 2) One partition of type 0xEE is found */ static int is_pmbr_valid(legacy_mbr *mbr) { int i, found = 0, signature = 0; if (!mbr) return 0; signature = (__le16_to_cpu(mbr->signature) == MSDOS_MBR_SIGNATURE); for (i = 0; signature && i < 4; i++) { if (mbr->partition[i].sys_type == EFI_PMBR_OSTYPE_EFI_GPT) { found = 1; break; } } return (signature && found); } /************************************************************ * _get_num_sectors * Requires: * - filedes is an open file descriptor, suitable for reading * Modifies: nothing * Returns: * Last LBA value on success * 0 on error * * Try getting BLKGETSIZE64 and BLKSSZGET first, * then BLKGETSIZE if necessary. * Kernels 2.4.15-2.4.18 and 2.5.0-2.5.3 have a broken BLKGETSIZE64 * which returns the number of 512-byte sectors, not the size of * the disk in bytes. Fixed in kernels 2.4.18-pre8 and 2.5.4-pre3. ************************************************************/ static uint64_t _get_num_sectors(int filedes) { int rc; uint64_t bytes=0; rc = ioctl(filedes, BLKGETSIZE64, &bytes); if (!rc) return bytes / get_sector_size(filedes); return 0; } /************************************************************ * last_lba(): return number of last logical block of device * * @fd * * Description: returns Last LBA value on success, 0 on error. * Notes: The value st_blocks gives the size of the file * in 512-byte blocks, which is OK if * EFI_BLOCK_SIZE_SHIFT == 9. ************************************************************/ static uint64_t last_lba(int filedes) { int rc; uint64_t sectors = 0; struct stat s; memset(&s, 0, sizeof (s)); rc = fstat(filedes, &s); if (rc == -1) { fprintf(stderr, "last_lba() could not stat: %s\n", strerror(errno)); return 0; } if (S_ISBLK(s.st_mode)) { sectors = _get_num_sectors(filedes); } else { fprintf(stderr, "last_lba(): I don't know how to handle files with mode %x\n", s.st_mode); sectors = 1; } return sectors ? sectors - 1 : 0; } static ssize_t read_lastoddsector(int fd, void *buffer, size_t count) { int rc; struct blkdev_ioctl_param ioctl_param; if (!buffer) return 0; ioctl_param.block = 0; /* read the last sector */ ioctl_param.content_length = count; ioctl_param.block_contents = buffer; rc = ioctl(fd, BLKGETLASTSECT, &ioctl_param); if (rc == -1) perror("read failed"); return !rc; } static ssize_t read_lba(int fd, uint64_t lba, void *buffer, size_t bytes) { int sector_size = get_sector_size(fd); off_t offset = lba * sector_size; uint64_t lastlba; ssize_t bytesread; if (lseek(fd, offset, SEEK_SET) < 0) return 0; bytesread = read(fd, buffer, bytes); lastlba = last_lba(fd); if (!lastlba) return bytesread; /* Kludge. This is necessary to read/write the last block of an odd-sized disk, until Linux 2.5.x kernel fixes. This is only used by gpt.c, and only to read one sector, so we don't have to be fancy. */ if (!bytesread && !(lastlba & 1) && lba == lastlba) { bytesread = read_lastoddsector(fd, buffer, bytes); } return bytesread; } /** * alloc_read_gpt_entries(): reads partition entries from disk * @fd is an open file descriptor to the whole disk * @gpt is a buffer into which the GPT will be put * Description: Returns ptes on success, NULL on error. * 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) { gpt_entry *pte; size_t count = __le32_to_cpu(gpt->num_partition_entries) * __le32_to_cpu(gpt->sizeof_partition_entry); if (!count) return NULL; if (aligned_malloc((void **)&pte, get_sector_size(fd), &count)) return NULL; memset(pte, 0, count); if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte, count)) { free(pte); return NULL; } return pte; } /** * alloc_read_gpt_header(): Allocates GPT header, reads into it from disk * @fd is an open file descriptor to the whole disk * @lba is the Logical Block Address of the partition table * * Description: returns GPT header on success, NULL on error. Allocates * and fills a GPT header starting at @ from @bdev. * Note: remember to free gpt when finished with it. */ static gpt_header * alloc_read_gpt_header(int fd, uint64_t lba) { gpt_header *gpt; size_t size = sizeof (gpt_header); if (aligned_malloc((void **)&gpt, get_sector_size(fd), &size)) return NULL; memset(gpt, 0, size); if (!read_lba(fd, lba, gpt, size)) { free(gpt); return NULL; } return gpt; } /** * is_gpt_valid() - tests one GPT header and PTEs for validity * @fd is an open file descriptor to the whole disk * @lba is the logical block address of the GPT header to test * @gpt is a GPT header ptr, filled on return. * @ptes is a PTEs ptr, filled on return. * * 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) { int rc = 0; /* default to not valid */ uint32_t crc, origcrc; if (!gpt || !ptes) return 0; if (!(*gpt = alloc_read_gpt_header(fd, lba))) return 0; /* Check the GUID Partition Table signature */ if (__le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) { /* printf("GUID Partition Table Header signature is wrong: %" PRIx64" != %" PRIx64 "\n", __le64_to_cpu((*gpt)->signature), GUID_PT_HEADER_SIGNATURE); */ free(*gpt); *gpt = NULL; return rc; } /* Check the GUID Partition Table Header CRC */ origcrc = __le32_to_cpu((*gpt)->header_crc32); (*gpt)->header_crc32 = 0; crc = efi_crc32(*gpt, __le32_to_cpu((*gpt)->header_size)); if (crc != origcrc) { // printf( "GPTH CRC check failed, %x != %x.\n", origcrc, crc); (*gpt)->header_crc32 = __cpu_to_le32(origcrc); free(*gpt); *gpt = NULL; return 0; } (*gpt)->header_crc32 = __cpu_to_le32(origcrc); /* Check that the my_lba entry points to the LBA * that contains the GPT we read */ if (__le64_to_cpu((*gpt)->my_lba) != lba) { /* printf( "my_lba % PRIx64 "x != lba %"PRIx64 "x.\n", __le64_to_cpu((*gpt)->my_lba), lba); */ free(*gpt); *gpt = NULL; return 0; } /* Check that sizeof_partition_entry has the correct value */ if (__le32_to_cpu((*gpt)->sizeof_partition_entry) != sizeof(gpt_entry)) { // printf("GUID partition entry size check failed.\n"); free(*gpt); *gpt = NULL; 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"); 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 * @agpt is the alternate GPT header * @lastlba is the last LBA number * Description: Returns nothing. Sanity checks pgpt and agpt fields * and prints warnings on discrepancies. * */ static void compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba) { int error_found = 0; if (!pgpt || !agpt) return; if (__le64_to_cpu(pgpt->my_lba) != __le64_to_cpu(agpt->alternate_lba)) { error_found++; fprintf(stderr, "GPT:Primary header LBA != Alt. header alternate_lba\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(pgpt->my_lba), __le64_to_cpu(agpt->alternate_lba)); #endif } if (__le64_to_cpu(pgpt->alternate_lba) != __le64_to_cpu(agpt->my_lba)) { error_found++; fprintf(stderr, "GPT:Primary header alternate_lba != Alt. header my_lba\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(pgpt->alternate_lba), __le64_to_cpu(agpt->my_lba)); #endif } if (__le64_to_cpu(pgpt->first_usable_lba) != __le64_to_cpu(agpt->first_usable_lba)) { error_found++; fprintf(stderr, "GPT:first_usable_lbas don't match.\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(pgpt->first_usable_lba), __le64_to_cpu(agpt->first_usable_lba)); #endif } if (__le64_to_cpu(pgpt->last_usable_lba) != __le64_to_cpu(agpt->last_usable_lba)) { error_found++; fprintf(stderr, "GPT:last_usable_lbas don't match.\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(pgpt->last_usable_lba), __le64_to_cpu(agpt->last_usable_lba)); #endif } if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) { error_found++; fprintf(stderr, "GPT:disk_guids don't match.\n"); } if (__le32_to_cpu(pgpt->num_partition_entries) != __le32_to_cpu(agpt->num_partition_entries)) { error_found++; fprintf(stderr, "GPT:num_partition_entries don't match: " "0x%x != 0x%x\n", __le32_to_cpu(pgpt->num_partition_entries), __le32_to_cpu(agpt->num_partition_entries)); } if (__le32_to_cpu(pgpt->sizeof_partition_entry) != __le32_to_cpu(agpt->sizeof_partition_entry)) { error_found++; fprintf(stderr, "GPT:sizeof_partition_entry values don't match: " "0x%x != 0x%x\n", __le32_to_cpu(pgpt->sizeof_partition_entry), __le32_to_cpu(agpt->sizeof_partition_entry)); } if (__le32_to_cpu(pgpt->partition_entry_array_crc32) != __le32_to_cpu(agpt->partition_entry_array_crc32)) { error_found++; fprintf(stderr, "GPT:partition_entry_array_crc32 values don't match: " "0x%x != 0x%x\n", __le32_to_cpu(pgpt->partition_entry_array_crc32), __le32_to_cpu(agpt->partition_entry_array_crc32)); } if (__le64_to_cpu(pgpt->alternate_lba) != lastlba) { error_found++; fprintf(stderr, "GPT:Primary header thinks Alt. header is not at the end of the disk.\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(pgpt->alternate_lba), lastlba); #endif } if (__le64_to_cpu(agpt->my_lba) != lastlba) { error_found++; fprintf(stderr, "GPT:Alternate GPT header not at the end of the disk.\n"); #ifdef DEBUG fprintf(stderr, "GPT:%" PRIx64 " != %" PRIx64 "\n", __le64_to_cpu(agpt->my_lba), lastlba); #endif } if (error_found) fprintf(stderr, "GPT: Use GNU Parted to correct GPT errors.\n"); return; } /** * find_valid_gpt() - Search disk for valid GPT headers and PTEs * @fd is an open file descriptor to the whole disk * @gpt is a GPT header ptr, filled on return. * @ptes is a PTEs ptr, filled on return. * Description: Returns 1 if valid, 0 on error. * If valid, returns pointers to newly allocated GPT header and PTEs. * 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) { int good_pgpt = 0, good_agpt = 0, good_pmbr = 0; gpt_header *pgpt = NULL, *agpt = NULL; gpt_entry *pptes = NULL, *aptes = NULL; legacy_mbr *legacymbr = NULL; size_t size = sizeof(legacy_mbr); uint64_t lastlba; if (!gpt || !ptes) return 0; if (!(lastlba = last_lba(fd))) return 0; good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA, &pgpt, &pptes); if (good_pgpt) { good_agpt = is_gpt_valid(fd, __le64_to_cpu(pgpt->alternate_lba), &agpt, &aptes); if (!good_agpt) { good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes); } } else { good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes); } /* The obviously unsuccessful case */ if (!good_pgpt && !good_agpt) { goto fail; } /* This will be added to the EFI Spec. per Intel after v1.02. */ if (aligned_malloc((void **)&legacymbr, get_sector_size(fd), &size) == 0) { memset(legacymbr, 0, size); read_lba(fd, 0, (uint8_t *) legacymbr, size); good_pmbr = is_pmbr_valid(legacymbr); free(legacymbr); legacymbr=NULL; } /* Failure due to bad PMBR */ if ((good_pgpt || good_agpt) && !good_pmbr && !force_gpt) { fprintf(stderr, " Warning: Disk has a valid GPT signature " "but invalid PMBR.\n" " Assuming this disk is *not* a GPT disk anymore.\n" " Use gpt kernel option to override. " "Use GNU Parted to correct disk.\n"); goto fail; } /* Would fail due to bad PMBR, but force GPT anyhow */ if ((good_pgpt || good_agpt) && !good_pmbr && force_gpt) { fprintf(stderr, " Warning: Disk has a valid GPT signature but " "invalid PMBR.\n" " Use GNU Parted to correct disk.\n" " gpt option taken, disk treated as GPT.\n"); } compare_gpts(pgpt, agpt, lastlba); /* The good cases */ if (good_pgpt && (good_pmbr || force_gpt)) { *gpt = pgpt; *ptes = pptes; if (agpt) { free(agpt); agpt = NULL; } if (aptes) { free(aptes); aptes = NULL; } if (!good_agpt) { fprintf(stderr, "Alternate GPT is invalid, " "using primary GPT.\n"); } return 1; } else if (good_agpt && (good_pmbr || force_gpt)) { *gpt = agpt; *ptes = aptes; if (pgpt) { free(pgpt); pgpt = NULL; } if (pptes) { free(pptes); pptes = NULL; } fprintf(stderr, "Primary GPT is invalid, using alternate GPT.\n"); return 1; } fail: if (pgpt) { free(pgpt); pgpt=NULL; } if (agpt) { free(agpt); agpt=NULL; } if (pptes) { free(pptes); pptes=NULL; } if (aptes) { free(aptes); aptes=NULL; } *gpt = NULL; *ptes = NULL; return 0; } /** * read_gpt_pt() * @fd * @all - slice with start/size of whole disk * * 0 if this isn't our partition table * number of partitions if successful * */ int read_gpt_pt (int fd, __attribute__((unused)) struct slice all, struct slice *sp, unsigned int ns) { gpt_header *gpt = NULL; gpt_entry *ptes = NULL; unsigned int i; int n = 0; int last_used_index = -1; int sector_size_mul = get_sector_size(fd)/512; if (!find_valid_gpt (fd, &gpt, &ptes) || !gpt || !ptes) { if (gpt) free (gpt); if (ptes) free (ptes); return 0; } for (i = 0; i < __le32_to_cpu(gpt->num_partition_entries) && i < ns; i++) { if (!efi_guidcmp (NULL_GUID, ptes[i].partition_type_guid)) { sp[n].start = 0; sp[n].size = 0; n++; } else { sp[n].start = sector_size_mul * __le64_to_cpu(ptes[i].starting_lba); sp[n].size = sector_size_mul * (__le64_to_cpu(ptes[i].ending_lba) - __le64_to_cpu(ptes[i].starting_lba) + 1); last_used_index=n; n++; } } free (ptes); free (gpt); return last_used_index+1; } multipath-tools-0.11.1/kpartx/gpt.h000066400000000000000000000061651475246302400172320ustar00rootroot00000000000000/* gpt.[ch] Copyright (C) 2000-2001 Dell Computer Corporation EFI GUID Partition Table handling Per Intel EFI Specification v1.02 http://developer.intel.com/technology/efi/efi.htm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GPT_H_INCLUDED #define GPT_H_INCLUDED #include #include "kpartx.h" #include "dos.h" #include "efi.h" #define EFI_PMBR_OSTYPE_EFI 0xEF #define EFI_PMBR_OSTYPE_EFI_GPT 0xEE #define MSDOS_MBR_SIGNATURE 0xaa55 #define GPT_BLOCK_SIZE 512 #define GPT_HEADER_SIGNATURE 0x5452415020494645ULL #define GPT_HEADER_REVISION_V1_02 0x00010200 #define GPT_HEADER_REVISION_V1_00 0x00010000 #define GPT_HEADER_REVISION_V0_99 0x00009900 #define GPT_PRIMARY_PARTITION_TABLE_LBA 1 typedef struct _gpt_header { uint64_t signature; uint32_t revision; uint32_t header_size; uint32_t header_crc32; uint32_t reserved1; uint64_t my_lba; uint64_t alternate_lba; uint64_t first_usable_lba; uint64_t last_usable_lba; efi_guid_t disk_guid; uint64_t partition_entry_lba; uint32_t num_partition_entries; uint32_t sizeof_partition_entry; uint32_t partition_entry_array_crc32; uint8_t reserved2[GPT_BLOCK_SIZE - 92]; } __attribute__ ((packed)) gpt_header; typedef struct _gpt_entry_attributes { uint64_t required_to_function:1; uint64_t reserved:47; uint64_t type_guid_specific:16; } __attribute__ ((packed)) gpt_entry_attributes; typedef struct _gpt_entry { efi_guid_t partition_type_guid; efi_guid_t unique_partition_guid; uint64_t starting_lba; uint64_t ending_lba; gpt_entry_attributes attributes; efi_char16_t partition_name[72 / sizeof(efi_char16_t)]; } __attribute__ ((packed)) gpt_entry; /* These values are only defaults. The actual on-disk structures may define different sizes, so use those unless creating a new GPT disk! */ #define GPT_DEFAULT_RESERVED_PARTITION_ENTRY_ARRAY_SIZE 16384 /* Number of actual partition entries should be calculated as: */ #define GPT_DEFAULT_RESERVED_PARTITION_ENTRIES \ (GPT_DEFAULT_RESERVED_PARTITION_ENTRY_ARRAY_SIZE / \ sizeof(gpt_entry)) /* Protected Master Boot Record & Legacy MBR share same structure */ /* Needs to be packed because the u16s force misalignment. */ typedef struct _legacy_mbr { uint8_t bootcode[440]; uint32_t unique_mbr_signature; uint16_t unknown; struct partition partition[4]; uint16_t signature; } __attribute__ ((packed)) legacy_mbr; #define EFI_GPT_PRIMARY_PARTITION_TABLE_LBA 1 /* Functions */ int read_gpt_pt (int fd, struct slice all, struct slice *sp, unsigned int ns); #endif multipath-tools-0.11.1/kpartx/kpartx.8000066400000000000000000000064041475246302400176650ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH KPARTX 8 2019-04-27 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . kpartx \- Create device maps from partition tables. . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B kpartx .RB [\| \-a | \-d | \-u | \-l \|] .RB [\| \-r \|] .RB [\| \-p \|] .RB [\| \-f \|] .RB [\| \-g \|] .RB [\| \-s | \-n \|] .RB [\| \-v \|] .B wholedisk . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . This tool, derived from util-linux' partx, reads partition tables on specified device and create device maps over partitions segments detected. It is called from hotplug upon device maps creation and deletion. . . .\" ---------------------------------------------------------------------------- .SH OPTIONS .\" ---------------------------------------------------------------------------- . .TP .B \-a Add partition mappings. . .TP .B \-d Delete partition mappings. . .TP .B \-u Update partition mappings. . .TP .B \-l List partition mappings that would be added \-a. . .TP .B \-r Read-only partition mappings. . .TP .B \-p Set device name-partition number delimiter. . .TP .B \-f Force creation of mappings; overrides 'no_partitions' feature. . .TP .B \-g Force GUID partition table (GPT). . .TP .B \-s Sync mode (Default). Don't return until the partitions are created. . .TP .B \-n Nosync mode. Return before the partitions are created. . .TP .B \-v Operate verbosely. . . .\" ---------------------------------------------------------------------------- .SH EXAMPLE .\" ---------------------------------------------------------------------------- . To mount all the partitions in a raw disk image: .IP kpartx \-av disk.img .PP This will output lines such as: .IP add map loop1p1 (254:4): 0 409597 linear 7:1 3 .PP The \fIloop1p1\fR is the name of a device file under \fI/dev/mapper\fR which you can use to access the partition, for example to fsck it: .IP fsck /dev/mapper/loop1p1 .PP When you're done, you need to remove the devices: .IP kpartx \-d disk.img . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR multipath (8) .BR multipathd (8) .BR hotplug (8) . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . This man page was assembled By Patrick Caulfield for the Debian project. .PP \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/kpartx/kpartx.c000066400000000000000000000373451475246302400177500ustar00rootroot00000000000000/* * Source: copy of util-linux' partx partx.c * * Copyrights of the original file applies * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda * Copyright (c) 2005 Lars Soltau */ /* * Given a block device and a partition table type, * try to parse the partition table, and list the * contents. Optionally add or remove partitions. * * Read wholedisk and add all partitions: * kpartx [-a|-d|-l] [-v] wholedisk * * aeb, 2000-03-21 * cva, 2002-10-26 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "autoconfig.h" #include "devmapper.h" #include "crc32.h" #include "lopart.h" #include "kpartx.h" #include "version.h" #define SIZE(a) (sizeof(a)/sizeof((a)[0])) #define MAXTYPES 64 #define MAXSLICES 256 #define DM_TARGET "linear" #define LO_NAME_SIZE 64 #define PARTNAME_SIZE 128 #define DELIM_SIZE 8 struct slice slices[MAXSLICES]; enum action { LIST, ADD, DELETE, UPDATE }; struct pt { char *type; ptreader *fn; } pts[MAXTYPES]; int ptct = 0; int udev_sync = 1; static void addpts(char *t, ptreader f) { if (ptct >= MAXTYPES) { fprintf(stderr, "addpts: too many types\n"); exit(1); } pts[ptct].type = t; pts[ptct].fn = f; ptct++; } static void initpts(void) { addpts("gpt", read_gpt_pt); addpts("dos", read_dos_pt); addpts("bsd", read_bsd_pt); addpts("solaris", read_solaris_pt); addpts("unixware", read_unixware_pt); addpts("dasd", read_dasd_pt); addpts("mac", read_mac_pt); addpts("sun", read_sun_pt); addpts("ps3", read_ps3_pt); } static char short_opts[] = "rladfgvp:t:snu"; /* Used in gpt.c */ int force_gpt=0; int force_devmap=0; static int usage(void) { printf(VERSION_STRING); printf("Usage:\n"); printf(" kpartx [-a|-d|-u|-l] [-r] [-p] [-f] [-g] [-s|-n] [-v] wholedisk\n"); printf("\t-a add partition devmappings\n"); printf("\t-r devmappings will be readonly\n"); printf("\t-d del partition devmappings\n"); printf("\t-u update partition devmappings\n"); printf("\t-l list partitions devmappings that would be added by -a\n"); printf("\t-p set device name-partition number delimiter\n"); printf("\t-g force GUID partition table (GPT)\n"); printf("\t-f force devmap create\n"); printf("\t-v verbose\n"); printf("\t-n nosync mode. Return before the partitions are created\n"); printf("\t-s sync mode (Default). Don't return until the partitions are created\n"); return 1; } static void set_delimiter (char * device, char * delimiter) { char * p = device; if (*p == 0x0) return; while (*(++p) != 0x0) continue; if (isdigit(*(p - 1))) *delimiter = 'p'; } static int find_devname_offset (char * device) { char *p, *q; q = p = device; while (*p) { if (*p == '/') q = p + 1; p++; } return (int)(q - device); } static char * get_hotplug_device(void) { unsigned int major, minor, off, len; char *mapname; char *devname = NULL; char *device = NULL; char *var = NULL; struct stat buf; var = getenv("ACTION"); if (!var || strcmp(var, "add")) return NULL; /* Get dm mapname for hotpluged device. */ if (!(devname = getenv("DEVNAME"))) return NULL; if (stat(devname, &buf)) return NULL; major = major(buf.st_rdev); minor = minor(buf.st_rdev); if (!(mapname = dm_mapname(major, minor))) /* Not dm device. */ return NULL; off = find_devname_offset(devname); len = strlen(mapname); /* Dirname + mapname + \0 */ if (!(device = (char *)malloc(sizeof(char) * (off + len + 1)))) { free(mapname); return NULL; } /* Create new device name. */ snprintf(device, off + 1, "%s", devname); snprintf(device + off, len + 1, "%s", mapname); if (strlen(device) != (off + len)) { free(device); free(mapname); return NULL; } free(mapname); return device; } static int check_uuid(char *uuid, char *part_uuid, char **err_msg) { char *map_uuid = strchr(part_uuid, '-'); if (!map_uuid || strncmp(part_uuid, "part", 4) != 0) { *err_msg = "not a kpartx partition"; return -1; } map_uuid++; if (strcmp(uuid, map_uuid) != 0) { *err_msg = "a partition of a different device"; return -1; } return 0; } static void * xmalloc (size_t size) { void *t; if (size == 0) return NULL; t = malloc (size); if (t == NULL) { fprintf(stderr, "Out of memory\n"); exit(1); } return t; } int main(int argc, char **argv){ int i, j, m, n, op, off, arg, c, d, ro=0; int fd = -1; struct slice all; struct pt *ptp; enum action what = LIST; char *type, *diskdevice, *device, *progname; int verbose = 0; char partname[PARTNAME_SIZE], params[PARTNAME_SIZE + 16]; char * loopdev = NULL; char * delim = NULL; char *uuid = NULL; char *mapname = NULL; int hotplug = 0; int loopcreated = 0; struct stat buf; initpts(); init_crc32(); type = device = diskdevice = NULL; memset(&all, 0, sizeof(all)); memset(&partname, 0, sizeof(partname)); /* Check whether hotplug mode. */ progname = strrchr(argv[0], '/'); if (!progname) progname = argv[0]; else progname++; if (!strcmp(progname, "kpartx.dev")) { /* Hotplug mode */ hotplug = 1; /* Setup for original kpartx variables */ if (!(device = get_hotplug_device())) exit(1); diskdevice = device; what = ADD; } else if (argc < 2) { usage(); exit(1); } while ((arg = getopt(argc, argv, short_opts)) != EOF) switch(arg) { case 'r': ro=1; break; case 'f': force_devmap=1; break; case 'g': force_gpt=1; break; case 't': type = optarg; break; case 'v': verbose = 1; break; case 'p': delim = optarg; break; case 'l': what = LIST; break; case 'a': what = ADD; break; case 'd': what = DELETE; break; case 's': udev_sync = 1; break; case 'n': udev_sync = 0; break; case 'u': what = UPDATE; break; default: usage(); exit(1); } #ifdef LIBDM_API_COOKIE if (!udev_sync) dm_udev_set_sync_support(0); else dm_udev_set_sync_support(1); #endif if (dm_prereq(DM_TARGET, 0, 0, 0) && (what == ADD || what == DELETE || what == UPDATE)) { fprintf(stderr, "device mapper prerequisites not met\n"); exit(1); } if (hotplug) { /* already got [disk]device */ } else if (optind == argc-2) { device = argv[optind]; diskdevice = argv[optind+1]; } else if (optind == argc-1) { diskdevice = device = argv[optind]; } else { usage(); exit(1); } if (stat(device, &buf)) { printf("failed to stat() %s\n", device); exit (1); } if (S_ISREG (buf.st_mode)) { /* already looped file ? */ char rpath[PATH_MAX]; if (realpath(device, rpath) == NULL) { fprintf(stderr, "Error: %s: %s\n", device, strerror(errno)); exit (1); } loopdev = find_loop_by_file(rpath); if (!loopdev && what == DELETE) exit (0); if (!loopdev) { if (set_loop(&loopdev, rpath, 0, &ro)) { fprintf(stderr, "can't set up loop\n"); exit (1); } loopcreated = 1; } device = loopdev; if (stat(device, &buf)) { printf("failed to stat() %s\n", device); exit (1); } } else if (!S_ISBLK(buf.st_mode)) { fprintf(stderr, "invalid device: %s\n", device); exit(1); } off = find_devname_offset(device); if (!loopdev) { mapname = dm_mapname(major(buf.st_rdev), minor(buf.st_rdev)); if (mapname) uuid = dm_mapuuid(mapname); } /* * We are called for a non-DM device. * Make up a fake UUID for the device, unless "-d -f" is given. * This allows deletion of partitions created with older kpartx * versions which didn't use the fake UUID during creation. */ if (!uuid && !(what == DELETE && force_devmap)) uuid = nondm_create_uuid(buf.st_rdev); if (!mapname) mapname = device + off; if (delim == NULL) { delim = xmalloc(DELIM_SIZE); memset(delim, 0, DELIM_SIZE); set_delimiter(mapname, delim); } fd = open(device, O_RDONLY | O_DIRECT); if (fd == -1) { perror(device); exit(1); } /* add/remove partitions to the kernel devmapper tables */ int r = 0; if (what == DELETE) { r = dm_remove_partmaps(mapname, uuid, buf.st_rdev, verbose); if (loopdev) { if (del_loop(loopdev)) { if (verbose) fprintf(stderr, "can't del loop : %s\n", loopdev); r = 1; } else if (verbose) fprintf(stderr, "loop deleted : %s\n", loopdev); } goto end; } for (i = 0; i < ptct; i++) { ptp = &pts[i]; if (type && strcmp(type, ptp->type)) continue; /* here we get partitions */ n = ptp->fn(fd, all, slices, SIZE(slices)); #ifdef DEBUG if (n >= 0) printf("%s: %d slices\n", ptp->type, n); #endif if (n <= 0) continue; switch(what) { case LIST: for (j = 0, c = 0, m = 0; j < n; j++) { if (slices[j].size == 0) continue; if (slices[j].container > 0) { c++; continue; } slices[j].minor = m++; printf("%s%s%d : 0 %" PRIu64 " %s %" PRIu64"\n", mapname, delim, j+1, slices[j].size, device, slices[j].start); } /* Loop to resolve contained slices */ d = c; while (c) { for (j = 0; j < n; j++) { uint64_t start; int k = slices[j].container - 1; if (slices[j].size == 0) continue; if (slices[j].minor > 0) continue; if (slices[j].container == 0) continue; slices[j].minor = m++; start = slices[j].start - slices[k].start; printf("%s%s%d : 0 %" PRIu64 " /dev/dm-%d %" PRIu64 "\n", mapname, delim, j+1, slices[j].size, slices[k].minor, start); c--; } /* Terminate loop if nothing more to resolve */ if (d == c) break; } break; case ADD: 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; if (slices[j].size == 0) continue; /* Skip all contained slices */ if (slices[j].container > 0) { c++; continue; } if (safe_sprintf(params, "%d:%d %" PRIu64 , major(buf.st_rdev), minor(buf.st_rdev), slices[j].start)) { fprintf(stderr, "params too small\n"); exit(1); } op = (dm_find_part(mapname, delim, j + 1, uuid, partname, sizeof(partname), &part_uuid, verbose) ? DM_DEVICE_RELOAD : DM_DEVICE_CREATE); if (part_uuid && uuid) { if (check_uuid(uuid, part_uuid, &reason) != 0) { fprintf(stderr, "%s is already in use, and %s\n", partname, reason); r++; free(part_uuid); continue; } free(part_uuid); } if (!dm_addmap(op, partname, DM_TARGET, params, slices[j].size, ro, uuid, j+1, buf.st_mode & 0777, buf.st_uid, buf.st_gid)) { fprintf(stderr, "create/reload failed on %s\n", partname); r++; continue; } if (op == DM_DEVICE_RELOAD && !dm_simplecmd(DM_DEVICE_RESUME, partname, 1, MPATH_UDEV_RELOAD_FLAG)) { fprintf(stderr, "resume failed on %s\n", partname); r++; continue; } dm_devn(partname, &slices[j].major, &slices[j].minor); if (verbose) printf("add map %s (%d:%d): 0 %" PRIu64 " %s %s\n", partname, slices[j].major, slices[j].minor, slices[j].size, DM_TARGET, params); } /* Loop to resolve contained slices */ d = c; while (c) { for (j = 0; j < n; j++) { char *part_uuid, *reason; int k = slices[j].container - 1; if (slices[j].size == 0) continue; /* Skip all existing slices */ if (slices[j].minor > 0) continue; /* Skip all simple slices */ if (slices[j].container == 0) continue; /* Check container slice */ if (slices[k].size == 0) fprintf(stderr, "Invalid slice %d\n", k); if (safe_sprintf(params, "%d:%d %" PRIu64, major(buf.st_rdev), minor(buf.st_rdev), slices[j].start)) { fprintf(stderr, "params too small\n"); exit(1); } op = (dm_find_part(mapname, delim, j + 1, uuid, partname, sizeof(partname), &part_uuid, verbose) ? DM_DEVICE_RELOAD : DM_DEVICE_CREATE); if (part_uuid && uuid) { if (check_uuid(uuid, part_uuid, &reason) != 0) { fprintf(stderr, "%s is already in use, and %s\n", partname, reason); free(part_uuid); continue; } free(part_uuid); } dm_addmap(op, partname, DM_TARGET, params, slices[j].size, ro, uuid, j+1, buf.st_mode & 0777, buf.st_uid, buf.st_gid); if (op == DM_DEVICE_RELOAD) dm_simplecmd(DM_DEVICE_RESUME, partname, 1, MPATH_UDEV_RELOAD_FLAG); dm_devn(partname, &slices[j].major, &slices[j].minor); if (verbose) printf("add map %s (%d:%d): 0 %" PRIu64 " %s %s\n", partname, slices[j].major, slices[j].minor, slices[j].size, DM_TARGET, params); c--; } /* Terminate loop */ if (d == c) break; } if (what == ADD) { /* Skip code that removes devmappings for deleted partitions */ break; } 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)) continue; if (part_uuid && uuid) { if (check_uuid(uuid, part_uuid, &reason) != 0) { fprintf(stderr, "%s is %s. Not removing\n", partname, reason); free(part_uuid); continue; } free(part_uuid); } if (!dm_simplecmd(DM_DEVICE_REMOVE, partname, 1, 0)) { fprintf(stderr, "failed to remove %s", partname); r++; continue; } if (verbose) printf("del devmap : %s\n", partname); } default: break; } if (n > 0) break; } if (fd != -1) close(fd); if (what == LIST && loopcreated) { if (del_loop(device)) { if (verbose) fprintf(stderr, "can't del loop : %s\n", device); exit(1); } if (verbose) fprintf(stderr, "loop deleted : %s\n", device); } end: dm_lib_exit(); return r; } /* * sseek: seek to specified sector */ static int sseek(int fd, unsigned int secnr, int secsz) { off64_t in, out; in = ((off64_t) secnr * secsz); out = 1; if ((out = lseek64(fd, in, SEEK_SET)) != in) { fprintf(stderr, "llseek error\n"); return -1; } return 0; } int aligned_malloc(void **mem_p, size_t align, size_t *size_p) { static size_t pgsize = 0; size_t size; int err; if (!mem_p || !align || (size_p && !*size_p)) return EINVAL; if (!pgsize) pgsize = getpagesize(); if (size_p) size = ((*size_p + align - 1) / align) * align; else size = pgsize; err = posix_memalign(mem_p, pgsize, size); if (!err && size_p) *size_p = size; return err; } /* always in sector size blocks */ static struct block { unsigned int secnr; char *block; struct block *next; } *blockhead; /* blknr is always in 512 byte blocks */ char * getblock (int fd, unsigned int blknr) { int secsz = get_sector_size(fd); unsigned int blks_per_sec = secsz / 512; unsigned int secnr = blknr / blks_per_sec; unsigned int blk_off = (blknr % blks_per_sec) * 512; struct block *bp; for (bp = blockhead; bp; bp = bp->next) if (bp->secnr == secnr) return bp->block + blk_off; if (sseek(fd, secnr, secsz)) return NULL; bp = xmalloc(sizeof(struct block)); bp->secnr = secnr; bp->next = blockhead; blockhead = bp; if (aligned_malloc((void **)&bp->block, secsz, NULL)) { fprintf(stderr, "aligned_malloc failed\n"); exit(1); } if (read(fd, bp->block, secsz) != secsz) { fprintf(stderr, "read error, sector %d\n", secnr); blockhead = bp->next; free(bp->block); free(bp); return NULL; } return bp->block + blk_off; } int get_sector_size(int filedes) { int rc, sector_size = 512; rc = ioctl(filedes, BLKSSZGET, §or_size); if (rc) sector_size = 512; return sector_size; } multipath-tools-0.11.1/kpartx/kpartx.h000066400000000000000000000033351475246302400177450ustar00rootroot00000000000000#ifndef KPARTX_H_INCLUDED #define KPARTX_H_INCLUDED #include #include #include /* * For each partition type there is a routine that takes * a block device and a range, and returns the list of * slices found there in the supplied array SP that can * hold NS entries. The return value is the number of * entries stored, or -1 if the appropriate type is not * present. */ #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define safe_snprintf(var, size, format, args...) \ ({ \ size_t __size = size; \ int __ret; \ \ __ret = snprintf(var, __size, format, ##args); \ __ret < 0 || (size_t)__ret >= __size; \ }) #define safe_sprintf(var, format, args...) \ safe_snprintf(var, sizeof(var), format, ##args) #ifndef BLKSSZGET #define BLKSSZGET _IO(0x12,104) /* get block device sector size */ #endif int get_sector_size(int filedes); /* * units: 512 byte sectors */ struct slice { uint64_t start; uint64_t size; int container; unsigned int major; unsigned int minor; }; typedef int (ptreader)(int fd, struct slice all, struct slice *sp, unsigned int ns); extern int force_gpt; extern ptreader read_dos_pt; extern ptreader read_bsd_pt; extern ptreader read_solaris_pt; extern ptreader read_unixware_pt; extern ptreader read_gpt_pt; extern ptreader read_dasd_pt; extern ptreader read_mac_pt; extern ptreader read_sun_pt; extern ptreader read_ps3_pt; int aligned_malloc(void **mem_p, size_t align, size_t *size_p); char *getblock(int fd, unsigned int secnr); static inline unsigned int four2int(unsigned char *p) { return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24); } #endif /* KPARTX_H_INCLUDED */ multipath-tools-0.11.1/kpartx/kpartx.rules.in000066400000000000000000000027761475246302400212650ustar00rootroot00000000000000# # persistent links for device-mapper devices # only hardware-backed device-mapper devices (ie multipath, dmraid, # and kpartx) have meaningful persistent device names # KERNEL!="dm-*", GOTO="kpartx_end" ACTION!="add|change", GOTO="kpartx_end" ENV{DM_UUID}!="?*", GOTO="kpartx_end" ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", GOTO="kpartx_end" # Create dm tables for partitions on multipath devices. ENV{DM_UUID}!="mpath-?*", GOTO="mpath_kpartx_end" # Ignore RAID members ENV{ID_FS_TYPE}=="linux_raid_member|isw_raid_member|ddf_raid_member", GOTO="mpath_kpartx_end" # DM_SUBSYSTEM_UDEV_FLAG1 is the "skip_kpartx" flag. # For events not generated by libdevmapper, we need to fetch it from db: # - "change" events with DM_ACTIVATION!="1" (e.g. partition table changes) # - "add" events for which rules are not disabled ("coldplug" case) ENV{DM_ACTIVATION}!="1", IMPORT{db}="DM_SUBSYSTEM_UDEV_FLAG1" ACTION=="add", IMPORT{db}="DM_SUBSYSTEM_UDEV_FLAG1" ENV{DM_SUBSYSTEM_UDEV_FLAG1}=="1", GOTO="mpath_kpartx_end" # 11-dm-mpath.rules sets MPATH_UNCHANGED for events that can be ignored. ENV{MPATH_UNCHANGED}=="1", GOTO="mpath_kpartx_end" # Don't run kpartx now if we know it will fail or hang. # This is required for device mapper rules v2 compatibility. ENV{DM_NOSCAN}=="1", GOTO="mpath_kpartx_end" # Run kpartx GOTO="run_kpartx" LABEL="mpath_kpartx_end" ## Code for other subsystems (non-multipath) could be placed here ## GOTO="kpartx_end" LABEL="run_kpartx" RUN+="@BINDIR@/kpartx -un -p -part /dev/$name" LABEL="kpartx_end" multipath-tools-0.11.1/kpartx/kpartx_id000077500000000000000000000046361475246302400202030ustar00rootroot00000000000000#!/bin/sh # # kpartx_id # # Generates ID information for device-mapper tables. # # Copyright (C) 2006 SUSE Linux Products GmbH # Author: # Hannes Reinecke # # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation version 2 of the License. # # This script generates ID information used to generate persistent symlinks. # It relies on the UUID strings generated by the various programs; the name # of the tables are of no consequence. # # Please note that dmraid does not provide the UUIDs (yet); a patch has been # sent upstream but has not been accepted yet. # MAJOR=$1 MINOR=$2 UUID=$3 if [ -z "$MAJOR" -o -z "$MINOR" ]; then echo "usage: $0 major minor UUID" exit 1; fi DMSETUP=$(command -v dmsetup) || DMSETUP=/sbin/dmsetup # Device-mapper not installed; not an error if [ ! -x "$DMSETUP" ] ; then echo "$0: dmsetup not found" >&2 exit 0 fi # Table UUIDs are always '-'. dmuuid=${UUID#*-} dmtbl=${UUID%%-*} dmpart=${dmtbl#part} dmserial= # kpartx types are 'part' if [ "$dmpart" = "$dmtbl" ] ; then dmpart= else dmtbl=part fi # Set the name of the table. We're only interested in dmraid, # multipath, and kpartx tables; everything else is ignored. if [ "$dmtbl" = "part" ] ; then dmname=$($DMSETUP info -c --noheadings -o name -u $dmuuid) echo "DM_MPATH=$dmname" # We need the dependencies of the parent table to figure out # the type if the parent is a multipath table case "$dmuuid" in mpath-*) dmdeps=$($DMSETUP deps -u $dmuuid) dmserial=${dmuuid#mpath-} ;; esac elif [ "$dmtbl" = "mpath" ] ; then dmname="$dmuuid" dmserial="$dmuuid" # We need the dependencies of the table to figure out the type dmdeps=$($DMSETUP deps -u $UUID) fi [ -n "$dmpart" ] && echo "DM_PART=$dmpart" # Figure out the type of the map. For non-multipath maps it's # always 'raid'. if [ -n "$dmdeps" ] ; then case "$dmdeps" in *\(94,*) echo "DM_TYPE=ccw" ;; *\(104,* | *\(105,* | *\(106,* | *\(107,* | *\(108,* | *\(109,* | *\(110,* | *\(112,*) echo "DM_TYPE=cciss" ;; *\(9*) echo "DM_TYPE=raid" ;; *) echo "DM_TYPE=scsi" echo "DM_WWN=0x${dmserial#?}" ;; esac else echo "DM_TYPE=raid" fi if [ -n "$dmserial" ]; then echo "DM_SERIAL=$dmserial" fi exit 0 multipath-tools-0.11.1/kpartx/lopart.c000066400000000000000000000150371475246302400177320ustar00rootroot00000000000000/* Taken from Ted's losetup.c - Mitch */ /* Added vfs mount options - aeb - 960223 */ /* Removed lomount - aeb - 960224 */ /* 1999-02-22 Arkadiusz MiÅ›kiewicz * - added Native Language Support * Sun Mar 21 1999 - Arnaldo Carvalho de Melo * - fixed strerr(errno) in gettext calls */ #define PROC_DEVICES "/proc/devices" /* * losetup.c - setup and control loop devices */ #include "kpartx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lopart.h" #include "xstrncpy.h" #ifndef LOOP_CTL_GET_FREE #define LOOP_CTL_GET_FREE 0x4C82 #endif #define SIZE(a) (sizeof(a)/sizeof(a[0])) char *find_loop_by_file(const char *filename) { DIR *dir; struct dirent *dent; char dev[64], *found = NULL, *p; int fd, bytes_read; struct stat statbuf; struct loop_info loopinfo; const char VIRT_BLOCK[] = "/sys/devices/virtual/block"; char path[PATH_MAX]; char bf_path[PATH_MAX]; char backing_file[PATH_MAX]; dir = opendir(VIRT_BLOCK); if (!dir) return NULL; while ((dent = readdir(dir)) != NULL) { if (strncmp(dent->d_name,"loop",4)) continue; if (snprintf(path, PATH_MAX, "%s/%s/dev", VIRT_BLOCK, dent->d_name) >= PATH_MAX) continue; fd = open(path, O_RDONLY); if (fd < 0) continue; bytes_read = read(fd, dev, sizeof(dev) - 1); if (bytes_read <= 0) { close(fd); continue; } close(fd); dev[bytes_read] = '\0'; p = strchr(dev, '\n'); if (p != NULL) *p = '\0'; if (snprintf(path, PATH_MAX, "/dev/block/%s", dev) >= PATH_MAX) continue; fd = open (path, O_RDONLY); if (fd < 0) continue; if (fstat (fd, &statbuf) != 0 || !S_ISBLK(statbuf.st_mode)) { close (fd); continue; } if (ioctl (fd, LOOP_GET_STATUS, &loopinfo) != 0) { close (fd); continue; } close (fd); if (0 == strcmp(filename, loopinfo.lo_name)) { found = realpath(path, NULL); break; } /* * filename is a realpath, while loopinfo.lo_name may hold just the * basename. If that's the case, try to match filename against the * backing_file entry for this loop entry */ if (snprintf(bf_path, PATH_MAX, "%s/%s/loop/backing_file", VIRT_BLOCK, dent->d_name) >= PATH_MAX) continue; fd = open(bf_path, O_RDONLY); if (fd < 0) continue; bytes_read = read(fd, backing_file, sizeof(backing_file) - 1); if (bytes_read <= 0) { close(fd); continue; } close(fd); backing_file[bytes_read-1] = '\0'; if (0 == strcmp(filename, backing_file)) { found = realpath(path, NULL); break; } } closedir(dir); return found; } static char *find_unused_loop_device(int mode, int *loop_fd) { char dev[21]; int fd, next_loop = 0, somedev = 0, someloop = 0, loop_known = 0; int next_loop_fd; struct stat statbuf; struct loop_info loopinfo; FILE *procdev; next_loop_fd = open("/dev/loop-control", O_RDWR); if (next_loop_fd < 0) goto no_loop_fd; if (!(fstat(next_loop_fd, &statbuf) == 0 && S_ISCHR(statbuf.st_mode))) goto nothing_found; for (;;) { next_loop = ioctl(next_loop_fd, LOOP_CTL_GET_FREE); if (next_loop < 0) goto nothing_found; sprintf(dev, "/dev/loop%d", next_loop); fd = open (dev, mode); if (fd >= 0) { if (fstat (fd, &statbuf) == 0 && S_ISBLK(statbuf.st_mode)) { somedev++; if(ioctl (fd, LOOP_GET_STATUS, &loopinfo) == 0) someloop++; /* in use */ else if (errno == ENXIO) { char *name = strdup(dev); if (name == NULL) close(fd); else *loop_fd = fd; close(next_loop_fd); return name; } } close (fd); /* continue trying as long as devices exist */ } else break; } nothing_found: close(next_loop_fd); no_loop_fd: /* Nothing found. Why not? */ if ((procdev = fopen(PROC_DEVICES, "r")) != NULL) { char line[100]; while (fgets (line, sizeof(line), procdev)) if (strstr (line, " loop\n")) { loop_known = 1; break; } fclose(procdev); if (!loop_known) loop_known = -1; } if (!somedev) fprintf(stderr, "mount: could not find any device /dev/loop#\n"); else if (!someloop) { if (loop_known == 1) fprintf(stderr, "mount: Could not find any loop device.\n" " Maybe /dev/loop# has a wrong major number?\n"); else if (loop_known == -1) fprintf(stderr, "mount: Could not find any loop device, and, according to %s,\n" " this kernel does not know about the loop device.\n" " (If so, then recompile or `modprobe loop'.)\n", PROC_DEVICES); else fprintf(stderr, "mount: Could not find any loop device. Maybe this kernel does not know\n" " about the loop device (then recompile or `modprobe loop'), or\n" " maybe /dev/loop# has the wrong major number?\n"); } else fprintf(stderr, "mount: could not find any free loop device\n"); return NULL; } int set_loop(char **device, const char *file, int offset, int *loopro) { struct loop_info loopinfo; int fd = -1, ret = 1, ffd, mode; mode = (*loopro ? O_RDONLY : O_RDWR); if ((ffd = open (file, mode)) < 0) { if (!*loopro && (errno == EROFS || errno == EACCES)) ffd = open (file, mode = O_RDONLY); if (ffd < 0) { perror (file); return 1; } } *device = find_unused_loop_device(mode, &fd); if (!*device) { close(ffd); return 1; } *loopro = (mode == O_RDONLY); memset (&loopinfo, 0, sizeof (loopinfo)); xstrncpy (loopinfo.lo_name, file, LO_NAME_SIZE); loopinfo.lo_offset = offset; loopinfo.lo_encrypt_type = LO_CRYPT_NONE; loopinfo.lo_encrypt_key_size = 0; if (ioctl(fd, LOOP_SET_FD, (void*)(uintptr_t)(ffd)) < 0) { perror ("ioctl: LOOP_SET_FD"); goto out; } if (ioctl (fd, LOOP_SET_STATUS, &loopinfo) < 0) { (void) ioctl (fd, LOOP_CLR_FD, 0); perror ("ioctl: LOOP_SET_STATUS"); goto out; } ret = 0; out: close (fd); close (ffd); return ret; } int del_loop(const char *device) { int retries = 5; int fd; if ((fd = open (device, O_RDONLY)) < 0) { int errsv = errno; fprintf(stderr, "loop: can't delete device %s: %s\n", device, strerror (errsv)); return 1; } while (ioctl (fd, LOOP_CLR_FD, 0) < 0) { if (errno != EBUSY || retries-- <= 0) { perror ("ioctl: LOOP_CLR_FD"); close (fd); return 1; } fprintf(stderr, "loop: device %s still in use, retrying delete\n", device); sleep(1); continue; } close (fd); return 0; } multipath-tools-0.11.1/kpartx/lopart.h000066400000000000000000000003341475246302400177310ustar00rootroot00000000000000#ifndef LOPART_H_INCLUDED #define LOPART_H_INCLUDED extern int verbose; extern int set_loop (char **, const char *, int, int *); extern int del_loop (const char *); extern char * find_loop_by_file (const char *); #endif multipath-tools-0.11.1/kpartx/mac.c000066400000000000000000000022401475246302400171610ustar00rootroot00000000000000#include "kpartx.h" #include "byteorder.h" #include #include #include "mac.h" int read_mac_pt(int fd, __attribute__((unused)) struct slice all, struct slice *sp, unsigned int ns) { struct mac_driver_desc *md; struct mac_partition *part; unsigned secsize; char *data; unsigned int blk, blocks_in_map; int n = 0; md = (struct mac_driver_desc *) getblock(fd, 0); if (md == NULL) return -1; if (be16_to_cpu(md->signature) != MAC_DRIVER_MAGIC) return -1; secsize = be16_to_cpu(md->block_size); data = getblock(fd, secsize/512); if (!data) return -1; part = (struct mac_partition *) (data + secsize%512); if (be16_to_cpu(part->signature) != MAC_PARTITION_MAGIC) return -1; blocks_in_map = be32_to_cpu(part->map_count); for (blk = 1; blk <= blocks_in_map && blk <= ns; ++blk, ++n) { int pos = blk * secsize; data = getblock(fd, pos/512); if (!data) return -1; part = (struct mac_partition *) (data + pos%512); if (be16_to_cpu(part->signature) != MAC_PARTITION_MAGIC) break; sp[n].start = be32_to_cpu(part->start_block) * (secsize/512); sp[n].size = be32_to_cpu(part->block_count) * (secsize/512); } return n; } multipath-tools-0.11.1/kpartx/mac.h000066400000000000000000000014731475246302400171750ustar00rootroot00000000000000#ifndef MAC_H_INCLUDED #define MAC_H_INCLUDED #include #define MAC_PARTITION_MAGIC 0x504d /* type field value for A/UX or other Unix partitions */ #define APPLE_AUX_TYPE "Apple_UNIX_SVR2" struct mac_partition { uint16_t signature; /* expected to be MAC_PARTITION_MAGIC */ uint16_t res1; uint32_t map_count; /* # blocks in partition map */ uint32_t start_block; /* absolute starting block # of partition */ uint32_t block_count; /* number of blocks in partition */ /* there is more stuff after this that we don't need */ }; #define MAC_DRIVER_MAGIC 0x4552 /* Driver descriptor structure, in block 0 */ struct mac_driver_desc { uint16_t signature; /* expected to be MAC_DRIVER_MAGIC */ uint16_t block_size; uint32_t block_count; /* ... more stuff */ }; #endif multipath-tools-0.11.1/kpartx/ps3.c000066400000000000000000000030441475246302400171310ustar00rootroot00000000000000#include "kpartx.h" #include "byteorder.h" #include #include #define SECTOR_SIZE 512 #define MAX_ACL_ENTRIES 8 #define MAX_PARTITIONS 8 #define MAGIC1 0x0FACE0FFULL #define MAGIC2 0xDEADFACEULL struct p_acl_entry { u_int64_t laid; u_int64_t rights; }; struct d_partition { u_int64_t p_start; u_int64_t p_size; struct p_acl_entry p_acl[MAX_ACL_ENTRIES]; }; struct disklabel { u_int8_t d_res1[16]; u_int64_t d_magic1; u_int64_t d_magic2; u_int64_t d_res2; u_int64_t d_res3; struct d_partition d_partitions[MAX_PARTITIONS]; u_int8_t d_pad[0x600 - MAX_PARTITIONS * sizeof(struct d_partition) - 0x30]; }; static int read_disklabel(int fd, struct disklabel *label) { unsigned char *data; unsigned int i; for (i = 0; i < sizeof(struct disklabel) / SECTOR_SIZE; i++) { data = (unsigned char *) getblock(fd, i); if (!data) return 0; memcpy((unsigned char *) label + i * SECTOR_SIZE, data, SECTOR_SIZE); } return 1; } int read_ps3_pt(int fd, __attribute__((unused)) struct slice all, struct slice *sp, __attribute__((unused)) unsigned int ns) { struct disklabel label; int n = 0; int i; if (!read_disklabel(fd, &label)) return -1; if ((be64_to_cpu(label.d_magic1) != MAGIC1) || (be64_to_cpu(label.d_magic2) != MAGIC2)) return -1; for (i = 0; i < MAX_PARTITIONS; i++) { if (label.d_partitions[i].p_start && label.d_partitions[i].p_size) { sp[n].start = be64_to_cpu(label.d_partitions[i].p_start); sp[n].size = be64_to_cpu(label.d_partitions[i].p_size); n++; } } return n; } multipath-tools-0.11.1/kpartx/solaris.c000066400000000000000000000033761475246302400201100ustar00rootroot00000000000000#include "kpartx.h" #include #include #include /* time_t */ #define SOLARIS_X86_NUMSLICE 8 #define SOLARIS_X86_VTOC_SANE (0x600DDEEEUL) struct solaris_x86_slice { unsigned short s_tag; /* ID tag of partition */ unsigned short s_flag; /* permission flags */ __kernel_daddr_t s_start; /* start sector no of partition */ long s_size; /* # of blocks in partition */ }; struct solaris_x86_vtoc { unsigned long v_bootinfo[3]; /* info for mboot */ unsigned long v_sanity; /* to verify vtoc sanity */ unsigned long v_version; /* layout version */ char v_volume[8]; /* volume name */ unsigned short v_sectorsz; /* sector size in bytes */ unsigned short v_nparts; /* number of partitions */ unsigned long v_reserved[10]; /* free space */ struct solaris_x86_slice v_slice[SOLARIS_X86_NUMSLICE]; /* slice headers */ time_t timestamp[SOLARIS_X86_NUMSLICE]; /* timestamp */ char v_asciilabel[128]; /* for compatibility */ }; int read_solaris_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct solaris_x86_vtoc *v; struct solaris_x86_slice *s; unsigned int offset = all.start; unsigned int i, n; char *bp; bp = getblock(fd, offset+1); /* 1 sector suffices */ if (bp == NULL) return -1; v = (struct solaris_x86_vtoc *) bp; if(v->v_sanity != SOLARIS_X86_VTOC_SANE) return -1; if(v->v_version != 1) { fprintf(stderr, "Cannot handle solaris version %ld vtoc\n", v->v_version); return 0; } for(i=0, n=0; iv_slice[i]; if (s->s_size == 0) continue; if (n < ns) { sp[n].start = offset + s->s_start; sp[n].size = s->s_size; n++; } else { fprintf(stderr, "solaris_x86_partition: too many slices\n"); break; } } return n; } multipath-tools-0.11.1/kpartx/sun.c000066400000000000000000000063131475246302400172330ustar00rootroot00000000000000/* * Lifted from util-linux' partx sun.c * * Copyrights of the original file apply * Copyright (c) 2007 Hannes Reinecke */ #include "kpartx.h" #include "byteorder.h" #include #include #include /* time_t */ #define SUN_DISK_MAGIC 0xDABE /* Disk magic number */ #define SUN_DISK_MAXPARTITIONS 8 struct __attribute__ ((packed)) sun_raw_part { u_int32_t start_cylinder; /* where the part starts... */ u_int32_t num_sectors; /* ...and it's length */ }; struct __attribute__ ((packed)) sun_part_info { u_int8_t spare1; u_int8_t id; /* Partition type */ u_int8_t spare2; u_int8_t flags; /* Partition flags */ }; struct __attribute__ ((packed)) sun_disk_label { char info[128]; /* Informative text string */ u_int8_t spare0[14]; struct sun_part_info infos[SUN_DISK_MAXPARTITIONS]; u_int8_t spare1[246]; /* Boot information etc. */ u_int16_t rspeed; /* Disk rotational speed */ u_int16_t pcylcount; /* Physical cylinder count */ u_int16_t sparecyl; /* extra sects per cylinder */ u_int8_t spare2[4]; /* More magic... */ u_int16_t ilfact; /* Interleave factor */ u_int16_t ncyl; /* Data cylinder count */ u_int16_t nacyl; /* Alt. cylinder count */ u_int16_t ntrks; /* Tracks per cylinder */ u_int16_t nsect; /* Sectors per track */ u_int8_t spare3[4]; /* Even more magic... */ struct sun_raw_part partitions[SUN_DISK_MAXPARTITIONS]; u_int16_t magic; /* Magic number */ u_int16_t csum; /* Label xor'd checksum */ }; /* Checksum Verification */ static int sun_verify_checksum (struct sun_disk_label *label) { u_int16_t *ush = ((u_int16_t *)(label + 1)) - 1; u_int16_t csum = 0; while (ush >= (u_int16_t *)label) csum ^= *ush--; return !csum; } int read_sun_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct sun_disk_label *l; struct sun_raw_part *s; unsigned int offset = all.start, end; unsigned int i, j, n; char *bp; bp = getblock(fd, offset); if (bp == NULL) return -1; l = (struct sun_disk_label *) bp; if(be16_to_cpu(l->magic) != SUN_DISK_MAGIC) return -1; if (!sun_verify_checksum(l)) { fprintf(stderr, "Corrupted Sun disk label\n"); return -1; } for(i=0, n=0; ipartitions[i]; if (n < ns) { sp[n].start = offset + be32_to_cpu(s->start_cylinder) * be16_to_cpu(l->nsect) * be16_to_cpu(l->ntrks); sp[n].size = be32_to_cpu(s->num_sectors); n++; } else { fprintf(stderr, "sun_disklabel: too many slices\n"); break; } } /* * Convention has it that the SUN disklabel will always have * the 'c' partition spanning the entire disk. * So we have to check for contained slices. */ for(i = 0; i < SUN_DISK_MAXPARTITIONS; i++) { if (sp[i].size == 0) continue; end = sp[i].start + sp[i].size; for(j = 0; j < SUN_DISK_MAXPARTITIONS; j ++) { if ( i == j ) continue; if (sp[j].size == 0) continue; if (sp[i].start < sp[j].start) { if (end > sp[j].start && end < sp[j].start + sp[j].size) { /* Invalid slice */ fprintf(stderr, "sun_disklabel: slice %d overlaps with %d\n", i , j); sp[i].size = 0; } } else { if (end <= sp[j].start + sp[j].size) { sp[i].container = j + 1; } } } } return n; } multipath-tools-0.11.1/kpartx/test-kpartx000077500000000000000000000213371475246302400205010ustar00rootroot00000000000000#! /bin/bash # This is a unit test program for kpartx, in particular for deleting partitions. # # The rationale is the following: # # 1) kpartx should delete all mappings it created beforehand. # 2) kpartx should handle partitions on dm devices and other devices # (e.g. loop devices) equally well. # 3) kpartx should only delete "partitions", which are single-target # linear mappings into a block device. Other maps should not be touched. # 4) kpartx should only delete mappings it created itself beforehand. # In particular, it shouldn't delete LVM LVs, even if they are fully # contained in the block device at hand and thus look like partitions # in the first place. (For historical compatibility reasons, we allow # such mappings to be deleted with the -f/--force flag). # 5) DM map names may be changed, thus kpartx shouldn't rely on them to # check whether a mapping is a partition of a particular device. It is # legal for a partition of /dev/loop0 to be named "loop0". # Note: This program tries hard to clean up, but if tests fail, # stale DM or loop devices may keep lurking around. # Set WORKDIR in environment to existing dir to for persistence # WARNING: existing files will be truncated. # If empty, test will be done in temporary dir : ${WORKDIR:=} # Set this environment variable to test an alternative kpartx executable : ${KPARTX:=} # Options to pass to kpartx always : ${KPARTX_OPTS:=-s} # Time to wait for device nodes to appear (microseconds) # Waiting is only needed if "s" is not in $KPARTX_OPTS : ${WAIT_US:=0} # IMPORTANT: The ERR trap is essential for this program to work correctly! trap 'LINE=$LINENO; trap - ERR; echo "== error in $BASH_COMMAND on line $LINE ==" >&2; exit 1' ERR trap 'cleanup' 0 CLEANUP=: cleanup() { trap - ERR trap - 0 if [[ $OK ]]; then echo == all tests completed successfully == >&2 else echo == step $STEP failed == >&2 fi eval "$CLEANUP" &>/dev/null } push_cleanup() { CLEANUP="$@;$CLEANUP" } pop_cleanup() { # CAUTION: simplistic CLEANUP=${CLEANUP#*;} } step() { STEP="$@" echo == Test step: $STEP == >&2 } mk_partitions() { parted -s $1 mklabel msdos parted -s -- $1 mkpart prim ext2 1MiB -1s } wipe_ptable() { dd if=/dev/zero of=$1 bs=1b count=1 } step preparation [[ $UID -eq 0 ]] [[ $KPARTX ]] || { if [[ -x $PWD/kpartx/kpartx ]]; then KPARTX=$PWD/kpartx/kpartx else KPARTX=$(which kpartx) fi } [[ $KPARTX ]] FILE1=kpartx1 FILE2=kpartx2 FILE3=kpartx3 FILE4=kpartx4 SIZE=$((1024*1024*1024)) # use bytes as units here SECTSIZ=512 OFFS=32 # offset of linear mapping into dev, sectors VG=kpvg # volume group name LV=kplv # logical vol name LVMCONF='devices { filter = [ "a|/dev/loop.*|", r".*" ] }' OK= [[ $WORKDIR ]] || { WORKDIR=$(mktemp -d /tmp/kpartx-XXXXXX) push_cleanup 'rm -rf $WORKDIR' } push_cleanup "cd $PWD" cd "$WORKDIR" step "create loop devices" truncate -s $SIZE $FILE1 truncate -s $SIZE $FILE2 truncate -s $SIZE $FILE3 truncate -s $SIZE $FILE4 LO1=$(losetup -f $FILE1 --show) push_cleanup 'losetup -d $LO1' LO2=$(losetup -f $FILE2 --show) push_cleanup 'losetup -d $LO2' LO3=$(losetup -f $FILE3 --show) push_cleanup 'losetup -d $LO3' LO4=$(losetup -f $FILE4 --show) push_cleanup 'losetup -d $LO4' [[ $LO1 && $LO2 && $LO3 && $LO4 && -b $LO1 && -b $LO2 && -b $LO3 && -b $LO4 ]] DEV1=$(stat -c "%t:%T" $LO1) DEV2=$(stat -c "%t:%T" $LO2) DEV3=$(stat -c "%t:%T" $LO3) usleep $WAIT_US step "create DM devices (spans)" # Create two linear mappings spanning two loopdevs. # One of them gets a pathological name colliding with # the loop device name. # These mappings must not be removed by kpartx. # They also serve as DM devices to test partition removal on those. TABLE="\ 0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS $((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS" SPAN1=kpt SPAN2=$(basename $LO2) dmsetup create $SPAN1 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN1' dmsetup create $SPAN2 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN2' # This is a non-kpartx pseudo "partition" mapping USER1=user1 push_cleanup 'dmsetup remove -f $USER1' dmsetup create $USER1 <&2 usleep $WAIT_US [[ -b $SPAN2P1 ]] [[ -b $LO1P1 ]] [[ -b $LO2P1 ]] [[ ! -b $SPAN1P1 ]] $KPARTX $KPARTX_OPTS -d /dev/mapper/$SPAN2 usleep $WAIT_US [[ -b $LO1P1 ]] [[ -b $LO2P1 ]] [[ ! -b $SPAN2P1 ]] step "rename partitions on loop device" $KPARTX $KPARTX_OPTS -u -p -spam $LO2 [[ ! -b ${LO2P1} ]] [[ -b ${LO2P1//-foo/-spam} ]] step "rename partitions on loop device back" $KPARTX $KPARTX_OPTS -u -p -foo $LO2 [[ -b ${LO2P1} ]] [[ ! -b ${LO2P1//-foo/-spam} ]] step "rename partitions on loop device to default" $KPARTX $KPARTX_OPTS -u $LO2 #read a [[ ! -b ${LO2P1} ]] # $LO1 ends in a digit [[ -b ${LO2P1//-foo/p} ]] step "rename partitions on loop device back from default" $KPARTX $KPARTX_OPTS -u -p -foo $LO2 [[ -b ${LO2P1} ]] [[ ! -b ${LO2P1//-foo/p} ]] step "rename partitions on loop devices" $KPARTX $KPARTX_OPTS -u -p spam $LO2 step "delete partitions on loop devices" $KPARTX $KPARTX_OPTS -d $LO3 # This will also delete the loop device $KPARTX $KPARTX_OPTS -d $FILE2 $KPARTX $KPARTX_OPTS -d $LO1 usleep $WAIT_US # ls -l /dev/mapper [[ ! -b $LO1P1 ]] pop_cleanup [[ ! -b $LO2P1 ]] pop_cleanup # spans should not have been removed [[ -b /dev/mapper/$SPAN1 ]] [[ -b /dev/mapper/$SPAN2 ]] [[ -b /dev/mapper/$USER1 ]] # LVs neither [[ -b /dev/mapper/$VG-$LV ]] step "delete partitions on $LO3 with -f" $KPARTX $KPARTX_OPTS -f -d $LO3 # -d -f should delete the LV, too [[ ! -b /dev/mapper/$VG-$LV ]] [[ -b /dev/mapper/$SPAN1 ]] [[ -b /dev/mapper/$SPAN2 ]] step "test kpartx creation/deletion on an image file with no existing loopdev" losetup -d $LO4 OUTPUT=$($KPARTX $KPARTX_OPTS -v -a $FILE4 2>&1) read loop dm < \ <(sed -n 's/^add map \(loop[0-9]*\)p1 ([0-9]*:\([0-9]*\)).*$/\1 dm-\2/p' \ <<<$OUTPUT) [[ $dm && $loop ]] push_cleanup "dmsetup remove -f /dev/$dm" push_cleanup "losetup -d /dev/$loop" [[ -b /dev/mapper/${loop}p1 ]] $KPARTX -d $KPARTX_OPTS $FILE4 [[ ! -b /dev/mapper/${loop}p1 ]] # /dev/$loop is _not_ automatically deleted [[ -b /dev/${loop} ]] OK=yes multipath-tools-0.11.1/kpartx/unixware.c000066400000000000000000000052651475246302400202750ustar00rootroot00000000000000#include "kpartx.h" #include #define UNIXWARE_FS_UNUSED 0 #define UNIXWARE_NUMSLICE 16 #define UNIXWARE_DISKMAGIC (0xCA5E600D) #define UNIXWARE_DISKMAGIC2 (0x600DDEEE) struct unixware_slice { unsigned short s_label; /* label */ unsigned short s_flags; /* permission flags */ unsigned int start_sect; /* starting sector */ unsigned int nr_sects; /* number of sectors in slice */ }; struct unixware_disklabel { unsigned int d_type; /* drive type */ unsigned char d_magic[4]; /* the magic number */ unsigned int d_version; /* version number */ char d_serial[12]; /* serial number of the device */ unsigned int d_ncylinders; /* # of data cylinders per device */ unsigned int d_ntracks; /* # of tracks per cylinder */ unsigned int d_nsectors; /* # of data sectors per track */ unsigned int d_secsize; /* # of bytes per sector */ unsigned int d_part_start; /* # of first sector of this partition */ unsigned int d_unknown1[12]; /* ? */ unsigned int d_alt_tbl; /* byte offset of alternate table */ unsigned int d_alt_len; /* byte length of alternate table */ unsigned int d_phys_cyl; /* # of physical cylinders per device */ unsigned int d_phys_trk; /* # of physical tracks per cylinder */ unsigned int d_phys_sec; /* # of physical sectors per track */ unsigned int d_phys_bytes; /* # of physical bytes per sector */ unsigned int d_unknown2; /* ? */ unsigned int d_unknown3; /* ? */ unsigned int d_pad[8]; /* pad */ struct unixware_vtoc { unsigned char v_magic[4]; /* the magic number */ unsigned int v_version; /* version number */ char v_name[8]; /* volume name */ unsigned short v_nslices; /* # of slices */ unsigned short v_unknown1; /* ? */ unsigned int v_reserved[10]; /* reserved */ struct unixware_slice v_slice[UNIXWARE_NUMSLICE]; /* slice headers */ } vtoc; }; /* 408 */ int read_unixware_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct unixware_disklabel *l; struct unixware_slice *p; unsigned int offset = all.start; char *bp; unsigned int n = 0; bp = getblock(fd, offset+29); /* 1 sector suffices */ if (bp == NULL) return -1; l = (struct unixware_disklabel *) bp; if (four2int(l->d_magic) != UNIXWARE_DISKMAGIC || four2int(l->vtoc.v_magic) != UNIXWARE_DISKMAGIC2) return -1; p = &l->vtoc.v_slice[1]; /* slice 0 is the whole disk. */ while (p - &l->vtoc.v_slice[0] < UNIXWARE_NUMSLICE) { if (p->s_label == UNIXWARE_FS_UNUSED) /* nothing */; else if (n < ns) { sp[n].start = p->start_sect; sp[n].size = p->nr_sects; n++; } else { fprintf(stderr, "unixware_partition: too many slices\n"); break; } p++; } return n; } multipath-tools-0.11.1/kpartx/xstrncpy.c000066400000000000000000000003261475246302400203160ustar00rootroot00000000000000/* NUL-terminated version of strncpy() */ #include #include "xstrncpy.h" /* caller guarantees n > 0 */ void xstrncpy(char *dest, const char *src, size_t n) { strncpy(dest, src, n-1); dest[n-1] = 0; } multipath-tools-0.11.1/kpartx/xstrncpy.h000066400000000000000000000001741475246302400203240ustar00rootroot00000000000000#ifndef XSTRNCPY_H_INCLUDED #define XSTRNCPY_H_INCLUDED extern void xstrncpy(char *dest, const char *src, size_t n); #endif multipath-tools-0.11.1/libdmmp/000077500000000000000000000000001475246302400163725ustar00rootroot00000000000000multipath-tools-0.11.1/libdmmp/DEV_NOTES000066400000000000000000000025011475246302400177010ustar00rootroot00000000000000== Planned features == * Expose all properties used by /usr/bin/multipath == Code style == * Keep things as simple as possible. * Linux Kernel code style. * Don't use typedef. * Don't use enum. * We are not smarter than API user, so don't create wrapping function like: ``` dmmp_mpath_search_by_id(struct dmmp_context *ctx, struct dmmp_mpath **dmmp_mp, uint32_t dmmp_mp_count, const char *id) dmmp_path_group_id_search(struct dmmp_mpath *dmmp_mp, const char *blk_name) ``` * The performance is the same for query single mpath and query all mpaths, so no `dmmp_mpath_of_wwid(struct dmmp_context *ctx, const char *wwid)` yet. == Naming scheme == * Public constants should be named as `DMMP_XXX_YYY`. * Public functions should be named as `dmmp__`. * Private constants should be named as `_DMMP_XXX_YYY`. * Private functions should be named as `_dmmp__`. == Code Layout == * libdmmp_private.h Internal functions or macros. * libdmmp.c Handling multipathd IPC and generate dmmp_context and dmmp_mpath_array_get(). * libdmmp_mp.c For `struct dmmp_mpath` * libdmmp_pg.c For `struct dmmp_path_group` * libdmmp_path.c For `struct dmmp_path` * libdmmp_misc.c Misc functions. multipath-tools-0.11.1/libdmmp/Makefile000066400000000000000000000047221475246302400200370ustar00rootroot00000000000000# Makefile # # Copyright (C) 2015 - 2016 Red Hat, Inc. # Gris Ge # include ../Makefile.inc LIBDMMP_VERSION := 0.2.0 SONAME := $(LIBDMMP_VERSION) DEVLIB := libdmmp.so PKGFILE := libdmmp.pc EXTRA_MAN_FILES := libdmmp.h.3 HEADERS := libdmmp/libdmmp.h OBJS := libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o CPPFLAGS += -I$(libdmmpdir) -I$(mpathcmddir) $(shell $(PKG_CONFIG) --cflags json-c) CFLAGS += $(LIB_CFLAGS) -fvisibility=hidden LIBDEPS += $(shell $(PKG_CONFIG) --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread all: $(LIBS) doc .PHONY: doc clean install uninstall check speed_test dep_clean $(LIBS): $(OBJS) $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS) $(DEVLIB): $(LIBS) $(Q)$(LN) $(LIBS) $@ abi: $(DEVLIB:%.so=%.abi) install: @mkdir -p $(DESTDIR)$(usrlibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(usrlibdir)/$(LIBS) $(Q)$(INSTALL_PROGRAM) -m 644 -D \ $(HEADERS) $(DESTDIR)$(includedir)/$(HEADERS) $(Q)$(LN) $(LIBS) $(DESTDIR)$(usrlibdir)/$(DEVLIB) $(Q)$(INSTALL_PROGRAM) -m 644 -D \ $(PKGFILE).in $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)sed -i 's|__VERSION__|$(LIBDMMP_VERSION)|g' \ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)sed -i 's|__LIBDIR__|$(usrlibdir)|g' \ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)sed -i 's|__INCLUDEDIR__|$(includedir)|g' \ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 644 -t $(DESTDIR)$(mandir)/man3 docs/man/*.3 uninstall: $(Q)$(RM) $(DESTDIR)$(usrlibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(includedir)/$(HEADERS) $(Q)$(RM) $(DESTDIR)$(usrlibdir)/$(DEVLIB) @for file in $(DESTDIR)$(mandir)/man3/dmmp_*; do \ $(RM) $$file; \ done $(Q)$(RM) $(DESTDIR)$(mandir)/man3/libdmmp.h* $(Q)$(RM) $(DESTDIR)$(pkgconfdir)/$(PKGFILE) clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi $(NV_VERSION_SCRIPT) @$(MAKE) -C test clean include $(wildcard $(OBJS:.o=.d)) check: all @$(MAKE) -C test check speed_test: all @$(MAKE) -C test speed_test doc: docs/man/dmmp_strerror.3 docs/man/dmmp_strerror.3: $(HEADERS) $(Q)TEMPFILE=$(shell mktemp); \ cat $^ | perl docs/doc-preclean.pl >$$TEMPFILE; \ [ "$KBUILD_BUILD_TIMESTAMP" ] || \ KBUILD_BUILD_TIMESTAMP=`git log -n1 --pretty=%cd --date=iso -- $^`; \ export KBUILD_BUILD_TIMESTAMP; \ LC_ALL=C \ perl docs/kernel-doc -man $$TEMPFILE | \ perl docs/split-man.pl docs/man; \ $(RM) -f $$TEMPFILE dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libdmmp/docs/000077500000000000000000000000001475246302400173225ustar00rootroot00000000000000multipath-tools-0.11.1/libdmmp/docs/doc-preclean.pl000077500000000000000000000015461475246302400222240ustar00rootroot00000000000000#!/usr/bin/perl # Copyright (C) 2016 Red Hat, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Author: Gris Ge use strict; my @REMOVE_KEY_LIST=("DMMP_DLL_EXPORT"); while (<>) { for my $key (@REMOVE_KEY_LIST) { (s/$key//g); } print; } multipath-tools-0.11.1/libdmmp/docs/kernel-doc000077500000000000000000001710731475246302400213040ustar00rootroot00000000000000#!/usr/bin/env perl # SPDX-License-Identifier: GPL-2.0 use warnings; use strict; ## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## ## Copyright (C) 2000, 1 Tim Waugh ## ## Copyright (C) 2001 Simon Huggins ## ## Copyright (C) 2005-2012 Randy Dunlap ## ## Copyright (C) 2012 Dan Luedtke ## ## ## ## #define enhancements by Armin Kuster ## ## Copyright (c) 2000 MontaVista Software, Inc. ## ## ## ## This software falls under the GNU General Public License. ## ## Please read the COPYING file for more information ## # 18/01/2001 - Cleanups # Functions prototyped as foo(void) same as foo() # Stop eval'ing where we don't need to. # -- huggie@earth.li # 27/06/2001 - Allowed whitespace after initial "/**" and # allowed comments before function declarations. # -- Christian Kreibich # Still to do: # - add perldoc documentation # - Look more closely at some of the scarier bits :) # 26/05/2001 - Support for separate source and object trees. # Return error code. # Keith Owens # 23/09/2001 - Added support for typedefs, structs, enums and unions # Support for Context section; can be terminated using empty line # Small fixes (like spaces vs. \s in regex) # -- Tim Jansen # 25/07/2012 - Added support for HTML5 # -- Dan Luedtke sub usage { my $message = <<"EOF"; Usage: $0 [OPTION ...] FILE ... Read C language source or header FILEs, extract embedded documentation comments, and print formatted documentation to standard output. The documentation comments are identified by "/**" opening comment mark. See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax. Output format selection (mutually exclusive): -man Output troff manual page format. This is the default. -rst Output reStructuredText format. -none Do not output documentation, only warnings. Output selection (mutually exclusive): -export Only output documentation for symbols that have been exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() in any input FILE or -export-file FILE. -internal Only output documentation for symbols that have NOT been exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() in any input FILE or -export-file FILE. -function NAME Only output documentation for the given function(s) or DOC: section title(s). All other functions and DOC: sections are ignored. May be specified multiple times. -nofunction NAME Do NOT output documentation for the given function(s); only output documentation for the other functions and DOC: sections. May be specified multiple times. Output selection modifiers: -no-doc-sections Do not output DOC: sections. -enable-lineno Enable output of #define LINENO lines. Only works with reStructuredText format. -export-file FILE Specify an additional FILE in which to look for EXPORT_SYMBOL() and EXPORT_SYMBOL_GPL(). To be used with -export or -internal. May be specified multiple times. Other parameters: -v Verbose output, more warnings and other information. -h Print this help. EOF print $message; exit 1; } # # format of comments. # In the following table, (...)? signifies optional structure. # (...)* signifies 0 or more structure elements # /** # * function_name(:)? (- short description)? # (* @parameterx: (description of parameter x)?)* # (* a blank line)? # * (Description:)? (Description of function)? # * (section header: (section description)? )* # (*)?*/ # # So .. the trivial example would be: # # /** # * my_function # */ # # If the Description: header tag is omitted, then there must be a blank line # after the last parameter specification. # e.g. # /** # * my_function - does my stuff # * @my_arg: its mine damnit # * # * Does my stuff explained. # */ # # or, could also use: # /** # * my_function - does my stuff # * @my_arg: its mine damnit # * Description: Does my stuff explained. # */ # etc. # # Besides functions you can also write documentation for structs, unions, # enums and typedefs. Instead of the function name you must write the name # of the declaration; the struct/union/enum/typedef must always precede # the name. Nesting of declarations is not supported. # Use the argument mechanism to document members or constants. # e.g. # /** # * struct my_struct - short description # * @a: first member # * @b: second member # * # * Longer description # */ # struct my_struct { # int a; # int b; # /* private: */ # int c; # }; # # All descriptions can be multiline, except the short function description. # # For really longs structs, you can also describe arguments inside the # body of the struct. # eg. # /** # * struct my_struct - short description # * @a: first member # * @b: second member # * # * Longer description # */ # struct my_struct { # int a; # int b; # /** # * @c: This is longer description of C # * # * You can use paragraphs to describe arguments # * using this method. # */ # int c; # }; # # This should be use only for struct/enum members. # # You can also add additional sections. When documenting kernel functions you # should document the "Context:" of the function, e.g. whether the functions # can be called form interrupts. Unlike other sections you can end it with an # empty line. # A non-void function should have a "Return:" section describing the return # value(s). # Example-sections should contain the string EXAMPLE so that they are marked # appropriately in DocBook. # # Example: # /** # * user_function - function that can only be called in user context # * @a: some argument # * Context: !in_interrupt() # * # * Some description # * Example: # * user_function(22); # */ # ... # # # All descriptive text is further processed, scanning for the following special # patterns, which are highlighted appropriately. # # 'funcname()' - function # '$ENVVAR' - environmental variable # '&struct_name' - name of a structure (up to two words including 'struct') # '&struct_name.member' - name of a structure member # '@parameter' - name of a parameter # '%CONST' - name of a constant. # '``LITERAL``' - literal string without any spaces on it. ## init lots of data my $errors = 0; my $warnings = 0; my $anon_struct_union = 0; # match expressions used to find embedded type information my $type_constant = '\b``([^\`]+)``\b'; my $type_constant2 = '\%([-_\w]+)'; my $type_func = '(\w+)\(\)'; my $type_param = '\@(\w*(\.\w+)*(\.\.\.)?)'; my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params my $type_env = '(\$\w+)'; my $type_enum = '\&(enum\s*([_\w]+))'; my $type_struct = '\&(struct\s*([_\w]+))'; my $type_typedef = '\&(typedef\s*([_\w]+))'; my $type_union = '\&(union\s*([_\w]+))'; my $type_member = '\&([_\w]+)(\.|->)([_\w]+)'; my $type_fallback = '\&([_\w]+)'; my $type_member_func = $type_member . '\(\)'; # Output conversion substitutions. # One for each output format # these are pretty rough my @highlights_man = ( [$type_constant, "\$1"], [$type_constant2, "\$1"], [$type_func, "\\\\fB\$1\\\\fP"], [$type_enum, "\\\\fI\$1\\\\fP"], [$type_struct, "\\\\fI\$1\\\\fP"], [$type_typedef, "\\\\fI\$1\\\\fP"], [$type_union, "\\\\fI\$1\\\\fP"], [$type_param, "\\\\fI\$1\\\\fP"], [$type_member, "\\\\fI\$1\$2\$3\\\\fP"], [$type_fallback, "\\\\fI\$1\\\\fP"] ); my $blankline_man = ""; # rst-mode my @highlights_rst = ( [$type_constant, "``\$1``"], [$type_constant2, "``\$1``"], # Note: need to escape () to avoid func matching later [$type_member_func, "\\:c\\:type\\:`\$1\$2\$3\\\\(\\\\) <\$1>`"], [$type_member, "\\:c\\:type\\:`\$1\$2\$3 <\$1>`"], [$type_fp_param, "**\$1\\\\(\\\\)**"], [$type_func, "\\:c\\:func\\:`\$1()`"], [$type_enum, "\\:c\\:type\\:`\$1 <\$2>`"], [$type_struct, "\\:c\\:type\\:`\$1 <\$2>`"], [$type_typedef, "\\:c\\:type\\:`\$1 <\$2>`"], [$type_union, "\\:c\\:type\\:`\$1 <\$2>`"], # in rst this can refer to any type [$type_fallback, "\\:c\\:type\\:`\$1`"], [$type_param, "**\$1**"] ); my $blankline_rst = "\n"; # read arguments if ($#ARGV == -1) { usage(); } my $kernelversion; my $dohighlight = ""; my $verbose = 0; my $output_mode = "rst"; my $output_preformatted = 0; my $no_doc_sections = 0; my $enable_lineno = 0; my @highlights = @highlights_rst; my $blankline = $blankline_rst; my $modulename = "Kernel API"; use constant { OUTPUT_ALL => 0, # output all symbols and doc sections OUTPUT_INCLUDE => 1, # output only specified symbols OUTPUT_EXCLUDE => 2, # output everything except specified symbols OUTPUT_EXPORTED => 3, # output exported symbols OUTPUT_INTERNAL => 4, # output non-exported symbols }; my $output_selection = OUTPUT_ALL; my $show_not_found = 0; my @export_file_list; my @build_time; if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { @build_time = gmtime($seconds); } else { @build_time = localtime; } my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December')[$build_time[4]] . " " . ($build_time[5]+1900); # Essentially these are globals. # They probably want to be tidied up, made more localised or something. # CAVEAT EMPTOR! Some of the others I localised may not want to be, which # could cause "use of undefined value" or other bugs. my ($function, %function_table, %parametertypes, $declaration_purpose); my $declaration_start_line; my ($type, $declaration_name, $return_type); my ($newsection, $newcontents, $prototype, $brcount, %source_map); if (defined($ENV{'KBUILD_VERBOSE'})) { $verbose = "$ENV{'KBUILD_VERBOSE'}"; } # Generated docbook code is inserted in a template at a point where # docbook v3.1 requires a non-zero sequence of RefEntry's; see: # http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html # We keep track of number of generated entries and generate a dummy # if needs be to ensure the expanded template can be postprocessed # into html. my $section_counter = 0; my $lineprefix=""; # Parser states use constant { STATE_NORMAL => 0, # normal code STATE_NAME => 1, # looking for function name STATE_BODY_MAYBE => 2, # body - or maybe more description STATE_BODY => 3, # the body of the comment STATE_PROTO => 4, # scanning prototype STATE_DOCBLOCK => 5, # documentation block STATE_INLINE => 6, # gathering documentation outside main block }; my $state; my $in_doc_sect; my $leading_space; # Inline documentation state use constant { STATE_INLINE_NA => 0, # not applicable ($state != STATE_INLINE) STATE_INLINE_NAME => 1, # looking for member name (@foo:) STATE_INLINE_TEXT => 2, # looking for member documentation STATE_INLINE_END => 3, # done STATE_INLINE_ERROR => 4, # error - Comment without header was found. # Spit a warning as it's not # proper kernel-doc and ignore the rest. }; my $inline_doc_state; #declaration types: can be # 'function', 'struct', 'union', 'enum', 'typedef' my $decl_type; my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. my $doc_end = '\*/'; my $doc_com = '\s*\*\s*'; my $doc_com_body = '\s*\* ?'; my $doc_decl = $doc_com . '(\w+)'; # @params and a strictly limited set of supported section names my $doc_sect = $doc_com . '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:(.*)'; my $doc_content = $doc_com_body . '(.*)'; my $doc_block = $doc_com . 'DOC:\s*(.*)?'; my $doc_inline_start = '^\s*/\*\*\s*$'; my $doc_inline_sect = '\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)'; my $doc_inline_end = '^\s*\*/\s*$'; my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$'; my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;'; my %parameterdescs; my %parameterdesc_start_lines; my @parameterlist; my %sections; my @sectionlist; my %section_start_lines; my $sectcheck; my $struct_actual; my $contents = ""; my $new_start_line = 0; # the canonical section names. see also $doc_sect above. my $section_default = "Description"; # default section my $section_intro = "Introduction"; my $section = $section_default; my $section_context = "Context"; my $section_return = "Return"; my $undescribed = "-- undescribed --"; reset_state(); while ($ARGV[0] =~ m/^--?(.*)/) { my $cmd = $1; shift @ARGV; if ($cmd eq "man") { $output_mode = "man"; @highlights = @highlights_man; $blankline = $blankline_man; } elsif ($cmd eq "rst") { $output_mode = "rst"; @highlights = @highlights_rst; $blankline = $blankline_rst; } elsif ($cmd eq "none") { $output_mode = "none"; } elsif ($cmd eq "module") { # not needed for XML, inherits from calling document $modulename = shift @ARGV; } elsif ($cmd eq "function") { # to only output specific functions $output_selection = OUTPUT_INCLUDE; $function = shift @ARGV; $function_table{$function} = 1; } elsif ($cmd eq "nofunction") { # output all except specific functions $output_selection = OUTPUT_EXCLUDE; $function = shift @ARGV; $function_table{$function} = 1; } elsif ($cmd eq "export") { # only exported symbols $output_selection = OUTPUT_EXPORTED; %function_table = (); } elsif ($cmd eq "internal") { # only non-exported symbols $output_selection = OUTPUT_INTERNAL; %function_table = (); } elsif ($cmd eq "export-file") { my $file = shift @ARGV; push(@export_file_list, $file); } elsif ($cmd eq "v") { $verbose = 1; } elsif (($cmd eq "h") || ($cmd eq "help")) { usage(); } elsif ($cmd eq 'no-doc-sections') { $no_doc_sections = 1; } elsif ($cmd eq 'enable-lineno') { $enable_lineno = 1; } elsif ($cmd eq 'show-not-found') { $show_not_found = 1; } else { # Unknown argument usage(); } } # continue execution near EOF; # get kernel version from env sub get_kernel_version() { my $version = 'unknown kernel version'; if (defined($ENV{'KERNELVERSION'})) { $version = $ENV{'KERNELVERSION'}; } return $version; } # sub print_lineno { my $lineno = shift; if ($enable_lineno && defined($lineno)) { print "#define LINENO " . $lineno . "\n"; } } ## # dumps section contents to arrays/hashes intended for that purpose. # sub dump_section { my $file = shift; my $name = shift; my $contents = join "\n", @_; if ($name =~ m/$type_param/) { $name = $1; $parameterdescs{$name} = $contents; $sectcheck = $sectcheck . $name . " "; $parameterdesc_start_lines{$name} = $new_start_line; $new_start_line = 0; } elsif ($name eq "@\.\.\.") { $name = "..."; $parameterdescs{$name} = $contents; $sectcheck = $sectcheck . $name . " "; $parameterdesc_start_lines{$name} = $new_start_line; $new_start_line = 0; } else { if (defined($sections{$name}) && ($sections{$name} ne "")) { # Only warn on user specified duplicate section names. if ($name ne $section_default) { print STDERR "${file}:$.: warning: duplicate section name '$name'\n"; ++$warnings; } $sections{$name} .= $contents; } else { $sections{$name} = $contents; push @sectionlist, $name; $section_start_lines{$name} = $new_start_line; $new_start_line = 0; } } } ## # dump DOC: section after checking that it should go out # sub dump_doc_section { my $file = shift; my $name = shift; my $contents = join "\n", @_; if ($no_doc_sections) { return; } if (($output_selection == OUTPUT_ALL) || ($output_selection == OUTPUT_INCLUDE && defined($function_table{$name})) || ($output_selection == OUTPUT_EXCLUDE && !defined($function_table{$name}))) { dump_section($file, $name, $contents); output_blockhead({'sectionlist' => \@sectionlist, 'sections' => \%sections, 'module' => $modulename, 'content-only' => ($output_selection != OUTPUT_ALL), }); } } ## # output function # # parameterdescs, a hash. # function => "function name" # parameterlist => @list of parameters # parameterdescs => %parameter descriptions # sectionlist => @list of sections # sections => %section descriptions # sub output_highlight { my $contents = join "\n",@_; my $line; # DEBUG # if (!defined $contents) { # use Carp; # confess "output_highlight got called with no args?\n"; # } # print STDERR "contents b4:$contents\n"; eval $dohighlight; die $@ if $@; # print STDERR "contents af:$contents\n"; foreach $line (split "\n", $contents) { if (! $output_preformatted) { $line =~ s/^\s*//; } if ($line eq ""){ if (! $output_preformatted) { print $lineprefix, $blankline; } } else { if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { print "\\&$line"; } else { print $lineprefix, $line; } } print "\n"; } } ## # output function in man sub output_function_man(%) { my %args = %{$_[0]}; my ($parameter, $section); my $count; print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; print ".SH NAME\n"; print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; print ".SH SYNOPSIS\n"; if ($args{'functiontype'} ne "") { print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; } else { print ".B \"" . $args{'function'} . "\n"; } $count = 0; my $parenth = "("; my $post = ","; foreach my $parameter (@{$args{'parameterlist'}}) { if ($count == $#{$args{'parameterlist'}}) { $post = ");"; } $type = $args{'parametertypes'}{$parameter}; if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { # pointer-to-function print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n"; } else { $type =~ s/([^\*])$/$1 /; print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n"; } $count++; $parenth = ""; } print ".SH ARGUMENTS\n"; foreach $parameter (@{$args{'parameterlist'}}) { my $parameter_name = $parameter; $parameter_name =~ s/\[.*//; print ".IP \"" . $parameter . "\" 12\n"; output_highlight($args{'parameterdescs'}{$parameter_name}); } foreach $section (@{$args{'sectionlist'}}) { print ".SH \"", uc $section, "\"\n"; output_highlight($args{'sections'}{$section}); } } ## # output enum in man sub output_enum_man(%) { my %args = %{$_[0]}; my ($parameter, $section); my $count; print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; print ".SH NAME\n"; print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; print ".SH SYNOPSIS\n"; print "enum " . $args{'enum'} . " {\n"; $count = 0; foreach my $parameter (@{$args{'parameterlist'}}) { print ".br\n.BI \" $parameter\"\n"; if ($count == $#{$args{'parameterlist'}}) { print "\n};\n"; last; } else { print ", \n.br\n"; } $count++; } print ".SH Constants\n"; foreach $parameter (@{$args{'parameterlist'}}) { my $parameter_name = $parameter; $parameter_name =~ s/\[.*//; print ".IP \"" . $parameter . "\" 12\n"; output_highlight($args{'parameterdescs'}{$parameter_name}); } foreach $section (@{$args{'sectionlist'}}) { print ".SH \"$section\"\n"; output_highlight($args{'sections'}{$section}); } } ## # output struct in man sub output_struct_man(%) { my %args = %{$_[0]}; my ($parameter, $section); print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; print ".SH NAME\n"; print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; my $declaration = $args{'definition'}; $declaration =~ s/\t/ /g; $declaration =~ s/\n/"\n.br\n.BI \"/g; print ".SH SYNOPSIS\n"; print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; print ".BI \"$declaration\n};\n.br\n\n"; print ".SH Members\n"; foreach $parameter (@{$args{'parameterlist'}}) { ($parameter =~ /^#/) && next; my $parameter_name = $parameter; $parameter_name =~ s/\[.*//; ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; print ".IP \"" . $parameter . "\" 12\n"; output_highlight($args{'parameterdescs'}{$parameter_name}); } foreach $section (@{$args{'sectionlist'}}) { print ".SH \"$section\"\n"; output_highlight($args{'sections'}{$section}); } } ## # output typedef in man sub output_typedef_man(%) { my %args = %{$_[0]}; my ($parameter, $section); print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; print ".SH NAME\n"; print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; foreach $section (@{$args{'sectionlist'}}) { print ".SH \"$section\"\n"; output_highlight($args{'sections'}{$section}); } } sub output_blockhead_man(%) { my %args = %{$_[0]}; my ($parameter, $section); my $count; print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; foreach $section (@{$args{'sectionlist'}}) { print ".SH \"$section\"\n"; output_highlight($args{'sections'}{$section}); } } ## # output in restructured text # # # This could use some work; it's used to output the DOC: sections, and # starts by putting out the name of the doc section itself, but that tends # to duplicate a header already in the template file. # sub output_blockhead_rst(%) { my %args = %{$_[0]}; my ($parameter, $section); foreach $section (@{$args{'sectionlist'}}) { if ($output_selection != OUTPUT_INCLUDE) { print "**$section**\n\n"; } print_lineno($section_start_lines{$section}); output_highlight_rst($args{'sections'}{$section}); print "\n"; } } # # Apply the RST highlights to a sub-block of text. # sub highlight_block($) { # The dohighlight kludge requires the text be called $contents my $contents = shift; eval $dohighlight; die $@ if $@; return $contents; } # # Regexes used only here. # my $sphinx_literal = '^[^.].*::$'; my $sphinx_cblock = '^\.\.\ +code-block::'; sub output_highlight_rst { my $input = join "\n",@_; my $output = ""; my $line; my $in_literal = 0; my $litprefix; my $block = ""; foreach $line (split "\n",$input) { # # If we're in a literal block, see if we should drop out # of it. Otherwise pass the line straight through unmunged. # if ($in_literal) { if (! ($line =~ /^\s*$/)) { # # If this is the first non-blank line in a literal # block we need to figure out what the proper indent is. # if ($litprefix eq "") { $line =~ /^(\s*)/; $litprefix = '^' . $1; $output .= $line . "\n"; } elsif (! ($line =~ /$litprefix/)) { $in_literal = 0; } else { $output .= $line . "\n"; } } else { $output .= $line . "\n"; } } # # Not in a literal block (or just dropped out) # if (! $in_literal) { $block .= $line . "\n"; if (($line =~ /$sphinx_literal/) || ($line =~ /$sphinx_cblock/)) { $in_literal = 1; $litprefix = ""; $output .= highlight_block($block); $block = "" } } } if ($block) { $output .= highlight_block($block); } foreach $line (split "\n", $output) { print $lineprefix . $line . "\n"; } } sub output_function_rst(%) { my %args = %{$_[0]}; my ($parameter, $section); my $oldprefix = $lineprefix; my $start = ""; if ($args{'typedef'}) { print ".. c:type:: ". $args{'function'} . "\n\n"; print_lineno($declaration_start_line); print " **Typedef**: "; $lineprefix = ""; output_highlight_rst($args{'purpose'}); $start = "\n\n**Syntax**\n\n ``"; } else { print ".. c:function:: "; } if ($args{'functiontype'} ne "") { $start .= $args{'functiontype'} . " " . $args{'function'} . " ("; } else { $start .= $args{'function'} . " ("; } print $start; my $count = 0; foreach my $parameter (@{$args{'parameterlist'}}) { if ($count ne 0) { print ", "; } $count++; $type = $args{'parametertypes'}{$parameter}; if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { # pointer-to-function print $1 . $parameter . ") (" . $2; } else { print $type . " " . $parameter; } } if ($args{'typedef'}) { print ");``\n\n"; } else { print ")\n\n"; print_lineno($declaration_start_line); $lineprefix = " "; output_highlight_rst($args{'purpose'}); print "\n"; } print "**Parameters**\n\n"; $lineprefix = " "; foreach $parameter (@{$args{'parameterlist'}}) { my $parameter_name = $parameter; $parameter_name =~ s/\[.*//; $type = $args{'parametertypes'}{$parameter}; if ($type ne "") { print "``$type $parameter``\n"; } else { print "``$parameter``\n"; } print_lineno($parameterdesc_start_lines{$parameter_name}); if (defined($args{'parameterdescs'}{$parameter_name}) && $args{'parameterdescs'}{$parameter_name} ne $undescribed) { output_highlight_rst($args{'parameterdescs'}{$parameter_name}); } else { print " *undescribed*\n"; } print "\n"; } $lineprefix = $oldprefix; output_section_rst(@_); } sub output_section_rst(%) { my %args = %{$_[0]}; my $section; my $oldprefix = $lineprefix; $lineprefix = ""; foreach $section (@{$args{'sectionlist'}}) { print "**$section**\n\n"; print_lineno($section_start_lines{$section}); output_highlight_rst($args{'sections'}{$section}); print "\n"; } print "\n"; $lineprefix = $oldprefix; } sub output_enum_rst(%) { my %args = %{$_[0]}; my ($parameter); my $oldprefix = $lineprefix; my $count; my $name = "enum " . $args{'enum'}; print "\n\n.. c:type:: " . $name . "\n\n"; print_lineno($declaration_start_line); $lineprefix = " "; output_highlight_rst($args{'purpose'}); print "\n"; print "**Constants**\n\n"; $lineprefix = " "; foreach $parameter (@{$args{'parameterlist'}}) { print "``$parameter``\n"; if ($args{'parameterdescs'}{$parameter} ne $undescribed) { output_highlight_rst($args{'parameterdescs'}{$parameter}); } else { print " *undescribed*\n"; } print "\n"; } $lineprefix = $oldprefix; output_section_rst(@_); } sub output_typedef_rst(%) { my %args = %{$_[0]}; my ($parameter); my $oldprefix = $lineprefix; my $name = "typedef " . $args{'typedef'}; print "\n\n.. c:type:: " . $name . "\n\n"; print_lineno($declaration_start_line); $lineprefix = " "; output_highlight_rst($args{'purpose'}); print "\n"; $lineprefix = $oldprefix; output_section_rst(@_); } sub output_struct_rst(%) { my %args = %{$_[0]}; my ($parameter); my $oldprefix = $lineprefix; my $name = $args{'type'} . " " . $args{'struct'}; print "\n\n.. c:type:: " . $name . "\n\n"; print_lineno($declaration_start_line); $lineprefix = " "; output_highlight_rst($args{'purpose'}); print "\n"; print "**Definition**\n\n"; print "::\n\n"; my $declaration = $args{'definition'}; $declaration =~ s/\t/ /g; print " " . $args{'type'} . " " . $args{'struct'} . " {\n$declaration };\n\n"; print "**Members**\n\n"; $lineprefix = " "; foreach $parameter (@{$args{'parameterlist'}}) { ($parameter =~ /^#/) && next; my $parameter_name = $parameter; $parameter_name =~ s/\[.*//; ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; $type = $args{'parametertypes'}{$parameter}; print_lineno($parameterdesc_start_lines{$parameter_name}); print "``" . $parameter . "``\n"; output_highlight_rst($args{'parameterdescs'}{$parameter_name}); print "\n"; } print "\n"; $lineprefix = $oldprefix; output_section_rst(@_); } ## none mode output functions sub output_function_none(%) { } sub output_enum_none(%) { } sub output_typedef_none(%) { } sub output_struct_none(%) { } sub output_blockhead_none(%) { } ## # generic output function for all types (function, struct/union, typedef, enum); # calls the generated, variable output_ function name based on # functype and output_mode sub output_declaration { no strict 'refs'; my $name = shift; my $functype = shift; my $func = "output_${functype}_$output_mode"; if (($output_selection == OUTPUT_ALL) || (($output_selection == OUTPUT_INCLUDE || $output_selection == OUTPUT_EXPORTED) && defined($function_table{$name})) || (($output_selection == OUTPUT_EXCLUDE || $output_selection == OUTPUT_INTERNAL) && !($functype eq "function" && defined($function_table{$name})))) { &$func(@_); $section_counter++; } } ## # generic output function - calls the right one based on current output mode. sub output_blockhead { no strict 'refs'; my $func = "output_blockhead_" . $output_mode; &$func(@_); $section_counter++; } ## # takes a declaration (struct, union, enum, typedef) and # invokes the right handler. NOT called for functions. sub dump_declaration($$) { no strict 'refs'; my ($prototype, $file) = @_; my $func = "dump_" . $decl_type; &$func(@_); } sub dump_union($$) { dump_struct(@_); } sub dump_struct($$) { my $x = shift; my $file = shift; if ($x =~ /(struct|union)\s+(\w+)\s*\{(.*)\}/) { my $decl_type = $1; $declaration_name = $2; my $members = $3; # ignore members marked private: $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; $members =~ s/\/\*\s*private:.*//gosi; # strip comments: $members =~ s/\/\*.*?\*\///gos; # strip attributes $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; $members =~ s/__aligned\s*\([^;]*\)//gos; $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos; # replace DECLARE_BITMAP $members =~ s/DECLARE_BITMAP\s*\(([^,)]+),\s*([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; # replace DECLARE_HASHTABLE $members =~ s/DECLARE_HASHTABLE\s*\(([^,)]+),\s*([^,)]+)\)/unsigned long $1\[1 << (($2) - 1)\]/gos; # replace DECLARE_KFIFO $members =~ s/DECLARE_KFIFO\s*\(([^,)]+),\s*([^,)]+),\s*([^,)]+)\)/$2 \*$1/gos; # replace DECLARE_KFIFO_PTR $members =~ s/DECLARE_KFIFO_PTR\s*\(([^,)]+),\s*([^,)]+)\)/$2 \*$1/gos; my $declaration = $members; # Split nested struct/union elements as newer ones while ($members =~ m/(struct|union)([^\{\};]+)\{([^\{\}]*)\}([^\{\}\;]*)\;/) { my $newmember; my $maintype = $1; my $ids = $4; my $content = $3; foreach my $id(split /,/, $ids) { $newmember .= "$maintype $id; "; $id =~ s/[:\[].*//; $id =~ s/^\s*\**(\S+)\s*/$1/; foreach my $arg (split /;/, $content) { next if ($arg =~ m/^\s*$/); if ($arg =~ m/^([^\(]+\(\*?\s*)([\w\.]*)(\s*\).*)/) { # pointer-to-function my $type = $1; my $name = $2; my $extra = $3; next if (!$name); if ($id =~ m/^\s*$/) { # anonymous struct/union $newmember .= "$type$name$extra; "; } else { $newmember .= "$type$id.$name$extra; "; } } else { my $type; my $names; $arg =~ s/^\s+//; $arg =~ s/\s+$//; # Handle bitmaps $arg =~ s/:\s*\d+\s*//g; # Handle arrays $arg =~ s/\[.*\]//g; # The type may have multiple words, # and multiple IDs can be defined, like: # const struct foo, *bar, foobar # So, we remove spaces when parsing the # names, in order to match just names # and commas for the names $arg =~ s/\s*,\s*/,/g; if ($arg =~ m/(.*)\s+([\S+,]+)/) { $type = $1; $names = $2; } else { $newmember .= "$arg; "; next; } foreach my $name (split /,/, $names) { $name =~ s/^\s*\**(\S+)\s*/$1/; next if (($name =~ m/^\s*$/)); if ($id =~ m/^\s*$/) { # anonymous struct/union $newmember .= "$type $name; "; } else { $newmember .= "$type $id.$name; "; } } } } } $members =~ s/(struct|union)([^\{\};]+)\{([^\{\}]*)\}([^\{\}\;]*)\;/$newmember/; } # Ignore other nested elements, like enums $members =~ s/(\{[^\{\}]*\})//g; create_parameterlist($members, ';', $file, $declaration_name); check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual); # Adjust declaration for better display $declaration =~ s/([\{;])/$1\n/g; $declaration =~ s/\}\s+;/};/g; # Better handle inlined enums do {} while ($declaration =~ s/(enum\s+\{[^\}]+),([^\n])/$1,\n$2/); my @def_args = split /\n/, $declaration; my $level = 1; $declaration = ""; foreach my $clause (@def_args) { $clause =~ s/^\s+//; $clause =~ s/\s+$//; $clause =~ s/\s+/ /; next if (!$clause); $level-- if ($clause =~ m/(\})/ && $level > 1); if (!($clause =~ m/^\s*#/)) { $declaration .= "\t" x $level; } $declaration .= "\t" . $clause . "\n"; $level++ if ($clause =~ m/(\{)/ && !($clause =~m/\}/)); } output_declaration($declaration_name, 'struct', {'struct' => $declaration_name, 'module' => $modulename, 'definition' => $declaration, 'parameterlist' => \@parameterlist, 'parameterdescs' => \%parameterdescs, 'parametertypes' => \%parametertypes, 'sectionlist' => \@sectionlist, 'sections' => \%sections, 'purpose' => $declaration_purpose, 'type' => $decl_type }); } else { print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; ++$errors; } } sub show_warnings($$) { my $functype = shift; my $name = shift; return 1 if ($output_selection == OUTPUT_ALL); if ($output_selection == OUTPUT_EXPORTED) { if (defined($function_table{$name})) { return 1; } else { return 0; } } if ($output_selection == OUTPUT_INTERNAL) { if (!($functype eq "function" && defined($function_table{$name}))) { return 1; } else { return 0; } } if ($output_selection == OUTPUT_INCLUDE) { if (defined($function_table{$name})) { return 1; } else { return 0; } } if ($output_selection == OUTPUT_EXCLUDE) { if (!defined($function_table{$name})) { return 1; } else { return 0; } } die("Please add the new output type at show_warnings()"); } sub dump_enum($$) { my $x = shift; my $file = shift; $x =~ s@/\*.*?\*/@@gos; # strip comments. # strip #define macros inside enums $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos; if ($x =~ /enum\s+(\w+)\s*\{(.*)\}/) { $declaration_name = $1; my $members = $2; my %_members; $members =~ s/\s+$//; foreach my $arg (split ',', $members) { $arg =~ s/^\s*(\w+).*/$1/; push @parameterlist, $arg; if (!$parameterdescs{$arg}) { $parameterdescs{$arg} = $undescribed; if (show_warnings("enum", $declaration_name)) { print STDERR "${file}:$.: warning: Enum value '$arg' not described in enum '$declaration_name'\n"; } } $_members{$arg} = 1; } while (my ($k, $v) = each %parameterdescs) { if (!exists($_members{$k})) { if (show_warnings("enum", $declaration_name)) { print STDERR "${file}:$.: warning: Excess enum value '$k' description in '$declaration_name'\n"; } } } output_declaration($declaration_name, 'enum', {'enum' => $declaration_name, 'module' => $modulename, 'parameterlist' => \@parameterlist, 'parameterdescs' => \%parameterdescs, 'sectionlist' => \@sectionlist, 'sections' => \%sections, 'purpose' => $declaration_purpose }); } else { print STDERR "${file}:$.: error: Cannot parse enum!\n"; ++$errors; } } sub dump_typedef($$) { my $x = shift; my $file = shift; $x =~ s@/\*.*?\*/@@gos; # strip comments. # Parse function prototypes if ($x =~ /typedef\s+(\w+)\s*\(\*\s*(\w\S+)\s*\)\s*\((.*)\);/ || $x =~ /typedef\s+(\w+)\s*(\w\S+)\s*\s*\((.*)\);/) { # Function typedefs $return_type = $1; $declaration_name = $2; my $args = $3; create_parameterlist($args, ',', $file, $declaration_name); output_declaration($declaration_name, 'function', {'function' => $declaration_name, 'typedef' => 1, 'module' => $modulename, 'functiontype' => $return_type, 'parameterlist' => \@parameterlist, 'parameterdescs' => \%parameterdescs, 'parametertypes' => \%parametertypes, 'sectionlist' => \@sectionlist, 'sections' => \%sections, 'purpose' => $declaration_purpose }); return; } while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { $x =~ s/\(*.\)\s*;$/;/; $x =~ s/\[*.\]\s*;$/;/; } if ($x =~ /typedef.*\s+(\w+)\s*;/) { $declaration_name = $1; output_declaration($declaration_name, 'typedef', {'typedef' => $declaration_name, 'module' => $modulename, 'sectionlist' => \@sectionlist, 'sections' => \%sections, 'purpose' => $declaration_purpose }); } else { print STDERR "${file}:$.: error: Cannot parse typedef!\n"; ++$errors; } } sub save_struct_actual($) { my $actual = shift; # strip all spaces from the actual param so that it looks like one string item $actual =~ s/\s*//g; $struct_actual = $struct_actual . $actual . " "; } sub create_parameterlist($$$$) { my $args = shift; my $splitter = shift; my $file = shift; my $declaration_name = shift; my $type; my $param; # temporarily replace commas inside function pointer definition while ($args =~ /(\([^\),]+),/) { $args =~ s/(\([^\),]+),/$1#/g; } foreach my $arg (split($splitter, $args)) { # strip comments $arg =~ s/\/\*.*\*\///; # strip leading/trailing spaces $arg =~ s/^\s*//; $arg =~ s/\s*$//; $arg =~ s/\s+/ /; if ($arg =~ /^#/) { # Treat preprocessor directive as a typeless variable just to fill # corresponding data structures "correctly". Catch it later in # output_* subs. push_parameter($arg, "", $file); } elsif ($arg =~ m/\(.+\)\s*\(/) { # pointer-to-function $arg =~ tr/#/,/; $arg =~ m/[^\(]+\(\*?\s*([\w\.]*)\s*\)/; $param = $1; $type = $arg; $type =~ s/([^\(]+\(\*?)\s*$param/$1/; save_struct_actual($param); push_parameter($param, $type, $file, $declaration_name); } elsif ($arg) { $arg =~ s/\s*:\s*/:/g; $arg =~ s/\s*\[/\[/g; my @args = split('\s*,\s*', $arg); if ($args[0] =~ m/\*/) { $args[0] =~ s/(\*+)\s*/ $1/; } my @first_arg; if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { shift @args; push(@first_arg, split('\s+', $1)); push(@first_arg, $2); } else { @first_arg = split('\s+', shift @args); } unshift(@args, pop @first_arg); $type = join " ", @first_arg; foreach $param (@args) { if ($param =~ m/^(\*+)\s*(.*)/) { save_struct_actual($2); push_parameter($2, "$type $1", $file, $declaration_name); } elsif ($param =~ m/(.*?):(\d+)/) { if ($type ne "") { # skip unnamed bit-fields save_struct_actual($1); push_parameter($1, "$type:$2", $file, $declaration_name) } } else { save_struct_actual($param); push_parameter($param, $type, $file, $declaration_name); } } } } } sub push_parameter($$$$) { my $param = shift; my $type = shift; my $file = shift; my $declaration_name = shift; if (($anon_struct_union == 1) && ($type eq "") && ($param eq "}")) { return; # ignore the ending }; from anon. struct/union } $anon_struct_union = 0; $param =~ s/[\[\)].*//; if ($type eq "" && $param =~ /\.\.\.$/) { if (!$param =~ /\w\.\.\.$/) { # handles unnamed variable parameters $param = "..."; } if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { $parameterdescs{$param} = "variable arguments"; } } elsif ($type eq "" && ($param eq "" or $param eq "void")) { $param="void"; $parameterdescs{void} = "no arguments"; } elsif ($type eq "" && ($param eq "struct" or $param eq "union")) # handle unnamed (anonymous) union or struct: { $type = $param; $param = "{unnamed_" . $param . "}"; $parameterdescs{$param} = "anonymous\n"; $anon_struct_union = 1; } # warn if parameter has no description # (but ignore ones starting with # as these are not parameters # but inline preprocessor statements); # Note: It will also ignore void params and unnamed structs/unions if (!defined $parameterdescs{$param} && $param !~ /^#/) { $parameterdescs{$param} = $undescribed; if (show_warnings($type, $declaration_name)) { print STDERR "${file}:$.: warning: Function parameter or member '$param' not described in '$declaration_name'\n"; ++$warnings; } } # strip spaces from $param so that it is one continuous string # on @parameterlist; # this fixes a problem where check_sections() cannot find # a parameter like "addr[6 + 2]" because it actually appears # as "addr[6", "+", "2]" on the parameter list; # but it's better to maintain the param string unchanged for output, # so just weaken the string compare in check_sections() to ignore # "[blah" in a parameter string; ###$param =~ s/\s*//g; push @parameterlist, $param; $type =~ s/\s\s+/ /g; $parametertypes{$param} = $type; } sub check_sections($$$$$) { my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck) = @_; my @sects = split ' ', $sectcheck; my @prms = split ' ', $prmscheck; my $err; my ($px, $sx); my $prm_clean; # strip trailing "[array size]" and/or beginning "*" foreach $sx (0 .. $#sects) { $err = 1; foreach $px (0 .. $#prms) { $prm_clean = $prms[$px]; $prm_clean =~ s/\[.*\]//; $prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; # ignore array size in a parameter string; # however, the original param string may contain # spaces, e.g.: addr[6 + 2] # and this appears in @prms as "addr[6" since the # parameter list is split at spaces; # hence just ignore "[..." for the sections check; $prm_clean =~ s/\[.*//; ##$prm_clean =~ s/^\**//; if ($prm_clean eq $sects[$sx]) { $err = 0; last; } } if ($err) { if ($decl_type eq "function") { print STDERR "${file}:$.: warning: " . "Excess function parameter " . "'$sects[$sx]' " . "description in '$decl_name'\n"; ++$warnings; } } } } ## # Checks the section describing the return value of a function. sub check_return_section { my $file = shift; my $declaration_name = shift; my $return_type = shift; # Ignore an empty return type (It's a macro) # Ignore functions with a "void" return type. (But don't ignore "void *") if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { return; } if (!defined($sections{$section_return}) || $sections{$section_return} eq "") { print STDERR "${file}:$.: warning: " . "No description found for return value of " . "'$declaration_name'\n"; ++$warnings; } } ## # takes a function prototype and the name of the current file being # processed and spits out all the details stored in the global # arrays/hashes. sub dump_function($$) { my $prototype = shift; my $file = shift; my $noret = 0; $prototype =~ s/^static +//; $prototype =~ s/^extern +//; $prototype =~ s/^asmlinkage +//; $prototype =~ s/^inline +//; $prototype =~ s/^__inline__ +//; $prototype =~ s/^__inline +//; $prototype =~ s/^__always_inline +//; $prototype =~ s/^noinline +//; $prototype =~ s/__init +//; $prototype =~ s/__init_or_module +//; $prototype =~ s/__meminit +//; $prototype =~ s/__must_check +//; $prototype =~ s/__weak +//; $prototype =~ s/__sched +//; my $define = $prototype =~ s/^#\s*define\s+//; #ak added $prototype =~ s/__attribute__\s*\(\( (?: [\w\s]++ # attribute name (?:\([^)]*+\))? # attribute arguments \s*+,? # optional comma at the end )+ \)\)\s+//x; # Yes, this truly is vile. We are looking for: # 1. Return type (may be nothing if we're looking at a macro) # 2. Function name # 3. Function parameters. # # All the while we have to watch out for function pointer parameters # (which IIRC is what the two sections are for), C types (these # regexps don't even start to express all the possibilities), and # so on. # # If you mess with these regexps, it's a good idea to check that # the following functions' documentation still comes out right: # - parport_register_device (function pointer parameters) # - atomic_set (macro) # - pci_match_device, __copy_to_user (long return type) if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) { # This is an object-like macro, it has no return type and no parameter # list. # Function-like macros are not allowed to have spaces between # declaration_name and opening parenthesis (notice the \s+). $return_type = $1; $declaration_name = $2; $noret = 1; } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || $prototype =~ m/^(\w+\s+\w+\s*\*+\s*\w+\s*\*+\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/) { $return_type = $1; $declaration_name = $2; my $args = $3; create_parameterlist($args, ',', $file, $declaration_name); } else { print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n"; return; } my $prms = join " ", @parameterlist; check_sections($file, $declaration_name, "function", $sectcheck, $prms); # This check emits a lot of warnings at the moment, because many # functions don't have a 'Return' doc section. So until the number # of warnings goes sufficiently down, the check is only performed in # verbose mode. # TODO: always perform the check. if ($verbose && !$noret) { check_return_section($file, $declaration_name, $return_type); } output_declaration($declaration_name, 'function', {'function' => $declaration_name, 'module' => $modulename, 'functiontype' => $return_type, 'parameterlist' => \@parameterlist, 'parameterdescs' => \%parameterdescs, 'parametertypes' => \%parametertypes, 'sectionlist' => \@sectionlist, 'sections' => \%sections, 'purpose' => $declaration_purpose }); } sub reset_state { $function = ""; %parameterdescs = (); %parametertypes = (); @parameterlist = (); %sections = (); @sectionlist = (); $sectcheck = ""; $struct_actual = ""; $prototype = ""; $state = STATE_NORMAL; $inline_doc_state = STATE_INLINE_NA; } sub tracepoint_munge($) { my $file = shift; my $tracepointname = 0; my $tracepointargs = 0; if ($prototype =~ m/TRACE_EVENT\((.*?),/) { $tracepointname = $1; } if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { $tracepointname = $1; } if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { $tracepointname = $2; } $tracepointname =~ s/^\s+//; #strip leading whitespace if ($prototype =~ m/TP_PROTO\((.*?)\)/) { $tracepointargs = $1; } if (($tracepointname eq 0) || ($tracepointargs eq 0)) { print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n". "$prototype\n"; } else { $prototype = "static inline void trace_$tracepointname($tracepointargs)"; } } sub syscall_munge() { my $void = 0; $prototype =~ s@[\r\n]+@ @gos; # strip newlines/CR's ## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { if ($prototype =~ m/SYSCALL_DEFINE0/) { $void = 1; ## $prototype = "long sys_$1(void)"; } $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name if ($prototype =~ m/long (sys_.*?),/) { $prototype =~ s/,/\(/; } elsif ($void) { $prototype =~ s/\)/\(void\)/; } # now delete all of the odd-number commas in $prototype # so that arg types & arg names don't have a comma between them my $count = 0; my $len = length($prototype); if ($void) { $len = 0; # skip the for-loop } for (my $ix = 0; $ix < $len; $ix++) { if (substr($prototype, $ix, 1) eq ',') { $count++; if ($count % 2 == 1) { substr($prototype, $ix, 1) = ' '; } } } } sub process_proto_function($$) { my $x = shift; my $file = shift; $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) { # do nothing } elsif ($x =~ /([^\{]*)/) { $prototype .= $1; } if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { $prototype =~ s@/\*.*?\*/@@gos; # strip comments. $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. $prototype =~ s@^\s+@@gos; # strip leading spaces if ($prototype =~ /SYSCALL_DEFINE/) { syscall_munge(); } if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || $prototype =~ /DEFINE_SINGLE_EVENT/) { tracepoint_munge($file); } dump_function($prototype, $file); reset_state(); } } sub process_proto_type($$) { my $x = shift; my $file = shift; $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. $x =~ s@^\s+@@gos; # strip leading spaces $x =~ s@\s+$@@gos; # strip trailing spaces $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line if ($x =~ /^#/) { # To distinguish preprocessor directive from regular declaration later. $x .= ";"; } while (1) { if ( $x =~ /([^\{\};]*)([\{\};])(.*)/ ) { if( length $prototype ) { $prototype .= " " } $prototype .= $1 . $2; ($2 eq '{') && $brcount++; ($2 eq '}') && $brcount--; if (($2 eq ';') && ($brcount == 0)) { dump_declaration($prototype, $file); reset_state(); last; } $x = $3; } else { $prototype .= $x; last; } } } sub map_filename($) { my $file; my ($orig_file) = @_; if (defined($ENV{'SRCTREE'})) { $file = "$ENV{'SRCTREE'}" . "/" . $orig_file; } else { $file = $orig_file; } if (defined($source_map{$file})) { $file = $source_map{$file}; } return $file; } sub process_export_file($) { my ($orig_file) = @_; my $file = map_filename($orig_file); if (!open(IN,"<$file")) { print STDERR "Error: Cannot open file $file\n"; ++$errors; return; } while () { if (/$export_symbol/) { $function_table{$2} = 1; } } close(IN); } # # Parsers for the various processing states. # # STATE_NORMAL: looking for the /** to begin everything. # sub process_normal() { if (/$doc_start/o) { $state = STATE_NAME; # next line is always the function name $in_doc_sect = 0; $declaration_start_line = $. + 1; } } # # STATE_NAME: Looking for the "name - description" line # sub process_name($$) { my $file = shift; my $identifier; my $descr; if (/$doc_block/o) { $state = STATE_DOCBLOCK; $contents = ""; $new_start_line = $. + 1; if ( $1 eq "" ) { $section = $section_intro; } else { $section = $1; } } elsif (/$doc_decl/o) { $identifier = $1; if (/\s*([\w\s]+?)(\(\))?\s*-/) { $identifier = $1; } $state = STATE_BODY; # if there's no @param blocks need to set up default section # here $contents = ""; $section = $section_default; $new_start_line = $. + 1; if (/-(.*)/) { # strip leading/trailing/multiple spaces $descr= $1; $descr =~ s/^\s*//; $descr =~ s/\s*$//; $descr =~ s/\s+/ /g; $declaration_purpose = $descr; $state = STATE_BODY_MAYBE; } else { $declaration_purpose = ""; } if (($declaration_purpose eq "") && $verbose) { print STDERR "${file}:$.: warning: missing initial short description on line:\n"; print STDERR $_; ++$warnings; } if ($identifier =~ m/^struct/) { $decl_type = 'struct'; } elsif ($identifier =~ m/^union/) { $decl_type = 'union'; } elsif ($identifier =~ m/^enum/) { $decl_type = 'enum'; } elsif ($identifier =~ m/^typedef/) { $decl_type = 'typedef'; } else { $decl_type = 'function'; } if ($verbose) { print STDERR "${file}:$.: info: Scanning doc for $identifier\n"; } } else { print STDERR "${file}:$.: warning: Cannot understand $_ on line $.", " - I thought it was a doc line\n"; ++$warnings; $state = STATE_NORMAL; } } # # STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. # sub process_body($$) { my $file = shift; if (/$doc_sect/i) { # case-insensitive for supported section names $newsection = $1; $newcontents = $2; # map the supported section names to the canonical names if ($newsection =~ m/^description$/i) { $newsection = $section_default; } elsif ($newsection =~ m/^context$/i) { $newsection = $section_context; } elsif ($newsection =~ m/^returns?$/i) { $newsection = $section_return; } elsif ($newsection =~ m/^\@return$/) { # special: @return is a section, not a param description $newsection = $section_return; } if (($contents ne "") && ($contents ne "\n")) { if (!$in_doc_sect && $verbose) { print STDERR "${file}:$.: warning: contents before sections\n"; ++$warnings; } dump_section($file, $section, $contents); $section = $section_default; } $in_doc_sect = 1; $state = STATE_BODY; $contents = $newcontents; $new_start_line = $.; while (substr($contents, 0, 1) eq " ") { $contents = substr($contents, 1); } if ($contents ne "") { $contents .= "\n"; } $section = $newsection; $leading_space = undef; } elsif (/$doc_end/) { if (($contents ne "") && ($contents ne "\n")) { dump_section($file, $section, $contents); $section = $section_default; $contents = ""; } # look for doc_com + + doc_end: if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { print STDERR "${file}:$.: warning: suspicious ending line: $_"; ++$warnings; } $prototype = ""; $state = STATE_PROTO; $brcount = 0; } elsif (/$doc_content/) { # miguel-style comment kludge, look for blank lines after # @parameter line to signify start of description if ($1 eq "") { if ($section =~ m/^@/ || $section eq $section_context) { dump_section($file, $section, $contents); $section = $section_default; $contents = ""; $new_start_line = $.; } else { $contents .= "\n"; } $state = STATE_BODY; } elsif ($state == STATE_BODY_MAYBE) { # Continued declaration purpose chomp($declaration_purpose); $declaration_purpose .= " " . $1; $declaration_purpose =~ s/\s+/ /g; } else { my $cont = $1; if ($section =~ m/^@/ || $section eq $section_context) { if (!defined $leading_space) { if ($cont =~ m/^(\s+)/) { $leading_space = $1; } else { $leading_space = ""; } } $cont =~ s/^$leading_space//; } $contents .= $cont . "\n"; } } else { # i dont know - bad line? ignore. print STDERR "${file}:$.: warning: bad line: $_"; ++$warnings; } } # # STATE_PROTO: reading a function/whatever prototype. # sub process_proto($$) { my $file = shift; if (/$doc_inline_oneline/) { $section = $1; $contents = $2; if ($contents ne "") { $contents .= "\n"; dump_section($file, $section, $contents); $section = $section_default; $contents = ""; } } elsif (/$doc_inline_start/) { $state = STATE_INLINE; $inline_doc_state = STATE_INLINE_NAME; } elsif ($decl_type eq 'function') { process_proto_function($_, $file); } else { process_proto_type($_, $file); } } # # STATE_DOCBLOCK: within a DOC: block. # sub process_docblock($$) { my $file = shift; if (/$doc_end/) { dump_doc_section($file, $section, $contents); $section = $section_default; $contents = ""; $function = ""; %parameterdescs = (); %parametertypes = (); @parameterlist = (); %sections = (); @sectionlist = (); $prototype = ""; $state = STATE_NORMAL; } elsif (/$doc_content/) { if ( $1 eq "" ) { $contents .= $blankline; } else { $contents .= $1 . "\n"; } } } # # STATE_INLINE: docbook comments within a prototype. # sub process_inline($$) { my $file = shift; # First line (state 1) needs to be a @parameter if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) { $section = $1; $contents = $2; $new_start_line = $.; if ($contents ne "") { while (substr($contents, 0, 1) eq " ") { $contents = substr($contents, 1); } $contents .= "\n"; } $inline_doc_state = STATE_INLINE_TEXT; # Documentation block end */ } elsif (/$doc_inline_end/) { if (($contents ne "") && ($contents ne "\n")) { dump_section($file, $section, $contents); $section = $section_default; $contents = ""; } $state = STATE_PROTO; $inline_doc_state = STATE_INLINE_NA; # Regular text } elsif (/$doc_content/) { if ($inline_doc_state == STATE_INLINE_TEXT) { $contents .= $1 . "\n"; # nuke leading blank lines if ($contents =~ /^\s*$/) { $contents = ""; } } elsif ($inline_doc_state == STATE_INLINE_NAME) { $inline_doc_state = STATE_INLINE_ERROR; print STDERR "${file}:$.: warning: "; print STDERR "Incorrect use of kernel-doc format: $_"; ++$warnings; } } } sub process_file($) { my $file; my $initial_section_counter = $section_counter; my ($orig_file) = @_; $file = map_filename($orig_file); if (!open(IN,"<$file")) { print STDERR "Error: Cannot open file $file\n"; ++$errors; return; } $. = 1; $section_counter = 0; while () { while (s/\\\s*$//) { $_ .= ; } # Replace tabs by spaces while ($_ =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}; # Hand this line to the appropriate state handler if ($state == STATE_NORMAL) { process_normal(); } elsif ($state == STATE_NAME) { process_name($file, $_); } elsif ($state == STATE_BODY || $state == STATE_BODY_MAYBE) { process_body($file, $_); } elsif ($state == STATE_INLINE) { # scanning for inline parameters process_inline($file, $_); } elsif ($state == STATE_PROTO) { process_proto($file, $_); } elsif ($state == STATE_DOCBLOCK) { process_docblock($file, $_); } } # Make sure we got something interesting. if ($initial_section_counter == $section_counter) { if ($output_mode ne "none") { print STDERR "${file}:1: warning: no structured comments found\n"; } if (($output_selection == OUTPUT_INCLUDE) && ($show_not_found == 1)) { print STDERR " Was looking for '$_'.\n" for keys %function_table; } } } $kernelversion = get_kernel_version(); # generate a sequence of code that will splice in highlighting information # using the s// operator. for (my $k = 0; $k < @highlights; $k++) { my $pattern = $highlights[$k][0]; my $result = $highlights[$k][1]; # print STDERR "scanning pattern:$pattern, highlight:($result)\n"; $dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n"; } # Read the file that maps relative names to absolute names for # separate source and object directories and for shadow trees. if (open(SOURCE_MAP, "<.tmp_filelist.txt")) { my ($relname, $absname); while() { chop(); ($relname, $absname) = (split())[0..1]; $relname =~ s:^/+::; $source_map{$relname} = $absname; } close(SOURCE_MAP); } if ($output_selection == OUTPUT_EXPORTED || $output_selection == OUTPUT_INTERNAL) { push(@export_file_list, @ARGV); foreach (@export_file_list) { chomp; process_export_file($_); } } foreach (@ARGV) { chomp; process_file($_); } if ($verbose && $errors) { print STDERR "$errors errors\n"; } if ($verbose && $warnings) { print STDERR "$warnings warnings\n"; } exit($output_mode eq "none" ? 0 : $errors); multipath-tools-0.11.1/libdmmp/docs/man/000077500000000000000000000000001475246302400200755ustar00rootroot00000000000000multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_free.3000066400000000000000000000007511475246302400236660ustar00rootroot00000000000000.TH "dmmp_context_free" 3 "dmmp_context_free" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_free \- Release the memory of struct dmmp_context. .SH SYNOPSIS .B "void" dmmp_context_free .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. .SH "DESCRIPTION" Release the memory of struct dmmp_context, but the userdata memory defined via \fBdmmp_context_userdata_set\fP will not be touched. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_log_func_set.3000066400000000000000000000015551475246302400254170ustar00rootroot00000000000000.TH "dmmp_context_log_func_set" 3 "dmmp_context_log_func_set" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_log_func_set \- Set log handler function. .SH SYNOPSIS .B "void" dmmp_context_log_func_set .BI "(struct dmmp_context *" ctx "," .BI "void (*" log_func ") (struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args));" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "log_func" 12 Pointer of log handler function. If set to NULL, all log will be ignored. .SH "DESCRIPTION" Set custom log handler. The log handler will be invoked when log message is equal or more important(less value) than log priority setting. Please check manpage libdmmp.h(3) for detail usage. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_log_priority_get.3000066400000000000000000000012141475246302400263210ustar00rootroot00000000000000.TH "dmmp_context_log_priority_get" 3 "dmmp_context_log_priority_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_log_priority_get \- Get log priority. .SH SYNOPSIS .B "int" dmmp_context_log_priority_get .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve current log priority. Valid log priority values are: * DMMP_LOG_PRIORITY_ERROR -- 3 * DMMP_LOG_PRIORITY_WARNING -- 4 * DMMP_LOG_PRIORITY_INFO -- 5 * DMMP_LOG_PRIORITY_DEBUG -- 7 .SH "RETURN" int, log priority. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_log_priority_set.3000066400000000000000000000014361475246302400263430ustar00rootroot00000000000000.TH "dmmp_context_log_priority_set" 3 "dmmp_context_log_priority_set" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_log_priority_set \- Set log priority. .SH SYNOPSIS .B "void" dmmp_context_log_priority_set .BI "(struct dmmp_context *" ctx "," .BI "int " priority ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "priority" 12 int, log priority. .SH "DESCRIPTION" When library generates log message, only equal or more important(less value) message will be forwarded to log handler function. Valid log priority values are: * DMMP_LOG_PRIORITY_ERROR -- 3 * DMMP_LOG_PRIORITY_WARNING -- 4 * DMMP_LOG_PRIORITY_INFO -- 5 * DMMP_LOG_PRIORITY_DEBUG -- 7 .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_new.3000066400000000000000000000013541475246302400235360ustar00rootroot00000000000000.TH "dmmp_context_new" 3 "dmmp_context_new" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_new \- Create struct dmmp_context. .SH SYNOPSIS .B "struct dmmp_context *" dmmp_context_new .BI "(" void ");" .SH ARGUMENTS .IP "void" 12 no arguments .SH "DESCRIPTION" The default logging level (DMMP_LOG_PRIORITY_DEFAULT) is DMMP_LOG_PRIORITY_WARNING which means only warning and error message will be forward to log handler function. The default log handler function will print log message to STDERR, to change so, please use \fBdmmp_context_log_func_set\fP to set your own log handler, check manpage libdmmp.h(3) for detail. .SH "RETURN" Pointer of 'struct dmmp_context'. Should be freed by \fBdmmp_context_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_timeout_get.3000066400000000000000000000010241475246302400252640ustar00rootroot00000000000000.TH "dmmp_context_timeout_get" 3 "dmmp_context_timeout_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_timeout_get \- Get IPC timeout. .SH SYNOPSIS .B "unsigned int" dmmp_context_timeout_get .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve timeout value of IPC connection to multipathd daemon. .SH "RETURN" unsigned int. Timeout in milliseconds. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_timeout_set.3000066400000000000000000000012231475246302400253010ustar00rootroot00000000000000.TH "dmmp_context_timeout_set" 3 "dmmp_context_timeout_set" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_timeout_set \- Set IPC timeout. .SH SYNOPSIS .B "void" dmmp_context_timeout_set .BI "(struct dmmp_context *" ctx "," .BI "unsigned int " tmo ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "tmo" 12 Timeout in milliseconds(1 seconds equal 1000 milliseconds). 0 means infinite, function only return when error or pass. .SH "DESCRIPTION" By default, the IPC to multipathd daemon will timeout after 60 seconds. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_userdata_get.3000066400000000000000000000010171475246302400254100ustar00rootroot00000000000000.TH "dmmp_context_userdata_get" 3 "dmmp_context_userdata_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_userdata_get \- Get user data pointer. .SH SYNOPSIS .B "void *" dmmp_context_userdata_get .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve user data pointer from 'struct dmmp_context'. .SH "RETURN" void *. Pointer of user defined data. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_context_userdata_set.3000066400000000000000000000010631475246302400254250ustar00rootroot00000000000000.TH "dmmp_context_userdata_set" 3 "dmmp_context_userdata_set" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_context_userdata_set \- Set user data pointer. .SH SYNOPSIS .B "void" dmmp_context_userdata_set .BI "(struct dmmp_context *" ctx "," .BI "void *" userdata ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "userdata" 12 Pointer of user defined data. .SH "DESCRIPTION" Store user data pointer into 'struct dmmp_context'. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_flush_mpath.3000066400000000000000000000015341475246302400235130ustar00rootroot00000000000000.TH "dmmp_flush_mpath" 3 "dmmp_flush_mpath" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_flush_mpath \- Flush specified multipath device map if unused. .SH SYNOPSIS .B "int" dmmp_flush_mpath .BI "(struct dmmp_context *" ctx "," .BI "const char *" mpath_name ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "mpath_name" 12 const char *. The name of multipath device map. .SH "DESCRIPTION" Flush a multipath device map specified as parameter, if unused. .SH "RETURN" int. Valid error codes are: * DMMP_OK * DMMP_ERR_BUG * DMMP_ERR_NO_MEMORY * DMMP_ERR_NO_DAEMON * DMMP_ERR_MPATH_BUSY * DMMP_ERR_MPATH_NOT_FOUND * DMMP_ERR_INVALID_ARGUMENT * DMMP_ERR_PERMISSION_DENY Error number could be converted to string by \fBdmmp_strerror\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_last_error_msg.3000066400000000000000000000010631475246302400242200ustar00rootroot00000000000000.TH "dmmp_last_error_msg" 3 "dmmp_last_error_msg" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_last_error_msg \- Retrieves the last error message. .SH SYNOPSIS .B "const char *" dmmp_last_error_msg .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieves the last error message. .SH "RETURN" const char *. No need to free this memory, the resources will get freed when \fBdmmp_context_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_log_priority_str.3000066400000000000000000000011631475246302400246110ustar00rootroot00000000000000.TH "dmmp_log_priority_str" 3 "dmmp_log_priority_str" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_log_priority_str \- Convert log priority to string. .SH SYNOPSIS .B "const char *" dmmp_log_priority_str .BI "(int " priority ");" .SH ARGUMENTS .IP "priority" 12 int. Log priority. .SH "DESCRIPTION" Convert log priority to string (const char *). .SH "RETURN" const char *. Valid string are: * "ERROR" for DMMP_LOG_PRIORITY_ERROR * "WARN " for DMMP_LOG_PRIORITY_WARNING * "INFO " for DMMP_LOG_PRIORITY_INFO * "DEBUG" for DMMP_LOG_PRIORITY_DEBUG * "Invalid argument" for invalid log priority. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_mpath_array_free.3000066400000000000000000000011751475246302400245120ustar00rootroot00000000000000.TH "dmmp_mpath_array_free" 3 "dmmp_mpath_array_free" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_mpath_array_free \- Free 'struct dmmp_mpath' pointer array. .SH SYNOPSIS .B "void" dmmp_mpath_array_free .BI "(struct dmmp_mpath **" dmmp_mps "," .BI "uint32_t " dmmp_mp_count ");" .SH ARGUMENTS .IP "dmmp_mps" 12 Pointer of 'struct dmmp_mpath' array. .IP "dmmp_mp_count" 12 uint32_t, the size of 'dmmp_mps' pointer array. .SH "DESCRIPTION" Free the 'dmmp_mps' pointer array generated by \fBdmmp_mpath_array_get\fP. If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_mpath_array_get.3000066400000000000000000000021631475246302400243460ustar00rootroot00000000000000.TH "dmmp_mpath_array_get" 3 "dmmp_mpath_array_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_mpath_array_get \- Query all existing multipath devices. .SH SYNOPSIS .B "int" dmmp_mpath_array_get .BI "(struct dmmp_context *" ctx "," .BI "struct dmmp_mpath ***" dmmp_mps "," .BI "uint32_t *" dmmp_mp_count ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_mps" 12 Output pointer array of 'struct dmmp_mpath'. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_mp_count" 12 Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Query all existing multipath devices and store them into a pointer array. The memory of 'dmmp_mps' should be freed via \fBdmmp_mpath_array_free\fP. .SH "RETURN" int. Valid error codes are: * DMMP_OK * DMMP_ERR_BUG * DMMP_ERR_NO_MEMORY * DMMP_ERR_NO_DAEMON * DMMP_ERR_INCONSISTENT_DATA Error number could be converted to string by \fBdmmp_strerror\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_mpath_kdev_name_get.3000066400000000000000000000012121475246302400251530ustar00rootroot00000000000000.TH "dmmp_mpath_kdev_name_get" 3 "dmmp_mpath_kdev_name_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_mpath_kdev_name_get \- Retrieve kernel DEVNAME of certain mpath. .SH SYNOPSIS .B "const char *" dmmp_mpath_kdev_name_get .BI "(struct dmmp_mpath *" dmmp_mp ");" .SH ARGUMENTS .IP "dmmp_mp" 12 Pointer of 'struct dmmp_mpath'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve DEVNAME name used by kernel uevent of specified mpath. For example: 'dm-1'. .SH "RETURN" const char *. No need to free this memory, the resources will get freed when \fBdmmp_mpath_array_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_mpath_name_get.3000066400000000000000000000013301475246302400241430ustar00rootroot00000000000000.TH "dmmp_mpath_name_get" 3 "dmmp_mpath_name_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_mpath_name_get \- Retrieve name(alias) of certain mpath. .SH SYNOPSIS .B "const char *" dmmp_mpath_name_get .BI "(struct dmmp_mpath *" dmmp_mp ");" .SH ARGUMENTS .IP "dmmp_mp" 12 Pointer of 'struct dmmp_mpath'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve the name (also known as alias) of certain mpath. When the config 'user_friendly_names' been set 'no', the name will be identical to WWID retrieved by \fBdmmp_mpath_wwid_get\fP. .SH "RETURN" const char *. No need to free this memory, the resources will get freed when \fBdmmp_mpath_array_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_mpath_wwid_get.3000066400000000000000000000010041475246302400241730ustar00rootroot00000000000000.TH "dmmp_mpath_wwid_get" 3 "dmmp_mpath_wwid_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_mpath_wwid_get \- Retrieve WWID of certain mpath. .SH SYNOPSIS .B "const char *" dmmp_mpath_wwid_get .BI "(struct dmmp_mpath *" dmmp_mp ");" .SH ARGUMENTS .IP "dmmp_mp" 12 Pointer of 'struct dmmp_mpath'. If this pointer is NULL, your program will be terminated by assert. .SH "RETURN" const char *. No need to free this memory, the resources will get freed when \fBdmmp_mpath_array_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_array_get.3000066400000000000000000000017021475246302400241670ustar00rootroot00000000000000.TH "dmmp_path_array_get" 3 "dmmp_path_array_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_array_get \- Retrieve path pointer array. .SH SYNOPSIS .B "void" dmmp_path_array_get .BI "(struct dmmp_path_group *" dmmp_pg "," .BI "struct dmmp_path ***" dmmp_ps "," .BI "uint32_t *" dmmp_p_count ");" .SH ARGUMENTS .IP "dmmp_pg" 12 Pointer of 'struct dmmp_path_group'. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_ps" 12 Output pointer of 'struct dmmp_path' pointer array. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_p_count" 12 Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" The memory of output pointer array is hold by 'struct dmmp_mpath', no need to free this memory, the resources will got freed when \fBdmmp_mpath_array_free\fP. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_blk_name_get.3000066400000000000000000000011521475246302400246200ustar00rootroot00000000000000.TH "dmmp_path_blk_name_get" 3 "dmmp_path_blk_name_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_blk_name_get \- Retrieve block name. .SH SYNOPSIS .B "const char *" dmmp_path_blk_name_get .BI "(struct dmmp_path *" dmmp_p ");" .SH ARGUMENTS .IP "dmmp_p" 12 Pointer of 'struct dmmp_path'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve block name of certain path. The example of block names are "sda", "nvme0n1". .SH "RETURN" const char *. No need to free this memory, the resources will get freed when \fBdmmp_mpath_array_free\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_array_get.3000066400000000000000000000020241475246302400254010ustar00rootroot00000000000000.TH "dmmp_path_group_array_get" 3 "dmmp_path_group_array_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_array_get \- Retrieve path groups pointer array. .SH SYNOPSIS .B "void" dmmp_path_group_array_get .BI "(struct dmmp_mpath *" dmmp_mp "," .BI "struct dmmp_path_group ***" dmmp_pgs "," .BI "uint32_t *" dmmp_pg_count ");" .SH ARGUMENTS .IP "dmmp_mp" 12 Pointer of 'struct dmmp_mpath'. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_pgs" 12 Output pointer of 'struct dmmp_path_group' pointer array. If this pointer is NULL, your program will be terminated by assert. .IP "dmmp_pg_count" 12 Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve the path groups of certain mpath. The memory of output pointer array is hold by 'struct dmmp_mpath', no need to free this memory, the resources will got freed when \fBdmmp_mpath_array_free\fP. .SH "RETURN" void multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_id_get.3000066400000000000000000000011131475246302400246550ustar00rootroot00000000000000.TH "dmmp_path_group_id_get" 3 "dmmp_path_group_id_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_id_get \- Retrieve path group ID. .SH SYNOPSIS .B "uint32_t" dmmp_path_group_id_get .BI "(struct dmmp_path_group *" dmmp_pg ");" .SH ARGUMENTS .IP "dmmp_pg" 12 Pointer of 'struct dmmp_path_group'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Retrieve the path group ID which could be used to switch active path group via command: multipathd -k'switch multipath mpathb group $id' .SH "RETURN" uint32_t. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_priority_get.3000066400000000000000000000011061475246302400261440ustar00rootroot00000000000000.TH "dmmp_path_group_priority_get" 3 "dmmp_path_group_priority_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_priority_get \- Retrieve path group priority. .SH SYNOPSIS .B "uint32_t" dmmp_path_group_priority_get .BI "(struct dmmp_path_group *" dmmp_pg ");" .SH ARGUMENTS .IP "dmmp_pg" 12 Pointer of 'struct dmmp_path_group'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" The enabled path group with highest priority will be next active path group if active path group down. .SH "RETURN" uint32_t. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_selector_get.3000066400000000000000000000010761475246302400261110ustar00rootroot00000000000000.TH "dmmp_path_group_selector_get" 3 "dmmp_path_group_selector_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_selector_get \- Retrieve path group selector. .SH SYNOPSIS .B "const char *" dmmp_path_group_selector_get .BI "(struct dmmp_path_group *" dmmp_pg ");" .SH ARGUMENTS .IP "dmmp_pg" 12 Pointer of 'struct dmmp_path_group'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Path group selector determine which path in active path group will be use to next I/O. .SH "RETURN" const char *. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_status_get.3000066400000000000000000000013241475246302400256100ustar00rootroot00000000000000.TH "dmmp_path_group_status_get" 3 "dmmp_path_group_status_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_status_get \- Retrieve path group status. .SH SYNOPSIS .B "uint32_t" dmmp_path_group_status_get .BI "(struct dmmp_path_group *" dmmp_pg ");" .SH ARGUMENTS .IP "dmmp_pg" 12 Pointer of 'struct dmmp_path_group'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" The valid path group statuses are: * DMMP_PATH_GROUP_STATUS_UNKNOWN * DMMP_PATH_GROUP_STATUS_ENABLED -- standby to be active * DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down * DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O .SH "RETURN" uint32_t. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_group_status_str.3000066400000000000000000000011671475246302400256460ustar00rootroot00000000000000.TH "dmmp_path_group_status_str" 3 "dmmp_path_group_status_str" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_group_status_str \- Convert path group status to string. .SH SYNOPSIS .B "const char *" dmmp_path_group_status_str .BI "(uint32_t " pg_status ");" .SH ARGUMENTS .IP "pg_status" 12 uint32_t. Path group status. When provided value is not a valid path group status, return "Invalid argument". .SH "DESCRIPTION" Convert path group status uint32_t to string (const char *). .SH "RETURN" const char *. Valid string are: * "Invalid argument" * "undef" * "enabled" * "disabled" * "active" multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_status_get.3000066400000000000000000000027301475246302400243760ustar00rootroot00000000000000.TH "dmmp_path_status_get" 3 "dmmp_path_status_get" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_status_get \- Retrieve the path status. .SH SYNOPSIS .B "uint32_t" dmmp_path_status_get .BI "(struct dmmp_path *" dmmp_p ");" .SH ARGUMENTS .IP "dmmp_p" 12 Pointer of 'struct dmmp_path'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" The valid path statuses are: * DMMP_PATH_STATUS_UNKNOWN * DMMP_PATH_STATUS_DOWN Path is down and you shouldn't try to send commands to it. * DMMP_PATH_STATUS_UP Path is up and I/O can be sent to it. * DMMP_PATH_STATUS_SHAKY Only emc_clariion checker when path not available for "normal" operations. * DMMP_PATH_STATUS_GHOST Only hp_sw and rdac checkers. Indicates a "passive/standby" path on active/passive HP arrays. These paths will return valid answers to certain SCSI commands (tur, read_capacity, inquiry, start_stop), but will fail I/O commands. The path needs an initialization command to be sent to it in order for I/Os to succeed. * DMMP_PATH_STATUS_PENDING Available for all async checkers when a check IO is in flight. * DMMP_PATH_STATUS_TIMEOUT Only tur checker when command timed out. * DMMP_PATH_STATUS_DELAYED If a path fails after being up for less than delay_watch_checks checks, when it comes back up again, it will not be marked as up until it has been up for delay_wait_checks checks. During this time, it is marked as "delayed". .SH "RETURN" uint32_t. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_path_status_str.3000066400000000000000000000015521475246302400244300ustar00rootroot00000000000000.TH "dmmp_path_status_str" 3 "dmmp_path_status_str" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_path_status_str \- Convert path status to string. .SH SYNOPSIS .B "const char *" dmmp_path_status_str .BI "(uint32_t " path_status ");" .SH ARGUMENTS .IP "path_status" 12 uint32_t. Path status. When provided value is not a valid path status, return "Invalid argument". .SH "DESCRIPTION" Convert path status uint32_t to string (const char *): * DMMP_PATH_STATUS_UNKNOWN -- "undef" * DMMP_PATH_STATUS_DOWN -- "faulty" * DMMP_PATH_STATUS_UP -- "ready" * DMMP_PATH_STATUS_SHAKY -- "shaky" * DMMP_PATH_STATUS_GHOST -- "ghost" * DMMP_PATH_STATUS_PENDING -- "pending" * DMMP_PATH_STATUS_TIMEOUT -- "timeout" * DMMP_PATH_STATUS_REMOVED -- "removed" * DMMP_PATH_STATUS_DELAYED -- "delayed" .SH "RETURN" const char *. The meaning of status value. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_reconfig.3000066400000000000000000000012161475246302400227720ustar00rootroot00000000000000.TH "dmmp_reconfig" 3 "dmmp_reconfig" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_reconfig \- Instruct multipathd daemon to do reconfiguration. .SH SYNOPSIS .B "int" dmmp_reconfig .BI "(struct dmmp_context *" ctx ");" .SH ARGUMENTS .IP "ctx" 12 Pointer of 'struct dmmp_context'. If this pointer is NULL, your program will be terminated by assert. .SH "DESCRIPTION" Instruct multipathd daemon to do reconfiguration. .SH "RETURN" int. Valid error codes are: * DMMP_OK * DMMP_ERR_BUG * DMMP_ERR_NO_MEMORY * DMMP_ERR_NO_DAEMON * DMMP_ERR_PERMISSION_DENY Error number could be converted to string by \fBdmmp_strerror\fP. multipath-tools-0.11.1/libdmmp/docs/man/dmmp_strerror.3000066400000000000000000000017751475246302400230720ustar00rootroot00000000000000.TH "dmmp_strerror" 3 "dmmp_strerror" "March 2018" "Device Mapper Multipath API - libdmmp Manual" .SH NAME dmmp_strerror \- Convert error code to string. .SH SYNOPSIS .B "const char *" dmmp_strerror .BI "(int " rc ");" .SH ARGUMENTS .IP "rc" 12 int. Return code by libdmmp functions. When provided error code is not a valid error code, return "Invalid argument". .SH "DESCRIPTION" Convert error code (int) to string (const char *): * DMMP_OK -- "OK" * DMMP_ERR_BUG -- "BUG of libdmmp library" * DMMP_ERR_NO_MEMORY -- "Out of memory" * DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd, try to set bigger timeout value via dmmp_context_timeout_set ()" * DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon" * DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started" * DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not compatible with current library" * Other invalid error number -- "Invalid argument" .SH "RETURN" const char *. The meaning of provided error code. multipath-tools-0.11.1/libdmmp/docs/man/libdmmp.h.3000066400000000000000000000073061475246302400220410ustar00rootroot00000000000000.TH "libdmmp.h" 3 "January 2016" "Device Mapper Multipath API - libdmmp Manual" .SH NAME libdmmp.h \- Device Mapper Multipath API. .SH SYNOPSIS #include .SH "DESCRIPTION" All the libdmmp public functions ships its own man pages. Use 'man 3 ' to check the detail usage. .SH "USAGE" To use libdmmp in your project, we suggest to use the 'pkg-config' way: * Add this line into your configure.ac: PKG_CHECK_MODULES([LIBDMMP], [libdmmp]) * Add these lines into your Makefile.am: foo_LDFLAGS += $(LIBDMMP_LIBS) foo_CFLAGS += $(LIBDMMP_CFLAGS) .SH LOG HANDLING The log handler function could be set via 'dmmp_context_log_func_set()'. The log priority could be set via 'dmmp_context_log_priority_set()'. By default, the log priorities is 'DMMP_LOG_PRIORITY_WARNING'. By default, the log handler is print log to STDERR, and its code is listed below in case you want to create your own log handler. static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80; static void _log_stderr(struct dmmp_context *ctx, enum dmmp_log_priority priority, const char *file, int line, const char *func_name, const char *format, va_list args) { int printed_bytes = 0; printed_bytes += fprintf(stderr, "libdmmp %s: ", dmmp_log_priority_str(priority)); printed_bytes += vfprintf(stderr, format, args); userdata = dmmp_context_userdata_get(ctx); if (userdata != NULL) fprintf(stderr, "(with user data at memory address %p)", userdata); if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) { fprintf(stderr, "%*s # %s:%s():%d\n", _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, func_name, line); } else { fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); } } .SH "SAMPLE CODE" #include int main(int argc, char *argv[]) { struct dmmp_context *ctx = NULL; struct dmmp_mpath **dmmp_mps = NULL; struct dmmp_path_group **dmmp_pgs = NULL; struct dmmp_path **dmmp_ps = NULL; uint32_t dmmp_mp_count = 0; uint32_t dmmp_pg_count = 0; uint32_t dmmp_p_count = 0; const char *name = NULL; const char *wwid = NULL; uint32_t i = 0; int rc = DMMP_OK; ctx = dmmp_context_new(); dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); // By default, log will be printed to STDERR, you could // change that via dmmp_context_log_func_set() rc = dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count); if (rc != DMMP_OK) { printf("dmmp_mpath_array_get() failed with %d: %s", rc, dmmp_strerror(rc)); goto out; } for (i = 0; i < dmmp_mp_count; ++i) { name = dmmp_mpath_name_get(dmmp_mps[i]); wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); printf("dmmp_mpath_array_get(): Got mpath: %s %s\n", name, wwid); // You could use dmmp_path_group_array_get() to retrieve // path group information and then invoke dmmp_path_array_get() // for path information. } out: dmmp_context_free(ctx); dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); if (rc != DMMP_OK) exit(1); exit(0); } .SH "LICENSE" GPLv2+ .SH "BUG" Please report bug to multipath-tools-0.11.1/libdmmp/docs/split-man.pl000077500000000000000000000020321475246302400215630ustar00rootroot00000000000000#!/usr/bin/perl # Originally From: # https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt # # Changes: # * Create manpage section 3 instead of 9. # * Replace 'Kernel Hackers Manual' to # 'Device Mapper Multipath API - libdmmp Manual' # * Remove LINUX from header. # * Remove DMMP_DLL_EXPORT. $man_sec_num = 3; $title = 'Device Mapper Multipath API - libdmmp Manual'; if ( $#ARGV < 0 ) { die "where do I put the results?\n"; } mkdir $ARGV[0], 0777; $state = 0; while () { if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) { if ( $state == 1 ) { close OUT } $state = 1; $fn = "$ARGV[0]/$1.$man_sec_num"; print STDERR "Creating $fn\n"; open OUT, ">$fn" or die "can't open $fn: $!\n"; # Change man page code from 9 to $man_sec_num; s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/; s/Kernel Hacker's Manual/$title/g; s/LINUX//g; print OUT $_; } elsif ( $state != 0 ) { print OUT $_; } } close OUT; multipath-tools-0.11.1/libdmmp/libdmmp.c000066400000000000000000000306061475246302400201670ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2017 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdmmp/libdmmp.h" #include "libdmmp_private.h" #define DEFAULT_UXSOCK_TIMEOUT 60000 /* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get() * only take 3.5 seconds, so this default value should be OK for most users. */ #define DMMP_IPC_SHOW_JSON_CMD "show maps json" #define DMMP_JSON_MAJOR_KEY "major_version" #define DMMP_JSON_MAJOR_VERSION 0 #define DMMP_JSON_MAPS_KEY "maps" #define ERRNO_STR_BUFF_SIZE 256 #define IPC_MAX_CMD_LEN 512 /* ^ Was MAX_CMD_LEN in ./libmultipath/uxsock.h */ #define LAST_ERR_MSG_BUFF_SIZE 1024 struct dmmp_context { void (*log_func)(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args); int log_priority; void *userdata; unsigned int tmo; char last_err_msg[LAST_ERR_MSG_BUFF_SIZE]; }; /* * The multipathd daemon are using "uxsock_timeout" to define timeout value, * if timeout at daemon side, we will get message "timeout\n". * To unify this timeout with `dmmp_context_timeout_set()`, this function * will keep retry mpath_process_cmd() tile meet the time of * dmmp_context_timeout_get(). * Need to free `*output` string manually. */ static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd, char **output); static int _ipc_connect(struct dmmp_context *ctx, int *fd); _dmmp_getter_func_gen(dmmp_context_log_priority_get, struct dmmp_context, ctx, log_priority, int); _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx, userdata, void *); _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo, unsigned int); _dmmp_getter_func_gen(dmmp_last_error_msg, struct dmmp_context, ctx, last_err_msg, const char *); _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath, dmmp_mpath_free); void dmmp_log(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, ...) { va_list args; if (ctx->log_func == NULL) return; va_start(args, format); ctx->log_func(ctx, priority, file, line, func_name, format, args); if (priority == DMMP_LOG_PRIORITY_ERROR) vsnprintf(ctx->last_err_msg, LAST_ERR_MSG_BUFF_SIZE, format, args); va_end(args); } struct dmmp_context *dmmp_context_new(void) { struct dmmp_context *ctx = NULL; ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context)); if (ctx == NULL) return NULL; ctx->log_func = dmmp_log_stderr; ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT; ctx->userdata = NULL; ctx->tmo = DEFAULT_UXSOCK_TIMEOUT; memset(ctx->last_err_msg, 0, LAST_ERR_MSG_BUFF_SIZE); return ctx; } void dmmp_context_free(struct dmmp_context *ctx) { free(ctx); } void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority) { assert(ctx != NULL); ctx->log_priority = priority; } void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo) { assert(ctx != NULL); ctx->tmo = tmo; } void dmmp_context_log_func_set (struct dmmp_context *ctx, void (*log_func)(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args)) { assert(ctx != NULL); ctx->log_func = log_func; } void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata) { assert(ctx != NULL); ctx->userdata = userdata; } int dmmp_mpath_array_get(struct dmmp_context *ctx, struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count) { struct dmmp_mpath *dmmp_mp = NULL; int rc = DMMP_OK; char *j_str = NULL; json_object *j_obj = NULL; json_object *j_obj_map = NULL; enum json_tokener_error j_err = json_tokener_success; json_tokener *j_token = NULL; struct array_list *ar_maps = NULL; uint32_t i = 0; int cur_json_major_version = -1; int ar_maps_len = -1; int ipc_fd = -1; assert(ctx != NULL); assert(dmmp_mps != NULL); assert(dmmp_mp_count != NULL); *dmmp_mps = NULL; *dmmp_mp_count = 0; _good(_ipc_connect(ctx, &ipc_fd), rc, out); _good(_process_cmd(ctx, ipc_fd, DMMP_IPC_SHOW_JSON_CMD, &j_str), rc, out); _debug(ctx, "Got json output from multipathd: '%s'", j_str); j_token = json_tokener_new(); if (j_token == NULL) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: json_tokener_new() returned NULL"); goto out; } j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1); if (j_obj == NULL) { rc = DMMP_ERR_IPC_ERROR; j_err = json_tokener_get_error(j_token); _error(ctx, "Failed to parse JSON output from multipathd IPC: " "%s", json_tokener_error_desc(j_err)); goto out; } _json_obj_get_value(ctx, j_obj, cur_json_major_version, DMMP_JSON_MAJOR_KEY, json_type_int, json_object_get_int, rc, out); if (cur_json_major_version != DMMP_JSON_MAJOR_VERSION) { rc = DMMP_ERR_INCOMPATIBLE; _error(ctx, "Incompatible multipathd JSON major version %d, " "should be %d", cur_json_major_version, DMMP_JSON_MAJOR_VERSION); goto out; } _debug(ctx, "multipathd JSON major version(%d) check pass", DMMP_JSON_MAJOR_VERSION); _json_obj_get_value(ctx, j_obj, ar_maps, DMMP_JSON_MAPS_KEY, json_type_array, json_object_get_array, rc, out); if (ar_maps == NULL) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: Got NULL map array from " "_json_obj_get_value()"); goto out; } ar_maps_len = array_list_length(ar_maps); if (ar_maps_len < 0) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: Got negative length for ar_maps"); goto out; } else if (ar_maps_len == 0) goto out; else *dmmp_mp_count = ar_maps_len & UINT32_MAX; *dmmp_mps = (struct dmmp_mpath **) malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count)); _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out); for (; i < *dmmp_mp_count; ++i) (*dmmp_mps)[i] = NULL; for (i = 0; i < *dmmp_mp_count; ++i) { j_obj_map = array_list_get_idx(ar_maps, i); if (j_obj_map == NULL) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: array_list_get_idx() return NULL"); goto out; } dmmp_mp = dmmp_mpath_new(); _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out); (*dmmp_mps)[i] = dmmp_mp; _good(dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out); } out: if (ipc_fd >= 0) mpath_disconnect(ipc_fd); free(j_str); if (j_token != NULL) json_tokener_free(j_token); if (j_obj != NULL) json_object_put(j_obj); if (rc != DMMP_OK) { dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count); *dmmp_mps = NULL; *dmmp_mp_count = 0; } return rc; } static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd, char **output) { int errno_save = 0; int rc = DMMP_OK; char errno_str_buff[ERRNO_STR_BUFF_SIZE]; struct timespec start_ts; struct timespec cur_ts; unsigned int ipc_tmo = 0; bool flag_check_tmo = false; unsigned int elapsed = 0; assert(output != NULL); assert(ctx != NULL); assert(cmd != NULL); *output = NULL; if (clock_gettime(CLOCK_MONOTONIC, &start_ts) != 0) { _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time " "via clock_gettime(), error %d", errno); return DMMP_ERR_BUG; } ipc_tmo = ctx->tmo; if (ctx->tmo == 0) ipc_tmo = DEFAULT_UXSOCK_TIMEOUT; invoke: _debug(ctx, "Invoking IPC command '%s' with IPC tmo %u milliseconds", cmd, ipc_tmo); flag_check_tmo = false; if (mpath_process_cmd(fd, cmd, output, ipc_tmo) != 0) { errno_save = errno; memset(errno_str_buff, 0, ERRNO_STR_BUFF_SIZE); strerror_r(errno_save, errno_str_buff, ERRNO_STR_BUFF_SIZE); if (errno_save == ETIMEDOUT) { flag_check_tmo = true; } else { _error(ctx, "IPC failed when process command '%s' with " "error %d(%s)", cmd, errno_save, errno_str_buff); _debug(ctx, "%s", *output); rc = DMMP_ERR_IPC_ERROR; goto out; } } if ((*output != NULL) && (strncmp(*output, "fail\ntimeout", strlen("fail\ntimeout")) == 0)) flag_check_tmo = true; if (flag_check_tmo == true) { free(*output); *output = NULL; if (ctx->tmo == 0) { _debug(ctx, "IPC timeout, but user requested infinite " "timeout"); goto invoke; } if (clock_gettime(CLOCK_MONOTONIC, &cur_ts) != 0) { _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time " "via clock_gettime(), error %d", errno); rc = DMMP_ERR_BUG; goto out; } elapsed = (cur_ts.tv_sec - start_ts.tv_sec) * 1000 + (cur_ts.tv_nsec - start_ts.tv_nsec) / 1000000; if (elapsed >= ctx->tmo) { rc = DMMP_ERR_IPC_TIMEOUT; _error(ctx, "Timeout, try to increase it via " "dmmp_context_timeout_set()"); goto out; } if (ctx->tmo != 0) ipc_tmo = ctx->tmo - elapsed; _debug(ctx, "IPC timeout, but user requested timeout has not " "reached yet, still have %u milliseconds", ipc_tmo); goto invoke; } if ((*output == NULL) || (strlen(*output) == 0)) { _error(ctx, "IPC return empty reply for command %s", cmd); rc = DMMP_ERR_IPC_ERROR; goto out; } if (strncmp(*output, "fail\n", 5) == 0) { if (strncmp(*output, "fail\npermission deny", strlen("fail\npermission deny")) == 0) { _error(ctx, "Permission deny, need to be root"); rc = DMMP_ERR_PERMISSION_DENY; goto out; } else if (strncmp(*output, "fail\nmap or partition in use", strlen("fail\nmap or partition in use")) == 0) { _error(ctx, "Specified mpath is in use"); rc = DMMP_ERR_MPATH_BUSY; goto out; } else if (strncmp(*output, "fail\ndevice not found", strlen("fail\ndevice not found")) == 0) { _error(ctx, "Specified mpath not found"); rc = DMMP_ERR_MPATH_NOT_FOUND; goto out; } _error(ctx, "Got unexpected error for cmd '%s': '%s'", cmd, *output); rc = DMMP_ERR_BUG; } out: if (rc != DMMP_OK) { free(*output); *output = NULL; } return rc; } static int _ipc_connect(struct dmmp_context *ctx, int *fd) { int rc = DMMP_OK; int errno_save = 0; char errno_str_buff[ERRNO_STR_BUFF_SIZE]; assert(ctx != NULL); assert(fd != NULL); *fd = -1; *fd = mpath_connect(); if (*fd == -1) { errno_save = errno; memset(errno_str_buff, 0, ERRNO_STR_BUFF_SIZE); strerror_r(errno_save, errno_str_buff, ERRNO_STR_BUFF_SIZE); if (errno_save == ECONNREFUSED) { rc = DMMP_ERR_NO_DAEMON; _error(ctx, "Socket connection refuse. " "Maybe multipathd daemon is not running"); } else { _error(ctx, "IPC failed with error %d(%s)", errno_save, errno_str_buff); rc = DMMP_ERR_IPC_ERROR; } } return rc; } int dmmp_flush_mpath(struct dmmp_context *ctx, const char *mpath_name) { int rc = DMMP_OK; int ipc_fd = -1; char cmd[IPC_MAX_CMD_LEN]; char *output = NULL; assert(ctx != NULL); assert(mpath_name != NULL); snprintf(cmd, IPC_MAX_CMD_LEN, "del map %s", mpath_name); if (strlen(cmd) == IPC_MAX_CMD_LEN - 1) { rc = DMMP_ERR_INVALID_ARGUMENT; _error(ctx, "Invalid mpath name %s", mpath_name); goto out; } _good(_ipc_connect(ctx, &ipc_fd), rc, out); _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out); /* _process_cmd() already make sure output is not NULL */ if (strncmp(output, "ok", strlen("ok")) != 0) { rc = DMMP_ERR_BUG; _error(ctx, "Got unexpected output for cmd '%s': '%s'", cmd, output); } out: if (ipc_fd >= 0) mpath_disconnect(ipc_fd); free(output); return rc; } int dmmp_reconfig(struct dmmp_context *ctx) { int rc = DMMP_OK; int ipc_fd = -1; char *output = NULL; char cmd[IPC_MAX_CMD_LEN]; snprintf(cmd, IPC_MAX_CMD_LEN, "%s", "reconfigure"); _good(_ipc_connect(ctx, &ipc_fd), rc, out); _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out); out: if (ipc_fd >= 0) mpath_disconnect(ipc_fd); free(output); return rc; } multipath-tools-0.11.1/libdmmp/libdmmp.pc.in000066400000000000000000000003031475246302400207430ustar00rootroot00000000000000includedir=__INCLUDEDIR__ libdir=__LIBDIR__ Name: libdmmp Version: __VERSION__ Description: Device mapper multipath management library Requires: Libs: -L${libdir} -ldmmp Cflags: -I${includedir} multipath-tools-0.11.1/libdmmp/libdmmp/000077500000000000000000000000001475246302400200165ustar00rootroot00000000000000multipath-tools-0.11.1/libdmmp/libdmmp/libdmmp.h000066400000000000000000000471231475246302400216220ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2017 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #ifndef LIBDMMP_H_INCLUDED #define LIBDMMP_H_INCLUDED #include #include #ifdef __cplusplus extern "C" { #endif #define DMMP_DLL_EXPORT __attribute__ ((visibility ("default"))) #define DMMP_DLL_LOCAL __attribute__ ((visibility ("hidden"))) #define DMMP_OK 0 #define DMMP_ERR_BUG 1 #define DMMP_ERR_NO_MEMORY 2 #define DMMP_ERR_IPC_TIMEOUT 3 #define DMMP_ERR_IPC_ERROR 4 #define DMMP_ERR_NO_DAEMON 5 #define DMMP_ERR_INCOMPATIBLE 6 #define DMMP_ERR_MPATH_BUSY 7 #define DMMP_ERR_MPATH_NOT_FOUND 8 #define DMMP_ERR_INVALID_ARGUMENT 9 #define DMMP_ERR_PERMISSION_DENY 10 /* * Use the syslog severity level as log priority */ #define DMMP_LOG_PRIORITY_ERROR 3 #define DMMP_LOG_PRIORITY_WARNING 4 #define DMMP_LOG_PRIORITY_INFO 6 #define DMMP_LOG_PRIORITY_DEBUG 7 #define DMMP_LOG_PRIORITY_DEFAULT DMMP_LOG_PRIORITY_WARNING /** * dmmp_log_priority_str() - Convert log priority to string. * * Convert log priority to string (const char *). * * @priority: * int. Log priority. * * Return: * const char *. Valid string are: * * * "ERROR" for DMMP_LOG_PRIORITY_ERROR * * * "WARN " for DMMP_LOG_PRIORITY_WARNING * * * "INFO " for DMMP_LOG_PRIORITY_INFO * * * "DEBUG" for DMMP_LOG_PRIORITY_DEBUG * * * "Invalid argument" for invalid log priority. */ DMMP_DLL_EXPORT const char *dmmp_log_priority_str(int priority); struct DMMP_DLL_EXPORT dmmp_context; struct DMMP_DLL_EXPORT dmmp_mpath; struct DMMP_DLL_EXPORT dmmp_path_group; #define DMMP_PATH_GROUP_STATUS_UNKNOWN 0 #define DMMP_PATH_GROUP_STATUS_ENABLED 1 #define DMMP_PATH_GROUP_STATUS_DISABLED 2 #define DMMP_PATH_GROUP_STATUS_ACTIVE 3 struct DMMP_DLL_EXPORT dmmp_path; #define DMMP_PATH_STATUS_UNKNOWN 0 //#define DMMP_PATH_STATUS_UNCHECKED 1 // ^ print.h does not expose this. #define DMMP_PATH_STATUS_DOWN 2 #define DMMP_PATH_STATUS_UP 3 #define DMMP_PATH_STATUS_SHAKY 4 #define DMMP_PATH_STATUS_GHOST 5 #define DMMP_PATH_STATUS_PENDING 6 #define DMMP_PATH_STATUS_TIMEOUT 7 //#define DMMP_PATH_STATUS_REMOVED 8 // ^ print.h does not expose this. #define DMMP_PATH_STATUS_DELAYED 9 /** * dmmp_strerror() - Convert error code to string. * * Convert error code (int) to string (const char *): * * * DMMP_OK -- "OK" * * * DMMP_ERR_BUG -- "BUG of libdmmp library" * * * DMMP_ERR_NO_MEMORY -- "Out of memory" * * * DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd, * try to set bigger timeout value via dmmp_context_timeout_set ()" * * * DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon" * * * DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started" * * * DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not * compatible with current library" * * * Other invalid error number -- "Invalid argument" * * @rc: * int. Return code by libdmmp functions. When provided error code is not a * valid error code, return "Invalid argument". * * Return: * const char *. The meaning of provided error code. * */ DMMP_DLL_EXPORT const char *dmmp_strerror(int rc); /** * dmmp_context_new() - Create struct dmmp_context. * * The default logging level (DMMP_LOG_PRIORITY_DEFAULT) is * DMMP_LOG_PRIORITY_WARNING which means only warning and error message will be * forward to log handler function. The default log handler function will print * log message to STDERR, to change so, please use dmmp_context_log_func_set() * to set your own log handler, check manpage libdmmp.h(3) for detail. * * Return: * Pointer of 'struct dmmp_context'. Should be freed by * dmmp_context_free(). */ DMMP_DLL_EXPORT struct dmmp_context *dmmp_context_new(void); /** * dmmp_context_free() - Release the memory of struct dmmp_context. * * Release the memory of struct dmmp_context, but the userdata memory defined * via dmmp_context_userdata_set() will not be touched. * * @ctx: * Pointer of 'struct dmmp_context'. * Return: * void */ DMMP_DLL_EXPORT void dmmp_context_free(struct dmmp_context *ctx); /** * dmmp_context_timeout_set() - Set IPC timeout. * * By default, the IPC to multipathd daemon will timeout after 60 seconds. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * @tmo: * Timeout in milliseconds(1 seconds equal 1000 milliseconds). * 0 means infinite, function only return when error or pass. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo); /** * dmmp_context_timeout_get() - Get IPC timeout. * * Retrieve timeout value of IPC connection to multipathd daemon. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * unsigned int. Timeout in milliseconds. */ DMMP_DLL_EXPORT unsigned int dmmp_context_timeout_get(struct dmmp_context *ctx); /** * dmmp_context_log_priority_set() - Set log priority. * * * When library generates log message, only equal or more important(less value) * message will be forwarded to log handler function. Valid log priority values * are: * * * DMMP_LOG_PRIORITY_ERROR -- 3 * * * DMMP_LOG_PRIORITY_WARNING -- 4 * * * DMMP_LOG_PRIORITY_INFO -- 5 * * * DMMP_LOG_PRIORITY_DEBUG -- 7 * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * @priority: * int, log priority. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority); /** * dmmp_context_log_priority_get() - Get log priority. * * Retrieve current log priority. Valid log priority values are: * * * DMMP_LOG_PRIORITY_ERROR -- 3 * * * DMMP_LOG_PRIORITY_WARNING -- 4 * * * DMMP_LOG_PRIORITY_INFO -- 5 * * * DMMP_LOG_PRIORITY_DEBUG -- 7 * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * int, log priority. */ DMMP_DLL_EXPORT int dmmp_context_log_priority_get(struct dmmp_context *ctx); /** * dmmp_context_log_func_set() - Set log handler function. * * Set custom log handler. The log handler will be invoked when log message * is equal or more important(less value) than log priority setting. * Please check manpage libdmmp.h(3) for detail usage. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * @log_func: * Pointer of log handler function. If set to NULL, all log will be * ignored. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_context_log_func_set (struct dmmp_context *ctx, void (*log_func) (struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args)); /** * dmmp_context_userdata_set() - Set user data pointer. * * Store user data pointer into 'struct dmmp_context'. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * @userdata: * Pointer of user defined data. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata); /** * dmmp_context_userdata_get() - Get user data pointer. * * Retrieve user data pointer from 'struct dmmp_context'. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * void *. Pointer of user defined data. */ DMMP_DLL_EXPORT void *dmmp_context_userdata_get(struct dmmp_context *ctx); /** * dmmp_mpath_array_get() - Query all existing multipath devices. * * Query all existing multipath devices and store them into a pointer array. * The memory of 'dmmp_mps' should be freed via dmmp_mpath_array_free(). * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_mps: * Output pointer array of 'struct dmmp_mpath'. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_mp_count: * Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array. * If this pointer is NULL, your program will be terminated by assert. * * Return: * int. Valid error codes are: * * * DMMP_OK * * * DMMP_ERR_BUG * * * DMMP_ERR_NO_MEMORY * * * DMMP_ERR_NO_DAEMON * * * DMMP_ERR_INCONSISTENT_DATA * * Error number could be converted to string by dmmp_strerror(). */ DMMP_DLL_EXPORT int dmmp_mpath_array_get(struct dmmp_context *ctx, struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count); /** * dmmp_mpath_array_free() - Free 'struct dmmp_mpath' pointer array. * * Free the 'dmmp_mps' pointer array generated by dmmp_mpath_array_get(). * If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing. * * @dmmp_mps: * Pointer of 'struct dmmp_mpath' array. * @dmmp_mp_count: * uint32_t, the size of 'dmmp_mps' pointer array. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_mpath_array_free(struct dmmp_mpath **dmmp_mps, uint32_t dmmp_mp_count); /** * dmmp_mpath_wwid_get() - Retrieve WWID of certain mpath. * * @dmmp_mp: * Pointer of 'struct dmmp_mpath'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. No need to free this memory, the resources will get * freed when dmmp_mpath_array_free(). */ DMMP_DLL_EXPORT const char *dmmp_mpath_wwid_get(struct dmmp_mpath *dmmp_mp); /** * dmmp_mpath_name_get() - Retrieve name(alias) of certain mpath. * * Retrieve the name (also known as alias) of certain mpath. * When the config 'user_friendly_names' been set 'no', the name will be * identical to WWID retrieved by dmmp_mpath_wwid_get(). * * @dmmp_mp: * Pointer of 'struct dmmp_mpath'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. No need to free this memory, the resources will get * freed when dmmp_mpath_array_free(). */ DMMP_DLL_EXPORT const char *dmmp_mpath_name_get(struct dmmp_mpath *dmmp_mp); /** * dmmp_mpath_kdev_name_get() - Retrieve kernel DEVNAME of certain mpath. * * Retrieve DEVNAME name used by kernel uevent of specified mpath. * For example: 'dm-1'. * * @dmmp_mp: * Pointer of 'struct dmmp_mpath'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. No need to free this memory, the resources will get * freed when dmmp_mpath_array_free(). */ DMMP_DLL_EXPORT const char *dmmp_mpath_kdev_name_get (struct dmmp_mpath *dmmp_mp); /** * dmmp_path_group_array_get() - Retrieve path groups pointer array. * * Retrieve the path groups of certain mpath. * * The memory of output pointer array is hold by 'struct dmmp_mpath', no * need to free this memory, the resources will got freed when * dmmp_mpath_array_free(). * * @dmmp_mp: * Pointer of 'struct dmmp_mpath'. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_pgs: * Output pointer of 'struct dmmp_path_group' pointer array. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_pg_count: * Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array. * If this pointer is NULL, your program will be terminated by assert. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_path_group_array_get (struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs, uint32_t *dmmp_pg_count); /** * dmmp_path_group_id_get() - Retrieve path group ID. * * Retrieve the path group ID which could be used to switch active path group * via command: * * multipathd -k'switch multipath mpathb group $id' * * @dmmp_pg: * Pointer of 'struct dmmp_path_group'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * uint32_t. */ DMMP_DLL_EXPORT uint32_t dmmp_path_group_id_get (struct dmmp_path_group *dmmp_pg); /** * dmmp_path_group_priority_get() - Retrieve path group priority. * * The enabled path group with highest priority will be next active path group * if active path group down. * * @dmmp_pg: * Pointer of 'struct dmmp_path_group'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * uint32_t. */ DMMP_DLL_EXPORT uint32_t dmmp_path_group_priority_get (struct dmmp_path_group *dmmp_pg); /** * dmmp_path_group_status_get() - Retrieve path group status. * * The valid path group statuses are: * * * DMMP_PATH_GROUP_STATUS_UNKNOWN * * * DMMP_PATH_GROUP_STATUS_ENABLED -- standby to be active * * * DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down * * * DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O * * @dmmp_pg: * Pointer of 'struct dmmp_path_group'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * uint32_t. */ DMMP_DLL_EXPORT uint32_t dmmp_path_group_status_get (struct dmmp_path_group *dmmp_pg); /** * dmmp_path_group_status_str() - Convert path group status to string. * * Convert path group status uint32_t to string (const char *). * * @pg_status: * uint32_t. Path group status. * When provided value is not a valid path group status, return "Invalid * argument". * * Return: * const char *. Valid string are: * * * "Invalid argument" * * * "undef" * * * "enabled" * * * "disabled" * * * "active" */ DMMP_DLL_EXPORT const char *dmmp_path_group_status_str(uint32_t pg_status); /** * dmmp_path_group_selector_get() - Retrieve path group selector. * * Path group selector determine which path in active path group will be * use to next I/O. * * @dmmp_pg: * Pointer of 'struct dmmp_path_group'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. */ DMMP_DLL_EXPORT const char *dmmp_path_group_selector_get (struct dmmp_path_group *dmmp_pg); /** * dmmp_path_array_get() - Retrieve path pointer array. * * The memory of output pointer array is hold by 'struct dmmp_mpath', no * need to free this memory, the resources will got freed when * dmmp_mpath_array_free(). * * @dmmp_pg: * Pointer of 'struct dmmp_path_group'. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_ps: * Output pointer of 'struct dmmp_path' pointer array. * If this pointer is NULL, your program will be terminated by assert. * @dmmp_p_count: * Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array. * If this pointer is NULL, your program will be terminated by assert. * * Return: * void */ DMMP_DLL_EXPORT void dmmp_path_array_get(struct dmmp_path_group *dmmp_pg, struct dmmp_path ***dmmp_ps, uint32_t *dmmp_p_count); /** * dmmp_path_blk_name_get() - Retrieve block name. * * Retrieve block name of certain path. The example of block names are "sda", * "nvme0n1". * * @dmmp_p: * Pointer of 'struct dmmp_path'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. No need to free this memory, the resources will get * freed when dmmp_mpath_array_free(). */ DMMP_DLL_EXPORT const char *dmmp_path_blk_name_get(struct dmmp_path *dmmp_p); /** * dmmp_path_status_get() - Retrieve the path status. * * The valid path statuses are: * * * DMMP_PATH_STATUS_UNKNOWN * * * DMMP_PATH_STATUS_DOWN * * Path is down and you shouldn't try to send commands to it. * * * DMMP_PATH_STATUS_UP * * Path is up and I/O can be sent to it. * * * DMMP_PATH_STATUS_SHAKY * * Only emc_clariion checker when path not available for "normal" * operations. * * * DMMP_PATH_STATUS_GHOST * * Only hp_sw and rdac checkers. Indicates a "passive/standby" * path on active/passive HP arrays. These paths will return valid * answers to certain SCSI commands (tur, read_capacity, inquiry, * start_stop), but will fail I/O commands. The path needs an * initialization command to be sent to it in order for I/Os to * succeed. * * * DMMP_PATH_STATUS_PENDING * * Available for all async checkers when a check IO is in flight. * * * DMMP_PATH_STATUS_TIMEOUT * * Only tur checker when command timed out. * * * DMMP_PATH_STATUS_DELAYED * * If a path fails after being up for less than delay_watch_checks checks, * when it comes back up again, it will not be marked as up until it has * been up for delay_wait_checks checks. During this time, it is marked as * "delayed". * * @dmmp_p: * Pointer of 'struct dmmp_path'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * uint32_t. */ DMMP_DLL_EXPORT uint32_t dmmp_path_status_get(struct dmmp_path *dmmp_p); /** * dmmp_path_status_str() - Convert path status to string. * * Convert path status uint32_t to string (const char *): * * * DMMP_PATH_STATUS_UNKNOWN -- "undef" * * * DMMP_PATH_STATUS_DOWN -- "faulty" * * * DMMP_PATH_STATUS_UP -- "ready" * * * DMMP_PATH_STATUS_SHAKY -- "shaky" * * * DMMP_PATH_STATUS_GHOST -- "ghost" * * * DMMP_PATH_STATUS_PENDING -- "pending" * * * DMMP_PATH_STATUS_TIMEOUT -- "timeout" * * * DMMP_PATH_STATUS_REMOVED -- "removed" * * * DMMP_PATH_STATUS_DELAYED -- "delayed" * * @path_status: * uint32_t. Path status. * When provided value is not a valid path status, return * "Invalid argument". * * Return: * const char *. The meaning of status value. */ DMMP_DLL_EXPORT const char *dmmp_path_status_str(uint32_t path_status); /** * dmmp_flush_mpath() - Flush specified multipath device map if unused. * * Flush a multipath device map specified as parameter, if unused. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * @mpath_name: * const char *. The name of multipath device map. * * Return: * int. Valid error codes are: * * * DMMP_OK * * * DMMP_ERR_BUG * * * DMMP_ERR_NO_MEMORY * * * DMMP_ERR_NO_DAEMON * * * DMMP_ERR_MPATH_BUSY * * * DMMP_ERR_MPATH_NOT_FOUND * * * DMMP_ERR_INVALID_ARGUMENT * * * DMMP_ERR_PERMISSION_DENY * * Error number could be converted to string by dmmp_strerror(). */ DMMP_DLL_EXPORT int dmmp_flush_mpath(struct dmmp_context *ctx, const char *mpath_name); /** * dmmp_reconfig() - Instruct multipathd daemon to do reconfiguration. * * Instruct multipathd daemon to do reconfiguration. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * int. Valid error codes are: * * * DMMP_OK * * * DMMP_ERR_BUG * * * DMMP_ERR_NO_MEMORY * * * DMMP_ERR_NO_DAEMON * * * DMMP_ERR_PERMISSION_DENY * * Error number could be converted to string by dmmp_strerror(). */ DMMP_DLL_EXPORT int dmmp_reconfig(struct dmmp_context *ctx); /** * dmmp_last_error_msg() - Retrieves the last error message. * * Retrieves the last error message. * * @ctx: * Pointer of 'struct dmmp_context'. * If this pointer is NULL, your program will be terminated by assert. * * Return: * const char *. No need to free this memory, the resources will get * freed when dmmp_context_free(). */ DMMP_DLL_EXPORT const char *dmmp_last_error_msg(struct dmmp_context *ctx); #ifdef __cplusplus } /* End of extern "C" */ #endif #endif /* End of LIBDMMP_H_INCLUDED */ multipath-tools-0.11.1/libdmmp/libdmmp_misc.c000066400000000000000000000061521475246302400212010ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2017 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #include #include #include #include #include #include #include #include #include "libdmmp/libdmmp.h" #include "libdmmp_private.h" #define DMMP_LOG_STRERR_ALIGN_WIDTH 80 /* ^ Only used in dmmp_log_stderr() for pretty log output. * When provided log message is less than 80 bytes, fill it with space, then * print code file name, function name, line after the 80th bytes. */ static const struct _num_str_conv _DMMP_RC_MSG_CONV[] = { {DMMP_OK, "OK"}, {DMMP_ERR_NO_MEMORY, "Out of memory"}, {DMMP_ERR_BUG, "BUG of libdmmp library"}, {DMMP_ERR_IPC_TIMEOUT, "Timeout when communicate with multipathd, " "try to increase it via " "dmmp_context_timeout_set()"}, {DMMP_ERR_IPC_ERROR, "Error when communicate with multipathd daemon"}, {DMMP_ERR_NO_DAEMON, "The multipathd daemon not started"}, {DMMP_ERR_INCOMPATIBLE, "Incompatible multipathd daemon version"}, {DMMP_ERR_MPATH_BUSY, "Specified multipath device map is in use"}, {DMMP_ERR_MPATH_NOT_FOUND, "Specified multipath not found"}, {DMMP_ERR_INVALID_ARGUMENT, "Invalid argument"}, {DMMP_ERR_PERMISSION_DENY, "Permission deny"}, }; _dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG_CONV); static const struct _num_str_conv _DMMP_PRI_CONV[] = { {DMMP_LOG_PRIORITY_DEBUG, "DEBUG"}, {DMMP_LOG_PRIORITY_INFO, "INFO"}, {DMMP_LOG_PRIORITY_WARNING, "WARNING"}, {DMMP_LOG_PRIORITY_ERROR, "ERROR"}, }; _dmmp_str_func_gen(dmmp_log_priority_str, int, priority, _DMMP_PRI_CONV); void dmmp_log_stderr(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args) { int printed_bytes = 0; void *userdata = NULL; printed_bytes += fprintf(stderr, "libdmmp %s: ", dmmp_log_priority_str(priority)); printed_bytes += vfprintf(stderr, format, args); userdata = dmmp_context_userdata_get(ctx); if (userdata != NULL) fprintf(stderr, "(userdata address: %p)", userdata); /* ^ Just demonstrate how userdata could be used and * bypass clang static analyzer about unused ctx argument warning */ if (printed_bytes < DMMP_LOG_STRERR_ALIGN_WIDTH) { fprintf(stderr, "%*s # %s:%s():%d\n", DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, func_name, line); } else { fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); } } multipath-tools-0.11.1/libdmmp/libdmmp_mp.c000066400000000000000000000106641475246302400206650ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #include #include #include #include #include #include #include #include "libdmmp/libdmmp.h" #include "libdmmp_private.h" struct dmmp_mpath { char *wwid; char *alias; uint32_t dmmp_pg_count; struct dmmp_path_group **dmmp_pgs; char *kdev_name; }; _dmmp_getter_func_gen(dmmp_mpath_name_get, struct dmmp_mpath, dmmp_mp, alias, const char *); _dmmp_getter_func_gen(dmmp_mpath_wwid_get, struct dmmp_mpath, dmmp_mp, wwid, const char *); _dmmp_getter_func_gen(dmmp_mpath_kdev_name_get, struct dmmp_mpath, dmmp_mp, kdev_name, const char *); struct dmmp_mpath *dmmp_mpath_new(void) { struct dmmp_mpath *dmmp_mp = NULL; dmmp_mp = (struct dmmp_mpath *) malloc(sizeof(struct dmmp_mpath)); if (dmmp_mp != NULL) { dmmp_mp->wwid = NULL; dmmp_mp->alias = NULL; dmmp_mp->dmmp_pg_count = 0; dmmp_mp->dmmp_pgs = NULL; } return dmmp_mp; } int dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp, json_object *j_obj_map) { int rc = DMMP_OK; const char *wwid = NULL; const char *alias = NULL; struct array_list *ar_pgs = NULL; int ar_pgs_len = -1; uint32_t i = 0; struct dmmp_path_group *dmmp_pg = NULL; const char *kdev_name = NULL; assert(ctx != NULL); assert(dmmp_mp != NULL); assert(j_obj_map != NULL); _json_obj_get_value(ctx, j_obj_map, wwid, "uuid", json_type_string, json_object_get_string, rc, out); _json_obj_get_value(ctx, j_obj_map, alias, "name", json_type_string, json_object_get_string, rc, out); _json_obj_get_value(ctx, j_obj_map, kdev_name, "sysfs", json_type_string, json_object_get_string, rc, out); _dmmp_null_or_empty_str_check(ctx, wwid, rc, out); _dmmp_null_or_empty_str_check(ctx, alias, rc, out); dmmp_mp->wwid = strdup(wwid); _dmmp_alloc_null_check(ctx, dmmp_mp->wwid, rc, out); dmmp_mp->alias = strdup(alias); _dmmp_alloc_null_check(ctx, dmmp_mp->alias, rc, out); dmmp_mp->kdev_name = strdup(kdev_name); _dmmp_alloc_null_check(ctx, dmmp_mp->kdev_name, rc, out); _json_obj_get_value(ctx, j_obj_map, ar_pgs, "path_groups", json_type_array, json_object_get_array, rc, out); ar_pgs_len = array_list_length(ar_pgs); if (ar_pgs_len < 0) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: Got negative length for ar_pgs"); goto out; } else if (ar_pgs_len == 0) goto out; else dmmp_mp->dmmp_pg_count = ar_pgs_len & UINT32_MAX; dmmp_mp->dmmp_pgs = (struct dmmp_path_group **) malloc(sizeof(struct dmmp_path_group *) * dmmp_mp->dmmp_pg_count); _dmmp_alloc_null_check(ctx, dmmp_mp->dmmp_pgs, rc, out); for (; i < dmmp_mp->dmmp_pg_count; ++i) dmmp_mp->dmmp_pgs[i] = NULL; for (i = 0; i < dmmp_mp->dmmp_pg_count; ++i) { dmmp_pg = dmmp_path_group_new(); _dmmp_alloc_null_check(ctx, dmmp_pg, rc, out); dmmp_mp->dmmp_pgs[i] = dmmp_pg; _good(dmmp_path_group_update(ctx, dmmp_pg, array_list_get_idx(ar_pgs, i)), rc, out); } _debug(ctx, "Got mpath wwid: '%s', alias: '%s'", dmmp_mp->wwid, dmmp_mp->alias); out: if (rc != DMMP_OK) dmmp_mpath_free(dmmp_mp); return rc; } void dmmp_mpath_free(struct dmmp_mpath *dmmp_mp) { if (dmmp_mp == NULL) return ; free((char *) dmmp_mp->alias); free((char *) dmmp_mp->wwid); free((char *) dmmp_mp->kdev_name); if (dmmp_mp->dmmp_pgs != NULL) _dmmp_path_group_array_free(dmmp_mp->dmmp_pgs, dmmp_mp->dmmp_pg_count); free(dmmp_mp); } void dmmp_path_group_array_get(struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs, uint32_t *dmmp_pg_count) { assert(dmmp_mp != NULL); assert(dmmp_pgs != NULL); assert(dmmp_pg_count != NULL); *dmmp_pgs = dmmp_mp->dmmp_pgs; *dmmp_pg_count = dmmp_mp->dmmp_pg_count; } multipath-tools-0.11.1/libdmmp/libdmmp_path.c000066400000000000000000000064031475246302400212010ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #include #include #include #include #include #include "libdmmp/libdmmp.h" #include "libdmmp_private.h" #define DMMP_SHOW_PS_INDEX_BLK_NAME 0 #define DMMP_SHOW_PS_INDEX_STATUS 1 #define DMMP_SHOW_PS_INDEX_WWID 2 #define DMMP_SHOW_PS_INDEX_PGID 3 struct dmmp_path { char *blk_name; uint32_t status; }; static const struct _num_str_conv DMMP_PATH_STATUS_CONV[] = { {DMMP_PATH_STATUS_UNKNOWN, "undef"}, {DMMP_PATH_STATUS_UP, "ready"}, {DMMP_PATH_STATUS_DOWN, "faulty"}, {DMMP_PATH_STATUS_SHAKY, "shaky"}, {DMMP_PATH_STATUS_GHOST, "ghost"}, {DMMP_PATH_STATUS_PENDING, "i/o pending"}, {DMMP_PATH_STATUS_TIMEOUT, "i/o timeout"}, {DMMP_PATH_STATUS_DELAYED, "delayed"}, }; _dmmp_str_func_gen(dmmp_path_status_str, uint32_t, path_status, DMMP_PATH_STATUS_CONV); _dmmp_str_conv_func_gen(_dmmp_path_status_str_conv, ctx, path_status_str, uint32_t, DMMP_PATH_STATUS_UNKNOWN, DMMP_PATH_STATUS_CONV); _dmmp_getter_func_gen(dmmp_path_blk_name_get, struct dmmp_path, dmmp_p, blk_name, const char *); _dmmp_getter_func_gen(dmmp_path_status_get, struct dmmp_path, dmmp_p, status, uint32_t); struct dmmp_path *dmmp_path_new(void) { struct dmmp_path *dmmp_p = NULL; dmmp_p = (struct dmmp_path *) malloc(sizeof(struct dmmp_path)); if (dmmp_p != NULL) { dmmp_p->blk_name = NULL; dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN; } return dmmp_p; } int dmmp_path_update(struct dmmp_context *ctx, struct dmmp_path *dmmp_p, json_object *j_obj_p) { int rc = DMMP_OK; const char *blk_name = NULL; const char *status_str = NULL; assert(ctx != NULL); assert(dmmp_p != NULL); assert(j_obj_p != NULL); _json_obj_get_value(ctx, j_obj_p, blk_name, "dev", json_type_string, json_object_get_string, rc, out); _json_obj_get_value(ctx, j_obj_p, status_str, "chk_st", json_type_string, json_object_get_string, rc, out); _dmmp_null_or_empty_str_check(ctx, blk_name, rc, out); _dmmp_null_or_empty_str_check(ctx, status_str, rc, out); dmmp_p->blk_name = strdup(blk_name); _dmmp_alloc_null_check(ctx, dmmp_p->blk_name, rc, out); dmmp_p->status = _dmmp_path_status_str_conv(ctx, status_str); _debug(ctx, "Got path blk_name: '%s'", dmmp_p->blk_name); _debug(ctx, "Got path status: %s(%" PRIu32 ")", dmmp_path_status_str(dmmp_p->status), dmmp_p->status); out: if (rc != DMMP_OK) dmmp_path_free(dmmp_p); return rc; } void dmmp_path_free(struct dmmp_path *dmmp_p) { if (dmmp_p == NULL) return; free(dmmp_p->blk_name); free(dmmp_p); } multipath-tools-0.11.1/libdmmp/libdmmp_pg.c000066400000000000000000000140471475246302400206560ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #include #include #include #include #include #include #include "libdmmp/libdmmp.h" #include "libdmmp_private.h" #define DMMP_SHOW_PGS_CMD "show groups raw format %w|%g|%p|%t|%s" #define DMMP_SHOW_PG_INDEX_WWID 0 #define DMMP_SHOW_PG_INDEX_PG_ID 1 #define DMMP_SHOW_PG_INDEX_PRI 2 #define DMMP_SHOW_PG_INDEX_STATUS 3 #define DMMP_SHOW_PG_INDEX_SELECTOR 4 struct dmmp_path_group { uint32_t id; /* ^ pgindex of struct path, will be used for path group switch */ uint32_t status; uint32_t priority; char *selector; uint32_t dmmp_p_count; struct dmmp_path **dmmp_ps; }; static const struct _num_str_conv DMMP_PATH_GROUP_STATUS_CONV[] = { {DMMP_PATH_GROUP_STATUS_UNKNOWN, "undef"}, {DMMP_PATH_GROUP_STATUS_ACTIVE, "active"}, {DMMP_PATH_GROUP_STATUS_DISABLED, "disabled"}, {DMMP_PATH_GROUP_STATUS_ENABLED, "enabled"}, }; _dmmp_str_func_gen(dmmp_path_group_status_str, uint32_t, pg_status, DMMP_PATH_GROUP_STATUS_CONV); _dmmp_str_conv_func_gen(_dmmp_path_group_status_str_conv, ctx, pg_status_str, uint32_t, DMMP_PATH_GROUP_STATUS_UNKNOWN, DMMP_PATH_GROUP_STATUS_CONV); _dmmp_getter_func_gen(dmmp_path_group_id_get, struct dmmp_path_group, dmmp_pg, id, uint32_t); _dmmp_getter_func_gen(dmmp_path_group_status_get, struct dmmp_path_group, dmmp_pg, status, uint32_t); _dmmp_getter_func_gen(dmmp_path_group_priority_get, struct dmmp_path_group, dmmp_pg, priority, uint32_t); _dmmp_getter_func_gen(dmmp_path_group_selector_get, struct dmmp_path_group, dmmp_pg, selector, const char *); _dmmp_array_free_func_gen(_dmmp_path_group_array_free, struct dmmp_path_group, dmmp_path_group_free); struct dmmp_path_group *dmmp_path_group_new(void) { struct dmmp_path_group *dmmp_pg = NULL; dmmp_pg = (struct dmmp_path_group *) malloc(sizeof(struct dmmp_path_group)); if (dmmp_pg != NULL) { dmmp_pg->id = DMMP_PATH_GROUP_ID_UNKNOWN; dmmp_pg->status = DMMP_PATH_GROUP_STATUS_UNKNOWN; dmmp_pg->priority = 0; dmmp_pg->selector = NULL; dmmp_pg->dmmp_p_count = 0; dmmp_pg->dmmp_ps = NULL; } return dmmp_pg; } int dmmp_path_group_update(struct dmmp_context *ctx, struct dmmp_path_group *dmmp_pg, json_object *j_obj_pg) { int rc = DMMP_OK; uint32_t id = 0; int priority_int = -1 ; const char *status_str = NULL; const char *selector = NULL; struct array_list *ar_ps = NULL; int ar_ps_len = -1; uint32_t i = 0; struct dmmp_path *dmmp_p = NULL; assert(ctx != NULL); assert(dmmp_pg != NULL); assert(j_obj_pg != NULL); _json_obj_get_value(ctx, j_obj_pg, status_str, "dm_st", json_type_string, json_object_get_string, rc, out); _json_obj_get_value(ctx, j_obj_pg, selector, "selector", json_type_string, json_object_get_string, rc, out); _json_obj_get_value(ctx, j_obj_pg, priority_int, "pri", json_type_int, json_object_get_int, rc, out); _json_obj_get_value(ctx, j_obj_pg, id, "group", json_type_int, json_object_get_int, rc, out); dmmp_pg->priority = (priority_int <= 0) ? 0 : priority_int & UINT32_MAX; _dmmp_null_or_empty_str_check(ctx, status_str, rc, out); _dmmp_null_or_empty_str_check(ctx, selector, rc, out); dmmp_pg->selector = strdup(selector); _dmmp_alloc_null_check(ctx, dmmp_pg->selector, rc, out); dmmp_pg->id = id; if (dmmp_pg->id == DMMP_PATH_GROUP_ID_UNKNOWN) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: Got unknown(%d) path group ID", DMMP_PATH_GROUP_ID_UNKNOWN); goto out; } dmmp_pg->status = _dmmp_path_group_status_str_conv(ctx, status_str); _json_obj_get_value(ctx, j_obj_pg, ar_ps, "paths", json_type_array, json_object_get_array, rc, out); ar_ps_len = array_list_length(ar_ps); if (ar_ps_len < 0) { rc = DMMP_ERR_BUG; _error(ctx, "BUG: Got negative length for ar_ps"); goto out; } else if (ar_ps_len == 0) goto out; else dmmp_pg->dmmp_p_count = ar_ps_len & UINT32_MAX; dmmp_pg->dmmp_ps = (struct dmmp_path **) malloc(sizeof(struct dmmp_path *) * dmmp_pg->dmmp_p_count); _dmmp_alloc_null_check(ctx, dmmp_pg->dmmp_ps, rc, out); for (; i < dmmp_pg->dmmp_p_count; ++i) dmmp_pg->dmmp_ps[i] = NULL; for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) { dmmp_p = dmmp_path_new(); _dmmp_alloc_null_check(ctx, dmmp_p, rc, out); dmmp_pg->dmmp_ps[i] = dmmp_p; _good(dmmp_path_update(ctx, dmmp_p, array_list_get_idx(ar_ps, i)), rc, out); } _debug(ctx, "Got path group id: %" PRIu32 "", dmmp_pg->id); _debug(ctx, "Got path group priority: %" PRIu32 "", dmmp_pg->priority); _debug(ctx, "Got path group status: %s(%" PRIu32 ")", dmmp_path_group_status_str(dmmp_pg->status), dmmp_pg->status); _debug(ctx, "Got path group selector: '%s'", dmmp_pg->selector); out: if (rc != DMMP_OK) dmmp_path_group_free(dmmp_pg); return rc; } void dmmp_path_group_free(struct dmmp_path_group *dmmp_pg) { uint32_t i = 0; if (dmmp_pg == NULL) return; free((char *) dmmp_pg->selector); if (dmmp_pg->dmmp_ps != NULL) { for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) { dmmp_path_free(dmmp_pg->dmmp_ps[i]); } free(dmmp_pg->dmmp_ps); } free(dmmp_pg); } void dmmp_path_array_get(struct dmmp_path_group *mp_pg, struct dmmp_path ***mp_paths, uint32_t *dmmp_p_count) { assert(mp_pg != NULL); assert(mp_paths != NULL); assert(dmmp_p_count != NULL); *mp_paths = mp_pg->dmmp_ps; *dmmp_p_count = mp_pg->dmmp_p_count; } multipath-tools-0.11.1/libdmmp/libdmmp_private.h000066400000000000000000000146501475246302400217270ustar00rootroot00000000000000/* * Copyright (C) 2015 - 2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge * Todd Gill */ #ifndef LIBDMMP_PRIVATE_H_INCLUDED #define LIBDMMP_PRIVATE_H_INCLUDED /* * Notes: * Internal/Private functions does not check input argument but using * assert() to abort if NULL pointer found in argument. */ #include #include #include #include #include #include "libdmmp/libdmmp.h" #ifdef __cplusplus extern "C" { #endif #define _good(rc, rc_val, out) \ do { \ rc_val = rc; \ if (rc_val != DMMP_OK) \ goto out; \ } while(0) #define DMMP_PATH_GROUP_ID_UNKNOWN 0 struct DMMP_DLL_LOCAL _num_str_conv; struct _num_str_conv { const uint32_t value; const char *str; }; #define _dmmp_str_func_gen(func_name, var_type, var, conv_array) \ const char *func_name(var_type var) { \ size_t i = 0; \ uint32_t tmp_var = var & UINT32_MAX; \ /* In the whole libdmmp, we don't have negative value */ \ for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \ if ((conv_array[i].value) == tmp_var) \ return conv_array[i].str; \ } \ return "Invalid argument"; \ } #define _dmmp_str_conv_func_gen(func_name, ctx, var_name, out_type, \ unknown_value, conv_array) \ static out_type func_name(struct dmmp_context *ctx, const char *var_name) { \ size_t i = 0; \ for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \ if (strcmp(conv_array[i].str, var_name) == 0) \ return conv_array[i].value; \ } \ _warn(ctx, "Got unknown " #var_name ": '%s'", var_name); \ return unknown_value; \ } #define _json_obj_get_value(ctx, j_obj, out_value, key, value_type, \ value_func, rc, out) \ do { \ json_type j_type = json_type_null; \ json_object *j_obj_tmp = NULL; \ if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != true) { \ _error(ctx, "Invalid JSON output from multipathd IPC: " \ "key '%s' not found", key); \ rc = DMMP_ERR_IPC_ERROR; \ goto out; \ } \ if (j_obj_tmp == NULL) { \ _error(ctx, "BUG: Got NULL j_obj_tmp from " \ "json_object_object_get_ex() while it return TRUE"); \ rc = DMMP_ERR_BUG; \ goto out; \ } \ j_type = json_object_get_type(j_obj_tmp); \ if (j_type != value_type) { \ _error(ctx, "Invalid value type for key'%s' of JSON output " \ "from multipathd IPC. Should be %s(%d), " \ "but got %s(%d)", key, json_type_to_name(value_type), \ value_type, json_type_to_name(j_type), j_type); \ rc = DMMP_ERR_IPC_ERROR; \ goto out; \ } \ out_value = value_func(j_obj_tmp); \ } while(0); DMMP_DLL_LOCAL int _dmmp_ipc_exec(struct dmmp_context *ctx, const char *cmd, char **output); DMMP_DLL_LOCAL struct dmmp_mpath *dmmp_mpath_new(void); DMMP_DLL_LOCAL struct dmmp_path_group *dmmp_path_group_new(void); DMMP_DLL_LOCAL struct dmmp_path *dmmp_path_new(void); DMMP_DLL_LOCAL int dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp, json_object *j_obj_map); DMMP_DLL_LOCAL int dmmp_path_group_update(struct dmmp_context *ctx, struct dmmp_path_group *dmmp_pg, json_object *j_obj_pg); DMMP_DLL_LOCAL int dmmp_path_update(struct dmmp_context *ctx, struct dmmp_path *dmmp_p, json_object *j_obj_p); DMMP_DLL_LOCAL void dmmp_mpath_free(struct dmmp_mpath *dmmp_mp); DMMP_DLL_LOCAL void dmmp_path_group_free(struct dmmp_path_group *dmmp_pg); DMMP_DLL_LOCAL void _dmmp_path_group_array_free (struct dmmp_path_group **dmmp_pgs, uint32_t dmmp_pg_count); DMMP_DLL_LOCAL void dmmp_path_free(struct dmmp_path *dmmp_p); DMMP_DLL_LOCAL void dmmp_log(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, ...) __attribute__((format(printf, 6, 7))); DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc); DMMP_DLL_LOCAL void dmmp_log_stderr(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args) __attribute__((format(printf, 6, 0))); #define _dmmp_log_cond(ctx, prio, arg...) \ do { \ if (dmmp_context_log_priority_get(ctx) >= prio) \ dmmp_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, \ ## arg); \ } while (0) #define _debug(ctx, arg...) \ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_DEBUG, ## arg) #define _info(ctx, arg...) \ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_INFO, ## arg) #define _warn(ctx, arg...) \ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_WARNING, ## arg) #define _error(ctx, arg...) \ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_ERROR, ## arg) /* * Check pointer returned by malloc() or strdup(), if NULL, set * rc as DMMP_ERR_NO_MEMORY, report error and goto goto_out. */ #define _dmmp_alloc_null_check(ctx, ptr, rc, goto_out) \ do { \ if (ptr == NULL) { \ rc = DMMP_ERR_NO_MEMORY; \ _error(ctx, "%s", dmmp_strerror(rc)); \ goto goto_out; \ } \ } while(0) #define _dmmp_null_or_empty_str_check(ctx, var, rc, goto_out) \ do { \ if (var == NULL) { \ rc = DMMP_ERR_BUG; \ _error(ctx, "BUG: Got NULL " #var); \ goto goto_out; \ } \ if (strlen(var) == 0) { \ rc = DMMP_ERR_BUG; \ _error(ctx, "BUG: Got empty " #var); \ goto goto_out; \ } \ } while(0) #define _dmmp_getter_func_gen(func_name, struct_name, struct_data, \ prop_name, prop_type) \ prop_type func_name(struct_name *struct_data) \ { \ assert(struct_data != NULL); \ return struct_data->prop_name; \ } #define _dmmp_array_free_func_gen(func_name, struct_name, struct_free_func) \ void func_name(struct_name **ptr_array, uint32_t ptr_count) \ { \ uint32_t i = 0; \ if (ptr_array == NULL) \ return; \ for (; i < ptr_count; ++i) \ struct_free_func(ptr_array[i]); \ free(ptr_array); \ } #ifdef __cplusplus } /* End of extern "C" */ #endif #endif /* End of LIBDMMP_PRIVATE_H_INCLUDED */ multipath-tools-0.11.1/libdmmp/test/000077500000000000000000000000001475246302400173515ustar00rootroot00000000000000multipath-tools-0.11.1/libdmmp/test/Makefile000066400000000000000000000016021475246302400210100ustar00rootroot00000000000000# Makefile # # Copyright (C) 2015-2016 Gris Ge # TOPDIR := ../.. include ../../Makefile.inc _libdmmpdir=../$(libdmmpdir) _mpathcmddir=../$(mpathcmddir) TEST_EXEC = libdmmp_test SPD_TEST_EXEC = libdmmp_speed_test CPPFLAGS += -I$(_libdmmpdir) LDFLAGS += -L$(_libdmmpdir) -ldmmp all: $(TEST_EXEC) $(SPD_TEST_EXEC) check: $(TEST_EXEC) $(SPD_TEST_EXEC) $(Q)sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \ valgrind --quiet --leak-check=full \ --show-reachable=no --show-possibly-lost=no \ --trace-children=yes --error-exitcode=1 \ ./$(TEST_EXEC) $(MAKE) speed_test speed_test: $(SPD_TEST_EXEC) $(Q)sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \ time -p ./$(SPD_TEST_EXEC) clean: dep_clean $(Q)$(RM) -f $(TEST_EXEC) $(SPD_TEST_EXEC) OBJS = $(TEST_EXEC).o $(SPD_TEST_EXEC).o include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libdmmp/test/libdmmp_speed_test.c000066400000000000000000000025771475246302400233730ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge */ #include #include #include #include #include #include #include #include int main(void) { struct dmmp_context *ctx = NULL; struct dmmp_mpath **dmmp_mps = NULL; uint32_t dmmp_mp_count = 0; int rc = EXIT_SUCCESS; ctx = dmmp_context_new(); dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_WARNING); if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) { printf("FAILED\n"); rc = EXIT_FAILURE; } else { printf("Got %" PRIu32 " mpath\n", dmmp_mp_count); dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); } dmmp_context_free(ctx); exit(rc); } multipath-tools-0.11.1/libdmmp/test/libdmmp_test.c000066400000000000000000000134631475246302400222070ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Gris Ge */ #include #include #include #include #include #include #include #include #include #define FAIL(rc, out, ...) \ do { \ rc = EXIT_FAILURE; \ fprintf(stderr, "FAIL: "__VA_ARGS__ ); \ goto out; \ } while(0) #define PASS(...) fprintf(stdout, "PASS: "__VA_ARGS__ ); #define FILE_NAME_SIZE 256 #define TMO 60000 /* Forcing timeout to 60 seconds */ int test_paths(struct dmmp_path_group *mp_pg) { struct dmmp_path **mp_ps = NULL; uint32_t mp_p_count = 0; uint32_t i = 0; const char *blk_name = NULL; int rc = EXIT_SUCCESS; dmmp_path_array_get(mp_pg, &mp_ps, &mp_p_count); if (mp_p_count == 0) FAIL(rc, out, "dmmp_path_array_get(): Got no path\n"); for (i = 0; i < mp_p_count; ++i) { blk_name = dmmp_path_blk_name_get(mp_ps[i]); if (blk_name == NULL) FAIL(rc, out, "dmmp_path_blk_name_get(): Got NULL\n"); PASS("dmmp_path_blk_name_get(): %s\n", blk_name); PASS("dmmp_path_status_get(): %" PRIu32 " -- %s\n", dmmp_path_status_get(mp_ps[i]), dmmp_path_status_str(dmmp_path_status_get(mp_ps[i]))); } out: return rc; } int test_path_groups(struct dmmp_mpath *dmmp_mp) { struct dmmp_path_group **dmmp_pgs = NULL; uint32_t dmmp_pg_count = 0; uint32_t i = 0; int rc = EXIT_SUCCESS; dmmp_path_group_array_get(dmmp_mp, &dmmp_pgs, &dmmp_pg_count); if ((dmmp_pg_count == 0) && (dmmp_pgs != NULL)) FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is not NULL " "but mp_pg_count is 0\n"); if ((dmmp_pg_count != 0) && (dmmp_pgs == NULL)) FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is NULL " "but mp_pg_count is not 0\n"); if (dmmp_pg_count == 0) FAIL(rc, out, "dmmp_path_group_array_get(): " "Got 0 path group\n"); PASS("dmmp_path_group_array_get(): Got %" PRIu32 " path groups\n", dmmp_pg_count); for (i = 0; i < dmmp_pg_count; ++i) { PASS("dmmp_path_group_id_get(): %" PRIu32 "\n", dmmp_path_group_id_get(dmmp_pgs[i])); PASS("dmmp_path_group_priority_get(): %" PRIu32 "\n", dmmp_path_group_priority_get(dmmp_pgs[i])); PASS("dmmp_path_group_status_get(): %" PRIu32 " -- %s\n", dmmp_path_group_status_get(dmmp_pgs[i]), dmmp_path_group_status_str (dmmp_path_group_status_get(dmmp_pgs[i]))); PASS("dmmp_path_group_selector_get(): %s\n", dmmp_path_group_selector_get(dmmp_pgs[i])); rc = test_paths(dmmp_pgs[i]); if (rc != 0) goto out; } out: return rc; } int main(void) { struct dmmp_context *ctx = NULL; struct dmmp_mpath **dmmp_mps = NULL; uint32_t dmmp_mp_count = 0; uint32_t old_dmmp_mp_count = 0; const char *name = NULL; const char *wwid = NULL; const char *kdev = NULL; uint32_t i = 0; int rc = EXIT_SUCCESS; const char *old_name = NULL; bool found = false; ctx = dmmp_context_new(); dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); dmmp_context_userdata_set(ctx, ctx); dmmp_context_userdata_set(ctx, NULL); dmmp_context_timeout_set(ctx, TMO); if (dmmp_context_timeout_get(ctx) != TMO) FAIL(rc, out, "dmmp_context_timeout_set(): Failed to set " "timeout to %u\n", TMO); if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) FAIL(rc, out, "dmmp_mpath_array_get() failed: %s\n", dmmp_last_error_msg(ctx)); if (dmmp_mp_count == 0) FAIL(rc, out, "dmmp_mpath_array_get(): " "Got no multipath devices\n"); PASS("dmmp_mpath_array_get(): Got %" PRIu32 " mpath\n", dmmp_mp_count); for (i = 0; i < dmmp_mp_count; ++i) { name = dmmp_mpath_name_get(dmmp_mps[i]); wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); kdev = dmmp_mpath_kdev_name_get(dmmp_mps[i]); if ((name == NULL) ||(wwid == NULL) || (kdev == NULL)) FAIL(rc, out, "dmmp_mpath_array_get(): Got NULL name or wwid"); PASS("dmmp_mpath_array_get(): Got mpath(%s): %s %s\n", kdev, name, wwid); rc = test_path_groups(dmmp_mps[i]); if (rc != 0) goto out; } old_name = strdup(name); if (old_name == NULL) FAIL(rc, out, "strdup(): no memory\n"); old_dmmp_mp_count = dmmp_mp_count; dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); if (dmmp_flush_mpath(ctx, old_name) != DMMP_OK) FAIL(rc, out, "dmmp_flush_mpath(): failed %s\n", dmmp_last_error_msg(ctx)); PASS("dmmp_flush_mpath(): OK\n"); if (dmmp_reconfig(ctx) != DMMP_OK) FAIL(rc, out, "dmmp_reconfig() failed: %s\n", dmmp_last_error_msg(ctx)); PASS("dmmp_reconfig(): OK\n"); if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) FAIL(rc, out, "dmmp_mpath_array_get() failed: %s\n", dmmp_last_error_msg(ctx)); if (dmmp_mp_count == 0) FAIL(rc, out, "dmmp_mpath_array_get(): " "Got no multipath devices\n"); if (dmmp_mp_count != old_dmmp_mp_count) FAIL(rc, out, "Got different mpath count after reconfig: " "old %" PRIu32 ", new %" PRIu32 "\n", old_dmmp_mp_count, dmmp_mp_count); for (i = 0; i < dmmp_mp_count; ++i) { if (strcmp(old_name, dmmp_mpath_name_get(dmmp_mps[i])) == 0) { found = true; break; } } if (found == false) FAIL(rc, out, "dmmp_reconfig() does not recreate deleted " "mpath %s\n", old_name); out: dmmp_context_free(ctx); exit(rc); } multipath-tools-0.11.1/libmpathcmd/000077500000000000000000000000001475246302400172325ustar00rootroot00000000000000multipath-tools-0.11.1/libmpathcmd/Makefile000066400000000000000000000013451475246302400206750ustar00rootroot00000000000000include ../Makefile.inc DEVLIB := libmpathcmd.so CFLAGS += $(LIB_CFLAGS) OBJS := mpath_cmd.o all: $(DEVLIB) include $(TOPDIR)/rules.mk install: all $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(includedir) $(Q)$(INSTALL_PROGRAM) -m 644 mpath_cmd.h $(DESTDIR)$(includedir) uninstall: $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) $(Q)$(RM) $(DESTDIR)$(includedir)/mpath_cmd.h clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi $(NV_VERSION_SCRIPT) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmpathcmd/libmpathcmd.version000066400000000000000000000012141475246302400231230ustar00rootroot00000000000000/* * Copyright (c) 2020 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later * * libmpathcmd ABI * * The ABI of libmpathcmd is supposed to remain stable. Removing symbols * or altering existing symbols' semantics is not allowed. When changing a * a symbol, either use a new name, or explicit symver directives. * * See libmultipath.version for general policy about version numbers. */ LIBMPATHCMD_1.0.0 { global: mpath_connect; __mpath_connect; mpath_disconnect; mpath_process_cmd; mpath_recv_reply; mpath_recv_reply_len; mpath_recv_reply_data; mpath_send_cmd; }; LIBMPATHCMD_1.1.0 { global: mpath_connect__; } LIBMPATHCMD_1.0.0; multipath-tools-0.11.1/libmpathcmd/mpath_cmd.c000066400000000000000000000107201475246302400213320ustar00rootroot00000000000000/* * Copyright (C) 2015 Red Hat, Inc. * * This file is part of the device-mapper multipath userspace tools. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "mpath_cmd.h" /* * keep reading until its all read */ static ssize_t read_all(int fd, void *buf, size_t len, unsigned int timeout) { size_t total = 0; ssize_t n; int ret; struct pollfd pfd; while (len) { pfd.fd = fd; pfd.events = POLLIN; ret = poll(&pfd, 1, timeout); if (!ret) { errno = ETIMEDOUT; return -1; } else if (ret < 0) { if (errno == EINTR) continue; return -1; } else if (!(pfd.revents & POLLIN)) continue; n = recv(fd, buf, len, 0); if (n < 0) { if ((errno == EINTR) || (errno == EAGAIN)) continue; return -1; } if (!n) return total; buf = n + (char *)buf; len -= n; total += n; } return total; } /* * keep writing until it's all sent */ static size_t write_all(int fd, const void *buf, size_t len) { size_t total = 0; while (len) { ssize_t n = send(fd, buf, len, MSG_NOSIGNAL); if (n < 0) { if ((errno == EINTR) || (errno == EAGAIN)) continue; return total; } if (!n) return total; buf = n + (const char *)buf; len -= n; total += n; } return total; } /* * connect to a unix domain socket */ int mpath_connect__(int nonblocking) { int fd; size_t len; struct sockaddr_un addr; int flags = 0; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; addr.sun_path[0] = '\0'; strncpy(&addr.sun_path[1], DEFAULT_SOCKET, sizeof(addr.sun_path) - 1); len = strlen(DEFAULT_SOCKET) + 1 + sizeof(sa_family_t); if (len > sizeof(struct sockaddr_un)) len = sizeof(struct sockaddr_un); fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd == -1) return -1; if (nonblocking) { flags = fcntl(fd, F_GETFL, 0); if (flags != -1) (void)fcntl(fd, F_SETFL, flags|O_NONBLOCK); } if (connect(fd, (struct sockaddr *)&addr, len) == -1) { int err = errno; close(fd); errno = err; return -1; } if (nonblocking && flags != -1) (void)fcntl(fd, F_SETFL, flags); return fd; } extern int __mpath_connect(int) __attribute__((weak, alias("mpath_connect__"))); /* * connect to a unix domain socket */ int mpath_connect(void) { return mpath_connect__(0); } int mpath_disconnect(int fd) { return close(fd); } ssize_t mpath_recv_reply_len(int fd, unsigned int timeout) { size_t len; ssize_t ret; ret = read_all(fd, &len, sizeof(len), timeout); if (ret < 0) return ret; if (ret != sizeof(len)) { errno = EIO; return -1; } if (len <= 0 || len >= MAX_REPLY_LEN) { errno = ERANGE; return -1; } return len; } int mpath_recv_reply_data(int fd, char *reply, size_t len, unsigned int timeout) { ssize_t ret; if (len <= 0) return 0; ret = read_all(fd, reply, len, timeout); if (ret < 0) return ret; if ((size_t)ret != len) { errno = EIO; return -1; } reply[len - 1] = '\0'; return 0; } int mpath_recv_reply(int fd, char **reply, unsigned int timeout) { int err; ssize_t len; *reply = NULL; len = mpath_recv_reply_len(fd, timeout); if (len <= 0) return len; *reply = malloc(len); if (!*reply) return -1; err = mpath_recv_reply_data(fd, *reply, len, timeout); if (err) { free(*reply); *reply = NULL; return -1; } return 0; } int mpath_send_cmd(int fd, const char *cmd) { size_t len; if (cmd != NULL) len = strlen(cmd) + 1; else len = 0; if (write_all(fd, &len, sizeof(len)) != sizeof(len)) return -1; if (len && write_all(fd, cmd, len) != len) return -1; return 0; } int mpath_process_cmd(int fd, const char *cmd, char **reply, unsigned int timeout) { if (mpath_send_cmd(fd, cmd) != 0) return -1; return mpath_recv_reply(fd, reply, timeout); } multipath-tools-0.11.1/libmpathcmd/mpath_cmd.h000066400000000000000000000101051475246302400213340ustar00rootroot00000000000000/* * Copyright (C) 2015 Red Hat, Inc. * * This file is part of the device-mapper multipath userspace tools. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MPATH_CMD_H_INCLUDED #define MPATH_CMD_H_INCLUDED /* * This should be sufficient for json output for >10000 maps, * and >60000 paths. */ #define MAX_REPLY_LEN (32 * 1024 * 1024) #ifdef __cplusplus extern "C" { #endif #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" #define DEFAULT_REPLY_TIMEOUT 4000 /* * DESCRIPTION: * Same as mpath_connect() (see below) except for the "nonblocking" * parameter. * If "nonblocking" is set, connects in non-blocking mode. This is * useful to avoid blocking if the listening socket's backlog is * exceeded. In this case, errno will be set to EAGAIN. * In case of success, the returned file descriptor is in in blocking * mode, even if "nonblocking" was true. * * RETURNS: * A file descriptor on success. -1 on failure (with errno set). */ int mpath_connect__(int nonblocking); /* * DESCRIPTION: * Connect to the running multipathd daemon. On systems with the * multipathd.socket systemd unit file installed, this command will * start multipathd if it is not already running. This function * must be run before any of the others in this library * * RETURNS: * A file descriptor on success. -1 on failure (with errno set). */ int mpath_connect(void); /* * DESCRIPTION: * Disconnect from the multipathd daemon. This function must be * run after processing all the multipath commands. * * RETURNS: * 0 on success. -1 on failure (with errno set). */ int mpath_disconnect(int fd); /* * DESCRIPTION * Send multipathd a command and return the reply. This function * does the same as calling mpath_send_cmd() and then * mpath_recv_reply() * * RETURNS: * 0 on success, and reply will either be NULL (if there was no * reply data), or point to the reply string, which must be freed by * the caller. -1 on failure (with errno set). */ int mpath_process_cmd(int fd, const char *cmd, char **reply, unsigned int timeout); /* * DESCRIPTION: * Send a command to multipathd * * RETURNS: * 0 on success. -1 on failure (with errno set) */ int mpath_send_cmd(int fd, const char *cmd); /* * DESCRIPTION: * Return a reply from multipathd for a previously sent command. * This is equivalent to calling mpath_recv_reply_len(), allocating * a buffer of the appropriate size, and then calling * mpath_recv_reply_data() with that buffer. * * RETURNS: * 0 on success, and reply will either be NULL (if there was no * reply data), or point to the reply string, which must be freed by * the caller, -1 on failure (with errno set). */ int mpath_recv_reply(int fd, char **reply, unsigned int timeout); /* * DESCRIPTION: * Return the size of the upcoming reply data from the sent multipath * command. This must be called before calling mpath_recv_reply_data(). * * RETURNS: * The required size of the reply data buffer on success. -1 on * failure (with errno set). */ ssize_t mpath_recv_reply_len(int fd, unsigned int timeout); /* * DESCRIPTION: * Return the reply data from the sent multipath command. * mpath_recv_reply_len must be called first. reply must point to a * buffer of len size. * * RETURNS: * 0 on success, and reply will contain the reply data string. -1 * on failure (with errno set). */ int mpath_recv_reply_data(int fd, char *reply, size_t len, unsigned int timeout); #ifdef __cplusplus } #endif #endif /* MPATH_CMD_H_INCLUDED */ multipath-tools-0.11.1/libmpathpersist/000077500000000000000000000000001475246302400201605ustar00rootroot00000000000000multipath-tools-0.11.1/libmpathpersist/Makefile000066400000000000000000000026541475246302400216270ustar00rootroot00000000000000include ../Makefile.inc DEVLIB := libmpathpersist.so CFLAGS += $(LIB_CFLAGS) -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) -I$(mpathcmddir) LDFLAGS += -L$(multipathdir) -L$(mpathutildir) -L$(mpathcmddir) LIBDEPS += -lmultipath -lmpathutil -lmpathcmd -ldevmapper -lpthread -ldl OBJS := mpath_persist.o mpath_updatepr.o mpath_pr_ioctl.o mpath_persist_int.o all: $(DEVLIB) include $(TOPDIR)/rules.mk install: all $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(includedir) $(Q)$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) $(Q)$(INSTALL_PROGRAM) -m 644 mpath_persistent_reserve_in.3 $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 644 mpath_persistent_reserve_out.3 $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 644 mpath_persist.h $(DESTDIR)$(includedir) uninstall: $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(mandir)/man3/mpath_persistent_reserve_in.3 $(Q)$(RM) $(DESTDIR)$(mandir)/man3/mpath_persistent_reserve_out.3 $(Q)$(RM) $(DESTDIR)$(includedir)/mpath_persist.h $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi $(NV_VERSION_SCRIPT) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmpathpersist/libmpathpersist.version000066400000000000000000000021021475246302400247740ustar00rootroot00000000000000/* * Copyright (c) 2020 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later * * libmpathpersist ABI * * The ABI of libmpathpersist is supposed to remain stable. Removing symbols * or altering existing symbols' semantics is not allowed. When changing a * a symbol, either use a new name, or explicit symver directives. * * See libmultipath.version for general policy about version numbers. */ /* Previous API for backward compatibility */ LIBMPATHPERSIST_2.1.0 { global: libmpathpersist_exit; libmpathpersist_init; mpath_lib_exit; mpath_lib_init; mpath_mx_alloc_len; mpath_persistent_reserve_free_vecs; __mpath_persistent_reserve_in; mpath_persistent_reserve_in; mpath_persistent_reserve_init_vecs; __mpath_persistent_reserve_out; mpath_persistent_reserve_out; local: *; }; LIBMPATHPERSIST_2.2.0 { global: mpath_persistent_reserve_in__; mpath_persistent_reserve_out__; } LIBMPATHPERSIST_2.1.0; __LIBMPATHPERSIST_INT_1.1.0 { /* Internal use by multipath-tools */ dumpHex; mpath_alloc_prin_response; prin_do_scsi_ioctl; prout_do_scsi_ioctl; update_map_pr; }; multipath-tools-0.11.1/libmpathpersist/mpath_persist.c000066400000000000000000000076661475246302400232250ustar00rootroot00000000000000#include #include "util.h" #include "vector.h" #include "config.h" #include "debug.h" #include "devmapper.h" #include "mpath_persist.h" #include "mpath_persist_int.h" extern struct udev *udev; static void adapt_config(struct config *conf) { conf->force_sync = 1; set_max_fds(conf->max_fds); } int libmpathpersist_init(void) { struct config *conf; int rc = 0; if (libmultipath_init()) { condlog(0, "Failed to initialize libmultipath."); return 1; } if (init_config(DEFAULT_CONFIGFILE)) { condlog(0, "Failed to initialize multipath config."); return 1; } conf = libmp_get_multipath_config(); adapt_config(conf); libmp_put_multipath_config(conf); return rc; } struct config * mpath_lib_init (void) { struct config *conf; conf = load_config(DEFAULT_CONFIGFILE); if (!conf) { condlog(0, "Failed to initialize multipath config."); return NULL; } adapt_config(conf); return conf; } static void libmpathpersist_cleanup(void) { libmultipath_exit(); dm_lib_exit(); } int mpath_lib_exit (struct config *conf) { free_config(conf); libmpathpersist_cleanup(); return 0; } int libmpathpersist_exit(void) { uninit_config(); libmpathpersist_cleanup(); return 0; } static vector curmp; static vector pathvec; static void mpath_persistent_reserve_free_vecs__(vector curmp, vector pathvec) { free_multipathvec(curmp, KEEP_PATHS); free_pathvec(pathvec, FREE_PATHS); } void mpath_persistent_reserve_free_vecs(void) { mpath_persistent_reserve_free_vecs__(curmp, pathvec); curmp = pathvec = NULL; } static int mpath_persistent_reserve_init_vecs__(vector *curmp_p, vector *pathvec_p, int verbose) { libmp_verbosity = verbose; if (*curmp_p) return MPATH_PR_SUCCESS; /* * allocate core vectors to store paths and multipaths */ *curmp_p = vector_alloc (); *pathvec_p = vector_alloc (); if (!*curmp_p || !*pathvec_p){ condlog (0, "vector allocation failed."); goto err; } if (dm_get_maps(*curmp_p)) goto err; return MPATH_PR_SUCCESS; err: mpath_persistent_reserve_free_vecs__(*curmp_p, *pathvec_p); *curmp_p = *pathvec_p = NULL; return MPATH_PR_DMMP_ERROR; } int mpath_persistent_reserve_init_vecs(int verbose) { return mpath_persistent_reserve_init_vecs__(&curmp, &pathvec, verbose); } int mpath_persistent_reserve_in__(int fd, int rq_servact, struct prin_resp *resp, int noisy) { return do_mpath_persistent_reserve_in(curmp, pathvec, fd, rq_servact, resp, noisy); } extern int __mpath_persistent_reserve_in(int, int, struct prin_resp *, int) __attribute__((weak, alias("mpath_persistent_reserve_in__"))); int mpath_persistent_reserve_out__( int fd, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy) { return do_mpath_persistent_reserve_out(curmp, pathvec, fd, rq_servact, rq_scope, rq_type, paramp, noisy); } extern int __mpath_persistent_reserve_out(int, int, int, unsigned int, struct prout_param_descriptor *, int) __attribute__((weak, alias("mpath_persistent_reserve_out__"))); int mpath_persistent_reserve_in (int fd, int rq_servact, struct prin_resp *resp, int noisy, int verbose) { vector curmp = NULL, pathvec; int ret = mpath_persistent_reserve_init_vecs__(&curmp, &pathvec, verbose); if (ret != MPATH_PR_SUCCESS) return ret; ret = do_mpath_persistent_reserve_in(curmp, pathvec, fd, rq_servact, resp, noisy); mpath_persistent_reserve_free_vecs__(curmp, pathvec); return ret; } 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) { vector curmp = NULL, pathvec; int ret = mpath_persistent_reserve_init_vecs__(&curmp, &pathvec, verbose); if (ret != MPATH_PR_SUCCESS) return ret; ret = do_mpath_persistent_reserve_out(curmp, pathvec, fd, rq_servact, rq_scope, rq_type, paramp, noisy); mpath_persistent_reserve_free_vecs__(curmp, pathvec); return ret; } multipath-tools-0.11.1/libmpathpersist/mpath_persist.h000066400000000000000000000263151475246302400232220ustar00rootroot00000000000000/* version - 1.0 */ #ifndef MPATH_PERSIST_H_INCLUDED #define MPATH_PERSIST_H_INCLUDED #ifdef __cplusplus extern "C" { #endif #include #define MPATH_MAX_PARAM_LEN 8192 #define MPATH_MX_TIDS 32 /* Max number of transport ids"*/ #define MPATH_MX_TID_LEN 256 /* Max length of transport id */ /* PRIN Service Actions */ #define MPATH_PRIN_RKEY_SA 0x00 /* READ KEYS SA*/ #define MPATH_PRIN_RRES_SA 0x01 /* READ RESERVATION SA*/ #define MPATH_PRIN_RCAP_SA 0x02 /* REPORT CAPABILITIES SA*/ #define MPATH_PRIN_RFSTAT_SA 0x03 /* READ FULL STATUS SA*/ /* PROUT Service Actions */ #define MPATH_PROUT_REG_SA 0x00 /* REGISTER SA */ #define MPATH_PROUT_RES_SA 0x01 /* RESERVE SA*/ #define MPATH_PROUT_REL_SA 0x02 /* RELEASE SA*/ #define MPATH_PROUT_CLEAR_SA 0x03 /* CLEAR SA*/ #define MPATH_PROUT_PREE_SA 0x04 /* PREEMPT SA*/ #define MPATH_PROUT_PREE_AB_SA 0x05 /* PREEMPT AND ABORT SA*/ #define MPATH_PROUT_REG_IGN_SA 0x06 /* REGISTER AND IGNORE EXISTING KEY SA*/ #define MPATH_PROUT_REG_MOV_SA 0x07 /* REGISTER AND MOVE SA*/ #define MPATH_LU_SCOPE 0x00 /* LU_SCOPE */ /* Persistent reservations type */ #define MPATH_PRTPE_WE 0x01 /* Write Exclusive */ #define MPATH_PRTPE_EA 0x03 /* Exclusive Access*/ #define MPATH_PRTPE_WE_RO 0x05 /* WriteExclusive Registrants Only */ #define MPATH_PRTPE_EA_RO 0x06 /* Exclusive Access. Registrants Only*/ #define MPATH_PRTPE_WE_AR 0x07 /* Write Exclusive. All Registrants*/ #define MPATH_PRTPE_EA_AR 0x08 /* Exclusive Access. All Registrants */ /* PR RETURN_STATUS */ #define MPATH_PR_SKIP -1 /* skipping this path */ #define MPATH_PR_SUCCESS 0 #define MPATH_PR_SYNTAX_ERROR 1 /* syntax error or invalid parameter */ /* status for check condition */ #define MPATH_PR_SENSE_NOT_READY 2 /* [sk,asc,ascq: 0x2,*,*] */ #define MPATH_PR_SENSE_MEDIUM_ERROR 3 /* [sk,asc,ascq: 0x3,*,*] */ #define MPATH_PR_SENSE_HARDWARE_ERROR 4 /* [sk,asc,ascq: 0x4,*,*] */ #define MPATH_PR_ILLEGAL_REQ 5 /* [sk,asc,ascq: 0x5,*,*]*/ #define MPATH_PR_SENSE_UNIT_ATTENTION 6 /* [sk,asc,ascq: 0x6,*,*] */ #define MPATH_PR_SENSE_INVALID_OP 7 /* [sk,asc,ascq: 0x5,0x20,0x0]*/ #define MPATH_PR_SENSE_ABORTED_COMMAND 8 /* [sk,asc,ascq: 0xb,*,*] */ #define MPATH_PR_NO_SENSE 9 /* [sk,asc,ascq: 0x0,*,*] */ #define MPATH_PR_SENSE_MALFORMED 10 /* Response to SCSI command malformed */ #define MPATH_PR_RESERV_CONFLICT 11 /* Reservation conflict on the device */ #define MPATH_PR_FILE_ERROR 12 /* file (device node) problems(e.g. not found)*/ #define MPATH_PR_DMMP_ERROR 13 /* DMMP related error.(e.g Error in getting dm info */ #define MPATH_PR_THREAD_ERROR 14 /* pthreads error (e.g. unable to create new thread) */ #define MPATH_PR_OTHER 15 /*other error/warning has occurred(transport or driver error) */ /* PR MASK */ #define MPATH_F_APTPL_MASK 0x01 /* APTPL MASK*/ #define MPATH_F_ALL_TG_PT_MASK 0x04 /* ALL_TG_PT MASK*/ #define MPATH_F_SPEC_I_PT_MASK 0x08 /* SPEC_I_PT MASK*/ #define MPATH_PR_TYPE_MASK 0x0f /* TYPE MASK*/ #define MPATH_PR_SCOPE_MASK 0xf0 /* SCOPE MASK*/ /*Transport ID PROTOCOL IDENTIFIER values */ #define MPATH_PROTOCOL_ID_FC 0x00 #define MPATH_PROTOCOL_ID_ISCSI 0x05 #define MPATH_PROTOCOL_ID_SAS 0x06 /*Transport ID FORMAT CODE */ #define MPATH_WWUI_DEVICE_NAME 0x00 /* World wide unique initiator device name */ #define MPATH_WWUI_PORT_IDENTIFIER 0x40 /* World wide unique initiator port identifier */ extern unsigned int mpath_mx_alloc_len; struct prin_readdescr { uint32_t prgeneration; uint32_t additional_length; /* The value should be either 0 or divisible by 8. 0 indicates no registered reservation key. */ uint8_t key_list[MPATH_MAX_PARAM_LEN]; }; struct prin_resvdescr { uint32_t prgeneration; uint32_t additional_length; /* The value should be either 0 or 10h. 0 indicates there is no reservation held. 10h indicates the key[8] and scope_type have valid values */ uint8_t key[8]; uint32_t _obsolete; uint8_t _reserved; uint8_t scope_type; /* Use PR SCOPE AND TYPE MASK specified above */ uint16_t _obsolete1; }; struct prin_capdescr { uint16_t length; uint8_t flags[2]; uint16_t pr_type_mask; uint16_t _reserved; }; struct transportid { uint8_t format_code; uint8_t protocol_id; union { uint8_t n_port_name[8]; /* FC transport*/ uint8_t sas_address[8]; /* SAS transport */ uint8_t iscsi_name[256]; /* ISCSI transport */ }; }; struct prin_fulldescr { uint8_t key[8]; uint8_t flag; /* All_tg_pt and reservation holder */ uint8_t scope_type; /* Use PR SCOPE AND TYPE MASK specified above. Meaningful only for reservation holder */ uint16_t rtpi; struct transportid trnptid; }; struct print_fulldescr_list { uint32_t prgeneration; uint32_t number_of_descriptor; uint8_t private_buffer[MPATH_MAX_PARAM_LEN]; /*Private buffer for list storage*/ struct prin_fulldescr *descriptors[]; }; struct prin_resp { union { struct prin_readdescr prin_readkeys; /* for PRIN read keys SA*/ struct prin_resvdescr prin_readresv; /* for PRIN read reservation SA*/ struct prin_capdescr prin_readcap; /* for PRIN Report Capabilities SA*/ struct print_fulldescr_list prin_readfd; /* for PRIN read full status SA*/ }prin_descriptor; }; struct prout_param_descriptor { /* PROUT parameter descriptor */ uint8_t key[8]; uint8_t sa_key[8]; uint32_t _obsolete; uint8_t sa_flags; uint8_t _reserved; uint16_t _obsolete1; uint8_t private_buffer[MPATH_MAX_PARAM_LEN]; /*private buffer for list storage*/ uint32_t num_transportid; /* Number of Transport ID listed in trnptid_list[]*/ struct transportid *trnptid_list[]; }; /* Function declarations */ /* * DESCRIPTION : * Initialize device mapper multipath configuration. This function must be invoked first * before performing reservation management functions. * Either this function or mpath_lib_init() may be used. * Use this function to work with libmultipath's internal "struct config" * and "struct udev". The latter will be initialized automatically. * Call libmpathpersist_exit() for cleanup. * RESTRICTIONS: * * RETURNS: 0->Success, 1->Failed. */ int libmpathpersist_init(void); /* * DESCRIPTION : * Initialize device mapper multipath configuration. This function must be invoked first * before performing reservation management functions. * Either this function or libmpathpersist_init() may be used. * Use this function to work with an application-specific "struct config" * and "struct udev". The latter must be initialized by the application. * Call mpath_lib_exit() for cleanup. * RESTRICTIONS: * * RETURNS: struct config ->Success, NULL->Failed. */ struct config *mpath_lib_init(void); /* * DESCRIPTION : * Release device mapper multipath configuration. This function must be invoked after * performing reservation management functions. * Use this after initialization with mpath_lib_init(). * RESTRICTIONS: * * RETURNS: 0->Success, 1->Failed. */ int mpath_lib_exit(struct config *conf); /* * DESCRIPTION : * Release device mapper multipath configuration a. This function must be invoked after * performing reservation management functions. * Use this after initialization with libmpathpersist_init(). * Calling libmpathpersist_init() after libmpathpersist_exit() will fail. * RESTRICTIONS: * * RETURNS: 0->Success, 1->Failed. */ int libmpathpersist_exit(void); /* * DESCRIPTION : * This function sends PRIN command to the DM device and get the response. * * @fd: The file descriptor of a multipath device. Input argument. * @rq_servact: PRIN command service action. Input argument * @resp: The response from PRIN service action. The resp is a struct specified above. The caller should * manage the memory allocation of this struct * @noisy: Turn on debugging trace: Input argument. 0->Disable, 1->Enable * @verbose: Set verbosity level. Input argument. value:[0-3]. 0->disabled, 3->Max verbose * * RESTRICTIONS: * * RETURNS: MPATH_PR_SUCCESS if PR command successful else returns any of the status specified * above in RETURN_STATUS. * */ int mpath_persistent_reserve_in(int fd, int rq_servact, struct prin_resp *resp, int noisy, int verbose); /* * DESCRIPTION : * This function is like mpath_persistent_reserve_in(), except that it * requires mpath_persistent_reserve_init_vecs() to be called before the * PR call to set up internal variables. These must later be cleanup up * by calling mpath_persistent_reserve_free_vecs(). * * RESTRICTIONS: * This function uses static internal variables, and is not thread-safe. */ int mpath_persistent_reserve_in__(int fd, int rq_servact, struct prin_resp *resp, int noisy); /* * DESCRIPTION : * This function sends PROUT command to the DM device and get the response. * * @fd: The file descriptor of a multipath device. Input argument. * @rq_servact: PROUT command service action. Input argument * @rq_scope: Persistent reservation scope. The value should be always LU_SCOPE (0h). * @rq_type: Persistent reservation type. The valid values of persistent reservation types are * 5h (Write exclusive - registrants only) * 6h (Exclusive access - registrants only) * 7h (Write exclusive - All registrants) * 8h (Exclusive access - All registrants). * @paramp: PROUT command parameter data. The paramp is a struct which describes PROUT * parameter list. The caller should manage the memory allocation of this struct. * @noisy: Turn on debugging trace: Input argument.0->Disable, 1->Enable. * @verbose: Set verbosity level. Input argument. value:0 to 3. 0->disabled, 3->Max verbose * * RESTRICTIONS: * * RETURNS: MPATH_PR_SUCCESS if PR command successful else returns any of the status specified * above in RETURN_STATUS. */ 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); /* * DESCRIPTION : * This function is like mpath_persistent_reserve_out(), except that it * requires mpath_persistent_reserve_init_vecs() to be called before the * PR call to set up internal variables. These must later be cleanup up * by calling mpath_persistent_reserve_free_vecs(). * * RESTRICTIONS: * This function uses static internal variables, and is not thread-safe. */ int mpath_persistent_reserve_out__(int fd, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); /* * DESCRIPTION : * This function allocates data structures and performs basic initialization and * device discovery for later calls of mpath_persistent_reserve_in__() or * mpath_persistent_reserve_out__(). * @verbose: Set verbosity level. Input argument. value:0 to 3. 0->disabled, 3->Max verbose * * RESTRICTIONS: * This function uses static internal variables, and is not thread-safe. * * RETURNS: MPATH_PR_SUCCESS if successful else returns any of the status specified * above in RETURN_STATUS. */ int mpath_persistent_reserve_init_vecs(int verbose); /* * DESCRIPTION : * This function frees data structures allocated by * mpath_persistent_reserve_init_vecs(). * * RESTRICTIONS: * This function uses static internal variables, and is not thread-safe. */ void mpath_persistent_reserve_free_vecs(void); #ifdef __cplusplus } #endif #endif /* MPATH_PERSIST_H_INCLUDED */ multipath-tools-0.11.1/libmpathpersist/mpath_persist_int.c000066400000000000000000000514611475246302400240670ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "defaults.h" #include "checkers.h" #include "structs.h" #include "structs_vec.h" #include "prio.h" #include "devmapper.h" #include "debug.h" #include "config.h" #include "switchgroup.h" #include "discovery.h" #include "configure.h" #include "dmparser.h" #include "propsel.h" #include "util.h" #include "unaligned.h" #include "mpath_persist.h" #include "mpath_persist_int.h" #include "mpathpr.h" #include "mpath_pr_ioctl.h" struct prout_param { char dev[FILE_NAME_SIZE]; int rq_servact; int rq_scope; unsigned int rq_type; struct prout_param_descriptor *paramp; int noisy; int status; }; struct threadinfo { int status; pthread_t id; struct prout_param param; }; static int mpath_send_prin_activepath (char * dev, int rq_servact, struct prin_resp * resp, int noisy) { int rc; rc = prin_do_scsi_ioctl(dev, rq_servact, resp, noisy); return (rc); } static int mpath_prin_activepath (struct multipath *mpp, int rq_servact, struct prin_resp * resp, int noisy) { int i,j, ret = MPATH_PR_DMMP_ERROR; struct pathgroup *pgp = NULL; struct path *pp = NULL; vector_foreach_slot (mpp->pg, pgp, j){ vector_foreach_slot (pgp->paths, pp, i){ if (!((pp->state == PATH_UP) || (pp->state == PATH_GHOST))){ condlog(2, "%s: %s not available. Skip.", mpp->wwid, pp->dev); condlog(3, "%s: status = %d.", mpp->wwid, pp->state); continue; } condlog(3, "%s: sending pr in command to %s ", mpp->wwid, pp->dev); ret = mpath_send_prin_activepath(pp->dev, rq_servact, resp, noisy); switch(ret) { case MPATH_PR_SUCCESS: case MPATH_PR_SENSE_INVALID_OP: return ret; default: continue; } } } return ret; } void *mpath_alloc_prin_response(int prin_sa) { void * ptr = NULL; int size=0; switch (prin_sa) { case MPATH_PRIN_RKEY_SA: size = sizeof(struct prin_readdescr); break; case MPATH_PRIN_RRES_SA: size = sizeof(struct prin_resvdescr); break; case MPATH_PRIN_RCAP_SA: size=sizeof(struct prin_capdescr); break; case MPATH_PRIN_RFSTAT_SA: size = sizeof(struct print_fulldescr_list) + sizeof(struct prin_fulldescr *)*MPATH_MX_TIDS; break; } if (size > 0) { ptr = calloc(size, 1); } return ptr; } static int get_mpvec(vector curmp, vector pathvec, char *refwwid) { int i; struct multipath *mpp; vector_foreach_slot (curmp, mpp, i){ /* * discard out of scope maps */ if (!mpp->alias) { condlog(0, "%s: map with empty alias!", __func__); continue; } if (mpp->pg != NULL) /* Already seen this one */ continue; if (refwwid && strncmp (mpp->alias, refwwid, WWID_SIZE - 1)) continue; if (update_multipath_table(mpp, pathvec, DI_CHECKER) != DMP_OK || update_mpp_paths(mpp, pathvec)) { condlog(1, "error parsing map %s", mpp->wwid); remove_map(mpp, pathvec, curmp); i--; } else extract_hwe_from_path(mpp); } return MPATH_PR_SUCCESS ; } static int mpath_get_map(vector curmp, vector pathvec, int fd, struct multipath **pmpp) { int rc; struct stat info; char alias[WWID_SIZE]; struct multipath *mpp; if (fstat(fd, &info) != 0){ condlog(0, "stat error fd=%d", fd); return MPATH_PR_FILE_ERROR; } if(!S_ISBLK(info.st_mode)){ condlog(3, "Failed to get major:minor. fd=%d", fd); return MPATH_PR_FILE_ERROR; } /* get alias from major:minor*/ rc = libmp_mapinfo(DM_MAP_BY_DEVT | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .devt = info.st_rdev }, (mapinfo_t) { .name = alias }); if (rc == DMP_NO_MATCH) { condlog(3, "%s: not a multipath device.", alias); return MPATH_PR_DMMP_ERROR; } else if (rc != DMP_OK) { condlog(1, "%d:%d failed to get device alias.", major(info.st_rdev), minor(info.st_rdev)); return MPATH_PR_DMMP_ERROR; } condlog(4, "alias = %s", alias); /* get info of all paths from the dm device */ if (get_mpvec(curmp, pathvec, alias)) { condlog(0, "%s: failed to get device info.", alias); return MPATH_PR_DMMP_ERROR; } mpp = find_mp_by_alias(curmp, alias); if (!mpp) { condlog(0, "%s: devmap not registered.", alias); return MPATH_PR_DMMP_ERROR; } if (pmpp) *pmpp = mpp; return MPATH_PR_SUCCESS; } int do_mpath_persistent_reserve_in(vector curmp, vector pathvec, int fd, int rq_servact, struct prin_resp *resp, int noisy) { struct multipath *mpp; int ret; ret = mpath_get_map(curmp, pathvec, fd, &mpp); if (ret != MPATH_PR_SUCCESS) return ret; ret = mpath_prin_activepath(mpp, rq_servact, resp, noisy); return ret; } static void *mpath_prout_pthread_fn(void *p) { int ret; struct prout_param * param = (struct prout_param *)p; ret = prout_do_scsi_ioctl( param->dev,param->rq_servact, param->rq_scope, param->rq_type, param->paramp, param->noisy); param->status = ret; pthread_exit(NULL); } static int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) { int i, j, k; struct pathgroup *pgp = NULL; struct path *pp = NULL; int rollback = 0; int active_pathcount=0; int rc; int count=0; int status = MPATH_PR_SUCCESS; int all_tg_pt; uint64_t sa_key = 0; if (!mpp) return MPATH_PR_DMMP_ERROR; all_tg_pt = (mpp->all_tg_pt == ALL_TG_PT_ON || paramp->sa_flags & MPATH_F_ALL_TG_PT_MASK); active_pathcount = count_active_paths(mpp); if (active_pathcount == 0) { condlog (0, "%s: no path available", mpp->wwid); return MPATH_PR_DMMP_ERROR; } struct threadinfo thread[active_pathcount]; int hosts[active_pathcount]; memset(thread, 0, sizeof(thread)); /* init thread parameter */ for (i =0; i< active_pathcount; i++){ hosts[i] = -1; thread[i].param.rq_servact = rq_servact; thread[i].param.rq_scope = rq_scope; thread[i].param.rq_type = rq_type; thread[i].param.paramp = paramp; thread[i].param.noisy = noisy; thread[i].param.status = MPATH_PR_SKIP; condlog (3, "THREAD ID [%d] INFO]", i); condlog (3, "rq_servact=%d ", thread[i].param.rq_servact); condlog (3, "rq_scope=%d ", thread[i].param.rq_scope); condlog (3, "rq_type=%d ", thread[i].param.rq_type); condlog (3, "rkey="); condlog (3, "paramp->sa_flags =%02x ", thread[i].param.paramp->sa_flags); condlog (3, "noisy=%d ", thread[i].param.noisy); condlog (3, "status=%d ", thread[i].param.status); } pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); vector_foreach_slot (mpp->pg, pgp, j){ vector_foreach_slot (pgp->paths, pp, i){ if (!((pp->state == PATH_UP) || (pp->state == PATH_GHOST))){ condlog (1, "%s: %s path not up. Skip.", mpp->wwid, pp->dev); continue; } if (all_tg_pt && pp->sg_id.host_no != -1) { for (k = 0; k < count; k++) { if (pp->sg_id.host_no == hosts[k]) { condlog(3, "%s: %s host %d matches skip.", pp->wwid, pp->dev, pp->sg_id.host_no); break; } } if (k < count) continue; } strlcpy(thread[count].param.dev, pp->dev, FILE_NAME_SIZE); if (count && (thread[count].param.paramp->sa_flags & MPATH_F_SPEC_I_PT_MASK)){ /* * Clearing SPEC_I_PT as transportids are already registered by now. */ thread[count].param.paramp->sa_flags &= (~MPATH_F_SPEC_I_PT_MASK); } condlog (3, "%s: sending pr out command to %s", mpp->wwid, pp->dev); rc = pthread_create(&thread[count].id, &attr, mpath_prout_pthread_fn, (void *)(&thread[count].param)); if (rc){ condlog (0, "%s: failed to create thread %d", mpp->wwid, rc); thread[count].param.status = MPATH_PR_THREAD_ERROR; } else hosts[count] = pp->sg_id.host_no; count = count + 1; } } for( i=0; i < count ; i++){ if (thread[i].param.status != MPATH_PR_THREAD_ERROR) { rc = pthread_join(thread[i].id, NULL); if (rc){ condlog (0, "%s: Thread[%d] failed to join thread %d", mpp->wwid, i, rc); } } if (!rollback && (thread[i].param.status == MPATH_PR_RESERV_CONFLICT)){ rollback = 1; sa_key = get_unaligned_be64(¶mp->sa_key[0]); status = MPATH_PR_RESERV_CONFLICT ; } if (!rollback && (status == MPATH_PR_SUCCESS)){ status = thread[i].param.status; } } if (rollback && ((rq_servact == MPATH_PROUT_REG_SA) && sa_key != 0 )){ condlog (3, "%s: ERROR: initiating pr out rollback", mpp->wwid); memcpy(¶mp->key, ¶mp->sa_key, 8); memset(¶mp->sa_key, 0, 8); for( i=0 ; i < count ; i++){ if(thread[i].param.status == MPATH_PR_SUCCESS) { rc = pthread_create(&thread[i].id, &attr, mpath_prout_pthread_fn, (void *)(&thread[i].param)); if (rc){ condlog (0, "%s: failed to create thread for rollback. %d", mpp->wwid, rc); thread[i].param.status = MPATH_PR_THREAD_ERROR; } } else thread[i].param.status = MPATH_PR_SKIP; } for(i=0; i < count ; i++){ if (thread[i].param.status != MPATH_PR_SKIP && thread[i].param.status != MPATH_PR_THREAD_ERROR) { rc = pthread_join(thread[i].id, NULL); if (rc){ condlog (3, "%s: failed to join thread while rolling back %d", mpp->wwid, i); } } } } pthread_attr_destroy(&attr); return (status); } static int send_prout_activepath(char *dev, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) { struct prout_param param; param.rq_servact = rq_servact; param.rq_scope = rq_scope; param.rq_type = rq_type; param.paramp = paramp; param.noisy = noisy; param.status = -1; pthread_t thread; pthread_attr_t attr; int rc; memset(&thread, 0, sizeof(thread)); strlcpy(param.dev, dev, FILE_NAME_SIZE); /* Initialize and set thread joinable attribute */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); rc = pthread_create(&thread, &attr, mpath_prout_pthread_fn, (void *)(¶m)); if (rc){ condlog (3, "%s: failed to create thread %d", dev, rc); return MPATH_PR_THREAD_ERROR; } /* Free attribute and wait for the other threads */ pthread_attr_destroy(&attr); rc = pthread_join(thread, NULL); return (param.status); } static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor* paramp, int noisy) { int i,j, ret; struct pathgroup *pgp = NULL; struct path *pp = NULL; vector_foreach_slot (mpp->pg, pgp, j){ vector_foreach_slot (pgp->paths, pp, i){ if (!((pp->state == PATH_UP) || (pp->state == PATH_GHOST))){ condlog (1, "%s: %s path not up. Skip", mpp->wwid, pp->dev); continue; } condlog (3, "%s: sending pr out command to %s", mpp->wwid, pp->dev); ret = send_prout_activepath(pp->dev, rq_servact, rq_scope, rq_type, paramp, noisy); return ret ; } } condlog (0, "%s: no path available", mpp->wwid); return MPATH_PR_DMMP_ERROR; } static int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) { int i, j; int num = 0; struct pathgroup *pgp = NULL; struct path *pp = NULL; int active_pathcount = 0; pthread_attr_t attr; int rc, found = 0; int count = 0; int status = MPATH_PR_SUCCESS; struct prin_resp resp; struct prout_param_descriptor *pamp; struct prin_resp *pr_buff; int length; struct transportid *pptr; if (!mpp) return MPATH_PR_DMMP_ERROR; active_pathcount = count_active_paths(mpp); if (active_pathcount == 0) { condlog (0, "%s: no path available", mpp->wwid); return MPATH_PR_DMMP_ERROR; } struct threadinfo thread[active_pathcount]; memset(thread, 0, sizeof(thread)); for (i = 0; i < active_pathcount; i++){ thread[i].param.rq_servact = rq_servact; thread[i].param.rq_scope = rq_scope; thread[i].param.rq_type = rq_type; thread[i].param.paramp = paramp; thread[i].param.noisy = noisy; thread[i].param.status = MPATH_PR_SKIP; condlog (3, " path count = %d", i); condlog (3, "rq_servact=%d ", thread[i].param.rq_servact); condlog (3, "rq_scope=%d ", thread[i].param.rq_scope); condlog (3, "rq_type=%d ", thread[i].param.rq_type); condlog (3, "noisy=%d ", thread[i].param.noisy); condlog (3, "status=%d ", thread[i].param.status); } pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE); vector_foreach_slot (mpp->pg, pgp, j){ vector_foreach_slot (pgp->paths, pp, i){ if (!((pp->state == PATH_UP) || (pp->state == PATH_GHOST))){ condlog (1, "%s: %s path not up.", mpp->wwid, pp->dev); continue; } strlcpy(thread[count].param.dev, pp->dev, FILE_NAME_SIZE); condlog (3, "%s: sending pr out command to %s", mpp->wwid, pp->dev); rc = pthread_create (&thread[count].id, &attr, mpath_prout_pthread_fn, (void *) (&thread[count].param)); if (rc) { condlog (0, "%s: failed to create thread. %d", mpp->wwid, rc); thread[count].param.status = MPATH_PR_THREAD_ERROR; } count = count + 1; } } pthread_attr_destroy (&attr); for (i = 0; i < count; i++){ if (thread[i].param.status != MPATH_PR_THREAD_ERROR) { rc = pthread_join (thread[i].id, NULL); if (rc){ condlog (1, "%s: failed to join thread. %d", mpp->wwid, rc); } } } for (i = 0; i < count; i++){ /* check thread status here and return the status */ if (thread[i].param.status == MPATH_PR_RESERV_CONFLICT) status = MPATH_PR_RESERV_CONFLICT; else if (status == MPATH_PR_SUCCESS && thread[i].param.status != MPATH_PR_RESERV_CONFLICT) status = thread[i].param.status; } status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); if (status != MPATH_PR_SUCCESS){ condlog (0, "%s: pr in read reservation command failed.", mpp->wwid); return MPATH_PR_OTHER; } num = resp.prin_descriptor.prin_readresv.additional_length / 8; if (num == 0){ condlog (2, "%s: Path holding reservation is released.", mpp->wwid); return MPATH_PR_SUCCESS; } condlog (2, "%s: Path holding reservation is not available.", mpp->wwid); pr_buff = mpath_alloc_prin_response(MPATH_PRIN_RFSTAT_SA); if (!pr_buff){ condlog (0, "%s: failed to alloc pr in response buffer.", mpp->wwid); return MPATH_PR_OTHER; } status = mpath_prin_activepath (mpp, MPATH_PRIN_RFSTAT_SA, pr_buff, noisy); if (status != MPATH_PR_SUCCESS){ condlog (0, "%s: pr in read full status command failed.", mpp->wwid); goto out; } num = pr_buff->prin_descriptor.prin_readfd.number_of_descriptor; if (0 == num){ goto out; } length = sizeof (struct prout_param_descriptor) + (sizeof (struct transportid *)); pamp = (struct prout_param_descriptor *)malloc (length); if (!pamp){ condlog (0, "%s: failed to alloc pr out parameter.", mpp->wwid); goto out1; } memset(pamp, 0, length); pamp->trnptid_list[0] = (struct transportid *) malloc (sizeof (struct transportid)); if (!pamp->trnptid_list[0]){ condlog (0, "%s: failed to alloc pr out transportid.", mpp->wwid); goto out1; } if (get_be64(mpp->reservation_key)){ memcpy (pamp->key, &mpp->reservation_key, 8); condlog (3, "%s: reservation key set.", mpp->wwid); } status = mpath_prout_common (mpp, MPATH_PROUT_CLEAR_SA, rq_scope, rq_type, pamp, noisy); if (status) { condlog(0, "%s: failed to send CLEAR_SA", mpp->wwid); goto out1; } pamp->num_transportid = 1; pptr=pamp->trnptid_list[0]; for (i = 0; i < num; i++){ if (get_be64(mpp->reservation_key) && memcmp(pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, &mpp->reservation_key, 8)){ /*register with transport id*/ memset(pamp, 0, length); pamp->trnptid_list[0] = pptr; memset (pamp->trnptid_list[0], 0, sizeof (struct transportid)); memcpy (pamp->sa_key, pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, 8); pamp->sa_flags = MPATH_F_SPEC_I_PT_MASK; pamp->num_transportid = 1; memcpy (pamp->trnptid_list[0], &pr_buff->prin_descriptor.prin_readfd.descriptors[i]->trnptid, sizeof (struct transportid)); status = mpath_prout_common (mpp, MPATH_PROUT_REG_SA, 0, rq_type, pamp, noisy); pamp->sa_flags = 0; memcpy (pamp->key, pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, 8); memset (pamp->sa_key, 0, 8); pamp->num_transportid = 0; status = mpath_prout_common (mpp, MPATH_PROUT_REG_SA, 0, rq_type, pamp, noisy); } else { if (get_be64(mpp->reservation_key)) found = 1; } } if (found){ memset (pamp, 0, length); memcpy (pamp->sa_key, &mpp->reservation_key, 8); memset (pamp->key, 0, 8); status = mpath_prout_reg(mpp, MPATH_PROUT_REG_SA, rq_scope, rq_type, pamp, noisy); } free(pptr); out1: free (pamp); out: free (pr_buff); return (status); } int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy) { struct multipath *mpp; int ret; uint64_t prkey; struct config *conf; ret = mpath_get_map(curmp, pathvec, fd, &mpp); if (ret != MPATH_PR_SUCCESS) return ret; conf = get_multipath_config(); mpp->mpe = find_mpe(conf->mptable, mpp->wwid); select_reservation_key(conf, mpp); select_all_tg_pt(conf, mpp); put_multipath_config(conf); memcpy(&prkey, paramp->sa_key, 8); if (mpp->prkey_source == PRKEY_SOURCE_FILE && prkey && (rq_servact == MPATH_PROUT_REG_IGN_SA || (rq_servact == MPATH_PROUT_REG_SA && (!get_be64(mpp->reservation_key) || memcmp(paramp->key, &mpp->reservation_key, 8) == 0)))) { memcpy(&mpp->reservation_key, paramp->sa_key, 8); if (update_prkey_flags(mpp->alias, get_be64(mpp->reservation_key), paramp->sa_flags)) { condlog(0, "%s: failed to set prkey for multipathd.", mpp->alias); return MPATH_PR_DMMP_ERROR; } } if (!get_be64(mpp->reservation_key) && (prkey || rq_servact != MPATH_PROUT_REG_IGN_SA)) { condlog(0, "%s: no configured reservation key", mpp->alias); return MPATH_PR_SYNTAX_ERROR; } if (memcmp(paramp->key, &mpp->reservation_key, 8) && memcmp(paramp->sa_key, &mpp->reservation_key, 8) && (prkey || rq_servact != MPATH_PROUT_REG_IGN_SA)) { condlog(0, "%s: configured reservation key doesn't match: 0x%" PRIx64, mpp->alias, get_be64(mpp->reservation_key)); return MPATH_PR_SYNTAX_ERROR; } switch(rq_servact) { case MPATH_PROUT_REG_SA: case MPATH_PROUT_REG_IGN_SA: ret= mpath_prout_reg(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); break; case MPATH_PROUT_RES_SA : case MPATH_PROUT_PREE_SA : case MPATH_PROUT_PREE_AB_SA : case MPATH_PROUT_CLEAR_SA: ret = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); break; case MPATH_PROUT_REL_SA: ret = mpath_prout_rel(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); break; default: return MPATH_PR_OTHER; } if ((ret == MPATH_PR_SUCCESS) && ((rq_servact == MPATH_PROUT_REG_SA) || (rq_servact == MPATH_PROUT_REG_IGN_SA))) { if (prkey == 0) { update_prflag(mpp->alias, 0); update_prkey(mpp->alias, 0); } else update_prflag(mpp->alias, 1); } else if ((ret == MPATH_PR_SUCCESS) && (rq_servact == MPATH_PROUT_CLEAR_SA)) { update_prflag(mpp->alias, 0); update_prkey(mpp->alias, 0); } return ret; } int update_map_pr(struct multipath *mpp) { int noisy=0; struct prin_resp *resp; unsigned int i; int ret = MPATH_PR_OTHER, isFound; if (!get_be64(mpp->reservation_key)) { /* Nothing to do. Assuming pr mgmt feature is disabled*/ mpp->prflag = PRFLAG_UNSET; condlog(4, "%s: reservation_key not set in multipath.conf", mpp->alias); return MPATH_PR_SUCCESS; } resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); if (!resp) { condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); return MPATH_PR_OTHER; } if (count_active_paths(mpp) == 0) { condlog(0,"%s: No available paths to check pr status", mpp->alias); goto out; } mpp->prflag = PRFLAG_UNSET; ret = mpath_prin_activepath(mpp, MPATH_PRIN_RKEY_SA, resp, noisy); if (ret != MPATH_PR_SUCCESS ) { condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); goto out; } ret = MPATH_PR_SUCCESS; if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) { condlog(3,"%s: No key found. Device may not be registered. ", mpp->alias); goto out; } condlog(2, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, get_be64(mpp->reservation_key)); isFound =0; for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) { condlog(2, "%s: PR IN READKEYS[%d] reservation key:", mpp->alias, i); dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i*8], 8 , 1); if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i*8], 8)) { condlog(2, "%s: reservation key found in pr in readkeys response", mpp->alias); isFound =1; } } if (isFound) { mpp->prflag = PRFLAG_SET; condlog(2, "%s: prflag flag set.", mpp->alias ); } out: free(resp); return ret; } multipath-tools-0.11.1/libmpathpersist/mpath_persist_int.h000066400000000000000000000017701475246302400240720ustar00rootroot00000000000000#ifndef MPATH_PERSIST_INT_H_INCLUDED #define MPATH_PERSIST_INT_H_INCLUDED /* * This header file contains symbols that are used by multipath-tools * but aren't part of the public libmpathpersist API. */ void * mpath_alloc_prin_response(int prin_sa); int do_mpath_persistent_reserve_in(vector curmp, vector pathvec, int fd, int rq_servact, struct prin_resp *resp, int noisy); void *mpath_alloc_prin_response(int prin_sa); int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int noisy); int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); void dumpHex(const char* , int len, int no_ascii); int update_map_pr(struct multipath *mpp); #endif /* MPATH_PERSIST_INT_H_INCLUDED */ multipath-tools-0.11.1/libmpathpersist/mpath_persistent_reserve_in.3000066400000000000000000000070501475246302400260600ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MPATH_PERSISTENT_RESERVE_IN 3 2024-02-09 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . mpath_persistent_reserve_in \- send PRIN command to DM device . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B #include .P .BI "int mpath_persistent_reserve_in" "(int fd, int rq_servact, struct prin_resp *resp, int noisy, int verbose)" .P . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . The function in the \fBmpath_persistent_reserve_in ()\fR sends PRIN command to the DM device and gets the response. .TP .B Parameters: .RS .TP 12 .I fd The file descriptor of a multipath device. Input argument. .TP .I rq_servact PRIN command service action. Input argument. .TP .I resp The response from PRIN service action. The caller should manage the memory allocation of this structure. .TP .I noisy Turn on debugging trace: Input argument. 0->Disable, 1->Enable. .TP .I verbose Set verbosity level. Input argument. value:[0-3]. 0->Errors, 1->Warnings, 2->Info, 3->Debug. .RE . . .\" ---------------------------------------------------------------------------- .SH RETURNS .\" ---------------------------------------------------------------------------- . .TP 12 .B MPATH_PR_SUCCESS If PR command successful. .TP .B MPATH_PR_SYNTAX_ERROR If syntax error or invalid parameter. .TP .B MPATH_PR_SENSE_NOT_READY If command fails with [sk,asc,ascq: 0x2,*,*]. .TP .B MPATH_PR_SENSE_MEDIUM_ERROR If command fails with [sk,asc,ascq: 0x3,*,*]. .TP .B MPATH_PR_SENSE_HARDWARE_ERROR If command fails with [sk,asc,ascq: 0x4,*,*]. .TP .B MPATH_PR_SENSE_INVALID_OP If command fails with [sk,asc,ascq: 0x5,0x20,0x0]. .TP .B MPATH_PR_ILLEGAL_REQ If command fails with [sk,asc,ascq: 0x5,*,*]. .TP .B MPATH_PR_SENSE_UNIT_ATTENTION If command fails with [sk,asc,ascq: 0x6,*,*]. .TP .B MPATH_PR_SENSE_ABORTED_COMMAND If command fails with [sk,asc,ascq: 0xb,*,*]. .TP .B MPATH_PR_NO_SENSE If command fails with [sk,asc,ascq: 0x0,*,*]. .TP .B MPATH_PR_SENSE_MALFORMED If command fails with SCSI command malformed. .TP .B MPATH_PR_FILE_ERROR If command fails while accessing file (device node) problems(e.g. not found). .TP .B MPATH_PR_DMMP_ERROR If Device Mapper related error.(e.g Error in getting dm info). .TP .B MPATH_PR_OTHER If other error/warning has occurred(e.g transport or driver error). . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR mpathpersist (8). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/libmpathpersist/mpath_persistent_reserve_out.3000066400000000000000000000101321475246302400262540ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MPATH_PERSISTENT_RESERVE_OUT 3 2024-02-09 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . mpath_persistent_reserve_out \- send PROUT command to DM device . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B #include .P .BI "int mpath_persistent_reserve_out" "(int fd, int rq_servact, struct prin_resp *resp, int noisy, int verbose)" .P . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . The function in the \fBmpath_persistent_reserve_out ()\fR sends PROUT command to the DM device and gets the response. .TP .B Parameters: .RS .TP 12 .I fd The file descriptor of a multipath device. Input argument. .TP .I rq_servact PROUT command service action. Input argument. .TP .I rq_scope Persistent reservation scope. The value should be always LU_SCOPE (0h). .TP .I rq_type Persistent reservation type. The valid values of persistent reservation types are: .RS .IP 5h (Write exclusive - registrants only). .IP 6h (Exclusive access - registrants only). .IP 7h (Write exclusive - All registrants). .IP 8h (Exclusive access - All registrants). .RE .TP .I paramp PROUT command parameter data. The paramp is a struct which describes PROUT parameter list. Caller should manage the memory allocation of this structure. .TP .I noisy Turn on debugging trace: Input argument. 0->Disable, 1->Enable. .TP .I verbose Set verbosity level. Input argument. value: 0 to 3. 0->Errors, 1->Warnings, 2->Info, 3->Debug. .RE . . .\" ---------------------------------------------------------------------------- .SH RETURNS .\" ---------------------------------------------------------------------------- . .TP 12 .B MPATH_PR_SUCCESS If PR command successful else returns any one of the status mentioned below. .TP .B MPATH_PR_SYNTAX_ERROR If syntax error or invalid parameter. .TP .B MPATH_PR_SENSE_NOT_READY If command fails with [sk,asc,ascq: 0x2,*,*]. .TP .B MPATH_PR_SENSE_MEDIUM_ERROR If command fails with [sk,asc,ascq: 0x3,*,*]. .TP .B MPATH_PR_SENSE_HARDWARE_ERROR If command fails with [sk,asc,ascq: 0x4,*,*]. .TP .B MPATH_PR_SENSE_INVALID_OP If command fails with [sk,asc,ascq: 0x5,0x20,0x0]. .TP .B MPATH_PR_ILLEGAL_REQ If command fails with [sk,asc,ascq: 0x5,*,*]. .TP .B MPATH_PR_SENSE_UNIT_ATTENTION If command fails with [sk,asc,ascq: 0x6,*,*]. .TP .B MPATH_PR_SENSE_ABORTED_COMMAND If command fails with [sk,asc,ascq: 0xb,*,*]. .TP .B MPATH_PR_NO_SENSE If command fails with [sk,asc,ascq: 0x0,*,*]. .TP .B MPATH_PR_SENSE_MALFORMED If command fails with SCSI command malformed. .TP .B MPATH_PR_FILE_ERROR If command fails while accessing file (device node) problems(e.g. not found). .TP .B MPATH_PR_DMMP_ERROR If Device Mapper related error.(e.g Error in getting dm info). .TP .B MPATH_PR_OTHER If other error/warning has occurred(e.g transport or driver error). .TP .B MPATH_PR_RESERV_CONFLICT If command fails with reservation conflict. . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR mpathpersist (8). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/libmpathpersist/mpath_pr_ioctl.c000066400000000000000000000355221475246302400233370ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "mpath_pr_ioctl.h" #include "mpath_persist.h" #include "unaligned.h" #include "debug.h" #include "structs.h" /* FILE_NAME_SIZE */ #define TIMEOUT 2000 #define MAXRETRY 5 int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp *resp, int noisy); int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, SenseData_t *Sensedata); void dumpHex(const char* str, int len, int no_ascii); int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); uint32_t format_transportids(struct prout_param_descriptor *paramp); void convert_be32_to_cpu(uint32_t *num); void convert_be16_to_cpu(uint16_t *num); void decode_transport_id(struct prin_fulldescr *fdesc, unsigned char * p, int length); int get_prin_length(int rq_servact); int mpath_isLittleEndian(void); unsigned int mpath_mx_alloc_len; int prout_do_scsi_ioctl(char * dev, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy) { int status, paramlen = 24, ret = 0; uint32_t translen=0; int retry = MAXRETRY; SenseData_t Sensedata; struct sg_io_hdr io_hdr; char devname[FILE_NAME_SIZE]; int fd = -1; snprintf(devname, FILE_NAME_SIZE, "/dev/%s",dev); fd = open(devname, O_RDONLY); if(fd < 0){ condlog (1, "%s: unable to open device.", dev); return MPATH_PR_FILE_ERROR; } unsigned char cdb[MPATH_PROUT_CMDLEN] = {MPATH_PROUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; if (paramp->sa_flags & MPATH_F_SPEC_I_PT_MASK) { translen = format_transportids(paramp); paramlen = 24 + translen; } else paramlen = 24; if ( rq_servact > 0) cdb[1] = (unsigned char)(rq_servact & 0x1f); cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf)); cdb[7] = (unsigned char)((paramlen >> 8) & 0xff); cdb[8] = (unsigned char)(paramlen & 0xff); retry : condlog(4, "%s: rq_servact = %d", dev, rq_servact); condlog(4, "%s: rq_scope = %d ", dev, rq_scope); condlog(4, "%s: rq_type = %d ", dev, rq_type); condlog(4, "%s: paramlen = %d", dev, paramlen); if (noisy) { condlog(4, "%s: Persistent Reservation OUT parameter:", dev); dumpHex((const char *)paramp, paramlen,1); } memset(&Sensedata, 0, sizeof(SenseData_t)); memset(&io_hdr,0 , sizeof( struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = MPATH_PROUT_CMDLEN; io_hdr.cmdp = cdb; io_hdr.sbp = (void *)&Sensedata; io_hdr.mx_sb_len = sizeof (SenseData_t); io_hdr.timeout = TIMEOUT; if (paramlen > 0) { io_hdr.dxferp = (void *)paramp; io_hdr.dxfer_len = paramlen; io_hdr.dxfer_direction = SG_DXFER_TO_DEV ; } else { io_hdr.dxfer_direction = SG_DXFER_NONE; } ret = ioctl(fd, SG_IO, &io_hdr); if (ret < 0) { condlog(0, "%s: ioctl failed %d", dev, ret); close(fd); return ret; } condlog(4, "%s: Duration=%u (ms)", dev, io_hdr.duration); status = mpath_translate_response(dev, io_hdr, &Sensedata); condlog(3, "%s: status = %d", dev, status); if (status == MPATH_PR_SENSE_UNIT_ATTENTION && (retry > 0)) { --retry; condlog(3, "%s: retrying for Unit Attention. Remaining retries = %d", dev, retry); goto retry; } if (((status == MPATH_PR_SENSE_NOT_READY )&& (Sensedata.ASC == 0x04)&& (Sensedata.ASCQ == 0x07))&& (retry > 0)) { usleep(1000); --retry; condlog(3, "%s: retrying for sense 02/04/07." " Remaining retries = %d", dev, retry); goto retry; } close(fd); return status; } /* * Helper macro to avoid overflow of prout_param_descriptor in * format_transportids(). Data must not be written past * MPATH_MAX_PARAM_LEN bytes from struct prout_param_descriptor. */ #define check_overflow(ofs, n, start, label) \ do { \ if ((ofs) + (n) + \ offsetof(struct prout_param_descriptor, private_buffer) \ > MPATH_MAX_PARAM_LEN) \ { \ (ofs) = (start); \ goto label; \ } \ } while(0) uint32_t format_transportids(struct prout_param_descriptor *paramp) { unsigned int i = 0, len; uint32_t buff_offset = 4; memset(paramp->private_buffer, 0, sizeof(paramp->private_buffer)); for (i=0; i < paramp->num_transportid; i++ ) { uint32_t start_offset = buff_offset; check_overflow(buff_offset, 1, start_offset, end_loop); paramp->private_buffer[buff_offset] = (uint8_t)((paramp->trnptid_list[i]->format_code & 0xff)| (paramp->trnptid_list[i]->protocol_id & 0xff)); buff_offset += 1; switch(paramp->trnptid_list[i]->protocol_id) { case MPATH_PROTOCOL_ID_FC: check_overflow(buff_offset, 7 + 8 + 8, start_offset, end_loop); buff_offset += 7; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->n_port_name, 8); buff_offset +=8 ; buff_offset +=8 ; break; case MPATH_PROTOCOL_ID_SAS: check_overflow(buff_offset, 3 + 12, start_offset, end_loop); buff_offset += 3; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->sas_address, 8); buff_offset += 12; break; case MPATH_PROTOCOL_ID_ISCSI: len = (paramp->trnptid_list[i]->iscsi_name[1] & 0xff)+2; check_overflow(buff_offset, 1 + len, start_offset, end_loop); buff_offset += 1; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->iscsi_name,len); buff_offset += len ; break; } } end_loop: buff_offset -= 4; paramp->private_buffer[0] = (unsigned char)((buff_offset >> 24) & 0xff); paramp->private_buffer[1] = (unsigned char)((buff_offset >> 16) & 0xff); paramp->private_buffer[2] = (unsigned char)((buff_offset >> 8) & 0xff); paramp->private_buffer[3] = (unsigned char)(buff_offset & 0xff); buff_offset += 4; return buff_offset; } static void mpath_format_readkeys(struct prin_resp *pr_buff) { convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.prgeneration); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.additional_length); } static void mpath_format_readresv(struct prin_resp *pr_buff) { convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readresv.prgeneration); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readresv.additional_length); return; } static void mpath_format_reportcapabilities(struct prin_resp *pr_buff) { convert_be16_to_cpu(&pr_buff->prin_descriptor.prin_readcap.length); convert_be16_to_cpu(&pr_buff->prin_descriptor.prin_readcap.pr_type_mask); return; } static void mpath_format_readfullstatus(struct prin_resp *pr_buff) { int num; uint32_t fdesc_count=0; unsigned char *p; char *ppbuff; uint32_t additional_length, k, tid_len_len = 0; char tempbuff[MPATH_MAX_PARAM_LEN]; struct prin_fulldescr fdesc; static const unsigned int pbuf_size = sizeof(pr_buff->prin_descriptor.prin_readfd.private_buffer); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readfd.prgeneration); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readfd.number_of_descriptor); if (pr_buff->prin_descriptor.prin_readfd.number_of_descriptor == 0) { condlog(3, "No registration or reservation found."); return; } additional_length = pr_buff->prin_descriptor.prin_readfd.number_of_descriptor; if (additional_length > pbuf_size) { condlog(3, "PRIN length %u exceeds max length %d", additional_length, pbuf_size); return; } memset(&fdesc, 0, sizeof(struct prin_fulldescr)); memcpy( tempbuff, pr_buff->prin_descriptor.prin_readfd.private_buffer, pbuf_size); memset(&pr_buff->prin_descriptor.prin_readfd.private_buffer, 0, pbuf_size); p =(unsigned char *)tempbuff; ppbuff = (char *)pr_buff->prin_descriptor.prin_readfd.private_buffer; for (k = 0; k < additional_length; k += num, p += num) { memcpy(&fdesc.key, p, 8 ); fdesc.flag = p[12]; fdesc.scope_type = p[13]; fdesc.rtpi = get_unaligned_be16(&p[18]); tid_len_len = get_unaligned_be32(&p[20]); if (tid_len_len + 24 + k > additional_length) { condlog(0, "%s: corrupt PRIN response: status descriptor end %d exceeds length %d", __func__, tid_len_len + k + 24, additional_length); tid_len_len = additional_length - k - 24; } if (tid_len_len > 0) decode_transport_id( &fdesc, &p[24], tid_len_len); num = 24 + tid_len_len; memcpy(ppbuff, &fdesc, sizeof(struct prin_fulldescr)); pr_buff->prin_descriptor.prin_readfd.descriptors[fdesc_count]= (struct prin_fulldescr *)ppbuff; ppbuff += sizeof(struct prin_fulldescr); ++fdesc_count; } pr_buff->prin_descriptor.prin_readfd.number_of_descriptor = fdesc_count; return; } void decode_transport_id(struct prin_fulldescr *fdesc, unsigned char * p, int length) { unsigned int num; int jump, k; for (k = 0, jump = 24; k < length; k += jump, p += jump) { fdesc->trnptid.format_code = ((p[0] >> 6) & 0x3); fdesc->trnptid.protocol_id = (p[0] & 0xf); switch (fdesc->trnptid.protocol_id) { case MPATH_PROTOCOL_ID_FC: memcpy(&fdesc->trnptid.n_port_name, &p[8], 8); jump = 24; break; case MPATH_PROTOCOL_ID_ISCSI: num = get_unaligned_be16(&p[2]); if (num >= sizeof(fdesc->trnptid.iscsi_name)) num = sizeof(fdesc->trnptid.iscsi_name); memcpy(&fdesc->trnptid.iscsi_name, &p[4], num); jump = (((num + 4) < 24) ? 24 : num + 4); break; case MPATH_PROTOCOL_ID_SAS: memcpy(&fdesc->trnptid.sas_address, &p[4], 8); jump = 24; break; default: jump = 24; break; } } } int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int noisy) { int ret, status, got, fd; int mx_resp_len; SenseData_t Sensedata; int retry = MAXRETRY; struct sg_io_hdr io_hdr; char devname[FILE_NAME_SIZE]; unsigned char cdb[MPATH_PRIN_CMDLEN] = {MPATH_PRIN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; snprintf(devname, FILE_NAME_SIZE, "/dev/%s",dev); fd = open(devname, O_RDONLY); if(fd < 0){ condlog(0, "%s: Unable to open device ", dev); return MPATH_PR_FILE_ERROR; } if (mpath_mx_alloc_len) mx_resp_len = mpath_mx_alloc_len; else mx_resp_len = get_prin_length(rq_servact); if (mx_resp_len == 0) { status = MPATH_PR_SYNTAX_ERROR; goto out; } cdb[1] = (unsigned char)(rq_servact & 0x1f); cdb[7] = (unsigned char)((mx_resp_len >> 8) & 0xff); cdb[8] = (unsigned char)(mx_resp_len & 0xff); retry : memset(&Sensedata, 0, sizeof(SenseData_t)); memset(&io_hdr,0 , sizeof( struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = MPATH_PRIN_CMDLEN; io_hdr.mx_sb_len = sizeof (SenseData_t); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.cmdp = cdb; io_hdr.sbp = (void *)&Sensedata; io_hdr.timeout = TIMEOUT; io_hdr.dxfer_len = mx_resp_len; io_hdr.dxferp = (void *)resp; ret =ioctl(fd, SG_IO, &io_hdr); if (ret < 0){ condlog(0, "%s: IOCTL failed %d", dev, ret); status = MPATH_PR_OTHER; goto out; } got = mx_resp_len - io_hdr.resid; condlog(3, "%s: duration = %u (ms)", dev, io_hdr.duration); condlog(4, "%s: persistent reservation in: requested %d bytes but got %d bytes)", dev, mx_resp_len, got); status = mpath_translate_response(dev, io_hdr, &Sensedata); if (status == MPATH_PR_SENSE_UNIT_ATTENTION && (retry > 0)) { --retry; condlog(3, "%s: retrying for Unit Attention. Remaining retries = %d", dev, retry); goto retry; } if (((status == MPATH_PR_SENSE_NOT_READY )&& (Sensedata.ASC == 0x04)&& (Sensedata.ASCQ == 0x07))&& (retry > 0)) { usleep(1000); --retry; condlog(3, "%s: retrying for 02/04/07. Remaining retries = %d", dev, retry); goto retry; } if (status != MPATH_PR_SUCCESS) goto out; if (noisy) dumpHex((const char *)resp, got , 1); switch (rq_servact) { case MPATH_PRIN_RKEY_SA : mpath_format_readkeys(resp); break; case MPATH_PRIN_RRES_SA : mpath_format_readresv(resp); break; case MPATH_PRIN_RCAP_SA : mpath_format_reportcapabilities(resp); break; case MPATH_PRIN_RFSTAT_SA : mpath_format_readfullstatus(resp); } out: close(fd); return status; } int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, SenseData_t *Sensedata) { condlog(3, "%s: status driver:%02x host:%02x scsi:%02x", dev, io_hdr.driver_status, io_hdr.host_status ,io_hdr.status); io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) return MPATH_PR_SUCCESS; switch(io_hdr.status) { case SAM_STAT_GOOD: break; case SAM_STAT_CHECK_CONDITION: condlog(3, "%s: Sense_Key=%02x, ASC=%02x ASCQ=%02x", dev, Sensedata->Sense_Key, Sensedata->ASC, Sensedata->ASCQ); switch(Sensedata->Sense_Key) { case NO_SENSE: return MPATH_PR_NO_SENSE; case RECOVERED_ERROR: return MPATH_PR_SUCCESS; case NOT_READY: return MPATH_PR_SENSE_NOT_READY; case MEDIUM_ERROR: return MPATH_PR_SENSE_MEDIUM_ERROR; case BLANK_CHECK: return MPATH_PR_OTHER; case HARDWARE_ERROR: return MPATH_PR_SENSE_HARDWARE_ERROR; case ILLEGAL_REQUEST: return MPATH_PR_ILLEGAL_REQ; case UNIT_ATTENTION: return MPATH_PR_SENSE_UNIT_ATTENTION; case DATA_PROTECT: case COPY_ABORTED: return MPATH_PR_OTHER; case ABORTED_COMMAND: return MPATH_PR_SENSE_ABORTED_COMMAND; default : return MPATH_PR_OTHER; } case SAM_STAT_RESERVATION_CONFLICT: return MPATH_PR_RESERV_CONFLICT; default : return MPATH_PR_OTHER; } switch(io_hdr.host_status) { case DID_OK : break; default : return MPATH_PR_OTHER; } switch(io_hdr.driver_status) { case DRIVER_OK: break; default : return MPATH_PR_OTHER; } return MPATH_PR_SUCCESS; } void convert_be16_to_cpu(uint16_t *num) { *num = get_unaligned_be16(num); } void convert_be32_to_cpu(uint32_t *num) { *num = get_unaligned_be32(num); } void dumpHex(const char* str, int len, int log) { const char * p = str; unsigned char c; char buff[82]; const int bpstart = 5; int bpos = bpstart; int k; if (len <= 0) return; memset(buff, ' ', 80); buff[80] = '\0'; for (k = 0; k < len; k++) { c = *p++; bpos += 3; if (bpos == (bpstart + (9 * 3))) bpos++; sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c); buff[bpos + 2] = ' '; if ((k > 0) && (0 == ((k + 1) % 16))) { if (log) condlog(0, "%.76s" , buff); else printf("%.76s" , buff); bpos = bpstart; memset(buff, ' ', 80); } } if (bpos > bpstart) { buff[bpos + 2] = '\0'; if (log) condlog(0, "%s", buff); else printf("%s\n" , buff); } return; } int get_prin_length(int rq_servact) { int mx_resp_len; switch (rq_servact) { case MPATH_PRIN_RKEY_SA: mx_resp_len = sizeof(struct prin_readdescr); break; case MPATH_PRIN_RRES_SA : mx_resp_len = sizeof(struct prin_resvdescr); break; case MPATH_PRIN_RCAP_SA : mx_resp_len = sizeof(struct prin_capdescr); break; case MPATH_PRIN_RFSTAT_SA: mx_resp_len = sizeof(struct print_fulldescr_list) + sizeof(struct prin_fulldescr *)*32; break; default: condlog(0, "invalid service action, %d", rq_servact); mx_resp_len = 0; break; } if (mx_resp_len > MPATH_MAX_PARAM_LEN) mx_resp_len = MPATH_MAX_PARAM_LEN; return mx_resp_len; } multipath-tools-0.11.1/libmpathpersist/mpath_pr_ioctl.h000066400000000000000000000064311475246302400233410ustar00rootroot00000000000000#ifndef MPATH_PR_IOCTL_H_INCLUDED #define MPATH_PR_IOCTL_H_INCLUDED #define MPATH_XFER_HOST_DEV 0 /*data transfer from initiator to target */ #define MPATH_XFER_DEV_HOST 1 /*data transfer from target to initiator */ #define MPATH_XFER_NONE 2 /*no data transfer */ #define MPATH_XFER_UNKNOWN 3 /*data transfer direction is unknown */ #if 0 static const char * pr_type_strs[] = { "obsolete [0]", "Write Exclusive", "obsolete [2]", "Exclusive Access", "obsolete [4]", "Write Exclusive, registrants only", "Exclusive Access, registrants only", "Write Exclusive, all registrants", "Exclusive Access, all registrants", "obsolete [9]", "obsolete [0xa]", "obsolete [0xb]", "obsolete [0xc]", "obsolete [0xd]", "obsolete [0xe]", "obsolete [0xf]", }; #endif typedef unsigned int LWORD; /* unsigned numeric, bit patterns */ typedef unsigned char BYTE; /* unsigned numeric, bit patterns */ typedef struct SenseData { BYTE Error_Code; BYTE Segment_Number; /* not applicable to DAC */ BYTE Sense_Key; BYTE Information[ 4 ]; BYTE Additional_Len; LWORD Command_Specific_Info; BYTE ASC; BYTE ASCQ; BYTE Field_Replaceable_Unit; BYTE Sense_Key_Specific_Info[ 3 ]; BYTE Recovery_Action[ 2 ]; BYTE Total_Errors; BYTE Total_Retries; BYTE ASC_Stack_1; BYTE ASCQ_Stack_1; BYTE ASC_Stack_2; BYTE ASCQ_Stack_2; BYTE Additional_FRU_Info[ 8 ]; BYTE Error_Specific_Info[ 3 ]; BYTE Error_Detection_Point[ 4 ]; BYTE Original_CDB[10]; BYTE Host_ID; BYTE Host_Descriptor[ 2 ]; BYTE Serial_Number[ 16 ]; BYTE Array_SW_Revision[ 4 ]; BYTE Data_Xfer_Operation; BYTE LUN_Number; BYTE LUN_Status; BYTE Drive_ID; BYTE Xfer_Start_Drive_ID; BYTE Drive_SW_Revision[ 4 ]; BYTE Drive_Product_ID[ 16 ]; BYTE PowerUp_Status[ 2 ]; BYTE RAID_Level; BYTE Drive_Sense_ID[ 2 ]; BYTE Drive_Sense_Data[ 32 ]; BYTE Reserved2[24]; } SenseData_t; #define MPATH_PRIN_CMD 0x5e #define MPATH_PRIN_CMDLEN 10 #define MPATH_PROUT_CMD 0x5f #define MPATH_PROUT_CMDLEN 10 #define DID_OK 0x00 /* * Status codes */ #define SAM_STAT_GOOD 0x00 #define SAM_STAT_CHECK_CONDITION 0x02 #define SAM_STAT_CONDITION_MET 0x04 #define SAM_STAT_BUSY 0x08 #define SAM_STAT_INTERMEDIATE 0x10 #define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 #define SAM_STAT_RESERVATION_CONFLICT 0x18 #define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */ #define SAM_STAT_TASK_SET_FULL 0x28 #define SAM_STAT_ACA_ACTIVE 0x30 #define SAM_STAT_TASK_ABORTED 0x40 #define STATUS_MASK 0x3e /* * SENSE KEYS */ #define NO_SENSE 0x00 #define RECOVERED_ERROR 0x01 #define NOT_READY 0x02 #define MEDIUM_ERROR 0x03 #define HARDWARE_ERROR 0x04 #define ILLEGAL_REQUEST 0x05 #define UNIT_ATTENTION 0x06 #define DATA_PROTECT 0x07 #define BLANK_CHECK 0x08 #define COPY_ABORTED 0x0a #define ABORTED_COMMAND 0x0b #define VOLUME_OVERFLOW 0x0d #define MISCOMPARE 0x0e /* Driver status */ #define DRIVER_OK 0x00 #endif multipath-tools-0.11.1/libmpathpersist/mpath_updatepr.c000066400000000000000000000035151475246302400233450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "mpath_cmd.h" #include "vector.h" #include "globals.h" #include "config.h" #include "uxsock.h" #include "mpathpr.h" static int do_update_pr(char *alias, char *cmd, char *key) { int fd; char str[256]; char *reply; int ret = 0; int timeout; struct config *conf; conf = get_multipath_config(); timeout = conf->uxsock_timeout; put_multipath_config(conf); fd = mpath_connect(); if (fd == -1) { condlog (0, "ux socket connect error"); return -1; } if (key) snprintf(str,sizeof(str),"%s map %s key %s", cmd, alias, key); else snprintf(str,sizeof(str),"%s map %s", cmd, alias); condlog (2, "%s: pr message=%s", alias, str); if (send_packet(fd, str) != 0) { condlog(2, "%s: message=%s send error=%d", alias, str, errno); mpath_disconnect(fd); return -1; } ret = recv_packet(fd, &reply, timeout); if (ret < 0) { condlog(2, "%s: message=%s recv error=%d", alias, str, errno); ret = -1; } else { condlog (2, "%s: message=%s reply=%s", alias, str, reply); if (reply && strncmp(reply,"ok", 2) == 0) ret = 0; else ret = -1; } free(reply); mpath_disconnect(fd); return ret; } int update_prflag(char *mapname, int set) { return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus", NULL); } int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags) { char str[256]; if (!prkey) return do_update_pr(mapname, "unsetprkey", NULL); sprintf(str, "%" PRIx64 "%s", prkey, (sa_flags & MPATH_F_APTPL_MASK) ? ":aptpl" : ""); return do_update_pr(mapname, "setprkey", str); } multipath-tools-0.11.1/libmpathpersist/mpathpr.h000066400000000000000000000005401475246302400220030ustar00rootroot00000000000000#ifndef MPATHPR_H_INCLUDED #define MPATHPR_H_INCLUDED /* * This header file contains symbols that are only used by * libmpathpersist internally. */ int update_prflag(char *mapname, int set); int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); #define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) #endif multipath-tools-0.11.1/libmpathutil/000077500000000000000000000000001475246302400174445ustar00rootroot00000000000000multipath-tools-0.11.1/libmpathutil/Makefile000066400000000000000000000020501475246302400211010ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # include ../Makefile.inc DEVLIB := libmpathutil.so CPPFLAGS += -I. -I$(multipathdir) -I$(mpathcmddir) $(SYSTEMD_CPPFLAGS) CFLAGS += $(LIB_CFLAGS) -D_GNU_SOURCE LIBDEPS += -lpthread -ldl -ludev -L$(mpathcmddir) -lmpathcmd $(SYSTEMD_LIBDEPS) -lrt # object files referencing MULTIPATH_DIR or CONFIG_DIR # they need to be recompiled for unit tests # other object files OBJS := parser.o vector.o util.o debug.o time-util.o \ uxsock.o log_pthread.o log.o strbuf.o globals.o msort.o all: $(DEVLIB) include $(TOPDIR)/rules.mk install: all $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) uninstall: $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi nvme-ioctl.c nvme-ioctl.h $(NV_VERSION_SCRIPT) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmpathutil/debug.c000066400000000000000000000015241475246302400207000ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include "log_pthread.h" #include #include #include "../third-party/valgrind/drd.h" #include "vector.h" #include "config.h" #include "defaults.h" #include "debug.h" #include "time-util.h" #include "util.h" int logsink; int libmp_verbosity = DEFAULT_VERBOSITY; void dlog(int prio, const char * fmt, ...) { va_list ap; va_start(ap, fmt); if (logsink != LOGSINK_SYSLOG) { if (logsink == LOGSINK_STDERR_WITH_TIME) { struct timespec ts; char buff[32]; get_monotonic_time(&ts); safe_sprintf(buff, "%ld.%06ld", (long)ts.tv_sec, ts.tv_nsec/1000); fprintf(stderr, "%s | ", buff); } vfprintf(stderr, fmt, ap); } else log_safe(prio + 3, fmt, ap); va_end(ap); } multipath-tools-0.11.1/libmpathutil/debug.h000066400000000000000000000011711475246302400207030ustar00rootroot00000000000000#ifndef DEBUG_H_INCLUDED #define DEBUG_H_INCLUDED void dlog (int prio, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #include #include #include "log_pthread.h" extern int logsink; extern int libmp_verbosity; #ifndef MAX_VERBOSITY #define MAX_VERBOSITY 4 #endif enum { LOGSINK_STDERR_WITH_TIME = 0, LOGSINK_STDERR_WITHOUT_TIME = -1, LOGSINK_SYSLOG = 1, }; #define condlog(prio, fmt, args...) \ do { \ int __p = (prio); \ \ if (__p <= MAX_VERBOSITY && __p <= libmp_verbosity) \ dlog(__p, fmt "\n", ##args); \ } while (0) #endif /* DEBUG_H_INCLUDED */ multipath-tools-0.11.1/libmpathutil/globals.c000066400000000000000000000003461475246302400212360ustar00rootroot00000000000000#include #include #include "globals.h" __attribute__((weak)) struct config *get_multipath_config(void) { return NULL; } __attribute__((weak)) void put_multipath_config(void *p __attribute__((unused))) {} multipath-tools-0.11.1/libmpathutil/globals.h000066400000000000000000000012611475246302400212400ustar00rootroot00000000000000#ifndef GLOBALS_H_INCLUDED #define GLOBALS_H_INCLUDED struct config; /* * libmultipath provides default implementations of * get_multipath_config() and put_multipath_config(). * Applications using these should use init_config(file, NULL) * to load the configuration, rather than load_config(file). * Likewise, uninit_config() should be used for teardown, but * using free_config() for that is supported, too. * Applications can define their own {get,put}_multipath_config() * functions, which override the library-internal ones, but * could still call libmp_{get,put}_multipath_config(). */ void put_multipath_config(void *); struct config *get_multipath_config(void); #endif multipath-tools-0.11.1/libmpathutil/libmpathutil.version000066400000000000000000000057151475246302400235610ustar00rootroot00000000000000/* * Copyright (c) 2020 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later * * libmultipath ABI (libmpathutil part) * * libmultipath doesn't have a stable ABI in the usual sense. In particular, * the library does not attempt to ship different versions of the same symbol * for backward compatibility. * * The ABI versioning only serves to avoid linking with a non-matching ABI, to * cut down the set of exported symbols, and to describe it. * The version string is LIBMULTIPATH_$MAJOR.$MINOR.$REL. * * Policy: * * * Bump $MAJOR for incompatible changes, like: * - symbols removed * - parameter list or return values changed for existing functions * - externally visible data structures changed in incompatible ways * (like offsets of previously existing struct members) * In this case, the new version doesn't inherit the previous versions, * because the new library doesn't provide the full previous ABI anymore. * All predecessors are merged into the new version. * * * Bump $MINOR for compatible changes, like adding symbols. * The new version inherits the previous ones. * * * Bump $REL to describe deviations from upstream, e.g. in * multipath-tools packages shipped by distributions. * The new version inherits the previous ones. */ /* * Symbols exported by both libmpathutil and libmultipath * libmpathutil exports just dummy symbols, intended to be overridden * by those in libmultipath. * CAUTION - the version in libmpathutil.version and libmultipath.version * must be THE SAME, otherwise the overriding will fail! */ LIBMPATHCOMMON_1.0.0 { get_multipath_config; put_multipath_config; }; LIBMPATHUTIL_4.0 { global: alloc_bitfield; alloc_strvec; append_strbuf_str; append_strbuf_str__; append_strbuf_quoted; basenamecpy; cleanup_charp; cleanup_fclose; cleanup_fd_ptr; cleanup_free_ptr; cleanup_mutex; cleanup_ucharp; cleanup_udev_device; cleanup_vector; cleanup_vector_free; convert_dev; dlog; filepresent; fill_strbuf; find_keyword; find_slot; free_keywords; free_scandir_result; free_strvec; get_linux_version_code; get_monotonic_time; get_strbuf_buf__; get_next_string; get_strbuf_len; get_strbuf_str; get_word; install_keyword__; install_sublevel; install_sublevel_end; is_quote; keyword_alloc; log_bitfield_overflow__; libmp_basename; libmp_strlcat; libmp_strlcpy; libmp_verbosity; log_safe; log_thread_reset; log_thread_start; log_thread_stop; logsink; msort; normalize_timespec; parse_devt; print_strbuf; process_file; pthread_cond_init_mono; recv_packet; reset_strbuf; safe_write; send_packet; set_max_fds; set_value; setup_thread_attr; strchop; should_exit; snprint_keyword; steal_strbuf_str; timespeccmp; timespecsub; truncate_strbuf; validate_config_strvec; ux_socket_listen; vector_alloc; vector_alloc_slot; vector_del_slot; vector_find_or_add_slot; vector_free; vector_insert_slot; vector_move_up; vector_reset; vector_set_slot; vector_sort; local: *; }; multipath-tools-0.11.1/libmpathutil/log.c000066400000000000000000000117251475246302400203770ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Jun'ichi Nomura, NEC */ #include #include #include #include #include #include #include #include "log.h" #include "util.h" #define ALIGN(len, s) (((len)+(s)-1)/(s)*(s)) struct logarea* la; static pthread_mutex_t logq_lock = PTHREAD_MUTEX_INITIALIZER; #if LOGDBG static void dump_logarea (void) { struct logmsg * msg; logdbg(stderr, "\n==== area: start addr = %p, end addr = %p ====\n", la->start, la->end); logdbg(stderr, "|addr |next |prio|msg\n"); for (msg = (struct logmsg *)la->head; (void *)msg != la->tail; msg = msg->next) logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); logdbg(stderr, "\n\n"); } #endif static int logarea_init (int size) { logdbg(stderr,"enter logarea_init\n"); la = (struct logarea *)calloc(1, sizeof(struct logarea)); if (!la) return 1; if (size < MAX_MSG_SIZE) size = DEFAULT_AREA_SIZE; la->start = calloc(1, size); if (!la->start) { free(la); la = NULL; return 1; } la->empty = 1; la->end = la->start + size; la->head = la->start; la->tail = la->start; la->buff = calloc(1, MAX_MSG_SIZE + sizeof(struct logmsg)); if (!la->buff) { free(la->start); free(la); la = NULL; return 1; } return 0; } int log_init(char *program_name, int size) { int ret = 1; logdbg(stderr,"enter log_init\n"); pthread_mutex_lock(&logq_lock); pthread_cleanup_push(cleanup_mutex, &logq_lock); openlog(program_name, 0, LOG_DAEMON); if (!la) ret = logarea_init(size); pthread_cleanup_pop(1); return ret; } static void free_logarea (void) { free(la->start); free(la->buff); free(la); la = NULL; return; } void log_close (void) { pthread_mutex_lock(&logq_lock); pthread_cleanup_push(cleanup_mutex, &logq_lock); if (la) free_logarea(); closelog(); pthread_cleanup_pop(1); return; } void log_reset (char *program_name) { pthread_mutex_lock(&logq_lock); pthread_cleanup_push(cleanup_mutex, &logq_lock); closelog(); openlog(program_name, 0, LOG_DAEMON); pthread_cleanup_pop(1); } __attribute__((format(printf, 2, 0))) static int _log_enqueue(int prio, const char * fmt, va_list ap) { int len, fwd; char buff[MAX_MSG_SIZE]; struct logmsg * msg; struct logmsg * lastmsg; lastmsg = (struct logmsg *)la->tail; if (!la->empty) { fwd = sizeof(struct logmsg) + strlen((char *)&lastmsg->str) * sizeof(char) + 1; la->tail += ALIGN(fwd, sizeof(void *)); } vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); len = ALIGN(sizeof(struct logmsg) + strlen(buff) * sizeof(char) + 1, sizeof(void *)); /* not enough space on tail : rewind */ if (la->head <= la->tail && len > (la->end - la->tail)) { logdbg(stderr, "enqueue: rewind tail to %p\n", la->tail); if (la->head == la->start ) { logdbg(stderr, "enqueue: cannot rewind tail, drop msg\n"); la->tail = lastmsg; return 1; /* can't reuse */ } la->tail = la->start; if (la->empty) la->head = la->start; } /* not enough space on head : drop msg */ if (la->head > la->tail && len >= (la->head - la->tail)) { logdbg(stderr, "enqueue: log area overrun, drop msg\n"); if (!la->empty) la->tail = lastmsg; return 1; } /* ok, we can stage the msg in the area */ la->empty = 0; msg = (struct logmsg *)la->tail; msg->prio = prio; memcpy((void *)&msg->str, buff, strlen(buff) + 1); lastmsg->next = la->tail; msg->next = la->head; logdbg(stderr, "enqueue: %p, %p, %i, %s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); #if LOGDBG dump_logarea(); #endif return 0; } int log_enqueue(int prio, const char *fmt, va_list ap) { int ret = 1; pthread_mutex_lock(&logq_lock); pthread_cleanup_push(cleanup_mutex, &logq_lock); if (la) ret = _log_enqueue(prio, fmt, ap); pthread_cleanup_pop(1); return ret; } static int _log_dequeue(void *buff) { struct logmsg * src = (struct logmsg *)la->head; struct logmsg * dst = (struct logmsg *)buff; struct logmsg * lst = (struct logmsg *)la->tail; if (la->empty) return 1; int len = strlen((char *)&src->str) * sizeof(char) + sizeof(struct logmsg) + 1; dst->prio = src->prio; memcpy(dst, src, len); if (la->tail == la->head) la->empty = 1; /* we purge the last logmsg */ else { la->head = src->next; lst->next = la->head; } logdbg(stderr, "dequeue: %p, %p, %i, %s\n", (void *)src, src->next, src->prio, (char *)&src->str); memset((void *)src, 0, len); return 0; } int log_dequeue(void *buff) { int ret = 1; pthread_mutex_lock(&logq_lock); pthread_cleanup_push(cleanup_mutex, &logq_lock); if (la) ret = _log_dequeue(buff); pthread_cleanup_pop(1); return ret; } /* * this one can block under memory pressure */ void log_syslog (void * buff) { struct logmsg * msg = (struct logmsg *)buff; syslog(msg->prio, "%s", (char *)&msg->str); } multipath-tools-0.11.1/libmpathutil/log.h000066400000000000000000000014231475246302400203760ustar00rootroot00000000000000#ifndef LOG_H_INCLUDED #define LOG_H_INCLUDED #define DEFAULT_AREA_SIZE 16384 #define MAX_MSG_SIZE 256 #ifndef LOGLEVEL #define LOGLEVEL 5 #endif #if LOGDBG #define logdbg(file, fmt, args...) fprintf(file, fmt, ##args) #else #define logdbg(file, fmt, args...) do {} while (0) #endif struct logmsg { short int prio; void * next; char str[0]; }; struct logarea { int empty; void * head; void * tail; void * start; void * end; char * buff; }; extern struct logarea* la; int log_init (char * progname, int size); void log_close (void); void log_reset (char * progname); int log_enqueue (int prio, const char * fmt, va_list ap) __attribute__((format(printf, 2, 0))); int log_dequeue (void *); void log_syslog (void *); void dump_logmsg (void *); #endif /* LOG_H_INCLUDED */ multipath-tools-0.11.1/libmpathutil/log_pthread.c000066400000000000000000000062571475246302400221120ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include #include #include "log_pthread.h" #include "log.h" #include "lock.h" #include "util.h" static pthread_t log_thr; /* logev_lock must not be taken with logq_lock held */ static pthread_mutex_t logev_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t logev_cond = PTHREAD_COND_INITIALIZER; static int logq_running; static int log_messages_pending; void log_safe (int prio, const char * fmt, va_list ap) { bool running; if (prio > LOG_DEBUG) prio = LOG_DEBUG; /* * logev_lock protects logq_running. By holding it, we avoid a race * with log_thread_stop() -> log_close(), which would free the logarea. */ pthread_mutex_lock(&logev_lock); pthread_cleanup_push(cleanup_mutex, &logev_lock); running = logq_running; if (running) { log_enqueue(prio, fmt, ap); log_messages_pending = 1; pthread_cond_signal(&logev_cond); } pthread_cleanup_pop(1); if (!running) vsyslog(prio, fmt, ap); } static void flush_logqueue (void) { int empty; do { empty = log_dequeue(la->buff); if (!empty) log_syslog(la->buff); } while (empty == 0); } static void cleanup_log_thread(__attribute__((unused)) void *arg) { logdbg(stderr, "log thread exiting"); pthread_mutex_lock(&logev_lock); logq_running = 0; pthread_mutex_unlock(&logev_lock); } static void * log_thread (__attribute__((unused)) void * et) { int running; pthread_mutex_lock(&logev_lock); running = logq_running; if (!running) logq_running = 1; pthread_cond_signal(&logev_cond); pthread_mutex_unlock(&logev_lock); if (running) /* already started */ return NULL; pthread_cleanup_push(cleanup_log_thread, NULL); mlockall(MCL_CURRENT | MCL_FUTURE); logdbg(stderr,"enter log_thread\n"); while (1) { pthread_mutex_lock(&logev_lock); pthread_cleanup_push(cleanup_mutex, &logev_lock); while (!log_messages_pending) /* this is a cancellation point */ pthread_cond_wait(&logev_cond, &logev_lock); log_messages_pending = 0; pthread_cleanup_pop(1); flush_logqueue(); } pthread_cleanup_pop(1); return NULL; } void log_thread_start (pthread_attr_t *attr) { int running = 0; logdbg(stderr,"enter log_thread_start\n"); if (log_init("multipathd", 0)) { fprintf(stderr,"can't initialize log buffer\n"); exit(1); } pthread_mutex_lock(&logev_lock); pthread_cleanup_push(cleanup_mutex, &logev_lock); if (!pthread_create(&log_thr, attr, log_thread, NULL)) while (!(running = logq_running)) pthread_cond_wait(&logev_cond, &logev_lock); pthread_cleanup_pop(1); if (!running) { fprintf(stderr,"can't start log thread\n"); exit(1); } return; } void log_thread_reset (void) { logdbg(stderr,"resetting log\n"); log_reset("multipathd"); } void log_thread_stop (void) { int running; if (!la) return; logdbg(stderr,"enter log_thread_stop\n"); pthread_mutex_lock(&logev_lock); pthread_cleanup_push(cleanup_mutex, &logev_lock); running = logq_running; if (running) { pthread_cancel(log_thr); pthread_cond_signal(&logev_cond); } pthread_cleanup_pop(1); if (running) pthread_join(log_thr, NULL); flush_logqueue(); log_close(); } multipath-tools-0.11.1/libmpathutil/log_pthread.h000066400000000000000000000004771475246302400221150ustar00rootroot00000000000000#ifndef LOG_PTHREAD_H_INCLUDED #define LOG_PTHREAD_H_INCLUDED #include void log_safe(int prio, const char * fmt, va_list ap) __attribute__((format(printf, 2, 0))); void log_thread_start(pthread_attr_t *attr); void log_thread_reset (void); void log_thread_stop(void); #endif /* LOG_PTHREAD_H_INCLUDED */ multipath-tools-0.11.1/libmpathutil/msort.c000066400000000000000000000134661475246302400207660ustar00rootroot00000000000000/* An alternative to qsort, with an identical interface. This file is part of the GNU C Library. Copyright (C) 1992-2022 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #include #include #include #include #include #include #include "msort.h" typedef int(*__compar_d_fn_t)(const void *, const void *, void *); struct msort_param { size_t s; size_t var; __compar_d_fn_t cmp; void *arg; char *t; }; static void msort_with_tmp (const struct msort_param *p, void *b, size_t n); static void msort_with_tmp (const struct msort_param *p, void *b, size_t n) { char *b1, *b2; size_t n1, n2; if (n <= 1) return; n1 = n / 2; n2 = n - n1; b1 = b; b2 = (char *) b + (n1 * p->s); msort_with_tmp (p, b1, n1); msort_with_tmp (p, b2, n2); char *tmp = p->t; const size_t s = p->s; __compar_d_fn_t cmp = p->cmp; void *arg = p->arg; switch (p->var) { case 0: while (n1 > 0 && n2 > 0) { if ((*cmp) (b1, b2, arg) <= 0) { *(uint32_t *) tmp = *(uint32_t *) b1; b1 += sizeof (uint32_t); --n1; } else { *(uint32_t *) tmp = *(uint32_t *) b2; b2 += sizeof (uint32_t); --n2; } tmp += sizeof (uint32_t); } break; case 1: while (n1 > 0 && n2 > 0) { if ((*cmp) (b1, b2, arg) <= 0) { *(uint64_t *) tmp = *(uint64_t *) b1; b1 += sizeof (uint64_t); --n1; } else { *(uint64_t *) tmp = *(uint64_t *) b2; b2 += sizeof (uint64_t); --n2; } tmp += sizeof (uint64_t); } break; case 2: while (n1 > 0 && n2 > 0) { unsigned long *tmpl = (unsigned long *) tmp; unsigned long *bl; tmp += s; if ((*cmp) (b1, b2, arg) <= 0) { bl = (unsigned long *) b1; b1 += s; --n1; } else { bl = (unsigned long *) b2; b2 += s; --n2; } while (tmpl < (unsigned long *) tmp) *tmpl++ = *bl++; } break; case 3: while (n1 > 0 && n2 > 0) { if ((*cmp) (*(const void **) b1, *(const void **) b2, arg) <= 0) { *(void **) tmp = *(void **) b1; b1 += sizeof (void *); --n1; } else { *(void **) tmp = *(void **) b2; b2 += sizeof (void *); --n2; } tmp += sizeof (void *); } break; default: while (n1 > 0 && n2 > 0) { if ((*cmp) (b1, b2, arg) <= 0) { tmp = (char *) mempcpy (tmp, b1, s); b1 += s; --n1; } else { tmp = (char *) mempcpy (tmp, b2, s); b2 += s; --n2; } } break; } if (n1 > 0) memcpy (tmp, b1, n1 * s); memcpy (b, p->t, (n - n2) * s); } static void msort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg) { size_t size = n * s; char *tmp = NULL; struct msort_param p; /* For large object sizes use indirect sorting. */ if (s > 32) size = 2 * n * sizeof (void *) + s; if (size < 1024) /* The temporary array is small, so put it on the stack. */ p.t = alloca (size); else { /* It's somewhat large, so malloc it. */ int save = errno; tmp = malloc (size); errno = save; if (tmp == NULL) return; p.t = tmp; } p.s = s; p.var = 4; p.cmp = cmp; p.arg = arg; if (s > 32) { /* Indirect sorting. */ char *ip = (char *) b; void **tp = (void **) (p.t + n * sizeof (void *)); void **t = tp; void *tmp_storage = (void *) (tp + n); while ((void *) t < tmp_storage) { *t++ = ip; ip += s; } p.s = sizeof (void *); p.var = 3; msort_with_tmp (&p, p.t + n * sizeof (void *), n); /* tp[0] .. tp[n - 1] is now sorted, copy around entries of the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */ char *kp; size_t i; for (i = 0, ip = (char *) b; i < n; i++, ip += s) if ((kp = tp[i]) != ip) { size_t j = i; char *jp = ip; memcpy (tmp_storage, ip, s); do { size_t k = (kp - (char *) b) / s; tp[j] = jp; memcpy (jp, kp, s); j = k; jp = kp; kp = tp[k]; } while (kp != ip); tp[j] = jp; memcpy (jp, tmp_storage, s); } } else { if ((s & (sizeof (uint32_t) - 1)) == 0 && ((unsigned long) b) % __alignof__ (uint32_t) == 0) { if (s == sizeof (uint32_t)) p.var = 0; else if (s == sizeof (uint64_t) && ((unsigned long) b) % __alignof__ (uint64_t) == 0) p.var = 1; else if ((s & (sizeof (unsigned long) - 1)) == 0 && ((unsigned long) b) % __alignof__ (unsigned long) == 0) p.var = 2; } msort_with_tmp (&p, b, n); } free (tmp); } /* * glibc apparently doesn't use -Wcast-function-type. * If this is safe for them, it should be for us, too. */ #pragma GCC diagnostic push #if __GNUC__ >= 8 || __clang_major__ >= 19 #pragma GCC diagnostic ignored "-Wcast-function-type" #endif #if __clang_major__ >= 19 #pragma GCC diagnostic ignored "-Wcast-function-type-mismatch" #endif void msort (void *b, size_t n, size_t s, __compar_fn_t cmp) { return msort_r (b, n, s, (__compar_d_fn_t)cmp, NULL); } #pragma GCC diagnostic pop multipath-tools-0.11.1/libmpathutil/msort.h000066400000000000000000000002601475246302400207570ustar00rootroot00000000000000#ifndef MSORT_H_INCLUDED #define MSORT_H_INCLUDED typedef int(*__compar_fn_t)(const void *, const void *); void msort (void *b, size_t n, size_t s, __compar_fn_t cmp); #endif multipath-tools-0.11.1/libmpathutil/parser.c000066400000000000000000000313431475246302400211100ustar00rootroot00000000000000/* * Part: Configuration file parser/reader. Place into the dynamic * data structure representation the conf file * * Version: $Id: parser.c,v 1.0.3 2003/05/11 02:28:03 acassen Exp $ * * Author: Alexandre Cassen, * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include "vector.h" #include "config.h" #include "parser.h" #include "debug.h" #include "strbuf.h" /* local vars */ static int sublevel = 0; static int line_nr; int keyword_alloc(vector keywords, char *string, handler_fn *handler, print_fn *print, int unique) { struct keyword *keyword; keyword = (struct keyword *)calloc(1, sizeof (struct keyword)); if (!keyword) return 1; if (!vector_alloc_slot(keywords)) { free(keyword); return 1; } keyword->string = string; keyword->handler = handler; keyword->print = print; keyword->unique = unique; vector_set_slot(keywords, keyword); return 0; } void install_sublevel(void) { sublevel++; } void install_sublevel_end(void) { sublevel--; } int install_keyword__(vector keywords, char *string, handler_fn *handler, print_fn *print, int unique) { int i = 0; struct keyword *keyword; /* fetch last keyword */ keyword = VECTOR_LAST_SLOT(keywords); if (!keyword) return 1; /* position to last sub level */ for (i = 0; i < sublevel; i++) { keyword = VECTOR_LAST_SLOT(keyword->sub); if (!keyword) return 1; } /* First sub level allocation */ if (!keyword->sub) keyword->sub = vector_alloc(); if (!keyword->sub) return 1; /* add new sub keyword */ return keyword_alloc(keyword->sub, string, handler, print, unique); } void free_keywords(vector keywords) { struct keyword *keyword; int i; if (!keywords) return; for (i = 0; i < VECTOR_SIZE(keywords); i++) { keyword = VECTOR_SLOT(keywords, i); if (keyword->sub) free_keywords(keyword->sub); free(keyword); } vector_free(keywords); } struct keyword * find_keyword(vector keywords, vector v, char * name) { struct keyword *keyword; int i; size_t len; if (!name || !keywords) return NULL; if (!v) v = keywords; len = strlen(name); for (i = 0; i < VECTOR_SIZE(v); i++) { keyword = VECTOR_SLOT(v, i); if ((strlen(keyword->string) == len) && !strcmp(keyword->string, name)) return keyword; if (keyword->sub) { keyword = find_keyword(keywords, keyword->sub, name); if (keyword) return keyword; } } return NULL; } int snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw, const void *data) { int r = 0; char *f; struct config *conf; STRBUF_ON_STACK(sbuf); if (!kw || !kw->print) return 0; do { f = strchr(fmt, '%'); if (f == NULL) { r = append_strbuf_str(&sbuf, fmt); goto out; } if (f != fmt && (r = append_strbuf_str__(&sbuf, fmt, f - fmt)) < 0) goto out; fmt = f + 1; switch(*fmt) { case 'k': if ((r = append_strbuf_str(&sbuf, kw->string)) < 0) goto out; break; case 'v': conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = kw->print(conf, &sbuf, data); pthread_cleanup_pop(1); if (r < 0) goto out; else if (r == 0) {/* no output if no value */ reset_strbuf(&sbuf); goto out; } break; } } while (*fmt++); out: if (r >= 0) r = append_strbuf_str__(buff, get_strbuf_str(&sbuf), get_strbuf_len(&sbuf)); return r; } static const char quote_marker[] = { '\0', '"', '\0' }; bool is_quote(const char* token) { return token[0] == quote_marker[0] && token[1] == quote_marker[1] && token[2] == quote_marker[2]; } vector alloc_strvec(char *string) { char *cp, *start, *token; int strlen; int in_string; vector strvec; if (!string) return NULL; cp = string; /* Skip white spaces */ while ((isspace((int) *cp) || !isascii((int) *cp)) && *cp != '\0') cp++; /* Return if there is only white spaces */ if (*cp == '\0') return NULL; /* Return if string begin with a comment */ if (*cp == '!' || *cp == '#') return NULL; /* Create a vector and alloc each command piece */ strvec = vector_alloc(); if (!strvec) return NULL; in_string = 0; while (1) { int two_quotes = 0; if (!vector_alloc_slot(strvec)) goto out; vector_set_slot(strvec, NULL); start = cp; if (*cp == '"' && !(in_string && *(cp + 1) == '"')) { cp++; token = calloc(1, sizeof(quote_marker)); if (!token) goto out; memcpy(token, quote_marker, sizeof(quote_marker)); if (in_string) in_string = 0; else in_string = 1; } else if (!in_string && (*cp == '{' || *cp == '}')) { token = malloc(2); if (!token) goto out; *(token) = *cp; *(token + 1) = '\0'; cp++; } else { move_on: while ((in_string || (!isspace((int) *cp) && isascii((int) *cp) && *cp != '!' && *cp != '#' && *cp != '{' && *cp != '}')) && *cp != '\0' && *cp != '"') cp++; /* Two consecutive double quotes - don't end string */ if (in_string && *cp == '"') { if (*(cp + 1) == '"') { two_quotes = 1; cp += 2; goto move_on; } } strlen = cp - start; token = calloc(1, strlen + 1); if (!token) goto out; memcpy(token, start, strlen); *(token + strlen) = '\0'; /* Replace "" by " */ if (two_quotes) { char *qq = strstr(token, "\"\""); while (qq != NULL) { memmove(qq + 1, qq + 2, strlen + 1 - (qq + 2 - token)); qq = strstr(qq + 1, "\"\""); } } } vector_set_slot(strvec, token); while ((!in_string && (isspace((int) *cp) || !isascii((int) *cp))) && *cp != '\0') cp++; if (*cp == '\0' || (!in_string && (*cp == '!' || *cp == '#'))) { return strvec; } } out: vector_free(strvec); return NULL; } static int read_line(FILE *stream, char *buf, int size) { char *p; if (fgets(buf, size, stream) == NULL) return 0; strtok_r(buf, "\n\r", &p); return 1; } void * set_value(vector strvec) { char *str = VECTOR_SLOT(strvec, 1); char *alloc = NULL; if (!str) { condlog(0, "option '%s' missing value", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } if (is_quote(str)) { if (VECTOR_SIZE(strvec) > 2) { str = VECTOR_SLOT(strvec, 2); if (!str) { condlog(0, "parse error for option '%s'", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } } /* Even empty quotes counts as a value (An empty string) */ if (is_quote(str)) { alloc = (char *)calloc(1, sizeof (char)); if (!alloc) goto oom; return alloc; } } alloc = strdup(str); if (alloc) return alloc; oom: condlog(0, "can't allocate memory for option '%s'", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } /* non-recursive configuration stream handler */ static int kw_level = 0; int warn_on_duplicates(vector uniques, char *str, const char *file) { char *tmp; int i; vector_foreach_slot(uniques, tmp, i) { if (!strcmp(str, tmp)) { condlog(1, "%s line %d, duplicate keyword: %s", file, line_nr, str); return 0; } } tmp = strdup(str); if (!tmp) return 1; if (!vector_alloc_slot(uniques)) { free(tmp); return 1; } vector_set_slot(uniques, tmp); return 0; } void free_uniques(vector uniques) { char *tmp; int i; vector_foreach_slot(uniques, tmp, i) free(tmp); vector_free(uniques); } int is_sublevel_keyword(char *str) { return (strcmp(str, "defaults") == 0 || strcmp(str, "blacklist") == 0 || strcmp(str, "blacklist_exceptions") == 0 || strcmp(str, "devices") == 0 || strcmp(str, "device") == 0 || strcmp(str, "multipaths") == 0 || strcmp(str, "multipath") == 0); } int validate_config_strvec(vector strvec, const char *file) { char *str = NULL; if (strvec && VECTOR_SIZE(strvec) > 0) str = VECTOR_SLOT(strvec, 0); if (str == NULL) { condlog(0, "can't parse option on line %d of %s", line_nr, file); return -1; } if (*str == '}') { if (VECTOR_SIZE(strvec) > 1) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 1), line_nr, file); return 0; } if (*str == '{') { condlog(0, "invalid keyword '%s' on line %d of %s", str, line_nr, file); return -1; } if (is_sublevel_keyword(str)) { str = VECTOR_SIZE(strvec) > 1 ? VECTOR_SLOT(strvec, 1) : NULL; if (str == NULL) condlog(0, "missing '{' on line %d of %s", line_nr, file); else if (*str != '{') condlog(0, "expecting '{' on line %d of %s. found '%s'", line_nr, file, str); else if (VECTOR_SIZE(strvec) > 2) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file); return 0; } str = VECTOR_SIZE(strvec) > 1 ? VECTOR_SLOT(strvec, 1) : NULL; if (str == NULL) { condlog(0, "missing value for option '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 0), line_nr, file); return -1; } if (!is_quote(str)) { if (VECTOR_SIZE(strvec) > 2) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file); return 0; } if (VECTOR_SIZE(strvec) == 2) { condlog(0, "missing closing quotes on line %d of %s", line_nr, file); return 0; } str = VECTOR_SLOT(strvec, 2); if (str == NULL) { condlog(0, "can't parse value on line %d of %s", line_nr, file); return -1; } if (is_quote(str)) { if (VECTOR_SIZE(strvec) > 3) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 3), line_nr, file); return 0; } if (VECTOR_SIZE(strvec) == 3) { condlog(0, "missing closing quotes on line %d of %s", line_nr, file); return 0; } str = VECTOR_SLOT(strvec, 3); if (str == NULL) { condlog(0, "can't parse value on line %d of %s", line_nr, file); return -1; } if (!is_quote(str)) { /* There should only ever be one token between quotes */ condlog(0, "parsing error starting with '%s' on line %d of %s", str, line_nr, file); return -1; } if (VECTOR_SIZE(strvec) > 4) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 4), line_nr, file); return 0; } static int process_stream(struct config *conf, FILE *stream, vector keywords, const char *section, const char *file) { int i; int r = 0, t; struct keyword *keyword; char *str; char *buf; vector strvec; vector uniques; uniques = vector_alloc(); if (!uniques) return 1; buf = calloc(1, MAXBUF); if (!buf) { vector_free(uniques); return 1; } while (read_line(stream, buf, MAXBUF)) { line_nr++; strvec = alloc_strvec(buf); if (!strvec) continue; if (validate_config_strvec(strvec, file) != 0) { free_strvec(strvec); continue; } str = VECTOR_SLOT(strvec, 0); if (!strcmp(str, EOB)) { if (kw_level > 0) { free_strvec(strvec); goto out; } condlog(0, "unmatched '%s' at line %d of %s", EOB, line_nr, file); } for (i = 0; i < VECTOR_SIZE(keywords); i++) { keyword = VECTOR_SLOT(keywords, i); if (!strcmp(keyword->string, str)) { if (keyword->unique && warn_on_duplicates(uniques, str, file)) { r = 1; free_strvec(strvec); goto out; } if (keyword->handler) { t = keyword->handler(conf, strvec, file, line_nr); r += t; if (t) condlog(1, "%s line %d, parsing failed: %s", file, line_nr, buf); } if (keyword->sub) { kw_level++; r += process_stream(conf, stream, keyword->sub, keyword->string, file); kw_level--; } break; } } if (i >= VECTOR_SIZE(keywords)) { if (section) condlog(1, "%s line %d, invalid keyword in the %s section: %s", file, line_nr, section, str); else condlog(1, "%s line %d, invalid keyword: %s", file, line_nr, str); } free_strvec(strvec); } if (kw_level == 1) condlog(1, "missing '%s' at end of %s", EOB, file); out: free(buf); free_uniques(uniques); return r; } /* Data initialization */ int process_file(struct config *conf, const char *file) { int r; FILE *stream; if (!conf->keywords) { condlog(0, "No keywords allocated"); return 1; } stream = fopen(file, "r"); if (!stream) { condlog(0, "couldn't open configuration file '%s': %s", file, strerror(errno)); return 1; } /* Stream handling */ line_nr = 0; r = process_stream(conf, stream, conf->keywords, NULL, file); fclose(stream); //free_keywords(keywords); return r; } multipath-tools-0.11.1/libmpathutil/parser.h000066400000000000000000000054371475246302400211220ustar00rootroot00000000000000/* * Soft: Keepalived is a failover program for the LVS project * . It monitor & manipulate * a loadbalanced server pool using multi-layer checks. * * Part: cfreader.c include file. * * Version: $Id: parser.h,v 1.0.3 2003/05/11 02:28:03 acassen Exp $ * * Author: Alexandre Cassen, * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #ifndef PARSER_H_INCLUDED #define PARSER_H_INCLUDED /* system includes */ #include #include #include #include #include #include /* local includes */ #include "vector.h" struct strbuf; struct config; /* Global definitions */ #define EOB "}" #define MAXBUF 1024 /* keyword definition */ typedef int print_fn(struct config *, struct strbuf *, const void *); typedef int handler_fn(struct config *, vector, const char *file, int line_nr); struct keyword { char *string; handler_fn *handler; print_fn *print; vector sub; int unique; }; /* Reloading helpers */ #define SET_RELOAD (reload = 1) #define UNSET_RELOAD (reload = 0) #define RELOAD_DELAY 5 /* iterator helper */ #define iterate_sub_keywords(k,p,i) \ for (i = 0; i < (k)->sub->allocated && ((p) = (k)->sub->slot[i]); i++) /* Prototypes */ int keyword_alloc(vector keywords, char *string, handler_fn *handler, print_fn *print, int unique); #define install_keyword_root(str, h) keyword_alloc(keywords, str, h, NULL, 1) void install_sublevel(void); void install_sublevel_end(void); int install_keyword__(vector keywords, char *string, handler_fn *handler, print_fn *print, int unique); #define install_keyword(str, vec, pri) install_keyword__(keywords, str, vec, pri, 1) #define install_keyword_multi(str, vec, pri) install_keyword__(keywords, str, vec, pri, 0) void dump_keywords(vector keydump, int level); void free_keywords(vector keywords); vector alloc_strvec(char *string); void *set_value(vector strvec); int process_file(struct config *conf, const char *conf_file); struct keyword * find_keyword(vector keywords, vector v, char * name); int snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw, const void *data); bool is_quote(const char* token); #endif multipath-tools-0.11.1/libmpathutil/strbuf.c000066400000000000000000000077061475246302400211270ustar00rootroot00000000000000/* * Copyright (c) 2021 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include "strbuf.h" static const char empty_str[] = ""; char *get_strbuf_buf__(struct strbuf *buf) { return buf->buf; } const char *get_strbuf_str(const struct strbuf *buf) { return buf->buf ? buf->buf : empty_str; } char *steal_strbuf_str(struct strbuf *buf) { char *p = buf->buf; buf->buf = NULL; buf->size = buf->offs = 0; return p; } size_t get_strbuf_len(const struct strbuf *buf) { return buf->offs; } static bool strbuf_is_sane(const struct strbuf *buf) { return buf && ((!buf->buf && !buf->size && !buf->offs) || (buf->buf && buf->size && buf->size > buf->offs)); } void reset_strbuf(struct strbuf *buf) { free(buf->buf); buf->buf = NULL; buf->size = buf->offs = 0; } void free_strbuf(struct strbuf *buf) { if (!buf) return; reset_strbuf(buf); free(buf); } struct strbuf *new_strbuf(void) { return calloc(1, sizeof(struct strbuf)); } int truncate_strbuf(struct strbuf *buf, size_t offs) { if (!buf->buf) return -EFAULT; if (offs > buf->offs) return -ERANGE; buf->offs = offs; buf->buf[offs] = '\0'; return 0; } #define BUF_CHUNK 64 static int expand_strbuf(struct strbuf *buf, int addsz) { size_t add; char *tmp; assert(strbuf_is_sane(buf)); if (addsz < 0) return -EINVAL; if (buf->size - buf->offs >= (size_t)addsz + 1) return 0; add = ((addsz - (buf->size - buf->offs)) / BUF_CHUNK + 1) * BUF_CHUNK; if (buf->size >= SIZE_MAX - add) { add = SIZE_MAX - buf->size; if (add < (size_t)addsz + 1) return -EOVERFLOW; } tmp = realloc(buf->buf, buf->size + add); if (!tmp) return -ENOMEM; buf->buf = tmp; buf->size += add; buf->buf[buf->offs] = '\0'; return 0; } int append_strbuf_str__(struct strbuf *buf, const char *str, int slen) { int ret; if ((ret = expand_strbuf(buf, slen)) < 0) return ret; memcpy(buf->buf + buf->offs, str, slen); buf->offs += slen; buf->buf[buf->offs] = '\0'; return slen; } int append_strbuf_str(struct strbuf *buf, const char *str) { size_t slen; if (!str) return -EINVAL; slen = strlen(str); if (slen > INT_MAX) return -ERANGE; return append_strbuf_str__(buf, str, slen); } int fill_strbuf(struct strbuf *buf, char c, int slen) { int ret; if ((ret = expand_strbuf(buf, slen)) < 0) return ret; memset(buf->buf + buf->offs, c, slen); buf->offs += slen; buf->buf[buf->offs] = '\0'; return slen; } int append_strbuf_quoted(struct strbuf *buff, const char *ptr) { char *quoted, *q; const char *p; unsigned n_quotes, i; size_t qlen; int ret; if (!ptr) return -EINVAL; for (n_quotes = 0, p = strchr(ptr, '"'); p; p = strchr(++p, '"')) n_quotes++; /* leading + trailing quote, 1 extra quote for every quote in ptr */ qlen = strlen(ptr) + 2 + n_quotes; if (qlen > INT_MAX) return -ERANGE; if ((ret = expand_strbuf(buff, qlen)) < 0) return ret; quoted = &(buff->buf[buff->offs]); *quoted++ = '"'; for (p = ptr, q = quoted, i = 0; i < n_quotes; i++) { char *q1 = memccpy(q, p, '"', qlen - 2 - (q - quoted)); assert(q1 != NULL); p += q1 - q; *q1++ = '"'; q = q1; } q = mempcpy(q, p, qlen - 2 - (q - quoted)); *q++ = '"'; *q = '\0'; ret = q - &(buff->buf[buff->offs]); buff->offs += ret; return ret; } __attribute__((format(printf, 2, 3))) int print_strbuf(struct strbuf *buf, const char *fmt, ...) { va_list ap; int ret; size_t space = buf->size - buf->offs; va_start(ap, fmt); ret = vsnprintf(buf->buf + buf->offs, space, fmt, ap); va_end(ap); if (ret < 0) return ret; else if ((size_t)ret < space) { buf->offs += ret; return ret; } ret = expand_strbuf(buf, ret); if (ret < 0) return ret; space = buf->size - buf->offs; va_start(ap, fmt); ret = vsnprintf(buf->buf + buf->offs, space, fmt, ap); va_end(ap); if (ret >= 0) buf->offs += ret; return ret; } multipath-tools-0.11.1/libmpathutil/strbuf.h000066400000000000000000000141711475246302400211260ustar00rootroot00000000000000/* * Copyright (c) 2021 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef STRBUF_H_INCLUDED #define STRBUF_H_INCLUDED #include #include struct strbuf { char *buf; size_t size; size_t offs; }; /** * reset_strbuf(): prepare strbuf for new content * @param strbuf: string buffer to reset * * Frees internal buffer and resets size and offset to 0. * Can be used to cleanup a struct strbuf on stack. */ void reset_strbuf(struct strbuf *buf); /** * free_strbuf(): free resources * @param strbuf: string buffer to discard * * Frees all memory occupied by a struct strbuf. */ void free_strbuf(struct strbuf *buf); /** * macro: STRBUF_INIT * * Use this to initialize a local struct strbuf on the stack, * or in a global/static variable. */ #define STRBUF_INIT { .buf = NULL, } /** * macro: STRBUF_ON_STACK * * Define and initialize a local struct @strbuf to be cleaned up when * the current scope is left */ #define STRBUF_ON_STACK(__x) \ struct strbuf __attribute__((cleanup(reset_strbuf))) (__x) = STRBUF_INIT; /** * new_strbuf(): allocate a struct strbuf on the heap * * @returns: pointer to allocated struct, or NULL in case of error. */ struct strbuf *new_strbuf(void); /** * get_strbuf_buf(): retrieve a pointer to the strbuf's buffer * @param buf: a struct strbuf * @returns: pointer to the string written to the strbuf so far. * * INTERNAL ONLY. * DANGEROUS: Unlike the return value of get_strbuf_str(), * this string can be written to, modifying the strbuf's content. * USE WITH CAUTION. * If @strbuf was never written to, the function returns NULL. * The return value of this function must not be free()d. */ char *get_strbuf_buf__(struct strbuf *buf); /** * get_strbuf_str(): retrieve string from strbuf * @param buf: a struct strbuf * @returns: pointer to the string written to the strbuf so far. * * If @strbuf was never written to, the function returns a zero- * length string. The return value of this function must not be * free()d. */ const char *get_strbuf_str(const struct strbuf *buf); /** * steal_strbuf_str(): retrieve string from strbuf and reset * @param buf: a struct strbuf * @returns: pointer to the string written to @strbuf, or NULL * * After calling this function, the @strbuf is empty as if freshly * initialized. The caller is responsible to free() the returned pointer. * If @strbuf was never written to (not even an empty string was appended), * the function returns NULL. */ char *steal_strbuf_str(struct strbuf *buf); /** * get_strbuf_len(): retrieve string length from strbuf * @param buf: a struct strbuf * @returns: the length of the string written to @strbuf so far. */ size_t get_strbuf_len(const struct strbuf *buf); /** * truncate_strbuf(): shorten the buffer * @param buf: struct strbuf to truncate * @param offs: new buffer position / offset * @returns: 0 on success, negative error code otherwise. * * If @strbuf is freshly allocated/reset (never written to), -EFAULT * is returned. if @offs must be higher than the current offset as returned * by get_strbuf_len(), -ERANGE is returned. The allocated size of the @strbuf * remains unchanged. */ int truncate_strbuf(struct strbuf *buf, size_t offs); /** * append_strbuf_str__(): append string of known length * @param buf: the struct strbuf to write to * @param str: the string to append, not necessarily 0-terminated * @param slen: max number of characters to append, must be non-negative * @returns: @slen = number of appended characters if successful (excluding * terminating '\0'); negative error code otherwise. * * Notes: a 0-byte is always appended to the output buffer after @slen characters. * 0-bytes possibly contained in the first @slen characters are copied into * the output. If the function returns an error, @strbuf is unchanged. */ int append_strbuf_str__(struct strbuf *buf, const char *str, int slen); /** * append_strbuf_str(): append string * @param buf: the struct strbuf to write to * @param str: the string to append, 0-terminated * @returns: number of appended characters if successful (excluding * terminating '\0'); negative error code otherwise * * Appends the given 0-terminated string to @strbuf, expanding @strbuf's size * as necessary. If the function returns an error, @strbuf is unchanged. */ int append_strbuf_str(struct strbuf *buf, const char *str); /** * fill_strbuf_str(): pad strbuf with a character * @param buf: the struct strbuf to write to * @param c: the character used for filling * @param slen: max number of characters to append, must be non-negative * @returns: number of appended characters if successful (excluding * terminating '\0'); negative error code otherwise * * Appends the given character @slen times to @strbuf, expanding @strbuf's size * as necessary. If the function returns an error, @strbuf is unchanged. */ int fill_strbuf(struct strbuf *buf, char c, int slen); /** * append_strbuf_quoted(): append string in double quotes, escaping quotes in string * @param buf: the struct strbuf to write to * @param str: the string to append, 0-terminated * @returns: number of appended characters if successful (excluding * terminating '\0'); negative error code otherwise * * Appends the given string to @strbuf, with leading and trailing double * quotes (") added, expanding @strbuf's size as necessary. Any double quote * characters (") in the string are transformed to a pair of double quotes (""). * If the function returns an error, @strbuf is unchanged. */ int append_strbuf_quoted(struct strbuf *buf, const char *str); /** * print_strbuf(): print to strbuf, formatted * @param buf: the struct strbuf to print to * @param fmt: printf()-like format string * @returns: number of appended characters if successful, (excluding * terminating '\0'); negative error code otherwise * * Appends the arguments following @fmt, formatted as in printf(), to * @strbuf, expanding @strbuf's size as necessary. The function makes sure that * the output @strbuf is always 0-terminated. * If the function returns an error, @strbuf is unchanged. */ __attribute__((format(printf, 2, 3))) int print_strbuf(struct strbuf *buf, const char *fmt, ...); #endif multipath-tools-0.11.1/libmpathutil/time-util.c000066400000000000000000000026401475246302400215230ustar00rootroot00000000000000#include #include #include #include "time-util.h" void get_monotonic_time(struct timespec *res) { struct timespec ts; int rv = clock_gettime(CLOCK_MONOTONIC, &ts); assert(rv == 0); *res = ts; } /* Initialize @cond as a condition variable that uses the monotonic clock */ void pthread_cond_init_mono(pthread_cond_t *cond) { pthread_condattr_t attr; int res; res = pthread_condattr_init(&attr); assert(res == 0); res = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); assert(res == 0); res = pthread_cond_init(cond, &attr); assert(res == 0); res = pthread_condattr_destroy(&attr); assert(res == 0); } /* Ensure that 0 <= ts->tv_nsec && ts->tv_nsec < 1000 * 1000 * 1000. */ void normalize_timespec(struct timespec *ts) { while (ts->tv_nsec < 0) { ts->tv_nsec += 1000L * 1000 * 1000; ts->tv_sec--; } while (ts->tv_nsec >= 1000L * 1000 * 1000) { ts->tv_nsec -= 1000L * 1000 * 1000; ts->tv_sec++; } } /* Compute *res = *a - *b */ void timespecsub(const struct timespec *a, const struct timespec *b, struct timespec *res) { res->tv_sec = a->tv_sec - b->tv_sec; res->tv_nsec = a->tv_nsec - b->tv_nsec; normalize_timespec(res); } int timespeccmp(const struct timespec *a, const struct timespec *b) { struct timespec tmp; timespecsub(a, b, &tmp); if (tmp.tv_sec > 0) return 1; if (tmp.tv_sec < 0) return -1; return tmp.tv_nsec > 0 ? 1 : (tmp.tv_nsec < 0 ? -1 : 0); } multipath-tools-0.11.1/libmpathutil/time-util.h000066400000000000000000000006721475246302400215330ustar00rootroot00000000000000#ifndef TIME_UTIL_H_INCLUDED #define TIME_UTIL_H_INCLUDED #include struct timespec; void get_monotonic_time(struct timespec *res); void pthread_cond_init_mono(pthread_cond_t *cond); void normalize_timespec(struct timespec *ts); void timespecsub(const struct timespec *a, const struct timespec *b, struct timespec *res); int timespeccmp(const struct timespec *a, const struct timespec *b); #endif /* TIME_UTIL_H_INCLUDED */ multipath-tools-0.11.1/libmpathutil/util.c000066400000000000000000000150771475246302400205770ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "debug.h" #include "checkers.h" #include "vector.h" #include "structs.h" #include "config.h" #include "log.h" size_t strchop(char *str) { size_t i; for (i = strlen(str); i != 0 && isspace(str[i - 1]); i--) ; str[i] = '\0'; return i; } /* * glibc's non-destructive version of basename() * License: LGPL-2.1-or-later */ const char *libmp_basename(const char *filename) { char *p = strrchr(filename, '/'); return p ? p + 1 : filename; } int basenamecpy (const char *src, char *dst, size_t size) { const char *p, *e; if (!src || !dst || !strlen(src)) return 0; p = basename(src); for (e = p + strlen(p) - 1; e >= p && isspace(*e); --e) ; if (e < p || (size_t)(e - p) > size - 2) return 0; strlcpy(dst, p, e - p + 2); return strlen(dst); } int filepresent (const char *run) { struct stat buf; if(!stat(run, &buf)) return 1; return 0; } char *get_next_string(char **temp, const char *split_char) { char *token = NULL; token = strsep(temp, split_char); while (token != NULL && !strcmp(token, "")) token = strsep(temp, split_char); return token; } int get_word (const char *sentence, char **word) { const char *p; int len; int skip = 0; if (word) *word = NULL; while (*sentence == ' ') { sentence++; skip++; } if (*sentence == '\0') return 0; p = sentence; while (*p != ' ' && *p != '\0') p++; len = (int) (p - sentence); if (!word) return skip + len; *word = calloc(1, len + 1); if (!*word) { condlog(0, "get_word : oom"); return 0; } strncpy(*word, sentence, len); strchop(*word); condlog(5, "*word = %s, len = %i", *word, len); if (*p == '\0') return 0; return skip + len; } size_t libmp_strlcpy(char * restrict dst, const char * restrict src, size_t size) { size_t bytes = 0; char ch; while ((ch = *src++)) { if (bytes + 1 < size) *dst++ = ch; bytes++; } /* If size == 0 there is no space for a final null... */ if (size) *dst = '\0'; return bytes; } size_t libmp_strlcat(char * restrict dst, const char * restrict src, size_t size) { size_t bytes = 0; char ch; while (bytes < size && *dst) { dst++; bytes++; } if (bytes == size) return (bytes + strlen(src)); while ((ch = *src++)) { if (bytes + 1 < size) *dst++ = ch; bytes++; } *dst = '\0'; return bytes; } /* This function returns a pointer inside of the supplied pathname string. * If is_path_device is true, it may also modify the supplied string */ char *convert_dev(char *name, int is_path_device) { char *ptr; if (!name) return NULL; if (is_path_device) { ptr = strstr(name, "cciss/"); if (ptr) { ptr += 5; *ptr = '!'; } } if (!strncmp(name, "/dev/", 5) && strlen(name) > 5) ptr = name + 5; else ptr = name; return ptr; } dev_t parse_devt(const char *dev_t) { int maj, min; if (sscanf(dev_t,"%d:%d", &maj, &min) != 2) return 0; return makedev(maj, min); } void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached) { int ret; ret = pthread_attr_init(attr); assert(ret == 0); if (PTHREAD_STACK_MIN > 0 && stacksize < (size_t)PTHREAD_STACK_MIN) stacksize = (size_t)PTHREAD_STACK_MIN; ret = pthread_attr_setstacksize(attr, stacksize); assert(ret == 0); if (detached) { ret = pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); assert(ret == 0); } } static int _linux_version_code; static pthread_once_t _lvc_initialized = PTHREAD_ONCE_INIT; /* Returns current kernel version encoded as major*65536 + minor*256 + patch, * so, for example, to check if the kernel is greater than 2.2.11: * * if (get_linux_version_code() > KERNEL_VERSION(2,2,11)) { } * * Copyright (C) 1999-2004 by Erik Andersen * Code copied from busybox (GPLv2 or later) */ static void _set_linux_version_code(void) { struct utsname name; char *t; int i, r; uname(&name); /* never fails */ t = name.release; r = 0; for (i = 0; i < 3; i++) { t = strtok(t, "."); r = r * 256 + (t ? atoi(t) : 0); t = NULL; } _linux_version_code = r; } int get_linux_version_code(void) { pthread_once(&_lvc_initialized, _set_linux_version_code); return _linux_version_code; } int safe_write(int fd, const void *buf, size_t count) { while (count > 0) { ssize_t r = write(fd, buf, count); if (r < 0) { if (errno == EINTR) continue; return -errno; } count -= r; buf = (const char *)buf + r; } return 0; } void set_max_fds(rlim_t max_fds) { struct rlimit fd_limit; if (!max_fds) return; if (getrlimit(RLIMIT_NOFILE, &fd_limit) < 0) { condlog(0, "can't get open fds limit: %s", strerror(errno)); fd_limit.rlim_cur = 0; fd_limit.rlim_max = 0; } if (fd_limit.rlim_cur < max_fds) { fd_limit.rlim_cur = max_fds; if (fd_limit.rlim_max < max_fds) fd_limit.rlim_max = max_fds; if (setrlimit(RLIMIT_NOFILE, &fd_limit) < 0) { condlog(0, "can't set open fds limit to " "%lu/%lu : %s", (unsigned long)fd_limit.rlim_cur, (unsigned long)fd_limit.rlim_max, strerror(errno)); } else { condlog(3, "set open fds limit to %lu/%lu", (unsigned long)fd_limit.rlim_cur, (unsigned long)fd_limit.rlim_max); } } } void free_scandir_result(struct scandir_result *res) { int i; for (i = 0; i < res->n; i++) free(res->di[i]); free(res->di); } void cleanup_free_ptr(void *arg) { void **p = arg; if (p && *p) { free(*p); *p = NULL; } } void cleanup_fd_ptr(void *arg) { int *fd = arg; if (*fd >= 0) { close(*fd); *fd = -1; } } void cleanup_mutex(void *arg) { pthread_mutex_unlock(arg); } void cleanup_vector_free(void *arg) { if (arg) vector_free((vector)arg); } void cleanup_fclose(void *p) { if (p) fclose(p); } struct bitfield *alloc_bitfield(unsigned int maxbit) { unsigned int n; struct bitfield *bf; if (maxbit == 0) { errno = EINVAL; return NULL; } n = (maxbit - 1) / bits_per_slot + 1; bf = calloc(1, sizeof(struct bitfield) + n * sizeof(bitfield_t)); if (bf) bf->len = maxbit; return bf; } void log_bitfield_overflow__(const char *f, unsigned int bit, unsigned int len) { condlog(0, "%s: bitfield overflow: %u >= %u", f, bit, len); } int should_exit(void) { return 0; } void cleanup_charp(char **p) { free(*p); } void cleanup_ucharp(unsigned char **p) { free(*p); } void cleanup_udev_device(struct udev_device **udd) { if (*udd) udev_device_unref(*udd); } multipath-tools-0.11.1/libmpathutil/util.h000066400000000000000000000103231475246302400205710ustar00rootroot00000000000000#ifndef UTIL_H_INCLUDED #define UTIL_H_INCLUDED #include #include #include #include /* for rlim_t */ #include #include #include #include #include #ifndef __GLIBC_PREREQ #define __GLIBC_PREREQ(x, y) 0 #endif size_t strchop(char *); const char *libmp_basename(const char *filename); #ifndef __GLIBC__ #define basename(x) libmp_basename(x) #endif int basenamecpy (const char *src, char *dst, size_t size); int filepresent (const char *run); char *get_next_string(char **temp, const char *split_char); int get_word (const char * sentence, char ** word); size_t libmp_strlcpy(char * restrict dst, const char * restrict src, size_t size); size_t libmp_strlcat(char * restrict dst, const char * restrict src, size_t size); #if defined(__GLIBC__) && ! (__GLIBC_PREREQ(2, 38)) #define strlcpy(dst, src, size) libmp_strlcpy(dst, src, size) #define strlcat(dst, src, size) libmp_strlcat(dst, src, size) #endif dev_t parse_devt(const char *dev_t); char *convert_dev(char *dev, int is_path_device); void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached); int get_linux_version_code(void); int safe_write(int fd, const void *buf, size_t count); void set_max_fds(rlim_t max_fds); int should_exit(void); #define KERNEL_VERSION(maj, min, ptc) ((((maj) * 256) + (min)) * 256 + (ptc)) #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) #define safe_sprintf(var, format, args...) \ safe_snprintf(var, sizeof(var), format, ##args) #define safe_snprintf(var, size, format, args...) \ ({ \ size_t __size = size; \ int __ret; \ \ __ret = snprintf(var, __size, format, ##args); \ __ret < 0 || (size_t)__ret >= __size; \ }) #define pthread_cleanup_push_cast(f, arg) \ pthread_cleanup_push(((void (*)(void *))&f), (arg)) void cleanup_fd_ptr(void *arg); void cleanup_free_ptr(void *arg); void cleanup_mutex(void *arg); void cleanup_vector_free(void *arg); void cleanup_fclose(void *p); struct scandir_result { struct dirent **di; int n; }; void free_scandir_result(struct scandir_result *); /* * ffsll() is also available on glibc < 2.27 if _GNU_SOURCE is defined. * But relying on that would require that every program using this header file * set _GNU_SOURCE during compilation, because otherwise the library and the * program would use different types for bitfield_t, causing errors. * That's too error prone, so if in doubt, use ffs(). */ #if __GLIBC_PREREQ(2, 27) typedef unsigned long long int bitfield_t; #define _ffs(x) ffsll(x) #else typedef unsigned int bitfield_t; #define _ffs(x) ffs(x) #endif #define bits_per_slot (sizeof(bitfield_t) * CHAR_BIT) struct bitfield { unsigned int len; bitfield_t bits[]; }; #define STATIC_BITFIELD(name, length) \ static struct { \ unsigned int len; \ bitfield_t bits[((length) - 1) / bits_per_slot + 1]; \ } __static__ ## name = { \ .len = (length), \ .bits = { 0, }, \ }; \ struct bitfield *name = (struct bitfield *)& __static__ ## name struct bitfield *alloc_bitfield(unsigned int maxbit); void log_bitfield_overflow__(const char *f, unsigned int bit, unsigned int len); #define log_bitfield_overflow(bit, len) \ log_bitfield_overflow__(__func__, bit, len) static inline bool is_bit_set_in_bitfield(unsigned int bit, const struct bitfield *bf) { if (bit >= bf->len) { log_bitfield_overflow(bit, bf->len); return false; } return !!(bf->bits[bit / bits_per_slot] & (1ULL << (bit % bits_per_slot))); } static inline void set_bit_in_bitfield(unsigned int bit, struct bitfield *bf) { if (bit >= bf->len) { log_bitfield_overflow(bit, bf->len); return; } bf->bits[bit / bits_per_slot] |= (1ULL << (bit % bits_per_slot)); } static inline void clear_bit_in_bitfield(unsigned int bit, struct bitfield *bf) { if (bit >= bf->len) { log_bitfield_overflow(bit, bf->len); return; } bf->bits[bit / bits_per_slot] &= ~(1ULL << (bit % bits_per_slot)); } #define steal_ptr(x) \ ({ \ void *___p = x; \ x = NULL; \ ___p; \ }) void cleanup_charp(char **p); void cleanup_ucharp(unsigned char **p); void cleanup_udev_device(struct udev_device **udd); #endif /* UTIL_H_INCLUDED */ multipath-tools-0.11.1/libmpathutil/uxsock.c000066400000000000000000000053361475246302400211330ustar00rootroot00000000000000/* * Original author : tridge@samba.org, January 2002 * * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Alasdair Kergon, Redhat */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_SYSTEMD #include #endif #include "mpath_cmd.h" #include "uxsock.h" #include "debug.h" /* * Code is similar with mpath_recv_reply() with data size limitation * and debug-able malloc. * When limit == 0, it means no limit on data size, used for socket client * to receiving data from multipathd. */ static int _recv_packet(int fd, char **buf, unsigned int timeout, ssize_t limit); /* * create a unix domain socket and start listening on it * return a file descriptor open on the socket */ int ux_socket_listen(const char *name) { int fd; size_t len; #ifdef USE_SYSTEMD int num; #endif struct sockaddr_un addr; #ifdef USE_SYSTEMD num = sd_listen_fds(0); if (num > 1) { condlog(3, "sd_listen_fds returned %d fds", num); return -1; } else if (num == 1) { fd = SD_LISTEN_FDS_START + 0; condlog(3, "using fd %d from sd_listen_fds", fd); return fd; } #endif fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd == -1) { condlog(3, "Couldn't create ux_socket, error %d", errno); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; addr.sun_path[0] = '\0'; len = strlen(name) + 1; if (len >= sizeof(addr.sun_path)) len = sizeof(addr.sun_path) - 1; memcpy(&addr.sun_path[1], name, len); len += sizeof(sa_family_t); if (bind(fd, (struct sockaddr *)&addr, len) == -1) { condlog(3, "Couldn't bind to ux_socket, error %d", errno); close(fd); return -1; } if (listen(fd, 10) == -1) { condlog(3, "Couldn't listen to ux_socket, error %d", errno); close(fd); return -1; } return fd; } /* * send a packet in length prefix format */ int send_packet(int fd, const char *buf) { if (mpath_send_cmd(fd, buf) < 0) return -errno; return 0; } static int _recv_packet(int fd, char **buf, unsigned int timeout, ssize_t limit) { int err = 0; ssize_t len = 0; *buf = NULL; len = mpath_recv_reply_len(fd, timeout); if (len == 0) return len; if (len < 0) return -errno; if ((limit > 0) && (len > limit)) return -EINVAL; (*buf) = calloc(1, len); if (!*buf) return -ENOMEM; err = mpath_recv_reply_data(fd, *buf, len, timeout); if (err != 0) { free(*buf); (*buf) = NULL; return -errno; } return err; } /* * receive a packet in length prefix format */ int recv_packet(int fd, char **buf, unsigned int timeout) { return _recv_packet(fd, buf, timeout, 0 /* no limit */); } multipath-tools-0.11.1/libmpathutil/uxsock.h000066400000000000000000000003711475246302400211320ustar00rootroot00000000000000/* some prototypes */ #ifndef UXSOCK_H_INCLUDED #define UXSOCK_H_INCLUDED int ux_socket_listen(const char *name); int send_packet(int fd, const char *buf); int recv_packet(int fd, char **buf, unsigned int timeout); #define MAX_CMD_LEN 512 #endif multipath-tools-0.11.1/libmpathutil/vector.c000066400000000000000000000076351475246302400211250ustar00rootroot00000000000000/* * Part: Vector structure manipulation. * * Version: $Id: vector.c,v 1.0.3 2003/05/11 02:28:03 acassen Exp $ * * Author: Alexandre Cassen, * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Copyright (c) 2002, 2003, 2004 Alexandre Cassen * Copyright (c) 2005 Christophe Varoqui */ #include #include "vector.h" #include "msort.h" /* * Initialize vector struct. * allocated 'size' slot elements then return vector. */ vector vector_alloc(void) { vector v = (vector) calloc(1, sizeof (struct vector_s)); return v; } /* allocated one slot */ bool vector_alloc_slot(vector v) { void *new_slot = NULL; int new_allocated; int i; if (!v) return false; new_allocated = v->allocated + VECTOR_DEFAULT_SIZE; new_slot = realloc(v->slot, sizeof (void *) * new_allocated); if (!new_slot) return false; v->slot = new_slot; for (i = v->allocated; i < new_allocated; i++) v->slot[i] = NULL; v->allocated = new_allocated; return true; } int vector_move_up(vector v, int src, int dest) { void *value; int i; if (dest == src) return 0; if (dest > src || src >= v->allocated) return -1; value = v->slot[src]; for (i = src - 1; i >= dest; i--) v->slot[i + 1] = v->slot[i]; v->slot[dest] = value; return 0; } void * vector_insert_slot(vector v, int slot, void *value) { int i; if (!vector_alloc_slot(v)) return NULL; for (i = VECTOR_SIZE(v) - 2; i >= slot; i--) v->slot[i + 1] = v->slot[i]; v->slot[slot] = value; return v->slot[slot]; } int find_slot(vector v, void * addr) { int i; if (!v) return -1; for (i = 0; i < VECTOR_SIZE(v); i++) if (v->slot[i] == addr) return i; return -1; } void vector_del_slot(vector v, int slot) { int i; if (!v || !v->allocated || slot < 0 || slot >= VECTOR_SIZE(v)) return; for (i = slot + 1; i < VECTOR_SIZE(v); i++) v->slot[i-1] = v->slot[i]; v->allocated -= VECTOR_DEFAULT_SIZE; if (v->allocated <= 0) { free(v->slot); v->slot = NULL; v->allocated = 0; } else { void *new_slot; new_slot = realloc(v->slot, sizeof (void *) * v->allocated); if (!new_slot) v->allocated += VECTOR_DEFAULT_SIZE; else v->slot = new_slot; } } void vector_repack(vector v) { int i; if (!v || !v->allocated) return; for (i = 0; i < VECTOR_SIZE(v); i++) if (i > 0 && v->slot[i] == NULL) vector_del_slot(v, i--); } vector vector_reset(vector v) { if (!v) return NULL; if (v->slot) free(v->slot); v->allocated = 0; v->slot = NULL; return v; } /* Free memory vector allocation */ void vector_free(vector v) { if (!vector_reset(v)) return; free(v); } void cleanup_vector(vector *pv) { if (*pv) vector_free(*pv); } void free_strvec(vector strvec) { int i; char *str; if (!strvec) return; vector_foreach_slot (strvec, str, i) if (str) free(str); vector_free(strvec); } /* Set a vector slot value */ void vector_set_slot(vector v, void *value) { unsigned int i; if (!v) return; i = VECTOR_SIZE(v) - 1; v->slot[i] = value; } int vector_find_or_add_slot(vector v, void *value) { int n = find_slot(v, value); if (n >= 0) return n; if (!vector_alloc_slot(v)) return -1; vector_set_slot(v, value); return VECTOR_SIZE(v) - 1; } void vector_sort(vector v, int (*compar)(const void *, const void *)) { if (!v || !v->slot || !v->allocated) return; return msort((void *)v->slot, v->allocated, sizeof(void *), compar); } multipath-tools-0.11.1/libmpathutil/vector.h000066400000000000000000000064501475246302400211240ustar00rootroot00000000000000/* * Soft: Keepalived is a failover program for the LVS project * . It monitor & manipulate * a loadbalanced server pool using multi-layer checks. * * Part: vector.c include file. * * Version: $Id: vector.h,v 1.0.3 2003/05/11 02:28:03 acassen Exp $ * * Author: Alexandre Cassen, * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #ifndef VECTOR_H_INCLUDED #define VECTOR_H_INCLUDED #include /* vector definition */ struct vector_s { int allocated; void **slot; }; typedef struct vector_s *vector; #define VECTOR_DEFAULT_SIZE 1 #define VECTOR_SIZE(V) ((V) ? ((V)->allocated) / VECTOR_DEFAULT_SIZE : 0) #define VECTOR_SLOT(V,E) (((V) && (E) < VECTOR_SIZE(V) && (E) >= 0) ? (V)->slot[(E)] : NULL) #define VECTOR_LAST_SLOT(V) (((V) && VECTOR_SIZE(V) > 0) ? (V)->slot[(VECTOR_SIZE(V) - 1)] : NULL) #define vector_foreach_slot(v,p,i) \ for (i = 0; (v) && (int)i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) #define vector_foreach_slot_after(v,p,i) \ for (; (v) && (int)i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) #define vector_foreach_slot_backwards(v,p,i) \ for (i = VECTOR_SIZE(v) - 1; (int)i >= 0 && ((p) = (v)->slot[i]); i--) #define identity(x) (x) /* * Given a vector vec with elements of given type, * return a newly allocated vector with elements conv(e) for each element * e in vec. "conv" may be a macro or a function. * Use "identity" for a simple copy. */ #define vector_convert(new, vec, type, conv) \ ({ \ const struct vector_s *__v = (vec); \ vector __t = (new); \ type *__j; \ int __i; \ \ if (__t == NULL) \ __t = vector_alloc(); \ if (__t != NULL) { \ vector_foreach_slot(__v, __j, __i) { \ if (!vector_alloc_slot(__t)) { \ vector_free(__t); \ __t = NULL; \ break; \ } \ vector_set_slot(__t, conv(__j)); \ } \ } \ __t; \ }) /* Prototypes */ extern vector vector_alloc(void); extern bool vector_alloc_slot(vector v); vector vector_reset(vector v); extern void vector_free(vector v); void cleanup_vector(vector *pv); #define vector_free_const(x) vector_free((vector)(long)(x)) extern void free_strvec(vector strvec); extern void vector_set_slot(vector v, void *value); extern void vector_del_slot(vector v, int slot); extern void *vector_insert_slot(vector v, int slot, void *value); int find_slot(vector v, void * addr); int vector_find_or_add_slot(vector v, void *value); extern void vector_repack(vector v); extern void vector_dump(vector v); extern void dump_strvec(vector strvec); extern int vector_move_up(vector v, int src, int dest); void vector_sort(vector v, int (*compar)(const void *, const void *)); #endif multipath-tools-0.11.1/libmpathvalid/000077500000000000000000000000001475246302400175665ustar00rootroot00000000000000multipath-tools-0.11.1/libmpathvalid/Makefile000066400000000000000000000017131475246302400212300ustar00rootroot00000000000000include ../Makefile.inc DEVLIB := libmpathvalid.so CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) CFLAGS += $(LIB_CFLAGS) LIBDEPS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath \ -L$(mpathutildir) -lmpathutil -L$(mpathcmddir) -lmpathcmd -ludev OBJS := mpath_valid.o all: $(DEVLIB) include $(TOPDIR)/rules.mk install: $(LIBS) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(includedir) $(Q)$(INSTALL_PROGRAM) -m 644 mpath_valid.h $(DESTDIR)$(includedir) uninstall: $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) $(Q)$(RM) $(DESTDIR)$(includedir)/mpath_valid.h clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi $(NV_VERSION_SCRIPT) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmpathvalid/libmpathvalid.version000066400000000000000000000002241475246302400240130ustar00rootroot00000000000000MPATH_1.0 { global: mpathvalid_init; mpathvalid_reload_config; mpathvalid_exit; mpathvalid_is_path; mpathvalid_get_mode; local: *; }; multipath-tools-0.11.1/libmpathvalid/mpath_valid.c000066400000000000000000000075571475246302400222400ustar00rootroot00000000000000#include #include #include #include #include #include #include "devmapper.h" #include "structs.h" #include "util.h" #include "config.h" #include "discovery.h" #include "wwids.h" #include "sysfs.h" #include "mpath_cmd.h" #include "valid.h" #include "mpath_valid.h" #include "debug.h" static unsigned int get_conf_mode(struct config *conf) { if (conf->find_multipaths == FIND_MULTIPATHS_SMART) return MPATH_SMART; if (conf->find_multipaths == FIND_MULTIPATHS_GREEDY) return MPATH_GREEDY; return MPATH_STRICT; } static void set_conf_mode(struct config *conf, unsigned int mode) { if (mode == MPATH_SMART) conf->find_multipaths = FIND_MULTIPATHS_SMART; else if (mode == MPATH_GREEDY) conf->find_multipaths = FIND_MULTIPATHS_GREEDY; else conf->find_multipaths = FIND_MULTIPATHS_STRICT; } unsigned int mpathvalid_get_mode(void) { int mode; struct config *conf; conf = get_multipath_config(); if (!conf) mode = MPATH_MODE_ERROR; else mode = get_conf_mode(conf); put_multipath_config(conf); return mode; } static int convert_result(int result) { switch (result) { case PATH_IS_ERROR: return MPATH_IS_ERROR; case PATH_IS_NOT_VALID: return MPATH_IS_NOT_VALID; case PATH_IS_VALID: return MPATH_IS_VALID; case PATH_IS_VALID_NO_CHECK: return MPATH_IS_VALID_NO_CHECK; case PATH_IS_MAYBE_VALID: return MPATH_IS_MAYBE_VALID; } return MPATH_IS_ERROR; } static void set_log_style(int log_style) { /* * convert MPATH_LOG_* to LOGSINK_* * currently there is no work to do here. */ logsink = log_style; } static int load_default_config(int verbosity) { /* need to set verbosity here to control logging during init_config() */ libmp_verbosity = verbosity; if (init_config(DEFAULT_CONFIGFILE)) return -1; /* Need to override verbosity from init_config() */ libmp_verbosity = verbosity; return 0; } int mpathvalid_init(int verbosity, int log_style) { unsigned int version[3]; set_log_style(log_style); if (libmultipath_init()) return -1; skip_libmp_dm_init(); if (load_default_config(verbosity)) goto fail; if (dm_prereq(version)) goto fail_config; return 0; fail_config: uninit_config(); fail: libmultipath_exit(); return -1; } int mpathvalid_reload_config(void) { uninit_config(); return load_default_config(libmp_verbosity); } int mpathvalid_exit(void) { uninit_config(); libmultipath_exit(); return 0; } /* * name: name of path to check * mode: mode to use for determination. MPATH_DEFAULT uses configured mode * info: on success, contains the path wwid * paths: array of the returned mpath_info from other claimed paths * nr_paths: the size of the paths array */ int mpathvalid_is_path(const char *name, unsigned int mode, char **wwid, const char **path_wwids, unsigned int nr_paths) { struct config *conf; int find_multipaths_saved, r = MPATH_IS_ERROR; unsigned int i; struct path *pp; if (!name || mode >= MPATH_MODE_ERROR) return r; if (nr_paths > 0 && !path_wwids) return r; if (!udev) return r; pp = alloc_path(); if (!pp) return r; if (wwid) { *wwid = (char *)malloc(WWID_SIZE); if (!*wwid) goto out; } conf = get_multipath_config(); if (!conf) goto out_wwid; find_multipaths_saved = conf->find_multipaths; if (mode != MPATH_DEFAULT) set_conf_mode(conf, mode); r = convert_result(is_path_valid(name, conf, pp, true)); conf->find_multipaths = find_multipaths_saved; put_multipath_config(conf); if (r == MPATH_IS_MAYBE_VALID) { for (i = 0; i < nr_paths; i++) { if (path_wwids[i] && strncmp(path_wwids[i], pp->wwid, WWID_SIZE) == 0) { r = MPATH_IS_VALID; break; } } } out_wwid: if (wwid) { if (r == MPATH_IS_VALID || r == MPATH_IS_VALID_NO_CHECK || r == MPATH_IS_MAYBE_VALID) strlcpy(*wwid, pp->wwid, WWID_SIZE); else { free(*wwid); *wwid = NULL; } } out: free_path(pp); return r; } multipath-tools-0.11.1/libmpathvalid/mpath_valid.h000066400000000000000000000121561475246302400222340ustar00rootroot00000000000000/* * Copyright (C) 2015 Red Hat, Inc. * * This file is part of the device-mapper multipath userspace tools. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MPATH_VALID_H_INCLUDED #define MPATH_VALID_H_INCLUDED #ifdef __cplusplus extern "C" { #endif enum mpath_valid_mode { MPATH_DEFAULT, MPATH_STRICT, MPATH_SMART, MPATH_GREEDY, MPATH_MODE_ERROR, }; /* * MPATH_IS_VALID_NO_CHECK is used to indicate that it is safe to skip * checks to see if the device has already been released to the system * for use by things other that multipath. * MPATH_IS_MAYBE_VALID is used to indicate that this device would * be a valid multipath path device if another device with the same * wwid existed */ enum mpath_valid_result { MPATH_IS_ERROR = -1, MPATH_IS_NOT_VALID, MPATH_IS_VALID, MPATH_IS_VALID_NO_CHECK, MPATH_IS_MAYBE_VALID, }; enum mpath_valid_log_style { MPATH_LOG_STDERR = -1, /* log to STDERR */ MPATH_LOG_STDERR_TIMESTAMP, /* log to STDERR, with timestamps */ MPATH_LOG_SYSLOG, /* log to system log */ }; enum mpath_valid_verbosity { MPATH_LOG_PRIO_NOLOG = -1, /* log nothing */ MPATH_LOG_PRIO_ERR, MPATH_LOG_PRIO_WARN, MPATH_LOG_PRIO_NOTICE, MPATH_LOG_PRIO_INFO, MPATH_LOG_PRIO_DEBUG, }; /* Function declarations */ /* * DESCRIPTION: * Initialize the device mapper multipath configuration. This * function must be invoked before calling any other * libmpathvalid functions. Call mpathvalid_exit() to cleanup. * @verbosity: the logging level (mpath_valid_verbosity) * @log_style: the logging style (mpath_valid_log_style) * * RESTRICTIONS: * Calling mpathvalid_init() after calling mpathvalid_exit() has no * effect. * * RETURNS: 0 = Success, -1 = Failure */ int mpathvalid_init(int verbosity, int log_style); /* * DESCRIPTION: * Reread the multipath configuration files and reinitialize * the device mapper multipath configuration. This function can * be called as many times as necessary. * * RETURNS: 0 = Success, -1 = Failure */ int mpathvalid_reload_config(void); /* * DESCRIPTION: * Release the device mapper multipath configuration. This * function must be called to cleanup resources allocated by * mpathvalid_init(). After calling this function, no further * libmpathvalid functions may be called. * * RETURNS: 0 = Success, -1 = Failure */ int mpathvalid_exit(void); /* * DESCRIPTION: * Return the configured find_multipaths claim mode, using the * configuration from either mpathvalid_init() or * mpathvalid_reload_config() * * RETURNS: * MPATH_STRICT, MPATH_SMART, MPATH_GREEDY, or MPATH_MODE_ERROR * * MPATH_STRICT = find_multipaths (yes|on|no|off) * MPATH_SMART = find_multipaths smart * MPATH_GREEDY = find_multipaths greedy * MPATH_MODE_ERROR = multipath configuration not initialized */ unsigned int mpathvalid_get_mode(void); /* * DESCRIPTION: * Return whether device-mapper multipath claims a path device, * using the configuration read from either mpathvalid_init() or * mpathvalid_reload_config(). If the device is either claimed or * potentially claimed (MPATH_IS_VALID, MPATH_IS_VALID_NO_CHECK, * or MPATH_IS_MAYBE_VALID) and wwid is not NULL, then *wwid will * be set to point to the wwid of device. If set, *wwid must be * freed by the caller. path_wwids is an optional parameter that * points to an array of wwids, that were returned from previous * calls to mpathvalid_is_path(). These are wwids of existing * devices that are or potentially are claimed by device-mapper * multipath. path_wwids is used with the MPATH_SMART claim mode, * to claim devices when another device with the same wwid exists. * nr_paths must either be set to the number of elements of * path_wwids, or 0, if path_wwids is NULL. * @name: The kernel name of the device. input argument * @mode: the find_multipaths claim mode (mpath_valid_mode). input argument * @wwid: address of a pointer to the path wwid, or NULL. Output argument. * Set if path is/may be claimed. If set, must be freed by caller * @path_wwids: Array of pointers to path wwids, or NULL. input argument * @nr_paths: number of elements in path_wwids array. input argument. * * RETURNS: device claim result (mpath_valid_result) * Also sets *wwid if wwid is not NULL, and the claim result is * MPATH_IS_VALID, MPATH_IS_VALID_NO_CHECK, or * MPATH_IS_MAYBE_VALID */ int mpathvalid_is_path(const char *name, unsigned int mode, char **wwid, const char **path_wwids, unsigned int nr_paths); #ifdef __cplusplus } #endif #endif /* MPATH_VALID_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/000077500000000000000000000000001475246302400174445ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/Makefile000066400000000000000000000046671475246302400211210ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # include ../Makefile.inc DEVLIB := libmultipath.so CPPFLAGS += -I$(mpathutildir) -I$(mpathcmddir) -I$(nvmedir) -D_GNU_SOURCE $(SYSTEMD_CPPFLAGS) CFLAGS += $(LIB_CFLAGS) LIBDEPS += -lpthread -ldl -ldevmapper -ludev -L$(mpathutildir) -lmpathutil -L$(mpathcmddir) -lmpathcmd \ -lmount -lurcu -laio $(SYSTEMD_LIBDEPS) # object files referencing MULTIPATH_DIR or CONFIG_DIR # they need to be recompiled for unit tests OBJS-U := prio.o checkers.o foreign.o config.o OBJS-T := $(patsubst %.o,%-test.o,$(OBJS-U)) # other object files OBJS-O := devmapper.o hwtable.o blacklist.o dmparser.o \ structs.o discovery.o propsel.o dict.o \ pgpolicies.o defaults.o uevent.o \ switchgroup.o print.o alias.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 OBJS := $(OBJS-O) $(OBJS-U) all: $(DEVLIB) include $(TOPDIR)/rules.mk nvme-lib.o: nvme-lib.c nvme-ioctl.c nvme-ioctl.h $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -Wno-unused-function -c -o $@ $< # there are lots of "unused parameters" in dict.c # because not all handler / snprint methods need all parameters dict.o: dict.c $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -Wno-unused-parameter -c -o $@ $< make_static = $(shell sed '/^static/!s/^\([a-z]\{1,\} \)/static \1/' <$1 >$2) nvme-ioctl.c: nvme/nvme-ioctl.c $(Q)$(call make_static,$<,$@) nvme-ioctl.h: nvme/nvme-ioctl.h $(Q)$(call make_static,$<,$@) ../tests/$(LIBS): $(OBJS-O) $(OBJS-T) $(VERSION_SCRIPT) $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=`basename $@` \ -o $@ $(OBJS-O) $(OBJS-T) $(LIBDEPS) $(Q)$(LN) $@ ${@:.so.0=.so} # This rule is invoked from tests/Makefile, overriding configdir and plugindir %-test.o: %.c @echo building $@ because of $? $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< test-lib: ../tests/$(LIBS) install: all $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir) $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(plugindir) $(Q)$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) uninstall: $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(LIBS) $(Q)$(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) clean: dep_clean $(Q)$(RM) core *.a *.o *.so *.so.* *.abi nvme-ioctl.c nvme-ioctl.h autoconfig.h $(NV_VERSION_SCRIPT) include $(wildcard $(OBJS:.o=.d) $(OBJS-T:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) $(OBJS-T:.o=.d) multipath-tools-0.11.1/libmultipath/alias.c000066400000000000000000000543051475246302400207100ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat */ #include #include #include #include #include #include #include #include #include #include "debug.h" #include "util.h" #include "uxsock.h" #include "alias.h" #include "file.h" #include "vector.h" #include "checkers.h" #include "structs.h" #include "config.h" #include "devmapper.h" #include "strbuf.h" #include "time-util.h" #include "lock.h" /* * significant parts of this file were taken from iscsi-bindings.c of the * linux-iscsi project. * Copyright (C) 2002 Cisco Systems, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * See the file COPYING included with this distribution for more details. */ #define BINDINGS_FILE_HEADER \ "# Multipath bindings, Version : 1.0\n" \ "# NOTE: this file is automatically maintained by the multipath program.\n" \ "# You should not need to edit this file in normal circumstances.\n" \ "#\n" \ "# Format:\n" \ "# alias wwid\n" \ "#\n" /* uatomic access only */ static int bindings_file_changed = 1; static const char bindings_file_path[] = DEFAULT_BINDINGS_FILE; static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER; static struct timespec bindings_last_updated; struct binding { char *alias; char *wwid; }; /* * Perhaps one day we'll implement this more efficiently, thus use * an abstract type. */ typedef struct vector_s Bindings; /* Protect global_bindings */ static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; static Bindings global_bindings = { .allocated = 0 }; enum { BINDING_EXISTS, BINDING_CONFLICT, BINDING_ADDED, BINDING_DELETED, BINDING_NOTFOUND, BINDING_ERROR, }; static void _free_binding(struct binding *bdg) { free(bdg->wwid); free(bdg->alias); free(bdg); } static void free_bindings(Bindings *bindings) { struct binding *bdg; int i; vector_foreach_slot(bindings, bdg, i) _free_binding(bdg); vector_reset(bindings); } static void set_global_bindings(Bindings *bindings) { Bindings old_bindings; pthread_mutex_lock(&bindings_mutex); old_bindings = global_bindings; global_bindings = *bindings; pthread_mutex_unlock(&bindings_mutex); free_bindings(&old_bindings); } static const struct binding *get_binding_for_alias(const Bindings *bindings, const char *alias) { const struct binding *bdg; int i; if (!alias) return NULL; vector_foreach_slot(bindings, bdg, i) { if (!strncmp(bdg->alias, alias, WWID_SIZE)) { condlog(3, "Found matching alias [%s] in bindings file." " Setting wwid to %s", alias, bdg->wwid); return bdg; } } condlog(3, "No matching alias [%s] in bindings file.", alias); return NULL; } static const struct binding *get_binding_for_wwid(const Bindings *bindings, const char *wwid) { const struct binding *bdg; int i; if (!wwid) return NULL; vector_foreach_slot(bindings, bdg, i) { if (!strncmp(bdg->wwid, wwid, WWID_SIZE)) { condlog(3, "Found matching wwid [%s] in bindings file." " Setting alias to %s", wwid, bdg->alias); return bdg; } } condlog(3, "No matching wwid [%s] in bindings file.", wwid); return NULL; } /* * Sort order for aliases. * * The "numeric" ordering of aliases for a given prefix P is * Pa, ..., Pz, Paa, ..., Paz, Pba, ... , Pzz, Paaa, ..., Pzzz, Paaaa, ... * We use the fact that for equal prefix, longer strings are always * higher than shorter ones. Strings of equal length are sorted alphabetically. * This is achieved by sorting be length first, then using strcmp(). * If multiple prefixes are in use, the aliases with a given prefix will * not necessarily be in a contiguous range of the vector, but they will * be ordered such that for a given prefix, numerically higher aliases will * always be sorted after lower ones. */ static int alias_compar(const void *p1, const void *p2) { const char *alias1 = *((char * const *)p1); const char *alias2 = *((char * const *)p2); if (alias1 && alias2) { ssize_t ldif = strlen(alias1) - strlen(alias2); if (ldif) return ldif; return strcmp(alias1, alias2); } else /* Move NULL alias to the end */ return alias1 ? -1 : alias2 ? 1 : 0; } static int add_binding(Bindings *bindings, const char *alias, const char *wwid) { struct binding *bdg; int i, cmp = 0; /* * Keep the bindings array sorted by alias. * Optimization: Search backwards, assuming that the bindings file is * sorted already. */ vector_foreach_slot_backwards(bindings, bdg, i) { if ((cmp = alias_compar(&bdg->alias, &alias)) <= 0) break; } /* Check for exact match */ if (i >= 0 && cmp == 0) return strcmp(bdg->wwid, wwid) ? BINDING_CONFLICT : BINDING_EXISTS; i++; bdg = calloc(1, sizeof(*bdg)); if (bdg) { bdg->wwid = strdup(wwid); bdg->alias = strdup(alias); if (bdg->wwid && bdg->alias && vector_insert_slot(bindings, i, bdg)) return BINDING_ADDED; else _free_binding(bdg); } return BINDING_ERROR; } static int delete_binding(Bindings *bindings, const char *wwid) { struct binding *bdg; int i; vector_foreach_slot(bindings, bdg, i) { if (!strncmp(bdg->wwid, wwid, WWID_SIZE)) { _free_binding(bdg); break; } } if (i >= VECTOR_SIZE(bindings)) return BINDING_NOTFOUND; vector_del_slot(bindings, i); return BINDING_DELETED; } static int write_bindings_file(const Bindings *bindings, int fd, struct timespec *ts) { struct binding *bnd; STRBUF_ON_STACK(content); int i; size_t len; if (append_strbuf_str__(&content, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) == -1) return -1; vector_foreach_slot(bindings, bnd, i) { if (print_strbuf(&content, "%s %s\n", bnd->alias, bnd->wwid) < 0) return -1; } len = get_strbuf_len(&content); while (len > 0) { ssize_t n = write(fd, get_strbuf_str(&content), len); if (n < 0) return n; else if (n == 0) { condlog(2, "%s: short write", __func__); return -1; } len -= n; } fsync(fd); if (ts) { struct stat st; if (fstat(fd, &st) == 0) *ts = st.st_mtim; else clock_gettime(CLOCK_REALTIME_COARSE, ts); } return 0; } void handle_bindings_file_inotify(const struct inotify_event *event) { const char *base; bool changed = false; struct stat st; struct timespec ts = { 0, 0 }; int ret; if (!(event->mask & IN_MOVED_TO)) return; base = strrchr(bindings_file_path, '/'); changed = base && !strcmp(base + 1, event->name); ret = stat(bindings_file_path, &st); if (!changed) return; pthread_mutex_lock(×tamp_mutex); if (ret == 0) { ts = st.st_mtim; changed = timespeccmp(&ts, &bindings_last_updated) > 0; } pthread_mutex_unlock(×tamp_mutex); if (changed) { uatomic_xchg_int(&bindings_file_changed, 1); condlog(3, "%s: bindings file must be re-read, new timestamp: %ld.%06ld", __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); } else condlog(3, "%s: bindings file is up-to-date, timestamp: %ld.%06ld", __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); } static int update_bindings_file(const Bindings *bindings) { int rc; int fd = -1; char tempname[PATH_MAX]; mode_t old_umask; struct timespec ts; if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file_path)) return -1; /* coverity: SECURE_TEMP */ old_umask = umask(0077); if ((fd = mkstemp(tempname)) == -1) { condlog(1, "%s: mkstemp: %m", __func__); return -1; } umask(old_umask); pthread_cleanup_push(cleanup_fd_ptr, &fd); rc = write_bindings_file(bindings, fd, &ts); pthread_cleanup_pop(1); if (rc == -1) { condlog(1, "failed to write new bindings file"); unlink(tempname); return rc; } if ((rc = rename(tempname, bindings_file_path)) == -1) condlog(0, "%s: rename: %m", __func__); else { pthread_mutex_lock(×tamp_mutex); bindings_last_updated = ts; pthread_mutex_unlock(×tamp_mutex); condlog(1, "updated bindings file %s", bindings_file_path); } return rc; } int valid_alias(const char *alias) { if (strchr(alias, '/') != NULL) return 0; return 1; } static int format_devname(struct strbuf *buf, int id) { /* * We need: 7 chars for 32bit integers, 14 chars for 64bit integers */ char devname[2 * sizeof(int)]; int pos = sizeof(devname) - 1, rc; if (id <= 0) return -1; devname[pos] = '\0'; for (; id >= 1; id /= 26) devname[--pos] = 'a' + --id % 26; rc = append_strbuf_str(buf, devname + pos); return rc >= 0 ? rc : -1; } static int scan_devname(const char *alias, const char *prefix) { const char *c; int i, n = 0; static const int last_26 = INT_MAX / 26; if (!prefix || strncmp(alias, prefix, strlen(prefix))) return -1; if (strlen(alias) == strlen(prefix)) return -1; if (strlen(alias) > strlen(prefix) + 7) /* id of 'aaaaaaaa' overflows int */ return -1; c = alias + strlen(prefix); while (*c != '\0' && *c != ' ' && *c != '\t') { if (*c < 'a' || *c > 'z') return -1; i = *c - 'a'; if (n > last_26 || (n == last_26 && i >= INT_MAX % 26)) return -1; n = n * 26 + i; c++; n++; } return n; } static bool alias_already_taken(const char *alias, const char *map_wwid) { char wwid[WWID_SIZE]; int rc = dm_get_wwid(alias, wwid, sizeof(wwid)); /* * If the map doesn't exist, it's fine. * In the generic error case, assume that the device is not * taken, and try to proceed. */ if (rc == DMP_NOT_FOUND || rc == DMP_ERR) return false; /* If both the name and the wwid match, it's fine.*/ if (rc == DMP_OK && strncmp(map_wwid, wwid, sizeof(wwid)) == 0) return false; condlog(3, "%s: alias '%s' already taken, reselecting alias", map_wwid, alias); return true; } static bool id_already_taken(int id, const char *prefix, const char *map_wwid) { STRBUF_ON_STACK(buf); const char *alias; if (append_strbuf_str(&buf, prefix) < 0 || format_devname(&buf, id) < 0) return false; alias = get_strbuf_str(&buf); return alias_already_taken(alias, map_wwid); } int get_free_id(const Bindings *bindings, const char *prefix, const char *map_wwid) { const struct binding *bdg; int i, id = 1; vector_foreach_slot(bindings, bdg, i) { int curr_id = scan_devname(bdg->alias, prefix); if (curr_id == -1) continue; if (id > curr_id) { condlog(0, "%s: ERROR: bindings are not sorted", __func__); return -1; } while (id < curr_id && id_already_taken(id, prefix, map_wwid)) id++; if (id < curr_id) return id; id++; if (id <= 0) break; } for (; id > 0; id++) { if (!id_already_taken(id, prefix, map_wwid)) break; } if (id <= 0) { id = -1; condlog(0, "no more available user_friendly_names"); } return id; } /* Called with binding_mutex held */ static char * allocate_binding(const char *wwid, int id, const char *prefix) { STRBUF_ON_STACK(buf); char *alias; if (id <= 0) { condlog(0, "%s: cannot allocate new binding for id %d", __func__, id); return NULL; } if (append_strbuf_str(&buf, prefix) < 0 || format_devname(&buf, id) == -1) return NULL; alias = steal_strbuf_str(&buf); if (add_binding(&global_bindings, alias, wwid) != BINDING_ADDED) { condlog(0, "%s: cannot allocate new binding %s for %s", __func__, alias, wwid); free(alias); return NULL; } if (update_bindings_file(&global_bindings) == -1) { condlog(1, "%s: deleting binding %s for %s", __func__, alias, wwid); delete_binding(&global_bindings, wwid); free(alias); return NULL; } condlog(3, "Created new binding [%s] for WWID [%s]", alias, wwid); return alias; } enum { BINDINGS_FILE_UP2DATE, BINDINGS_FILE_READ, BINDINGS_FILE_ERROR, BINDINGS_FILE_BAD, }; static int _read_bindings_file(const struct config *conf, Bindings *bindings, bool force); static void read_bindings_file(void) { struct config *conf; Bindings bindings = {.allocated = 0, }; int rc; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); rc = _read_bindings_file(conf, &bindings, false); pthread_cleanup_pop(1); if (rc == BINDINGS_FILE_READ) set_global_bindings(&bindings); } /* * get_user_friendly_alias() action table * * The table shows the various cases, the actions taken, and the CI * functions from tests/alias.c that represent them. * * - O: old alias given * - A: old alias in table (y: yes, correct WWID; X: yes, wrong WWID) * - W: wwid in table * * | No | O | A | W | action | function gufa_X | * |----+---+---+---+--------------------------------------------+------------------------------| * | 1 | n | - | n | get new alias | nomatch_Y | * | 2 | n | - | y | use alias from bindings | match_a_Y | * | 3 | y | n | n | add binding for old alias | old_nomatch_nowwidmatch | * | 4 | y | n | y | use alias from bindings (avoid duplicates) | old_nomatch_wwidmatch | * | 5 | y | y | n | [ impossible ] | - | * | 6 | y | y | y | use old alias == alias from bindings | old_match | * | 7 | y | X | n | get new alias | old_match_other | * | 8 | y | X | y | use alias from bindings | old_match_other_wwidmatch | * * Notes: * - "use alias from bindings" means that the alias from the bindings file will * be tried; if it is in use, the alias selection will fail. No other * bindings will be attempted. * - "get new alias" fails if all aliases are used up, or if writing the * bindings file fails. * - if "alias_old" is set, it can't be bound to a different map. alias_old is * initialized in find_existing_alias() by scanning the mpvec. We trust * that the mpvec correctly represents kernel state. */ char *get_user_friendly_alias(const char *wwid, const char *alias_old, const char *prefix, bool bindings_read_only) { char *alias = NULL; int id = 0; bool new_binding = false; const struct binding *bdg; read_bindings_file(); pthread_mutex_lock(&bindings_mutex); pthread_cleanup_push(cleanup_mutex, &bindings_mutex); if (!*alias_old) goto new_alias; /* See if there's a binding matching both alias_old and wwid */ bdg = get_binding_for_alias(&global_bindings, alias_old); if (bdg) { if (!strcmp(bdg->wwid, wwid)) { alias = strdup(alias_old); goto out; } else { condlog(0, "alias %s already bound to wwid %s, cannot reuse", alias_old, bdg->wwid); goto new_alias; } } /* allocate the existing alias in the bindings file */ id = scan_devname(alias_old, prefix); new_alias: /* Check for existing binding of WWID */ bdg = get_binding_for_wwid(&global_bindings, wwid); if (bdg) { if (!alias_already_taken(bdg->alias, wwid)) { condlog(3, "Use existing binding [%s] for WWID [%s]", bdg->alias, wwid); alias = strdup(bdg->alias); } goto out; } if (id <= 0) { /* * no existing alias was provided, or allocating it * failed. Try a new one. */ id = get_free_id(&global_bindings, prefix, wwid); if (id <= 0) goto out; else new_binding = true; } if (!bindings_read_only && id > 0) alias = allocate_binding(wwid, id, prefix); if (alias && !new_binding) condlog(2, "Allocated existing binding [%s] for WWID [%s]", alias, wwid); out: /* unlock bindings_mutex */ pthread_cleanup_pop(1); return alias; } int get_user_friendly_wwid(const char *alias, char *buff) { const struct binding *bdg; int rc = -1; if (!alias || *alias == '\0') { condlog(3, "Cannot find binding for empty alias"); return -1; } read_bindings_file(); pthread_mutex_lock(&bindings_mutex); pthread_cleanup_push(cleanup_mutex, &bindings_mutex); bdg = get_binding_for_alias(&global_bindings, alias); if (bdg) { strlcpy(buff, bdg->wwid, WWID_SIZE); rc = 0; } else *buff = '\0'; pthread_cleanup_pop(1); return rc; } void cleanup_bindings(void) { pthread_mutex_lock(&bindings_mutex); free_bindings(&global_bindings); pthread_mutex_unlock(&bindings_mutex); } enum { READ_BINDING_OK, READ_BINDING_SKIP, }; static int read_binding(char *line, unsigned int linenr, char **alias, char **wwid) { char *c, *saveptr; c = strpbrk(line, "#\n\r"); if (c) *c = '\0'; *alias = strtok_r(line, " \t", &saveptr); if (!*alias) /* blank line */ return READ_BINDING_SKIP; *wwid = strtok_r(NULL, " \t", &saveptr); if (!*wwid) { condlog(1, "invalid line %u in bindings file, missing WWID", linenr); return READ_BINDING_SKIP; } if (strlen(*wwid) > WWID_SIZE - 1) { condlog(3, "Ignoring too large wwid at %u in bindings file", linenr); return READ_BINDING_SKIP; } c = strtok_r(NULL, " \t", &saveptr); if (c) /* This is non-fatal */ condlog(1, "invalid line %d in bindings file, extra args \"%s\"", linenr, c); return READ_BINDING_OK; } static int _check_bindings_file(const struct config *conf, FILE *file, Bindings *bindings) { int rc = 0; unsigned int linenr = 0; char *line = NULL; size_t line_len = 0; ssize_t n; char header[sizeof(BINDINGS_FILE_HEADER)]; header[sizeof(BINDINGS_FILE_HEADER) - 1] = '\0'; if (fread(header, sizeof(BINDINGS_FILE_HEADER) - 1, 1, file) < 1) { condlog(2, "%s: failed to read header from %s", __func__, bindings_file_path); fseek(file, 0, SEEK_SET); rc = -1; } else if (strcmp(header, BINDINGS_FILE_HEADER)) { condlog(2, "%s: invalid header in %s", __func__, bindings_file_path); fseek(file, 0, SEEK_SET); rc = -1; } pthread_cleanup_push(cleanup_free_ptr, &line); while ((n = getline(&line, &line_len, file)) >= 0) { char *alias, *wwid; const char *mpe_wwid; if (read_binding(line, ++linenr, &alias, &wwid) == READ_BINDING_SKIP) continue; mpe_wwid = get_mpe_wwid(conf->mptable, alias); if (mpe_wwid && strcmp(mpe_wwid, wwid)) { condlog(0, "ERROR: alias \"%s\" for WWID %s in bindings file " "on line %u conflicts with multipath.conf entry for %s", alias, wwid, linenr, mpe_wwid); rc = -1; continue; } switch (add_binding(bindings, alias, wwid)) { case BINDING_CONFLICT: condlog(0, "ERROR: multiple bindings for alias \"%s\" in " "bindings file on line %u, discarding binding to WWID %s", alias, linenr, wwid); rc = -1; break; case BINDING_EXISTS: condlog(2, "duplicate line for alias %s in bindings file on line %u", alias, linenr); break; case BINDING_ERROR: condlog(2, "error adding binding %s -> %s", alias, wwid); break; default: break; } } pthread_cleanup_pop(1); return rc; } static int mp_alias_compar(const void *p1, const void *p2) { return alias_compar(&((*(struct mpentry * const *)p1)->alias), &((*(struct mpentry * const *)p2)->alias)); } static int _read_bindings_file(const struct config *conf, Bindings *bindings, bool force) { int can_write; int rc = 0, ret, fd; FILE *file; struct stat st; int has_changed = uatomic_xchg_int(&bindings_file_changed, 0); if (!force) { if (!has_changed) { condlog(4, "%s: bindings are unchanged", __func__); return BINDINGS_FILE_UP2DATE; } } fd = open_file(bindings_file_path, &can_write, BINDINGS_FILE_HEADER); if (fd == -1) return BINDINGS_FILE_ERROR; file = fdopen(fd, "r"); if (file != NULL) { condlog(3, "%s: reading %s", __func__, bindings_file_path); pthread_cleanup_push(cleanup_fclose, file); ret = _check_bindings_file(conf, file, bindings); if (ret == 0) { struct timespec ts; rc = BINDINGS_FILE_READ; ret = fstat(fd, &st); if (ret == 0) ts = st.st_mtim; else { condlog(1, "%s: fstat failed (%m), using current time", __func__); clock_gettime(CLOCK_REALTIME_COARSE, &ts); } pthread_mutex_lock(×tamp_mutex); bindings_last_updated = ts; pthread_mutex_unlock(×tamp_mutex); } else if (ret == -1 && can_write && !conf->bindings_read_only) { ret = update_bindings_file(bindings); if (ret == 0) rc = BINDINGS_FILE_READ; else rc = BINDINGS_FILE_BAD; } else { condlog(0, "ERROR: bad settings in read-only bindings file %s", bindings_file_path); rc = BINDINGS_FILE_BAD; } pthread_cleanup_pop(1); } else { condlog(1, "failed to fdopen %s: %m", bindings_file_path); close(fd); rc = BINDINGS_FILE_ERROR; } return rc; } /* * check_alias_settings(): test for inconsistent alias configuration * * It's a fatal configuration error if the same alias is assigned to * multiple WWIDs. In the worst case, it can cause data corruption * by mangling devices with different WWIDs into the same multipath map. * This function tests the configuration from multipath.conf and the * bindings file for consistency, drops inconsistent multipath.conf * alias settings, and rewrites the bindings file if necessary, dropping * conflicting lines (if user_friendly_names is on, multipathd will * fill in the deleted lines with a newly generated alias later). * Note that multipath.conf is not rewritten. Use "multipath -T" for that. * * Returns: 0 in case of success, -1 if the configuration was bad * and couldn't be fixed. */ int check_alias_settings(const struct config *conf) { int i, rc; Bindings bindings = {.allocated = 0, }; vector mptable = NULL; struct mpentry *mpe; mptable = vector_convert(NULL, conf->mptable, struct mpentry *, identity); if (!mptable) return -1; pthread_cleanup_push_cast(free_bindings, &bindings); pthread_cleanup_push(cleanup_vector_free, mptable); vector_sort(mptable, mp_alias_compar); vector_foreach_slot(mptable, mpe, i) { if (!mpe->alias) /* * alias_compar() sorts NULL alias at the end, * so we can stop if we encounter this. */ break; if (add_binding(&bindings, mpe->alias, mpe->wwid) == BINDING_CONFLICT) { condlog(0, "ERROR: alias \"%s\" bound to multiple wwids in multipath.conf, " "discarding binding to %s", mpe->alias, mpe->wwid); free(mpe->alias); mpe->alias = NULL; } } /* This clears the bindings */ pthread_cleanup_pop(1); pthread_cleanup_pop(1); rc = _read_bindings_file(conf, &bindings, true); if (rc == BINDINGS_FILE_READ) { set_global_bindings(&bindings); rc = 0; } return rc; } multipath-tools-0.11.1/libmultipath/alias.h000066400000000000000000000007501475246302400207100ustar00rootroot00000000000000#ifndef ALIAS_H_INCLUDED #define ALIAS_H_INCLUDED int valid_alias(const char *alias); int get_user_friendly_wwid(const char *alias, char *buff); char *get_user_friendly_alias(const char *wwid, const char *alias_old, const char *prefix, bool bindings_read_only); struct config; int check_alias_settings(const struct config *); void cleanup_bindings(void); struct inotify_event; void handle_bindings_file_inotify(const struct inotify_event *event); #endif /* ALIAS_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/blacklist.c000066400000000000000000000307241475246302400215660ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui */ #include #include #include #include #include "checkers.h" #include "vector.h" #include "util.h" #include "debug.h" #include "structs.h" #include "config.h" #include "blacklist.h" #include "structs_vec.h" #include "print.h" #include "strbuf.h" char *check_invert(char *str, bool *invert) { if (str[0] == '!') { *invert = true; return str + 1; } if (str[0] == '\\' && str[1] == '!') { *invert = false; return str + 1; } *invert = false; return str; } int store_ble(vector blist, const char *str, int origin) { struct blentry * ble; char *regex_str; char *strdup_str = NULL; if (!str) return 0; strdup_str = strdup(str); if (!strdup_str) return 1; if (!blist) goto out; ble = calloc(1, sizeof(struct blentry)); if (!ble) goto out; regex_str = check_invert(strdup_str, &ble->invert); if (regcomp(&ble->regex, regex_str, REG_EXTENDED|REG_NOSUB)) goto out1; if (!vector_alloc_slot(blist)) goto out1; ble->str = strdup_str; ble->origin = origin; vector_set_slot(blist, ble); return 0; out1: free(ble); out: free(strdup_str); return 1; } int alloc_ble_device(vector blist) { struct blentry_device *ble; if (!blist) return 1; ble = calloc(1, sizeof(struct blentry_device)); if (!ble) return 1; if (!vector_alloc_slot(blist)) { free(ble); return 1; } vector_set_slot(blist, ble); return 0; } int set_ble_device(vector blist, const char *vendor, const char *product, int origin) { struct blentry_device * ble; char *regex_str; char *vendor_str = NULL; char *product_str = NULL; if (!blist) return 1; ble = VECTOR_LAST_SLOT(blist); if (!ble) return 1; if (vendor) { vendor_str = strdup(vendor); if (!vendor_str) goto out; regex_str = check_invert(vendor_str, &ble->vendor_invert); if (regcomp(&ble->vendor_reg, regex_str, REG_EXTENDED|REG_NOSUB)) goto out; ble->vendor = vendor_str; } if (product) { product_str = strdup(product); if (!product_str) goto out1; regex_str = check_invert(product_str, &ble->product_invert); if (regcomp(&ble->product_reg, regex_str, REG_EXTENDED|REG_NOSUB)) goto out1; ble->product = product_str; } ble->origin = origin; return 0; out1: if (vendor) { regfree(&ble->vendor_reg); ble->vendor = NULL; } out: free(vendor_str); free(product_str); return 1; } static int match_reglist (const struct vector_s *blist, const char *str) { int i; struct blentry * ble; vector_foreach_slot (blist, ble, i) { if (!!regexec(&ble->regex, str, 0, NULL, 0) == ble->invert) return 1; } return 0; } static int match_reglist_device (const struct vector_s *blist, const char *vendor, const char * product) { int i; struct blentry_device * ble; vector_foreach_slot (blist, ble, i) { if (!ble->vendor && !ble->product) continue; if ((!ble->vendor || !!regexec(&ble->vendor_reg, vendor, 0, NULL, 0) == ble->vendor_invert) && (!ble->product || !!regexec(&ble->product_reg, product, 0, NULL, 0) == ble->product_invert)) return 1; } return 0; } static int find_blacklist_device (const struct vector_s *blist, const char *vendor, const char *product) { int i; struct blentry_device * ble; vector_foreach_slot (blist, ble, i) { if (((!vendor && !ble->vendor) || (vendor && ble->vendor && !strcmp(vendor, ble->vendor))) && ((!product && !ble->product) || (product && ble->product && !strcmp(product, ble->product)))) return 1; } return 0; } /* * Test if nvme native multipath is enabled. If the sysfs file can't * be accessed, multipath is either disabled at compile time, or no * nvme driver is loaded at all. Thus treat errors as "no". */ static bool nvme_multipath_enabled(void) { static const char fn[] = "/sys/module/nvme_core/parameters/multipath"; int fd, len; char buf[2]; fd = open(fn, O_RDONLY); if (fd == -1) return false; len = read(fd, buf, sizeof(buf)); close(fd); return (len >= 1 && buf[0] == 'Y'); } int setup_default_blist (struct config * conf) { struct blentry * ble; struct hwentry *hwe; int i; if (nvme_multipath_enabled()) { if (store_ble(conf->blist_devnode, "!^(sd[a-z]|dasd[a-z])", ORIGIN_DEFAULT)) return 1; } else { if (store_ble(conf->blist_devnode, "!^(sd[a-z]|dasd[a-z]|nvme[0-9])", ORIGIN_DEFAULT)) return 1; } if (store_ble(conf->elist_property, "(SCSI_IDENT_|ID_WWN)", ORIGIN_DEFAULT)) return 1; vector_foreach_slot (conf->hwtable, hwe, i) { if (hwe->bl_product) { if (find_blacklist_device(conf->blist_device, hwe->vendor, hwe->bl_product)) continue; if (alloc_ble_device(conf->blist_device)) return 1; ble = VECTOR_SLOT(conf->blist_device, VECTOR_SIZE(conf->blist_device) - 1); if (set_ble_device(conf->blist_device, hwe->vendor, hwe->bl_product, ORIGIN_DEFAULT)) { free(ble); vector_del_slot(conf->blist_device, VECTOR_SIZE(conf->blist_device) - 1); return 1; } } } return 0; } #define LOG_BLIST(M, S, lvl) \ if (vendor && product) \ condlog(lvl, "%s: (%s:%s) %s %s", \ dev, vendor, product, (M), (S)); \ else if (wwid && !dev) \ condlog(lvl, "%s: %s %s", wwid, (M), (S)); \ else if (wwid) \ condlog(lvl, "%s: %s %s %s", dev, (M), wwid, (S)); \ else if (env) \ condlog(lvl, "%s: %s %s %s", dev, (M), env, (S)); \ else if (protocol) \ condlog(lvl, "%s: %s %s %s", dev, (M), protocol, (S)); \ else \ condlog(lvl, "%s: %s %s", dev, (M), (S)) static void log_filter (const char *dev, const char *vendor, const char *product, const char *wwid, const char *env, const char *protocol, int r, int lvl) { /* * Try to sort from most likely to least. */ switch (r) { case MATCH_NOTHING: break; case MATCH_DEVICE_BLIST: LOG_BLIST("vendor/product", "blacklisted", lvl); break; case MATCH_WWID_BLIST: LOG_BLIST("wwid", "blacklisted", lvl); break; case MATCH_DEVNODE_BLIST: LOG_BLIST("device node name", "blacklisted", lvl); break; case MATCH_PROPERTY_BLIST: LOG_BLIST("udev property", "blacklisted", lvl); break; case MATCH_PROTOCOL_BLIST: LOG_BLIST("protocol", "blacklisted", lvl); break; case MATCH_DEVICE_BLIST_EXCEPT: LOG_BLIST("vendor/product", "whitelisted", lvl); break; case MATCH_WWID_BLIST_EXCEPT: LOG_BLIST("wwid", "whitelisted", lvl); break; case MATCH_DEVNODE_BLIST_EXCEPT: LOG_BLIST("device node name", "whitelisted", lvl); break; case MATCH_PROPERTY_BLIST_EXCEPT: LOG_BLIST("udev property", "whitelisted", lvl); break; case MATCH_PROPERTY_BLIST_MISSING: LOG_BLIST("blacklisted,", "udev property missing", lvl); break; case MATCH_PROTOCOL_BLIST_EXCEPT: LOG_BLIST("protocol", "whitelisted", lvl); break; } } int filter_device (const struct vector_s *blist, const struct vector_s *elist, const char *vendor, const char * product, const char *dev) { int r = MATCH_NOTHING; if (vendor && product) { if (match_reglist_device(elist, vendor, product)) r = MATCH_DEVICE_BLIST_EXCEPT; else if (match_reglist_device(blist, vendor, product)) r = MATCH_DEVICE_BLIST; } log_filter(dev, vendor, product, NULL, NULL, NULL, r, 3); return r; } int filter_devnode (const struct vector_s *blist, const struct vector_s *elist, const char *dev) { int r = MATCH_NOTHING; if (dev) { if (match_reglist(elist, dev)) r = MATCH_DEVNODE_BLIST_EXCEPT; else if (match_reglist(blist, dev)) r = MATCH_DEVNODE_BLIST; } log_filter(dev, NULL, NULL, NULL, NULL, NULL, r, 3); return r; } int filter_wwid (const struct vector_s *blist, const struct vector_s *elist, const char *wwid, const char *dev) { int r = MATCH_NOTHING; if (wwid) { if (match_reglist(elist, wwid)) r = MATCH_WWID_BLIST_EXCEPT; else if (match_reglist(blist, wwid)) r = MATCH_WWID_BLIST; } log_filter(dev, NULL, NULL, wwid, NULL, NULL, r, 3); return r; } int filter_protocol(const struct vector_s *blist, const struct vector_s *elist, const struct path *pp) { STRBUF_ON_STACK(buf); const char *prot; int r = MATCH_NOTHING; if (pp) { snprint_path_protocol(&buf, pp); prot = get_strbuf_str(&buf); if (match_reglist(elist, prot)) r = MATCH_PROTOCOL_BLIST_EXCEPT; else if (match_reglist(blist, prot)) r = MATCH_PROTOCOL_BLIST; log_filter(pp->dev, NULL, NULL, NULL, NULL, prot, r, 3); } return r; } int filter_path (const struct config *conf, const struct path *pp) { int r; r = filter_property(conf, pp->udev, 3, pp->uid_attribute); if (r > 0) return r; r = filter_devnode(conf->blist_devnode, conf->elist_devnode, pp->dev); if (r > 0) return r; r = filter_device(conf->blist_device, conf->elist_device, pp->vendor_id, pp->product_id, pp->dev); if (r > 0) return r; r = filter_protocol(conf->blist_protocol, conf->elist_protocol, pp); if (r > 0) return r; r = filter_wwid(conf->blist_wwid, conf->elist_wwid, pp->wwid, pp->dev); return r; } int filter_property(const struct config *conf, struct udev_device *udev, int lvl, const char *uid_attribute) { const char *devname = udev_device_get_sysname(udev); struct udev_list_entry *list_entry; const char *env = NULL; int r = MATCH_NOTHING; if (udev) { /* * This is the inverse of the 'normal' matching; * the environment variable _has_ to match. * But only if the uid_attribute used for determining the WWID * of the path is is present in the environment * (uid_attr_seen). If this is not the case, udev probably * just failed to access the device, which should not cause the * device to be blacklisted (it won't be used by multipath * anyway without WWID). * Likewise, if no uid attribute is defined, udev-based WWID * determination is effectively off, and devices shouldn't be * blacklisted by missing properties (check_missing_prop). */ bool check_missing_prop = uid_attribute != NULL && *uid_attribute != '\0'; bool uid_attr_seen = false; r = MATCH_PROPERTY_BLIST_MISSING; udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev)) { env = udev_list_entry_get_name(list_entry); if (!env) continue; if (check_missing_prop && !strcmp(env, uid_attribute)) uid_attr_seen = true; if (match_reglist(conf->elist_property, env)) { r = MATCH_PROPERTY_BLIST_EXCEPT; break; } if (match_reglist(conf->blist_property, env)) { r = MATCH_PROPERTY_BLIST; break; } env = NULL; } if (r == MATCH_PROPERTY_BLIST_MISSING && (!check_missing_prop || !uid_attr_seen)) r = MATCH_NOTHING; } log_filter(devname, NULL, NULL, NULL, env, NULL, r, lvl); return r; } static void free_ble(struct blentry *ble) { if (!ble) return; regfree(&ble->regex); free(ble->str); free(ble); } void free_blacklist (vector blist) { struct blentry * ble; int i; if (!blist) return; vector_foreach_slot (blist, ble, i) { free_ble(ble); } vector_free(blist); } void merge_blacklist(vector blist) { struct blentry *bl1, *bl2; int i, j; vector_foreach_slot(blist, bl1, i) { j = i + 1; vector_foreach_slot_after(blist, bl2, j) { if (!bl1->str || !bl2->str || strcmp(bl1->str, bl2->str)) continue; condlog(3, "%s: duplicate blist entry section for %s", __func__, bl1->str); free_ble(bl2); vector_del_slot(blist, j); j--; } } } static void free_ble_device(struct blentry_device *ble) { if (ble) { if (ble->vendor) { regfree(&ble->vendor_reg); free(ble->vendor); } if (ble->product) { regfree(&ble->product_reg); free(ble->product); } free(ble); } } void free_blacklist_device (vector blist) { struct blentry_device * ble; int i; if (!blist) return; vector_foreach_slot (blist, ble, i) { free_ble_device(ble); } vector_free(blist); } void merge_blacklist_device(vector blist) { struct blentry_device *bl1, *bl2; int i, j; vector_foreach_slot(blist, bl1, i) { if (!bl1->vendor && !bl1->product) { free_ble_device(bl1); vector_del_slot(blist, i); i--; } } vector_foreach_slot(blist, bl1, i) { j = i + 1; vector_foreach_slot_after(blist, bl2, j) { if ((!bl1->vendor && bl2->vendor) || (bl1->vendor && !bl2->vendor) || (bl1->vendor && bl2->vendor && strcmp(bl1->vendor, bl2->vendor))) continue; if ((!bl1->product && bl2->product) || (bl1->product && !bl2->product) || (bl1->product && bl2->product && strcmp(bl1->product, bl2->product))) continue; condlog(3, "%s: duplicate blist entry section for %s:%s", __func__, bl1->vendor, bl1->product); free_ble_device(bl2); vector_del_slot(blist, j); j--; } } } multipath-tools-0.11.1/libmultipath/blacklist.h000066400000000000000000000033271475246302400215720ustar00rootroot00000000000000#ifndef BLACKLIST_H_INCLUDED #define BLACKLIST_H_INCLUDED #include #include #define MATCH_NOTHING 0 #define MATCH_WWID_BLIST 1 #define MATCH_DEVICE_BLIST 2 #define MATCH_DEVNODE_BLIST 3 #define MATCH_PROPERTY_BLIST 4 #define MATCH_PROPERTY_BLIST_MISSING 5 #define MATCH_PROTOCOL_BLIST 6 #define MATCH_WWID_BLIST_EXCEPT -MATCH_WWID_BLIST #define MATCH_DEVICE_BLIST_EXCEPT -MATCH_DEVICE_BLIST #define MATCH_DEVNODE_BLIST_EXCEPT -MATCH_DEVNODE_BLIST #define MATCH_PROPERTY_BLIST_EXCEPT -MATCH_PROPERTY_BLIST #define MATCH_PROTOCOL_BLIST_EXCEPT -MATCH_PROTOCOL_BLIST struct blentry { char * str; regex_t regex; bool invert; int origin; }; struct blentry_device { char * vendor; char * product; regex_t vendor_reg; regex_t product_reg; bool vendor_invert; bool product_invert; int origin; }; int setup_default_blist (struct config *); int alloc_ble_device (vector); int filter_devnode (const struct vector_s *, const struct vector_s *, const char *); int filter_wwid (const struct vector_s *, const struct vector_s *, const char *, const char *); int filter_device (const struct vector_s *, const struct vector_s *, const char *, const char *, const char *); int filter_path (const struct config *, const struct path *); int filter_property(const struct config *, struct udev_device *, int, const char*); int filter_protocol(const struct vector_s *, const struct vector_s *, const struct path *); int store_ble (vector, const char *, int); int set_ble_device (vector, const char *, const char *, int); void free_blacklist (vector); void free_blacklist_device (vector); void merge_blacklist(vector); void merge_blacklist_device(vector); #endif /* BLACKLIST_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/byteorder.h000066400000000000000000000021341475246302400216140ustar00rootroot00000000000000#ifndef BYTEORDER_H_INCLUDED #define BYTEORDER_H_INCLUDED #ifdef __linux__ # include # include #else # error unsupported #endif #if BYTE_ORDER == LITTLE_ENDIAN # define le16_to_cpu(x) (uint16_t)(x) # define be16_to_cpu(x) bswap_16(x) # define le32_to_cpu(x) (uint32_t)(x) # define le64_to_cpu(x) (uint64_t)(x) # define be32_to_cpu(x) bswap_32(x) # define be64_to_cpu(x) bswap_64(x) #elif BYTE_ORDER == BIG_ENDIAN # define le16_to_cpu(x) bswap_16(x) # define be16_to_cpu(x) (uint16_t)(x) # define le32_to_cpu(x) bswap_32(x) # define le64_to_cpu(x) bswap_64(x) # define be32_to_cpu(x) (uint32_t)(x) # define be64_to_cpu(x) (uint64_t)(x) #else # error unsupported #endif #define cpu_to_le16(x) le16_to_cpu(x) #define cpu_to_be16(x) be16_to_cpu(x) #define cpu_to_le32(x) le32_to_cpu(x) #define cpu_to_be32(x) be32_to_cpu(x) #define cpu_to_le64(x) le64_to_cpu(x) #define cpu_to_be64(x) be64_to_cpu(x) struct be64 { uint64_t _v; }; #define get_be64(x) be64_to_cpu((x)._v) #define put_be64(x, y) do { (x)._v = cpu_to_be64(y); } while (0) #endif /* BYTEORDER_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers.c000066400000000000000000000243111475246302400214000ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "debug.h" #include "checkers.h" #include "vector.h" #include "util.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", [PATH_DOWN] = "down", [PATH_UP] = "up", [PATH_SHAKY] = "shaky", [PATH_GHOST] = "ghost", [PATH_PENDING] = "pending", [PATH_TIMEOUT] = "timeout", [PATH_REMOVED] = "removed", [PATH_DELAYED] = "delayed", }; static LIST_HEAD(checkers); const char *checker_state_name(int i) { if (i < 0 || i >= PATH_MAX_STATE) { condlog (2, "invalid state index = %d", i); return INVALID; } return checker_state_names[i]; } static struct checker_class *alloc_checker_class(void) { struct checker_class *c; c = calloc(1, sizeof(struct checker_class)); if (c) { INIT_LIST_HEAD(&c->node); uatomic_set(&c->refcount, 1); } return c; } /* Use uatomic_{sub,add}_return() to ensure proper memory barriers */ static int checker_class_ref(struct checker_class *cls) { return uatomic_add_return(&cls->refcount, 1); } static int checker_class_unref(struct checker_class *cls) { return uatomic_sub_return(&cls->refcount, 1); } void free_checker_class(struct checker_class *c) { int cnt; 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) c->reset(); if (c->handle) { if (dlclose(c->handle) != 0) { condlog(0, "Cannot unload checker %s: %s", c->name, dlerror()); } } free(c); } 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); } } static struct checker_class *checker_class_lookup(const char *name) { struct checker_class *c; if (!name || !strlen(name)) return NULL; list_for_each_entry(c, &checkers, node) { if (!strncmp(name, c->name, CHECKER_NAME_LEN)) return c; } return NULL; } void reset_checker_classes(void) { struct checker_class *c; list_for_each_entry(c, &checkers, node) { if (c->reset) c->reset(); } } static struct checker_class *add_checker_class(const char *name) { char libname[LIB_CHECKER_NAMELEN]; struct stat stbuf; struct checker_class *c; char *errstr; c = alloc_checker_class(); if (!c) return NULL; snprintf(c->name, CHECKER_NAME_LEN, "%s", name); if (!strncmp(c->name, NONE, 4)) goto done; snprintf(libname, LIB_CHECKER_NAMELEN, "%s/libcheck%s.so", checker_dir, name); if (stat(libname,&stbuf) < 0) { condlog(0,"Checker '%s' not found in %s", name, checker_dir); goto out; } condlog(3, "loading %s checker", libname); c->handle = dlopen(libname, RTLD_NOW); if (!c->handle) { if ((errstr = dlerror()) != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); goto out; } c->check = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_check"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); if (!c->check) goto out; c->init = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_init"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); 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->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 */ dlerror(); c->free = (void (*)(struct checker *)) dlsym(c->handle, "libcheck_free"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); 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); return NULL; } void checker_set_fd (struct checker * c, int fd) { if (!c) return; c->fd = fd; } void checker_set_sync (struct checker * c) { if (!c || !c->cls) return; c->cls->sync = 1; } void checker_set_async (struct checker * c) { if (!c || !c->cls) return; c->cls->sync = 0; } void checker_enable (struct checker * c) { if (!c) return; c->disable = 0; } void checker_disable (struct checker * c) { if (!c) return; c->disable = 1; c->msgid = CHECKER_MSGID_DISABLED; c->path_state = PATH_UNCHECKED; } int checker_init (struct checker * c, void ** mpctxt_addr) { 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; } void checker_clear (struct checker *c) { memset(c, 0x0, sizeof(struct checker)); c->fd = -1; } void checker_put (struct checker * dst) { struct checker_class *src; if (!dst) return; src = dst->cls; if (src && src->free) src->free(dst); checker_clear(dst); free_checker_class(src); } int checker_get_state(struct checker *c) { 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); return c->path_state; } bool checker_need_wait(struct checker *c) { if (!c || !c->cls || c->path_state != PATH_PENDING || !c->cls->need_wait) return false; return c->cls->need_wait(c); } void checker_check (struct checker * c, int path_state) { if (!c) return; c->msgid = CHECKER_MSGID_NONE; if (c->disable) { c->msgid = CHECKER_MSGID_DISABLED; c->path_state = PATH_UNCHECKED; } else if (!strncmp(c->cls->name, NONE, 4)) { c->path_state = path_state; } else if (c->fd < 0) { c->msgid = CHECKER_MSGID_NO_FD; c->path_state = PATH_WILD; } else { c->path_state = c->cls->check(c); } } const char *checker_name(const struct checker *c) { if (!c || !c->cls) return NULL; return c->cls->name; } int checker_is_sync(const struct checker *c) { return c && c->cls && c->cls->sync; } static const char *generic_msg[CHECKER_GENERIC_MSGTABLE_SIZE] = { [CHECKER_MSGID_NONE] = "", [CHECKER_MSGID_DISABLED] = " is disabled", [CHECKER_MSGID_NO_FD] = " has no usable fd", [CHECKER_MSGID_INVALID] = " provided invalid message id", [CHECKER_MSGID_UP] = " reports path is up", [CHECKER_MSGID_DOWN] = " reports path is down", [CHECKER_MSGID_GHOST] = " reports path is ghost", [CHECKER_MSGID_UNSUPPORTED] = " doesn't support this device", }; const char *checker_message(const struct checker *c) { int id; if (!c || !c->cls || c->msgid < 0 || (c->msgid >= CHECKER_GENERIC_MSGTABLE_SIZE && c->msgid < CHECKER_FIRST_MSGID)) goto bad_id; if (c->msgid < CHECKER_GENERIC_MSGTABLE_SIZE) return generic_msg[c->msgid]; id = c->msgid - CHECKER_FIRST_MSGID; if (id < c->cls->msgtable_size) return c->cls->msgtable[id]; bad_id: 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) return; c->msgid = CHECKER_MSGID_NONE; } void checker_get(struct checker *dst, const char *name) { struct checker_class *src = NULL; if (!dst) return; if (name && strlen(name)) { src = checker_class_lookup(name); if (!src) src = add_checker_class(name); } dst->cls = src; if (!src) return; (void)checker_class_ref(dst->cls); } int init_checkers(void) { #ifdef LOAD_ALL_SHARED_LIBS static const char *const all_checkers[] = { DIRECTIO, TUR, HP_SW, RDAC, EMC_CLARIION, READSECTOR0, CCISS_TUR, }; unsigned int i; for (i = 0; i < ARRAY_SIZE(all_checkers); i++) add_checker_class(all_checkers[i]); #else if (!add_checker_class(DEFAULT_CHECKER)) return 1; #endif return 0; } multipath-tools-0.11.1/libmultipath/checkers.h000066400000000000000000000147151475246302400214140ustar00rootroot00000000000000#ifndef CHECKERS_H_INCLUDED #define CHECKERS_H_INCLUDED #include #include #include "list.h" #include "defaults.h" /* * * Userspace (multipath/multipathd) path states * * PATH_WILD: * - Use: Any checker * - Description: Corner case where "fd < 0" for path fd (see checker_check()), * or where a checker detects an unsupported device * (e.g. wrong checker configured for a given device). * * PATH_UNCHECKED: * - Use: Only in directio checker * - Description: set when fcntl(F_GETFL) fails to return flags or O_DIRECT * not include in flags, or O_DIRECT read fails * - Notes: * - multipathd: uses it to skip over paths in sync_map_state() * - multipath: used in update_paths(); if state==PATH_UNCHECKED, call * pathinfo() * * PATH_DOWN: * - Use: All checkers (directio, emc_clariion, hp_sw, readsector0, tur) * - Description: Either a) SG_IO ioctl failed, or b) check condition on some * SG_IO ioctls that succeed (tur, readsector0 checkers); path is down and * you shouldn't try to send commands to it * * PATH_UP: * - Use: All checkers (directio, emc_clariion, hp_sw, readsector0, tur) * - Description: Path is up and I/O can be sent to it * * PATH_SHAKY: * - Use: Only emc_clariion * - Description: Indicates path not available for "normal" operations * * PATH_GHOST: * - Use: Only hp_sw and rdac * - Description: Indicates a "passive/standby" path on active/passive HP * arrays. These paths will return valid answers to certain SCSI commands * (tur, read_capacity, inquiry, start_stop), but will fail I/O commands. * The path needs an initialization command to be sent to it in order for * I/Os to succeed. * * PATH_PENDING: * - Use: All async checkers * - Description: Indicates a check IO is in flight. * * PATH_TIMEOUT: * - Use: Only tur checker * - Description: Command timed out * * PATH REMOVED: * - Use: All checkers * - Description: Device has been removed from the system * * PATH_DELAYED: * - Use: None of the checkers (returned if the path is being delayed before * reintegration. * - Description: If a path fails after being up for less than * delay_watch_checks checks, when it comes back up again, it will not * be marked as up until it has been up for delay_wait_checks checks. * During this time, it is marked as "delayed" */ enum path_check_state { PATH_WILD = 0, PATH_UNCHECKED, PATH_DOWN, PATH_UP, PATH_SHAKY, PATH_GHOST, PATH_PENDING, PATH_TIMEOUT, PATH_REMOVED, PATH_DELAYED, PATH_MAX_STATE }; #define DIRECTIO "directio" #define TUR "tur" #define HP_SW "hp_sw" #define RDAC "rdac" #define EMC_CLARIION "emc_clariion" #define READSECTOR0 "readsector0" #define CCISS_TUR "cciss_tur" #define NONE "none" #define INVALID "invalid" #define ASYNC_TIMEOUT_SEC 30 /* * strings lengths */ #define CHECKER_NAME_LEN 16 #define CHECKER_MSG_LEN 256 #define CHECKER_DEV_LEN 256 #define LIB_CHECKER_NAMELEN 256 /* * Generic message IDs for use in checkers. */ enum { CHECKER_MSGID_NONE = 0, CHECKER_MSGID_DISABLED, CHECKER_MSGID_NO_FD, CHECKER_MSGID_INVALID, CHECKER_MSGID_UP, CHECKER_MSGID_DOWN, CHECKER_MSGID_GHOST, CHECKER_MSGID_UNSUPPORTED, 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; struct checker { struct checker_class *cls; int fd; unsigned int timeout; 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. */ }; static inline int checker_selected(const struct checker *c) { return c != NULL && c->cls != NULL; } 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 **); void checker_clear (struct checker *); void checker_put (struct checker *); void checker_reset (struct checker *); 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); bool checker_need_wait(struct checker *c); void checker_check (struct checker *, int); int checker_is_sync(const struct checker *); const char *checker_name (const struct checker *); void reset_checker_classes(void); /* * This returns a string that's best prepended with "$NAME checker", * where $NAME is the return value of checker_name(). */ const char *checker_message(const struct checker *); 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_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); bool libcheck_need_wait(struct checker *c); /* * msgid => message map. * * It only needs to be provided if the checker defines specific * message IDs. * Message IDs available to checkers start at CHECKER_FIRST_MSG. * The msgtable array is 0-based, i.e. msgtable[0] is the message * for msgid == CHECKER_FIRST_MSG. * The table ends with a NULL element. */ extern const char *libcheck_msgtable[]; #endif /* CHECKERS_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/000077500000000000000000000000001475246302400212335ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/checkers/Makefile000066400000000000000000000016741475246302400227030ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # TOPDIR = ../.. include ../../Makefile.inc CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) CFLAGS += $(LIB_CFLAGS) LDFLAGS += -L$(multipathdir) -L$(mpathutildir) LIBDEPS = -lmultipath -lmpathutil -laio -lpthread -lrt # If you add or remove a checker also update multipath/multipath.conf.5 LIBS= \ libcheckcciss_tur.so \ libcheckreadsector0.so \ libchecktur.so \ libcheckdirectio.so \ libcheckemc_clariion.so \ libcheckhp_sw.so \ libcheckrdac.so all: $(LIBS) libcheck%.so: %.o $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS) install: $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(plugindir) uninstall: $(Q)for file in $(LIBS); do $(RM) $(DESTDIR)$(plugindir)/$$file; done clean: dep_clean $(Q)$(RM) core *.a *.o *.gz *.so OBJS := $(LIBS:libcheck%.so=%.o) .SECONDARY: $(OBJS) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmultipath/checkers/cciss.h000066400000000000000000000053611475246302400225150ustar00rootroot00000000000000#ifndef CCISS_H_INCLUDED #define CCISS_H_INCLUDED #include #include #define CCISS_IOC_MAGIC 'B' /* * transfer direction */ #define XFER_NONE 0x00 #define XFER_WRITE 0x01 #define XFER_READ 0x02 #define XFER_RSVD 0x03 /* * task attribute */ #define ATTR_UNTAGGED 0x00 #define ATTR_SIMPLE 0x04 #define ATTR_HEADOFQUEUE 0x05 #define ATTR_ORDERED 0x06 #define ATTR_ACA 0x07 /* * cdb type */ #define TYPE_CMD 0x00 #define TYPE_MSG 0x01 #define SENSEINFOBYTES 32 /* * Type defs used in the following structs */ #define BYTE __u8 #define WORD __u16 #define HWORD __u16 #define DWORD __u32 #pragma pack(1) //Command List Structure typedef union { struct { BYTE Dev; BYTE Bus:6; BYTE Mode:2; // b00 } PeripDev; struct { BYTE DevLSB; BYTE DevMSB:6; BYTE Mode:2; // b01 } LogDev; struct { BYTE Dev:5; BYTE Bus:3; BYTE Targ:6; BYTE Mode:2; // b10 } LogUnit; } SCSI3Addr_struct; typedef struct { DWORD TargetId:24; DWORD Bus:6; DWORD Mode:2; SCSI3Addr_struct Target[2]; //2 level target device addr } PhysDevAddr_struct; typedef struct { DWORD VolId:30; DWORD Mode:2; BYTE reserved[4]; } LogDevAddr_struct; typedef union { BYTE LunAddrBytes[8]; SCSI3Addr_struct SCSI3Lun[4]; PhysDevAddr_struct PhysDev; LogDevAddr_struct LogDev; } LUNAddr_struct; typedef struct { BYTE CDBLen; struct { BYTE Type:3; BYTE Attribute:3; BYTE Direction:2; } Type; HWORD Timeout; BYTE CDB[16]; } RequestBlock_struct; typedef union { struct { BYTE Reserved[3]; BYTE Type; DWORD ErrorInfo; } Common_Info; struct{ BYTE Reserved[2]; BYTE offense_size;//size of offending entry BYTE offense_num; //byte # of offense 0-base DWORD offense_value; } Invalid_Cmd; } MoreErrInfo_struct; typedef struct { BYTE ScsiStatus; BYTE SenseLen; HWORD CommandStatus; DWORD ResidualCnt; MoreErrInfo_struct MoreErrInfo; BYTE SenseInfo[SENSEINFOBYTES]; } ErrorInfo_struct; #pragma pack() typedef struct { LUNAddr_struct LUN_info; RequestBlock_struct Request; ErrorInfo_struct error_info; WORD buf_size; /* size in bytes of the buf */ BYTE *buf; } IOCTL_Command_struct; typedef struct { __u32 LunID; int num_opens; /* number of opens on the logical volume */ int num_parts; /* number of partitions configured on logvol */ } LogvolInfo_struct; #define CCISS_PASSTHRU _IOWR(CCISS_IOC_MAGIC, 11, IOCTL_Command_struct) #define CCISS_GETLUNINFO _IOR(CCISS_IOC_MAGIC, 17, LogvolInfo_struct) int cciss_init( struct checker *); void cciss_free (struct checker * c); int cciss_tur( struct checker *); #endif multipath-tools-0.11.1/libmultipath/checkers/cciss_tur.c000066400000000000000000000071251475246302400234020ustar00rootroot00000000000000/* ***************************************************************************** * * * (C) Copyright 2007 Hewlett-Packard Development Company, L.P * * * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the Free* * Software Foundation; either version 2 of the License, or (at your option)* * any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along * * with this program. If not, see . * * * ***************************************************************************** */ /* * This program originally derived from and inspired by * Christophe Varoqui's tur.c, part of libchecker. */ #include #include #include #include #include #include #include #include #include #include "checkers.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 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; ret = -1; goto out; } rc = ioctl(c->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; ret = PATH_DOWN; goto out; } else { lun = lvi.LunID; } memset(&cic, 0, sizeof(cic)); cic.LUN_info.LogDev.VolId = lun & 0x3FFFFFFF; cic.LUN_info.LogDev.Mode = 0x01; /* logical volume addressing */ cic.Request.CDBLen = 6; /* need to try just 2 bytes here */ cic.Request.Type.Type = TYPE_CMD; // It is a command. cic.Request.Type.Attribute = ATTR_SIMPLE; cic.Request.Type.Direction = XFER_NONE; cic.Request.Timeout = 0; cic.Request.CDB[0] = 0; cic.Request.CDB[1] = 0; cic.Request.CDB[2] = 0; cic.Request.CDB[3] = 0; cic.Request.CDB[4] = 0; cic.Request.CDB[5] = 0; rc = ioctl(c->fd, CCISS_PASSTHRU, &cic); if (rc < 0) { fprintf(stderr, "cciss TUR failed: %s\n", strerror(errno)); c->msgid = CHECKER_MSGID_DOWN; ret = PATH_DOWN; goto out; } if ((cic.error_info.CommandStatus | cic.error_info.ScsiStatus )) { c->msgid = CHECKER_MSGID_DOWN; ret = PATH_DOWN; goto out; } c->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); } multipath-tools-0.11.1/libmultipath/checkers/directio.c000066400000000000000000000237051475246302400232100ustar00rootroot00000000000000/* * Copyright (c) 2005 Hannes Reinecke, Suse */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "debug.h" #include "time-util.h" #define AIO_GROUP_SIZE 1024 /* Note: This checker type relies on the fact that only one checker can be run * at a time, since multiple checkers share the same aio_group, and must be * able to modify other checker's async_reqs. If multiple checkers become able * to be run at the same time, this checker will need to add locking, and * probably polling on event fds, to deal with that */ struct aio_group { struct list_head node; int holders; io_context_t ioctx; struct list_head orphans; }; struct async_req { struct iocb io; unsigned int blksize; unsigned char * buf; struct list_head node; int state; /* PATH_REMOVED means this is an orphan */ }; static LIST_HEAD(aio_grp_list); enum { MSG_DIRECTIO_UNKNOWN = CHECKER_FIRST_MSGID, MSG_DIRECTIO_PENDING, MSG_DIRECTIO_BLOCKSIZE, }; #define IDX_(x) (MSG_DIRECTIO_##x - CHECKER_FIRST_MSGID) const char *libcheck_msgtable[] = { [IDX_(UNKNOWN)] = " is not available", [IDX_(PENDING)] = " is waiting on aio", [IDX_(BLOCKSIZE)] = " cannot get blocksize, set default", NULL, }; #define LOG(prio, fmt, args...) condlog(prio, "directio: " fmt, ##args) struct directio_context { struct timespec timeout; int reset_flags; struct aio_group *aio_grp; struct async_req *req; bool checked_state; }; static bool is_running(struct directio_context *ct) { return (ct->timeout.tv_sec != 0 || ct->timeout.tv_nsec != 0); } static void start_running(struct directio_context *ct, int timeout_secs) { get_monotonic_time(&ct->timeout); ct->timeout.tv_sec += timeout_secs; } static void stop_running(struct directio_context *ct) { ct->timeout.tv_sec = ct->timeout.tv_nsec = 0; } static struct aio_group * add_aio_group(void) { struct aio_group *aio_grp; int rc; aio_grp = malloc(sizeof(struct aio_group)); if (!aio_grp) return NULL; memset(aio_grp, 0, sizeof(struct aio_group)); INIT_LIST_HEAD(&aio_grp->orphans); if ((rc = io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx)) != 0) { LOG(1, "io_setup failed"); if (rc == -EAGAIN) LOG(1, "global number of io events too small. Increase fs.aio-max-nr with sysctl"); free(aio_grp); return NULL; } list_add(&aio_grp->node, &aio_grp_list); return aio_grp; } static int set_aio_group(struct directio_context *ct) { struct aio_group *aio_grp = NULL; list_for_each_entry(aio_grp, &aio_grp_list, node) if (aio_grp->holders < AIO_GROUP_SIZE) goto found; aio_grp = add_aio_group(); if (!aio_grp) { ct->aio_grp = NULL; return -1; } found: aio_grp->holders++; ct->aio_grp = aio_grp; return 0; } static void remove_aio_group(struct aio_group *aio_grp) { struct async_req *req, *tmp; io_destroy(aio_grp->ioctx); list_for_each_entry_safe(req, tmp, &aio_grp->orphans, node) { list_del(&req->node); free(req->buf); free(req); } list_del(&aio_grp->node); free(aio_grp); } /* If an aio_group is completely full of orphans, then no checkers can * use it, which means that no checkers can clear out the orphans. To * avoid keeping the useless group around, simply remove the * group */ static void check_orphaned_group(struct aio_group *aio_grp) { int count = 0; struct list_head *item; if (aio_grp->holders < AIO_GROUP_SIZE) return; list_for_each(item, &aio_grp->orphans) count++; if (count >= AIO_GROUP_SIZE) remove_aio_group(aio_grp); } void libcheck_reset (void) { struct aio_group *aio_grp, *tmp; list_for_each_entry_safe(aio_grp, tmp, &aio_grp_list, node) remove_aio_group(aio_grp); } int libcheck_init (struct checker * c) { unsigned long pgsize = getpagesize(); struct directio_context * ct; struct async_req *req = NULL; long flags; ct = malloc(sizeof(struct directio_context)); if (!ct) return 1; memset(ct, 0, sizeof(struct directio_context)); if (set_aio_group(ct) < 0) goto out; req = malloc(sizeof(struct async_req)); if (!req) { goto out; } memset(req, 0, sizeof(struct async_req)); INIT_LIST_HEAD(&req->node); if (ioctl(c->fd, BLKBSZGET, &req->blksize) < 0) { c->msgid = MSG_DIRECTIO_BLOCKSIZE; req->blksize = 4096; } if (req->blksize > 4096) { /* * Sanity check for DASD; BSZGET is broken */ req->blksize = 4096; } if (!req->blksize) goto out; if (posix_memalign((void **)&req->buf, pgsize, req->blksize) != 0) goto out; flags = fcntl(c->fd, F_GETFL); if (flags < 0) goto out; if (!(flags & O_DIRECT)) { flags |= O_DIRECT; if (fcntl(c->fd, F_SETFL, flags) < 0) goto out; ct->reset_flags = 1; } /* Successfully initialized, return the context. */ ct->req = req; c->context = (void *) ct; return 0; out: if (req) { if (req->buf) free(req->buf); free(req); } if (ct->aio_grp) ct->aio_grp->holders--; free(ct); return 1; } void libcheck_free (struct checker * c) { struct directio_context * ct = (struct directio_context *)c->context; struct io_event event; long flags; if (!ct) return; if (ct->reset_flags) { if ((flags = fcntl(c->fd, F_GETFL)) >= 0) { int ret __attribute__ ((unused)); flags &= ~O_DIRECT; /* No point in checking for errors */ ret = fcntl(c->fd, F_SETFL, flags); } } if (is_running(ct) && ct->req->state != PATH_PENDING) stop_running(ct); if (!is_running(ct)) { free(ct->req->buf); free(ct->req); ct->aio_grp->holders--; } else { /* Currently a no-op */ io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); ct->req->state = PATH_REMOVED; list_add(&ct->req->node, &ct->aio_grp->orphans); check_orphaned_group(ct->aio_grp); } free(ct); c->context = NULL; } static int get_events(struct aio_group *aio_grp, struct timespec *timeout) { struct io_event events[128]; int i, nr, got_events = 0; struct timespec zero_timeout = { .tv_sec = 0, }; struct timespec *timep = timeout; do { nr = io_getevents(aio_grp->ioctx, 1, 128, events, timep); got_events |= (nr > 0); for (i = 0; i < nr; i++) { struct async_req *req = container_of(events[i].obj, struct async_req, io); LOG(4, "io finished %lu/%lu", events[i].res, events[i].res2); /* got an orphaned request */ if (req->state == PATH_REMOVED) { list_del(&req->node); free(req->buf); free(req); aio_grp->holders--; } else req->state = (events[i].res == req->blksize) ? PATH_UP : PATH_DOWN; } timep = &zero_timeout; } while (nr == 128); /* assume there are more events and try again */ if (nr < 0) LOG(4, "async io getevents returned %s", strerror(-nr)); return got_events; } static void check_pending(struct directio_context *ct, struct timespec timeout) { int r; struct timespec endtime, currtime; ct->checked_state = true; get_monotonic_time(&endtime); endtime.tv_sec += timeout.tv_sec; endtime.tv_nsec += timeout.tv_nsec; normalize_timespec(&endtime); while(1) { r = get_events(ct->aio_grp, &timeout); if (ct->req->state != PATH_PENDING) { stop_running(ct); return; } else if (r == 0 || (timeout.tv_sec == 0 && timeout.tv_nsec == 0)) return; get_monotonic_time(&currtime); timespecsub(&endtime, &currtime, &timeout); if (timeout.tv_sec < 0) timeout.tv_sec = timeout.tv_nsec = 0; } } static int check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) { struct stat sb; int rc; struct io_event event; struct timespec timeout = { .tv_sec = timeout_secs }; if (fstat(fd, &sb) == 0) { LOG(4, "called for %x", (unsigned) sb.st_rdev); } if (sync > 0) LOG(4, "called in synchronous mode"); if (is_running(ct)) { ct->checked_state = true; if (ct->req->state != PATH_PENDING) { stop_running(ct); return ct->req->state; } } else { struct iocb *ios[1] = { &ct->req->io }; LOG(4, "starting new request"); memset(&ct->req->io, 0, sizeof(struct iocb)); io_prep_pread(&ct->req->io, fd, ct->req->buf, ct->req->blksize, 0); ct->req->state = PATH_PENDING; if ((rc = io_submit(ct->aio_grp->ioctx, 1, ios)) != 1) { LOG(3, "io_submit error %i", -rc); return PATH_UNCHECKED; } start_running(ct, timeout_secs); ct->checked_state = false; } if (!sync) return PATH_PENDING; check_pending(ct, timeout); if (ct->req->state != PATH_PENDING) return ct->req->state; LOG(3, "abort check on timeout"); io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); return PATH_DOWN; } static void set_msgid(struct checker *c, int state) { switch (state) { case PATH_UNCHECKED: c->msgid = MSG_DIRECTIO_UNKNOWN; break; case PATH_DOWN: c->msgid = CHECKER_MSGID_DOWN; break; case PATH_UP: c->msgid = CHECKER_MSGID_UP; break; case PATH_PENDING: c->msgid = MSG_DIRECTIO_PENDING; break; default: break; } } bool libcheck_need_wait(struct checker *c) { struct directio_context *ct = (struct directio_context *)c->context; return (ct && is_running(ct) && ct->req->state == PATH_PENDING && !ct->checked_state); } int libcheck_pending(struct checker *c) { int rc; struct io_event event; struct directio_context *ct = (struct directio_context *)c->context; struct timespec no_wait = { .tv_sec = 0 }; /* The if path checker isn't running, just return the exiting value. */ if (!ct || !is_running(ct)) { rc = c->path_state; goto out; } if (ct->req->state == PATH_PENDING) check_pending(ct, no_wait); else stop_running(ct); rc = ct->req->state; if (rc == PATH_PENDING) { struct timespec now; get_monotonic_time(&now); if (timespeccmp(&now, &ct->timeout) > 0) { LOG(3, "abort check on timeout"); io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); rc = PATH_DOWN; } else LOG(4, "async io pending"); } out: set_msgid(c, rc); return rc; } int libcheck_check (struct checker * c) { int ret; struct directio_context * ct = (struct directio_context *)c->context; if (!ct) return PATH_UNCHECKED; ret = check_state(c->fd, ct, checker_is_sync(c), c->timeout); set_msgid(c, ret); return ret; } multipath-tools-0.11.1/libmultipath/checkers/directio.h000066400000000000000000000003111475246302400232010ustar00rootroot00000000000000#ifndef DIRECTIO_H_INCLUDED #define DIRECTIO_H_INCLUDED int directio (struct checker *); int directio_init (struct checker *); void directio_free (struct checker *); #endif /* DIRECTIO_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/emc_clariion.c000066400000000000000000000201231475246302400240210ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Lars Marowsky-Bree */ #include #include #include #include #include #include #include #include #include #include "sg_include.h" #include "libsg.h" #include "checkers.h" #include "debug.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 #define HEAVY_CHECK_COUNT 10 #define SCSI_COMMAND_TERMINATED 0x22 #define SCSI_CHECK_CONDITION 0x2 #define RECOVERED_ERROR 0x01 #define ILLEGAL_REQUEST 0x05 #define SG_ERR_DRIVER_SENSE 0x08 /* * Mechanism to track CLARiiON inactive snapshot LUs. * This is done so that we can fail passive paths * to an inactive snapshot LU even though since a * 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 SET_INACTIVE_SNAP(c) if (c->mpcontext) \ ((struct emc_clariion_checker_LU_context *)\ (*c->mpcontext))->inactive_snap = 1 #define CLR_INACTIVE_SNAP(c) if (c->mpcontext) \ ((struct emc_clariion_checker_LU_context *)\ (*c->mpcontext))->inactive_snap = 0 enum { MSG_CLARIION_QUERY_FAILED = CHECKER_FIRST_MSGID, MSG_CLARIION_QUERY_ERROR, MSG_CLARIION_PATH_CONFIG, MSG_CLARIION_UNIT_REPORT, MSG_CLARIION_PATH_NOT_AVAIL, MSG_CLARIION_LUN_UNBOUND, MSG_CLARIION_WWN_CHANGED, MSG_CLARIION_READ_ERROR, MSG_CLARIION_PASSIVE_GOOD, }; #define IDX_(x) (MSG_CLARIION_ ## x - CHECKER_FIRST_MSGID) const char *libcheck_msgtable[] = { [IDX_(QUERY_FAILED)] = ": sending query command failed", [IDX_(QUERY_ERROR)] = ": query command indicates error", [IDX_(PATH_CONFIG)] = ": Path not correctly configured for failover", [IDX_(UNIT_REPORT)] = ": Path unit report page in unknown format", [IDX_(PATH_NOT_AVAIL)] = ": Path not available for normal operations", [IDX_(LUN_UNBOUND)] = ": Logical Unit is unbound or LUNZ", [IDX_(WWN_CHANGED)] = ": Logical Unit WWN has changed", [IDX_(READ_ERROR)] = ": Read error", [IDX_(PASSIVE_GOOD)] = ": Active path is healthy", NULL, }; struct emc_clariion_checker_path_context { char wwn[16]; unsigned wwn_set; }; struct emc_clariion_checker_LU_context { int inactive_snap; }; void hexadecimal_to_ascii(char * wwn, char *wwnstr) { int i,j, nbl; for (i=0,j=0;i<16;i++) { wwnstr[j++] = ((nbl = ((wwn[i]&0xf0) >> 4)) <= 9) ? '0' + nbl : 'a' + (nbl - 10); wwnstr[j++] = ((nbl = (wwn[i]&0x0f)) <= 9) ? '0' + nbl : 'a' + (nbl - 10); } 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) { /* * Allocate and initialize the multi-path global context. */ if (c->mpcontext && *c->mpcontext == NULL) { void * mpctxt = malloc(sizeof(int)); if (!mpctxt) return 1; *c->mpcontext = mpctxt; CLR_INACTIVE_SNAP(c); } return 0; } void libcheck_free (struct checker * c) { free(c->context); } int libcheck_check (struct checker * c) { 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; char wwnstr[33]; int ret; int retry_emc = 5; retry: memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_buffer, 0, 128); memset(sb, 0, SENSE_BUFF_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = sizeof (sense_buffer); io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; io_hdr.timeout = c->timeout * 1000; io_hdr.pack_id = 0; if (ioctl(c->fd, SG_IO, &io_hdr) < 0) { if (errno == ENOTTY) { c->msgid = CHECKER_MSGID_UNSUPPORTED; return PATH_WILD; } c->msgid = MSG_CLARIION_QUERY_FAILED; return PATH_DOWN; } if (io_hdr.info & SG_INFO_OK_MASK) { switch (io_hdr.host_status) { case DID_BUS_BUSY: case DID_ERROR: case DID_SOFT_ERROR: case DID_TRANSPORT_DISRUPTED: /* Transport error, retry */ if (--retry_emc) goto retry; break; default: break; } } if (SCSI_CHECK_CONDITION == io_hdr.status || SCSI_COMMAND_TERMINATED == io_hdr.status || SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status)) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { unsigned char *sbp = io_hdr.sbp; int sense_key; if (sbp[0] & 0x2) sense_key = sbp[1] & 0xf; else sense_key = sbp[2] & 0xf; if (sense_key == ILLEGAL_REQUEST) { c->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; return PATH_DOWN; } } } 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; 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; return PATH_DOWN; } if ( /* Effective initiator type */ sense_buffer[27] != 0x03 /* * Failover mode should be set to 1 (PNR failover mode) * or 4 (ALUA failover mode). */ || (((sense_buffer[28] & 0x07) != 0x04) && ((sense_buffer[28] & 0x07) != 0x06)) /* Arraycommpath should be set to 1 */ || (sense_buffer[30] & 0x04) != 0x04) { c->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; 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; return PATH_DOWN; } /* * store the LUN WWN there and compare that it indeed did not * change in between, to protect against the path suddenly * pointing somewhere else. */ if (ct->wwn_set) { if (memcmp(ct->wwn, &sense_buffer[10], 16) != 0) { c->msgid = MSG_CLARIION_WWN_CHANGED; return PATH_DOWN; } } else { memcpy(ct->wwn, &sense_buffer[10], 16); ct->wwn_set = 1; } /* * Issue read on active path to determine if inactive snapshot. */ if (sense_buffer[4] == 2) {/* if active path */ unsigned char buf[4096]; memset(buf, 0, 4096); ret = sg_read(c->fd, &buf[0], 4096, sbb = &sb[0], SENSE_BUFF_LEN, c->timeout); if (ret == PATH_DOWN) { hexadecimal_to_ascii(ct->wwn, wwnstr); /* * Check for inactive snapshot LU this way. Must * fail these. */ if (((sbb[2]&0xf) == 5) && (sbb[12] == 0x25) && (sbb[13]==1)) { /* * Do this so that we can fail even the * passive paths which will return * 02/04/03 not 05/25/01 on read. */ SET_INACTIVE_SNAP(c); condlog(3, "emc_clariion_checker: Active " "path to inactive snapshot WWN %s.", wwnstr); } else { condlog(3, "emc_clariion_checker: Read " "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; } } else { c->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); } } else { if (IS_INACTIVE_SNAP(c)) { 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; ret = PATH_UP; /* not ghost */ } } return ret; } multipath-tools-0.11.1/libmultipath/checkers/emc_clariion.h000066400000000000000000000003411475246302400240260ustar00rootroot00000000000000#ifndef EMC_CLARIION_H_INCLUDED #define EMC_CLARIION_H_INCLUDED int emc_clariion (struct checker *); int emc_clariion_init (struct checker *); void emc_clariion_free (struct checker *); #endif /* EMC_CLARIION_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/hp_sw.c000066400000000000000000000066651475246302400225340ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "sg_include.h" #include "unaligned.h" #define TUR_CMD_LEN 6 #define INQUIRY_CMDLEN 6 #define INQUIRY_CMD 0x12 #define SENSE_BUFF_LEN 32 #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define ILLEGAL_REQUEST 0x05 #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) { unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY_CMD, 0, 0, 0, 0, 0 }; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; if (cmddt) inqCmdBlk[1] |= 2; if (evpd) inqCmdBlk[1] |= 1; inqCmdBlk[2] = (unsigned char) pg_op; put_unaligned_be16(mx_resp_len, &inqCmdBlk[3]); memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = mx_resp_len; io_hdr.dxferp = resp; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_b; io_hdr.timeout = timeout * 1000; if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { if (errno == ENOTTY) return PATH_WILD; else return PATH_DOWN; } /* treat SG_ERR here to get rid of sg_err.[ch] */ io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) return PATH_UP; if ((SCSI_CHECK_CONDITION == io_hdr.status) || (SCSI_COMMAND_TERMINATED == io_hdr.status) || (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { int sense_key; unsigned char * sense_buffer = io_hdr.sbp; if (sense_buffer[0] & 0x2) sense_key = sense_buffer[1] & 0xf; else sense_key = sense_buffer[2] & 0xf; if (RECOVERED_ERROR == sense_key) return PATH_UP; else if (ILLEGAL_REQUEST == sense_key) return PATH_WILD; } } return PATH_DOWN; } static int do_tur (int fd, unsigned int timeout) { unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; struct sg_io_hdr io_hdr; unsigned char sense_buffer[32]; memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (turCmdBlk); io_hdr.mx_sb_len = sizeof (sense_buffer); io_hdr.dxfer_direction = SG_DXFER_NONE; io_hdr.cmdp = turCmdBlk; io_hdr.sbp = sense_buffer; io_hdr.timeout = timeout * 1000; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) return 1; if (io_hdr.info & SG_INFO_OK_MASK) return 1; return 0; } int libcheck_check(struct checker * c) { char buff[MX_ALLOC_LEN]; int ret = do_inq(c->fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, c->timeout); if (ret == PATH_WILD) { c->msgid = CHECKER_MSGID_UNSUPPORTED; return ret; } if (ret != PATH_UP) { c->msgid = CHECKER_MSGID_DOWN; return ret; }; if (do_tur(c->fd, c->timeout)) { c->msgid = CHECKER_MSGID_GHOST; return PATH_GHOST; } c->msgid = CHECKER_MSGID_UP; return PATH_UP; } multipath-tools-0.11.1/libmultipath/checkers/hp_sw.h000066400000000000000000000002671475246302400225310ustar00rootroot00000000000000#ifndef HP_SW_H_INCLUDED #define HP_SW_H_INCLUDED int hp_sw (struct checker *); int hp_sw_init (struct checker *); void hp_sw_free (struct checker *); #endif /* HP_SW_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/rdac.c000066400000000000000000000215431475246302400223150ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "debug.h" #include "sg_include.h" #define INQUIRY_CMDLEN 6 #define INQUIRY_CMD 0x12 #define MODE_SENSE_CMD 0x5a #define MODE_SELECT_CMD 0x55 #define MODE_SEN_SEL_CMDLEN 10 #define SENSE_BUFF_LEN 32 #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define ILLEGAL_REQUEST 0x05 #define CURRENT_PAGE_CODE_VALUES 0 #define CHANGEABLE_PAGE_CODE_VALUES 1 #define MSG_RDAC_DOWN " reports path is down" #define MSG_RDAC_DOWN_TYPE(STR) MSG_RDAC_DOWN": "STR #define RTPG_UNAVAILABLE 0x3 #define RTPG_OFFLINE 0xE #define RTPG_TRANSITIONING 0xF #define RTPG_UNAVAIL_NON_RESPONSIVE 0x2 #define RTPG_UNAVAIL_IN_RESET 0x3 #define RTPG_UNAVAIL_CFW_DL1 0x4 #define RTPG_UNAVAIL_CFW_DL2 0x5 #define RTPG_UNAVAIL_QUIESCED 0x6 #define RTPG_UNAVAIL_SERVICE_MODE 0x7 struct control_mode_page { unsigned char header[8]; unsigned char page_code; unsigned char page_len; unsigned char dontcare0[3]; unsigned char tas_bit; unsigned char dontcare1[6]; }; struct rdac_checker_context { void * dummy; }; int libcheck_init (struct checker * c) { unsigned char cmd[MODE_SEN_SEL_CMDLEN]; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; struct control_mode_page current, changeable; int set = 0; memset(cmd, 0, MODE_SEN_SEL_CMDLEN); cmd[0] = MODE_SENSE_CMD; cmd[1] = 0x08; /* DBD bit on */ cmd[2] = 0xA + (CURRENT_PAGE_CODE_VALUES << 6); cmd[8] = (sizeof(struct control_mode_page) & 0xff); memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); memset(¤t, 0, sizeof(struct control_mode_page)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = MODE_SEN_SEL_CMDLEN; io_hdr.mx_sb_len = sizeof(sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = (sizeof(struct control_mode_page) & 0xff); io_hdr.dxferp = ¤t; io_hdr.cmdp = cmd; io_hdr.sbp = sense_b; io_hdr.timeout = c->timeout * 1000; if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* check the TAS bit to see if it is already set */ if ((current.tas_bit >> 6) & 0x1) { set = 1; goto out; } /* get the changeable values */ cmd[2] = 0xA + (CHANGEABLE_PAGE_CODE_VALUES << 6); io_hdr.dxferp = &changeable; memset(&changeable, 0, sizeof(struct control_mode_page)); if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* if TAS bit is not settable exit */ if (((changeable.tas_bit >> 6) & 0x1) == 0) goto out; /* Now go ahead and set it */ memset(cmd, 0, MODE_SEN_SEL_CMDLEN); cmd[0] = MODE_SELECT_CMD; cmd[1] = 0x1; /* set SP bit on */ cmd[8] = (sizeof(struct control_mode_page) & 0xff); /* use the same buffer as current, only set the tas bit */ current.page_code = 0xA; current.page_len = 0xA; current.tas_bit |= (1 << 6); io_hdr.dxfer_direction = SG_DXFER_TO_DEV; io_hdr.dxferp = ¤t; if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* Success */ set = 1; out: if (set == 0) condlog(3, "rdac checker failed to set TAS bit"); 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) { unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY_CMD, 1, 0, 0, 0, 0 }; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; int retry_rdac = 5; retry: inqCmdBlk[2] = (unsigned char) pg_op; inqCmdBlk[4] = (unsigned char) (mx_resp_len & 0xff); memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = mx_resp_len; io_hdr.dxferp = resp; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_b; io_hdr.timeout = timeout * 1000; if (ioctl(sg_fd, SG_IO, &io_hdr) < 0 && errno == ENOTTY) return PATH_WILD; /* treat SG_ERR here to get rid of sg_err.[ch] */ io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) return PATH_UP; /* check if we need to retry this error */ if (io_hdr.info & SG_INFO_OK_MASK) { switch (io_hdr.host_status) { case DID_BUS_BUSY: case DID_ERROR: case DID_SOFT_ERROR: case DID_TRANSPORT_DISRUPTED: /* Transport error, retry */ if (--retry_rdac) goto retry; break; default: break; } } if ((SCSI_CHECK_CONDITION == io_hdr.status) || (SCSI_COMMAND_TERMINATED == io_hdr.status) || (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { int sense_key; unsigned char * sense_buffer = io_hdr.sbp; if (sense_buffer[0] & 0x2) sense_key = sense_buffer[1] & 0xf; else sense_key = sense_buffer[2] & 0xf; if (RECOVERED_ERROR == sense_key) return PATH_UP; else if (ILLEGAL_REQUEST == sense_key) return PATH_WILD; condlog(1, "rdac checker: INQUIRY failed with sense key %02x", sense_key); } } return PATH_DOWN; } struct volume_access_inq { char PQ_PDT; char dontcare0[7]; char avtcvp; char vol_ppp; char aas_cur; char vendor_specific_cur; char aas_alt; char vendor_specific_alt; char dontcare1[34]; }; enum { RDAC_MSGID_NOT_CONN = CHECKER_FIRST_MSGID, RDAC_MSGID_IN_STARTUP, RDAC_MSGID_NON_RESPONSIVE, RDAC_MSGID_IN_RESET, RDAC_MSGID_FW_DOWNLOADING, RDAC_MSGID_QUIESCED, RDAC_MSGID_SERVICE_MODE, RDAC_MSGID_UNAVAILABLE, RDAC_MSGID_INQUIRY_FAILED, }; #define IDX_(x) (RDAC_MSGID_##x - CHECKER_FIRST_MSGID) const char *libcheck_msgtable[] = { [IDX_(NOT_CONN)] = MSG_RDAC_DOWN_TYPE("lun not connected"), [IDX_(IN_STARTUP)] = MSG_RDAC_DOWN_TYPE("ctlr is in startup sequence"), [IDX_(NON_RESPONSIVE)] = MSG_RDAC_DOWN_TYPE("non-responsive to queries"), [IDX_(IN_RESET)] = MSG_RDAC_DOWN_TYPE("ctlr held in reset"), [IDX_(FW_DOWNLOADING)] = MSG_RDAC_DOWN_TYPE("ctlr firmware downloading"), [IDX_(QUIESCED)] = MSG_RDAC_DOWN_TYPE("ctlr quiesced by admin request"), [IDX_(SERVICE_MODE)] = MSG_RDAC_DOWN_TYPE("ctlr is in service mode"), [IDX_(UNAVAILABLE)] = MSG_RDAC_DOWN_TYPE("ctlr is unavailable"), [IDX_(INQUIRY_FAILED)] = MSG_RDAC_DOWN_TYPE("inquiry failed"), NULL, }; static int checker_msg_string(const struct volume_access_inq *inq) { /* lun not connected */ if (((inq->PQ_PDT & 0xE0) == 0x20) || (inq->PQ_PDT & 0x7f)) return RDAC_MSGID_NOT_CONN; /* if no tpg data is available, give the generic path down message */ if (!(inq->avtcvp & 0x10)) return CHECKER_MSGID_DOWN; /* controller is booting up */ if (((inq->aas_cur & 0x0F) == RTPG_TRANSITIONING) && (inq->aas_alt & 0x0F) != RTPG_TRANSITIONING) return RDAC_MSGID_IN_STARTUP; /* if not unavailable, give generic message */ if ((inq->aas_cur & 0x0F) != RTPG_UNAVAILABLE) return CHECKER_MSGID_DOWN; /* target port group unavailable */ switch (inq->vendor_specific_cur) { case RTPG_UNAVAIL_NON_RESPONSIVE: return RDAC_MSGID_NON_RESPONSIVE; case RTPG_UNAVAIL_IN_RESET: return RDAC_MSGID_IN_RESET; case RTPG_UNAVAIL_CFW_DL1: case RTPG_UNAVAIL_CFW_DL2: return RDAC_MSGID_FW_DOWNLOADING; case RTPG_UNAVAIL_QUIESCED: return RDAC_MSGID_QUIESCED; case RTPG_UNAVAIL_SERVICE_MODE: return RDAC_MSGID_SERVICE_MODE; default: return RDAC_MSGID_UNAVAILABLE; } } int libcheck_check(struct checker * c) { 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); if (ret != PATH_UP) { inqfail = 1; goto done; } if (((inq.PQ_PDT & 0xE0) == 0x20) || (inq.PQ_PDT & 0x7f)) { /* LUN not connected*/ ret = PATH_DOWN; goto done; } /* If TPGDE bit set, evaluate TPG information */ if ((inq.avtcvp & 0x10)) { switch (inq.aas_cur & 0x0F) { /* Never use the path if it reports unavailable */ case RTPG_UNAVAILABLE: ret = PATH_DOWN; goto done; /* * If both controllers report transitioning, it * means mode select or STPG is being processed. * * If this controller alone is transitioning, it's * booting and we shouldn't use it yet. */ case RTPG_TRANSITIONING: if ((inq.aas_alt & 0xF) != RTPG_TRANSITIONING) { ret = PATH_DOWN; goto done; } break; } } /* If owner set or ioship mode is enabled return PATH_UP always */ if ((inq.avtcvp & 0x1) || ((inq.avtcvp >> 5) & 0x1)) ret = PATH_UP; else ret = PATH_GHOST; done: switch (ret) { case PATH_WILD: c->msgid = CHECKER_MSGID_UNSUPPORTED; break; case PATH_DOWN: c->msgid = (inqfail ? RDAC_MSGID_INQUIRY_FAILED : checker_msg_string(&inq)); break; case PATH_UP: c->msgid = CHECKER_MSGID_UP; break; case PATH_GHOST: c->msgid = CHECKER_MSGID_GHOST; break; } return ret; } multipath-tools-0.11.1/libmultipath/checkers/rdac.h000066400000000000000000000002561475246302400223200ustar00rootroot00000000000000#ifndef RDAC_H_INCLUDED #define RDAC_H_INCLUDED int rdac(struct checker *); int rdac_init(struct checker *); void rdac_free(struct checker *); #endif /* RDAC_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/readsector0.c000066400000000000000000000012721475246302400236140ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui */ #include #include "checkers.h" #include "libsg.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) { 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); switch (ret) { case PATH_DOWN: c->msgid = CHECKER_MSGID_DOWN; break; case PATH_UP: c->msgid = CHECKER_MSGID_UP; break; default: break; } return ret; } multipath-tools-0.11.1/libmultipath/checkers/readsector0.h000066400000000000000000000003331475246302400236160ustar00rootroot00000000000000#ifndef READSECTOR0_H_INCLUDED #define READSECTOR0_H_INCLUDED int readsector0 (struct checker *); int readsector0_init (struct checker *); void readsector0_free (struct checker *); #endif /* READSECTOR0_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/checkers/tur.c000066400000000000000000000270151475246302400222160ustar00rootroot00000000000000/* * Some code borrowed from sg-utils. * * Copyright (c) 2004 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "debug.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, }; #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) { struct sg_io_hdr io_hdr; unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; unsigned char sense_buffer[32]; int retry_tur = 5; retry: memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(&sense_buffer, 0, 32); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (turCmdBlk); io_hdr.mx_sb_len = sizeof (sense_buffer); io_hdr.dxfer_direction = SG_DXFER_NONE; io_hdr.cmdp = turCmdBlk; io_hdr.sbp = sense_buffer; io_hdr.timeout = timeout * 1000; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) { if (errno == ENOTTY) { *msgid = CHECKER_MSGID_UNSUPPORTED; return PATH_WILD; } *msgid = CHECKER_MSGID_DOWN; return PATH_DOWN; } if ((io_hdr.status & 0x7e) == 0x18) { /* * SCSI-3 arrays might return * reservation conflict on TUR */ *msgid = CHECKER_MSGID_UP; return PATH_UP; } if (io_hdr.info & SG_INFO_OK_MASK) { int key = 0, asc, ascq; switch (io_hdr.host_status) { case DID_OK: case DID_NO_CONNECT: case DID_BAD_TARGET: case DID_ABORT: case DID_TRANSPORT_FAILFAST: break; default: /* Driver error, retry */ if (--retry_tur) goto retry; break; } if (io_hdr.sb_len_wr > 3) { if (io_hdr.sbp[0] == 0x72 || io_hdr.sbp[0] == 0x73) { key = io_hdr.sbp[1] & 0x0f; asc = io_hdr.sbp[2]; ascq = io_hdr.sbp[3]; } else if (io_hdr.sb_len_wr > 13 && ((io_hdr.sbp[0] & 0x7f) == 0x70 || (io_hdr.sbp[0] & 0x7f) == 0x71)) { key = io_hdr.sbp[2] & 0x0f; asc = io_hdr.sbp[12]; ascq = io_hdr.sbp[13]; } } if (key == 0x6) { /* Unit Attention, retry */ if (--retry_tur) goto retry; } else if (key == 0x2) { /* Not Ready */ /* Note: Other ALUA states are either UP or DOWN */ if (asc == 0x04 && ascq == 0x0b) { /* * LOGICAL UNIT NOT ACCESSIBLE, * TARGET PORT IN STANDBY STATE */ *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; return PATH_PENDING; } } *msgid = CHECKER_MSGID_DOWN; return PATH_DOWN; } *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; } multipath-tools-0.11.1/libmultipath/checkers/tur.h000066400000000000000000000002531475246302400222160ustar00rootroot00000000000000#ifndef TUR_H_INCLUDED #define TUR_H_INCLUDED int tur (struct checker *); int tur_init (struct checker *); void tur_free (struct checker *); #endif /* TUR_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/config.c000066400000000000000000000574531475246302400210730ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include #include #include "checkers.h" #include "util.h" #include "debug.h" #include "parser.h" #include "dict.h" #include "hwtable.h" #include "vector.h" #include "structs.h" #include "config.h" #include "blacklist.h" #include "defaults.h" #include "prio.h" #include "devmapper.h" #include "mpath_cmd.h" #include "propsel.h" #include "foreign.h" /* * We don't support re-initialization after * libmultipath_exit(). */ static bool libmultipath_exit_called; static pthread_once_t _init_once = PTHREAD_ONCE_INIT; static pthread_once_t _exit_once = PTHREAD_ONCE_INIT; struct udev *udev; static void _udev_init(void) { if (udev) udev_ref(udev); else udev = udev_new(); if (!udev) condlog(0, "%s: failed to initialize udev", __func__); } static bool _is_libmultipath_initialized(void) { return !libmultipath_exit_called && !!udev; } int libmultipath_init(void) { pthread_once(&_init_once, _udev_init); return !_is_libmultipath_initialized(); } static void _libmultipath_exit(void) { libmultipath_exit_called = true; cleanup_foreign(); cleanup_checkers(); cleanup_prio(); libmp_dm_exit(); udev_unref(udev); } void libmultipath_exit(void) { pthread_once(&_exit_once, _libmultipath_exit); } static struct config internal_config; struct config *libmp_get_multipath_config(void) { if (!internal_config.hwtable) /* not initialized */ return NULL; return &internal_config; } struct config *get_multipath_config(void) __attribute__((weak, alias("libmp_get_multipath_config"))); void libmp_put_multipath_config(void *conf __attribute__((unused))) { /* empty */ } void put_multipath_config(void *conf) __attribute__((weak, alias("libmp_put_multipath_config"))); static int hwe_strmatch (const struct hwentry *hwe1, const struct hwentry *hwe2) { if ((hwe2->vendor && !hwe1->vendor) || (hwe1->vendor && (!hwe2->vendor || strcmp(hwe1->vendor, hwe2->vendor)))) return 1; if ((hwe2->product && !hwe1->product) || (hwe1->product && (!hwe2->product || strcmp(hwe1->product, hwe2->product)))) return 1; if ((hwe2->revision && !hwe1->revision) || (hwe1->revision && (!hwe2->revision || strcmp(hwe1->revision, hwe2->revision)))) return 1; return 0; } static struct hwentry * find_hwe_strmatch (const struct vector_s *hwtable, const struct hwentry *hwe) { int i; struct hwentry *tmp, *ret = NULL; vector_foreach_slot (hwtable, tmp, i) { if (hwe_strmatch(tmp, hwe)) continue; ret = tmp; break; } return ret; } static int hwe_regmatch (const struct hwentry *hwe1, const char *vendor, const char *product, const char *revision) { regex_t vre, pre, rre; int retval = 1; if (hwe1->vendor && regcomp(&vre, hwe1->vendor, REG_EXTENDED|REG_NOSUB)) goto out; if (hwe1->product && regcomp(&pre, hwe1->product, REG_EXTENDED|REG_NOSUB)) goto out_vre; if (hwe1->revision && regcomp(&rre, hwe1->revision, REG_EXTENDED|REG_NOSUB)) goto out_pre; if ((vendor || product || revision) && (!hwe1->vendor || !vendor || !regexec(&vre, vendor, 0, NULL, 0)) && (!hwe1->product || !product || !regexec(&pre, product, 0, NULL, 0)) && (!hwe1->revision || !revision || !regexec(&rre, revision, 0, NULL, 0))) retval = 0; if (hwe1->revision) regfree(&rre); out_pre: if (hwe1->product) regfree(&pre); out_vre: if (hwe1->vendor) regfree(&vre); out: return retval; } static void _log_match(const char *fn, const struct hwentry *h, const char *vendor, const char *product, const char *revision) { condlog(4, "%s: found match /%s:%s:%s/ for '%s:%s:%s'", fn, h->vendor, h->product, h->revision, vendor, product, revision); } #define log_match(h, v, p, r) _log_match(__func__, (h), (v), (p), (r)) int find_hwe (const struct vector_s *hwtable, const char * vendor, const char * product, const char * revision, vector result) { int i, n = 0; struct hwentry *tmp; /* * Search backwards here, and add forward. * User modified entries are attached at the end of * the list, so we have to check them first before * continuing to the generic entries */ vector_reset(result); vector_foreach_slot_backwards (hwtable, tmp, i) { if (hwe_regmatch(tmp, vendor, product, revision)) continue; if (vector_alloc_slot(result)) { vector_set_slot(result, tmp); n++; } log_match(tmp, vendor, product, revision); } condlog(n > 1 ? 3 : 4, "%s: found %d hwtable matches for %s:%s:%s", __func__, n, vendor, product, revision); return n; } struct mpentry *find_mpe(vector mptable, char *wwid) { int i; struct mpentry * mpe; if (!wwid || !*wwid) return NULL; vector_foreach_slot (mptable, mpe, i) if (mpe->wwid && !strcmp(mpe->wwid, wwid)) return mpe; return NULL; } const char *get_mpe_wwid(const struct vector_s *mptable, const char *alias) { int i; struct mpentry * mpe; if (!alias) return NULL; vector_foreach_slot (mptable, mpe, i) if (mpe->alias && strcmp(mpe->alias, alias) == 0) return mpe->wwid; return NULL; } static void free_pctable (vector pctable) { int i; struct pcentry *pce; vector_foreach_slot(pctable, pce, i) free(pce); vector_free(pctable); } void free_hwe (struct hwentry * hwe) { if (!hwe) return; if (hwe->vendor) free(hwe->vendor); if (hwe->product) free(hwe->product); if (hwe->revision) free(hwe->revision); if (hwe->uid_attribute) free(hwe->uid_attribute); if (hwe->features) free(hwe->features); if (hwe->hwhandler) free(hwe->hwhandler); if (hwe->selector) free(hwe->selector); if (hwe->checker_name) free(hwe->checker_name); if (hwe->prio_name) free(hwe->prio_name); if (hwe->prio_args) free(hwe->prio_args); if (hwe->alias_prefix) free(hwe->alias_prefix); if (hwe->bl_product) free(hwe->bl_product); if (hwe->pctable) free_pctable(hwe->pctable); free(hwe); } void free_hwtable (vector hwtable) { int i; struct hwentry * hwe; if (!hwtable) return; vector_foreach_slot (hwtable, hwe, i) free_hwe(hwe); vector_free(hwtable); } void free_mpe (struct mpentry * mpe) { if (!mpe) return; if (mpe->wwid) free(mpe->wwid); if (mpe->selector) free(mpe->selector); if (mpe->uid_attribute) free(mpe->uid_attribute); if (mpe->alias) free(mpe->alias); if (mpe->prio_name) free(mpe->prio_name); if (mpe->prio_args) free(mpe->prio_args); free(mpe); } void free_mptable (vector mptable) { int i; struct mpentry * mpe; if (!mptable) return; vector_foreach_slot (mptable, mpe, i) free_mpe(mpe); vector_free(mptable); } struct mpentry * alloc_mpe (void) { struct mpentry * mpe = (struct mpentry *) calloc(1, sizeof(struct mpentry)); return mpe; } struct hwentry * alloc_hwe (void) { struct hwentry * hwe = (struct hwentry *) calloc(1, sizeof(struct hwentry)); return hwe; } struct pcentry * alloc_pce (void) { struct pcentry *pce = (struct pcentry *) calloc(1, sizeof(struct pcentry)); pce->type = PCE_INVALID; return pce; } static char * set_param_str(const char * str) { char * dst; int len; if (!str) return NULL; len = strlen(str); if (!len) return NULL; dst = (char *)calloc(1, len + 1); if (!dst) return NULL; strcpy(dst, str); return dst; } #define merge_str(s) \ if (!dst->s && src->s && strlen(src->s)) { \ dst->s = src->s; \ src->s = NULL; \ } #define merge_num(s) \ if (!dst->s && src->s) \ dst->s = src->s static void merge_pce(struct pcentry *dst, struct pcentry *src) { merge_num(fast_io_fail); merge_num(dev_loss); merge_num(eh_deadline); } static void merge_hwe (struct hwentry * dst, struct hwentry * src) { char id[SCSI_VENDOR_SIZE+PATH_PRODUCT_SIZE]; merge_str(vendor); merge_str(product); merge_str(revision); merge_str(uid_attribute); merge_str(features); merge_str(hwhandler); merge_str(selector); merge_str(checker_name); merge_str(prio_name); merge_str(prio_args); merge_str(alias_prefix); 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); merge_num(eh_deadline); merge_num(user_friendly_names); merge_num(retain_hwhandler); merge_num(detect_prio); merge_num(detect_checker); merge_num(detect_pgpolicy); merge_num(detect_pgpolicy_use_tpg); merge_num(deferred_remove); merge_num(delay_watch_checks); merge_num(delay_wait_checks); merge_num(skip_kpartx); merge_num(max_sectors_kb); merge_num(ghost_delay); merge_num(all_tg_pt); merge_num(recheck_wwid); merge_num(vpd_vendor_id); merge_num(san_path_err_threshold); merge_num(san_path_err_forget_rate); merge_num(san_path_err_recovery_time); merge_num(marginal_path_err_sample_time); merge_num(marginal_path_err_rate_threshold); merge_num(marginal_path_err_recheck_gap_time); merge_num(marginal_path_double_failed_time); snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product); reconcile_features_with_options(id, &dst->features, &dst->no_path_retry, &dst->retain_hwhandler); } static void merge_mpe(struct mpentry *dst, struct mpentry *src) { merge_str(alias); merge_str(uid_attribute); merge_str(selector); merge_str(features); merge_str(prio_name); merge_str(prio_args); if (dst->prkey_source == PRKEY_SOURCE_NONE && src->prkey_source != PRKEY_SOURCE_NONE) { dst->prkey_source = src->prkey_source; dst->sa_flags = src->sa_flags; memcpy(&dst->reservation_key, &src->reservation_key, sizeof(dst->reservation_key)); } 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); merge_num(deferred_remove); merge_num(delay_watch_checks); merge_num(delay_wait_checks); merge_num(san_path_err_threshold); merge_num(san_path_err_forget_rate); merge_num(san_path_err_recovery_time); merge_num(marginal_path_err_sample_time); merge_num(marginal_path_err_rate_threshold); merge_num(marginal_path_err_recheck_gap_time); merge_num(marginal_path_double_failed_time); merge_num(skip_kpartx); merge_num(max_sectors_kb); merge_num(ghost_delay); merge_num(uid); merge_num(gid); merge_num(mode); } static int wwid_compar(const void *p1, const void *p2) { const char *wwid1 = (*(struct mpentry * const *)p1)->wwid; const char *wwid2 = (*(struct mpentry * const *)p2)->wwid; return strcmp(wwid1, wwid2); } void merge_mptable(vector mptable) { struct mpentry *mp1, *mp2; int i, j; vector_foreach_slot(mptable, mp1, i) { /* drop invalid multipath configs */ if (!mp1->wwid) { condlog(0, "multipaths config section missing wwid"); vector_del_slot(mptable, i--); free_mpe(mp1); continue; } } vector_sort(mptable, wwid_compar); vector_foreach_slot(mptable, mp1, i) { j = i + 1; vector_foreach_slot_after(mptable, mp2, j) { if (strcmp(mp1->wwid, mp2->wwid)) break; condlog(1, "%s: duplicate multipath config section for %s", __func__, mp1->wwid); merge_mpe(mp2, mp1); free_mpe(mp1); vector_del_slot(mptable, i); i--; break; } } } int store_hwe (vector hwtable, struct hwentry * dhwe) { struct hwentry * hwe; if (find_hwe_strmatch(hwtable, dhwe)) return 0; if (!(hwe = alloc_hwe())) return 1; if (!dhwe->vendor || !(hwe->vendor = set_param_str(dhwe->vendor))) goto out; if (!dhwe->product || !(hwe->product = set_param_str(dhwe->product))) goto out; if (dhwe->revision && !(hwe->revision = set_param_str(dhwe->revision))) goto out; if (dhwe->uid_attribute && !(hwe->uid_attribute = set_param_str(dhwe->uid_attribute))) goto out; if (dhwe->features && !(hwe->features = set_param_str(dhwe->features))) goto out; if (dhwe->hwhandler && !(hwe->hwhandler = set_param_str(dhwe->hwhandler))) goto out; if (dhwe->selector && !(hwe->selector = set_param_str(dhwe->selector))) goto out; if (dhwe->checker_name && !(hwe->checker_name = set_param_str(dhwe->checker_name))) goto out; if (dhwe->prio_name && !(hwe->prio_name = set_param_str(dhwe->prio_name))) goto out; if (dhwe->prio_args && !(hwe->prio_args = set_param_str(dhwe->prio_args))) goto out; if (dhwe->alias_prefix && !(hwe->alias_prefix = set_param_str(dhwe->alias_prefix))) goto out; 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; hwe->eh_deadline = dhwe->eh_deadline; hwe->user_friendly_names = dhwe->user_friendly_names; hwe->retain_hwhandler = dhwe->retain_hwhandler; hwe->detect_prio = dhwe->detect_prio; hwe->detect_checker = dhwe->detect_checker; hwe->detect_pgpolicy = dhwe->detect_pgpolicy; hwe->detect_pgpolicy_use_tpg = dhwe->detect_pgpolicy_use_tpg; hwe->ghost_delay = dhwe->ghost_delay; hwe->vpd_vendor_id = dhwe->vpd_vendor_id; if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product))) goto out; if (!vector_alloc_slot(hwtable)) goto out; vector_set_slot(hwtable, hwe); return 0; out: free_hwe(hwe); return 1; } static void validate_pctable(struct hwentry *ovr, int idx, const char *table_desc) { struct pcentry *pce; if (!ovr || !ovr->pctable) return; vector_foreach_slot_after(ovr->pctable, pce, idx) { if (pce->type == PCE_INVALID) { condlog(0, "protocol section in %s missing type", table_desc); vector_del_slot(ovr->pctable, idx--); free(pce); } } if (VECTOR_SIZE(ovr->pctable) == 0) { vector_free(ovr->pctable); ovr->pctable = NULL; } } static void merge_pctable(struct hwentry *ovr) { struct pcentry *pce1, *pce2; int i, j; if (!ovr || !ovr->pctable) return; vector_foreach_slot(ovr->pctable, pce1, i) { j = i + 1; vector_foreach_slot_after(ovr->pctable, pce2, j) { if (pce1->type != pce2->type) continue; merge_pce(pce2,pce1); vector_del_slot(ovr->pctable, i--); free(pce1); break; } } } static void factorize_hwtable (vector hw, int n, const char *table_desc) { struct hwentry *hwe1, *hwe2; int i, j; restart: vector_foreach_slot(hw, hwe1, i) { /* drop invalid device configs */ if (i >= n && (!hwe1->vendor || !hwe1->product)) { condlog(0, "device config in %s missing vendor or product parameter", table_desc); vector_del_slot(hw, i--); free_hwe(hwe1); continue; } j = n > i + 1 ? n : i + 1; vector_foreach_slot_after(hw, hwe2, j) { if (hwe_strmatch(hwe2, hwe1) == 0) { condlog(i >= n ? 1 : 3, "%s: duplicate device section for %s:%s:%s in %s", __func__, hwe1->vendor, hwe1->product, hwe1->revision, table_desc); vector_del_slot(hw, i); merge_hwe(hwe2, hwe1); free_hwe(hwe1); if (i < n) n -= 1; /* * Play safe here; we have modified * the original vector so the outer * vector_foreach_slot() might * become confused. */ goto restart; } } } return; } static struct config *alloc_config (void) { return (struct config *)calloc(1, sizeof(struct config)); } static void _uninit_config(struct config *conf) { void *ptr; int i; if (!conf) conf = &internal_config; if (conf->selector) free(conf->selector); if (conf->uid_attribute) free(conf->uid_attribute); vector_foreach_slot(&conf->uid_attrs, ptr, i) free(ptr); vector_reset(&conf->uid_attrs); if (conf->features) free(conf->features); if (conf->hwhandler) free(conf->hwhandler); if (conf->prio_name) free(conf->prio_name); if (conf->alias_prefix) free(conf->alias_prefix); if (conf->partition_delim) free(conf->partition_delim); if (conf->prio_args) free(conf->prio_args); if (conf->checker_name) free(conf->checker_name); if (conf->enable_foreign) free(conf->enable_foreign); free_blacklist(conf->blist_devnode); free_blacklist(conf->blist_wwid); free_blacklist(conf->blist_property); free_blacklist(conf->blist_protocol); free_blacklist_device(conf->blist_device); free_blacklist(conf->elist_devnode); free_blacklist(conf->elist_wwid); free_blacklist(conf->elist_property); free_blacklist(conf->elist_protocol); free_blacklist_device(conf->elist_device); free_mptable(conf->mptable); free_hwtable(conf->hwtable); free_hwe(conf->overrides); free_keywords(conf->keywords); memset(conf, 0, sizeof(*conf)); } void uninit_config(void) { _uninit_config(&internal_config); } void free_config(struct config *conf) { if (!conf) return; else if (conf == &internal_config) { condlog(0, "ERROR: %s called for internal config. Use uninit_config() instead", __func__); return; } _uninit_config(conf); free(conf); } /* if multipath fails to process the config directory, it should continue, * with just a warning message */ static void process_config_dir(struct config *conf, char *dir) { struct dirent **namelist; struct scandir_result sr; int i, n; char path[LINE_MAX]; int old_hwtable_size; int old_pctable_size = 0; if (dir[0] != '/') { condlog(1, "config_dir '%s' must be a fully qualified path", dir); return; } n = scandir(dir, &namelist, NULL, alphasort); if (n < 0) { if (errno == ENOENT) condlog(3, "No configuration dir '%s'", dir); else condlog(0, "couldn't open configuration dir '%s': %s", dir, strerror(errno)); return; } else if (n == 0) return; sr.di = namelist; sr.n = n; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < n; i++) { char *ext = strrchr(namelist[i]->d_name, '.'); if (!ext || strcmp(ext, ".conf")) continue; old_hwtable_size = VECTOR_SIZE(conf->hwtable); old_pctable_size = conf->overrides ? VECTOR_SIZE(conf->overrides->pctable) : 0; snprintf(path, LINE_MAX, "%s/%s", dir, namelist[i]->d_name); path[LINE_MAX-1] = '\0'; process_file(conf, path); factorize_hwtable(conf->hwtable, old_hwtable_size, namelist[i]->d_name); validate_pctable(conf->overrides, old_pctable_size, namelist[i]->d_name); } pthread_cleanup_pop(1); } static int init_config__ (const char *file, struct config *conf); int init_config(const char *file) { return init_config__(file, &internal_config); } struct config *load_config(const char *file) { struct config *conf = alloc_config(); if (conf && !init_config__(file, conf)) return conf; free(conf); return NULL; } int init_config__ (const char *file, struct config *conf) { if (!conf) conf = &internal_config; /* * Processing the config file will overwrite conf->verbosity if set * When we return, we'll copy the config value back */ conf->verbosity = libmp_verbosity; /* * internal defaults */ get_sys_max_fds(&conf->max_fds); conf->attribute_flags = 0; conf->reassign_maps = DEFAULT_REASSIGN_MAPS; conf->checkint = CHECKINT_UNDEF; conf->max_checkint = 0; conf->force_sync = DEFAULT_FORCE_SYNC; conf->partition_delim = (default_partition_delim != NULL ? strdup(default_partition_delim) : NULL); conf->processed_main_config = 0; conf->find_multipaths = DEFAULT_FIND_MULTIPATHS; conf->uxsock_timeout = DEFAULT_REPLY_TIMEOUT; conf->retrigger_tries = DEFAULT_RETRIGGER_TRIES; conf->retrigger_delay = DEFAULT_RETRIGGER_DELAY; conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; conf->auto_resize = DEFAULT_AUTO_RESIZE; conf->remove_retries = 0; conf->ghost_delay = DEFAULT_GHOST_DELAY; conf->all_tg_pt = DEFAULT_ALL_TG_PT; conf->recheck_wwid = DEFAULT_RECHECK_WWID; /* * preload default hwtable */ conf->hwtable = vector_alloc(); if (!conf->hwtable) goto out; if (setup_default_hwtable(conf->hwtable)) goto out; #ifdef CHECK_BUILTIN_HWTABLE factorize_hwtable(conf->hwtable, 0, "builtin"); #endif /* * read the config file */ conf->keywords = vector_alloc(); init_keywords(conf->keywords); if (filepresent(file)) { int builtin_hwtable_size; builtin_hwtable_size = VECTOR_SIZE(conf->hwtable); if (process_file(conf, file)) { condlog(0, "error parsing config file"); goto out; } factorize_hwtable(conf->hwtable, builtin_hwtable_size, file); validate_pctable(conf->overrides, 0, file); } conf->processed_main_config = 1; process_config_dir(conf, CONFIG_DIR); /* * fill the voids left in the config file */ if (conf->max_checkint == 0) { if (conf->checkint == CHECKINT_UNDEF) conf->checkint = DEFAULT_CHECKINT; conf->max_checkint = (conf->checkint < UINT_MAX / 4 ? conf->checkint * 4 : UINT_MAX); } else if (conf->checkint == CHECKINT_UNDEF) conf->checkint = (conf->max_checkint >= 4 ? conf->max_checkint / 4 : 1); else if (conf->checkint > conf->max_checkint) conf->checkint = conf->max_checkint; condlog(3, "polling interval: %d, max: %d", conf->checkint, conf->max_checkint); /* * make sure that that adjust_int is a multiple of all possible values * of pp->checkint. */ if (conf->max_checkint % conf->checkint == 0) { conf->adjust_int = conf->max_checkint; } else { conf->adjust_int = conf->checkint; while (2 * conf->adjust_int < conf->max_checkint) conf->adjust_int *= 2; conf->adjust_int *= conf->max_checkint; } if (conf->blist_devnode == NULL) { conf->blist_devnode = vector_alloc(); if (!conf->blist_devnode) goto out; } if (conf->blist_wwid == NULL) { conf->blist_wwid = vector_alloc(); if (!conf->blist_wwid) goto out; } if (conf->blist_device == NULL) { conf->blist_device = vector_alloc(); if (!conf->blist_device) goto out; } if (conf->blist_property == NULL) { conf->blist_property = vector_alloc(); if (!conf->blist_property) goto out; } if (conf->blist_protocol == NULL) { conf->blist_protocol = vector_alloc(); if (!conf->blist_protocol) goto out; } if (conf->elist_devnode == NULL) { conf->elist_devnode = vector_alloc(); if (!conf->elist_devnode) goto out; } if (conf->elist_wwid == NULL) { conf->elist_wwid = vector_alloc(); if (!conf->elist_wwid) goto out; } if (conf->elist_device == NULL) { conf->elist_device = vector_alloc(); if (!conf->elist_device) goto out; } if (conf->elist_property == NULL) { conf->elist_property = vector_alloc(); if (!conf->elist_property) goto out; } if (conf->elist_protocol == NULL) { conf->elist_protocol = vector_alloc(); if (!conf->elist_protocol) goto out; } if (setup_default_blist(conf)) goto out; if (conf->mptable == NULL) { conf->mptable = vector_alloc(); if (!conf->mptable) goto out; } merge_pctable(conf->overrides); merge_mptable(conf->mptable); merge_blacklist(conf->blist_devnode); merge_blacklist(conf->blist_property); merge_blacklist(conf->blist_wwid); merge_blacklist_device(conf->blist_device); merge_blacklist(conf->elist_devnode); merge_blacklist(conf->elist_property); merge_blacklist(conf->elist_wwid); merge_blacklist_device(conf->elist_device); libmp_verbosity = conf->verbosity; return 0; out: _uninit_config(conf); return 1; } const char *get_uid_attribute_by_attrs(const struct config *conf, const char *path_dev) { const struct vector_s *uid_attrs = &conf->uid_attrs; int j; char *att, *col; vector_foreach_slot(uid_attrs, att, j) { col = strrchr(att, ':'); if (!col) continue; if (!strncmp(path_dev, att, col - att)) return col + 1; } return NULL; } int parse_uid_attrs(char *uid_attrs, struct config *conf) { vector attrs = &conf->uid_attrs; char *uid_attr_record, *tmp; int ret = 0, count; if (!uid_attrs) return 1; count = get_word(uid_attrs, &uid_attr_record); while (uid_attr_record) { tmp = strchr(uid_attr_record, ':'); if (!tmp) { condlog(2, "invalid record in uid_attrs: %s", uid_attr_record); free(uid_attr_record); ret = 1; } else if (!vector_alloc_slot(attrs)) { free(uid_attr_record); ret = 1; } else vector_set_slot(attrs, uid_attr_record); if (!count) break; uid_attrs += count; count = get_word(uid_attrs, &uid_attr_record); } return ret; } multipath-tools-0.11.1/libmultipath/config.h000066400000000000000000000165561475246302400210770ustar00rootroot00000000000000#ifndef CONFIG_H_INCLUDED #define CONFIG_H_INCLUDED #include #include #include #include #include "byteorder.h" #include "globals.h" #define ORIGIN_DEFAULT 0 #define ORIGIN_CONFIG 1 enum devtypes { DEV_NONE, DEV_DEVT, DEV_DEVNODE, DEV_DEVMAP, DEV_UEVENT }; enum mpath_cmds { CMD_NONE, CMD_CREATE, CMD_DRY_RUN, CMD_LIST_SHORT, CMD_LIST_LONG, CMD_VALID_PATH, CMD_REMOVE_WWID, CMD_RESET_WWIDS, CMD_ADD_WWID, CMD_USABLE_PATHS, CMD_DUMP_CONFIG, CMD_FLUSH_ONE, CMD_FLUSH_ALL, }; enum force_reload_types { FORCE_RELOAD_NONE, FORCE_RELOAD_YES, FORCE_RELOAD_WEAK, }; #define PCE_INVALID -1 struct pcentry { int type; int fast_io_fail; unsigned int dev_loss; int eh_deadline; }; struct hwentry { char * vendor; char * product; char * revision; char * uid_attribute; char * features; char * hwhandler; char * selector; char * checker_name; char * prio_name; char * prio_args; char * alias_prefix; 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; int eh_deadline; int user_friendly_names; int retain_hwhandler; int detect_prio; int detect_checker; int detect_pgpolicy; int detect_pgpolicy_use_tpg; int deferred_remove; int delay_watch_checks; int delay_wait_checks; int san_path_err_threshold; int san_path_err_forget_rate; int san_path_err_recovery_time; int marginal_path_err_sample_time; int marginal_path_err_rate_threshold; int marginal_path_err_recheck_gap_time; int marginal_path_double_failed_time; int skip_kpartx; int max_sectors_kb; int ghost_delay; int all_tg_pt; int vpd_vendor_id; int recheck_wwid; char * bl_product; vector pctable; }; struct mpentry { char * wwid; char * alias; char * uid_attribute; char * selector; char * features; char * prio_name; char * prio_args; int prkey_source; struct be64 reservation_key; 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; int deferred_remove; int delay_watch_checks; int delay_wait_checks; int san_path_err_threshold; int san_path_err_forget_rate; int san_path_err_recovery_time; int marginal_path_err_sample_time; int marginal_path_err_rate_threshold; int marginal_path_err_recheck_gap_time; int marginal_path_double_failed_time; int skip_kpartx; int max_sectors_kb; int ghost_delay; uid_t uid; gid_t gid; mode_t mode; }; struct config { struct rcu_head rcu; int verbosity; 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; int max_fds; int force_reload; int queue_without_daemon; int checker_timeout; int flush_on_last_del; int attribute_flags; int fast_io_fail; unsigned int dev_loss; int eh_deadline; int max_retries; int log_checker_err; int allow_queueing; int allow_usb_devices; int find_multipaths; uid_t uid; gid_t gid; mode_t mode; int reassign_maps; int retain_hwhandler; int detect_prio; int detect_checker; int detect_pgpolicy; int detect_pgpolicy_use_tpg; int force_sync; int deferred_remove; int processed_main_config; int delay_watch_checks; int delay_wait_checks; int san_path_err_threshold; int san_path_err_forget_rate; int san_path_err_recovery_time; int marginal_path_err_sample_time; int marginal_path_err_rate_threshold; int marginal_path_err_recheck_gap_time; int marginal_path_double_failed_time; int uxsock_timeout; int strict_timing; int retrigger_tries; int retrigger_delay; int uev_wait_timeout; int skip_kpartx; int remove_retries; int max_sectors_kb; int ghost_delay; int find_multipaths_timeout; int marginal_pathgroups; int skip_delegate; unsigned int sequence_nr; int recheck_wwid; int auto_resize; char * selector; struct vector_s uid_attrs; char * uid_attribute; char * features; char * hwhandler; char * prio_name; char * prio_args; char * checker_name; char * alias_prefix; char * partition_delim; int prkey_source; int all_tg_pt; struct be64 reservation_key; uint8_t sa_flags; vector keywords; vector mptable; vector hwtable; struct hwentry *overrides; vector blist_devnode; vector blist_wwid; vector blist_device; vector blist_property; vector blist_protocol; vector elist_devnode; vector elist_wwid; vector elist_device; vector elist_property; vector elist_protocol; char *enable_foreign; }; /** * extern variable: udev * * A &struct udev instance used by libmultipath. libmultipath expects * a valid, initialized &struct udev in this variable. * An application can define this variable itself, in which case * the applications's instance will take precedence. * The application can initialize and destroy this variable by * calling libmultipath_init() and libmultipath_exit(), respectively, * whether or not it defines the variable itself. * An application can initialize udev with udev_new() before calling * libmultipath_init(), e.g. if it has to make libudev calls before * libmultipath calls. If an application wants to keep using the * udev variable after calling libmultipath_exit(), it should have taken * an additional reference on it beforehand. This is the case e.g. * after initializing udev with udev_new(). */ extern struct udev *udev; /** * libmultipath_init() - library initialization * * This function initializes libmultipath data structures. * It is light-weight; some other initializations, like device-mapper * initialization, are done lazily when the respective functionality * is required. * * Clean up by libmultipath_exit() when the program terminates. * It is an error to call libmultipath_init() after libmultipath_exit(). * Return: 0 on success, 1 on failure. */ int libmultipath_init(void); /** * libmultipath_exit() - library un-initialization * * This function un-initializes libmultipath data structures. * It is recommended to call this function at program exit. * If the application also calls dm_lib_exit(), it should do so * after libmultipath_exit(). * * Calls to libmultipath_init() after libmultipath_exit() will fail * (in other words, libmultipath can't be re-initialized). * Any other libmultipath calls after libmultipath_exit() may cause * undefined behavior. */ void libmultipath_exit(void); int find_hwe (const struct vector_s *hwtable, const char * vendor, const char * product, const char *revision, vector result); struct mpentry * find_mpe (vector mptable, char * wwid); const char *get_mpe_wwid (const struct vector_s *mptable, const char *alias); struct hwentry * alloc_hwe (void); struct mpentry * alloc_mpe (void); struct pcentry * alloc_pce (void); void free_hwe (struct hwentry * hwe); void free_hwtable (vector hwtable); void free_mpe (struct mpentry * mpe); void free_mptable (vector mptable); int store_hwe (vector hwtable, struct hwentry *); struct config *load_config (const char *file); void free_config (struct config * conf); int init_config(const char *file); void uninit_config(void); struct config *libmp_get_multipath_config(void); void libmp_put_multipath_config(void *); int parse_uid_attrs(char *uid_attrs, struct config *conf); const char *get_uid_attribute_by_attrs(const struct config *conf, const char *path_dev); #endif multipath-tools-0.11.1/libmultipath/configure.c000066400000000000000000001035661475246302400216040ustar00rootroot00000000000000/* * Copyright (c) 2003, 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Patrick Caulfield, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include #include #include #include #include #include "mpath_cmd.h" #include "checkers.h" #include "vector.h" #include "devmapper.h" #include "defaults.h" #include "structs.h" #include "structs_vec.h" #include "dmparser.h" #include "config.h" #include "blacklist.h" #include "propsel.h" #include "discovery.h" #include "debug.h" #include "switchgroup.h" #include "dm-generic.h" #include "print.h" #include "configure.h" #include "pgpolicies.h" #include "dict.h" #include "alias.h" #include "prio.h" #include "util.h" #include "uxsock.h" #include "wwids.h" #include "sysfs.h" #include "io_err_stat.h" /* group paths in pg by host adapter */ int group_by_host_adapter(struct pathgroup *pgp, vector adapters) { struct adapter_group *agp; struct host_group *hgp; struct path *pp, *pp1; char adapter_name1[SLOT_NAME_SIZE]; char adapter_name2[SLOT_NAME_SIZE]; int i, j; int found_hostgroup = 0; while (VECTOR_SIZE(pgp->paths) > 0) { pp = VECTOR_SLOT(pgp->paths, 0); if (sysfs_get_host_adapter_name(pp, adapter_name1)) goto out; /* create a new host adapter group */ agp = alloc_adaptergroup(); if (!agp) goto out; agp->pgp = pgp; strlcpy(agp->adapter_name, adapter_name1, SLOT_NAME_SIZE); store_adaptergroup(adapters, agp); /* create a new host port group */ hgp = alloc_hostgroup(); if (!hgp) goto out; if (store_hostgroup(agp->host_groups, hgp)) goto out; hgp->host_no = pp->sg_id.host_no; agp->num_hosts++; if (store_path(hgp->paths, pp)) goto out; hgp->num_paths++; /* delete path from path group */ vector_del_slot(pgp->paths, 0); /* add all paths belonging to same host adapter */ vector_foreach_slot(pgp->paths, pp1, i) { if (sysfs_get_host_adapter_name(pp1, adapter_name2)) goto out; if (strcmp(adapter_name1, adapter_name2) == 0) { found_hostgroup = 0; vector_foreach_slot(agp->host_groups, hgp, j) { if (hgp->host_no == pp1->sg_id.host_no) { if (store_path(hgp->paths, pp1)) goto out; hgp->num_paths++; found_hostgroup = 1; break; } } if (!found_hostgroup) { /* this path belongs to new host port * within this adapter */ hgp = alloc_hostgroup(); if (!hgp) goto out; if (store_hostgroup(agp->host_groups, hgp)) goto out; agp->num_hosts++; if (store_path(hgp->paths, pp1)) goto out; hgp->host_no = pp1->sg_id.host_no; hgp->num_paths++; } /* delete paths from original path_group * as they are added into adapter group now */ vector_del_slot(pgp->paths, i); i--; } } } return 0; out: /* add back paths into pg as re-ordering failed */ vector_foreach_slot(adapters, agp, i) { vector_foreach_slot(agp->host_groups, hgp, j) { while (VECTOR_SIZE(hgp->paths) > 0) { pp = VECTOR_SLOT(hgp->paths, 0); if (store_path(pgp->paths, pp)) condlog(3, "failed to restore " "path %s into path group", pp->dev); vector_del_slot(hgp->paths, 0); } } } free_adaptergroup(adapters); return 1; } /* re-order paths in pg by alternating adapters and host ports * for optimized selection */ int order_paths_in_pg_by_alt_adapters(struct pathgroup *pgp, vector adapters, int total_paths) { int next_adapter_index = 0; struct adapter_group *agp; struct host_group *hgp; struct path *pp; while (total_paths > 0) { agp = VECTOR_SLOT(adapters, next_adapter_index); if (!agp) { condlog(0, "can't get adapter group %d", next_adapter_index); return 1; } hgp = VECTOR_SLOT(agp->host_groups, agp->next_host_index); if (!hgp) { condlog(0, "can't get host group %d of adapter group %d", next_adapter_index, agp->next_host_index); return 1; } if (!hgp->num_paths) { agp->next_host_index++; agp->next_host_index %= agp->num_hosts; next_adapter_index++; next_adapter_index %= VECTOR_SIZE(adapters); continue; } pp = VECTOR_SLOT(hgp->paths, 0); if (store_path(pgp->paths, pp)) return 1; total_paths--; vector_del_slot(hgp->paths, 0); hgp->num_paths--; agp->next_host_index++; agp->next_host_index %= agp->num_hosts; next_adapter_index++; next_adapter_index %= VECTOR_SIZE(adapters); } /* all paths are added into path_group * in crafted child order */ return 0; } /* round-robin: order paths in path group to alternate * between all host adapters */ int rr_optimize_path_order(struct pathgroup *pgp) { vector adapters; struct path *pp; int total_paths; int i; total_paths = VECTOR_SIZE(pgp->paths); vector_foreach_slot(pgp->paths, pp, i) { if (pp->bus != SYSFS_BUS_SCSI || (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP && pp->sg_id.proto_id != SCSI_PROTOCOL_SAS && pp->sg_id.proto_id != SCSI_PROTOCOL_ISCSI && pp->sg_id.proto_id != SCSI_PROTOCOL_SRP)) { /* return success as default path order * is maintained in path group */ return 0; } } adapters = vector_alloc(); if (!adapters) return 0; /* group paths in path group by host adapters */ if (group_by_host_adapter(pgp, adapters)) { /* already freed adapters */ condlog(3, "Failed to group paths by adapters"); return 0; } /* re-order paths in pg to alternate between adapters and host ports */ if (order_paths_in_pg_by_alt_adapters(pgp, adapters, total_paths)) { condlog(3, "Failed to re-order paths in pg by adapters " "and host ports"); free_adaptergroup(adapters); /* return failure as original paths are * removed form pgp */ return 1; } free_adaptergroup(adapters); return 0; } int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) { struct pathgroup * pgp; struct path *pp; struct config *conf; int i, marginal_pathgroups; char *save_attr; /* * don't bother if devmap size is unknown */ if (mpp->size <= 0) { condlog(3, "%s: devmap size is unknown", mpp->alias); return 1; } if (mpp->disable_queueing && VECTOR_SIZE(mpp->paths) != 0) mpp->disable_queueing = 0; /* Force QUEUE_MODE_BIO for maps with nvme:tcp paths */ vector_foreach_slot(mpp->paths, pp, i) { if (pp->bus == SYSFS_BUS_NVME && pp->sg_id.proto_id == NVME_PROTOCOL_TCP) { mpp->queue_mode = QUEUE_MODE_BIO; break; } } /* * If this map was created with add_map_without_path(), * mpp->hwe might not be set yet. */ if (!mpp->hwe) extract_hwe_from_path(mpp); /* * properties selectors * * Ordering matters for some properties: * - features after no_path_retry and retain_hwhandler * - hwhandler after retain_hwhandler * No guarantee that this list is complete, check code in * propsel.c if in doubt. */ conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); select_pgfailback(conf, mpp); select_detect_pgpolicy(conf, mpp); select_detect_pgpolicy_use_tpg(conf, mpp); select_pgpolicy(conf, mpp); /* * If setup_map() is called from e.g. from reload_map() or resize_map(), * make sure that we don't corrupt attributes. */ save_attr = steal_ptr(mpp->selector); select_selector(conf, mpp); if (!mpp->selector) mpp->selector = save_attr; else free(save_attr); select_no_path_retry(conf, mpp); select_retain_hwhandler(conf, mpp); save_attr = steal_ptr(mpp->features); select_features(conf, mpp); if (!mpp->features) mpp->features = save_attr; else free(save_attr); save_attr = steal_ptr(mpp->hwhandler); select_hwhandler(conf, mpp); if (!mpp->hwhandler) mpp->hwhandler = save_attr; else free(save_attr); select_rr_weight(conf, mpp); select_minio(conf, mpp); select_mode(conf, mpp); select_uid(conf, mpp); select_gid(conf, mpp); select_reservation_key(conf, mpp); select_deferred_remove(conf, mpp); select_marginal_path_err_sample_time(conf, mpp); select_marginal_path_err_rate_threshold(conf, mpp); select_marginal_path_err_recheck_gap_time(conf, mpp); select_marginal_path_double_failed_time(conf, mpp); select_san_path_err_threshold(conf, mpp); select_san_path_err_forget_rate(conf, mpp); select_san_path_err_recovery_time(conf, mpp); select_delay_checks(conf, mpp); select_skip_kpartx(conf, mpp); select_max_sectors_kb(conf, mpp); select_ghost_delay(conf, mpp); select_flush_on_last_del(conf, mpp); sysfs_set_scsi_tmo(conf, mpp); marginal_pathgroups = conf->marginal_pathgroups; mpp->sync_tick = conf->max_checkint; pthread_cleanup_pop(1); if (!mpp->features || !mpp->hwhandler || !mpp->selector) { condlog(0, "%s: map select failed", mpp->alias); return 1; } if (marginal_path_check_enabled(mpp)) start_io_err_stat_thread(vecs); /* * assign paths to path groups -- start with no groups and all paths * in mpp->paths */ if (mpp->pg) { vector_foreach_slot (mpp->pg, pgp, i) free_pathgroup(pgp, KEEP_PATHS); vector_free(mpp->pg); mpp->pg = NULL; } if (group_paths(mpp, marginal_pathgroups)) return 1; /* * ponders each path group and determine highest prio pg * to switch over (default to first) */ mpp->bestpg = select_path_group(mpp); /* re-order paths in all path groups in an optimized way * for round-robin path selectors to get maximum throughput. */ if (!strncmp(mpp->selector, "round-robin", 11)) { vector_foreach_slot(mpp->pg, pgp, i) { if (VECTOR_SIZE(pgp->paths) <= 2) continue; if (rr_optimize_path_order(pgp)) { condlog(2, "cannot re-order paths for " "optimization: %s", mpp->alias); return 1; } } } /* * transform the mp->pg vector of vectors of paths * into a mp->params strings to feed the device-mapper */ if (assemble_map(mpp, params)) { condlog(0, "%s: problem assembling map", mpp->alias); return 1; } return 0; } static void compute_pgid(struct pathgroup * pgp) { struct path * pp; int i; vector_foreach_slot (pgp->paths, pp, i) pgp->id ^= (long)pp; } static void cleanup_bitfield(struct bitfield **p) { free(*p); } static int pgcmp (struct multipath * mpp, struct multipath * cmpp) { int i, j; struct pathgroup * pgp; struct pathgroup * cpgp; int r = 0; struct bitfield *bf __attribute__((cleanup(cleanup_bitfield))) = NULL; if (!mpp) return 0; if (VECTOR_SIZE(mpp->pg) != VECTOR_SIZE(cmpp->pg)) return 1; bf = alloc_bitfield(VECTOR_SIZE(cmpp->pg)); if (!bf) return 1; vector_foreach_slot (mpp->pg, pgp, i) { compute_pgid(pgp); vector_foreach_slot (cmpp->pg, cpgp, j) { if (pgp->id == cpgp->id && !pathcmp(pgp, cpgp)) { set_bit_in_bitfield(j, bf); r = 0; break; } r++; } if (r) return r; } vector_foreach_slot (cmpp->pg, cpgp, j) { if (!is_bit_set_in_bitfield(j, bf)) return 1; } return r; } void trigger_partitions_udev_change(struct udev_device *dev, const char *action, int len) { struct udev_enumerate *part_enum; struct udev_list_entry *item; const char *devtype; part_enum = udev_enumerate_new(udev); if (!part_enum) return; if (udev_enumerate_add_match_parent(part_enum, dev) < 0 || udev_enumerate_add_match_subsystem(part_enum, "block") < 0 || udev_enumerate_scan_devices(part_enum) < 0) goto unref; udev_list_entry_foreach(item, udev_enumerate_get_list_entry(part_enum)) { const char *syspath; struct udev_device *part; syspath = udev_list_entry_get_name(item); part = udev_device_new_from_syspath(udev, syspath); if (!part) continue; devtype = udev_device_get_devtype(part); if (devtype && !strcmp("partition", devtype)) { ssize_t ret; condlog(4, "%s: triggering %s event for %s", __func__, action, syspath); ret = sysfs_attr_set_value(part, "uevent", action, len); if (ret != len) log_sysfs_attr_set_value(2, ret, "%s: failed to trigger %s uevent", syspath, action); } udev_device_unref(part); } unref: udev_enumerate_unref(part_enum); } void trigger_path_udev_change(struct path *pp, bool is_mpath) { /* * If a path changes from multipath to non-multipath, we must * synthesize an artificial "add" event, otherwise the LVM2 rules * (69-lvm2-lvmetad.rules) won't pick it up. Otherwise, we'd just * irritate ourselves with an "add", so use "change". */ const char *action = is_mpath ? "change" : "add"; const char *env; ssize_t len, ret; if (!pp->udev) return; /* * Paths that are already classified as multipath * members don't need another uevent. */ env = udev_device_get_property_value( pp->udev, "DM_MULTIPATH_DEVICE_PATH"); if (is_mpath && env != NULL && !strcmp(env, "1")) { /* * If FIND_MULTIPATHS_WAIT_UNTIL is not "0", * path is in "maybe" state and timer is running * Send uevent now (see multipath.rules). */ env = udev_device_get_property_value( pp->udev, "FIND_MULTIPATHS_WAIT_UNTIL"); if (env == NULL || !strcmp(env, "0")) return; } else if (!is_mpath && (env == NULL || !strcmp(env, "0"))) return; condlog(3, "triggering %s uevent for %s (is %smultipath member)", action, pp->dev, is_mpath ? "" : "no "); len = strlen(action); ret = sysfs_attr_set_value(pp->udev, "uevent", action, len); if (ret != len) log_sysfs_attr_set_value(2, ret, "%s: failed to trigger %s uevent", pp->dev, action); trigger_partitions_udev_change(pp->udev, action, strlen(action)); } void trigger_paths_udev_change(struct multipath *mpp, bool is_mpath) { struct pathgroup *pgp; struct path *pp; int i, j; if (!mpp || !mpp->pg) return; vector_foreach_slot (mpp->pg, pgp, i) { if (!pgp->paths) continue; vector_foreach_slot(pgp->paths, pp, j) trigger_path_udev_change(pp, is_mpath); } } static int sysfs_set_max_sectors_kb(struct multipath *mpp) { struct pathgroup * pgp; struct path *pp; char buff[11]; ssize_t len; int i, j, ret, err = 0; if (mpp->max_sectors_kb == MAX_SECTORS_KB_UNDEF) return 0; len = snprintf(buff, sizeof(buff), "%d", mpp->max_sectors_kb); vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) { ret = sysfs_attr_set_value(pp->udev, "queue/max_sectors_kb", buff, len); if (ret != len) { log_sysfs_attr_set_value(1, ret, "failed setting max_sectors_kb on %s", pp->dev); err = 1; } } } return err; } static bool is_udev_ready(struct multipath *cmpp) { struct udev_device *mpp_ud; const char *env; bool rc; /* * MPATH_DEVICE_READY != 1 can mean two things: * (a) no usable paths * (b) device was never fully processed (e.g. udev killed) * If we are in this code path (startup or forced reconfigure), * (b) can mean that upper layers like kpartx have never been * run for this map. Thus force udev reload. */ mpp_ud = get_udev_for_mpp(cmpp); if (!mpp_ud) return true; env = udev_device_get_property_value(mpp_ud, "MPATH_DEVICE_READY"); rc = (env != NULL && !strcmp(env, "1")); udev_device_unref(mpp_ud); condlog(4, "%s: %s: \"%s\" -> %d\n", __func__, cmpp->alias, env ? env : "", rc); return rc; } static void select_reload_action(struct multipath *mpp, const char *reason) { mpp->action = mpp->action == ACT_RENAME ? ACT_RELOAD_RENAME : ACT_RELOAD; condlog(3, "%s: set ACT_RELOAD (%s)", mpp->alias, reason); } void select_action (struct multipath *mpp, const struct vector_s *curmp, int force_reload) { struct multipath * cmpp; struct multipath * cmpp_by_name; char * mpp_feat, * cmpp_feat; mpp->action = ACT_NOTHING; cmpp = find_mp_by_wwid(curmp, mpp->wwid); cmpp_by_name = find_mp_by_alias(curmp, mpp->alias); if (mpp->need_reload || (cmpp && cmpp->need_reload)) force_reload = 1; if (!cmpp) { if (cmpp_by_name) { condlog(1, "%s: can't use alias \"%s\" used by %s, falling back to WWID", mpp->wwid, mpp->alias, cmpp_by_name->wwid); /* We can do this because wwid wasn't found */ free(mpp->alias); mpp->alias = strdup(mpp->wwid); } mpp->action = ACT_CREATE; condlog(3, "%s: set ACT_CREATE (map does not exist%s)", mpp->alias, cmpp_by_name ? ", name changed" : ""); return; } if (!cmpp_by_name) { condlog(2, "%s: rename %s to %s", mpp->wwid, cmpp->alias, mpp->alias); strlcpy(mpp->alias_old, cmpp->alias, WWID_SIZE); mpp->action = ACT_RENAME; /* don't return here. Check for other needed actions */ } else if (cmpp != cmpp_by_name) { condlog(2, "%s: unable to rename %s to %s (%s is used by %s)", mpp->wwid, cmpp->alias, mpp->alias, mpp->alias, cmpp_by_name->wwid); /* reset alias to existing alias */ free(mpp->alias); mpp->alias = strdup(cmpp->alias); mpp->action = ACT_IMPOSSIBLE; /* don't return here. Check for other needed actions */ } if (cmpp->size != mpp->size) { mpp->force_udev_reload = 1; mpp->action = mpp->action == ACT_RENAME ? ACT_RESIZE_RENAME : ACT_RESIZE; condlog(3, "%s: set ACT_RESIZE (size change)", mpp->alias); return; } if (force_reload) { mpp->force_udev_reload = 1; select_reload_action(mpp, "forced by user"); return; } if (!is_udev_ready(cmpp) && count_active_paths(mpp) > 0) { mpp->force_udev_reload = 1; select_reload_action(mpp, "udev incomplete"); return; } if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF && !!strstr(mpp->features, "queue_if_no_path") != !!strstr(cmpp->features, "queue_if_no_path")) { select_reload_action(mpp, "no_path_retry change"); return; } if ((mpp->retain_hwhandler != RETAIN_HWHANDLER_ON || strcmp(cmpp->hwhandler, "0") == 0) && (strlen(cmpp->hwhandler) != strlen(mpp->hwhandler) || strncmp(cmpp->hwhandler, mpp->hwhandler, strlen(mpp->hwhandler)))) { select_reload_action(mpp, "hwhandler change"); return; } if (mpp->retain_hwhandler != RETAIN_HWHANDLER_UNDEF && !!strstr(mpp->features, "retain_attached_hw_handler") != !!strstr(cmpp->features, "retain_attached_hw_handler") && get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) { select_reload_action(mpp, "retain_hwhandler change"); return; } cmpp_feat = strdup(cmpp->features); mpp_feat = strdup(mpp->features); if (cmpp_feat && mpp_feat) { remove_feature(&mpp_feat, "queue_if_no_path"); remove_feature(&mpp_feat, "retain_attached_hw_handler"); remove_feature(&cmpp_feat, "queue_if_no_path"); remove_feature(&cmpp_feat, "retain_attached_hw_handler"); if (strcmp(mpp_feat, cmpp_feat)) { select_reload_action(mpp, "features change"); free(cmpp_feat); free(mpp_feat); return; } } free(cmpp_feat); free(mpp_feat); if (!cmpp->selector || strncmp(cmpp->selector, mpp->selector, strlen(mpp->selector))) { select_reload_action(mpp, "selector change"); return; } if (cmpp->minio != mpp->minio) { select_reload_action(mpp, "minio change"); return; } if (!cmpp->pg || VECTOR_SIZE(cmpp->pg) != VECTOR_SIZE(mpp->pg)) { select_reload_action(mpp, "path group number change"); return; } if (pgcmp(mpp, cmpp)) { select_reload_action(mpp, "path group topology change"); return; } if (cmpp->nextpg != mpp->bestpg) { mpp->action = mpp->action == ACT_RENAME ? ACT_SWITCHPG_RENAME : ACT_SWITCHPG; condlog(3, "%s: set ACT_SWITCHPG (next path group change)", mpp->alias); return; } if (mpp->action == ACT_NOTHING) condlog(3, "%s: set ACT_NOTHING (map unchanged)", mpp->alias); return; } int reinstate_paths(struct multipath *mpp) { int i, j; struct pathgroup * pgp; struct path * pp; if (!mpp->pg) return 0; vector_foreach_slot (mpp->pg, pgp, i) { if (!pgp->paths) continue; vector_foreach_slot (pgp->paths, pp, j) { if (pp->state != PATH_UP && (pgp->status == PGSTATE_DISABLED || pgp->status == PGSTATE_ACTIVE)) continue; if (pp->dmstate == PSTATE_FAILED) { if (dm_reinstate_path(mpp->alias, pp->dev_t)) condlog(0, "%s: error reinstating", pp->dev); } } } return 0; } static int lock_multipath (struct multipath * mpp, int lock) { struct pathgroup * pgp; struct path * pp; int i, j; int x, y; if (!mpp || !mpp->pg) return 0; vector_foreach_slot (mpp->pg, pgp, i) { if (!pgp->paths) continue; vector_foreach_slot(pgp->paths, pp, j) { if (lock && flock(pp->fd, LOCK_SH | LOCK_NB) && errno == EWOULDBLOCK) goto fail; else if (!lock) flock(pp->fd, LOCK_UN); } } return 0; fail: vector_foreach_slot (mpp->pg, pgp, x) { if (x > i) return 1; if (!pgp->paths) continue; vector_foreach_slot(pgp->paths, pp, y) { if (x == i && y >= j) return 1; flock(pp->fd, LOCK_UN); } } return 1; } int domap(struct multipath *mpp, char *params, int is_daemon) { int r = DOMAP_FAIL; struct config *conf; /* * last chance to quit before touching the devmaps */ if (mpp->action == ACT_DRY_RUN) { print_multipath_topology(mpp, libmp_verbosity); return DOMAP_DRY; } if (mpp->action == ACT_CREATE) { char wwid[WWID_SIZE]; int rc = dm_get_wwid(mpp->alias, wwid, sizeof(wwid)); if (rc == DMP_OK && !strncmp(mpp->wwid, wwid, sizeof(wwid))) { condlog(3, "%s: map already present", mpp->alias); mpp->action = ACT_RELOAD; } else if (rc == DMP_OK) { condlog(1, "%s: map \"%s\" already present with WWID \"%s\", skipping\n" "please check alias settings in config and bindings file", mpp->wwid, mpp->alias, wwid); mpp->action = ACT_REJECT; } else if (rc == DMP_NO_MATCH) { condlog(1, "%s: alias \"%s\" already taken by a non-multipath map", mpp->wwid, mpp->alias); mpp->action = ACT_REJECT; } } if (mpp->action == ACT_CREATE) { char alias[WWID_SIZE]; int rc = dm_find_map_by_wwid(mpp->wwid, alias, NULL); if (rc == DMP_NO_MATCH) { condlog(1, "%s: wwid \"%s\" already in use by non-multipath map \"%s\"", mpp->alias, mpp->wwid, alias); mpp->action = ACT_REJECT; } else if (rc == DMP_OK || rc == DMP_EMPTY) { /* * we already handled the case were rc == DMO_OK and * the alias == mpp->alias above. So the alias must be * different here. */ condlog(3, "%s: map already present with a different name \"%s\". reloading", mpp->alias, alias); strlcpy(mpp->alias_old, alias, WWID_SIZE); mpp->action = ACT_RELOAD_RENAME; } } if (mpp->action == ACT_RENAME || mpp->action == ACT_SWITCHPG_RENAME || mpp->action == ACT_RELOAD_RENAME || mpp->action == ACT_RESIZE_RENAME) { conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = dm_rename(mpp->alias_old, mpp->alias, conf->partition_delim, mpp->skip_kpartx); pthread_cleanup_pop(1); if (r == DOMAP_FAIL) return r; } switch (mpp->action) { case ACT_REJECT: case ACT_NOTHING: case ACT_IMPOSSIBLE: return DOMAP_EXIST; case ACT_SWITCHPG: case ACT_SWITCHPG_RENAME: dm_switchgroup(mpp->alias, mpp->bestpg); /* * we may have avoided reinstating paths because there where in * active or disabled PG. Now that the topology has changed, * retry. */ reinstate_paths(mpp); return DOMAP_EXIST; case ACT_CREATE: if (lock_multipath(mpp, 1)) { condlog(3, "%s: failed to create map (in use)", mpp->alias); return DOMAP_RETRY; } sysfs_set_max_sectors_kb(mpp); if (is_daemon && mpp->ghost_delay > 0 && count_active_paths(mpp) && pathcount(mpp, PATH_UP) == 0) mpp->ghost_delay_tick = mpp->ghost_delay; r = dm_addmap_create(mpp, params); lock_multipath(mpp, 0); break; case ACT_RELOAD: case ACT_RELOAD_RENAME: if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP)) mpp->ghost_delay_tick = 0; r = dm_addmap_reload(mpp, params, 0); break; case ACT_RESIZE: case ACT_RESIZE_RENAME: if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP)) mpp->ghost_delay_tick = 0; r = dm_addmap_reload(mpp, params, 1); break; case ACT_RENAME: break; default: r = DOMAP_FAIL; break; } if (r == DOMAP_OK) { /* * DM_DEVICE_CREATE, DM_DEVICE_RENAME, or DM_DEVICE_RELOAD * succeeded */ mpp->force_udev_reload = 0; if (mpp->action == ACT_CREATE) { remember_wwid(mpp->wwid); trigger_paths_udev_change(mpp, true); } if (!is_daemon) { /* multipath client mode */ dm_switchgroup(mpp->alias, mpp->bestpg); } else { /* multipath daemon mode */ mpp->stat_map_loads++; condlog(4, "%s: load table [0 %llu %s %s]", mpp->alias, mpp->size, TGT_MPATH, params); /* * Required action is over, reset for the stateful daemon. * But don't do it for creation as we use in the caller the * mpp->action to figure out whether to start the watievent checker. */ if (mpp->action != ACT_CREATE) mpp->action = ACT_NOTHING; else { conf = get_multipath_config(); mpp->wait_for_udev = 1; mpp->uev_wait_tick = conf->uev_wait_timeout; put_multipath_config(conf); } } dm_setgeometry(mpp); return DOMAP_OK; } else if (r == DOMAP_FAIL && mpp->action == ACT_CREATE) trigger_paths_udev_change(mpp, false); return DOMAP_FAIL; } extern int check_daemon(void) { int fd; char *reply; int ret = 0; unsigned int timeout; struct config *conf; fd = mpath_connect(); if (fd == -1) return 0; if (send_packet(fd, "show daemon") != 0) goto out; conf = get_multipath_config(); timeout = conf->uxsock_timeout; put_multipath_config(conf); if (recv_packet(fd, &reply, timeout) != 0) goto out; if (reply && strstr(reply, "shutdown")) goto out_free; ret = 1; out_free: free(reply); out: mpath_disconnect(fd); return ret; } /* * The force_reload parameter determines how coalesce_paths treats existing maps. * FORCE_RELOAD_NONE: existing maps aren't touched at all * FORCE_RELOAD_YES: all maps are rebuilt from scratch and (re)loaded in DM * FORCE_RELOAD_WEAK: existing maps are compared to the current conf and only * reloaded in DM if there's a difference. This is normally sufficient. */ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, int force_reload, enum mpath_cmds cmd) { int ret = CP_FAIL; int k, i, r; int is_daemon = (cmd == CMD_NONE) ? 1 : 0; char *params __attribute__((cleanup(cleanup_charp))) = NULL; struct multipath * mpp; struct path * pp1 = NULL; struct path * pp2; vector curmp = vecs->mpvec; vector pathvec = vecs->pathvec; vector newmp; struct config *conf = NULL; int allow_queueing; struct bitfield *size_mismatch_seen; struct multipath * cmpp; /* ignore refwwid if it's empty */ if (refwwid && !strlen(refwwid)) refwwid = NULL; if (force_reload != FORCE_RELOAD_NONE) { vector_foreach_slot (pathvec, pp1, k) { pp1->mpp = NULL; } } if (VECTOR_SIZE(pathvec) == 0) return CP_OK; size_mismatch_seen = alloc_bitfield(VECTOR_SIZE(pathvec)); if (size_mismatch_seen == NULL) return CP_FAIL; if (mpvec) newmp = mpvec; else newmp = vector_alloc(); if (!newmp) { condlog(0, "cannot allocate newmp"); goto out; } vector_foreach_slot (pathvec, pp1, k) { int invalid; if (should_exit()) { ret = CP_FAIL; goto out; } /* skip this path for some reason */ /* 1. if path has no unique id or wwid blacklisted */ if (strlen(pp1->wwid) == 0) { orphan_path(pp1, "no WWID"); continue; } conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); invalid = (filter_path(conf, pp1) > 0); pthread_cleanup_pop(1); if (invalid) { orphan_path(pp1, "blacklisted"); continue; } /* 2. if path already coalesced, or seen and discarded */ if (pp1->mpp || is_bit_set_in_bitfield(k, size_mismatch_seen)) continue; /* 3. if path has disappeared */ if (pp1->state == PATH_REMOVED) { orphan_path(pp1, "path removed"); continue; } /* 4. path is out of scope */ if (refwwid && strncmp(pp1->wwid, refwwid, WWID_SIZE - 1)) continue; /* If find_multipaths was selected check if the path is valid */ if (!refwwid && !should_multipath(pp1, pathvec, curmp)) { orphan_path(pp1, "only one path"); continue; } cmpp = find_mp_by_wwid(curmp, pp1->wwid); if (cmpp && cmpp->queue_mode == QUEUE_MODE_RQ && pp1->bus == SYSFS_BUS_NVME && pp1->sg_id.proto_id == NVME_PROTOCOL_TCP) { orphan_path(pp1, "nvme:tcp path not allowed with request queue_mode multipath device"); continue; } /* * at this point, we know we really got a new mp */ mpp = add_map_with_path(vecs, pp1, 0, cmpp); if (!mpp) { orphan_path(pp1, "failed to create multipath device"); continue; } if (!mpp->paths) { condlog(0, "%s: skip coalesce (no paths)", mpp->alias); remove_map(mpp, vecs->pathvec, NULL); continue; } for (i = k + 1; i < VECTOR_SIZE(pathvec); i++) { pp2 = VECTOR_SLOT(pathvec, i); if (strcmp(pp1->wwid, pp2->wwid)) continue; if (!mpp->size && pp2->size) mpp->size = pp2->size; if (mpp->size && pp2->size && pp2->size != mpp->size) { /* * ouch, avoid feeding that to the DM */ condlog(0, "%s: size %llu, expected %llu. " "Discard", pp2->dev, pp2->size, mpp->size); mpp->action = ACT_REJECT; set_bit_in_bitfield(i, size_mismatch_seen); } } verify_paths(mpp); if (cmpp) mpp->queue_mode = cmpp->queue_mode; if (cmd == CMD_DRY_RUN && mpp->action == ACT_UNDEF) mpp->action = ACT_DRY_RUN; if (setup_map(mpp, ¶ms, vecs)) { remove_map(mpp, vecs->pathvec, NULL); continue; } if (mpp->action == ACT_UNDEF) select_action(mpp, curmp, force_reload == FORCE_RELOAD_YES ? 1 : 0); r = domap(mpp, params, is_daemon); free(params); params = NULL; if (r == DOMAP_FAIL || r == DOMAP_RETRY) { condlog(3, "%s: domap (%u) failure " "for create/reload map", mpp->alias, r); if (r == DOMAP_FAIL || is_daemon) { condlog(2, "%s: %s map", mpp->alias, (mpp->action == ACT_CREATE)? "ignoring" : "removing"); remove_map(mpp, vecs->pathvec, NULL); continue; } else /* if (r == DOMAP_RETRY && !is_daemon) */ { ret = CP_RETRY; goto out; } } if (r == DOMAP_DRY) { if (!vector_alloc_slot(newmp)) { remove_map(mpp, vecs->pathvec, NULL); goto out; } vector_set_slot(newmp, mpp); continue; } conf = get_multipath_config(); allow_queueing = conf->allow_queueing; put_multipath_config(conf); if (!is_daemon && !allow_queueing && !check_daemon()) { if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF && mpp->no_path_retry != NO_PATH_RETRY_FAIL) condlog(3, "%s: multipathd not running, unset " "queue_if_no_path feature", mpp->alias); dm_queue_if_no_path(mpp, 0); } if (!is_daemon && mpp->action != ACT_NOTHING) print_multipath_topology(mpp, libmp_verbosity); if (mpp->action != ACT_REJECT) { if (!vector_alloc_slot(newmp)) { remove_map(mpp, vecs->pathvec, NULL); goto out; } vector_set_slot(newmp, mpp); } else remove_map(mpp, vecs->pathvec, NULL); } ret = CP_OK; out: free(size_mismatch_seen); if (!mpvec) { vector_foreach_slot (newmp, mpp, i) remove_map(mpp, vecs->pathvec, NULL); vector_free(newmp); } return ret; } struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type) { struct udev_device *ud = NULL; const char *base; if (dev == NULL || *dev == '\0') return NULL; switch (dev_type) { case DEV_DEVNODE: case DEV_DEVMAP: /* This should be GNU basename, compiler will warn if not */ base = basename(dev); if (*base == '\0') break; ud = udev_device_new_from_subsystem_sysname(udev, "block", base); break; case DEV_DEVT: ud = udev_device_new_from_devnum(udev, 'b', parse_devt(dev)); break; case DEV_UEVENT: ud = udev_device_new_from_environment(udev); break; default: condlog(0, "Internal error: get_udev_device called with invalid type %d\n", dev_type); break; } if (ud == NULL) condlog(2, "get_udev_device: failed to look up %s with type %d", dev, dev_type); return ud; } static int _get_refwwid(enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, vector pathvec, struct config *conf, char **wwid) { int ret = 1; struct path * pp; char buff[FILE_NAME_SIZE]; const char *refwwid = NULL; char tmpwwid[WWID_SIZE]; struct udev_device *udevice; int flags = DI_SYSFS | DI_WWID; if (!wwid) return PATHINFO_FAILED; *wwid = NULL; if (dev_type == DEV_NONE) return PATHINFO_FAILED; if (cmd != CMD_REMOVE_WWID) flags |= DI_BLACKLIST; switch (dev_type) { case DEV_DEVNODE: if (basenamecpy(dev, buff, FILE_NAME_SIZE) == 0) { condlog(1, "basename failed for '%s' (%s)", dev, buff); return PATHINFO_FAILED; } /* dev is used in common code below */ dev = buff; pp = find_path_by_dev(pathvec, dev); goto common; case DEV_DEVT: pp = find_path_by_devt(pathvec, dev); goto common; case DEV_UEVENT: pp = NULL; /* For condlog below, dev is unused in get_udev_device() */ dev = "environment"; common: if (!pp) { udevice = get_udev_device(dev, dev_type); if (!udevice) { condlog(0, "%s: cannot find block device", dev); return PATHINFO_FAILED; } ret = store_pathinfo(pathvec, conf, udevice, flags, &pp); udev_device_unref(udevice); if (!pp) { if (ret == PATHINFO_FAILED) condlog(0, "%s: can't store path info", dev); return ret; } } if (flags & DI_BLACKLIST && filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0) return PATHINFO_SKIPPED; refwwid = pp->wwid; break; case DEV_DEVMAP: if (((dm_get_wwid(dev, tmpwwid, WWID_SIZE)) == DMP_OK) && (strlen(tmpwwid))) refwwid = tmpwwid; /* or may be a binding */ else if (get_user_friendly_wwid(dev, tmpwwid) == 0) refwwid = tmpwwid; /* or may be an alias */ else { refwwid = get_mpe_wwid(conf->mptable, dev); /* or directly a wwid */ if (!refwwid) refwwid = dev; } if (flags & DI_BLACKLIST && refwwid && strlen(refwwid) && filter_wwid(conf->blist_wwid, conf->elist_wwid, refwwid, NULL) > 0) return PATHINFO_SKIPPED; break; default: break; } if (refwwid && strlen(refwwid)) { *wwid = strdup(refwwid); return PATHINFO_OK; } return PATHINFO_FAILED; } /* * Returns: PATHINFO_OK, PATHINFO_FAILED, or PATHINFO_SKIPPED (see pathinfo()) */ int get_refwwid(enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, vector pathvec, char **wwid) { int ret; struct config *conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = _get_refwwid(cmd, dev, dev_type, pathvec, conf, wwid); pthread_cleanup_pop(1); return ret; } multipath-tools-0.11.1/libmultipath/configure.h000066400000000000000000000034111475246302400215750ustar00rootroot00000000000000#ifndef CONFIGURE_H_INCLUDED #define CONFIGURE_H_INCLUDED /* * configurator actions */ #define ACT_NOTHING_STR "unchanged" #define ACT_REJECT_STR "reject" #define ACT_RELOAD_STR "reload" #define ACT_SWITCHPG_STR "switchpg" #define ACT_RENAME_STR "rename" #define ACT_CREATE_STR "create" #define ACT_RESIZE_STR "resize" enum actions { ACT_UNDEF, ACT_NOTHING, ACT_REJECT, ACT_RELOAD, ACT_SWITCHPG, ACT_RENAME, ACT_CREATE, ACT_RESIZE, ACT_RELOAD_RENAME, ACT_DRY_RUN, ACT_IMPOSSIBLE, ACT_RESIZE_RENAME, ACT_SWITCHPG_RENAME, }; /* * Return value of domap() * DAEMON_RETRY is only used for ACT_CREATE (see domap()). */ enum { DOMAP_RETRY = -1, DOMAP_FAIL = 0, DOMAP_OK = 1, DOMAP_EXIST = 2, DOMAP_DRY = 3 }; /* * Return value of coalesce_paths() * CP_RETRY is only used in non-daemon case (multipath). */ enum { CP_OK = 0, CP_FAIL, CP_RETRY, }; struct vectors; int setup_map (struct multipath * mpp, char **params, struct vectors *vecs); void select_action (struct multipath *mpp, const struct vector_s *curmp, int force_reload); int domap (struct multipath * mpp, char * params, int is_daemon); int reinstate_paths (struct multipath *mpp); int coalesce_paths (struct vectors *vecs, vector curmp, char * refwwid, int force_reload, enum mpath_cmds cmd); int get_refwwid (enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, vector pathvec, char **wwid); struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type); void trigger_path_udev_change(struct path *pp, bool is_mpath); void trigger_paths_udev_change(struct multipath *mpp, bool is_mpath); void trigger_partitions_udev_change(struct udev_device *dev, const char *action, int len); int check_daemon(void); #endif multipath-tools-0.11.1/libmultipath/defaults.c000066400000000000000000000002421475246302400214150ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include "defaults.h" const char * const default_partition_delim = DEFAULT_PARTITION_DELIM; multipath-tools-0.11.1/libmultipath/defaults.h000066400000000000000000000055431475246302400214330ustar00rootroot00000000000000#ifndef DEFAULTS_H_INCLUDED #define DEFAULTS_H_INCLUDED #include #include /* * If you add or modify a value also update multipath/multipath.conf.5 * and the TEMPLATE in libmultipath/hwtable.c */ #define DEFAULT_UID_ATTRIBUTE "ID_SERIAL" #define DEFAULT_NVME_UID_ATTRIBUTE "ID_WWN" #define DEFAULT_DASD_UID_ATTRIBUTE "ID_UID" #define DEFAULT_UDEVDIR "/dev" #define DEFAULT_SELECTOR "service-time 0" #define DEFAULT_ALIAS_PREFIX "mpath" #define DEFAULT_FEATURES "0" #define DEFAULT_HWHANDLER "0" #define DEFAULT_MINIO 1000 #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 #define DEFAULT_FIND_MULTIPATHS FIND_MULTIPATHS_STRICT #define DEFAULT_FAST_IO_FAIL 5 #define DEFAULT_DEV_LOSS_TMO 600 #define DEFAULT_RETAIN_HWHANDLER RETAIN_HWHANDLER_ON #define DEFAULT_DETECT_PRIO DETECT_PRIO_ON #define DEFAULT_DETECT_CHECKER DETECT_CHECKER_ON #define DEFAULT_DETECT_PGPOLICY DETECT_PGPOLICY_ON #define DEFAULT_DETECT_PGPOLICY_USE_TPG DETECT_PGPOLICY_USE_TPG_OFF #define DEFAULT_DEFERRED_REMOVE DEFERRED_REMOVE_OFF #define DEFAULT_DELAY_CHECKS NU_NO #define DEFAULT_ERR_CHECKS NU_NO /* half of minimum value for marginal_path_err_sample_time */ #define IOTIMEOUT_SEC 60 #define DEFAULT_UEVENT_STACKSIZE 256 #define DEFAULT_RETRIGGER_DELAY 10 #define DEFAULT_RETRIGGER_TRIES 3 #define DEFAULT_UEV_WAIT_TIMEOUT 30 #define DEFAULT_PRIO PRIO_CONST #define DEFAULT_PRIO_ARGS "" #define DEFAULT_CHECKER TUR #define DEFAULT_FLUSH FLUSH_UNUSED #define DEFAULT_USER_FRIENDLY_NAMES USER_FRIENDLY_NAMES_OFF #define DEFAULT_FORCE_SYNC 0 #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 #define DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT 1 #define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF #define DEFAULT_RECHECK_WWID RECHECK_WWID_OFF #define DEFAULT_AUTO_RESIZE AUTO_RESIZE_NEVER /* Enable no foreign libraries by default */ #define DEFAULT_ENABLE_FOREIGN "NONE" #define CHECKINT_UNDEF UINT_MAX #define DEFAULT_CHECKINT 5 #define DEV_LOSS_TMO_UNSET 0U #define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE RUNTIME_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" #define DEFAULT_BINDINGS_FILE STATE_DIR "/bindings" #define DEFAULT_WWIDS_FILE STATE_DIR "/wwids" #define DEFAULT_PRKEYS_FILE STATE_DIR "/prkeys" #define MULTIPATH_SHM_BASE RUNTIME_DIR "/multipath/" static inline char *set_default(char *str) { return strdup(str); } extern const char *const default_partition_delim; #endif /* DEFAULTS_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/devmapper.c000066400000000000000000001205751475246302400216050ustar00rootroot00000000000000/* * snippets copied from device-mapper dmsetup.c * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Patrick Caulfield, Redhat */ #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "vector.h" #include "structs.h" #include "debug.h" #include "devmapper.h" #include "sysfs.h" #include "wwids.h" #include "version.h" #include "time-util.h" #include "log_pthread.h" #include #include #define MAX_WAIT 5 #define LOOPS_PER_SEC 5 #define INVALID_VERSION ~0U static unsigned int dm_library_version[3] = { INVALID_VERSION, }; static unsigned int dm_kernel_version[3] = { INVALID_VERSION, }; static unsigned int dm_mpath_target_version[3] = { INVALID_VERSION, }; static pthread_once_t dm_initialized = PTHREAD_ONCE_INIT; static pthread_once_t versions_initialized = PTHREAD_ONCE_INIT; static pthread_mutex_t libmp_dm_lock = PTHREAD_MUTEX_INITIALIZER; static int dm_conf_verbosity; #ifdef LIBDM_API_DEFERRED static int dm_cancel_remove_partmaps(const char * mapname); #define DR_UNUSED__ /* empty */ #else #define DR_UNUSED__ __attribute__((unused)) #endif static int dm_remove_partmaps (const char *mapname, int flags); static int do_foreach_partmaps(const char *mapname, int (*partmap_func)(const char *, void *), void *data); static int _dm_queue_if_no_path(const char *mapname, int enable); #ifndef LIBDM_API_COOKIE static inline int dm_task_set_cookie(struct dm_task *dmt, uint32_t *c, int a) { return 1; } static void libmp_udev_wait(unsigned int c) { } static void dm_udev_set_sync_support(int c) { } #else static void libmp_udev_wait(unsigned int c) { pthread_mutex_lock(&libmp_dm_lock); pthread_cleanup_push(cleanup_mutex, &libmp_dm_lock); dm_udev_wait(c); pthread_cleanup_pop(1); } #endif const char *dmp_errstr(int rc) { static const char *str[] = { [DMP_ERR] = "generic error", [DMP_OK] = "success", [DMP_NOT_FOUND] = "not found", [DMP_NO_MATCH] = "target type mismatch", [DMP_EMPTY] = "no target", [DMP_LAST__] = "**invalid**", }; if (rc < 0 || rc > DMP_LAST__) rc = DMP_LAST__; return str[rc]; } int libmp_dm_task_run(struct dm_task *dmt) { int r; pthread_mutex_lock(&libmp_dm_lock); pthread_cleanup_push(cleanup_mutex, &libmp_dm_lock); r = dm_task_run(dmt); pthread_cleanup_pop(1); return r; } static void cleanup_dm_task(struct dm_task **pdmt) { if (*pdmt) dm_task_destroy(*pdmt); } __attribute__((format(printf, 4, 5))) static void dm_write_log (int level, const char *file, int line, const char *f, ...) { va_list ap; /* * libdm uses the same log levels as syslog, * except that EMERG/ALERT are not used */ if (level > LOG_DEBUG) level = LOG_DEBUG; if (level > dm_conf_verbosity) return; va_start(ap, f); if (logsink != LOGSINK_SYSLOG) { if (logsink == LOGSINK_STDERR_WITH_TIME) { struct timespec ts; char buff[32]; get_monotonic_time(&ts); safe_sprintf(buff, "%ld.%06ld", (long)ts.tv_sec, ts.tv_nsec/1000); fprintf(stderr, "%s | ", buff); } fprintf(stderr, "libdevmapper: %s(%i): ", file, line); vfprintf(stderr, f, ap); fprintf(stderr, "\n"); } else { condlog(level >= LOG_ERR ? level - LOG_ERR : 0, "libdevmapper: %s(%i): ", file, line); log_safe(level, f, ap); } va_end(ap); return; } static void dm_init(int v) { /* * This maps libdm's standard loglevel _LOG_WARN (= 4), which is rather * quiet in practice, to multipathd's default verbosity 2 */ dm_conf_verbosity = v + 2; dm_log_init(&dm_write_log); } static void init_dm_library_version(void) { char version[64]; unsigned int v[3]; dm_get_library_version(version, sizeof(version)); if (sscanf(version, "%u.%u.%u ", &v[0], &v[1], &v[2]) != 3) { condlog(0, "invalid libdevmapper version %s", version); return; } memcpy(dm_library_version, v, sizeof(dm_library_version)); condlog(3, "libdevmapper version %u.%.2u.%.2u", dm_library_version[0], dm_library_version[1], dm_library_version[2]); } static int dm_lib_prereq (void) { #if defined(LIBDM_API_HOLD_CONTROL) unsigned int minv[3] = {1, 2, 111}; #elif defined(LIBDM_API_GET_ERRNO) unsigned int minv[3] = {1, 2, 99}; #elif defined(LIBDM_API_DEFERRED) unsigned int minv[3] = {1, 2, 89}; #elif defined(DM_SUBSYSTEM_UDEV_FLAG0) unsigned int minv[3] = {1, 2, 82}; #elif defined(LIBDM_API_COOKIE) unsigned int minv[3] = {1, 2, 38}; #else unsigned int minv[3] = {1, 2, 8}; #endif if (VERSION_GE(dm_library_version, minv)) return 0; condlog(0, "libdevmapper version must be >= %u.%.2u.%.2u", minv[0], minv[1], minv[2]); return 1; } static void init_dm_drv_version(void) { char buff[64]; unsigned int v[3]; if (!dm_driver_version(buff, sizeof(buff))) { condlog(0, "cannot get kernel dm version"); return; } if (sscanf(buff, "%u.%u.%u ", &v[0], &v[1], &v[2]) != 3) { condlog(0, "invalid kernel dm version '%s'", buff); return; } memcpy(dm_kernel_version, v, sizeof(dm_library_version)); condlog(3, "kernel device mapper v%u.%u.%u", dm_kernel_version[0], dm_kernel_version[1], dm_kernel_version[2]); } static int dm_tgt_version (unsigned int *version, char *str) { bool found = false; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_versions *target; struct dm_versions *last_target; unsigned int *v; /* * We have to call dm_task_create() and not libmp_dm_task_create() * here to avoid a recursive invocation of * pthread_once(&dm_initialized), which would cause a deadlock. */ if (!(dmt = dm_task_create(DM_DEVICE_LIST_VERSIONS))) return 1; if (!libmp_dm_task_run(dmt)) { dm_log_error(2, DM_DEVICE_LIST_VERSIONS, dmt); condlog(0, "Cannot communicate with kernel DM"); return 1; } target = dm_task_get_versions(dmt); do { last_target = target; if (!strncmp(str, target->name, strlen(str))) { found = true; break; } target = (void *) target + target->next; } while (last_target != target); if (!found) { condlog(0, "DM %s kernel driver not loaded", str); return 1; } v = target->version; version[0] = v[0]; version[1] = v[1]; version[2] = v[2]; return 0; } static void init_dm_mpath_version(void) { if (!dm_tgt_version(dm_mpath_target_version, TGT_MPATH)) condlog(3, "DM multipath kernel driver v%u.%u.%u", dm_mpath_target_version[0], dm_mpath_target_version[1], dm_mpath_target_version[2]); } static int dm_tgt_prereq (unsigned int *ver) { unsigned int minv[3] = {1, 0, 3}; if (VERSION_GE(dm_mpath_target_version, minv)) { if (ver) { ver[0] = dm_mpath_target_version[0]; ver[1] = dm_mpath_target_version[1]; ver[2] = dm_mpath_target_version[2]; } return 0; } condlog(0, "DM multipath kernel driver must be >= v%u.%u.%u", minv[0], minv[1], minv[2]); return 1; } static void _init_versions(void) { /* Can't use condlog here because of how VERSION_STRING is defined */ if (3 <= libmp_verbosity) dlog(3, VERSION_STRING); init_dm_library_version(); init_dm_drv_version(); init_dm_mpath_version(); } static int init_versions(void) { pthread_once(&versions_initialized, _init_versions); return (dm_library_version[0] == INVALID_VERSION || dm_kernel_version[0] == INVALID_VERSION || dm_mpath_target_version[0] == INVALID_VERSION); } int dm_prereq(unsigned int *v) { if (init_versions()) return 1; if (dm_lib_prereq()) return 1; return dm_tgt_prereq(v); } int libmp_get_version(int which, unsigned int version[3]) { unsigned int *src_version; init_versions(); switch (which) { case DM_LIBRARY_VERSION: src_version = dm_library_version; break; case DM_KERNEL_VERSION: src_version = dm_kernel_version; break; case DM_MPATH_TARGET_VERSION: src_version = dm_mpath_target_version; break; case MULTIPATH_VERSION: version[0] = (VERSION_CODE >> 16) & 0xff; version[1] = (VERSION_CODE >> 8) & 0xff; version[2] = VERSION_CODE & 0xff; return 0; default: condlog(0, "%s: invalid value for 'which'", __func__); return 1; } if (src_version[0] == INVALID_VERSION) return 1; memcpy(version, src_version, 3 * sizeof(*version)); return 0; } static int libmp_dm_udev_sync = 0; void libmp_udev_set_sync_support(int on) { libmp_dm_udev_sync = !!on; } static bool libmp_dm_init_called; void libmp_dm_exit(void) { if (!libmp_dm_init_called) return; /* switch back to default libdm logging */ dm_log_init(NULL); #ifdef LIBDM_API_HOLD_CONTROL /* make sure control fd is closed in dm_lib_release() */ dm_hold_control_dev(0); #endif } static void libmp_dm_init(void) { unsigned int version[3]; if (dm_prereq(version)) exit(1); dm_init(libmp_verbosity); #ifdef LIBDM_API_HOLD_CONTROL dm_hold_control_dev(1); #endif dm_udev_set_sync_support(libmp_dm_udev_sync); libmp_dm_init_called = true; } static void _do_skip_libmp_dm_init(void) { } void skip_libmp_dm_init(void) { pthread_once(&dm_initialized, _do_skip_libmp_dm_init); } struct dm_task* libmp_dm_task_create(int task) { pthread_once(&dm_initialized, libmp_dm_init); return dm_task_create(task); } static int dm_simplecmd (int task, const char *name, int flags, uint16_t udev_flags) { int r; int udev_wait_flag = (((flags & DMFL_NEED_SYNC) || udev_flags) && (task == DM_DEVICE_RESUME || task == DM_DEVICE_REMOVE)); uint32_t cookie = 0; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; if (!(dmt = libmp_dm_task_create (task))) return 0; if (!dm_task_set_name (dmt, name)) return 0; dm_task_skip_lockfs(dmt); /* for DM_DEVICE_RESUME */ #ifdef LIBDM_API_FLUSH if (flags & DMFL_NO_FLUSH) dm_task_no_flush(dmt); /* for DM_DEVICE_SUSPEND/RESUME */ #endif #ifdef LIBDM_API_DEFERRED if (flags & DMFL_DEFERRED) dm_task_deferred_remove(dmt); #endif if (udev_wait_flag && !dm_task_set_cookie(dmt, &cookie, DM_UDEV_DISABLE_LIBRARY_FALLBACK | udev_flags)) return 0; r = libmp_dm_task_run (dmt); if (!r) dm_log_error(2, task, dmt); if (udev_wait_flag) libmp_udev_wait(cookie); return r; } int dm_simplecmd_flush (int task, const char *name, uint16_t udev_flags) { return dm_simplecmd(task, name, DMFL_NEED_SYNC, udev_flags); } int dm_simplecmd_noflush (int task, const char *name, uint16_t udev_flags) { return dm_simplecmd(task, name, DMFL_NO_FLUSH|DMFL_NEED_SYNC, udev_flags); } static int dm_device_remove (const char *name, int flags) { return dm_simplecmd(DM_DEVICE_REMOVE, name, flags, 0); } static int dm_addmap (int task, const char *target, struct multipath *mpp, char * params, int ro, uint16_t udev_flags) { int r = 0; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; char __attribute__((cleanup(cleanup_charp))) *prefixed_uuid = NULL; uint32_t cookie = 0; if (task == DM_DEVICE_CREATE && strlen(mpp->wwid) == 0) { condlog(1, "%s: refusing to create map with empty WWID", mpp->alias); return 0; } /* Need to add this here to allow 0 to be passed in udev_flags */ udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (!(dmt = libmp_dm_task_create (task))) return 0; if (!dm_task_set_name (dmt, mpp->alias)) return 0; if (!dm_task_add_target (dmt, 0, mpp->size, target, params)) return 0; if (ro) dm_task_set_ro(dmt); if (task == DM_DEVICE_CREATE) { if (asprintf(&prefixed_uuid, UUID_PREFIX "%s", mpp->wwid) < 0) { condlog(0, "cannot create prefixed uuid : %s", strerror(errno)); return 0; } if (!dm_task_set_uuid(dmt, prefixed_uuid)) return 0; dm_task_skip_lockfs(dmt); #ifdef LIBDM_API_FLUSH dm_task_no_flush(dmt); #endif } if (mpp->attribute_flags & (1 << ATTR_MODE) && !dm_task_set_mode(dmt, mpp->mode)) return 0; if (mpp->attribute_flags & (1 << ATTR_UID) && !dm_task_set_uid(dmt, mpp->uid)) return 0; if (mpp->attribute_flags & (1 << ATTR_GID) && !dm_task_set_gid(dmt, mpp->gid)) return 0; condlog(2, "%s: %s [0 %llu %s %s]", mpp->alias, task == DM_DEVICE_RELOAD ? "reload" : "addmap", mpp->size, target, params); if (task == DM_DEVICE_CREATE && !dm_task_set_cookie(dmt, &cookie, udev_flags)) return 0; r = libmp_dm_task_run (dmt); if (!r) dm_log_error(2, task, dmt); if (task == DM_DEVICE_CREATE) libmp_udev_wait(cookie); if (r) mpp->need_reload = false; return r; } static uint16_t build_udev_flags(const struct multipath *mpp, int reload) { /* DM_UDEV_DISABLE_LIBRARY_FALLBACK is added in dm_addmap */ return (mpp->skip_kpartx == SKIP_KPARTX_ON ? MPATH_UDEV_NO_KPARTX_FLAG : 0) | ((count_active_pending_paths(mpp) == 0 || mpp->ghost_delay_tick > 0) ? MPATH_UDEV_NO_PATHS_FLAG : 0) | (reload && !mpp->force_udev_reload ? MPATH_UDEV_RELOAD_FLAG : 0); } int dm_addmap_create (struct multipath *mpp, char *params) { int ro; uint16_t udev_flags = build_udev_flags(mpp, 0); for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { int err; if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, params, ro, udev_flags)) { unmark_failed_wwid(mpp->wwid); return 1; } /* * DM_DEVICE_CREATE is actually DM_DEV_CREATE + DM_TABLE_LOAD. * Failing the second part leaves an empty map. Clean it up. */ err = errno; if (libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = mpp->alias }, (mapinfo_t) { .uuid = NULL }) == DMP_EMPTY) { condlog(3, "%s: failed to load map (a path might be in use)", mpp->alias); dm_device_remove(mpp->alias, 0); } if (err != EROFS) { condlog(3, "%s: failed to load map, error %d", mpp->alias, err); break; } } mark_failed_wwid(mpp->wwid); return 0; } #define ADDMAP_RW 0 #define ADDMAP_RO 1 int dm_addmap_reload(struct multipath *mpp, char *params, int flush) { int r = 0; uint16_t udev_flags = build_udev_flags(mpp, 1); /* * DM_DEVICE_RELOAD cannot wait on a cookie, as * the cookie will only ever be released after an * DM_DEVICE_RESUME. So call DM_DEVICE_RESUME * after each successful call to DM_DEVICE_RELOAD. */ if (!mpp->force_readonly) r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, ADDMAP_RW, 0); if (!r) { if (!mpp->force_readonly && errno != EROFS) return 0; r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, ADDMAP_RO, 0); } if (r) r = dm_simplecmd(DM_DEVICE_RESUME, mpp->alias, DMFL_NEED_SYNC | (flush ? 0 : DMFL_NO_FLUSH), udev_flags); if (r) return r; /* If the resume failed, dm will leave the device suspended, and * drop the new table, so doing a second resume will try using * the original table */ if (dm_is_suspended(mpp->alias)) dm_simplecmd(DM_DEVICE_RESUME, mpp->alias, DMFL_NEED_SYNC | (flush ? 0 : DMFL_NO_FLUSH), udev_flags); return 0; } static bool is_mpath_uuid(const char uuid[DM_UUID_LEN]) { return !strncmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN); } static bool is_mpath_part_uuid(const char part_uuid[DM_UUID_LEN], const char map_uuid[DM_UUID_LEN]) { char c; int np, nc; if (2 != sscanf(part_uuid, "part%d-%n" UUID_PREFIX "%c", &np, &nc, &c) || np <= 0) return false; return map_uuid == NULL || !strcmp(part_uuid + nc, map_uuid); } bool has_dm_info(const struct multipath *mpp) { return (mpp && mpp->dmi.exists != 0); } static int libmp_set_map_identifier(int flags, mapid_t id, struct dm_task *dmt) { switch (flags & DM_MAP_BY_MASK__) { case DM_MAP_BY_UUID: if (!id.str || !(*id.str)) return 0; return dm_task_set_uuid(dmt, id.str); case DM_MAP_BY_NAME: if (!id.str || !(*id.str)) return 0; return dm_task_set_name(dmt, id.str); case DM_MAP_BY_DEV: if (!dm_task_set_major(dmt, id._u.major)) return 0; return dm_task_set_minor(dmt, id._u.minor); case DM_MAP_BY_DEVT: if (!dm_task_set_major(dmt, major(id.devt))) return 0; return dm_task_set_minor(dmt, minor(id.devt)); default: condlog(0, "%s: invalid by_id", __func__); return 0; } } static int libmp_mapinfo__(int flags, mapid_t id, mapinfo_t info, const char *map_id) { /* avoid libmp_mapinfo__ in log messages */ static const char fname__[] = "libmp_mapinfo"; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_info dmi; int rc, ioctl_nr; uint64_t start, length = 0; char *target_type = NULL, *params = NULL; const char *name = NULL, *uuid = NULL; char __attribute__((cleanup(cleanup_charp))) *tmp_target = NULL; char __attribute__((cleanup(cleanup_charp))) *tmp_status = NULL; bool tgt_set = false; /* * If both info.target and info.status are set, we need two * ioctls. Call this function recursively. * If successful, tmp_target will be non-NULL. */ if (info.target && info.status) { rc = libmp_mapinfo__(flags, id, (mapinfo_t) { .target = &tmp_target }, map_id); if (rc != DMP_OK) return rc; tgt_set = true; } /* * The DM_DEVICE_TABLE and DM_DEVICE_STATUS ioctls both fetch the basic * information from DM_DEVICE_INFO, too. * Choose the most lightweight ioctl to fetch all requested info. */ if (info.target && !info.status) ioctl_nr = DM_DEVICE_TABLE; else if (info.status || info.size || flags & MAPINFO_TGT_TYPE__) ioctl_nr = DM_DEVICE_STATUS; else ioctl_nr = DM_DEVICE_INFO; if (!(dmt = libmp_dm_task_create(ioctl_nr))) return DMP_ERR; if (!libmp_set_map_identifier(flags, id, dmt)) { condlog(2, "%s: failed to set map identifier to %s", fname__, map_id); return DMP_ERR; } if (!libmp_dm_task_run(dmt)) { dm_log_error(3, ioctl_nr, dmt); if (dm_task_get_errno(dmt) == ENXIO) { condlog(2, "%s: map %s not found", fname__, map_id); return DMP_NOT_FOUND; } else return DMP_ERR; } condlog(4, "%s: DM ioctl %d succeeded for %s", fname__, ioctl_nr, map_id); if (!dm_task_get_info(dmt, &dmi)) { condlog(2, "%s: dm_task_get_info() failed for %s ", fname__, map_id); return DMP_ERR; } else if(!dmi.exists) { condlog(3, "%s: map %s doesn't exist", fname__, map_id); return DMP_NOT_FOUND; } if ((info.name && !(name = dm_task_get_name(dmt))) || ((info.uuid || flags & MAPINFO_CHECK_UUID) && !(uuid = dm_task_get_uuid(dmt)))) return DMP_ERR; if (info.name) { strlcpy(info.name, name, WWID_SIZE); condlog(4, "%s: %s: name: \"%s\"", fname__, map_id, info.name); } if (info.uuid) { strlcpy(info.uuid, uuid, DM_UUID_LEN); condlog(4, "%s: %s: uuid: \"%s\"", fname__, map_id, info.uuid); } if (info.dmi) { memcpy(info.dmi, &dmi, sizeof(*info.dmi)); condlog(4, "%s: %s %d:%d, %d targets, %s table, %s, %s, %d opened, %u events", fname__, map_id, info.dmi->major, info.dmi->minor, info.dmi->target_count, info.dmi->live_table ? "live" : info.dmi->inactive_table ? "inactive" : "no", info.dmi->suspended ? "suspended" : "active", info.dmi->read_only ? "ro" : "rw", info.dmi->open_count, info.dmi->event_nr); } if (flags & MAPINFO_CHECK_UUID && ((flags & MAPINFO_PART_ONLY && !is_mpath_part_uuid(uuid, NULL)) || (!(flags & MAPINFO_PART_ONLY) && !is_mpath_uuid(uuid)))) { condlog(4, "%s: UUID mismatch: %s", fname__, uuid); return DMP_NO_MATCH; } if (info.target || info.status || info.size || flags & MAPINFO_TGT_TYPE__) { int lvl = MAPINFO_CHECK_UUID ? 2 : 4; if (dm_get_next_target(dmt, NULL, &start, &length, &target_type, ¶ms) != NULL) { condlog(lvl, "%s: map %s has multiple targets", fname__, map_id); return DMP_NO_MATCH; } if (!params || !target_type) { condlog(lvl, "%s: map %s has no targets", fname__, map_id); return DMP_EMPTY; } if (flags & MAPINFO_TGT_TYPE__) { const char *tgt_type = flags & MAPINFO_MPATH_ONLY ? TGT_MPATH : TGT_PART; if (strcmp(target_type, tgt_type)) { condlog(lvl, "%s: target type mismatch: \"%s\" != \"%s\"", fname__, tgt_type, target_type); return DMP_NO_MATCH; } } } /* * Check possible error conditions. */ if ((info.status && !(tmp_status = strdup(params))) || (info.target && !tmp_target && !(tmp_target = strdup(params)))) return DMP_ERR; if (info.size) { *info.size = length; condlog(4, "%s: %s: size: %lld", fname__, map_id, *info.size); } if (info.target) { *info.target = steal_ptr(tmp_target); if (!tgt_set) condlog(4, "%s: %s: target: \"%s\"", fname__, map_id, *info.target); } if (info.status) { *info.status = steal_ptr(tmp_status); condlog(4, "%s: %s: status: \"%s\"", fname__, map_id, *info.status); } return DMP_OK; } /* Helper: format a string describing the map for log messages */ static const char* libmp_map_identifier(int flags, mapid_t id, char buf[BLK_DEV_SIZE]) { switch (flags & DM_MAP_BY_MASK__) { case DM_MAP_BY_NAME: case DM_MAP_BY_UUID: return id.str; case DM_MAP_BY_DEV: safe_snprintf(buf, BLK_DEV_SIZE, "%d:%d", id._u.major, id._u.minor); return buf; case DM_MAP_BY_DEVT: safe_snprintf(buf, BLK_DEV_SIZE, "%d:%d", major(id.devt), minor(id.devt)); return buf; default: safe_snprintf(buf, BLK_DEV_SIZE, "*invalid*"); return buf; } } int libmp_mapinfo(int flags, mapid_t id, mapinfo_t info) { char idbuf[BLK_DEV_SIZE]; return libmp_mapinfo__(flags, id, info, libmp_map_identifier(flags, id, idbuf)); } /** * dm_get_wwid(): return WWID for a multipath map * @returns: * DMP_OK if successful * DMP_NOT_FOUND if the map doesn't exist * DMP_NO_MATCH if the map exists but is not a multipath map * DMP_ERR for other errors * Caller may access uuid if and only if DMP_OK is returned. */ int dm_get_wwid(const char *name, char *uuid, int uuid_len) { char tmp[DM_UUID_LEN]; int rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = name }, (mapinfo_t) { .uuid = tmp }); if (rc != DMP_OK) return rc; strlcpy(uuid, tmp + UUID_PREFIX_LEN, uuid_len); return DMP_OK; } int dm_is_mpath(const char *name) { int rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = name }, (mapinfo_t) { .uuid = NULL }); switch (rc) { case DMP_OK: return DM_IS_MPATH_YES; case DMP_NOT_FOUND: case DMP_NO_MATCH: case DMP_EMPTY: return DM_IS_MPATH_NO; case DMP_ERR: default: return DM_IS_MPATH_ERR; } } /* if name is non-NULL, it must point to an array of WWID_SIZE bytes */ int dm_find_map_by_wwid(const char *wwid, char *name, struct dm_info *dmi) { char tmp[DM_UUID_LEN]; if (safe_sprintf(tmp, UUID_PREFIX "%s", wwid)) return DMP_ERR; return libmp_mapinfo(DM_MAP_BY_UUID | MAPINFO_MPATH_ONLY, (mapid_t) { .str = tmp }, (mapinfo_t) { .name = name, .dmi = dmi }); } static int dm_dev_t (const char *mapname, char *dev_t, int len) { struct dm_info info; if (dm_get_info(mapname, &info) != DMP_OK) return 1; if (safe_snprintf(dev_t, len, "%i:%i", info.major, info.minor)) return 1; return 0; } int dm_get_opencount (const char *mapname) { struct dm_info info; if (dm_get_info(mapname, &info) != DMP_OK) return -1; return info.open_count; } int dm_get_major_minor(const char *name, int *major, int *minor) { struct dm_info info; if (dm_get_info(name, &info) != DMP_OK) return -1; *major = info.major; *minor = info.minor; return 0; } static int has_partmap(const char *name __attribute__((unused)), void *data __attribute__((unused))) { return 1; } /* * This will be called from mpath_in_use, for each partition. * If the partition itself in use, returns 1 immediately, causing * do_foreach_partmaps() to stop iterating and return 1. * Otherwise, increases the partition count if the partition has a * table (live or inactive). Devices with no table don't count * towards the multipath device open count. */ static int count_partitions(const char *name, void *data) { struct dm_info info; int *ret_count = (int *)data; if (dm_get_info(name, &info) != DMP_OK) return 1; if (info.open_count) return 1; if (info.live_table || info.inactive_table) (*ret_count)++; return 0; } int mpath_in_use(const char *name) { int open_count = dm_get_opencount(name); if (open_count < 0) { condlog(0, "%s: %s: failed to get open count, assuming in use", __func__, name); return 1; } if (open_count) { int part_count = 0; if (do_foreach_partmaps(name, count_partitions, &part_count)) { condlog(4, "%s: %s has open partitions", __func__, name); return 1; } condlog(4, "%s: %s: %d openers, %d partitions", __func__, name, open_count, part_count); return open_count > part_count; } return 0; } int dm_flush_map__ (const char *mapname, int flags, int retries) { int r; int queue_if_no_path = 0; int udev_flags = 0; char *params __attribute__((cleanup(cleanup_charp))) = NULL; r = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = mapname }, (mapinfo_t) { .target = ¶ms }); if (r != DMP_OK && r != DMP_EMPTY) return DM_FLUSH_OK; /* nothing to do */ /* device mapper will not let you resume an empty device */ if (r == DMP_EMPTY) flags &= ~DMFL_SUSPEND; /* if the device currently has no partitions, do not run kpartx on it if you fail to delete it */ if (do_foreach_partmaps(mapname, has_partmap, NULL) == 0) udev_flags |= MPATH_UDEV_NO_KPARTX_FLAG; /* If you aren't doing a deferred remove, make sure that no * devices are in use */ if (!(flags & DMFL_DEFERRED) && mpath_in_use(mapname)) return DM_FLUSH_BUSY; if ((flags & DMFL_SUSPEND) && strstr(params, "queue_if_no_path")) { if (!_dm_queue_if_no_path(mapname, 0)) queue_if_no_path = 1; else /* Leave queue_if_no_path alone if unset failed */ queue_if_no_path = -1; } if ((r = dm_remove_partmaps(mapname, flags))) return r; if (!(flags & DMFL_DEFERRED) && dm_get_opencount(mapname)) { condlog(2, "%s: map in use", mapname); return DM_FLUSH_BUSY; } do { if ((flags & DMFL_SUSPEND) && queue_if_no_path != -1) dm_simplecmd_flush(DM_DEVICE_SUSPEND, mapname, 0); r = dm_device_remove(mapname, flags); if (r) { if ((flags & DMFL_DEFERRED) && dm_map_present(mapname)) { condlog(4, "multipath map %s remove deferred", mapname); return DM_FLUSH_DEFERRED; } condlog(4, "multipath map %s removed", mapname); return DM_FLUSH_OK; } else if (dm_is_mpath(mapname) != DM_IS_MPATH_YES) { condlog(4, "multipath map %s removed externally", mapname); return DM_FLUSH_OK; /* raced. someone else removed it */ } else { condlog(2, "failed to remove multipath map %s", mapname); if ((flags & DMFL_SUSPEND) && queue_if_no_path != -1) { dm_simplecmd_noflush(DM_DEVICE_RESUME, mapname, udev_flags); } } if (retries) sleep(1); } while (retries-- > 0); if (queue_if_no_path == 1 && _dm_queue_if_no_path(mapname, 1) != 0) return DM_FLUSH_FAIL_CANT_RESTORE; return DM_FLUSH_FAIL; } int dm_flush_map_nopaths(const char *mapname, int deferred_remove DR_UNUSED__) { int flags = DMFL_NEED_SYNC; #ifdef LIBDM_API_DEFERRED flags |= ((deferred_remove == DEFERRED_REMOVE_ON || deferred_remove == DEFERRED_REMOVE_IN_PROGRESS) ? DMFL_DEFERRED : 0); #endif return dm_flush_map__(mapname, flags, 0); } int dm_flush_maps(int retries) { int r = DM_FLUSH_FAIL; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_names *names; unsigned next = 0; if (!(dmt = libmp_dm_task_create (DM_DEVICE_LIST))) return r; if (!libmp_dm_task_run (dmt)) { dm_log_error(3, DM_DEVICE_LIST, dmt); return r; } if (!(names = dm_task_get_names (dmt))) return r; r = DM_FLUSH_OK; if (!names->dev) return r; do { int ret; ret = dm_suspend_and_flush_map(names->name, retries); if (ret == DM_FLUSH_FAIL || (r != DM_FLUSH_FAIL && ret == DM_FLUSH_BUSY)) r = ret; next = names->next; names = (void *) names + next; } while (next); return r; } int dm_message(const char * mapname, char * message) { struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; if (!(dmt = libmp_dm_task_create(DM_DEVICE_TARGET_MSG))) return 1; if (!dm_task_set_name(dmt, mapname)) goto out; if (!dm_task_set_sector(dmt, 0)) goto out; if (!dm_task_set_message(dmt, message)) goto out; if (!libmp_dm_task_run(dmt)) { dm_log_error(2, DM_DEVICE_TARGET_MSG, dmt); goto out; } return 0; out: condlog(0, "DM message failed [%s]", message); return 1; } int dm_fail_path(const char * mapname, char * path) { char message[32]; if (snprintf(message, 32, "fail_path %s", path) > 32) return 1; return dm_message(mapname, message); } int dm_reinstate_path(const char * mapname, char * path) { char message[32]; if (snprintf(message, 32, "reinstate_path %s", path) > 32) return 1; return dm_message(mapname, message); } static int _dm_queue_if_no_path(const char *mapname, int enable) { char *message; if (enable) message = "queue_if_no_path"; else message = "fail_if_no_path"; return dm_message(mapname, message); } int dm_queue_if_no_path(struct multipath *mpp, int enable) { int r; static const char no_path_retry[] = "queue_if_no_path"; if ((r = _dm_queue_if_no_path(mpp->alias, enable)) == 0) { if (enable) add_feature(&mpp->features, no_path_retry); else remove_feature(&mpp->features, no_path_retry); } return r; } static int dm_groupmsg (const char * msg, const char * mapname, int index) { char message[32]; if (snprintf(message, 32, "%s_group %i", msg, index) > 32) return 1; return dm_message(mapname, message); } int dm_switchgroup(const char * mapname, int index) { return dm_groupmsg("switch", mapname, index); } int dm_enablegroup(const char * mapname, int index) { return dm_groupmsg("enable", mapname, index); } int dm_disablegroup(const char * mapname, int index) { return dm_groupmsg("disable", mapname, index); } static int dm_get_multipath(const char *name, struct multipath **pmpp) { struct multipath __attribute__((cleanup(cleanup_multipath))) *mpp = NULL; char uuid[DM_UUID_LEN]; int rc; mpp = alloc_multipath(); if (!mpp) return DMP_ERR; mpp->alias = strdup(name); if (!mpp->alias) return DMP_ERR; if ((rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID | MAPINFO_MPATH_ONLY, (mapid_t) { .str = name }, (mapinfo_t) { .size = &mpp->size, .uuid = uuid, .dmi = &mpp->dmi, })) != DMP_OK) return rc; strlcpy(mpp->wwid, uuid + UUID_PREFIX_LEN, sizeof(mpp->wwid)); *pmpp = steal_ptr(mpp); return DMP_OK; } int dm_get_maps(vector mp) { struct multipath *mpp = NULL; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_names *names; unsigned next = 0; if (!mp) return 1; if (!(dmt = libmp_dm_task_create(DM_DEVICE_LIST))) return 1; if (!libmp_dm_task_run(dmt)) { dm_log_error(3, DM_DEVICE_LIST, dmt); return 1; } if (!(names = dm_task_get_names(dmt))) return 1; if (!names->dev) { /* this is perfectly valid */ return 0; } do { switch (dm_get_multipath(names->name, &mpp)) { case DMP_OK: if (!vector_alloc_slot(mp)) { free_multipath(mpp, KEEP_PATHS); return 1; } vector_set_slot(mp, mpp); break; default: break; } next = names->next; names = (void *) names + next; } while (next); return 0; } int dm_geteventnr (const char *name) { struct dm_info info; if (dm_get_info(name, &info) != DMP_OK) return -1; return info.event_nr; } int dm_is_suspended(const char *name) { struct dm_info info; if (dm_get_info(name, &info) != DMP_OK) return -1; return info.suspended; } char *dm_mapname(int major, int minor) { char name[WWID_SIZE]; if (libmp_mapinfo(DM_MAP_BY_DEV, (mapid_t) { ._u = { major, minor } }, (mapinfo_t) { .name = name }) != DMP_OK) return NULL; return strdup(name); } static bool is_valid_partmap(const char *name, const char *map_dev_t, const char *map_uuid) { int r; char __attribute__((cleanup(cleanup_charp))) *params = NULL; char *p; char part_uuid[DM_UUID_LEN]; r = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = name }, (mapinfo_t) { .uuid = part_uuid, .target = ¶ms}); /* There must be a single linear target or an empty map. */ if (r != DMP_OK && r != DMP_EMPTY) return false; /* * and the uuid of the target must be a partition of the uuid of the * multipath device */ if (!is_mpath_part_uuid(part_uuid, map_uuid)) return false; if (r == DMP_EMPTY) return true; /* and if the table isn't empty it must map over the multipath map */ return ((p = strstr(params, map_dev_t)) && !isdigit(*(p + strlen(map_dev_t)))); } static int do_foreach_partmaps (const char *mapname, int (*partmap_func)(const char *, void *), void *data) { struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_names *names; unsigned next = 0; char dev_t[BLK_DEV_SIZE]; char map_uuid[DM_UUID_LEN]; struct dm_info info; if (libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = mapname }, (mapinfo_t) { .uuid = map_uuid, .dmi = &info }) != DMP_OK) return 1; if (safe_sprintf(dev_t, "%i:%i", info.major, info.minor)) return 1; if (!(dmt = libmp_dm_task_create(DM_DEVICE_LIST))) return 1; if (!libmp_dm_task_run(dmt)) return 1; if (!(names = dm_task_get_names(dmt))) return 1; if (!names->dev) /* this is perfectly valid */ return 0; do { if (is_valid_partmap(names->name, dev_t, map_uuid) && (partmap_func(names->name, data) != 0)) return 1; next = names->next; names = (void*) names + next; } while (next); return 0; } struct remove_data { int flags; }; static int remove_partmap(const char *name, void *data) { struct remove_data *rd = (struct remove_data *)data; if (!(rd->flags & DMFL_DEFERRED) && dm_get_opencount(name)) { condlog(2, "%s: map in use", name); return DM_FLUSH_BUSY; } condlog(3, "partition map %s removed", name); dm_device_remove(name, rd->flags); return DM_FLUSH_OK; } static int dm_remove_partmaps (const char * mapname, int flags) { struct remove_data rd = { flags }; return do_foreach_partmaps(mapname, remove_partmap, &rd); } #ifdef LIBDM_API_DEFERRED static int cancel_remove_partmap (const char *name, void *unused __attribute__((unused))) { if (dm_message(name, "@cancel_deferred_remove") != 0) condlog(0, "%s: can't cancel deferred remove: %s", name, strerror(errno)); return 0; } static int dm_get_deferred_remove (const char * mapname) { struct dm_info info; if (dm_get_info(mapname, &info) != DMP_OK) return -1; return info.deferred_remove; } static int dm_cancel_remove_partmaps(const char * mapname) { return do_foreach_partmaps(mapname, cancel_remove_partmap, NULL); } int dm_cancel_deferred_remove (struct multipath *mpp) { int r = 0; if (!dm_get_deferred_remove(mpp->alias)) return 0; if (mpp->deferred_remove == DEFERRED_REMOVE_IN_PROGRESS) mpp->deferred_remove = DEFERRED_REMOVE_ON; dm_cancel_remove_partmaps(mpp->alias); r = dm_message(mpp->alias, "@cancel_deferred_remove"); if (r) condlog(0, "%s: can't cancel deferred remove: %s", mpp->alias, strerror(errno)); else condlog(2, "%s: canceled deferred remove", mpp->alias); return r; } #else int dm_cancel_deferred_remove (struct multipath *mpp __attribute__((unused))) { return 0; } #endif struct rename_data { const char *old; char *new; char *delim; }; static int dm_rename__(const char *old, char *new, int skip_kpartx) { int r = 0; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; uint32_t cookie = 0; uint16_t udev_flags = DM_UDEV_DISABLE_LIBRARY_FALLBACK | ((skip_kpartx == SKIP_KPARTX_ON)? MPATH_UDEV_NO_KPARTX_FLAG : 0); if (!(dmt = libmp_dm_task_create(DM_DEVICE_RENAME))) return r; if (!dm_task_set_name(dmt, old)) return r; if (!dm_task_set_newname(dmt, new)) return r; if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) return r; r = libmp_dm_task_run(dmt); if (!r) dm_log_error(2, DM_DEVICE_RENAME, dmt); libmp_udev_wait(cookie); return r; } static int rename_partmap (const char *name, void *data) { char *buff = NULL; int offset; struct rename_data *rd = (struct rename_data *)data; if (strncmp(name, rd->old, strlen(rd->old)) != 0) return 0; for (offset = strlen(rd->old); name[offset] && !(isdigit(name[offset])); offset++); /* do nothing */ if (asprintf(&buff, "%s%s%s", rd->new, rd->delim, name + offset) >= 0) { dm_rename__(name, buff, SKIP_KPARTX_OFF); free(buff); condlog(4, "partition map %s renamed", name); } else condlog(1, "failed to rename partition map %s", name); return 0; } int dm_rename_partmaps (const char * old, char * new, char *delim) { struct rename_data rd; rd.old = old; rd.new = new; if (delim) rd.delim = delim; else { if (isdigit(new[strlen(new)-1])) rd.delim = "p"; else rd.delim = ""; } return do_foreach_partmaps(old, rename_partmap, &rd); } int dm_rename (const char * old, char * new, char *delim, int skip_kpartx) { if (dm_rename_partmaps(old, new, delim)) return 0; return dm_rename__(old, new, skip_kpartx); } void dm_reassign_deps(char *table, const char *dep, const char *newdep) { char *n, *newtable; const char *p; newtable = strdup(table); if (!newtable) return; p = strstr(newtable, dep); n = table + (p - newtable); strcpy(n, newdep); n += strlen(newdep); p += strlen(dep); strcat(n, p); free(newtable); } int dm_reassign_table(const char *name, char *old, char *new) { int modified = 0; uint64_t start, length; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *reload_dmt = NULL; char *target, *params = NULL; char *buff; void *next = NULL; if (!(dmt = libmp_dm_task_create(DM_DEVICE_TABLE))) return 0; if (!dm_task_set_name(dmt, name)) return 0; if (!libmp_dm_task_run(dmt)) { dm_log_error(3, DM_DEVICE_TABLE, dmt); return 0; } if (!(reload_dmt = libmp_dm_task_create(DM_DEVICE_RELOAD))) return 0; if (!dm_task_set_name(reload_dmt, name)) return 0; do { next = dm_get_next_target(dmt, next, &start, &length, &target, ¶ms); if (!target || !params) { /* * We can't call dm_task_add_target() with * invalid parameters. But simply dropping this * target feels wrong, too. Abort and warn. */ condlog(1, "%s: invalid target found in map %s", __func__, name); return 0; } buff = strdup(params); if (!buff) { condlog(3, "%s: failed to replace target %s, " "out of memory", name, target); return 0; } if (strcmp(target, TGT_MPATH) && strstr(params, old)) { condlog(3, "%s: replace target %s %s", name, target, buff); dm_reassign_deps(buff, old, new); condlog(3, "%s: with target %s %s", name, target, buff); modified++; } dm_task_add_target(reload_dmt, start, length, target, buff); free(buff); } while (next); if (modified) { if (!libmp_dm_task_run(reload_dmt)) { dm_log_error(3, DM_DEVICE_RELOAD, reload_dmt); condlog(3, "%s: failed to reassign targets", name); return 0; } dm_simplecmd_noflush(DM_DEVICE_RESUME, name, MPATH_UDEV_RELOAD_FLAG); } return 1; } /* * Reassign existing device-mapper table(s) to not use * the block devices but point to the multipathed * device instead */ int dm_reassign(const char *mapname) { struct dm_deps *deps; struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct dm_info info; char dev_t[32], dm_dep[32]; unsigned int i; if (dm_dev_t(mapname, &dev_t[0], 32)) { condlog(3, "%s: failed to get device number", mapname); return 1; } if (!(dmt = libmp_dm_task_create(DM_DEVICE_DEPS))) { condlog(3, "%s: couldn't make dm task", mapname); return 0; } if (!dm_task_set_name(dmt, mapname)) return 0; if (!libmp_dm_task_run(dmt)) { dm_log_error(3, DM_DEVICE_DEPS, dmt); return 0; } if (!dm_task_get_info(dmt, &info)) return 0; if (!(deps = dm_task_get_deps(dmt))) return 0; if (!info.exists) return 0; for (i = 0; i < deps->count; i++) { sprintf(dm_dep, "%d:%d", major(deps->device[i]), minor(deps->device[i])); sysfs_check_holders(dm_dep, dev_t); } return 1; } int dm_setgeometry(struct multipath *mpp) { struct dm_task __attribute__((cleanup(cleanup_dm_task))) *dmt = NULL; struct path *pp; char heads[4], sectors[4]; char cylinders[10], start[32]; int r = 0; if (!mpp) return 1; pp = first_path(mpp); if (!pp) { condlog(3, "%s: no path for geometry", mpp->alias); return 1; } if (pp->geom.cylinders == 0 || pp->geom.heads == 0 || pp->geom.sectors == 0) { condlog(3, "%s: invalid geometry on %s", mpp->alias, pp->dev); return 1; } if (!(dmt = libmp_dm_task_create(DM_DEVICE_SET_GEOMETRY))) return 0; if (!dm_task_set_name(dmt, mpp->alias)) return 0; /* What a sick interface ... */ snprintf(heads, 4, "%u", pp->geom.heads); snprintf(sectors, 4, "%u", pp->geom.sectors); snprintf(cylinders, 10, "%u", pp->geom.cylinders); snprintf(start, 32, "%lu", pp->geom.start); if (!dm_task_set_geometry(dmt, cylinders, heads, sectors, start)) { condlog(3, "%s: Failed to set geometry", mpp->alias); return 0; } r = libmp_dm_task_run(dmt); if (!r) dm_log_error(3, DM_DEVICE_SET_GEOMETRY, dmt); return r; } multipath-tools-0.11.1/libmultipath/devmapper.h000066400000000000000000000160241475246302400216030ustar00rootroot00000000000000#ifndef DEVMAPPER_H_INCLUDED #define DEVMAPPER_H_INCLUDED #include #include #include "autoconfig.h" #include "structs.h" #define TGT_MPATH "multipath" #define TGT_PART "linear" #ifdef DM_SUBSYSTEM_UDEV_FLAG0 #define MPATH_UDEV_RELOAD_FLAG DM_SUBSYSTEM_UDEV_FLAG0 #else #define MPATH_UDEV_RELOAD_FLAG 0 #endif #ifdef DM_SUBSYSTEM_UDEV_FLAG1 #define MPATH_UDEV_NO_KPARTX_FLAG DM_SUBSYSTEM_UDEV_FLAG1 #else #define MPATH_UDEV_NO_KPARTX_FLAG 0 #endif #ifdef DM_SUBSYSTEM_UDEV_FLAG2 #define MPATH_UDEV_NO_PATHS_FLAG DM_SUBSYSTEM_UDEV_FLAG2 #else #define MPATH_UDEV_NO_PATHS_FLAG 0 #endif #define UUID_PREFIX "mpath-" #define UUID_PREFIX_LEN (sizeof(UUID_PREFIX) - 1) enum { DMP_ERR, DMP_OK, DMP_NOT_FOUND, DMP_NO_MATCH, DMP_EMPTY, DMP_LAST__, }; const char* dmp_errstr(int rc); /** * input flags for libmp_mapinfo() */ enum { /** DM_MAP_BY_NAME: identify map by device-mapper name from @name */ DM_MAP_BY_NAME = 0, /** DM_MAP_BY_UUID: identify map by device-mapper UUID from @uuid */ DM_MAP_BY_UUID, /** DM_MAP_BY_DEV: identify map by major/minor number from @dmi */ DM_MAP_BY_DEV, /** DM_MAP_BY_DEVT: identify map by a dev_t */ DM_MAP_BY_DEVT, DM_MAP_BY_MASK__ = (1 << 8) - 1, /* Fail if target type is not multipath */ MAPINFO_MPATH_ONLY = (1 << 8), /* Fail if target type is not "partition" (linear) */ MAPINFO_PART_ONLY = (1 << 9), MAPINFO_TGT_TYPE__ = (MAPINFO_MPATH_ONLY | MAPINFO_PART_ONLY), /* * Fail if the UUID doesn't match the expected UUID format * If combined with MAPINFO_PART_ONLY, checks for partition UUID format * ("part-mpath-xyz"). * Otherwise (whether or not MAPINFO_MPATH_ONLY is set) checks for * multipath UUID format ("mpath-xyz"). */ MAPINFO_CHECK_UUID = (1 << 10), }; typedef union libmp_map_identifier { const char *str; struct { int major; int minor; } _u; dev_t devt; } mapid_t; typedef struct libmp_map_info { /** @name: name of the map. * If non-NULL, it must point to an array of WWID_SIZE bytes */ char *name; /** @uuid: UUID of the map. * If non-NULL it must point to an array of DM_UUID_LEN bytes */ char *uuid; /** @dmi: Basic info, must point to a valid dm_info buffer if non-NULL */ struct dm_info *dmi; /** @target: target params, *@target will be allocated if @target is non-NULL*/ char **target; /** @size: target size. */ unsigned long long *size; /** @status: target status, *@status will be allocated if @status is non-NULL */ char **status; } mapinfo_t; /** * libmp_mapinfo(): obtain information about a map from the kernel * @param flags: see enum values above. * Exactly one of DM_MAP_BY_NAME, DM_MAP_BY_UUID, and DM_MAP_BY_DEV must be set. * @param id: string or major/minor to identify the map to query * @param info: output parameters, see above. Non-NULL elements will be filled in. * @returns: * DMP_OK if successful. * DMP_NOT_FOUND if the map wasn't found, or has no or multiple targets. * DMP_NO_MATCH if the map didn't match @tgt_type (see above) or didn't * have a multipath uuid prefix. * DMP_EMPTY if the map has no table. Note. The check for matching uuid * prefix will happen first, but the check for matching * tgt_type will happen afterwards. * DMP_ERR if some other error occurred. * * This function obtains the requested information for the device-mapper map * identified by the input parameters. * If non-NULL, the name, uuid, and dmi output paramters may be filled in for * any return value besides DMP_NOT_FOUND and will always be filled in for * return values other than DMP_NOT_FOUND and DMP_ERR. * The other parameters are only filled in if the return value is DMP_OK. * For target / status / size information, the map's table should contain * only one target (usually multipath or linear). */ int libmp_mapinfo(int flags, mapid_t id, mapinfo_t info); static inline int dm_get_info(const char *mapname, struct dm_info *info) { return libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = mapname }, (mapinfo_t) { .dmi = info }); } static inline int dm_map_present(const char *mapname) { return libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = mapname }, (mapinfo_t) { .name = NULL }) == DMP_OK; } int dm_prereq(unsigned int *v); void skip_libmp_dm_init(void); void libmp_dm_exit(void); void libmp_udev_set_sync_support(int on); struct dm_task *libmp_dm_task_create(int task); int dm_simplecmd_flush (int task, const char *name, uint16_t udev_flags); int dm_simplecmd_noflush (int task, const char *name, uint16_t udev_flags); int dm_addmap_create (struct multipath *mpp, char *params); int dm_addmap_reload (struct multipath *mpp, char *params, int flush); int dm_find_map_by_wwid(const char *wwid, char *name, struct dm_info *dmi); enum { DM_IS_MPATH_NO, DM_IS_MPATH_YES, DM_IS_MPATH_ERR, }; int dm_is_mpath(const char *name); enum { DM_FLUSH_OK = 0, DM_FLUSH_FAIL, DM_FLUSH_FAIL_CANT_RESTORE, DM_FLUSH_DEFERRED, DM_FLUSH_BUSY, }; int mpath_in_use(const char *name); enum { DMFL_NONE = 0, DMFL_NEED_SYNC = 1 << 0, DMFL_DEFERRED = 1 << 1, DMFL_SUSPEND = 1 << 2, DMFL_NO_FLUSH = 1 << 3, }; int dm_flush_map__ (const char *mapname, int flags, int retries); #define dm_flush_map(mapname) dm_flush_map__(mapname, DMFL_NEED_SYNC, 0) #define dm_suspend_and_flush_map(mapname, retries) \ dm_flush_map__(mapname, DMFL_NEED_SYNC|DMFL_SUSPEND, retries) int dm_flush_map_nopaths(const char * mapname, int deferred_remove); int dm_cancel_deferred_remove(struct multipath *mpp); int dm_flush_maps (int retries); int dm_fail_path(const char * mapname, char * path); int dm_reinstate_path(const char * mapname, char * path); int dm_queue_if_no_path(struct multipath *mpp, int enable); int dm_switchgroup(const char * mapname, int index); int dm_enablegroup(const char * mapname, int index); int dm_disablegroup(const char * mapname, int index); int dm_get_maps (vector mp); int dm_geteventnr (const char *name); int dm_is_suspended(const char *name); int dm_get_major_minor (const char *name, int *major, int *minor); char * dm_mapname(int major, int minor); int dm_get_wwid(const char *name, char *uuid, int uuid_len); bool has_dm_info(const struct multipath *mpp); int dm_rename (const char * old, char * new, char * delim, int skip_kpartx); int dm_reassign(const char * mapname); int dm_reassign_table(const char *name, char *old, char *new); int dm_setgeometry(struct multipath *mpp); #define VERSION_GE(v, minv) ( \ (v[0] > minv[0]) || \ ((v[0] == minv[0]) && (v[1] > minv[1])) || \ ((v[0] == minv[0]) && (v[1] == minv[1]) && (v[2] >= minv[2])) \ ) #ifndef LIBDM_API_GET_ERRNO #include #define dm_task_get_errno(x) errno #endif enum { DM_LIBRARY_VERSION, DM_KERNEL_VERSION, DM_MPATH_TARGET_VERSION, MULTIPATH_VERSION }; int libmp_get_version(int which, unsigned int version[3]); struct dm_task; int libmp_dm_task_run(struct dm_task *dmt); #define dm_log_error(lvl, cmd, dmt) \ condlog(lvl, "%s: libdm task=%d error: %s", __func__, \ cmd, strerror(dm_task_get_errno(dmt))) \ #endif /* DEVMAPPER_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/dict.c000066400000000000000000002345031475246302400205420ustar00rootroot00000000000000/* * Based on Alexandre Cassen template for keepalived * Copyright (c) 2004, 2005, 2006 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Kiyoshi Ueda, NEC */ #include #include #include #include #include #include "checkers.h" #include "vector.h" #include "hwtable.h" #include "structs.h" #include "parser.h" #include "config.h" #include "debug.h" #include "pgpolicies.h" #include "blacklist.h" #include "defaults.h" #include "prio.h" #include "util.h" #include #include #include #include "autoconfig.h" #include "mpath_cmd.h" #include "dict.h" #include "strbuf.h" #include "prkey.h" static void do_set_int(vector strvec, void *ptr, int min, int max, const char *file, int line_nr, char *buff) { int *int_ptr = (int *)ptr; char *eptr; long res; res = strtol(buff, &eptr, 10); if (eptr > buff) while (isspace(*eptr)) eptr++; if (*buff == '\0' || *eptr != '\0') { condlog(1, "%s line %d, invalid value for %s: \"%s\"", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), buff); return; } if (res > max || res < min) { res = (res > max) ? max : min; condlog(1, "%s line %d, value for %s too %s, capping at %ld", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), (res == max)? "large" : "small", res); } *int_ptr = res; return; } static int set_int(vector strvec, void *ptr, int min, int max, const char *file, int line_nr) { char *buff; buff = set_value(strvec); if (!buff) return 1; do_set_int(strvec, ptr, min, max, file, line_nr, buff); free(buff); return 0; } static int set_uint(vector strvec, void *ptr, const char *file, int line_nr) { unsigned int *uint_ptr = (unsigned int *)ptr; char *buff, *eptr, *p; unsigned long res; buff = set_value(strvec); if (!buff) return 1; p = buff; while (isspace(*p)) p++; res = strtoul(p, &eptr, 10); if (eptr > buff) while (isspace(*eptr)) eptr++; if (*buff == '\0' || *eptr != '\0' || !isdigit(*p) || res > UINT_MAX) condlog(1, "%s line %d, invalid value for %s: \"%s\"", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), buff); else *uint_ptr = res; free(buff); return 0; } static int set_str(vector strvec, void *ptr, const char *file, int line_nr) { char **str_ptr = (char **)ptr; if (*str_ptr) free(*str_ptr); *str_ptr = set_value(strvec); if (!*str_ptr) return 1; return 0; } static int set_arg_str(vector strvec, void *ptr, int count_idx, const char *file, int line_nr) { char **str_ptr = (char **)ptr; char *old_str = *str_ptr; const char * const spaces = " \f\r\t\v"; char *p, *end; int idx = -1; long int count = -1; *str_ptr = set_value(strvec); if (!*str_ptr) { free(old_str); return 1; } p = *str_ptr; while (*p != '\0') { p += strspn(p, spaces); if (*p == '\0') break; idx += 1; if (idx == count_idx) { errno = 0; count = strtol(p, &end, 10); if (errno == ERANGE || end == p || !(isspace(*end) || *end == '\0')) { count = -1; break; } } p += strcspn(p, spaces); } if (count < 0) { condlog(1, "%s line %d, missing argument count for %s", file, line_nr, (char*)VECTOR_SLOT(strvec, 0)); goto fail; } if (count != idx - count_idx) { condlog(1, "%s line %d, invalid argument count for %s:, got '%ld' expected '%d'", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), count, idx - count_idx); goto fail; } free(old_str); return 0; fail: free(*str_ptr); *str_ptr = old_str; return 0; } static int set_str_noslash(vector strvec, void *ptr, const char *file, int line_nr) { char **str_ptr = (char **)ptr; char *old_str = *str_ptr; *str_ptr = set_value(strvec); if (!*str_ptr) { free(old_str); return 1; } if (strchr(*str_ptr, '/')) { condlog(1, "%s line %d, %s cannot contain a slash. Ignoring", file, line_nr, *str_ptr); free(*str_ptr); *str_ptr = old_str; } else free(old_str); return 0; } static int set_yes_no(vector strvec, void *ptr, const char *file, int line_nr) { char * buff; int *int_ptr = (int *)ptr; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0) *int_ptr = YN_YES; else if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0) *int_ptr = YN_NO; else condlog(1, "%s line %d, invalid value for %s: \"%s\"", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), buff); free(buff); return 0; } static int set_yes_no_undef(vector strvec, void *ptr, const char *file, int line_nr) { char * buff; int *int_ptr = (int *)ptr; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0) *int_ptr = YNU_NO; else if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0) *int_ptr = YNU_YES; else condlog(1, "%s line %d, invalid value for %s: \"%s\"", file, line_nr, (char*)VECTOR_SLOT(strvec, 0), buff); free(buff); return 0; } static int print_int(struct strbuf *buff, long v) { return print_strbuf(buff, "%li", v); } static int print_nonzero(struct strbuf *buff, long v) { if (!v) return 0; return print_strbuf(buff, "%li", v); } static int print_str(struct strbuf *buff, const char *ptr) { int ret = append_strbuf_quoted(buff, ptr); /* * -EINVAL aka (ptr == NULL) means "not set". * Returning an error here breaks unit tests * (logic in snprint_keyword()). */ return ret == -EINVAL ? 0 : ret; } static int print_yes_no(struct strbuf *buff, long v) { return append_strbuf_quoted(buff, v == YN_NO ? "no" : "yes"); } static int print_yes_no_undef(struct strbuf *buff, long v) { if (!v) return 0; return append_strbuf_quoted(buff, v == YNU_NO? "no" : "yes"); } #define declare_def_handler(option, function) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ return function (strvec, &conf->option, file, line_nr); \ } #define declare_def_warn_handler(option, function) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ static bool warned; \ if (!warned) { \ condlog(2, "%s line %d, \"" #option "\" is deprecated and will be disabled in a future release", file, line_nr); \ warned = true; \ } \ return function (strvec, &conf->option, file, line_nr); \ } static int deprecated_handler(struct config *conf, vector strvec, const char *file, int line_nr); #define declare_deprecated_handler(option, default) \ static int \ deprecated_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ static bool warned; \ if (!warned) { \ condlog(1, "%s line %d: ignoring deprecated option \"" \ #option "\", using built-in value: \"%s\"", \ file, line_nr, default); \ warned = true; \ } \ return deprecated_handler(conf, strvec, file, line_nr); \ } #define declare_def_range_handler(option, minval, maxval) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ return set_int(strvec, &conf->option, minval, maxval, file, line_nr); \ } #define declare_def_arg_str_handler(option, count_idx) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ return set_arg_str(strvec, &conf->option, count_idx, file, line_nr); \ } #define declare_def_snprint(option, function) \ static int \ snprint_def_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ return function(buff, conf->option); \ } #define declare_def_snprint_defint(option, function, value) \ static int \ snprint_def_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ int i = value; \ if (!conf->option) \ return function(buff, i); \ return function (buff, conf->option); \ } #define declare_def_snprint_defstr(option, function, value) \ static int \ snprint_def_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ static const char *s = value; \ if (!conf->option) \ return function(buff, s); \ return function(buff, conf->option); \ } #define declare_hw_handler(option, function) \ static int \ hw_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct hwentry * hwe = VECTOR_LAST_SLOT(conf->hwtable); \ if (!hwe) \ return 1; \ return function (strvec, &hwe->option, file, line_nr); \ } #define declare_hw_range_handler(option, minval, maxval) \ static int \ hw_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct hwentry * hwe = VECTOR_LAST_SLOT(conf->hwtable); \ if (!hwe) \ return 1; \ return set_int(strvec, &hwe->option, minval, maxval, file, line_nr); \ } #define declare_hw_arg_str_handler(option, count_idx) \ static int \ hw_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct hwentry * hwe = VECTOR_LAST_SLOT(conf->hwtable); \ if (!hwe) \ return 1; \ return set_arg_str(strvec, &hwe->option, count_idx, file, line_nr); \ } #define declare_hw_snprint(option, function) \ static int \ snprint_hw_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ const struct hwentry * hwe = (const struct hwentry *)data; \ return function(buff, hwe->option); \ } #define declare_ovr_handler(option, function) \ static int \ ovr_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ if (!conf->overrides) \ return 1; \ return function (strvec, &conf->overrides->option, file, line_nr); \ } #define declare_ovr_range_handler(option, minval, maxval) \ static int \ ovr_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ if (!conf->overrides) \ return 1; \ return set_int(strvec, &conf->overrides->option, minval, maxval, \ file, line_nr); \ } #define declare_ovr_arg_str_handler(option, count_idx) \ static int \ ovr_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ if (!conf->overrides) \ return 1; \ return set_arg_str(strvec, &conf->overrides->option, count_idx, file, line_nr); \ } #define declare_ovr_snprint(option, function) \ static int \ snprint_ovr_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ return function (buff, conf->overrides->option); \ } #define declare_mp_handler(option, function) \ static int \ mp_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct mpentry * mpe = VECTOR_LAST_SLOT(conf->mptable); \ if (!mpe) \ return 1; \ return function (strvec, &mpe->option, file, line_nr); \ } #define declare_mp_range_handler(option, minval, maxval) \ static int \ mp_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct mpentry * mpe = VECTOR_LAST_SLOT(conf->mptable); \ if (!mpe) \ return 1; \ return set_int(strvec, &mpe->option, minval, maxval, file, line_nr); \ } #define declare_mp_arg_str_handler(option, count_idx) \ static int \ mp_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct mpentry * mpe = VECTOR_LAST_SLOT(conf->mptable); \ if (!mpe) \ return 1; \ return set_arg_str(strvec, &mpe->option, count_idx, file, line_nr); \ } #define declare_mp_snprint(option, function) \ static int \ snprint_mp_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ const struct mpentry * mpe = (const struct mpentry *)data; \ return function(buff, mpe->option); \ } #define declare_pc_handler(option, function) \ static int \ pc_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct pcentry *pce; \ if (!conf->overrides || !conf->overrides->pctable) \ return 1; \ pce = VECTOR_LAST_SLOT(conf->overrides->pctable); \ if (!pce) \ return 1; \ return function (strvec, &pce->option, file, line_nr); \ } #define declare_pc_snprint(option, function) \ static int \ snprint_pc_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ const struct pcentry *pce = (const struct pcentry *)data; \ return function(buff, pce->option); \ } static int checkint_handler(struct config *conf, vector strvec, const char *file, int line_nr) { int rc = set_uint(strvec, &conf->checkint, file, line_nr); if (rc) return rc; if (conf->checkint == CHECKINT_UNDEF) conf->checkint--; return 0; } declare_def_snprint(checkint, print_int) declare_def_handler(max_checkint, set_uint) declare_def_snprint(max_checkint, print_int) declare_def_range_handler(verbosity, 0, MAX_VERBOSITY) declare_def_snprint(verbosity, print_int) declare_def_handler(reassign_maps, set_yes_no) declare_def_snprint(reassign_maps, print_yes_no) static int def_partition_delim_handler(struct config *conf, vector strvec, const char *file, int line_nr) { int rc = set_str_noslash(strvec, &conf->partition_delim, file, line_nr); if (rc != 0) return rc; if (!strcmp(conf->partition_delim, UNSET_PARTITION_DELIM)) { free(conf->partition_delim); conf->partition_delim = NULL; } return 0; } static int snprint_def_partition_delim(struct config *conf, struct strbuf *buff, const void *data) { if (default_partition_delim == NULL || conf->partition_delim != NULL) return print_str(buff, conf->partition_delim); else return print_str(buff, UNSET_PARTITION_DELIM); } static const char * const find_multipaths_optvals[] = { [FIND_MULTIPATHS_OFF] = "off", [FIND_MULTIPATHS_ON] = "on", [FIND_MULTIPATHS_STRICT] = "strict", [FIND_MULTIPATHS_GREEDY] = "greedy", [FIND_MULTIPATHS_SMART] = "smart", }; static int def_find_multipaths_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char *buff; int i; buff = set_value(strvec); if (!buff) return 1; for (i = FIND_MULTIPATHS_OFF; i < FIND_MULTIPATHS_LAST__; i++) { if (find_multipaths_optvals[i] != NULL && !strcmp(buff, find_multipaths_optvals[i])) { conf->find_multipaths = i; break; } } if (i >= FIND_MULTIPATHS_LAST__) { if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0) conf->find_multipaths = FIND_MULTIPATHS_OFF; else if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0) conf->find_multipaths = FIND_MULTIPATHS_ON; else condlog(1, "%s line %d, invalid value for find_multipaths: \"%s\"", file, line_nr, buff); } free(buff); return 0; } static int snprint_def_find_multipaths(struct config *conf, struct strbuf *buff, const void *data) { return append_strbuf_quoted(buff, find_multipaths_optvals[conf->find_multipaths]); } static const char * const marginal_pathgroups_optvals[] = { [MARGINAL_PATHGROUP_OFF] = "off", [MARGINAL_PATHGROUP_ON] = "on", #ifdef FPIN_EVENT_HANDLER [MARGINAL_PATHGROUP_FPIN] = "fpin", #endif }; static int def_marginal_pathgroups_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char *buff; unsigned int i; buff = set_value(strvec); if (!buff) return 1; for (i = MARGINAL_PATHGROUP_OFF; i < ARRAY_SIZE(marginal_pathgroups_optvals); i++) { if (marginal_pathgroups_optvals[i] != NULL && !strcmp(buff, marginal_pathgroups_optvals[i])) { conf->marginal_pathgroups = i; break; } } if (i >= ARRAY_SIZE(marginal_pathgroups_optvals)) { if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0) conf->marginal_pathgroups = MARGINAL_PATHGROUP_OFF; else if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0) conf->marginal_pathgroups = MARGINAL_PATHGROUP_ON; /* This can only be true if FPIN_EVENT_HANDLER isn't defined, * otherwise this check will have already happened above */ else if (strcmp(buff, "fpin") == 0) condlog(1, "%s line %d, support for \"fpin\" is not compiled in for marginal_pathgroups", file, line_nr); else condlog(1, "%s line %d, invalid value for marginal_pathgroups: \"%s\"", file, line_nr, buff); } free(buff); return 0; } static int snprint_def_marginal_pathgroups(struct config *conf, struct strbuf *buff, const void *data) { return append_strbuf_quoted(buff, marginal_pathgroups_optvals[conf->marginal_pathgroups]); } declare_def_arg_str_handler(selector, 1) declare_def_snprint_defstr(selector, print_str, DEFAULT_SELECTOR) declare_hw_arg_str_handler(selector, 1) declare_hw_snprint(selector, print_str) declare_ovr_arg_str_handler(selector, 1) declare_ovr_snprint(selector, print_str) declare_mp_arg_str_handler(selector, 1) declare_mp_snprint(selector, print_str) static int snprint_uid_attrs(struct config *conf, struct strbuf *buff, const void *dummy) { int j, ret, total = 0; const char *att; vector_foreach_slot(&conf->uid_attrs, att, j) { ret = print_strbuf(buff, "%s%s", j == 0 ? "" : " ", att); if (ret < 0) return ret; total += ret; } return total; } static int uid_attrs_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char *val; void *ptr; int i; vector_foreach_slot(&conf->uid_attrs, ptr, i) free(ptr); vector_reset(&conf->uid_attrs); val = set_value(strvec); if (!val) return 1; if (parse_uid_attrs(val, conf)) condlog(1, "%s line %d,error parsing uid_attrs: \"%s\"", file, line_nr, val); else condlog(4, "parsed %d uid_attrs", VECTOR_SIZE(&conf->uid_attrs)); free(val); return 0; } declare_def_handler(uid_attribute, set_str) declare_def_snprint_defstr(uid_attribute, print_str, DEFAULT_UID_ATTRIBUTE) declare_ovr_handler(uid_attribute, set_str) declare_ovr_snprint(uid_attribute, print_str) declare_hw_handler(uid_attribute, set_str) declare_hw_snprint(uid_attribute, print_str) declare_def_handler(prio_name, set_str) declare_def_snprint_defstr(prio_name, print_str, DEFAULT_PRIO) declare_ovr_handler(prio_name, set_str) declare_ovr_snprint(prio_name, print_str) declare_hw_handler(prio_name, set_str) declare_hw_snprint(prio_name, print_str) declare_mp_handler(prio_name, set_str) declare_mp_snprint(prio_name, print_str) declare_def_handler(alias_prefix, set_str_noslash) declare_def_snprint_defstr(alias_prefix, print_str, DEFAULT_ALIAS_PREFIX) declare_ovr_handler(alias_prefix, set_str_noslash) declare_ovr_snprint(alias_prefix, print_str) declare_hw_handler(alias_prefix, set_str_noslash) declare_hw_snprint(alias_prefix, print_str) declare_def_handler(prio_args, set_str) declare_def_snprint_defstr(prio_args, print_str, DEFAULT_PRIO_ARGS) declare_ovr_handler(prio_args, set_str) declare_ovr_snprint(prio_args, print_str) declare_hw_handler(prio_args, set_str) declare_hw_snprint(prio_args, print_str) declare_mp_handler(prio_args, set_str) declare_mp_snprint(prio_args, print_str) declare_def_arg_str_handler(features, 0) declare_def_snprint_defstr(features, print_str, DEFAULT_FEATURES) declare_ovr_arg_str_handler(features, 0) declare_ovr_snprint(features, print_str) declare_hw_arg_str_handler(features, 0) declare_hw_snprint(features, print_str) declare_mp_arg_str_handler(features, 0) declare_mp_snprint(features, print_str) declare_def_handler(checker_name, set_str) declare_def_snprint_defstr(checker_name, print_str, DEFAULT_CHECKER) declare_ovr_handler(checker_name, set_str) 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, const void * data) { const char *qwd = "unknown"; switch (conf->queue_without_daemon) { case QUE_NO_DAEMON_OFF: qwd = "no"; break; case QUE_NO_DAEMON_ON: qwd = "yes"; break; case QUE_NO_DAEMON_FORCE: qwd = "forced"; break; } return append_strbuf_quoted(buff, qwd); } declare_def_range_handler(checker_timeout, 0, INT_MAX) declare_def_snprint(checker_timeout, print_nonzero) declare_def_handler(allow_usb_devices, set_yes_no) declare_def_snprint(allow_usb_devices, print_yes_no) static const char * const flush_on_last_del_optvals[] = { [FLUSH_NEVER] = "never", [FLUSH_ALWAYS] = "always", [FLUSH_UNUSED] = "unused", }; static int set_flush_on_last_del(vector strvec, void *ptr, const char *file, int line_nr) { int i; int *flush_val_ptr = (int *)ptr; char *buff; buff = set_value(strvec); if (!buff) return 1; for (i = FLUSH_NEVER; i <= FLUSH_UNUSED; i++) { if (flush_on_last_del_optvals[i] != NULL && !strcmp(buff, flush_on_last_del_optvals[i])) { *flush_val_ptr = i; break; } } if (i > FLUSH_UNUSED) { bool deprecated = true; if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0) *flush_val_ptr = FLUSH_UNUSED; else if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0) *flush_val_ptr = FLUSH_ALWAYS; else { deprecated = false; condlog(1, "%s line %d, invalid value for flush_on_last_del: \"%s\"", file, line_nr, buff); } if (deprecated) condlog(2, "%s line %d, \"%s\" is a deprecated value for flush_on_last_del and is treated as \"%s\"", file, line_nr, buff, flush_on_last_del_optvals[*flush_val_ptr]); } free(buff); return 0; } int print_flush_on_last_del(struct strbuf *buff, long v) { if (v == FLUSH_UNDEF) return 0; return append_strbuf_quoted(buff, flush_on_last_del_optvals[(int)v]); } declare_def_handler(flush_on_last_del, set_flush_on_last_del) declare_def_snprint_defint(flush_on_last_del, print_flush_on_last_del, DEFAULT_FLUSH) declare_ovr_handler(flush_on_last_del, set_flush_on_last_del) declare_ovr_snprint(flush_on_last_del, print_flush_on_last_del) declare_hw_handler(flush_on_last_del, set_flush_on_last_del) declare_hw_snprint(flush_on_last_del, print_flush_on_last_del) declare_mp_handler(flush_on_last_del, set_flush_on_last_del) declare_mp_snprint(flush_on_last_del, print_flush_on_last_del) declare_def_handler(user_friendly_names, set_yes_no_undef) declare_def_snprint_defint(user_friendly_names, print_yes_no_undef, DEFAULT_USER_FRIENDLY_NAMES) declare_ovr_handler(user_friendly_names, set_yes_no_undef) declare_ovr_snprint(user_friendly_names, print_yes_no_undef) declare_hw_handler(user_friendly_names, set_yes_no_undef) declare_hw_snprint(user_friendly_names, print_yes_no_undef) declare_mp_handler(user_friendly_names, set_yes_no_undef) declare_mp_snprint(user_friendly_names, print_yes_no_undef) declare_def_handler(retain_hwhandler, set_yes_no_undef) declare_def_snprint_defint(retain_hwhandler, print_yes_no_undef, DEFAULT_RETAIN_HWHANDLER) declare_ovr_handler(retain_hwhandler, set_yes_no_undef) declare_ovr_snprint(retain_hwhandler, print_yes_no_undef) declare_hw_handler(retain_hwhandler, set_yes_no_undef) declare_hw_snprint(retain_hwhandler, print_yes_no_undef) declare_def_handler(detect_prio, set_yes_no_undef) declare_def_snprint_defint(detect_prio, print_yes_no_undef, DEFAULT_DETECT_PRIO) declare_ovr_handler(detect_prio, set_yes_no_undef) declare_ovr_snprint(detect_prio, print_yes_no_undef) declare_hw_handler(detect_prio, set_yes_no_undef) declare_hw_snprint(detect_prio, print_yes_no_undef) declare_def_handler(detect_checker, set_yes_no_undef) declare_def_snprint_defint(detect_checker, print_yes_no_undef, DEFAULT_DETECT_CHECKER) declare_ovr_handler(detect_checker, set_yes_no_undef) declare_ovr_snprint(detect_checker, print_yes_no_undef) declare_hw_handler(detect_checker, set_yes_no_undef) declare_hw_snprint(detect_checker, print_yes_no_undef) declare_def_handler(detect_pgpolicy, set_yes_no_undef) declare_def_snprint_defint(detect_pgpolicy, print_yes_no_undef, DEFAULT_DETECT_PGPOLICY) declare_ovr_handler(detect_pgpolicy, set_yes_no_undef) declare_ovr_snprint(detect_pgpolicy, print_yes_no_undef) declare_hw_handler(detect_pgpolicy, set_yes_no_undef) declare_hw_snprint(detect_pgpolicy, print_yes_no_undef) declare_def_handler(detect_pgpolicy_use_tpg, set_yes_no_undef) declare_def_snprint_defint(detect_pgpolicy_use_tpg, print_yes_no_undef, DEFAULT_DETECT_PGPOLICY_USE_TPG) declare_ovr_handler(detect_pgpolicy_use_tpg, set_yes_no_undef) declare_ovr_snprint(detect_pgpolicy_use_tpg, print_yes_no_undef) declare_hw_handler(detect_pgpolicy_use_tpg, set_yes_no_undef) declare_hw_snprint(detect_pgpolicy_use_tpg, print_yes_no_undef) declare_def_handler(force_sync, set_yes_no) declare_def_snprint(force_sync, print_yes_no) declare_def_handler(deferred_remove, set_yes_no_undef) declare_def_snprint_defint(deferred_remove, print_yes_no_undef, DEFAULT_DEFERRED_REMOVE) declare_ovr_handler(deferred_remove, set_yes_no_undef) declare_ovr_snprint(deferred_remove, print_yes_no_undef) declare_hw_handler(deferred_remove, set_yes_no_undef) declare_hw_snprint(deferred_remove, print_yes_no_undef) declare_mp_handler(deferred_remove, set_yes_no_undef) declare_mp_snprint(deferred_remove, print_yes_no_undef) declare_def_range_handler(retrigger_tries, 0, INT_MAX) declare_def_snprint(retrigger_tries, print_int) declare_def_range_handler(retrigger_delay, 0, INT_MAX) declare_def_snprint(retrigger_delay, print_int) declare_def_range_handler(uev_wait_timeout, 0, INT_MAX) declare_def_snprint(uev_wait_timeout, print_int) declare_def_handler(strict_timing, set_yes_no) declare_def_snprint(strict_timing, print_yes_no) declare_def_handler(skip_kpartx, set_yes_no_undef) declare_def_snprint_defint(skip_kpartx, print_yes_no_undef, DEFAULT_SKIP_KPARTX) declare_ovr_handler(skip_kpartx, set_yes_no_undef) declare_ovr_snprint(skip_kpartx, print_yes_no_undef) declare_hw_handler(skip_kpartx, set_yes_no_undef) declare_hw_snprint(skip_kpartx, print_yes_no_undef) declare_mp_handler(skip_kpartx, set_yes_no_undef) declare_mp_snprint(skip_kpartx, print_yes_no_undef) declare_def_range_handler(remove_retries, 0, INT_MAX) declare_def_snprint(remove_retries, print_int) declare_def_range_handler(max_sectors_kb, 0, INT_MAX) declare_def_snprint(max_sectors_kb, print_nonzero) declare_ovr_range_handler(max_sectors_kb, 0, INT_MAX) declare_ovr_snprint(max_sectors_kb, print_nonzero) declare_hw_range_handler(max_sectors_kb, 0, INT_MAX) declare_hw_snprint(max_sectors_kb, print_nonzero) declare_mp_range_handler(max_sectors_kb, 0, INT_MAX) declare_mp_snprint(max_sectors_kb, print_nonzero) declare_def_range_handler(find_multipaths_timeout, INT_MIN, INT_MAX) declare_def_snprint_defint(find_multipaths_timeout, print_int, DEFAULT_FIND_MULTIPATHS_TIMEOUT) declare_def_handler(enable_foreign, set_str) declare_def_snprint_defstr(enable_foreign, print_str, DEFAULT_ENABLE_FOREIGN) #define declare_def_attr_handler(option, function) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ return function (strvec, &conf->option, &conf->attribute_flags, \ file, line_nr); \ } #define declare_def_attr_snprint(option, function) \ static int \ snprint_def_ ## option (struct config *conf, struct strbuf *buff, \ const void *data) \ { \ return function(buff, conf->option, conf->attribute_flags); \ } #define declare_mp_attr_handler(option, function) \ static int \ mp_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ struct mpentry * mpe = VECTOR_LAST_SLOT(conf->mptable); \ if (!mpe) \ return 1; \ return function (strvec, &mpe->option, &mpe->attribute_flags, \ file, line_nr); \ } #define declare_mp_attr_snprint(option, function) \ static int \ snprint_mp_ ## option (struct config *conf, struct strbuf *buff, \ const void * data) \ { \ const struct mpentry * mpe = (const struct mpentry *)data; \ return function(buff, mpe->option, mpe->attribute_flags); \ } static int set_mode(vector strvec, void *ptr, int *flags, const char *file, int line_nr) { mode_t mode; mode_t *mode_ptr = (mode_t *)ptr; char *buff; buff = set_value(strvec); if (!buff) return 1; if (sscanf(buff, "%o", &mode) == 1 && mode <= 0777) { *flags |= (1 << ATTR_MODE); *mode_ptr = mode; } else condlog(1, "%s line %d, invalid value for mode: \"%s\"", file, line_nr, buff); free(buff); return 0; } static int set_uid(vector strvec, void *ptr, int *flags, const char *file, int line_nr) { uid_t uid; uid_t *uid_ptr = (uid_t *)ptr; char *buff; char passwd_buf[1024]; struct passwd info, *found; buff = set_value(strvec); if (!buff) return 1; if (getpwnam_r(buff, &info, passwd_buf, 1024, &found) == 0 && found) { *flags |= (1 << ATTR_UID); *uid_ptr = info.pw_uid; } else if (sscanf(buff, "%u", &uid) == 1){ *flags |= (1 << ATTR_UID); *uid_ptr = uid; } else condlog(1, "%s line %d, invalid value for uid: \"%s\"", file, line_nr, buff); free(buff); return 0; } static int set_gid(vector strvec, void *ptr, int *flags, const char *file, int line_nr) { gid_t gid; gid_t *gid_ptr = (gid_t *)ptr; char *buff; char passwd_buf[1024]; struct passwd info, *found; buff = set_value(strvec); if (!buff) return 1; if (getpwnam_r(buff, &info, passwd_buf, 1024, &found) == 0 && found) { *flags |= (1 << ATTR_GID); *gid_ptr = info.pw_gid; } else if (sscanf(buff, "%u", &gid) == 1){ *flags |= (1 << ATTR_GID); *gid_ptr = gid; } else condlog(1, "%s line %d, invalid value for gid: \"%s\"", file, line_nr, buff); free(buff); return 0; } static int print_mode(struct strbuf *buff, long v, int flags) { mode_t mode = (mode_t)v; if ((flags & (1 << ATTR_MODE)) == 0) return 0; return print_strbuf(buff, "0%o", mode); } static int print_uid(struct strbuf *buff, long v, int flags) { uid_t uid = (uid_t)v; if ((flags & (1 << ATTR_UID)) == 0) return 0; return print_strbuf(buff, "0%o", uid); } static int print_gid(struct strbuf *buff, long v, int flags) { gid_t gid = (gid_t)v; if ((flags & (1 << ATTR_GID)) == 0) return 0; return print_strbuf(buff, "0%o", gid); } declare_def_attr_handler(mode, set_mode) declare_def_attr_snprint(mode, print_mode) declare_mp_attr_handler(mode, set_mode) declare_mp_attr_snprint(mode, print_mode) declare_def_attr_handler(uid, set_uid) declare_def_attr_snprint(uid, print_uid) declare_mp_attr_handler(uid, set_uid) declare_mp_attr_snprint(uid, print_uid) declare_def_attr_handler(gid, set_gid) declare_def_attr_snprint(gid, print_gid) declare_mp_attr_handler(gid, set_gid) declare_mp_attr_snprint(gid, print_gid) static int set_undef_off_zero(vector strvec, void *ptr, const char *file, int line_nr) { char * buff; int *int_ptr = (int *)ptr; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "off") == 0) *int_ptr = UOZ_OFF; else if (strcmp(buff, "0") == 0) *int_ptr = UOZ_ZERO; else do_set_int(strvec, int_ptr, 1, INT_MAX, file, line_nr, buff); free(buff); return 0; } int print_undef_off_zero(struct strbuf *buff, long v) { if (v == UOZ_UNDEF) return 0; if (v == UOZ_OFF) return append_strbuf_str(buff, "off"); if (v == UOZ_ZERO) return append_strbuf_str(buff, "0"); return print_int(buff, v); } declare_def_handler(fast_io_fail, set_undef_off_zero) declare_def_snprint_defint(fast_io_fail, print_undef_off_zero, DEFAULT_FAST_IO_FAIL) declare_ovr_handler(fast_io_fail, set_undef_off_zero) declare_ovr_snprint(fast_io_fail, print_undef_off_zero) declare_hw_handler(fast_io_fail, set_undef_off_zero) declare_hw_snprint(fast_io_fail, print_undef_off_zero) declare_pc_handler(fast_io_fail, set_undef_off_zero) declare_pc_snprint(fast_io_fail, print_undef_off_zero) static int set_dev_loss(vector strvec, void *ptr, const char *file, int line_nr) { char * buff; unsigned int *uint_ptr = (unsigned int *)ptr; buff = set_value(strvec); if (!buff) return 1; if (!strcmp(buff, "infinity")) *uint_ptr = MAX_DEV_LOSS_TMO; else if (sscanf(buff, "%u", uint_ptr) != 1) condlog(1, "%s line %d, invalid value for dev_loss_tmo: \"%s\"", file, line_nr, buff); free(buff); return 0; } int print_dev_loss(struct strbuf *buff, unsigned long v) { if (v == DEV_LOSS_TMO_UNSET) return 0; if (v >= MAX_DEV_LOSS_TMO) return append_strbuf_quoted(buff, "infinity"); return print_strbuf(buff, "%lu", v); } declare_def_handler(dev_loss, set_dev_loss) declare_def_snprint(dev_loss, print_dev_loss) declare_ovr_handler(dev_loss, set_dev_loss) declare_ovr_snprint(dev_loss, print_dev_loss) declare_hw_handler(dev_loss, set_dev_loss) declare_hw_snprint(dev_loss, print_dev_loss) declare_pc_handler(dev_loss, set_dev_loss) declare_pc_snprint(dev_loss, print_dev_loss) declare_def_handler(eh_deadline, set_undef_off_zero) declare_def_snprint(eh_deadline, print_undef_off_zero) declare_ovr_handler(eh_deadline, set_undef_off_zero) declare_ovr_snprint(eh_deadline, print_undef_off_zero) declare_hw_handler(eh_deadline, set_undef_off_zero) declare_hw_snprint(eh_deadline, print_undef_off_zero) declare_pc_handler(eh_deadline, set_undef_off_zero) declare_pc_snprint(eh_deadline, print_undef_off_zero) static int def_max_retries_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char * buff; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "off") == 0) conf->max_retries = MAX_RETRIES_OFF; else if (strcmp(buff, "0") == 0) conf->max_retries = MAX_RETRIES_ZERO; else do_set_int(strvec, &conf->max_retries, 1, 5, file, line_nr, buff); free(buff); return 0; } declare_def_snprint(max_retries, print_undef_off_zero) static int set_pgpolicy(vector strvec, void *ptr, const char *file, int line_nr) { char * buff; int policy; int *int_ptr = (int *)ptr; buff = set_value(strvec); if (!buff) return 1; policy = get_pgpolicy_id(buff); if (policy != IOPOLICY_UNDEF) *int_ptr = policy; else condlog(1, "%s line %d, invalid value for path_grouping_policy: \"%s\"", file, line_nr, buff); free(buff); return 0; } int print_pgpolicy(struct strbuf *buff, long pgpolicy) { if (!pgpolicy) return 0; return append_strbuf_quoted(buff, get_pgpolicy_name(pgpolicy)); } declare_def_handler(pgpolicy, set_pgpolicy) declare_def_snprint_defint(pgpolicy, print_pgpolicy, DEFAULT_PGPOLICY) declare_ovr_handler(pgpolicy, set_pgpolicy) declare_ovr_snprint(pgpolicy, print_pgpolicy) declare_hw_handler(pgpolicy, set_pgpolicy) declare_hw_snprint(pgpolicy, print_pgpolicy) declare_mp_handler(pgpolicy, set_pgpolicy) declare_mp_snprint(pgpolicy, print_pgpolicy) int get_sys_max_fds(int *max_fds) { FILE *file; int nr_open; int ret = 1; file = fopen("/proc/sys/fs/nr_open", "r"); if (!file) { fprintf(stderr, "Cannot open /proc/sys/fs/nr_open : %s\n", strerror(errno)); return 1; } if (fscanf(file, "%d", &nr_open) != 1) { fprintf(stderr, "Cannot read max open fds from /proc/sys/fs/nr_open"); if (ferror(file)) fprintf(stderr, " : %s\n", strerror(errno)); else fprintf(stderr, "\n"); } else { *max_fds = nr_open; ret = 0; } fclose(file); return ret; } static int max_fds_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char * buff; int max_fds; buff = set_value(strvec); if (!buff) return 1; if (get_sys_max_fds(&max_fds) != 0) max_fds = 4096; /* Assume safe limit */ if (!strcmp(buff, "max")) conf->max_fds = max_fds; else do_set_int(strvec, &conf->max_fds, 0, max_fds, file, line_nr, buff); free(buff); return 0; } static int snprint_max_fds (struct config *conf, struct strbuf *buff, const void *data) { int r = 0, max_fds; if (!conf->max_fds) return 0; r = get_sys_max_fds(&max_fds); if (!r && conf->max_fds >= max_fds) return append_strbuf_quoted(buff, "max"); else 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) { int *int_ptr = (int *)ptr; char * buff; buff = set_value(strvec); if (!buff) return 1; if (strlen(buff) == 6 && !strcmp(buff, "manual")) *int_ptr = -FAILBACK_MANUAL; else if (strlen(buff) == 9 && !strcmp(buff, "immediate")) *int_ptr = -FAILBACK_IMMEDIATE; else if (strlen(buff) == 10 && !strcmp(buff, "followover")) *int_ptr = -FAILBACK_FOLLOWOVER; else do_set_int(strvec, ptr, 0, INT_MAX, file, line_nr, buff); free(buff); return 0; } int print_pgfailback (struct strbuf *buff, long v) { switch(v) { case FAILBACK_UNDEF: return 0; case -FAILBACK_MANUAL: return append_strbuf_quoted(buff, "manual"); case -FAILBACK_IMMEDIATE: return append_strbuf_quoted(buff, "immediate"); case -FAILBACK_FOLLOWOVER: return append_strbuf_quoted(buff, "followover"); default: return print_int(buff, v); } } declare_def_handler(pgfailback, set_pgfailback) declare_def_snprint_defint(pgfailback, print_pgfailback, DEFAULT_FAILBACK) declare_ovr_handler(pgfailback, set_pgfailback) declare_ovr_snprint(pgfailback, print_pgfailback) declare_hw_handler(pgfailback, set_pgfailback) declare_hw_snprint(pgfailback, print_pgfailback) declare_mp_handler(pgfailback, set_pgfailback) declare_mp_snprint(pgfailback, print_pgfailback) static int no_path_retry_helper(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, "fail") || !strcmp(buff, "0")) *int_ptr = NO_PATH_RETRY_FAIL; else if (!strcmp(buff, "queue")) *int_ptr = NO_PATH_RETRY_QUEUE; else do_set_int(strvec, ptr, 1, INT_MAX, file, line_nr, buff); free(buff); return 0; } int print_no_path_retry(struct strbuf *buff, long v) { switch(v) { case NO_PATH_RETRY_UNDEF: return 0; case NO_PATH_RETRY_FAIL: return append_strbuf_quoted(buff, "fail"); case NO_PATH_RETRY_QUEUE: return append_strbuf_quoted(buff, "queue"); default: return print_int(buff, v); } } declare_def_handler(no_path_retry, no_path_retry_helper) declare_def_snprint(no_path_retry, print_no_path_retry) declare_ovr_handler(no_path_retry, no_path_retry_helper) declare_ovr_snprint(no_path_retry, print_no_path_retry) declare_hw_handler(no_path_retry, no_path_retry_helper) declare_hw_snprint(no_path_retry, print_no_path_retry) declare_mp_handler(no_path_retry, no_path_retry_helper) declare_mp_snprint(no_path_retry, print_no_path_retry) static int def_log_checker_err_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char * buff; buff = set_value(strvec); if (!buff) return 1; if (!strcmp(buff, "once")) conf->log_checker_err = LOG_CHKR_ERR_ONCE; else if (!strcmp(buff, "always")) conf->log_checker_err = LOG_CHKR_ERR_ALWAYS; else condlog(1, "%s line %d, invalid value for log_checker_err: \"%s\"", file, line_nr, buff); free(buff); return 0; } static int snprint_def_log_checker_err(struct config *conf, struct strbuf *buff, const void * data) { if (conf->log_checker_err == LOG_CHKR_ERR_ONCE) return append_strbuf_quoted(buff, "once"); return append_strbuf_quoted(buff, "always"); } static int set_reservation_key(vector strvec, struct be64 *be64_ptr, uint8_t *flags_ptr, int *source_ptr) { char *buff; uint64_t prkey; uint8_t sa_flags; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "file") == 0) { *source_ptr = PRKEY_SOURCE_FILE; *flags_ptr = 0; put_be64(*be64_ptr, 0); free(buff); return 0; } if (parse_prkey_flags(buff, &prkey, &sa_flags) != 0) { free(buff); return 1; } *source_ptr = PRKEY_SOURCE_CONF; *flags_ptr = sa_flags; put_be64(*be64_ptr, prkey); free(buff); return 0; } static int def_reservation_key_handler(struct config *conf, vector strvec, const char *file, int line_nr) { return set_reservation_key(strvec, &conf->reservation_key, &conf->sa_flags, &conf->prkey_source); } static int snprint_def_reservation_key (struct config *conf, struct strbuf *buff, const void * data) { return print_reservation_key(buff, conf->reservation_key, conf->sa_flags, conf->prkey_source); } static int mp_reservation_key_handler(struct config *conf, vector strvec, const char *file, int line_nr) { struct mpentry * mpe = VECTOR_LAST_SLOT(conf->mptable); if (!mpe) return 1; return set_reservation_key(strvec, &mpe->reservation_key, &mpe->sa_flags, &mpe->prkey_source); } static int snprint_mp_reservation_key (struct config *conf, struct strbuf *buff, const void *data) { const struct mpentry * mpe = (const struct mpentry *)data; return print_reservation_key(buff, mpe->reservation_key, mpe->sa_flags, mpe->prkey_source); } static int set_off_int_undef(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, "no") || !strcmp(buff, "0")) *int_ptr = NU_NO; else do_set_int(strvec, ptr, 1, INT_MAX, file, line_nr, buff); free(buff); return 0; } int print_off_int_undef(struct strbuf *buff, long v) { switch(v) { case NU_UNDEF: return 0; case NU_NO: return append_strbuf_quoted(buff, "no"); default: return print_int(buff, v); } } declare_def_handler(delay_watch_checks, set_off_int_undef) declare_def_snprint_defint(delay_watch_checks, print_off_int_undef, DEFAULT_DELAY_CHECKS) declare_ovr_handler(delay_watch_checks, set_off_int_undef) declare_ovr_snprint(delay_watch_checks, print_off_int_undef) declare_hw_handler(delay_watch_checks, set_off_int_undef) declare_hw_snprint(delay_watch_checks, print_off_int_undef) declare_mp_handler(delay_watch_checks, set_off_int_undef) declare_mp_snprint(delay_watch_checks, print_off_int_undef) declare_def_handler(delay_wait_checks, set_off_int_undef) declare_def_snprint_defint(delay_wait_checks, print_off_int_undef, DEFAULT_DELAY_CHECKS) declare_ovr_handler(delay_wait_checks, set_off_int_undef) declare_ovr_snprint(delay_wait_checks, print_off_int_undef) declare_hw_handler(delay_wait_checks, set_off_int_undef) declare_hw_snprint(delay_wait_checks, print_off_int_undef) declare_mp_handler(delay_wait_checks, set_off_int_undef) declare_mp_snprint(delay_wait_checks, print_off_int_undef) declare_def_handler(san_path_err_threshold, set_off_int_undef) declare_def_snprint_defint(san_path_err_threshold, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(san_path_err_threshold, set_off_int_undef) declare_ovr_snprint(san_path_err_threshold, print_off_int_undef) declare_hw_handler(san_path_err_threshold, set_off_int_undef) declare_hw_snprint(san_path_err_threshold, print_off_int_undef) declare_mp_handler(san_path_err_threshold, set_off_int_undef) declare_mp_snprint(san_path_err_threshold, print_off_int_undef) declare_def_handler(san_path_err_forget_rate, set_off_int_undef) declare_def_snprint_defint(san_path_err_forget_rate, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(san_path_err_forget_rate, set_off_int_undef) declare_ovr_snprint(san_path_err_forget_rate, print_off_int_undef) declare_hw_handler(san_path_err_forget_rate, set_off_int_undef) declare_hw_snprint(san_path_err_forget_rate, print_off_int_undef) declare_mp_handler(san_path_err_forget_rate, set_off_int_undef) declare_mp_snprint(san_path_err_forget_rate, print_off_int_undef) declare_def_handler(san_path_err_recovery_time, set_off_int_undef) declare_def_snprint_defint(san_path_err_recovery_time, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(san_path_err_recovery_time, set_off_int_undef) declare_ovr_snprint(san_path_err_recovery_time, print_off_int_undef) declare_hw_handler(san_path_err_recovery_time, set_off_int_undef) declare_hw_snprint(san_path_err_recovery_time, print_off_int_undef) declare_mp_handler(san_path_err_recovery_time, set_off_int_undef) declare_mp_snprint(san_path_err_recovery_time, print_off_int_undef) declare_def_handler(marginal_path_err_sample_time, set_off_int_undef) declare_def_snprint_defint(marginal_path_err_sample_time, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(marginal_path_err_sample_time, set_off_int_undef) declare_ovr_snprint(marginal_path_err_sample_time, print_off_int_undef) declare_hw_handler(marginal_path_err_sample_time, set_off_int_undef) declare_hw_snprint(marginal_path_err_sample_time, print_off_int_undef) declare_mp_handler(marginal_path_err_sample_time, set_off_int_undef) declare_mp_snprint(marginal_path_err_sample_time, print_off_int_undef) declare_def_handler(marginal_path_err_rate_threshold, set_off_int_undef) declare_def_snprint_defint(marginal_path_err_rate_threshold, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(marginal_path_err_rate_threshold, set_off_int_undef) declare_ovr_snprint(marginal_path_err_rate_threshold, print_off_int_undef) declare_hw_handler(marginal_path_err_rate_threshold, set_off_int_undef) declare_hw_snprint(marginal_path_err_rate_threshold, print_off_int_undef) declare_mp_handler(marginal_path_err_rate_threshold, set_off_int_undef) declare_mp_snprint(marginal_path_err_rate_threshold, print_off_int_undef) declare_def_handler(marginal_path_err_recheck_gap_time, set_off_int_undef) declare_def_snprint_defint(marginal_path_err_recheck_gap_time, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(marginal_path_err_recheck_gap_time, set_off_int_undef) declare_ovr_snprint(marginal_path_err_recheck_gap_time, print_off_int_undef) declare_hw_handler(marginal_path_err_recheck_gap_time, set_off_int_undef) declare_hw_snprint(marginal_path_err_recheck_gap_time, print_off_int_undef) declare_mp_handler(marginal_path_err_recheck_gap_time, set_off_int_undef) declare_mp_snprint(marginal_path_err_recheck_gap_time, print_off_int_undef) declare_def_handler(marginal_path_double_failed_time, set_off_int_undef) declare_def_snprint_defint(marginal_path_double_failed_time, print_off_int_undef, DEFAULT_ERR_CHECKS) declare_ovr_handler(marginal_path_double_failed_time, set_off_int_undef) declare_ovr_snprint(marginal_path_double_failed_time, print_off_int_undef) declare_hw_handler(marginal_path_double_failed_time, set_off_int_undef) declare_hw_snprint(marginal_path_double_failed_time, print_off_int_undef) declare_mp_handler(marginal_path_double_failed_time, set_off_int_undef) declare_mp_snprint(marginal_path_double_failed_time, print_off_int_undef) declare_def_handler(ghost_delay, set_off_int_undef) declare_def_snprint(ghost_delay, print_off_int_undef) declare_ovr_handler(ghost_delay, set_off_int_undef) declare_ovr_snprint(ghost_delay, print_off_int_undef) declare_hw_handler(ghost_delay, set_off_int_undef) declare_hw_snprint(ghost_delay, print_off_int_undef) declare_mp_handler(ghost_delay, set_off_int_undef) declare_mp_snprint(ghost_delay, print_off_int_undef) declare_def_handler(all_tg_pt, set_yes_no_undef) declare_def_snprint_defint(all_tg_pt, print_yes_no_undef, DEFAULT_ALL_TG_PT) declare_ovr_handler(all_tg_pt, set_yes_no_undef) declare_ovr_snprint(all_tg_pt, print_yes_no_undef) declare_hw_handler(all_tg_pt, set_yes_no_undef) declare_hw_snprint(all_tg_pt, print_yes_no_undef) declare_def_handler(recheck_wwid, set_yes_no_undef) declare_def_snprint_defint(recheck_wwid, print_yes_no_undef, DEFAULT_RECHECK_WWID) declare_ovr_handler(recheck_wwid, set_yes_no_undef) declare_ovr_snprint(recheck_wwid, print_yes_no_undef) declare_hw_handler(recheck_wwid, set_yes_no_undef) declare_hw_snprint(recheck_wwid, print_yes_no_undef) declare_def_range_handler(uxsock_timeout, DEFAULT_REPLY_TIMEOUT, INT_MAX) static int def_auto_resize_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char * buff; buff = set_value(strvec); if (!buff) return 1; if (strcmp(buff, "never") == 0) conf->auto_resize = AUTO_RESIZE_NEVER; else if (strcmp(buff, "grow_only") == 0) conf->auto_resize = AUTO_RESIZE_GROW_ONLY; else if (strcmp(buff, "grow_shrink") == 0) conf->auto_resize = AUTO_RESIZE_GROW_SHRINK; else condlog(1, "%s line %d, invalid value for auto_resize: \"%s\"", file, line_nr, buff); free(buff); return 0; } int print_auto_resize(struct strbuf *buff, long v) { return append_strbuf_quoted(buff, v == AUTO_RESIZE_GROW_ONLY ? "grow_only" : v == AUTO_RESIZE_GROW_SHRINK ? "grow_shrink" : "never"); } declare_def_snprint(auto_resize, print_auto_resize) static int hw_vpd_vendor_handler(struct config *conf, vector strvec, const char *file, int line_nr) { int i; char *buff; struct hwentry * hwe = VECTOR_LAST_SLOT(conf->hwtable); if (!hwe) return 1; buff = set_value(strvec); if (!buff) return 1; for (i = 0; i < VPD_VP_ARRAY_SIZE; i++) { if (strcmp(buff, vpd_vendor_pages[i].name) == 0) { hwe->vpd_vendor_id = i; goto out; } } condlog(1, "%s line %d, invalid value for vpd_vendor: \"%s\"", file, line_nr, buff); out: free(buff); return 0; } static int snprint_hw_vpd_vendor(struct config *conf, struct strbuf *buff, const void * data) { const struct hwentry * hwe = (const struct hwentry *)data; if (hwe->vpd_vendor_id > 0 && hwe->vpd_vendor_id < VPD_VP_ARRAY_SIZE) return append_strbuf_quoted(buff, vpd_vendor_pages[hwe->vpd_vendor_id].name); return 0; } /* * blacklist block handlers */ static int blacklist_handler(struct config *conf, vector strvec, const char*file, int line_nr) { if (!conf->blist_devnode) conf->blist_devnode = vector_alloc(); if (!conf->blist_wwid) conf->blist_wwid = vector_alloc(); if (!conf->blist_device) conf->blist_device = vector_alloc(); if (!conf->blist_property) conf->blist_property = vector_alloc(); if (!conf->blist_protocol) conf->blist_protocol = vector_alloc(); if (!conf->blist_devnode || !conf->blist_wwid || !conf->blist_device || !conf->blist_property || !conf->blist_protocol) return 1; return 0; } static int blacklist_exceptions_handler(struct config *conf, vector strvec, const char *file, int line_nr) { if (!conf->elist_devnode) conf->elist_devnode = vector_alloc(); if (!conf->elist_wwid) conf->elist_wwid = vector_alloc(); if (!conf->elist_device) conf->elist_device = vector_alloc(); if (!conf->elist_property) conf->elist_property = vector_alloc(); if (!conf->elist_protocol) conf->elist_protocol = vector_alloc(); if (!conf->elist_devnode || !conf->elist_wwid || !conf->elist_device || !conf->elist_property || !conf->elist_protocol) return 1; return 0; } #define declare_ble_handler(option) \ static int \ ble_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ char *buff; \ int rc; \ \ if (!conf->option) \ return 1; \ \ buff = set_value(strvec); \ if (!buff) \ return 1; \ \ rc = store_ble(conf->option, buff, ORIGIN_CONFIG); \ free(buff); \ return rc; \ } #define declare_ble_device_handler(name, option, vend, prod) \ static int \ ble_ ## option ## _ ## name ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ char * buff; \ int rc; \ \ if (!conf->option) \ return 1; \ \ buff = set_value(strvec); \ if (!buff) \ return 1; \ \ rc = set_ble_device(conf->option, vend, prod, ORIGIN_CONFIG); \ free(buff); \ return rc; \ } declare_ble_handler(blist_devnode) declare_ble_handler(elist_devnode) declare_ble_handler(blist_wwid) declare_ble_handler(elist_wwid) declare_ble_handler(blist_property) declare_ble_handler(elist_property) declare_ble_handler(blist_protocol) declare_ble_handler(elist_protocol) static int snprint_def_uxsock_timeout(struct config *conf, struct strbuf *buff, const void *data) { return print_strbuf(buff, "%u", conf->uxsock_timeout); } static int snprint_ble_simple (struct config *conf, struct strbuf *buff, const void *data) { const struct blentry *ble = (const struct blentry *)data; return print_str(buff, ble->str); } static int ble_device_handler(struct config *conf, vector strvec, const char *file, int line_nr) { return alloc_ble_device(conf->blist_device); } static int ble_except_device_handler(struct config *conf, vector strvec, const char *file, int line_nr) { return alloc_ble_device(conf->elist_device); } declare_ble_device_handler(vendor, blist_device, buff, NULL) declare_ble_device_handler(vendor, elist_device, buff, NULL) declare_ble_device_handler(product, blist_device, NULL, buff) declare_ble_device_handler(product, elist_device, NULL, buff) static int snprint_bled_vendor(struct config *conf, struct strbuf *buff, const void * data) { const struct blentry_device * bled = (const struct blentry_device *)data; return print_str(buff, bled->vendor); } static int snprint_bled_product(struct config *conf, struct strbuf *buff, const void *data) { const struct blentry_device * bled = (const struct blentry_device *)data; return print_str(buff, bled->product); } /* * devices block handlers */ static int devices_handler(struct config *conf, vector strvec, const char *file, int line_nr) { if (!conf->hwtable) conf->hwtable = vector_alloc(); if (!conf->hwtable) return 1; return 0; } static int device_handler(struct config *conf, vector strvec, const char *file, int line_nr) { struct hwentry * hwe; hwe = alloc_hwe(); if (!hwe) return 1; if (!vector_alloc_slot(conf->hwtable)) { free_hwe(hwe); return 1; } vector_set_slot(conf->hwtable, hwe); return 0; } declare_hw_handler(vendor, set_str) declare_hw_snprint(vendor, print_str) declare_hw_handler(product, set_str) declare_hw_snprint(product, print_str) declare_hw_handler(revision, set_str) declare_hw_snprint(revision, print_str) declare_hw_handler(bl_product, set_str) declare_hw_snprint(bl_product, print_str) declare_hw_arg_str_handler(hwhandler, 0) declare_hw_snprint(hwhandler, print_str) /* * overrides handlers */ static int overrides_handler(struct config *conf, vector strvec, const char *file, int line_nr) { if (!conf->overrides) conf->overrides = alloc_hwe(); if (!conf->overrides) return 1; return 0; } /* * multipaths block handlers */ static int multipaths_handler(struct config *conf, vector strvec, const char *file, int line_nr) { if (!conf->mptable) conf->mptable = vector_alloc(); if (!conf->mptable) return 1; return 0; } static int multipath_handler(struct config *conf, vector strvec, const char *file, int line_nr) { struct mpentry * mpe; mpe = alloc_mpe(); if (!mpe) return 1; if (!vector_alloc_slot(conf->mptable)) { free_mpe(mpe); return 1; } vector_set_slot(conf->mptable, mpe); return 0; } declare_mp_handler(wwid, set_str) declare_mp_snprint(wwid, print_str) declare_mp_handler(alias, set_str_noslash) declare_mp_snprint(alias, print_str) static int protocol_handler(struct config *conf, vector strvec, const char *file, int line_nr) { struct pcentry *pce; if (!conf->overrides) return 1; if (!conf->overrides->pctable && !(conf->overrides->pctable = vector_alloc())) return 1; if (!(pce = alloc_pce())) return 1; if (!vector_alloc_slot(conf->overrides->pctable)) { free(pce); return 1; } vector_set_slot(conf->overrides->pctable, pce); return 0; } static int set_protocol_type(vector strvec, void *ptr, const char *file, int line_nr) { int *int_ptr = (int *)ptr; char *buff; int i; buff = set_value(strvec); if (!buff) return 1; for (i = 0; i <= LAST_BUS_PROTOCOL_ID; i++) { if (protocol_name[i] && !strcmp(buff, protocol_name[i])) { *int_ptr = i; break; } } if (i > LAST_BUS_PROTOCOL_ID) condlog(1, "%s line %d, invalid value for type: \"%s\"", file, line_nr, buff); free(buff); return 0; } static int print_protocol_type(struct strbuf *buff, int type) { if (type < 0) return 0; return append_strbuf_quoted(buff, protocol_name[type]); } declare_pc_handler(type, set_protocol_type) declare_pc_snprint(type, print_protocol_type) /* * deprecated handlers */ static int deprecated_handler(struct config *conf, vector strvec, const char *file, int line_nr) { char * buff; buff = set_value(strvec); if (!buff) return 1; free(buff); return 0; } static int snprint_deprecated (struct config *conf, struct strbuf *buff, const void * data) { return 0; } // Deprecated keywords declare_deprecated_handler(config_dir, CONFIG_DIR) declare_deprecated_handler(disable_changed_wwids, "yes") declare_deprecated_handler(getuid_callout, "(not set)") declare_deprecated_handler(multipath_dir, MULTIPATH_DIR) 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) /* * If you add or remove a keyword also update multipath/multipath.conf.5 */ void init_keywords(vector keywords) { install_keyword_root("defaults", NULL); install_keyword("verbosity", &def_verbosity_handler, &snprint_def_verbosity); install_keyword("polling_interval", &checkint_handler, &snprint_def_checkint); install_keyword("max_polling_interval", &def_max_checkint_handler, &snprint_def_max_checkint); install_keyword("reassign_maps", &def_reassign_maps_handler, &snprint_def_reassign_maps); install_keyword("multipath_dir", &deprecated_multipath_dir_handler, &snprint_deprecated); install_keyword("path_selector", &def_selector_handler, &snprint_def_selector); install_keyword("path_grouping_policy", &def_pgpolicy_handler, &snprint_def_pgpolicy); install_keyword("uid_attrs", &uid_attrs_handler, &snprint_uid_attrs); install_keyword("uid_attribute", &def_uid_attribute_handler, &snprint_def_uid_attribute); install_keyword("getuid_callout", &deprecated_getuid_callout_handler, &snprint_deprecated); install_keyword("prio", &def_prio_name_handler, &snprint_def_prio_name); install_keyword("prio_args", &def_prio_args_handler, &snprint_def_prio_args); install_keyword("features", &def_features_handler, &snprint_def_features); install_keyword("path_checker", &def_checker_name_handler, &snprint_def_checker_name); 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("max_fds", &max_fds_handler, &snprint_max_fds); install_keyword("rr_weight", &def_rr_weight_handler, &snprint_def_rr_weight); 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); install_keyword("allow_usb_devices", &def_allow_usb_devices_handler, &snprint_def_allow_usb_devices); install_keyword("pg_timeout", &deprecated_pg_timeout_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &def_flush_on_last_del_handler, &snprint_def_flush_on_last_del); install_keyword("user_friendly_names", &def_user_friendly_names_handler, &snprint_def_user_friendly_names); install_keyword("mode", &def_mode_handler, &snprint_def_mode); install_keyword("uid", &def_uid_handler, &snprint_def_uid); install_keyword("gid", &def_gid_handler, &snprint_def_gid); install_keyword("fast_io_fail_tmo", &def_fast_io_fail_handler, &snprint_def_fast_io_fail); install_keyword("dev_loss_tmo", &def_dev_loss_handler, &snprint_def_dev_loss); install_keyword("eh_deadline", &def_eh_deadline_handler, &snprint_def_eh_deadline); install_keyword("max_retries", &def_max_retries_handler, &snprint_def_max_retries); install_keyword("bindings_file", &deprecated_bindings_file_handler, &snprint_deprecated); install_keyword("wwids_file", &deprecated_wwids_file_handler, &snprint_deprecated); install_keyword("prkeys_file", &deprecated_prkeys_file_handler, &snprint_deprecated); install_keyword("log_checker_err", &def_log_checker_err_handler, &snprint_def_log_checker_err); install_keyword("reservation_key", &def_reservation_key_handler, &snprint_def_reservation_key); install_keyword("all_tg_pt", &def_all_tg_pt_handler, &snprint_def_all_tg_pt); install_keyword("retain_attached_hw_handler", &def_retain_hwhandler_handler, &snprint_def_retain_hwhandler); install_keyword("detect_prio", &def_detect_prio_handler, &snprint_def_detect_prio); install_keyword("detect_checker", &def_detect_checker_handler, &snprint_def_detect_checker); install_keyword("detect_pgpolicy", &def_detect_pgpolicy_handler, &snprint_def_detect_pgpolicy); install_keyword("detect_pgpolicy_use_tpg", &def_detect_pgpolicy_use_tpg_handler, &snprint_def_detect_pgpolicy_use_tpg); install_keyword("force_sync", &def_force_sync_handler, &snprint_def_force_sync); install_keyword("strict_timing", &def_strict_timing_handler, &snprint_def_strict_timing); install_keyword("deferred_remove", &def_deferred_remove_handler, &snprint_def_deferred_remove); install_keyword("partition_delimiter", &def_partition_delim_handler, &snprint_def_partition_delim); install_keyword("config_dir", &deprecated_config_dir_handler, &snprint_deprecated); install_keyword("delay_watch_checks", &def_delay_watch_checks_handler, &snprint_def_delay_watch_checks); install_keyword("delay_wait_checks", &def_delay_wait_checks_handler, &snprint_def_delay_wait_checks); install_keyword("san_path_err_threshold", &def_san_path_err_threshold_handler, &snprint_def_san_path_err_threshold); install_keyword("san_path_err_forget_rate", &def_san_path_err_forget_rate_handler, &snprint_def_san_path_err_forget_rate); install_keyword("san_path_err_recovery_time", &def_san_path_err_recovery_time_handler, &snprint_def_san_path_err_recovery_time); install_keyword("marginal_path_err_sample_time", &def_marginal_path_err_sample_time_handler, &snprint_def_marginal_path_err_sample_time); install_keyword("marginal_path_err_rate_threshold", &def_marginal_path_err_rate_threshold_handler, &snprint_def_marginal_path_err_rate_threshold); install_keyword("marginal_path_err_recheck_gap_time", &def_marginal_path_err_recheck_gap_time_handler, &snprint_def_marginal_path_err_recheck_gap_time); install_keyword("marginal_path_double_failed_time", &def_marginal_path_double_failed_time_handler, &snprint_def_marginal_path_double_failed_time); install_keyword("find_multipaths", &def_find_multipaths_handler, &snprint_def_find_multipaths); install_keyword("uxsock_timeout", &def_uxsock_timeout_handler, &snprint_def_uxsock_timeout); install_keyword("retrigger_tries", &def_retrigger_tries_handler, &snprint_def_retrigger_tries); install_keyword("retrigger_delay", &def_retrigger_delay_handler, &snprint_def_retrigger_delay); install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout); install_keyword("skip_kpartx", &def_skip_kpartx_handler, &snprint_def_skip_kpartx); install_keyword("disable_changed_wwids", &deprecated_disable_changed_wwids_handler, &snprint_deprecated); install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb); install_keyword("ghost_delay", &def_ghost_delay_handler, &snprint_def_ghost_delay); install_keyword("auto_resize", &def_auto_resize_handler, &snprint_def_auto_resize); install_keyword("find_multipaths_timeout", &def_find_multipaths_timeout_handler, &snprint_def_find_multipaths_timeout); install_keyword("enable_foreign", &def_enable_foreign_handler, &snprint_def_enable_foreign); install_keyword("marginal_pathgroups", &def_marginal_pathgroups_handler, &snprint_def_marginal_pathgroups); install_keyword("recheck_wwid", &def_recheck_wwid_handler, &snprint_def_recheck_wwid); install_keyword_root("blacklist", &blacklist_handler); install_keyword_multi("devnode", &ble_blist_devnode_handler, &snprint_ble_simple); install_keyword_multi("wwid", &ble_blist_wwid_handler, &snprint_ble_simple); install_keyword_multi("property", &ble_blist_property_handler, &snprint_ble_simple); install_keyword_multi("protocol", &ble_blist_protocol_handler, &snprint_ble_simple); install_keyword_multi("device", &ble_device_handler, NULL); install_sublevel(); install_keyword("vendor", &ble_blist_device_vendor_handler, &snprint_bled_vendor); install_keyword("product", &ble_blist_device_product_handler, &snprint_bled_product); install_sublevel_end(); install_keyword_root("blacklist_exceptions", &blacklist_exceptions_handler); install_keyword_multi("devnode", &ble_elist_devnode_handler, &snprint_ble_simple); install_keyword_multi("wwid", &ble_elist_wwid_handler, &snprint_ble_simple); install_keyword_multi("property", &ble_elist_property_handler, &snprint_ble_simple); install_keyword_multi("protocol", &ble_elist_protocol_handler, &snprint_ble_simple); install_keyword_multi("device", &ble_except_device_handler, NULL); install_sublevel(); install_keyword("vendor", &ble_elist_device_vendor_handler, &snprint_bled_vendor); install_keyword("product", &ble_elist_device_product_handler, &snprint_bled_product); install_sublevel_end(); /* * If you add or remove a "device subsection" keyword also update * multipath/multipath.conf.5 and the TEMPLATE in libmultipath/hwtable.c */ install_keyword_root("devices", &devices_handler); install_keyword_multi("device", &device_handler, NULL); install_sublevel(); install_keyword("vendor", &hw_vendor_handler, &snprint_hw_vendor); install_keyword("product", &hw_product_handler, &snprint_hw_product); install_keyword("revision", &hw_revision_handler, &snprint_hw_revision); install_keyword("product_blacklist", &hw_bl_product_handler, &snprint_hw_bl_product); install_keyword("path_grouping_policy", &hw_pgpolicy_handler, &snprint_hw_pgpolicy); install_keyword("uid_attribute", &hw_uid_attribute_handler, &snprint_hw_uid_attribute); install_keyword("getuid_callout", &deprecated_getuid_callout_handler, &snprint_deprecated); install_keyword("path_selector", &hw_selector_handler, &snprint_hw_selector); install_keyword("path_checker", &hw_checker_name_handler, &snprint_hw_checker_name); install_keyword("checker", &hw_checker_name_handler, NULL); install_keyword("alias_prefix", &hw_alias_prefix_handler, &snprint_hw_alias_prefix); install_keyword("features", &hw_features_handler, &snprint_hw_features); install_keyword("hardware_handler", &hw_hwhandler_handler, &snprint_hw_hwhandler); 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("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("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); install_keyword("dev_loss_tmo", &hw_dev_loss_handler, &snprint_hw_dev_loss); install_keyword("eh_deadline", &hw_eh_deadline_handler, &snprint_hw_eh_deadline); install_keyword("user_friendly_names", &hw_user_friendly_names_handler, &snprint_hw_user_friendly_names); install_keyword("retain_attached_hw_handler", &hw_retain_hwhandler_handler, &snprint_hw_retain_hwhandler); install_keyword("detect_prio", &hw_detect_prio_handler, &snprint_hw_detect_prio); install_keyword("detect_checker", &hw_detect_checker_handler, &snprint_hw_detect_checker); install_keyword("detect_pgpolicy", &hw_detect_pgpolicy_handler, &snprint_hw_detect_pgpolicy); install_keyword("detect_pgpolicy_use_tpg", &hw_detect_pgpolicy_use_tpg_handler, &snprint_hw_detect_pgpolicy_use_tpg); install_keyword("deferred_remove", &hw_deferred_remove_handler, &snprint_hw_deferred_remove); install_keyword("delay_watch_checks", &hw_delay_watch_checks_handler, &snprint_hw_delay_watch_checks); install_keyword("delay_wait_checks", &hw_delay_wait_checks_handler, &snprint_hw_delay_wait_checks); install_keyword("san_path_err_threshold", &hw_san_path_err_threshold_handler, &snprint_hw_san_path_err_threshold); install_keyword("san_path_err_forget_rate", &hw_san_path_err_forget_rate_handler, &snprint_hw_san_path_err_forget_rate); install_keyword("san_path_err_recovery_time", &hw_san_path_err_recovery_time_handler, &snprint_hw_san_path_err_recovery_time); install_keyword("marginal_path_err_sample_time", &hw_marginal_path_err_sample_time_handler, &snprint_hw_marginal_path_err_sample_time); install_keyword("marginal_path_err_rate_threshold", &hw_marginal_path_err_rate_threshold_handler, &snprint_hw_marginal_path_err_rate_threshold); install_keyword("marginal_path_err_recheck_gap_time", &hw_marginal_path_err_recheck_gap_time_handler, &snprint_hw_marginal_path_err_recheck_gap_time); install_keyword("marginal_path_double_failed_time", &hw_marginal_path_double_failed_time_handler, &snprint_hw_marginal_path_double_failed_time); install_keyword("skip_kpartx", &hw_skip_kpartx_handler, &snprint_hw_skip_kpartx); install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb); install_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay); install_keyword("all_tg_pt", &hw_all_tg_pt_handler, &snprint_hw_all_tg_pt); install_keyword("vpd_vendor", &hw_vpd_vendor_handler, &snprint_hw_vpd_vendor); install_keyword("recheck_wwid", &hw_recheck_wwid_handler, &snprint_hw_recheck_wwid); install_sublevel_end(); install_keyword_root("overrides", &overrides_handler); install_keyword("path_grouping_policy", &ovr_pgpolicy_handler, &snprint_ovr_pgpolicy); install_keyword("uid_attribute", &ovr_uid_attribute_handler, &snprint_ovr_uid_attribute); install_keyword("getuid_callout", &deprecated_getuid_callout_handler, &snprint_deprecated); install_keyword("path_selector", &ovr_selector_handler, &snprint_ovr_selector); install_keyword("path_checker", &ovr_checker_name_handler, &snprint_ovr_checker_name); install_keyword("checker", &ovr_checker_name_handler, NULL); install_keyword("alias_prefix", &ovr_alias_prefix_handler, &snprint_ovr_alias_prefix); install_keyword("features", &ovr_features_handler, &snprint_ovr_features); 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("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("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); install_keyword("eh_deadline", &ovr_eh_deadline_handler, &snprint_ovr_eh_deadline); install_keyword("user_friendly_names", &ovr_user_friendly_names_handler, &snprint_ovr_user_friendly_names); install_keyword("retain_attached_hw_handler", &ovr_retain_hwhandler_handler, &snprint_ovr_retain_hwhandler); install_keyword("detect_prio", &ovr_detect_prio_handler, &snprint_ovr_detect_prio); install_keyword("detect_checker", &ovr_detect_checker_handler, &snprint_ovr_detect_checker); install_keyword("detect_pgpolicy", &ovr_detect_pgpolicy_handler, &snprint_ovr_detect_pgpolicy); install_keyword("detect_pgpolicy_use_tpg", &ovr_detect_pgpolicy_use_tpg_handler, &snprint_ovr_detect_pgpolicy_use_tpg); install_keyword("deferred_remove", &ovr_deferred_remove_handler, &snprint_ovr_deferred_remove); install_keyword("delay_watch_checks", &ovr_delay_watch_checks_handler, &snprint_ovr_delay_watch_checks); install_keyword("delay_wait_checks", &ovr_delay_wait_checks_handler, &snprint_ovr_delay_wait_checks); install_keyword("san_path_err_threshold", &ovr_san_path_err_threshold_handler, &snprint_ovr_san_path_err_threshold); install_keyword("san_path_err_forget_rate", &ovr_san_path_err_forget_rate_handler, &snprint_ovr_san_path_err_forget_rate); install_keyword("san_path_err_recovery_time", &ovr_san_path_err_recovery_time_handler, &snprint_ovr_san_path_err_recovery_time); install_keyword("marginal_path_err_sample_time", &ovr_marginal_path_err_sample_time_handler, &snprint_ovr_marginal_path_err_sample_time); install_keyword("marginal_path_err_rate_threshold", &ovr_marginal_path_err_rate_threshold_handler, &snprint_ovr_marginal_path_err_rate_threshold); install_keyword("marginal_path_err_recheck_gap_time", &ovr_marginal_path_err_recheck_gap_time_handler, &snprint_ovr_marginal_path_err_recheck_gap_time); install_keyword("marginal_path_double_failed_time", &ovr_marginal_path_double_failed_time_handler, &snprint_ovr_marginal_path_double_failed_time); install_keyword("skip_kpartx", &ovr_skip_kpartx_handler, &snprint_ovr_skip_kpartx); install_keyword("max_sectors_kb", &ovr_max_sectors_kb_handler, &snprint_ovr_max_sectors_kb); install_keyword("ghost_delay", &ovr_ghost_delay_handler, &snprint_ovr_ghost_delay); install_keyword("all_tg_pt", &ovr_all_tg_pt_handler, &snprint_ovr_all_tg_pt); install_keyword("recheck_wwid", &ovr_recheck_wwid_handler, &snprint_ovr_recheck_wwid); install_keyword_multi("protocol", &protocol_handler, NULL); install_sublevel(); install_keyword("type", &pc_type_handler, &snprint_pc_type); install_keyword("fast_io_fail_tmo", &pc_fast_io_fail_handler, &snprint_pc_fast_io_fail); install_keyword("dev_loss_tmo", &pc_dev_loss_handler, &snprint_pc_dev_loss); install_keyword("eh_deadline", &pc_eh_deadline_handler, &snprint_pc_eh_deadline); install_sublevel_end(); install_keyword_root("multipaths", &multipaths_handler); install_keyword_multi("multipath", &multipath_handler, NULL); install_sublevel(); install_keyword("wwid", &mp_wwid_handler, &snprint_mp_wwid); install_keyword("alias", &mp_alias_handler, &snprint_mp_alias); install_keyword("path_grouping_policy", &mp_pgpolicy_handler, &snprint_mp_pgpolicy); install_keyword("path_selector", &mp_selector_handler, &snprint_mp_selector); 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("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("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); install_keyword("mode", &mp_mode_handler, &snprint_mp_mode); install_keyword("uid", &mp_uid_handler, &snprint_mp_uid); install_keyword("gid", &mp_gid_handler, &snprint_mp_gid); install_keyword("reservation_key", &mp_reservation_key_handler, &snprint_mp_reservation_key); install_keyword("user_friendly_names", &mp_user_friendly_names_handler, &snprint_mp_user_friendly_names); install_keyword("deferred_remove", &mp_deferred_remove_handler, &snprint_mp_deferred_remove); install_keyword("delay_watch_checks", &mp_delay_watch_checks_handler, &snprint_mp_delay_watch_checks); install_keyword("delay_wait_checks", &mp_delay_wait_checks_handler, &snprint_mp_delay_wait_checks); install_keyword("san_path_err_threshold", &mp_san_path_err_threshold_handler, &snprint_mp_san_path_err_threshold); install_keyword("san_path_err_forget_rate", &mp_san_path_err_forget_rate_handler, &snprint_mp_san_path_err_forget_rate); install_keyword("san_path_err_recovery_time", &mp_san_path_err_recovery_time_handler, &snprint_mp_san_path_err_recovery_time); install_keyword("marginal_path_err_sample_time", &mp_marginal_path_err_sample_time_handler, &snprint_mp_marginal_path_err_sample_time); install_keyword("marginal_path_err_rate_threshold", &mp_marginal_path_err_rate_threshold_handler, &snprint_mp_marginal_path_err_rate_threshold); install_keyword("marginal_path_err_recheck_gap_time", &mp_marginal_path_err_recheck_gap_time_handler, &snprint_mp_marginal_path_err_recheck_gap_time); install_keyword("marginal_path_double_failed_time", &mp_marginal_path_double_failed_time_handler, &snprint_mp_marginal_path_double_failed_time); install_keyword("skip_kpartx", &mp_skip_kpartx_handler, &snprint_mp_skip_kpartx); install_keyword("max_sectors_kb", &mp_max_sectors_kb_handler, &snprint_mp_max_sectors_kb); install_keyword("ghost_delay", &mp_ghost_delay_handler, &snprint_mp_ghost_delay); install_sublevel_end(); } multipath-tools-0.11.1/libmultipath/dict.h000066400000000000000000000012531475246302400205410ustar00rootroot00000000000000#ifndef DICT_H_INCLUDED #define DICT_H_INCLUDED #include "vector.h" #include "byteorder.h" 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); int print_undef_off_zero(struct strbuf *buff, long v); int print_dev_loss(struct strbuf *buff, unsigned long v); int print_off_int_undef(struct strbuf *buff, long v); int print_auto_resize(struct strbuf *buff, long v); int print_flush_on_last_del(struct strbuf *buff, long v); #endif /* DICT_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/discovery.c000066400000000000000000001727521475246302400216350ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005, 2006 Christophe Varoqui * Copyright (c) 2005 Stefan Bader, IBM * Copyright (c) 2005 Mike Anderson */ #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "vector.h" #include "util.h" #include "structs.h" #include "config.h" #include "blacklist.h" #include "debug.h" #include "propsel.h" #include "sg_include.h" #include "sysfs.h" #include "discovery.h" #include "prio.h" #include "defaults.h" #include "unaligned.h" #include "prioritizers/alua_rtpg.h" #include "foreign.h" #include "configure.h" #include "print.h" #include "strbuf.h" #define VPD_BUFLEN 4096 struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE] = { [VPD_VP_UNDEF] = { 0x00, "undef" }, [VPD_VP_HP3PAR] = { 0xc0, "hp3par" }, }; int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice, const char *wwid, int flag, struct path **pp_ptr) { int err = PATHINFO_FAILED; struct path * pp; const char * devname; if (pp_ptr) *pp_ptr = NULL; devname = udev_device_get_sysname(udevice); if (!devname) return PATHINFO_FAILED; pp = alloc_path(); if (!pp) return PATHINFO_FAILED; if (wwid) strlcpy(pp->wwid, wwid, sizeof(pp->wwid)); if (safe_sprintf(pp->dev, "%s", devname)) { condlog(0, "pp->dev too small"); err = 1; } else { pp->udev = udev_device_ref(udevice); err = pathinfo(pp, conf, flag | DI_BLACKLIST); } if (err || !pp_ptr) free_path(pp); else if (pp_ptr) *pp_ptr = pp; return err; } int store_pathinfo (vector pathvec, struct config *conf, struct udev_device *udevice, int flag, struct path **pp_ptr) { int err = PATHINFO_FAILED; struct path * pp; const char * devname; if (pp_ptr) *pp_ptr = NULL; devname = udev_device_get_sysname(udevice); if (!devname) return PATHINFO_FAILED; pp = alloc_path(); if (!pp) return PATHINFO_FAILED; if(safe_sprintf(pp->dev, "%s", devname)) { condlog(0, "pp->dev too small"); goto out; } pp->udev = udev_device_ref(udevice); err = pathinfo(pp, conf, flag); if (err) goto out; err = store_path(pathvec, pp); if (err) goto out; pp->checkint = conf->checkint; out: if (err) free_path(pp); else if (pp_ptr) *pp_ptr = pp; return err; } static int path_discover (vector pathvec, struct config * conf, struct udev_device *udevice, int flag) { struct path *pp; char devt[BLK_DEV_SIZE]; dev_t devnum = udev_device_get_devnum(udevice); snprintf(devt, BLK_DEV_SIZE, "%d:%d", major(devnum), minor(devnum)); pp = find_path_by_devt(pathvec, devt); if (!pp) return store_pathinfo(pathvec, conf, udevice, flag | DI_BLACKLIST, NULL); else /* * Don't use DI_BLACKLIST on paths already in pathvec. We rely * on the caller to pre-populate the pathvec with valid paths * only. */ return pathinfo(pp, conf, flag); } static void cleanup_udev_enumerate_ptr(void *arg) { struct udev_enumerate *ue; if (!arg) return; ue = *((struct udev_enumerate**) arg); if (ue) (void)udev_enumerate_unref(ue); } static void cleanup_udev_device_ptr(void *arg) { struct udev_device *ud; if (!arg) return; ud = *((struct udev_device**) arg); if (ud) (void)udev_device_unref(ud); } int path_discovery (vector pathvec, int flag) { struct udev_enumerate *udev_iter = NULL; struct udev_list_entry *entry; struct udev_device *udevice = NULL; struct config *conf; int num_paths = 0, total_paths = 0, ret; pthread_cleanup_push(cleanup_udev_enumerate_ptr, &udev_iter); pthread_cleanup_push(cleanup_udev_device_ptr, &udevice); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); udev_iter = udev_enumerate_new(udev); if (!udev_iter) { ret = -ENOMEM; goto out; } if (udev_enumerate_add_match_subsystem(udev_iter, "block") < 0 || udev_enumerate_add_match_is_initialized(udev_iter) < 0 || udev_enumerate_scan_devices(udev_iter) < 0) { condlog(1, "%s: error setting up udev_enumerate: %m", __func__); ret = -1; goto out; } udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_iter)) { const char *devtype; const char *devpath; if (should_exit()) break; devpath = udev_list_entry_get_name(entry); condlog(4, "Discover device %s", devpath); udevice = udev_device_new_from_syspath(udev, devpath); if (!udevice) { condlog(4, "%s: no udev information", devpath); continue; } devtype = udev_device_get_devtype(udevice); if(devtype && !strncmp(devtype, "disk", 4)) { total_paths++; if (path_discover(pathvec, conf, udevice, flag) == PATHINFO_OK) num_paths++; } udev_device_unref(udevice); udevice = NULL; } ret = total_paths - num_paths; condlog(4, "Discovered %d/%d paths", num_paths, total_paths); out: pthread_cleanup_pop(1); pthread_cleanup_pop(1); pthread_cleanup_pop(1); return ret; } #define declare_sysfs_get_str(fname) \ ssize_t \ sysfs_get_##fname (struct udev_device * udev, char * buff, size_t len) \ { \ size_t l; \ const char * attr; \ const char * devname; \ \ if (!udev) \ return -ENOSYS; \ \ devname = udev_device_get_sysname(udev); \ \ attr = udev_device_get_sysattr_value(udev, #fname); \ if (!attr) { \ condlog(3, "%s: attribute %s not found in sysfs", \ devname, #fname); \ return -ENXIO; \ } \ for (l = strlen(attr); l >= 1 && isspace(attr[l-1]); l--); \ if (l > len) { \ condlog(3, "%s: overflow in attribute %s", \ devname, #fname); \ return -EINVAL; \ } \ strlcpy(buff, attr, len); \ return strchop(buff); \ } declare_sysfs_get_str(devtype); declare_sysfs_get_str(vendor); declare_sysfs_get_str(model); declare_sysfs_get_str(rev); ssize_t sysfs_get_vpd(struct udev_device * udev, unsigned char pg, unsigned char *buff, size_t len) { char attrname[9]; snprintf(attrname, sizeof(attrname), "vpd_pg%02x", pg); return sysfs_bin_attr_get_value(udev, attrname, buff, len); } ssize_t sysfs_get_inquiry(struct udev_device * udev, unsigned char *buff, size_t len) { return sysfs_bin_attr_get_value(udev, "inquiry", buff, len); } int sysfs_get_timeout(const struct path *pp, unsigned int *timeout) { const char *attr = NULL; const char *subsys; struct udev_device *parent; char *eptr; unsigned long t; if (!pp->udev || pp->bus != SYSFS_BUS_SCSI) return -ENOSYS; parent = pp->udev; while (parent) { subsys = udev_device_get_subsystem(parent); attr = udev_device_get_sysattr_value(parent, "timeout"); if (subsys && attr) break; parent = udev_device_get_parent(parent); } if (!attr) { condlog(3, "%s: No timeout value in sysfs", pp->dev); return -ENXIO; } t = strtoul(attr, &eptr, 0); if (attr == eptr || t == ULONG_MAX) { condlog(3, "%s: Cannot parse timeout attribute '%s'", pp->dev, attr); return -EINVAL; } if (t > UINT_MAX) { condlog(3, "%s: Overflow in timeout value '%s'", pp->dev, attr); return -ERANGE; } *timeout = t; return 1; } static int sysfs_get_tgt_nodename(struct path *pp, char *node) { const char *tgtname, *value; struct udev_device *parent, *tgtdev; int host, channel, tgtid = -1; if (!pp->udev) return 1; parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_device"); if (!parent) return 1; /* Check for SAS */ value = udev_device_get_sysattr_value(parent, "sas_address"); if (value) { tgtdev = udev_device_get_parent(parent); while (tgtdev) { char c; tgtname = udev_device_get_sysname(tgtdev); if (tgtname) { if (sscanf(tgtname, "end_device-%d:%d:%d%c", &host, &channel, &tgtid, &c) == 3) break; if (sscanf(tgtname, "end_device-%d:%d%c", &host, &tgtid, &c) == 2) break; } tgtdev = udev_device_get_parent(tgtdev); tgtid = -1; } if (tgtid >= 0) { pp->sg_id.proto_id = SCSI_PROTOCOL_SAS; pp->sg_id.transport_id = tgtid; strlcpy(node, value, NODE_NAME_SIZE); return 0; } } /* Check for USB */ tgtdev = udev_device_get_parent(parent); while (tgtdev) { value = udev_device_get_subsystem(tgtdev); if (value && !strcmp(value, "usb")) { pp->sg_id.proto_id = SCSI_PROTOCOL_USB; tgtname = udev_device_get_sysname(tgtdev); if (tgtname) { strlcpy(node, tgtname, NODE_NAME_SIZE); return 0; } } tgtdev = udev_device_get_parent(tgtdev); } parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_target"); if (!parent) return 1; /* Check for FibreChannel */ tgtdev = udev_device_get_parent(parent); value = udev_device_get_sysname(tgtdev); if (value && sscanf(value, "rport-%d:%d-%d", &host, &channel, &tgtid) == 3) { tgtdev = udev_device_new_from_subsystem_sysname(udev, "fc_remote_ports", value); if (tgtdev) { condlog(4, "SCSI target %d:%d:%d -> " "FC rport %d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, host, channel, tgtid); value = udev_device_get_sysattr_value(tgtdev, "node_name"); if (value) { pp->sg_id.proto_id = SCSI_PROTOCOL_FCP; pp->sg_id.transport_id = tgtid; strlcpy(node, value, NODE_NAME_SIZE); udev_device_unref(tgtdev); return 0; } else udev_device_unref(tgtdev); } } /* Check for iSCSI */ parent = pp->udev; tgtname = NULL; while (parent) { tgtname = udev_device_get_sysname(parent); if (tgtname && sscanf(tgtname , "session%d", &tgtid) == 1) break; parent = udev_device_get_parent(parent); tgtname = NULL; tgtid = -1; } if (parent && tgtname) { tgtdev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", tgtname); if (tgtdev) { const char *value; value = udev_device_get_sysattr_value(tgtdev, "targetname"); if (value) { pp->sg_id.proto_id = SCSI_PROTOCOL_ISCSI; pp->sg_id.transport_id = tgtid; strlcpy(node, value, NODE_NAME_SIZE); udev_device_unref(tgtdev); return 0; } else udev_device_unref(tgtdev); } } /* Check for libata */ parent = pp->udev; tgtname = NULL; while (parent) { tgtname = udev_device_get_sysname(parent); if (tgtname && sscanf(tgtname, "ata%d", &tgtid) == 1) break; parent = udev_device_get_parent(parent); tgtname = NULL; } if (tgtname) { pp->sg_id.proto_id = SCSI_PROTOCOL_ATA; pp->sg_id.transport_id = tgtid; snprintf(node, NODE_NAME_SIZE, "ata-%d.00", tgtid); return 0; } /* Unknown SCSI transport. Keep fingers crossed */ pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; return 0; } static int sysfs_get_host_bus_id(const struct path *pp, char *bus_id) { struct udev_device *hostdev, *parent; char host_name[HOST_NAME_LEN]; const char *driver_name, *subsystem_name, *value; if (!pp || !bus_id) return 1; snprintf(host_name, sizeof(host_name), "host%d", pp->sg_id.host_no); hostdev = udev_device_new_from_subsystem_sysname(udev, "scsi_host", host_name); if (!hostdev) return 1; for (parent = udev_device_get_parent(hostdev); parent; parent = udev_device_get_parent(parent)) { driver_name = udev_device_get_driver(parent); subsystem_name = udev_device_get_subsystem(parent); if (driver_name && !strcmp(driver_name, "pcieport")) break; if (subsystem_name && !strcmp(subsystem_name, "ccw")) break; } if (parent) { /* pci_device or ccw fcp device found */ value = udev_device_get_sysname(parent); if (!value) { udev_device_unref(hostdev); return 1; } strlcpy(bus_id, value, SLOT_NAME_SIZE); udev_device_unref(hostdev); return 0; } udev_device_unref(hostdev); return 1; } int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name) { int proto_id; if (!pp || !adapter_name) return 1; proto_id = pp->sg_id.proto_id; if (pp->bus != SYSFS_BUS_SCSI || (proto_id != SCSI_PROTOCOL_FCP && proto_id != SCSI_PROTOCOL_SAS && proto_id != SCSI_PROTOCOL_ISCSI && proto_id != SCSI_PROTOCOL_SRP)) { return 1; } /* iscsi doesn't have adapter info in sysfs * get ip_address for grouping paths */ if (pp->sg_id.proto_id == SCSI_PROTOCOL_ISCSI) return sysfs_get_iscsi_ip_address(pp, adapter_name); /* fetch adapter bus-ID for other protocols */ return sysfs_get_host_bus_id(pp, adapter_name); } int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address) { struct udev_device *hostdev; char host_name[HOST_NAME_LEN]; const char *value; sprintf(host_name, "host%d", pp->sg_id.host_no); hostdev = udev_device_new_from_subsystem_sysname(udev, "iscsi_host", host_name); if (hostdev) { value = udev_device_get_sysattr_value(hostdev, "ipaddress"); if (value) { strncpy(ip_address, value, SLOT_NAME_SIZE); udev_device_unref(hostdev); return 0; } else udev_device_unref(hostdev); } return 1; } int sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen) { struct udev_device *parent = pp->udev; char value[16], *eptr; unsigned long preferred; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "scsi", 4)) break; parent = udev_device_get_parent(parent); } if (!parent) return -1; if (!sysfs_attr_get_value_ok(parent, "access_state", buff, buflen)) return -1; if (!sysfs_attr_get_value_ok(parent, "preferred_path", value, sizeof(value))) return 0; preferred = strtoul(value, &eptr, 0); if (value == eptr || preferred == ULONG_MAX) { /* Parse error, ignore */ return 0; } return !!preferred; } static int sysfs_set_eh_deadline(struct path *pp) { struct udev_device *hostdev; char host_name[HOST_NAME_LEN], value[16]; int ret, len; if (pp->eh_deadline == EH_DEADLINE_UNSET) return 0; sprintf(host_name, "host%d", pp->sg_id.host_no); hostdev = udev_device_new_from_subsystem_sysname(udev, "scsi_host", host_name); if (!hostdev) return 1; if (pp->eh_deadline == EH_DEADLINE_OFF) len = sprintf(value, "off"); else if (pp->eh_deadline == EH_DEADLINE_ZERO) len = sprintf(value, "0"); else len = sprintf(value, "%d", pp->eh_deadline); ret = sysfs_attr_set_value(hostdev, "eh_deadline", value, len); /* * not all scsi drivers support setting eh_deadline, so failing * is totally reasonable */ if (ret != len) log_sysfs_attr_set_value(3, ret, "%s: failed to set eh_deadline to %s", udev_device_get_sysname(hostdev), value); udev_device_unref(hostdev); return (ret <= 0); } static int sysfs_set_max_retries(struct config *conf, struct path *pp) { struct udev_device *parent; char value[16]; STRBUF_ON_STACK(buf); int ret, len; if (conf->max_retries == MAX_RETRIES_UNSET) return 0; if (!pp->udev || pp->sg_id.host_no < 0) return 1; len = sprintf(value, "%d", (conf->max_retries == MAX_RETRIES_OFF)? -1 : (conf->max_retries == MAX_RETRIES_ZERO)? 0 : conf->max_retries); parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_device"); if (!parent) return 1; if (print_strbuf(&buf, "scsi_disk/%i:%i:%i:%" PRIu64 "/max_retries", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun) < 0) return 1; ret = sysfs_attr_set_value(parent, get_strbuf_str(&buf), value, len); if (len != ret) log_sysfs_attr_set_value(3, ret, "%s/%s: failed to set value to %s", udev_device_get_sysname(parent), get_strbuf_str(&buf), value); return (len != ret); } static void sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) { struct udev_device *rport_dev = NULL; char value[16], *eptr; char rport_id[42]; unsigned int tmo; int ret; if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) return; sprintf(rport_id, "rport-%d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); rport_dev = udev_device_new_from_subsystem_sysname(udev, "fc_remote_ports", rport_id); if (!rport_dev) { condlog(1, "%s: No fc_remote_port device for '%s'", pp->dev, rport_id); return; } condlog(4, "target%d:%d:%d -> %s", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, rport_id); /* * read the current dev_loss_tmo value from sysfs */ ret = sysfs_attr_get_value(rport_dev, "dev_loss_tmo", value, sizeof(value)); if (!sysfs_attr_value_ok(ret, sizeof(value))) { condlog(0, "%s: failed to read dev_loss_tmo value, " "error %d", rport_id, -ret); goto out; } tmo = strtoul(value, &eptr, 0); if (value == eptr) { condlog(0, "%s: Cannot parse dev_loss_tmo " "attribute '%s'", rport_id, value); goto out; } /* * This is tricky. * dev_loss_tmo will be limited to 600 if fast_io_fail * is _not_ set. * fast_io_fail will be limited by the current dev_loss_tmo * setting. * So to get everything right we first need to increase * dev_loss_tmo to the fast_io_fail setting (if present), * then set fast_io_fail, and _then_ set dev_loss_tmo * to the correct value. */ if (pp->fast_io_fail != MP_FAST_IO_FAIL_UNSET && pp->fast_io_fail != MP_FAST_IO_FAIL_ZERO && pp->fast_io_fail != MP_FAST_IO_FAIL_OFF) { /* Check if we need to temporarily increase dev_loss_tmo */ if ((unsigned int)pp->fast_io_fail >= tmo) { ssize_t len; /* Increase dev_loss_tmo temporarily */ snprintf(value, sizeof(value), "%u", (unsigned int)pp->fast_io_fail + 1); len = strlen(value); ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo", value, len); if (ret != len) { if (ret == -EBUSY) condlog(3, "%s: rport blocked", rport_id); else log_sysfs_attr_set_value(0, ret, "%s: failed to set dev_loss_tmo to %s", rport_id, value); goto out; } } } else if (pp->dev_loss > DEFAULT_DEV_LOSS_TMO && mpp->no_path_retry != NO_PATH_RETRY_QUEUE) { condlog(2, "%s: limiting dev_loss_tmo to %d, since " "fast_io_fail is not set", rport_id, DEFAULT_DEV_LOSS_TMO); pp->dev_loss = DEFAULT_DEV_LOSS_TMO; } if (pp->fast_io_fail != MP_FAST_IO_FAIL_UNSET) { ssize_t len; if (pp->fast_io_fail == MP_FAST_IO_FAIL_OFF) sprintf(value, "off"); else if (pp->fast_io_fail == MP_FAST_IO_FAIL_ZERO) sprintf(value, "0"); else snprintf(value, 16, "%u", pp->fast_io_fail); len = strlen(value); ret = sysfs_attr_set_value(rport_dev, "fast_io_fail_tmo", value, len); if (ret != len) { if (ret == -EBUSY) condlog(3, "%s: rport blocked", rport_id); else log_sysfs_attr_set_value(0, ret, "%s: failed to set fast_io_fail_tmo to %s", rport_id, value); } } if (pp->dev_loss != DEV_LOSS_TMO_UNSET) { ssize_t len; snprintf(value, 16, "%u", pp->dev_loss); len = strlen(value); ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo", value, len); if (ret != len) { if (ret == -EBUSY) condlog(3, "%s: rport blocked", rport_id); else log_sysfs_attr_set_value(0, ret, "%s: failed to set dev_loss_tmo to %s", rport_id, value); } } out: udev_device_unref(rport_dev); } static void sysfs_set_session_tmo(struct path *pp) { struct udev_device *session_dev = NULL; char session_id[64]; char value[11]; if (pp->dev_loss != DEV_LOSS_TMO_UNSET) condlog(3, "%s: ignoring dev_loss_tmo on iSCSI", pp->dev); if (pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) return; sprintf(session_id, "session%d", pp->sg_id.transport_id); session_dev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", session_id); if (!session_dev) { condlog(1, "%s: No iscsi session for '%s'", pp->dev, session_id); return; } condlog(4, "target%d:%d:%d -> %s", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, session_id); if (pp->fast_io_fail != MP_FAST_IO_FAIL_UNSET) { if (pp->fast_io_fail == MP_FAST_IO_FAIL_OFF) { condlog(3, "%s: can't switch off fast_io_fail_tmo " "on iSCSI", pp->dev); } else if (pp->fast_io_fail == MP_FAST_IO_FAIL_ZERO) { condlog(3, "%s: can't set fast_io_fail_tmo to '0'" "on iSCSI", pp->dev); } else { ssize_t len, ret; snprintf(value, 11, "%u", pp->fast_io_fail); len = strlen(value); ret = sysfs_attr_set_value(session_dev, "recovery_tmo", value, len); if (ret != len) log_sysfs_attr_set_value(3, ret, "%s: Failed to set recovery_tmo to %s", pp->dev, value); } } udev_device_unref(session_dev); return; } static void sysfs_set_nexus_loss_tmo(struct path *pp) { struct udev_device *parent, *sas_dev = NULL; const char *end_dev_id = NULL; char value[11]; static const char ed_str[] = "end_device-"; if (!pp->udev || pp->dev_loss == DEV_LOSS_TMO_UNSET) return; for (parent = udev_device_get_parent(pp->udev); parent; parent = udev_device_get_parent(parent)) { const char *ed = udev_device_get_sysname(parent); if (ed && !strncmp(ed, ed_str, sizeof(ed_str) - 1)) { end_dev_id = ed; break; } } if (!end_dev_id) { condlog(1, "%s: No SAS end device", pp->dev); return; } sas_dev = udev_device_new_from_subsystem_sysname(udev, "sas_end_device", end_dev_id); if (!sas_dev) { condlog(1, "%s: No SAS end device for '%s'", pp->dev, end_dev_id); return; } condlog(4, "target%d:%d:%d -> %s", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, end_dev_id); if (pp->dev_loss != DEV_LOSS_TMO_UNSET) { ssize_t len, ret; snprintf(value, 11, "%u", pp->dev_loss); len = strlen(value); ret = sysfs_attr_set_value(sas_dev, "I_T_nexus_loss_timeout", value, len); if (ret != len) log_sysfs_attr_set_value(3, ret, "%s: failed to update I_T Nexus loss timeout", pp->dev); } udev_device_unref(sas_dev); return; } static void scsi_tmo_error_msg(struct path *pp) { STATIC_BITFIELD(bf, LAST_BUS_PROTOCOL_ID + 1); STRBUF_ON_STACK(proto_buf); unsigned int proto_id = bus_protocol_id(pp); snprint_path_protocol(&proto_buf, pp); condlog(2, "%s: setting scsi timeouts is unsupported for protocol %s", pp->dev, get_strbuf_str(&proto_buf)); set_bit_in_bitfield(proto_id, bf); } int sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) { struct path *pp; int i; unsigned int min_dev_loss = 0; bool warn_dev_loss = false; bool warn_fast_io_fail = false; if (mpp->action == ACT_DRY_RUN || mpp->action == ACT_REJECT) return 0; if (mpp->no_path_retry > 0) { uint64_t no_path_retry_tmo = (uint64_t)mpp->no_path_retry * conf->checkint; /* pad no_path_retry_tmo by one standard check interval * plus one second per minute to account for timing * issues with the rechecks */ no_path_retry_tmo += no_path_retry_tmo / 60 + DEFAULT_CHECKINT; if (no_path_retry_tmo > MAX_DEV_LOSS_TMO) min_dev_loss = MAX_DEV_LOSS_TMO; else min_dev_loss = no_path_retry_tmo; } else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE) min_dev_loss = MAX_DEV_LOSS_TMO; vector_foreach_slot(mpp->paths, pp, i) { select_fast_io_fail(conf, pp); select_dev_loss(conf, pp); select_eh_deadline(conf, pp); if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET && pp->eh_deadline == EH_DEADLINE_UNSET && conf->max_retries == MAX_RETRIES_UNSET) continue; if (pp->bus != SYSFS_BUS_SCSI) { scsi_tmo_error_msg(pp); continue; } sysfs_set_eh_deadline(pp); sysfs_set_max_retries(conf, pp); if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) continue; if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP && pp->sg_id.proto_id != SCSI_PROTOCOL_ISCSI && pp->sg_id.proto_id != SCSI_PROTOCOL_SAS) { scsi_tmo_error_msg(pp); continue; } if (pp->dev_loss == DEV_LOSS_TMO_UNSET && min_dev_loss != 0) pp->dev_loss = min_dev_loss; else if (pp->dev_loss < min_dev_loss) { pp->dev_loss = min_dev_loss; warn_dev_loss = true; } if (pp->dev_loss != DEV_LOSS_TMO_UNSET && pp->fast_io_fail > 0 && (unsigned int)pp->fast_io_fail >= pp->dev_loss) { warn_fast_io_fail = true; pp->fast_io_fail = MP_FAST_IO_FAIL_OFF; } switch (pp->sg_id.proto_id) { case SCSI_PROTOCOL_FCP: sysfs_set_rport_tmo(mpp, pp); break; case SCSI_PROTOCOL_ISCSI: sysfs_set_session_tmo(pp); break; case SCSI_PROTOCOL_SAS: sysfs_set_nexus_loss_tmo(pp); break; default: break; } } if (warn_dev_loss) condlog(2, "%s: Raising dev_loss_tmo to %u because of no_path_retry setting", mpp->alias, min_dev_loss); if (warn_fast_io_fail) condlog(3, "%s: turning off fast_io_fail (not smaller than dev_loss_tmo)", mpp->alias); return 0; } int do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, void *resp, int mx_resp_len) { unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY_CMD, 0, 0, 0, 0, 0 }; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; if (cmddt) inqCmdBlk[1] |= 2; if (evpd) inqCmdBlk[1] |= 1; inqCmdBlk[2] = (unsigned char) pg_op; inqCmdBlk[3] = (unsigned char)((mx_resp_len >> 8) & 0xff); inqCmdBlk[4] = (unsigned char) (mx_resp_len & 0xff); memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = mx_resp_len; io_hdr.dxferp = resp; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_b; io_hdr.timeout = DEF_TIMEOUT * 1000; if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) return -1; /* treat SG_ERR here to get rid of sg_err.[ch] */ io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) return 0; if ((SCSI_CHECK_CONDITION == io_hdr.status) || (SCSI_COMMAND_TERMINATED == io_hdr.status) || (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { int sense_key; unsigned char * sense_buffer = io_hdr.sbp; if (sense_buffer[0] & 0x2) sense_key = sense_buffer[1] & 0xf; else sense_key = sense_buffer[2] & 0xf; if(RECOVERED_ERROR == sense_key) return 0; } } return -1; } static int get_serial (char * str, int maxlen, int fd) { int len = 0; char buff[MX_ALLOC_LEN + 1] = {0}; if (fd < 0) return 1; if (0 == do_inq(fd, 0, 1, 0x80, buff, MX_ALLOC_LEN)) { len = buff[3]; if (len >= maxlen) return 1; if (len > 0) { memcpy(str, buff + 4, len); str[len] = '\0'; } return 0; } return 1; } /* * Side effect: sets pp->tpgs if it could be determined. * If ALUA calls fail because paths are unreachable, pp->tpgs remains unchanged. */ static void detect_alua(struct path * pp) { int ret; int tpgs; if (pp->bus != SYSFS_BUS_SCSI) { pp->tpgs = TPGS_NONE; return; } tpgs = get_target_port_group_support(pp); if (tpgs == -RTPG_INQUIRY_FAILED) return; else if (tpgs <= 0) { pp->tpgs = TPGS_NONE; return; } if (pp->fd == -1 || pp->sysfs_state == PATH_DOWN) return; ret = get_target_port_group(pp); if (ret < 0 || get_asymmetric_access_state(pp, ret) < 0) { if (ret == -RTPG_INQUIRY_FAILED) return; path_sysfs_state(pp); if (pp->sysfs_state != PATH_UP) return; pp->tpgs = TPGS_NONE; return; } pp->tpgs = tpgs; pp->tpg_id = ret; } int path_get_tpgs(struct path *pp) { if (pp->tpgs == TPGS_UNDEF) detect_alua(pp); return pp->tpgs; } #define DEFAULT_SGIO_LEN 254 /* Query VPD page @pg. Returns number of INQUIRY bytes upon success and -1 upon failure. */ static int sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg) { int len = DEFAULT_SGIO_LEN; int rlen; if (fd < 0) { errno = EBADF; return -1; } retry: if (0 == do_inq(fd, 0, 1, pg, buff, len)) { rlen = get_unaligned_be16(&buff[2]) + 4; if (rlen <= len || len >= maxlen) return rlen; len = (rlen < maxlen)? rlen : maxlen; goto retry; } return -1; } static int get_geometry(struct path *pp) { if (pp->fd < 0) return 1; if (ioctl(pp->fd, HDIO_GETGEO, &pp->geom)) { condlog(2, "%s: HDIO_GETGEO failed with %d", pp->dev, errno); memset(&pp->geom, 0, sizeof(pp->geom)); return 1; } condlog(3, "%s: %u cyl, %u heads, %u sectors/track, start at %lu", pp->dev, pp->geom.cylinders, pp->geom.heads, pp->geom.sectors, pp->geom.start); return 0; } static int parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len) { size_t len = get_unaligned_be16(&in[2]); if (out_len == 0) return 0; if (len > WWID_SIZE) len = WWID_SIZE; /* * Strip leading and trailing whitespace */ while (len > 0 && in[len + 3] == ' ') --len; while (len > 0 && in[4] == ' ') { ++in; --len; } if (len >= out_len) { condlog(2, "vpd pg80 overflow, %zu/%zu bytes required", len + 1, out_len); len = out_len - 1; } if (len > 0) { memcpy(out, in + 4, len); out[len] = '\0'; } return len; } static int parse_vpd_pg83(const unsigned char *in, size_t in_len, char *out, size_t out_len) { const unsigned char *d; const unsigned char *vpd = NULL; size_t len, vpd_len, i; int vpd_type, prio = -1; int err = -ENODATA; STRBUF_ON_STACK(buf); /* Need space at least for one digit */ if (out_len <= 1) return 0; d = in + 4; while (d <= in + in_len - 4) { bool invalid = false; int new_prio = -1; /* Select 'association: LUN' */ if ((d[1] & 0x30) == 0x30) { invalid = true; goto next_designator; } else if ((d[1] & 0x30) != 0x00) goto next_designator; switch (d[1] & 0xf) { unsigned char good_len; case 0x3: /* NAA: Prio 5 */ switch (d[4] >> 4) { case 6: /* IEEE Registered Extended: Prio 8 */ new_prio = 8; good_len = 16; break; case 5: /* IEEE Registered: Prio 7 */ new_prio = 7; good_len = 8; break; case 2: /* IEEE Extended: Prio 6 */ new_prio = 6; good_len = 8; break; case 3: /* IEEE Locally assigned: Prio 1 */ new_prio = 1; good_len = 8; break; default: /* Default: no priority */ good_len = 0xff; break; } invalid = good_len == 0xff || good_len != d[3]; break; case 0x2: /* EUI-64: Prio 4 */ invalid = (d[3] != 8 && d[3] != 12 && d[3] != 16); new_prio = 4; break; case 0x8: /* SCSI Name: Prio 3 */ invalid = (d[3] < 4 || (memcmp(d + 4, "eui.", 4) && memcmp(d + 4, "naa.", 4) && memcmp(d + 4, "iqn.", 4))); new_prio = 3; break; case 0x1: /* T-10 Vendor ID: Prio 2 */ invalid = (d[3] < 8); new_prio = 2; break; case 0x6: /* Logical Unit Group */ invalid = (d[3] != 4); break; case 0x7: /* MD5 logical unit designator */ invalid = (d[3] != 16); break; case 0x0: /* Vendor Specific */ break; case 0xa: condlog(2, "%s: UUID identifiers not yet supported", __func__); break; default: invalid = true; break; } next_designator: if (d + d[3] + 4 - in > (ssize_t)in_len) { condlog(2, "%s: device descriptor length overflow: %zd > %zu", __func__, d + d[3] + 4 - in, in_len); err = -EOVERFLOW; break; } else if (invalid) { condlog(2, "%s: invalid device designator at offset %zd: %02x%02x%02x%02x", __func__, d - in, d[0], d[1], d[2], d[3]); /* * We checked above that the next offset is within limits. * Proceed, fingers crossed. */ err = -EINVAL; } else if (new_prio > prio) { vpd = d; prio = new_prio; } d += d[3] + 4; } if (prio <= 0) return err; if (d != in + in_len) /* Should this be fatal? (overflow covered above) */ condlog(2, "%s: warning: last descriptor end %zd != VPD length %zu", __func__, d - in, in_len); len = 0; vpd_type = vpd[1] & 0xf; vpd_len = vpd[3]; vpd += 4; /* untaint vpd_len for coverity */ if (vpd_len > WWID_SIZE) { condlog(1, "%s: suspicious designator length %zu truncated to %u", __func__, vpd_len, WWID_SIZE); vpd_len = WWID_SIZE; } if (vpd_type == 0x2 || vpd_type == 0x3) { size_t i; if ((err = print_strbuf(&buf, "%d", vpd_type)) < 0) return err; for (i = 0; i < vpd_len; i++) if ((err = print_strbuf(&buf, "%02x", vpd[i])) < 0) return err; } else if (vpd_type == 0x8) { char type; if (!memcmp("eui.", vpd, 4)) type = '2'; else if (!memcmp("naa.", vpd, 4)) type = '3'; else type = '8'; if ((err = fill_strbuf(&buf, type, 1)) < 0) return err; vpd += 4; len = vpd_len - 4; if ((err = append_strbuf_str__(&buf, (const char *)vpd, len)) < 0) return err; /* The input is 0-padded, make sure the length is correct */ truncate_strbuf(&buf, strlen(get_strbuf_str(&buf))); len = get_strbuf_len(&buf); if (type != '8') { char *buffer = get_strbuf_buf__(&buf); for (i = 0; i < len; ++i) buffer[i] = tolower(buffer[i]); } } else if (vpd_type == 0x1) { const unsigned char *p; size_t p_len; if ((err = fill_strbuf(&buf, '1', 1)) < 0) return err; while (vpd && (p = memchr(vpd, ' ', vpd_len))) { p_len = p - vpd; if ((err = append_strbuf_str__(&buf, (const char *)vpd, p_len)) < 0) return err; vpd = p; vpd_len -= p_len; while (vpd && vpd_len > 0 && *vpd == ' ') { vpd++; vpd_len --; } if (vpd_len > 0 && (err = fill_strbuf(&buf, '_', 1)) < 0) return err; } if (vpd_len > 0) { if ((err = append_strbuf_str__(&buf, (const char *)vpd, vpd_len)) < 0) return err; } } len = get_strbuf_len(&buf); if (len >= out_len) { condlog(1, "%s: WWID overflow, type %d, %zu/%zu bytes required", __func__, vpd_type, len, out_len); if (vpd_type == 2 || vpd_type == 3) /* designator must have an even number of characters */ len = 2 * (out_len / 2) - 1; else len = out_len - 1; } strlcpy(out, get_strbuf_str(&buf), len + 1); return len; } static int parse_vpd_c0_hp3par(const unsigned char *in, size_t in_len, char *out, size_t out_len) { size_t len; memset(out, 0x0, out_len); if (in_len <= 4 || (in[4] > 3 && in_len < 44)) { condlog(3, "HP/3PAR vendor specific VPD page length too short: %zu", in_len); return -EINVAL; } if (in[4] <= 3) /* revision must be > 3 to have Volume Name */ return -ENODATA; len = get_unaligned_be32(&in[40]); if (len > out_len || len + 44 > in_len) { condlog(3, "HP/3PAR vendor specific Volume name too long: %zu", len); return -EINVAL; } memcpy(out, &in[44], len); out[out_len - 1] = '\0'; return len; } static int get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen) { int len; ssize_t buff_len; unsigned char buff[VPD_BUFLEN]; memset(buff, 0x0, VPD_BUFLEN); buff_len = sysfs_get_vpd(parent, pg, buff, VPD_BUFLEN); if (buff_len < 0) { condlog(3, "failed to read sysfs vpd pg%02x: %s", pg, strerror(-buff_len)); return buff_len; } if (buff[1] != pg) { condlog(3, "vpd pg%02x error, invalid vpd page %02x", pg, buff[1]); return -ENODATA; } buff_len = get_unaligned_be16(&buff[2]) + 4; if (buff_len > VPD_BUFLEN) { condlog(3, "vpd pg%02x page truncated", pg); buff_len = VPD_BUFLEN; } if (pg == 0x80) len = parse_vpd_pg80(buff, str, maxlen); else if (pg == 0x83) len = parse_vpd_pg83(buff, buff_len, str, maxlen); else len = -ENOSYS; return len; } static int fetch_vpd_page(int fd, int pg, unsigned char *buff, int maxlen) { int buff_len; memset(buff, 0x0, maxlen); if (sgio_get_vpd(buff, maxlen, fd, pg) < 0) { int lvl = pg == 0x80 || pg == 0x83 ? 3 : 4; condlog(lvl, "failed to issue vpd inquiry for pg%02x", pg); return -errno; } if (buff[1] != pg) { condlog(3, "vpd pg%02x error, invalid vpd page %02x", pg, buff[1]); return -ENODATA; } buff_len = get_unaligned_be16(&buff[2]) + 4; if (buff_len > maxlen) { condlog(3, "vpd pg%02x page truncated", pg); buff_len = maxlen; } return buff_len; } /* based on sg_inq.c from sg3_utils */ bool is_vpd_page_supported(int fd, int pg) { int i, len; unsigned char buff[VPD_BUFLEN]; len = fetch_vpd_page(fd, 0x00, buff, sizeof(buff)); if (len < 0) return false; for (i = 4; i < len; ++i) if (buff[i] == pg) return true; return false; } int get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen) { int len, buff_len; unsigned char buff[VPD_BUFLEN]; buff_len = fetch_vpd_page(fd, pg, buff, sizeof(buff)); if (buff_len < 0) return buff_len; if (pg == 0x80) len = parse_vpd_pg80(buff, str, maxlen); else if (pg == 0x83) len = parse_vpd_pg83(buff, buff_len, str, maxlen); else if (pg == 0xc9 && maxlen >= 8) { if (buff_len < 8) len = -ENODATA; else { len = (buff_len <= maxlen)? buff_len : maxlen; memcpy (str, buff, len); } } else if (pg == 0xc0 && vend_id == VPD_VP_HP3PAR) len = parse_vpd_c0_hp3par(buff, buff_len, str, maxlen); else len = -ENOSYS; return len; } static int scsi_sysfs_pathinfo (struct path *pp, const struct vector_s *hwtable) { struct udev_device *parent; const char *attr_path = NULL; static const char unknown[] = "UNKNOWN"; parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "scsi", 4)) { attr_path = udev_device_get_sysname(parent); if (!attr_path) break; if (sscanf(attr_path, "%i:%i:%i:%" SCNu64, &pp->sg_id.host_no, &pp->sg_id.channel, &pp->sg_id.scsi_id, &pp->sg_id.lun) == 4) break; } parent = udev_device_get_parent(parent); } if (!attr_path || pp->sg_id.host_no == -1) return PATHINFO_FAILED; if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0) { condlog(1, "%s: broken device without vendor ID", pp->dev); strlcpy(pp->vendor_id, unknown, SCSI_VENDOR_SIZE); } condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id); if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0) { condlog(1, "%s: broken device without product ID", pp->dev); strlcpy(pp->product_id, unknown, PATH_PRODUCT_SIZE); } condlog(3, "%s: product = %s", pp->dev, pp->product_id); if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) < 0) { condlog(2, "%s: broken device without revision", pp->dev); strlcpy(pp->rev, unknown, PATH_REV_SIZE); } condlog(3, "%s: rev = %s", pp->dev, pp->rev); /* * set the hwe configlet pointer */ find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe); /* * host / bus / target / lun */ condlog(3, "%s: h:b:t:l = %i:%i:%i:%" PRIu64, pp->dev, pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun); /* * target node name */ if(sysfs_get_tgt_nodename(pp, pp->tgt_node_name)) return PATHINFO_FAILED; condlog(3, "%s: tgt_node_name = %s", pp->dev, pp->tgt_node_name); return PATHINFO_OK; } static int nvme_sysfs_pathinfo (struct path *pp, const struct vector_s *hwtable) { struct udev_device *parent; const char *attr_path = NULL; const char *attr; int i; if (pp->udev) attr_path = udev_device_get_sysname(pp->udev); if (!attr_path) return PATHINFO_FAILED; if (sscanf(attr_path, "nvme%dn%d", &pp->sg_id.host_no, &pp->sg_id.scsi_id) != 2) return PATHINFO_FAILED; parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "nvme", NULL); if (!parent) return PATHINFO_SKIPPED; attr = udev_device_get_sysattr_value(pp->udev, "nsid"); pp->sg_id.lun = attr ? atoi(attr) : 0; attr = udev_device_get_sysattr_value(parent, "cntlid"); pp->sg_id.channel = attr ? atoi(attr) : 0; attr = udev_device_get_sysattr_value(parent, "transport"); if (attr) { for (i = 0; i < NVME_PROTOCOL_UNSPEC; i++){ if (protocol_name[SYSFS_BUS_NVME + i] && !strcmp(attr, protocol_name[SYSFS_BUS_NVME + i] + 5)) { pp->sg_id.proto_id = i; break; } } } snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME"); snprintf(pp->product_id, PATH_PRODUCT_SIZE, "%s", udev_device_get_sysattr_value(parent, "model")); snprintf(pp->serial, SERIAL_SIZE, "%s", udev_device_get_sysattr_value(parent, "serial")); snprintf(pp->rev, PATH_REV_SIZE, "%s", udev_device_get_sysattr_value(parent, "firmware_rev")); condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id); condlog(3, "%s: product = %s", pp->dev, pp->product_id); condlog(3, "%s: serial = %s", pp->dev, pp->serial); condlog(3, "%s: rev = %s", pp->dev, pp->rev); find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe); return PATHINFO_OK; } static int ccw_sysfs_pathinfo (struct path *pp, const struct vector_s *hwtable) { struct udev_device *parent; char attr_buff[NAME_SIZE]; const char *attr_path; parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "ccw", 3)) break; parent = udev_device_get_parent(parent); } if (!parent) return PATHINFO_FAILED; sprintf(pp->vendor_id, "IBM"); condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id); if (sysfs_get_devtype(parent, attr_buff, FILE_NAME_SIZE) <= 0) return PATHINFO_FAILED; if (!strncmp(attr_buff, "3370", 4)) { sprintf(pp->product_id,"S/390 DASD FBA"); } else if (!strncmp(attr_buff, "9336", 4)) { sprintf(pp->product_id,"S/390 DASD FBA"); } else { sprintf(pp->product_id,"S/390 DASD ECKD"); } condlog(3, "%s: product = %s", pp->dev, pp->product_id); /* * set the hwe configlet pointer */ find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe); /* * host / bus / target / lun */ attr_path = udev_device_get_sysname(parent); if (!attr_path) return PATHINFO_FAILED; pp->sg_id.lun = 0; if (sscanf(attr_path, "%i.%i.%x", &pp->sg_id.host_no, &pp->sg_id.channel, &pp->sg_id.scsi_id) == 3) { condlog(3, "%s: h:b:t:l = %i:%i:%i:%" PRIu64, pp->dev, pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun); } return PATHINFO_OK; } static int cciss_sysfs_pathinfo (struct path *pp, const struct vector_s *hwtable) { const char * attr_path = NULL; struct udev_device *parent; parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "cciss", 5)) { attr_path = udev_device_get_sysname(parent); if (!attr_path) break; if (sscanf(attr_path, "c%id%i", &pp->sg_id.host_no, &pp->sg_id.scsi_id) == 2) break; } parent = udev_device_get_parent(parent); } if (!attr_path || pp->sg_id.host_no == -1) return PATHINFO_FAILED; if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0) return PATHINFO_FAILED; condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id); if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0) return PATHINFO_FAILED; condlog(3, "%s: product = %s", pp->dev, pp->product_id); if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) <= 0) return PATHINFO_FAILED; condlog(3, "%s: rev = %s", pp->dev, pp->rev); /* * set the hwe configlet pointer */ find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe); /* * host / bus / target / lun */ pp->sg_id.lun = 0; pp->sg_id.channel = 0; condlog(3, "%s: h:b:t:l = %i:%i:%i:%" PRIu64, pp->dev, pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun); return PATHINFO_OK; } static int common_sysfs_pathinfo (struct path * pp) { dev_t devt; if (!pp) return PATHINFO_FAILED; if (!pp->udev) { condlog(4, "%s: udev not initialised", pp->dev); return PATHINFO_FAILED; } devt = udev_device_get_devnum(pp->udev); if (major(devt) == 0 && minor(devt) == 0) return PATHINFO_FAILED; snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), minor(devt)); condlog(4, "%s: dev_t = %s", pp->dev, pp->dev_t); if (sysfs_get_size(pp, &pp->size)) return PATHINFO_FAILED; condlog(3, "%s: size = %llu", pp->dev, pp->size); return PATHINFO_OK; } int path_sysfs_state(struct path * pp) { struct udev_device * parent; char buff[SCSI_STATE_SIZE]; int err; const char *subsys_type; if (pp->bus == SYSFS_BUS_SCSI) { subsys_type = "scsi"; } else if (pp->bus == SYSFS_BUS_NVME) { subsys_type = "nvme"; } else { pp->sysfs_state = PATH_UP; goto out; } parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, subsys_type, 4)) break; parent = udev_device_get_parent(parent); } if (!parent) { condlog(1, "%s: failed to get sysfs information", pp->dev); pp->sysfs_state = PATH_REMOVED; goto out; } memset(buff, 0x0, SCSI_STATE_SIZE); err = sysfs_attr_get_value(parent, "state", buff, sizeof(buff)); if (!sysfs_attr_value_ok(err, sizeof(buff))) { if (err == -ENXIO) pp->sysfs_state = PATH_REMOVED; else pp->sysfs_state = PATH_DOWN; goto out; } condlog(4, "%s: path state = %s", pp->dev, buff); if (pp->bus == SYSFS_BUS_SCSI) { if (!strncmp(buff, "offline", 7)) { pp->sysfs_state = PATH_DOWN; goto out; } else if (!strncmp(buff, "blocked", 7) || !strncmp(buff, "quiesce", 7)) { pp->sysfs_state = PATH_PENDING; goto out; } else if (!strncmp(buff, "running", 7)) { pp->sysfs_state = PATH_UP; goto out; } } else if (pp->bus == SYSFS_BUS_NVME) { if (!strncmp(buff, "dead", 4)) { pp->sysfs_state = PATH_DOWN; goto out; } else if (!strncmp(buff, "new", 3) || !strncmp(buff, "deleting", 8)) { pp->sysfs_state = PATH_PENDING; goto out; } else if (!strncmp(buff, "live", 4)) { pp->sysfs_state = PATH_UP; goto out; } } pp->sysfs_state = PATH_DOWN; out: return pp->sysfs_state; } static int sysfs_pathinfo(struct path *pp, const struct vector_s *hwtable) { int r = common_sysfs_pathinfo(pp); if (r != PATHINFO_OK) return r; pp->bus = SYSFS_BUS_UNDEF; if (!strncmp(pp->dev,"cciss",5)) pp->bus = SYSFS_BUS_CCISS; if (!strncmp(pp->dev,"dasd", 4)) pp->bus = SYSFS_BUS_CCW; if (!strncmp(pp->dev,"sd", 2)) { pp->bus = SYSFS_BUS_SCSI; pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; } if (!strncmp(pp->dev,"nvme", 4)) { pp->bus = SYSFS_BUS_NVME; pp->sg_id.proto_id = NVME_PROTOCOL_UNSPEC; } switch (pp->bus) { case SYSFS_BUS_SCSI: return scsi_sysfs_pathinfo(pp, hwtable); case SYSFS_BUS_CCW: return ccw_sysfs_pathinfo(pp, hwtable); case SYSFS_BUS_CCISS: return cciss_sysfs_pathinfo(pp, hwtable); case SYSFS_BUS_NVME: return nvme_sysfs_pathinfo(pp, hwtable); case SYSFS_BUS_UNDEF: default: return PATHINFO_OK; } } static void scsi_ioctl_pathinfo (struct path * pp, int mask) { struct udev_device *parent; const char *attr_path = NULL; int vpd_id; if (!(mask & DI_SERIAL)) return; select_vpd_vendor_id(pp); vpd_id = pp->vpd_vendor_id; if (vpd_id != VPD_VP_UNDEF) { char vpd_data[VPD_DATA_SIZE] = {0}; if (get_vpd_sgio(pp->fd, vpd_vendor_pages[vpd_id].pg, vpd_id, vpd_data, sizeof(vpd_data)) < 0) condlog(3, "%s: failed to get extra vpd data", pp->dev); else { vpd_data[VPD_DATA_SIZE - 1] = '\0'; if (pp->vpd_data) free(pp->vpd_data); pp->vpd_data = strdup(vpd_data); if (!pp->vpd_data) condlog(0, "%s: failed to allocate space for vpd data", pp->dev); } } parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "scsi", 4)) { attr_path = udev_device_get_sysname(parent); if (!attr_path) break; if (sscanf(attr_path, "%i:%i:%i:%" SCNu64, &pp->sg_id.host_no, &pp->sg_id.channel, &pp->sg_id.scsi_id, &pp->sg_id.lun) == 4) break; } parent = udev_device_get_parent(parent); } if (!attr_path || pp->sg_id.host_no == -1) return; if (get_vpd_sysfs(parent, 0x80, pp->serial, SERIAL_SIZE) <= 0) { if (get_serial(pp->serial, SERIAL_SIZE, pp->fd)) { condlog(3, "%s: fail to get serial", pp->dev); return; } } condlog(3, "%s: serial = %s", pp->dev, pp->serial); return; } static void cciss_ioctl_pathinfo(struct path *pp) { get_serial(pp->serial, SERIAL_SIZE, pp->fd); condlog(3, "%s: serial = %s", pp->dev, pp->serial); } int start_checker (struct path * pp, struct config *conf, int daemon, int oldstate) { struct checker * c = &pp->checker; if (!checker_selected(c)) { if (daemon) { if (pathinfo(pp, conf, DI_SYSFS) != PATHINFO_OK) { condlog(3, "%s: couldn't get sysfs pathinfo", pp->dev); return -1; } } select_detect_checker(conf, pp); select_checker(conf, pp); if (!checker_selected(c)) { condlog(3, "%s: No checker selected", pp->dev); return -1; } checker_set_fd(c, pp->fd); if (checker_init(c, pp->mpp?&pp->mpp->mpcontext:NULL)) { 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); return 0; } int get_state (struct path * pp) { struct checker * c = &pp->checker; int state, lvl; state = checker_get_state(c); lvl = state == pp->oldstate || state == PATH_PENDING ? 4 : 3; condlog(lvl, "%s: %s state = %s", pp->dev, checker_name(c), checker_state_name(state)); if (state != PATH_UP && state != PATH_GHOST && strlen(checker_message(c))) condlog(lvl, "%s: %s checker%s", pp->dev, checker_name(c), checker_message(c)); if (state != PATH_PENDING) pp->oldstate = state; return state; } static int get_prio (struct path * pp) { struct prio * p; struct config *conf; int old_prio; if (!pp) return 0; p = &pp->prio; if (!prio_selected(p)) { conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); select_detect_prio(conf, pp); select_prio(conf, pp); pthread_cleanup_pop(1); if (!prio_selected(p)) { condlog(3, "%s: no prio selected", pp->dev); pp->priority = PRIO_UNDEF; return 1; } } old_prio = pp->priority; pp->priority = prio_getprio(p, pp); if (pp->priority < 0) { int state = path_sysfs_state(pp); if (state == PATH_DOWN || state == PATH_PENDING) { pp->priority = old_prio; condlog(3, "%s: %s prio error in state %d, keeping prio = %d", pp->dev, prio_name(p), state, pp->priority); } else { condlog(3, "%s: %s prio error in state %d", pp->dev, prio_name(p), state); pp->priority = PRIO_UNDEF; } return 1; } condlog((old_prio == pp->priority ? 4 : 3), "%s: %s prio = %u", pp->dev, prio_name(p), pp->priority); return 0; } /* * Mangle string of length *len starting at start * by removing character sequence "00" (hex for a 0 byte), * starting at end, backwards. * Changes the value of *len if characters were removed. * Returns a pointer to the position where "end" was moved to. */ static char *skip_zeroes_backward(char* start, size_t *len, char *end) { char *p = end; while (p >= start + 2 && *(p - 1) == '0' && *(p - 2) == '0') p -= 2; if (p == end) return p; memmove(p, end, start + *len + 1 - end); *len -= end - p; return p; } /* * Fix for NVME wwids looking like this: * nvme.0000-3163653363666438366239656630386200-4c696e75780000000000000000000000000000000000000000000000000000000000000000000000-00000002 * which are encountered in some combinations of Linux NVME host and target. * The '00' are hex-encoded 0-bytes which are forbidden in the serial (SN) * and model (MN) fields. Discard them. * If a WWID of the above type is found, sets pp->wwid and returns a value > 0. * Otherwise, returns 0. */ static int fix_broken_nvme_wwid(struct path *pp, const char *value, size_t size) { static const char _nvme[] = "nvme."; size_t len, i; char mangled[256]; char *p; len = strlen(value); if (len >= sizeof(mangled)) return 0; /* Check that value starts with "nvme.%04x-" */ if (memcmp(value, _nvme, sizeof(_nvme) - 1) || value[9] != '-') return 0; for (i = 5; i < 9; i++) if (!isxdigit(value[i])) return 0; memcpy(mangled, value, len + 1); /* search end of "model" part and strip trailing '00' */ p = memrchr(mangled, '-', len); if (p == NULL) return 0; p = skip_zeroes_backward(mangled, &len, p); /* search end of "serial" part */ p = memrchr(mangled, '-', p - mangled); if (p == NULL || memrchr(mangled, '-', p - mangled) != mangled + 9) /* We expect exactly 3 '-' in the value */ return 0; p = skip_zeroes_backward(mangled, &len, p); if (len >= size) return 0; memcpy(pp->wwid, mangled, len + 1); condlog(2, "%s: over-long WWID shortened to %s", pp->dev, pp->wwid); return len; } static int get_udev_uid(struct path * pp, const char *uid_attribute, struct udev_device *udev) { ssize_t len; const char *value; value = udev_device_get_property_value(udev, uid_attribute); if ((!value || strlen(value) == 0) && pp->can_use_env_uid) value = getenv(uid_attribute); if (value && strlen(value)) { len = strlcpy(pp->wwid, value, WWID_SIZE); if (len >= WWID_SIZE) { len = fix_broken_nvme_wwid(pp, value, WWID_SIZE); if (len > 0) return len; condlog(0, "%s: wwid overflow", pp->dev); len = WWID_SIZE; } } else { condlog(3, "%s: no %s attribute", pp->dev, uid_attribute); len = -ENODATA; } return len; } static int get_vpd_uid(struct path * pp) { struct udev_device *parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "scsi", 4)) break; parent = udev_device_get_parent(parent); } if (!parent) return -EINVAL; return get_vpd_sysfs(parent, 0x83, pp->wwid, WWID_SIZE); } /* based on code from s390-tools/dasdinfo/dasdinfo.c */ static ssize_t dasd_get_uid(struct path *pp) { struct udev_device *parent; char value[80]; char *p; int i; parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "ccw", NULL); if (!parent) return -1; if (sysfs_attr_get_value(parent, "uid", value, 80) < 0) return -1; p = value - 1; /* look for the 4th '.' and cut there */ for (i = 0; i < 4; i++) { p = index(p + 1, '.'); if (!p) break; } if (p) *p = '\0'; return strlcpy(pp->wwid, value, WWID_SIZE); } static ssize_t uid_fallback(struct path *pp, int path_state, const char **origin) { ssize_t len = -1; if (pp->bus == SYSFS_BUS_CCW) { len = dasd_get_uid(pp); *origin = "sysfs"; } else if (pp->bus == SYSFS_BUS_SCSI) { len = get_vpd_uid(pp); *origin = "sysfs"; if (len < 0 && path_state == PATH_UP) { condlog(1, "%s: failed to get sysfs uid: %s", pp->dev, strerror(-len)); len = get_vpd_sgio(pp->fd, 0x83, 0, pp->wwid, WWID_SIZE); *origin = "sgio"; } } else if (pp->bus == SYSFS_BUS_NVME) { char value[256]; if (!pp->udev) return -1; len = sysfs_attr_get_value(pp->udev, "wwid", value, sizeof(value)); if (!sysfs_attr_value_ok(len, sizeof(value))) return -1; len = strlcpy(pp->wwid, value, WWID_SIZE); if (len >= WWID_SIZE) { len = fix_broken_nvme_wwid(pp, value, WWID_SIZE); if (len > 0) return len; condlog(0, "%s: wwid overflow", pp->dev); len = WWID_SIZE; } *origin = "sysfs"; } return len; } bool has_uid_fallback(struct path *pp) { /* * Falling back to direct WWID determination is dangerous * if uid_attribute is set to something non-standard. * Allow it only if it's either the default, or if udev * has been disabled by setting 'uid_attribute ""'. */ if (!pp->uid_attribute) return false; return ((pp->bus == SYSFS_BUS_SCSI && (!strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE) || !strcmp(pp->uid_attribute, ""))) || (pp->bus == SYSFS_BUS_NVME && (!strcmp(pp->uid_attribute, DEFAULT_NVME_UID_ATTRIBUTE) || !strcmp(pp->uid_attribute, ""))) || (pp->bus == SYSFS_BUS_CCW && (!strcmp(pp->uid_attribute, DEFAULT_DASD_UID_ATTRIBUTE) || !strcmp(pp->uid_attribute, "")))); } int get_uid (struct path * pp, int path_state, struct udev_device *udev, int allow_fallback) { const char *origin = "unknown"; ssize_t len = 0; struct config *conf; int used_fallback = 0; size_t i; if (!pp->uid_attribute) { conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); select_getuid(conf, pp); select_recheck_wwid(conf, pp); pthread_cleanup_pop(1); } memset(pp->wwid, 0, WWID_SIZE); if (pp->uid_attribute) { /* if the uid_attribute is an empty string skip udev checking */ bool check_uid_attr = udev && *pp->uid_attribute; if (check_uid_attr) { len = get_udev_uid(pp, pp->uid_attribute, udev); origin = "udev"; if (len == 0) condlog(1, "%s: empty udev uid", pp->dev); } if ((!check_uid_attr || (len <= 0 && allow_fallback)) && has_uid_fallback(pp)) { /* if udev wasn't set or we failed in get_udev_uid() * log at a higher priority */ if (!udev || check_uid_attr) used_fallback = 1; len = uid_fallback(pp, path_state, &origin); } } if ( len < 0 ) { condlog(1, "%s: failed to get %s uid: %s", pp->dev, origin, strerror(-len)); memset(pp->wwid, 0x0, WWID_SIZE); return 1; } else { /* Strip any trailing blanks */ for (i = strlen(pp->wwid); i > 0 && pp->wwid[i-1] == ' '; i--); /* no-op */ pp->wwid[i] = '\0'; } condlog((used_fallback)? 1 : 3, "%s: uid = %s (%s)", pp->dev, *pp->wwid == '\0' ? "" : pp->wwid, origin); return 0; } int pathinfo(struct path *pp, struct config *conf, int mask) { int path_state; if (!pp || !conf) return PATHINFO_FAILED; /* Treat removed paths as if they didn't exist */ if (pp->initialized == INIT_REMOVED) return PATHINFO_FAILED; /* * For behavior backward-compatibility with multipathd, * the blacklisting by filter_property|devnode() is not * limited by DI_BLACKLIST and occurs before this debug * message with the mask value. */ if (pp->udev) { const char *hidden = udev_device_get_sysattr_value(pp->udev, "hidden"); if (hidden && !strcmp(hidden, "1")) { condlog(4, "%s: hidden", pp->dev); return PATHINFO_SKIPPED; } if (is_claimed_by_foreign(pp->udev)) return PATHINFO_SKIPPED; /* * uid_attribute is required for filter_property below, * and needs access to pp->hwe. */ if (!(mask & DI_SYSFS) && (mask & DI_BLACKLIST) && !pp->uid_attribute && VECTOR_SIZE(pp->hwe) == 0) mask |= DI_SYSFS; } if (strlen(pp->dev) != 0 && filter_devnode(conf->blist_devnode, conf->elist_devnode, pp->dev) > 0) return PATHINFO_SKIPPED; condlog(4, "%s: mask = 0x%x", pp->dev, mask); /* * Sanity check: we need the device number to * avoid inconsistent information in * find_path_by_dev()/find_path_by_devt() */ if (!strlen(pp->dev_t) && !(mask & DI_SYSFS)) { condlog(1, "%s: empty device number", pp->dev); mask |= DI_SYSFS; } /* * fetch info available in sysfs */ if (mask & DI_SYSFS) { int rc = sysfs_pathinfo(pp, conf->hwtable); if (rc != PATHINFO_OK) return rc; if (pp->bus == SYSFS_BUS_SCSI && pp->sg_id.proto_id == SCSI_PROTOCOL_USB && !conf->allow_usb_devices) { condlog(3, "%s: skip USB device %s", pp->dev, pp->tgt_node_name); return PATHINFO_SKIPPED; } } if (mask & DI_BLACKLIST && mask & DI_SYSFS) { /* uid_attribute is required for filter_property() */ if (pp->udev && !pp->uid_attribute) { select_getuid(conf, pp); select_recheck_wwid(conf, pp); } if (filter_property(conf, pp->udev, 4, pp->uid_attribute) > 0 || filter_device(conf->blist_device, conf->elist_device, pp->vendor_id, pp->product_id, pp->dev) > 0 || filter_protocol(conf->blist_protocol, conf->elist_protocol, pp) > 0) return PATHINFO_SKIPPED; } path_state = path_sysfs_state(pp); if (path_state == PATH_REMOVED) goto blank; else if (mask & DI_NOIO) { if (mask & DI_CHECKER) /* * Avoid any IO on the device itself. * simply use the path_offline() return as its state */ if (path_state != PATH_PENDING || pp->state == PATH_UNCHECKED || pp->state == PATH_WILD) pp->chkrstate = pp->state = path_state; return PATHINFO_OK; } /* * fetch info not available through sysfs */ if (pp->fd < 0) pp->fd = open(udev_device_get_devnode(pp->udev), O_RDONLY); if (pp->fd < 0) { condlog(4, "Couldn't open device node for %s: %s", pp->dev, strerror(errno)); goto blank; } if (mask & DI_SERIAL) get_geometry(pp); if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI) scsi_ioctl_pathinfo(pp, mask); if (pp->bus == SYSFS_BUS_CCISS && mask & DI_SERIAL) cciss_ioctl_pathinfo(pp); if (mask & DI_CHECKER) { if (path_state == PATH_UP) { int newstate = PATH_UNCHECKED; if (start_checker(pp, conf, 0, path_state) == 0) { if (checker_need_wait(&pp->checker)) { struct timespec wait = { .tv_nsec = 1000 * 1000, }; nanosleep(&wait, NULL); } newstate = get_state(pp); } if (newstate != PATH_PENDING || pp->state == PATH_UNCHECKED || pp->state == PATH_WILD) pp->chkrstate = pp->state = newstate; if (pp->state == PATH_TIMEOUT) pp->state = PATH_DOWN; if (pp->state == PATH_UP && !pp->size) { condlog(3, "%s: device size is 0, " "path unusable", pp->dev); pp->state = PATH_GHOST; } } else { condlog(3, "%s: path inaccessible", pp->dev); pp->chkrstate = pp->state = path_state; } } if ((mask & DI_WWID) && !strlen(pp->wwid)) { int allow_fallback = ((mask & DI_NOFALLBACK) == 0 && pp->retriggers >= conf->retrigger_tries); get_uid(pp, path_state, pp->udev, allow_fallback); if (!strlen(pp->wwid)) { if (pp->bus == SYSFS_BUS_UNDEF) return PATHINFO_SKIPPED; if (pp->initialized != INIT_FAILED) { pp->initialized = INIT_MISSING_UDEV; pp->tick = conf->retrigger_delay; } else if (allow_fallback && (pp->state == PATH_UP || pp->state == PATH_GHOST)) { /* * We have failed to read udev info for this path * repeatedly. We used the fallback in get_uid() * if there was any, and still got no WWID, * although the path is allegedly up. * It's likely that this path is not fit for * multipath use. */ STRBUF_ON_STACK(buf); snprint_path(&buf, "%T", pp, 0); condlog(1, "%s: no WWID in state \"%s\", giving up", pp->dev, get_strbuf_str(&buf)); return PATHINFO_SKIPPED; } return PATHINFO_OK; } else pp->tick = 1; } if (mask & DI_BLACKLIST && mask & DI_WWID) { if (filter_wwid(conf->blist_wwid, conf->elist_wwid, pp->wwid, pp->dev) > 0) { return PATHINFO_SKIPPED; } } /* * Retrieve path priority, even for PATH_DOWN paths if it has never * been successfully obtained before. If path is down don't try * for too long. */ if ((mask & DI_PRIO) && path_state == PATH_UP && strlen(pp->wwid)) { if (pp->state != PATH_DOWN || pp->priority == PRIO_UNDEF) { get_prio(pp); } } if ((mask & DI_ALL) == DI_ALL) pp->initialized = INIT_OK; return PATHINFO_OK; blank: /* * Recoverable error, for example faulty or offline path */ pp->chkrstate = pp->state = PATH_DOWN; if (pp->initialized == INIT_NEW || pp->initialized == INIT_FAILED) memset(pp->wwid, 0, WWID_SIZE); return PATHINFO_OK; } multipath-tools-0.11.1/libmultipath/discovery.h000066400000000000000000000052461475246302400216330ustar00rootroot00000000000000#ifndef DISCOVERY_H_INCLUDED #define DISCOVERY_H_INCLUDED #define SYSFS_PATH_SIZE 255 #define INQUIRY_CMDLEN 6 #define INQUIRY_CMD 0x12 #define SENSE_BUFF_LEN 32 #define RECOVERED_ERROR 0x01 #define MX_ALLOC_LEN 255 #define TUR_CMD_LEN 6 #ifndef BLKGETSIZE #define BLKGETSIZE _IO(0x12,96) #endif #ifndef DEF_TIMEOUT #define DEF_TIMEOUT 30 #endif /* * excerpt from sg_err.h */ #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define PATHINFO_OK 0 #define PATHINFO_FAILED 1 #define PATHINFO_SKIPPED 2 struct config; int path_discovery (vector pathvec, int flag); int path_get_tpgs(struct path *pp); /* This function never returns TPGS_UNDEF */ int do_tur (char *); int path_sysfs_state(struct path *); int start_checker(struct path * pp, struct config * conf, int daemon, int state); int get_state(struct path * pp); int get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen); int pathinfo (struct path * pp, struct config * conf, int mask); int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice, const char *wwid, int flag, struct path **pp_ptr); int store_pathinfo (vector pathvec, struct config *conf, struct udev_device *udevice, int flag, struct path **pp_ptr); int sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp); int sysfs_get_timeout(const struct path *pp, unsigned int *timeout); int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address); int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name); ssize_t sysfs_get_vpd (struct udev_device *udev, unsigned char pg, unsigned char *buff, size_t len); ssize_t sysfs_get_inquiry(struct udev_device *udev, unsigned char *buff, size_t len); int sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen); bool has_uid_fallback(struct path *pp); int get_uid(struct path * pp, int path_state, struct udev_device *udev, int allow_fallback); bool is_vpd_page_supported(int fd, int pg); /* * discovery bitmask */ enum discovery_mode { DI_SYSFS__, DI_SERIAL__, DI_CHECKER__, DI_PRIO__, DI_WWID__, DI_BLACKLIST__, DI_NOIO__, DI_NOFALLBACK__, }; #define DI_SYSFS (1 << DI_SYSFS__) #define DI_SERIAL (1 << DI_SERIAL__) #define DI_CHECKER (1 << DI_CHECKER__) #define DI_PRIO (1 << DI_PRIO__) #define DI_WWID (1 << DI_WWID__) #define DI_BLACKLIST (1 << DI_BLACKLIST__) #define DI_NOIO (1 << DI_NOIO__) /* Avoid IO on the device */ #define DI_NOFALLBACK (1 << DI_NOFALLBACK__) /* do not allow wwid fallback */ #define DI_ALL (DI_SYSFS | DI_SERIAL | DI_CHECKER | DI_PRIO | \ DI_WWID) #endif /* DISCOVERY_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/dm-generic.c000066400000000000000000000036731475246302400216330ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "generic.h" #include "dm-generic.h" #include "structs.h" #include "structs_vec.h" #include "config.h" #include "print.h" static const struct vector_s* dm_mp_get_pgs(const struct gen_multipath *gmp) { return vector_convert(NULL, gen_multipath_to_dm(gmp)->pg, struct pathgroup, dm_pathgroup_to_gen); } static void dm_mp_rel_pgs(__attribute__((unused)) const struct gen_multipath *gmp, const struct vector_s* v) { vector_free_const(v); } static const struct vector_s* dm_pg_get_paths(const struct gen_pathgroup *gpg) { return vector_convert(NULL, gen_pathgroup_to_dm(gpg)->paths, struct path, dm_path_to_gen); } static void dm_mp_rel_paths(__attribute__((unused)) const struct gen_pathgroup *gpg, const struct vector_s* v) { vector_free_const(v); } const struct gen_multipath_ops dm_gen_multipath_ops = { .get_pathgroups = dm_mp_get_pgs, .rel_pathgroups = dm_mp_rel_pgs, .snprint = snprint_multipath_attr, .style = snprint_multipath_style, }; const struct gen_pathgroup_ops dm_gen_pathgroup_ops = { .get_paths = dm_pg_get_paths, .rel_paths = dm_mp_rel_paths, .snprint = snprint_pathgroup_attr, }; const struct gen_path_ops dm_gen_path_ops = { .snprint = snprint_path_attr, }; multipath-tools-0.11.1/libmultipath/dm-generic.h000066400000000000000000000026741475246302400216400ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DM_GENERIC_H_INCLUDED #define DM_GENERIC_H_INCLUDED #include "generic.h" #include "list.h" /* for container_of */ #include "structs.h" #define dm_multipath_to_gen(mpp) (&((mpp)->generic_mp)) #define gen_multipath_to_dm(gm) \ container_of_const((gm), struct multipath, generic_mp) #define dm_pathgroup_to_gen(pg) (&(pg->generic_pg)) #define gen_pathgroup_to_dm(gpg) \ container_of_const((gpg), struct pathgroup, generic_pg) #define dm_path_to_gen(pp) (&((pp)->generic_path)) #define gen_path_to_dm(gp) \ container_of_const((gp), struct path, generic_path) extern const struct gen_multipath_ops dm_gen_multipath_ops; extern const struct gen_pathgroup_ops dm_gen_pathgroup_ops; extern const struct gen_path_ops dm_gen_path_ops; #endif /* DM_GENERIC_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/dmparser.c000066400000000000000000000216311475246302400214300ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Stefan Bader, IBM * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include "checkers.h" #include "vector.h" #include "structs.h" #include "util.h" #include "debug.h" #include "dmparser.h" #include "strbuf.h" #define WORD_SIZE 64 static int merge_words(char **dst, const char *word) { char * p = *dst; int len, dstlen; dstlen = strlen(*dst); len = dstlen + strlen(word) + 2; *dst = realloc(*dst, len); if (!*dst) { free(p); return 1; } p = *dst + dstlen; *p = ' '; ++p; strncpy(p, word, len - dstlen - 1); return 0; } /* * Transforms the path group vector into a proper device map string */ int assemble_map(struct multipath *mp, char **params) { static const char no_path_retry[] = "queue_if_no_path"; static const char retain_hwhandler[] = "retain_attached_hw_handler"; int i, j; int minio; int nr_priority_groups, initial_pg_nr; STRBUF_ON_STACK(buff); struct pathgroup * pgp; struct path * pp; minio = mp->minio; nr_priority_groups = VECTOR_SIZE(mp->pg); initial_pg_nr = (nr_priority_groups ? mp->bestpg : 0); switch (mp->no_path_retry) { case NO_PATH_RETRY_UNDEF: case NO_PATH_RETRY_FAIL: break; default: /* don't enable queueing if no_path_retry has timed out */ if (mp->in_recovery && mp->retry_tick == 0 && count_active_paths(mp) == 0) break; /* fallthrough */ case NO_PATH_RETRY_QUEUE: add_feature(&mp->features, no_path_retry); break; } if (mp->retain_hwhandler == RETAIN_HWHANDLER_ON && get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) add_feature(&mp->features, retain_hwhandler); if (print_strbuf(&buff, "%s %s %i %i", mp->features, mp->hwhandler, nr_priority_groups, initial_pg_nr) < 0) goto err; vector_foreach_slot (mp->pg, pgp, i) { pgp = VECTOR_SLOT(mp->pg, i); if (print_strbuf(&buff, " %s %i 1", mp->selector, VECTOR_SIZE(pgp->paths)) < 0) 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) goto err; } } *params = steal_strbuf_str(&buff); condlog(4, "%s: assembled map [%s]", mp->alias, *params); return 0; err: return 1; } /* * Caution callers: If this function encounters yet unknown path devices, it * adds them uninitialized to the mpp. * Call update_pathvec_from_dm() after this function to make sure * all data structures are in a sane state. */ int disassemble_map(const struct vector_s *pathvec, const char *params, struct multipath *mpp) { char * word; const char *p; int i, j, k; int num_features = 0; int num_hwhandler = 0; int num_pg = 0; int num_pg_args = 0; int num_paths = 0; int num_paths_args = 0; int def_minio = 0; struct path * pp; struct pathgroup * pgp; assert(pathvec != NULL); p = params; condlog(4, "%s: disassemble map [%s]", mpp->alias, params); /* * features */ p += get_word(p, &mpp->features); if (!mpp->features) return 1; num_features = atoi(mpp->features); for (i = 0; i < num_features; i++) { p += get_word(p, &word); if (!word) return 1; if (merge_words(&mpp->features, word)) { free(word); return 1; } free(word); } mpp->queue_mode = strstr(mpp->features, "queue_mode bio") ? QUEUE_MODE_BIO : QUEUE_MODE_RQ; /* * hwhandler */ p += get_word(p, &mpp->hwhandler); if (!mpp->hwhandler) return 1; num_hwhandler = atoi(mpp->hwhandler); for (i = 0; i < num_hwhandler; i++) { p += get_word(p, &word); if (!word) return 1; if (merge_words(&mpp->hwhandler, word)) { free(word); return 1; } free(word); } /* * nb of path groups */ p += get_word(p, &word); if (!word) return 1; num_pg = atoi(word); free(word); if (num_pg > 0) { if (!mpp->pg) { mpp->pg = vector_alloc(); if (!mpp->pg) return 1; } } else { free_pgvec(mpp->pg, KEEP_PATHS); mpp->pg = NULL; } /* * first pg to try */ p += get_word(p, &word); if (!word) goto out; mpp->nextpg = atoi(word); free(word); for (i = 0; i < num_pg; i++) { /* * selector */ if (!mpp->selector) { p += get_word(p, &mpp->selector); if (!mpp->selector) goto out; /* * selector args */ p += get_word(p, &word); if (!word) goto out; num_pg_args = atoi(word); if (merge_words(&mpp->selector, word)) goto out1; free(word); } else { p += get_word(p, NULL); p += get_word(p, NULL); } for (j = 0; j < num_pg_args; j++) p += get_word(p, NULL); /* * paths */ pgp = alloc_pathgroup(); if (!pgp) goto out; if (add_pathgroup(mpp, pgp)) { free_pathgroup(pgp, KEEP_PATHS); goto out; } p += get_word(p, &word); if (!word) goto out; num_paths = atoi(word); free(word); p += get_word(p, &word); if (!word) goto out; num_paths_args = atoi(word); free(word); for (j = 0; j < num_paths; j++) { pp = NULL; p += get_word(p, &word); if (!word) goto out; pp = find_path_by_devt(pathvec, word); if (!pp) { pp = alloc_path(); if (!pp) goto out1; strlcpy(pp->dev_t, word, BLK_DEV_SIZE); if (store_path(pgp->paths, pp)) { free_path(pp); goto out1; } } else if (store_path(pgp->paths, pp)) goto out1; free(word); pgp->id ^= (long)pp; pp->pgindex = i + 1; for (k = 0; k < num_paths_args; k++) if (k == 0) { p += get_word(p, &word); 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; } else p += get_word(p, NULL); } } return 0; out1: free(word); out: free_pgvec(mpp->pg, KEEP_PATHS); mpp->pg = NULL; return 1; } int disassemble_status(const char *params, struct multipath *mpp) { char *word; const char *p; int i, j, k; int num_feature_args; int num_hwhandler_args; int num_pg; int num_pg_args; int num_paths; int def_minio = 0; struct path * pp; struct pathgroup * pgp; p = params; condlog(4, "%s: disassemble status [%s]", mpp->alias, params); /* * features */ p += get_word(p, &word); if (!word) return 1; num_feature_args = atoi(word); free(word); for (i = 0; i < num_feature_args; i++) { if (i == 1) { p += get_word(p, &word); if (!word) return 1; mpp->queuedio = atoi(word); free(word); continue; } /* unknown */ p += get_word(p, NULL); } /* * hwhandler */ p += get_word(p, &word); if (!word) return 1; num_hwhandler_args = atoi(word); free(word); for (i = 0; i < num_hwhandler_args; i++) p += get_word(p, NULL); /* * nb of path groups */ p += get_word(p, &word); if (!word) return 1; num_pg = atoi(word); free(word); if (num_pg == 0) return 0; /* * next pg to try */ p += get_word(p, NULL); if (VECTOR_SIZE(mpp->pg) < num_pg) return 1; for (i = 0; i < num_pg; i++) { pgp = VECTOR_SLOT(mpp->pg, i); /* * PG status */ p += get_word(p, &word); if (!word) return 1; switch (*word) { case 'D': pgp->status = PGSTATE_DISABLED; break; case 'A': pgp->status = PGSTATE_ACTIVE; break; case 'E': pgp->status = PGSTATE_ENABLED; break; default: pgp->status = PGSTATE_UNDEF; break; } free(word); /* * Path Selector Group Arguments */ p += get_word(p, &word); if (!word) return 1; num_pg_args = atoi(word); free(word); /* Ignore ps group arguments */ for (j = 0; j < num_pg_args; j++) p += get_word(p, NULL); p += get_word(p, &word); if (!word) return 1; num_paths = atoi(word); free(word); p += get_word(p, &word); if (!word) return 1; num_pg_args = atoi(word); free(word); if (VECTOR_SIZE(pgp->paths) < num_paths) return 1; for (j = 0; j < num_paths; j++) { pp = VECTOR_SLOT(pgp->paths, j); /* * path */ p += get_word(p, NULL); /* * path status */ p += get_word(p, &word); if (!word) return 1; switch (*word) { case 'F': pp->dmstate = PSTATE_FAILED; break; case 'A': pp->dmstate = PSTATE_ACTIVE; break; default: break; } free(word); /* * fail count */ p += get_word(p, &word); if (!word) return 1; pp->failcount = atoi(word); free(word); /* * 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); } } } return 0; } multipath-tools-0.11.1/libmultipath/dmparser.h000066400000000000000000000003751475246302400214370ustar00rootroot00000000000000#ifndef DMPARSER_H_INCLUDED #define DMPARSER_H_INCLUDED int assemble_map (struct multipath *, char **); int disassemble_map (const struct vector_s *, const char *, struct multipath *); int disassemble_status (const char *, struct multipath *); #endif multipath-tools-0.11.1/libmultipath/file.c000066400000000000000000000076321475246302400205370ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat */ #include #include #include #include #include #include #include #include #include #include #include "file.h" #include "debug.h" #include "uxsock.h" /* * significant parts of this file were taken from iscsi-bindings.c of the * linux-iscsi project. * Copyright (C) 2002 Cisco Systems, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * See the file COPYING included with this distribution for more details. */ int ensure_directories_exist(const char *str, mode_t dir_mode) { char *pathname; char *end; int err; pathname = strdup(str); if (!pathname){ condlog(0, "Cannot copy file pathname %s : %s", str, strerror(errno)); return -1; } end = pathname; /* skip leading slashes */ while (end && *end && (*end == '/')) end++; while ((end = strchr(end, '/'))) { /* if there is another slash, make the dir. */ *end = '\0'; err = mkdir(pathname, dir_mode); if (err && errno != EEXIST) { condlog(0, "Cannot make directory [%s] : %s", pathname, strerror(errno)); free(pathname); return -1; } if (!err) condlog(3, "Created dir [%s]", pathname); *end = '/'; end++; } free(pathname); return 0; } static void sigalrm(__attribute__((unused)) int sig) { /* do nothing */ } static int lock_file(int fd, const char *file_name) { struct sigaction act, oldact; sigset_t set, oldset; struct flock lock; int err; memset(&lock, 0, sizeof(lock)); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; act.sa_handler = sigalrm; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigemptyset(&set); sigaddset(&set, SIGALRM); sigaction(SIGALRM, &act, &oldact); pthread_sigmask(SIG_UNBLOCK, &set, &oldset); alarm(FILE_TIMEOUT); err = fcntl(fd, F_SETLKW, &lock); alarm(0); if (err) { if (errno != EINTR) condlog(0, "Cannot lock %s : %s", file_name, strerror(errno)); else condlog(0, "%s is locked. Giving up.", file_name); } pthread_sigmask(SIG_SETMASK, &oldset, NULL); sigaction(SIGALRM, &oldact, NULL); return err; } int open_file(const char *file, int *can_write, const char *header) { int fd; struct stat s; if (ensure_directories_exist(file, 0700)) return -1; *can_write = 1; fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd < 0) { if (errno == EROFS) { *can_write = 0; condlog(3, "Cannot open file [%s] read/write. " " trying readonly", file); fd = open(file, O_RDONLY); if (fd < 0) { condlog(0, "Cannot open file [%s] " "readonly : %s", file, strerror(errno)); return -1; } } else { condlog(0, "Cannot open file [%s] : %s", file, strerror(errno)); return -1; } } if (*can_write && lock_file(fd, file) < 0) goto fail; memset(&s, 0, sizeof(s)); if (fstat(fd, &s) < 0){ condlog(0, "Cannot stat file %s : %s", file, strerror(errno)); goto fail; } if (s.st_size == 0) { if (*can_write == 0) goto fail; /* If file is empty, write the header */ int len = strlen(header); if (write(fd, header, len) != len) { condlog(0, "Cannot write header to file %s : %s", file, strerror(errno)); /* cleanup partially written header */ if (ftruncate(fd, 0)) condlog(0, "Cannot truncate header : %s", strerror(errno)); goto fail; } fsync(fd); condlog(3, "Initialized new file [%s]", file); } return fd; fail: close(fd); return -1; } multipath-tools-0.11.1/libmultipath/file.h000066400000000000000000000004741475246302400205410ustar00rootroot00000000000000/* * Copyright (c) 2010 Benjamin Marzinski, Redhat */ #ifndef FILE_H_INCLUDED #define FILE_H_INCLUDED #include #define FILE_TIMEOUT 30 int ensure_directories_exist(const char *str, mode_t dir_mode); int open_file(const char *file, int *can_write, const char *header); #endif /* FILE_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/foreign.c000066400000000000000000000341631475246302400212500ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "debug.h" #include "util.h" #include "structs.h" #include "structs_vec.h" #include "print.h" #include "foreign.h" #include "strbuf.h" static vector foreigns; static const char *const foreign_dir = MULTIPATH_DIR; /* This protects vector foreigns */ static pthread_rwlock_t foreign_lock = PTHREAD_RWLOCK_INITIALIZER; static void rdlock_foreigns(void) { pthread_rwlock_rdlock(&foreign_lock); } static void wrlock_foreigns(void) { pthread_rwlock_wrlock(&foreign_lock); } static void unlock_foreigns(__attribute__((unused)) void *unused) { pthread_rwlock_unlock(&foreign_lock); } #define get_dlsym(foreign, sym, lbl) \ do { \ foreign->sym = dlsym(foreign->handle, #sym); \ if (foreign->sym == NULL) { \ condlog(0, "%s: symbol \"%s\" not found in \"%s\"", \ __func__, #sym, foreign->name); \ goto lbl; \ } \ } while(0) static void free_foreign(struct foreign *fgn) { struct context *ctx; if (fgn == NULL) return; ctx = fgn->context; fgn->context = NULL; if (ctx != NULL) fgn->cleanup(ctx); if (fgn->handle != NULL) dlclose(fgn->handle); free(fgn); } void cleanup_foreign__(void) { struct foreign *fgn; int i; if (foreigns == NULL) return; vector_foreach_slot_backwards(foreigns, fgn, i) { vector_del_slot(foreigns, i); free_foreign(fgn); } vector_free(foreigns); foreigns = NULL; } void cleanup_foreign(void) { wrlock_foreigns(); cleanup_foreign__(); unlock_foreigns(NULL); } static const char foreign_pattern[] = "libforeign-*.so"; static int select_foreign_libs(const struct dirent *di) { return fnmatch(foreign_pattern, di->d_name, FNM_FILE_NAME) == 0; } static void free_pre(void *arg) { regex_t **pre = arg; if (pre != NULL && *pre != NULL) { regfree(*pre); free(*pre); *pre = NULL; } } static int _init_foreign(const char *enable) { char pathbuf[PATH_MAX]; struct dirent **di = NULL; struct scandir_result sr; int r, i; regex_t *enable_re = NULL; foreigns = vector_alloc(); if (foreigns == NULL) return -ENOMEM; pthread_cleanup_push(free_pre, &enable_re); enable_re = calloc(1, sizeof(*enable_re)); if (enable_re) { const char *str = enable ? enable : DEFAULT_ENABLE_FOREIGN; r = regcomp(enable_re, str, REG_EXTENDED|REG_NOSUB); if (r != 0) { char errbuf[64]; (void)regerror(r, enable_re, errbuf, sizeof(errbuf)); condlog (2, "%s: error compiling enable_foreign = \"%s\": \"%s\"", __func__, str, errbuf); goto out_free_pre; } } r = scandir(foreign_dir, &di, select_foreign_libs, alphasort); if (r == 0) { condlog(3, "%s: no foreign multipath libraries found", __func__); goto out_free_pre; } else if (r < 0) { r = -errno; condlog(1, "%s: error scanning foreign multipath libraries: %m", __func__); cleanup_foreign__(); goto out_free_pre; } sr.di = di; sr.n = r; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < r; i++) { const char *msg, *fn, *c; struct foreign *fgn; size_t len, namesz; fn = di[i]->d_name; len = strlen(fn); c = strchr(fn, '-'); if (len < sizeof(foreign_pattern) - 1 || c == NULL) { condlog(0, "%s: bad file name %s, fnmatch error?", __func__, fn); continue; } c++; condlog(4, "%s: found %s", __func__, fn); namesz = len - sizeof(foreign_pattern) + 3; fgn = malloc(sizeof(*fgn) + namesz); if (fgn == NULL) continue; memset(fgn, 0, sizeof(*fgn)); strlcpy((char*)fgn + offsetof(struct foreign, name), c, namesz); if (enable_re != NULL) { int ret = regexec(enable_re, fgn->name, 0, NULL, 0); if (ret == REG_NOMATCH) { condlog(3, "%s: foreign library \"%s\" is not enabled", __func__, fgn->name); free(fgn); continue; } else if (ret != 0) /* assume it matches */ condlog(2, "%s: error %d in regexec() for %s", __func__, ret, fgn->name); } snprintf(pathbuf, sizeof(pathbuf), "%s/%s", foreign_dir, fn); fgn->handle = dlopen(pathbuf, RTLD_NOW|RTLD_LOCAL); msg = dlerror(); if (fgn->handle == NULL) { condlog(1, "%s: failed to dlopen %s: %s", __func__, pathbuf, msg); goto dl_err; } get_dlsym(fgn, init, dl_err); get_dlsym(fgn, cleanup, dl_err); get_dlsym(fgn, add, dl_err); get_dlsym(fgn, change, dl_err); get_dlsym(fgn, delete, dl_err); get_dlsym(fgn, delete_all, dl_err); get_dlsym(fgn, check, dl_err); get_dlsym(fgn, lock, dl_err); get_dlsym(fgn, unlock, dl_err); get_dlsym(fgn, get_multipaths, dl_err); get_dlsym(fgn, release_multipaths, dl_err); get_dlsym(fgn, get_paths, dl_err); get_dlsym(fgn, release_paths, dl_err); fgn->context = fgn->init(LIBMP_FOREIGN_API, fgn->name); if (fgn->context == NULL) { condlog(0, "%s: init() failed for %s", __func__, fn); goto dl_err; } if (!vector_alloc_slot(foreigns)) { goto dl_err; } vector_set_slot(foreigns, fgn); condlog(3, "foreign library \"%s\" loaded successfully", fgn->name); continue; dl_err: free_foreign(fgn); } r = 0; pthread_cleanup_pop(1); /* free_scandir_result */ out_free_pre: pthread_cleanup_pop(1); /* free_pre */ return r; } int init_foreign(const char *enable) { int ret; wrlock_foreigns(); if (foreigns != NULL) { unlock_foreigns(NULL); condlog(0, "%s: already initialized", __func__); return -EEXIST; } pthread_cleanup_push(unlock_foreigns, NULL); ret = _init_foreign(enable); pthread_cleanup_pop(1); return ret; } int add_foreign(struct udev_device *udev) { struct foreign *fgn; dev_t dt; int j; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->add(fgn->context, udev); if (r == FOREIGN_CLAIMED) { condlog(3, "%s: foreign \"%s\" claims device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r == FOREIGN_OK) { condlog(4, "%s: foreign \"%s\" owns device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int change_foreign(struct udev_device *udev) { struct foreign *fgn; int j; dev_t dt; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->change(fgn->context, udev); if (r == FOREIGN_OK) { condlog(4, "%s: foreign \"%s\" completed %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int delete_foreign(struct udev_device *udev) { struct foreign *fgn; int j; dev_t dt; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->delete(fgn->context, udev); if (r == FOREIGN_OK) { condlog(3, "%s: foreign \"%s\" deleted device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int delete_all_foreign(void) { struct foreign *fgn; int j; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, j) { int r; r = fgn->delete_all(fgn->context); if (r != FOREIGN_IGNORED && r != FOREIGN_OK) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return FOREIGN_OK; } void check_foreign(void) { struct foreign *fgn; int j; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, j) { fgn->check(fgn->context); } pthread_cleanup_pop(1); } /* Call this after get_path_layout */ void foreign_path_layout(fieldwidth_t *width) { struct foreign *fgn; int i; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_paths(fgn->context); if (vec != NULL) { get_path_layout__(vec, LAYOUT_RESET_NOT, width); } fgn->release_paths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); } /* Call this after get_multipath_layout */ void foreign_multipath_layout(fieldwidth_t *width) { struct foreign *fgn; int i; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { get_multipath_layout__(vec, LAYOUT_RESET_NOT, width); } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); } static int snprint_foreign_topology__(struct strbuf *buf, int verbosity, const fieldwidth_t *width) { struct foreign *fgn; int i; size_t initial_len = get_strbuf_len(buf); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; const struct gen_multipath *gm; int j; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gm, j) { if (snprint_multipath_topology__( gm, buf, verbosity, width) < 0) break; } } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); } return get_strbuf_len(buf) - initial_len; } int snprint_foreign_topology(struct strbuf *buf, int verbosity, const fieldwidth_t *width) { int rc; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); rc = snprint_foreign_topology__(buf, verbosity, width); pthread_cleanup_pop(1); return rc; } void print_foreign_topology(int verbosity) { STRBUF_ON_STACK(buf); struct foreign *fgn; int i; fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((width = alloc_path_layout()) == NULL) return; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_paths(fgn->context); get_multipath_layout__(vec, LAYOUT_RESET_NOT, width); fgn->release_paths(fgn->context, vec); pthread_cleanup_pop(1); } snprint_foreign_topology__(&buf, verbosity, width); pthread_cleanup_pop(1); printf("%s", get_strbuf_str(&buf)); } int snprint_foreign_paths(struct strbuf *buf, const char *style, const fieldwidth_t *width) { struct foreign *fgn; int i; size_t initial_len = get_strbuf_len(buf); rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; const struct gen_path *gp; int j, ret = 0; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_paths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gp, j) { ret = snprint_path__(gp, buf, style, width); if (ret < 0) break; } } fgn->release_paths(fgn->context, vec); pthread_cleanup_pop(1); if (ret < 0) break; } pthread_cleanup_pop(1); return get_strbuf_len(buf) - initial_len; } int snprint_foreign_multipaths(struct strbuf *buf, const char *style, const fieldwidth_t *width) { struct foreign *fgn; int i; size_t initial_len = get_strbuf_len(buf); rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct vector_s *vec; const struct gen_multipath *gm; int j, ret = 0; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gm, j) { ret = snprint_multipath__(gm, buf, style, width); if (ret < 0) break; } } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); if (ret < 0) break; } pthread_cleanup_pop(1); return get_strbuf_len(buf) - initial_len; } multipath-tools-0.11.1/libmultipath/foreign.h000066400000000000000000000234661475246302400212610ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FOREIGN_H_INCLUDED #define FOREIGN_H_INCLUDED #include #include #define LIBMP_FOREIGN_API ((1 << 8) | 2) struct strbuf; struct context; /* return codes of functions below returning "int" */ enum foreign_retcode { FOREIGN_OK, FOREIGN_CLAIMED, FOREIGN_IGNORED, FOREIGN_UNCLAIMED, FOREIGN_NODEV, FOREIGN_ERR, LAST_FOREIGN_RETCODE__, }; /** * Foreign multipath library API * Foreign libraries must implement the following methods. */ struct foreign { /** * method: init(api, name) * Initialize foreign library, and check API compatibility * return pointer to opaque internal data structure if successful, * NULL otherwise. * * @param[in] api: API version * @param[in] name: name to use for references to self in log messages, * doesn't need to be strdup'd * @returns context pointer to use in future method calls. */ struct context* (*init)(unsigned int api, const char *name); /** * method: cleanup(context) * Free data structures used by foreign library, including * context itself. * * @param[in] context foreign library context. This shouldn't be * referenced any more after calling cleanup(). */ void (*cleanup)(struct context *); /** * method: add(context, udev) * This is called during path detection, and for udev ADD events. * * @param[in] context foreign library context * @param[in] udev udev device to add * @returns status code * @retval FOREIGN_CLAIMED: device newly claimed * @retval FOREIGN_OK: device already registered, no action taken * @retval FOREIGN_IGNORED: device is ignored, no action taken * @retval FOREIGN_ERR: an error occurred (e.g. out-of-memory) */ int (*add)(struct context *, struct udev_device *); /** * method: change * This is called on udev CHANGE events. * * @param[in] context foreign library context * @param[in] udev udev device that has generated the event * @returns status code * @retval FOREIGN_OK: event processed * @retval FOREIGN_IGNORED: the device is ignored * @retval FOREIGN_ERR: an error occurred (e.g. out-of-memory) * * Note: theoretically it can happen that the status of a foreign device * (claimed vs. not claimed) changes in a change event. * Supporting this correctly would require big efforts. For now, we * don't support it. "multipathd reconfigure" starts foreign device * detection from scratch and should be able to handle this situation. */ int (*change)(struct context *, struct udev_device *); /** * method: delete * This is called on udev DELETE events. * * @param[in] context foreign library context * @param[in] udev udev device that has generated the event and * should be deleted * @returns status code * @retval FOREIGN_OK: processed correctly (device deleted) * @retval FOREIGN_IGNORED: device wasn't registered internally * @retval FOREIGN_ERR: error occurred. */ int (*delete)(struct context *, struct udev_device *); /** * method: delete_all * This is called if multipathd reconfigures itself. * Deletes all registered devices (maps and paths) * * @param[in] context foreign library context * @returns status code * @retval FOREIGN_OK: processed correctly * @retval FOREIGN_IGNORED: nothing to delete * @retval FOREIGN_ERR: error occurred */ int (*delete_all)(struct context*); /** * method: check * This is called from multipathd's checker loop. * * Check status of managed devices, update internal status, and print * log messages if appropriate. * @param[in] context foreign library context */ void (*check)(struct context *); /** * lock internal data structures. * @param[in] ctx: foreign context */ void (*lock)(struct context *ctx); /** * unlock internal data structures. * @param[in] ctx: foreign context (void* in order to use the function * as argument to pthread_cleanup_push()) */ void (*unlock)(void *ctx); /** * method: get_multipaths(context) * Returned vector must be freed by calling release_multipaths(). * Lock must be held until release_multipaths() is called. * * @param[in] context foreign library context * @returns a vector of "struct gen_multipath*" with the map devices * belonging to this library (see generic.h). */ const struct vector_s* (*get_multipaths)(const struct context *); /** * method: release_multipaths(context, mpvec) * release data structures obtained with get_multipaths (if any) * * @param[in] ctx the foreign context * @param[in] mpvec the vector allocated with get_multipaths() */ void (*release_multipaths)(const struct context *ctx, const struct vector_s* mpvec); /** * method: get_paths * Returned vector must be freed by calling release_paths(). * Lock must be held until release_paths() is called. * * @param[in] context foreign library context * @returns a vector of "struct gen_path*" with the path devices * belonging to this library (see generic.h) */ const struct vector_s* (*get_paths)(const struct context *); /** * release data structures obtained with get_multipaths (if any) * * @param[in] ctx the foreign context * @param[in] ppvec the vector allocated with get_paths() */ void (*release_paths)(const struct context *ctx, const struct vector_s* ppvec); void *handle; struct context *context; const char name[0]; }; /** * init_foreign(dir) * load and initialize foreign multipath libraries in dir (libforeign-*.so). * @param dir: directory to search * @param enable: regex to match foreign library name ("*" above) against * @returns: 0 on success, negative value on failure. */ int init_foreign(const char *enable); /** * cleanup_foreign(dir) * cleanup and free all data structures owned by foreign libraries */ void cleanup_foreign(void); /** * add_foreign(udev) * check if a device belongs to any foreign library. * calls add() for all known foreign libs, in the order registered, * until the first one returns FOREIGN_CLAIMED or FOREIGN_OK. * @param udev: udev device to check * @returns: status code * @retval FOREIGN_CLAIMED: newly claimed by a foreign lib * @retval FOREIGN_OK: already claimed by a foreign lib * @retval FOREIGN_IGNORED: ignored by all foreign libs * @retval FOREIGN_ERR: an error occurred */ int add_foreign(struct udev_device *); /** * change_foreign(udev) * Notify foreign libraries of an udev CHANGE event * @param udev: udev device to check * @returns: status code (see change() method above). */ int change_foreign(struct udev_device *); /** * delete_foreign(udev) * @param udev: udev device being removed * @returns: status code (see remove() above) */ int delete_foreign(struct udev_device *); /** * delete_all_foreign() * call delete_all() for all foreign libraries * @returns: status code (see delete_all() above) */ int delete_all_foreign(void); /** * check_foreign() * call check() (see above) for all foreign libraries */ void check_foreign(void); /** * foreign_path_layout() * call this before printing paths, after get_path_layout(), to determine * output field width. * @param width: an array allocated by alloc_path_layout() */ void foreign_path_layout(fieldwidth_t *width); /** * foreign_multipath_layout() * call this before printing maps, after get_multipath_layout(), to determine * output field width. * @param width: an array allocated by alloc_multipath_layout() */ void foreign_multipath_layout(fieldwidth_t *width); /** * snprint_foreign_topology(buf, len, verbosity); * prints topology information from foreign libraries into buffer, * '\0' - terminated. * @param buf: output buffer * @param verbosity: verbosity level * @param width: an array of field widths, initialized by get_path_layout__() * @returns: number of printed characters excluding trailing '\0'. */ int snprint_foreign_topology(struct strbuf *buf, int verbosity, const fieldwidth_t *width); /** * snprint_foreign_paths(buf, len, style, pad); * prints formatted path information from foreign libraries into buffer, * '\0' - terminated. * @param buf: output buffer * @param style: format string * @param width: array initialized with get_path_layout(), or NULL for no padding * @returns: number of printed characters excluding trailing '\0'. */ int snprint_foreign_paths(struct strbuf *buf, const char *style, const fieldwidth_t *width); /** * snprint_foreign_multipaths(buf, len, style, pad); * prints formatted map information from foreign libraries into buffer, * '\0' - terminated. * @param buf: output buffer * @param style: format string * @param width: array initialized with get_path_layout(), or NULL for no padding * @returns: number of printed characters excluding trailing '\0'. */ int snprint_foreign_multipaths(struct strbuf *buf, const char *style, const fieldwidth_t *width); /** * print_foreign_topology(v) * print foreign topology to stdout * @param verbosity: verbosity level */ void print_foreign_topology(int verbosity); /** * is_claimed_by_foreign(ud) * @param udev: udev device * @returns: true if device is (newly or already) claimed by a foreign lib */ static inline bool is_claimed_by_foreign(struct udev_device *ud) { int rc = add_foreign(ud); return (rc == FOREIGN_CLAIMED || rc == FOREIGN_OK); } #endif /* FOREIGN_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/foreign/000077500000000000000000000000001475246302400210755ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/foreign/Makefile000066400000000000000000000013741475246302400225420ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # TOPDIR=../.. include ../../Makefile.inc CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(nvmedir) CFLAGS += $(LIB_CFLAGS) LDFLAGS += -L$(multipathdir) -L$(mpathutildir) LIBDEPS = -lmultipath -lmpathutil -ludev -lpthread -lrt LIBS = libforeign-nvme.so all: $(LIBS) libforeign-%.so: %.o $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS) install: $(Q)$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(plugindir) uninstall: $(Q)for file in $(LIBS); do $(RM) $(DESTDIR)$(plugindir)/$$file; done clean: dep_clean $(Q)$(RM) core *.a *.o *.gz *.so OBJS := $(LIBS:libforeign-%.so=%.o) .SECONDARY: $(OBJS) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmultipath/foreign/nvme.c000066400000000000000000000566071475246302400222240ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "nvme-lib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "vector.h" #include "generic.h" #include "foreign.h" #include "debug.h" #include "structs.h" #include "sysfs.h" #include "strbuf.h" static const char nvme_vendor[] = "NVMe"; static const char N_A[] = "n/a"; const char *THIS; struct nvme_map; struct nvme_pathgroup { struct gen_pathgroup gen; struct vector_s pathvec; }; struct nvme_path { struct gen_path gen; struct udev_device *udev; struct udev_device *ctl; struct nvme_map *map; bool seen; /* * The kernel works in failover mode. * Each path has a separate path group. */ struct nvme_pathgroup pg; }; struct nvme_map { struct gen_multipath gen; struct udev_device *udev; struct udev_device *subsys; dev_t devt; struct vector_s pgvec; int nr_live; int ana_supported; }; #define NAME_LEN 64 /* buffer length for temp attributes */ #define const_gen_mp_to_nvme(g) ((const struct nvme_map*)(g)) #define gen_mp_to_nvme(g) ((struct nvme_map*)(g)) #define nvme_mp_to_gen(n) &((n)->gen) #define const_gen_pg_to_nvme(g) ((const struct nvme_pathgroup*)(g)) #define gen_pg_to_nvme(g) ((struct nvme_pathgroup*)(g)) #define nvme_pg_to_gen(n) &((n)->gen) #define const_gen_path_to_nvme(g) ((const struct nvme_path*)(g)) #define gen_path_to_nvme(g) ((struct nvme_path*)(g)) #define nvme_path_to_gen(n) &((n)->gen) #define nvme_pg_to_path(x) (VECTOR_SLOT(&((x)->pathvec), 0)) #define nvme_path_to_pg(x) &((x)->pg) static void cleanup_nvme_path(struct nvme_path *path) { condlog(5, "%s: %p %p", __func__, path, path->udev); if (path->udev) udev_device_unref(path->udev); vector_reset(&path->pg.pathvec); /* ctl is implicitly referenced by udev, no need to unref */ free(path); } static void cleanup_nvme_map(struct nvme_map *map) { struct nvme_pathgroup *pg; struct nvme_path *path; int i; vector_foreach_slot_backwards(&map->pgvec, pg, i) { path = nvme_pg_to_path(pg); condlog(5, "%s: %d %p", __func__, i, path); cleanup_nvme_path(path); vector_del_slot(&map->pgvec, i); } vector_reset(&map->pgvec); if (map->udev) udev_device_unref(map->udev); /* subsys is implicitly referenced by udev, no need to unref */ free(map); } static const struct vector_s* nvme_mp_get_pgs(const struct gen_multipath *gmp) { const struct nvme_map *nvme = const_gen_mp_to_nvme(gmp); /* This is all used under the lock, no need to copy */ return &nvme->pgvec; } static void nvme_mp_rel_pgs(__attribute__((unused)) const struct gen_multipath *gmp, __attribute__((unused)) const struct vector_s *v) { /* empty */ } static void rstrip(char *str) { int n; for (n = strlen(str) - 1; n >= 0 && str[n] == ' '; n--); str[n+1] = '\0'; } static int snprint_nvme_map(const struct gen_multipath *gmp, struct strbuf *buff, char wildcard) { const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp); char fld[NAME_LEN]; const char *val; switch (wildcard) { case 'd': return append_strbuf_str(buff, udev_device_get_sysname(nvm->udev)); case 'n': return print_strbuf(buff, "%s:nsid.%s", udev_device_get_sysattr_value(nvm->subsys, "subsysnqn"), udev_device_get_sysattr_value(nvm->udev, "nsid")); case 'w': return append_strbuf_str(buff, udev_device_get_sysattr_value(nvm->udev, "wwid")); case 'N': return print_strbuf(buff, "%u", nvm->nr_live); case 'S': return append_strbuf_str(buff, udev_device_get_sysattr_value(nvm->udev, "size")); case 'v': return append_strbuf_str(buff, nvme_vendor); case 's': case 'p': snprintf(fld, sizeof(fld), "%s", udev_device_get_sysattr_value(nvm->subsys, "model")); rstrip(fld); if (wildcard == 'p') return append_strbuf_str(buff, fld); return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld, udev_device_get_sysattr_value(nvm->subsys, "firmware_rev")); case 'e': return append_strbuf_str(buff, udev_device_get_sysattr_value(nvm->subsys, "firmware_rev")); case 'r': val = udev_device_get_sysattr_value(nvm->udev, "ro"); if (!val) return append_strbuf_str(buff, "undef"); else if (val[0] == 1) return append_strbuf_str(buff, "ro"); else return append_strbuf_str(buff, "rw"); case 'G': return append_strbuf_str(buff, THIS); case 'h': if (nvm->ana_supported == YNU_YES) return append_strbuf_str(buff, "ANA"); default: break; } return append_strbuf_str(buff, N_A); } static const struct vector_s* nvme_pg_get_paths(const struct gen_pathgroup *gpg) { const struct nvme_pathgroup *gp = const_gen_pg_to_nvme(gpg); /* This is all used under the lock, no need to copy */ return &gp->pathvec; } static void nvme_pg_rel_paths(__attribute__((unused)) const struct gen_pathgroup *gpg, __attribute__((unused)) const struct vector_s *v) { /* empty */ } static int snprint_hcil(const struct nvme_path *np, struct strbuf *buf) { unsigned int nvmeid, ctlid, nsid; int rc; const char *sysname = udev_device_get_sysname(np->udev); rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid); if (rc != 3) { condlog(1, "%s: failed to scan %s", __func__, sysname); return print_strbuf(buf, "(ERR:%s)", sysname); } else return print_strbuf(buf, "%u:%u:%u", nvmeid, ctlid, nsid); } static int snprint_nvme_path(const struct gen_path *gp, struct strbuf *buff, char wildcard) { const struct nvme_path *np = const_gen_path_to_nvme(gp); dev_t devt; char fld[NAME_LEN]; struct udev_device *pci; switch (wildcard) { case 'w': return print_strbuf(buff, "%s", udev_device_get_sysattr_value(np->udev, "wwid")); case 'd': return print_strbuf(buff, "%s", udev_device_get_sysname(np->udev)); case 'i': return snprint_hcil(np, buff); case 'D': devt = udev_device_get_devnum(np->udev); return print_strbuf(buff, "%u:%u", major(devt), minor(devt)); case 'o': if (sysfs_attr_get_value(np->ctl, "state", fld, sizeof(fld)) > 0) return append_strbuf_str(buff, fld); break; case 'T': if (sysfs_attr_get_value(np->udev, "ana_state", fld, sizeof(fld)) > 0) return append_strbuf_str(buff, fld); break; case 'p': if (sysfs_attr_get_value(np->udev, "ana_state", fld, sizeof(fld)) > 0) { rstrip(fld); if (!strcmp(fld, "optimized")) return print_strbuf(buff, "%d", 50); else if (!strcmp(fld, "non-optimized")) return print_strbuf(buff, "%d", 10); else return print_strbuf(buff, "%d", 0); } break; case 's': snprintf(fld, sizeof(fld), "%s", udev_device_get_sysattr_value(np->ctl, "model")); rstrip(fld); return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld, udev_device_get_sysattr_value(np->ctl, "firmware_rev")); case 'S': return append_strbuf_str(buff, udev_device_get_sysattr_value(np->udev, "size")); case 'z': return append_strbuf_str(buff, udev_device_get_sysattr_value(np->ctl, "serial")); case 'm': return append_strbuf_str(buff, udev_device_get_sysname(np->map->udev)); case 'N': case 'R': return print_strbuf(buff, "%s:%s", udev_device_get_sysattr_value(np->ctl, "transport"), udev_device_get_sysattr_value(np->ctl, "address")); case 'G': return print_strbuf(buff, "[%s]", THIS); case 'a': pci = udev_device_get_parent_with_subsystem_devtype(np->ctl, "pci", NULL); if (pci != NULL) return print_strbuf(buff, "PCI:%s", udev_device_get_sysname(pci)); /* fall through */ default: break; } return append_strbuf_str(buff, N_A); } static int snprint_nvme_pg(const struct gen_pathgroup *gmp, struct strbuf *buff, char wildcard) { const struct nvme_pathgroup *pg = const_gen_pg_to_nvme(gmp); const struct nvme_path *path = nvme_pg_to_path(pg); switch (wildcard) { case 't': return snprint_nvme_path(nvme_path_to_gen(path), buff, 'T'); case 'p': return snprint_nvme_path(nvme_path_to_gen(path), buff, 'p'); default: return append_strbuf_str(buff, N_A); } } static int nvme_style(__attribute__((unused)) const struct gen_multipath* gm, struct strbuf *buf, __attribute__((unused)) int verbosity) { return append_strbuf_str(buf, "%w [%G]:%d %s"); } static const struct gen_multipath_ops nvme_map_ops = { .get_pathgroups = nvme_mp_get_pgs, .rel_pathgroups = nvme_mp_rel_pgs, .style = nvme_style, .snprint = snprint_nvme_map, }; static const struct gen_pathgroup_ops nvme_pg_ops __attribute__((unused)) = { .get_paths = nvme_pg_get_paths, .rel_paths = nvme_pg_rel_paths, .snprint = snprint_nvme_pg, }; static const struct gen_path_ops nvme_path_ops __attribute__((unused)) = { .snprint = snprint_nvme_path, }; struct context { pthread_mutex_t mutex; vector mpvec; struct udev *udev; }; void lock(struct context *ctx) { pthread_mutex_lock(&ctx->mutex); } void unlock(void *arg) { struct context *ctx = arg; pthread_mutex_unlock(&ctx->mutex); } static int _delete_all(struct context *ctx) { struct nvme_map *nm; int n = VECTOR_SIZE(ctx->mpvec), i; if (n == 0) return FOREIGN_IGNORED; vector_foreach_slot_backwards(ctx->mpvec, nm, i) { vector_del_slot(ctx->mpvec, i); cleanup_nvme_map(nm); } return FOREIGN_OK; } int delete_all(struct context *ctx) { int rc; condlog(5, "%s called for \"%s\"", __func__, THIS); lock(ctx); pthread_cleanup_push(unlock, ctx); rc = _delete_all(ctx); pthread_cleanup_pop(1); return rc; } void cleanup(struct context *ctx) { (void)delete_all(ctx); lock(ctx); /* * Locking is not strictly necessary here, locking in foreign.c * makes sure that no other code is called with this ctx anymore. * But this should make static checkers feel better. */ pthread_cleanup_push(unlock, ctx); if (ctx->udev) udev_unref(ctx->udev); if (ctx->mpvec) vector_free(ctx->mpvec); ctx->mpvec = NULL; ctx->udev = NULL; pthread_cleanup_pop(1); pthread_mutex_destroy(&ctx->mutex); free(ctx); } struct context *init(unsigned int api, const char *name) { struct context *ctx; if (api > LIBMP_FOREIGN_API) { condlog(0, "%s: api version mismatch: %08x > %08x\n", __func__, api, LIBMP_FOREIGN_API); return NULL; } if ((ctx = calloc(1, sizeof(*ctx)))== NULL) return NULL; pthread_mutex_init(&ctx->mutex, NULL); ctx->udev = udev_new(); if (ctx->udev == NULL) goto err; ctx->mpvec = vector_alloc(); if (ctx->mpvec == NULL) goto err; THIS = name; return ctx; err: cleanup(ctx); return NULL; } static struct nvme_map *_find_nvme_map_by_devt(const struct context *ctx, dev_t devt) { struct nvme_map *nm; int i; if (ctx->mpvec == NULL) return NULL; vector_foreach_slot(ctx->mpvec, nm, i) { if (nm->devt == devt) return nm; } return NULL; } static struct nvme_path * _find_path_by_syspath(struct nvme_map *map, const char *syspath) { struct nvme_pathgroup *pg; char real[PATH_MAX]; const char *ppath; const char *psyspath; int i; ppath = realpath(syspath, real); if (ppath == NULL) { condlog(1, "%s: %s: error in realpath", __func__, THIS); ppath = syspath; } vector_foreach_slot(&map->pgvec, pg, i) { struct nvme_path *path = nvme_pg_to_path(pg); psyspath = udev_device_get_syspath(path->udev); if (psyspath && !strcmp(ppath, psyspath)) return path; } condlog(4, "%s: %s: %s not found", __func__, THIS, ppath); return NULL; } static void _udev_device_unref(void *p) { udev_device_unref(p); } static void _udev_enumerate_unref(void *p) { udev_enumerate_unref(p); } static int _dirent_controller(const struct dirent *di) { static const char nvme_prefix[] = "nvme"; const char *p; #ifdef _DIRENT_HAVE_D_TYPE if (di->d_type != DT_LNK) return 0; #endif if (strncmp(di->d_name, nvme_prefix, sizeof(nvme_prefix) - 1)) return 0; p = di->d_name + sizeof(nvme_prefix) - 1; if (*p == '\0' || !isdigit(*p)) return 0; for (++p; *p != '\0'; ++p) if (!isdigit(*p)) return 0; return 1; } /* Find the block device for a given nvme controller */ struct udev_device *get_ctrl_blkdev(const struct context *ctx, struct udev_device *ctrl, const char *ctrl_name) { int ctrl_num, ns_num; struct udev_list_entry *item; struct udev_device *blkdev = NULL; struct udev_enumerate *enm = udev_enumerate_new(ctx->udev); const char *devtype; if (enm == NULL || ctrl_name == NULL) return NULL; if (sscanf(ctrl_name, "nvme%dn%d", &ctrl_num, &ns_num) != 2) return NULL; pthread_cleanup_push(_udev_enumerate_unref, enm); if (udev_enumerate_add_match_parent(enm, ctrl) < 0) goto out; if (udev_enumerate_add_match_subsystem(enm, "block")) goto out; if (udev_enumerate_scan_devices(enm) < 0) { condlog(1, "%s: %s: error enumerating devices", __func__, THIS); goto out; } for (item = udev_enumerate_get_list_entry(enm); item != NULL; item = udev_list_entry_get_next(item)) { struct udev_device *tmp; const char *name = NULL ; int m, n, l; tmp = udev_device_new_from_syspath(ctx->udev, udev_list_entry_get_name(item)); if (tmp == NULL) continue; devtype = udev_device_get_devtype(tmp); if (devtype == NULL || strcmp(devtype, "disk")) { udev_device_unref(tmp); continue; } name = udev_device_get_sysname(tmp); if (name != NULL && sscanf(name, "nvme%dc%dn%d", &m, &n, &l) == 3 && l == ns_num) { blkdev = tmp; break; } udev_device_unref(tmp); } if (blkdev == NULL) condlog(1, "%s: %s: failed to get blockdev for %s", __func__, THIS, udev_device_get_sysname(ctrl)); else condlog(5, "%s: %s: got %s", __func__, THIS, udev_device_get_sysname(blkdev)); out: pthread_cleanup_pop(1); return blkdev; } static void test_ana_support(struct nvme_map *map, struct udev_device *ctl) { const char *dev_t; char sys_path[64]; int fd = -1; int rc; if (map->ana_supported != YNU_UNDEF) return; dev_t = udev_device_get_sysattr_value(ctl, "dev"); if (safe_sprintf(sys_path, "/dev/char/%s", dev_t)) return; fd = open(sys_path, O_RDONLY); if (fd == -1) { condlog(2, "%s: error opening %s", __func__, sys_path); return; } pthread_cleanup_push(cleanup_fd_ptr, &fd); rc = nvme_id_ctrl_ana(fd, NULL); if (rc < 0) condlog(2, "%s: error in nvme_id_ctrl: %s", __func__, strerror(errno)); else { map->ana_supported = (rc == 1 ? YNU_YES : YNU_NO); condlog(3, "%s: NVMe ctrl %s: ANA %s supported", __func__, dev_t, rc == 1 ? "is" : "is not"); } pthread_cleanup_pop(1); } static void _find_controllers(struct context *ctx, struct nvme_map *map) { char pathbuf[PATH_MAX], realbuf[PATH_MAX]; struct dirent **di = NULL; struct scandir_result sr; struct udev_device *subsys; struct nvme_pathgroup *pg; struct nvme_path *path; int r, i, n; if (map == NULL || map->udev == NULL) return; vector_foreach_slot(&map->pgvec, pg, i) { path = nvme_pg_to_path(pg); path->seen = false; } subsys = udev_device_get_parent_with_subsystem_devtype(map->udev, "nvme-subsystem", NULL); if (subsys == NULL) { condlog(1, "%s: %s: BUG: no NVME subsys for %s", __func__, THIS, udev_device_get_sysname(map->udev)); return; } n = snprintf(pathbuf, sizeof(pathbuf), "%s", udev_device_get_syspath(subsys)); r = scandir(pathbuf, &di, _dirent_controller, alphasort); if (r == 0) { condlog(3, "%s: %s: no controllers for %s", __func__, THIS, udev_device_get_sysname(map->udev)); return; } else if (r < 0) { condlog(1, "%s: %s: error %d scanning controllers of %s", __func__, THIS, errno, udev_device_get_sysname(map->udev)); return; } sr.di = di; sr.n = r; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < r; i++) { char *fn = di[i]->d_name; struct udev_device *ctrl; struct udev_device *udev __attribute__((cleanup(cleanup_udev_device))) = NULL; if (safe_snprintf(pathbuf + n, sizeof(pathbuf) - n, "/%s", fn)) continue; if (realpath(pathbuf, realbuf) == NULL) { condlog(3, "%s: %s: realpath: %s", __func__, THIS, strerror(errno)); continue; } condlog(4, "%s: %s: found %s", __func__, THIS, realbuf); ctrl = udev_device_new_from_syspath(ctx->udev, realbuf); if (ctrl == NULL) { condlog(1, "%s: %s: failed to get udev device for %s", __func__, THIS, realbuf); continue; } pthread_cleanup_push(_udev_device_unref, ctrl); udev = get_ctrl_blkdev(ctx, ctrl, udev_device_get_sysname(map->udev)); /* * We give up the reference to the nvme device here and get * it back from the child below. * This way we don't need to worry about unreffing it. */ pthread_cleanup_pop(1); if (udev == NULL) continue; path = _find_path_by_syspath(map, udev_device_get_syspath(udev)); if (path != NULL) { path->seen = true; condlog(4, "%s: %s already known", __func__, fn); continue; } path = calloc(1, sizeof(*path)); if (path == NULL) continue; path->gen.ops = &nvme_path_ops; path->udev = steal_ptr(udev); path->seen = true; path->map = map; path->ctl = udev_device_get_parent_with_subsystem_devtype (path->udev, "nvme", NULL); if (path->ctl == NULL) { condlog(1, "%s: %s: failed to get controller for %s", __func__, THIS, fn); cleanup_nvme_path(path); continue; } test_ana_support(map, path->ctl); path->pg.gen.ops = &nvme_pg_ops; if (!vector_alloc_slot(&path->pg.pathvec)) { cleanup_nvme_path(path); continue; } vector_set_slot(&path->pg.pathvec, path); if (!vector_alloc_slot(&map->pgvec)) { cleanup_nvme_path(path); continue; } vector_set_slot(&map->pgvec, &path->pg); condlog(3, "%s: %s: new path %s added to %s", __func__, THIS, udev_device_get_sysname(path->udev), udev_device_get_sysname(map->udev)); } pthread_cleanup_pop(1); map->nr_live = 0; vector_foreach_slot_backwards(&map->pgvec, pg, i) { path = nvme_pg_to_path(pg); if (!path->seen) { condlog(1, "path %d not found in %s any more", i, udev_device_get_sysname(map->udev)); vector_del_slot(&map->pgvec, i); cleanup_nvme_path(path); } else { static const char live_state[] = "live"; char state[16]; if ((sysfs_attr_get_value(path->ctl, "state", state, sizeof(state)) > 0) && !strncmp(state, live_state, sizeof(live_state) - 1)) map->nr_live++; } } condlog(3, "%s: %s: map %s has %d/%d live paths", __func__, THIS, udev_device_get_sysname(map->udev), map->nr_live, VECTOR_SIZE(&map->pgvec)); } static int _add_map(struct context *ctx, struct udev_device *ud, struct udev_device *subsys) { dev_t devt = udev_device_get_devnum(ud); struct nvme_map *map; if (_find_nvme_map_by_devt(ctx, devt) != NULL) return FOREIGN_OK; map = calloc(1, sizeof(*map)); if (map == NULL) return FOREIGN_ERR; map->devt = devt; map->udev = udev_device_ref(ud); /* * subsys is implicitly referenced by map->udev, * no need to take a reference here. */ map->subsys = subsys; map->gen.ops = &nvme_map_ops; if (!vector_alloc_slot(ctx->mpvec)) { cleanup_nvme_map(map); return FOREIGN_ERR; } vector_set_slot(ctx->mpvec, map); _find_controllers(ctx, map); return FOREIGN_CLAIMED; } int add(struct context *ctx, struct udev_device *ud) { struct udev_device *subsys; int rc; const char *devtype; condlog(5, "%s called for \"%s\"", __func__, THIS); if (ud == NULL) return FOREIGN_ERR; if ((devtype = udev_device_get_devtype(ud)) == NULL || strcmp("disk", devtype)) return FOREIGN_IGNORED; subsys = udev_device_get_parent_with_subsystem_devtype(ud, "nvme-subsystem", NULL); if (subsys == NULL) return FOREIGN_IGNORED; lock(ctx); pthread_cleanup_push(unlock, ctx); rc = _add_map(ctx, ud, subsys); pthread_cleanup_pop(1); if (rc == FOREIGN_CLAIMED) condlog(3, "%s: %s: added map %s", __func__, THIS, udev_device_get_sysname(ud)); else if (rc != FOREIGN_OK) condlog(1, "%s: %s: retcode %d adding %s", __func__, THIS, rc, udev_device_get_sysname(ud)); return rc; } int change(__attribute__((unused)) struct context *ctx, __attribute__((unused)) struct udev_device *ud) { condlog(5, "%s called for \"%s\"", __func__, THIS); return FOREIGN_IGNORED; } static int _delete_map(struct context *ctx, struct udev_device *ud) { int k; struct nvme_map *map; dev_t devt = udev_device_get_devnum(ud); map = _find_nvme_map_by_devt(ctx, devt); if (map ==NULL) return FOREIGN_IGNORED; k = find_slot(ctx->mpvec, map); if (k == -1) return FOREIGN_ERR; else vector_del_slot(ctx->mpvec, k); cleanup_nvme_map(map); return FOREIGN_OK; } int delete(struct context *ctx, struct udev_device *ud) { int rc; condlog(5, "%s called for \"%s\"", __func__, THIS); if (ud == NULL) return FOREIGN_ERR; lock(ctx); pthread_cleanup_push(unlock, ctx); rc = _delete_map(ctx, ud); pthread_cleanup_pop(1); if (rc == FOREIGN_OK) condlog(3, "%s: %s: map %s deleted", __func__, THIS, udev_device_get_sysname(ud)); else if (rc != FOREIGN_IGNORED) condlog(1, "%s: %s: retcode %d deleting map %s", __func__, THIS, rc, udev_device_get_sysname(ud)); return rc; } void check__(struct context *ctx) { struct gen_multipath *gm; int i; vector_foreach_slot(ctx->mpvec, gm, i) { struct nvme_map *map = gen_mp_to_nvme(gm); _find_controllers(ctx, map); } } void check(struct context *ctx) { condlog(4, "%s called for \"%s\"", __func__, THIS); lock(ctx); pthread_cleanup_push(unlock, ctx); check__(ctx); pthread_cleanup_pop(1); return; } /* * It's safe to pass our internal pointer, this is only used under the lock. */ const struct vector_s *get_multipaths(const struct context *ctx) { condlog(5, "%s called for \"%s\"", __func__, THIS); return ctx->mpvec; } void release_multipaths(__attribute__((unused)) const struct context *ctx, __attribute__((unused)) const struct vector_s *mpvec) { condlog(5, "%s called for \"%s\"", __func__, THIS); /* NOP */ } /* * It's safe to pass our internal pointer, this is only used under the lock. */ const struct vector_s * get_paths(const struct context *ctx) { vector paths = NULL; const struct gen_multipath *gm; int i; condlog(5, "%s called for \"%s\"", __func__, THIS); vector_foreach_slot(ctx->mpvec, gm, i) { const struct nvme_map *nm = const_gen_mp_to_nvme(gm); paths = vector_convert(paths, &nm->pgvec, struct nvme_pathgroup, nvme_pg_to_path); } return paths; } void release_paths(__attribute__((unused)) const struct context *ctx, const struct vector_s *mpvec) { condlog(5, "%s called for \"%s\"", __func__, THIS); vector_free_const(mpvec); } /* compile-time check whether all methods are present and correctly typed */ #define METHOD_INIT(x) .x = x static struct foreign __methods __attribute__((unused)) = { METHOD_INIT(init), METHOD_INIT(cleanup), METHOD_INIT(change), METHOD_INIT(delete), METHOD_INIT(delete_all), METHOD_INIT(check), METHOD_INIT(lock), METHOD_INIT(unlock), METHOD_INIT(get_multipaths), METHOD_INIT(release_multipaths), METHOD_INIT(get_paths), METHOD_INIT(release_paths), }; multipath-tools-0.11.1/libmultipath/generic.c000066400000000000000000000023251475246302400212260ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "generic.h" #include "structs.h" #include "util.h" #include "strbuf.h" int generic_style(const struct gen_multipath* gm, struct strbuf *buf, __attribute__((unused)) int verbosity) { STRBUF_ON_STACK(tmp); char *alias_buf __attribute__((cleanup(cleanup_charp))); const char *wwid_buf; gm->ops->snprint(gm, &tmp, 'n'); alias_buf = steal_strbuf_str(&tmp); gm->ops->snprint(gm, &tmp, 'w'); wwid_buf = get_strbuf_str(&tmp); return print_strbuf(buf, "%%n %s[%%G]:%%d %%s", strcmp(alias_buf, wwid_buf) ? "(%w) " : ""); } multipath-tools-0.11.1/libmultipath/generic.h000066400000000000000000000102671475246302400212370ustar00rootroot00000000000000/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GENERIC_H_INCLUDED #define GENERIC_H_INCLUDED #include "vector.h" /* * fieldwidth_t is required in print.h and foreign.h. * Defining it twice is not allowed before C11. * So do it here. */ typedef unsigned char fieldwidth_t; #define MAX_FIELD_WIDTH UCHAR_MAX struct strbuf; struct gen_multipath; struct gen_pathgroup; struct gen_path; /** * Methods implemented for gen_multipath "objects" */ struct gen_multipath_ops { /** * method: get_pathgroups(gmp) * caller is responsible to returned data using rel_pathgroups() * caller is also responsible to lock the gmp (directly or indirectly) * while working with the return value. * @param gmp: generic multipath object to act on * @returns a vector of const struct gen_pathgroup* */ const struct vector_s* (*get_pathgroups)(const struct gen_multipath*); /** * method: rel_pathgroups(gmp, v) * free data allocated by get_pathgroups(), if any * @param gmp: generic multipath object to act on * @param v the value returned by get_pathgroups() */ void (*rel_pathgroups)(const struct gen_multipath*, const struct vector_s*); /** * method: snprint(gmp, buf, len, wildcard) * prints the property of the multipath map matching * the passed-in wildcard character into "buf", * 0-terminated, no more than "len" characters including trailing '\0'. * * @param gmp: generic multipath object to act on * @param buf: output struct strbuf * @param wildcard: the multipath wildcard (see print.c) * @returns the number of characters printed (without trailing '\0'). */ int (*snprint)(const struct gen_multipath*, struct strbuf *buf, char wildcard); /** * method: style(gmp, buf, len, verbosity) * returns the format string to be used for the multipath object, * defined with the wildcards as defined in print.c * generic_style() should work well in most cases. * @param gmp: generic multipath object to act on * @param buf: output strbuf * @param verbosity: verbosity level * @returns number of format chars printed */ int (*style)(const struct gen_multipath*, struct strbuf *buf, int verbosity); }; /** * Methods implemented for gen_pathgroup "objects" */ struct gen_pathgroup_ops { /** * method: get_paths(gpg) * caller is responsible to returned data using rel_paths() * @param gpg: generic pathgroup object to act on * @returns a vector of const struct gen_path* */ const struct vector_s* (*get_paths)(const struct gen_pathgroup*); /** * method: rel_paths(gpg, v) * free data allocated by get_paths(), if any * @param gmp: generic pathgroup object to act on * @param v the value returned by get_paths() */ void (*rel_paths)(const struct gen_pathgroup*, const struct vector_s*); /** * Method snprint() * see gen_multipath_ops->snprint() above */ int (*snprint)(const struct gen_pathgroup*, struct strbuf *buf, char wildcard); }; struct gen_path_ops { /** * Method snprint() * see gen_multipath_ops->snprint() above */ int (*snprint)(const struct gen_path*, struct strbuf *buf, char wildcard); }; struct gen_multipath { const struct gen_multipath_ops *ops; }; struct gen_pathgroup { const struct gen_pathgroup_ops *ops; }; struct gen_path { const struct gen_path_ops *ops; }; /** * Helper functions for setting up the various generic_X_ops */ /** * generic_style() * A simple style() method (see above) that should fit most * foreign libraries. */ int generic_style(const struct gen_multipath*, struct strbuf *buf, int verbosity); #endif /* GENERIC_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/hwtable.c000066400000000000000000001002711475246302400212370ustar00rootroot00000000000000#include #include "checkers.h" #include "vector.h" #include "defaults.h" #include "structs.h" #include "config.h" #include "pgpolicies.h" #include "prio.h" #include "hwtable.h" /* * Tuning suggestions on these parameters should go to * (see README.md) * * You are welcome to claim maintainership over a controller * family. Please mail the currently enlisted maintainer and * the upstream package maintainer. * * Please, use the TEMPLATE below to add new hardware. * * WARNING: * * Devices with a proprietary handler must also be included in * the kernel side. Currently at drivers/scsi/scsi_dh.c * * Moreover, if a device needs a special treatment by the SCSI * subsystem it should be included in drivers/scsi/scsi_devinfo.c */ #if 0 /* * Copy this TEMPLATE to add new hardware. * * Keep only mandatory(.vendor and .product) and modified attributes. * Attributes with default values must be removed. * .vendor, .product, .revision and .bl_product are POSIX Extended regex. * * COMPANY_NAME * * Maintainer: NAME */ { /* Product Name */ .vendor = "VENDOR", .product = "PRODUCT", .revision = "REVISION", .bl_product = "BL_PRODUCT", .pgpolicy = FAILOVER, .uid_attribute = "ID_SERIAL", .selector = "service-time 0", .checker_name = TUR, .alias_prefix = "mpath", .features = "0", .hwhandler = "0", .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, .dev_loss = 600, .retain_hwhandler = RETAIN_HWHANDLER_ON, .detect_prio = DETECT_PRIO_ON, .detect_checker = DETECT_CHECKER_ON, .detect_pgpolicy = DETECT_PGPOLICY_ON, .detect_pgpolicy_use_tpg = DETECT_PGPOLICY_USE_TPG_OFF, .deferred_remove = DEFERRED_REMOVE_OFF, .delay_watch_checks = DELAY_CHECKS_OFF, .delay_wait_checks = DELAY_CHECKS_OFF, .skip_kpartx = SKIP_KPARTX_OFF, .max_sectors_kb = MAX_SECTORS_KB_UNDEF, .ghost_delay = GHOST_DELAY_OFF, }, #endif static struct hwentry default_hw[] = { /* * Generic NVMe devices * * Due to the parsing logic in find_hwe(), generic entries * have to be put on top of this list, and more specific ones * below. */ { /* Generic NVMe */ .vendor = "NVM[eE]", .product = ".*", .uid_attribute = DEFAULT_NVME_UID_ATTRIBUTE, .checker_name = NONE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, }, /* * Apple * * Maintainer: Shyam Sundar */ { /* Xserve RAID */ .vendor = "APPLE", .product = "Xserve RAID", .pgpolicy = MULTIBUS, }, /* * HPE */ { /* 3PAR / Primera / Alletra 9000 */ .vendor = "3PARdata", .product = "VV", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 18, .fast_io_fail = 10, .dev_loss = MAX_DEV_LOSS_TMO, .vpd_vendor_id = VPD_VP_HP3PAR, }, { // Alletra 9000 NVMe / Alletra Storage MP // GreenLake Block Storage MP .vendor = "NVME", .product = "HPE Alletra", .no_path_retry = NO_PATH_RETRY_QUEUE, }, { /* RA8000 / ESA12000 */ .vendor = "DEC", .product = "HSG80", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .checker_name = HP_SW, .prio_name = PRIO_HP_SW, }, { /* VIRTUAL ARRAY 7400 */ .vendor = "HP", .product = "A6189A", .pgpolicy = MULTIBUS, .no_path_retry = 12, }, { /* MSA 1000/1500 and EVA 3000/5000, with old firmware */ .vendor = "(COMPAQ|HP)", .product = "(MSA|HSV)1[01]0", .pgpolicy = GROUP_BY_PRIO, .no_path_retry = 12, .checker_name = HP_SW, .prio_name = PRIO_HP_SW, }, { /* MSA 1000/1500 with new firmware */ .vendor = "(COMPAQ|HP)", .product = "MSA VOLUME", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, }, { /* EVA 3000/5000 with new firmware, EVA 4000/6000/8000 */ .vendor = "(COMPAQ|HP)", .product = "(HSV1[01]1|HSV2[01]0|HSV3[046]0|HSV4[05]0)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, }, { /* MSA2000 family with old firmware */ .vendor = "HP", .product = "(MSA2[02]12fc|MSA2012i)", .pgpolicy = MULTIBUS, .no_path_retry = 18, }, { /* MSA2000 family with new firmware */ .vendor = "HP", .product = "(MSA2012sa|MSA23(12|24)(fc|i|sa)|MSA2000s VOLUME)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 18, .prio_name = PRIO_ALUA, }, { /* MSA 1040, 1050, 1060, 2040, 2050, 2060 and 2070 families */ .vendor = "(HP|HPE)", .product = "MSA [12]0[4567]0 (SAN|SAS|FC|iSCSI)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 18, .prio_name = PRIO_ALUA, }, { /* SAN Virtualization Services Platform */ .vendor = "HP", .product = "(HSVX700|HSVX740)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, }, { /* Smart Array */ .vendor = "HP", .product = "LOGICAL VOLUME", .pgpolicy = MULTIBUS, .no_path_retry = 12, }, { /* P2000 family */ .vendor = "HP", .product = "(P2000 G3 FC|P2000G3 FC/iSCSI|P2000 G3 SAS|P2000 G3 iSCSI)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 18, .prio_name = PRIO_ALUA, }, { /* StoreVirtual 4000 and 3200 families */ .vendor = "LEFTHAND", .product = "(P4000|iSCSIDisk|FCDISK)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 18, .prio_name = PRIO_ALUA, }, { /* Nimble Storage / HPE Alletra 5000/6000 */ .vendor = "Nimble", .product = "Server", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = NO_PATH_RETRY_QUEUE, }, /* SGI */ { /* Total Performance 9100 */ .vendor = "SGI", .product = "TP9100", .pgpolicy = MULTIBUS, }, { /* Total Performance family */ .vendor = "SGI", .product = "TP9[3457]00", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* (RDAC) InfiniteStorage */ .vendor = "SGI", .product = "IS", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* (DDN) InfiniteStorage */ .vendor = "SGI", .product = "^DD[46]A-", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * DataDirect Networks */ { /* SAN DataDirector */ .vendor = "DDN", .product = "SAN DataDirector", .pgpolicy = MULTIBUS, }, { /* EF3010 */ .vendor = "DDN", .product = "^EF3010", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, { /* EF3015 / S2A and SFA families */ .vendor = "DDN", .product = "^(EF3015|S2A|SFA)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, { /* * Nexenta COMSTAR * * Maintainer: Yacine Kheddache */ .vendor = "NEXENTA", .product = "COMSTAR", .pgpolicy = GROUP_BY_SERIAL, .no_path_retry = 30, }, { /* Tegile IntelliFlash */ .vendor = "TEGILE", .product = "(ZEBI-(FC|ISCSI)|INTELLIFLASH)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 10, }, /* * Dell EMC */ { /* Symmetrix / DMX / VMAX / PowerMax */ .vendor = "EMC", .product = "SYMMETRIX", .pgpolicy = MULTIBUS, .no_path_retry = 6, }, { /* PowerMax NVMe */ .vendor = "NVME", .product = "EMC PowerMax", .no_path_retry = NO_PATH_RETRY_QUEUE, }, { /* DGC CLARiiON CX/AX / VNX and Unity */ .vendor = "^DGC", .product = "^(RAID|DISK|VRAID)", .bl_product = "LUNZ", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = (300 / DEFAULT_CHECKINT), .checker_name = EMC_CLARIION, .prio_name = PRIO_EMC, .detect_checker = DETECT_CHECKER_OFF, }, { /* Invista / VPLEX */ .vendor = "EMC", .product = "Invista", .bl_product = "LUNZ", .pgpolicy = MULTIBUS, .no_path_retry = 5, }, { /* XtremIO */ .vendor = "XtremIO", .product = "XtremApp", .pgpolicy = MULTIBUS, }, { /* SC Series (formerly Compellent) */ .vendor = "COMPELNT", .product = "Compellent Vol", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = NO_PATH_RETRY_QUEUE, }, { /* MD Series */ .vendor = "DELL", .product = "^MD3", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* PowerStore */ .vendor = "DellEMC", .product = "PowerStore", .pgpolicy = GROUP_BY_PRIO, .prio_name = PRIO_ALUA, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 3, .fast_io_fail = 15, }, { /* PowerStore NVMe */ .vendor = ".*", .product = "dellemc-powerstore", .no_path_retry = 3, }, { /* PowerVault ME 4/5 families */ .vendor = "DellEMC", .product = "^ME", .pgpolicy = GROUP_BY_PRIO, .prio_name = PRIO_ALUA, .pgfailback = -FAILBACK_IMMEDIATE, }, /* * Fujitsu */ { /* CentricStor Virtual Tape */ .vendor = "FSC", .product = "CentricStor", .pgpolicy = GROUP_BY_SERIAL, }, { /* ETERNUS family */ .vendor = "FUJITSU", .product = "ETERNUS_DX(H|L|M|400|8000)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 10, .prio_name = PRIO_ALUA, }, { /* FibreCAT S80 */ .vendor = "(EUROLOGC|EuroLogc)", .product = "FC2502", .pgpolicy = MULTIBUS, }, { /* ETERNUS 2000, 3000 and 4000 */ .vendor = "FUJITSU", .product = "E[234]000", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 10, .prio_name = PRIO_ALUA, }, { /* ETERNUS 6000 and 8000 */ .vendor = "FUJITSU", .product = "E[68]000", .pgpolicy = MULTIBUS, .no_path_retry = 10, }, { /* * ETERNUS AB/HB * * Maintainer: NetApp RDAC team */ .vendor = "FUJITSU", .product = "ETERNUS_AHB", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, /* * Hitachi Vantara * * Maintainer: Matthias Rudolph */ { /* USP-V, HUS VM, VSP, VSP G1X00 and VSP GX00 families / HPE XP */ .vendor = "(HITACHI|HP|HPE)", .product = "^OPEN-", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 10, }, { /* AMS other than AMS 2000 */ .vendor = "HITACHI", .product = "^DF", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_HDS, }, { /* AMS 2000 and HUS 100 families */ .vendor = "HITACHI", .product = "^DF600F", .pgpolicy = MULTIBUS, }, /* * IBM */ { /* ProFibre 4000R */ .vendor = "IBM", .product = "ProFibre 4000R", .pgpolicy = MULTIBUS, }, { /* DS4300 / FAStT600 */ .vendor = "IBM", .product = "^1722-600", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS4100 / FAStT100 */ .vendor = "IBM", .product = "^1724", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS3000 / DS3200 / DS3300 / DS3400 / Boot DS */ .vendor = "IBM", .product = "^1726", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS4400 / DS4500 / FAStT700 / FAStT900 */ .vendor = "IBM", .product = "^1742", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS3500 / DS3512 / DS3524 */ .vendor = "IBM", .product = "^1746", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DCS3860 */ .vendor = "IBM", .product = "^1813", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS3950 / DS4200 / DS4700 / DS5020 */ .vendor = "IBM", .product = "^1814", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS4800 */ .vendor = "IBM", .product = "^1815", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* DS5000 / DS5100 / DS5300 / DCS3700 */ .vendor = "IBM", .product = "^1818", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* Netfinity Fibre Channel RAID Controller Unit */ .vendor = "IBM", .product = "^3526", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* FAStT200 and FAStT500 */ .vendor = "IBM", .product = "^(3542|3552)", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* Enterprise Storage Server(ESS) / Shark family */ .vendor = "IBM", .product = "^2105", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = MULTIBUS, }, { /* DS6000 / DS6800 */ .vendor = "IBM", .product = "^1750500", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, { /* DS8000 family */ .vendor = "IBM", .product = "^2107900", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, }, { // Storwize V5000/V7000 lines / SAN Volume Controller (SVC) // Flex System V7000 / FlashSystem V840/V9000 and 5x00/7x00/9x00 .vendor = "IBM", .product = "^2145", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, { /* FlashSystem(Storwize/SVC) NVMe */ .vendor = "NVME", .product = "IBM[ ]+2145", .no_path_retry = NO_PATH_RETRY_QUEUE, }, { /* PAV DASD ECKD */ .vendor = "IBM", .product = "S/390 DASD ECKD", .bl_product = "S/390", .uid_attribute = "ID_UID", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = MULTIBUS, .checker_name = DIRECTIO, }, { /* PAV DASD FBA */ .vendor = "IBM", .product = "S/390 DASD FBA", .bl_product = "S/390", .uid_attribute = "ID_UID", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = MULTIBUS, .checker_name = DIRECTIO, }, { /* Power RAID */ .vendor = "IBM", .product = "^IPR", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, { /* SAS RAID Controller Module (RSSM) */ .vendor = "IBM", .product = "1820N00", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = NO_PATH_RETRY_QUEUE, .prio_name = PRIO_ALUA, }, { /* XIV Storage System / FlashSystem A9000/A9000R */ .vendor = "(XIV|IBM)", .product = "(NEXTRA|2810XIV)", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = 15, }, { /* TMS RamSan / FlashSystem 710/720/810/820/840/900 */ .vendor = "(TMS|IBM)", .product = "(RamSan|FlashSystem)", .pgpolicy = MULTIBUS, }, { /* FlashSystem(RamSan) NVMe */ .vendor = "NVMe", .product = "FlashSystem", .no_path_retry = NO_PATH_RETRY_FAIL, }, { /* (DDN) DCS9900, SONAS 2851-DR1 */ .vendor = "IBM", .product = "^(DCS9900|2851)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * IBM Power Virtual SCSI Devices * * Maintainer: Brian King */ { /* AIX VDASD */ .vendor = "AIX", .product = "VDASD", .pgpolicy = MULTIBUS, .no_path_retry = (300 / DEFAULT_CHECKINT), }, { /* 3303 NVDISK */ .vendor = "IBM", .product = "3303[ ]+NVDISK", .no_path_retry = (300 / DEFAULT_CHECKINT), }, { /* AIX NVDISK */ .vendor = "AIX", .product = "NVDISK", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = (300 / DEFAULT_CHECKINT), .prio_name = PRIO_ALUA, }, /* * Lenovo */ { /* * DE Series * * Maintainer: NetApp RDAC team */ .vendor = "LENOVO", .product = "DE_Series", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, /* * NetApp */ { /* * ONTAP FAS/AFF/ASA Series * * Maintainer: Martin George */ .vendor = "NETAPP", .product = "LUN", .features = "2 pg_init_retries 50", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .flush_on_last_del = FLUSH_ALWAYS, .dev_loss = MAX_DEV_LOSS_TMO, .prio_name = PRIO_ONTAP, .user_friendly_names = USER_FRIENDLY_NAMES_OFF, }, { /* * SANtricity(RDAC) E/EF Series * * Maintainer: NetApp RDAC team */ .vendor = "(NETAPP|LSI|ENGENIO)", .product = "INF-01-00", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* * SolidFir family * * Maintainer: PJ Waskiewicz */ .vendor = "SolidFir", .product = "SSD SAN", .pgpolicy = MULTIBUS, .no_path_retry = 24, }, { /* ONTAP NVMe */ .vendor = "NVME", .product = "^NetApp ONTAP Controller", .no_path_retry = NO_PATH_RETRY_QUEUE, }, /* * NEC */ { /* M-Series */ .vendor = "NEC", .product = "DISK ARRAY", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, /* * Oracle */ /* * Pillar Data / Oracle FS * * Maintainer: Srinivasan Ramani */ { /* Axiom */ .vendor = "^Pillar", .product = "^Axiom", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, { /* FS */ .vendor = "^Oracle", .product = "^Oracle FS", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, /* Sun - StorageTek */ { /* B210, B220, B240 and B280 */ .vendor = "STK", .product = "BladeCtlr", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* 9176, D173, D178, D210, D220, D240 and D280 */ .vendor = "STK", .product = "OPENstorage", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* 6540 */ .vendor = "STK", .product = "FLEXLINE 380", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* (Dot Hill) 3120, 3310, 3320, 3510 and 3511 */ .vendor = "SUN", .product = "StorEdge 3", .pgpolicy = MULTIBUS, }, { /* 6580 and 6780 */ .vendor = "SUN", .product = "STK6580_6780", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* 6130 / 6140 */ .vendor = "SUN", .product = "CSM[12]00_R", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* 2500 / 2510 / 2530 / 2540 */ .vendor = "SUN", .product = "LCSM100_[IEFS]", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* 6180 */ .vendor = "SUN", .product = "SUN_6180", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* ArrayStorage */ .vendor = "SUN", .product = "ArrayStorage", .bl_product = "Universal Xport", .pgpolicy = GROUP_BY_PRIO, .checker_name = RDAC, .features = "2 pg_init_retries 50", .prio_name = PRIO_RDAC, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 30, }, { /* ZFS Storage Appliances */ .vendor = "SUN", .product = "(Sun Storage|ZFS Storage|COMSTAR)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * Pivot3 * * Maintainer: Bart Brooks */ { /* Raige */ .vendor = "PIVOT3", .product = "RAIGE VOLUME", .no_path_retry = NO_PATH_RETRY_QUEUE, .pgpolicy = MULTIBUS, }, { /* NexGen / vSTAC */ .vendor = "(NexGen|Pivot3)", .product = "(TierStore|vSTAC)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = NO_PATH_RETRY_QUEUE, }, /* * Intel */ { /* Multi-Flex */ .vendor = "(Intel|INTEL)", .product = "Multi-Flex", .bl_product = "VTrak V-LUN", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = NO_PATH_RETRY_QUEUE, .prio_name = PRIO_ALUA, }, /* * Linux */ { /* Linux-IO (LIO) Target */ .vendor = "(LIO-ORG|SUSE)", .product = ".*", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, .checker_name = DIRECTIO, .detect_checker = DETECT_CHECKER_OFF, }, { /* Generic SCSI Target Subsystem for Linux (SCST) */ .vendor = "^SCST_", .product = ".*", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, }, /* * DataCore */ { /* SANmelody */ .vendor = "DataCore", .product = "SANmelody", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = NO_PATH_RETRY_QUEUE, .prio_name = PRIO_ALUA, }, { /* SANsymphony */ .vendor = "DataCore", .product = "Virtual Disk", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = NO_PATH_RETRY_QUEUE, .prio_name = PRIO_ALUA, }, /* * Pure Storage */ { /* FlashArray family */ .vendor = "PURE", .product = "FlashArray", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .detect_prio = DETECT_PRIO_OFF, .fast_io_fail = 10, .max_sectors_kb = 4096, }, { /* FlashArray NVMe */ .vendor = "NVME", .product = "Pure Storage FlashArray", .no_path_retry = 10, }, /* * Huawei */ { /* Older than OceanStor V3 */ .vendor = "^(HUAWEI|HUASY|HS)", .product = "^(Dorado[25]|HVS8|S[23568]|V1[568]|VIS6000)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 15, }, { /* OceanStor V3 or better */ .vendor = "^(HUAWEI|AnyStor|Marstor|NETPOSA|SanM|SUGON|UDsafe)", .product = "XSG1", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 15, }, { /* OceanStor NVMe */ .vendor = "NVM[eE]", .product = "Huawei-XSG1", .checker_name = DIRECTIO, .no_path_retry = 12, }, /* * Kove */ { /* XPD */ .vendor = "KOVE", .product = "XPD", .pgpolicy = MULTIBUS, }, /* * Infinidat * * Maintainer: Arnon Yaari */ { /* InfiniBox */ .vendor = "NFINIDAT", .product = "InfiniBox", .pgpolicy = GROUP_BY_PRIO, .pgfailback = 30, .prio_name = PRIO_ALUA, .selector = "round-robin 0", .rr_weight = RR_WEIGHT_PRIO, .no_path_retry = NO_PATH_RETRY_FAIL, .minio = 1, .minio_rq = 1, .fast_io_fail = 15, }, /* * Kaminario */ { /* K2 */ .vendor = "KMNRIO", .product = "K2", .pgpolicy = MULTIBUS, }, /* * StorCentric */ /* Nexsan */ { /* E-Series */ .vendor = "NEXSAN", .product = "NXS-B0", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 15, }, { /* SATABeast / SATABoy */ .vendor = "NEXSAN", .product = "SATAB", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 15, }, { /* NST / UNITY */ .vendor = "Nexsan", .product = "(NestOS|NST5000)", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* Violin Systems */ { /* 3000 / 6000 Series */ .vendor = "VIOLIN", .product = "SAN ARRAY$", .pgpolicy = GROUP_BY_SERIAL, .no_path_retry = 30, }, { /* 3000 / 6000 Series (ALUA mode) */ .vendor = "VIOLIN", .product = "SAN ARRAY ALUA", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, { /* FSP 7000 family */ .vendor = "VIOLIN", .product = "CONCERTO ARRAY", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, /* Xiotech */ { /* Intelligent Storage Elements family */ .vendor = "(XIOTECH|XIOtech)", .product = "ISE", .pgpolicy = MULTIBUS, .no_path_retry = 12, }, { /* iglu blaze family */ .vendor = "(XIOTECH|XIOtech)", .product = "IGLU DISK", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, { /* Magnitude family */ .vendor = "(XIOTECH|XIOtech)", .product = "Magnitude", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, /* Vexata */ { /* VX */ .vendor = "Vexata", .product = "VX", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, /* * Promise Technology */ { /* VTrak family */ .vendor = "Promise", .product = "VTrak", .bl_product = "VTrak V-LUN", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, { /* Vess family */ .vendor = "Promise", .product = "Vess", .bl_product = "Vess V-LUN", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * Infortrend Technology */ { /* EonStor / ESVA */ .vendor = "^IFT", .product = ".*", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * Seagate Technology (Dot Hill Systems) */ { /* SANnet family */ .vendor = "DotHill", .product = "SANnet", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, { /* R/Evolution family */ .vendor = "DotHill", .product = "R/Evo", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, { /* AssuredSAN family */ .vendor = "DotHill", .product = "^DH", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * AccelStor */ { /* NeoSapphire */ .vendor = "AStor", .product = "NeoSapphire", .pgpolicy = MULTIBUS, .no_path_retry = 30, }, /* * INSPUR */ { /* AS5300/AS5500 G2 */ .vendor = "INSPUR", .product = "MCS", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, /* * MacroSAN Technologies */ { /* MS family */ .vendor = "MacroSAN", .product = "LU", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, .no_path_retry = 30, }, /* * EOL */ { /* NULL */ .vendor = NULL, .product = NULL, }, }; int setup_default_hwtable(vector hw) { int r = 0; struct hwentry * hwe = default_hw; while (hwe->vendor) { r += store_hwe(hw, hwe); hwe++; } return r; } multipath-tools-0.11.1/libmultipath/hwtable.h000066400000000000000000000001771475246302400212500ustar00rootroot00000000000000#ifndef HWTABLE_H_INCLUDED #define HWTABLE_H_INCLUDED int setup_default_hwtable (vector hw); #endif /* HWTABLE_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/io_err_stat.c000066400000000000000000000441051475246302400221260ustar00rootroot00000000000000/* * (C) Copyright HUAWEI Technology Corp. 2017, All Rights Reserved. * * io_err_stat.c * version 1.0 * * IO error stream statistic process for path failure event from kernel * * Author(s): Guan Junxiong 2017 * * This file is released under the GPL version 2, or any later version. */ #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "checkers.h" #include "config.h" #include "structs.h" #include "structs_vec.h" #include "devmapper.h" #include "debug.h" #include "lock.h" #include "time-util.h" #include "io_err_stat.h" #include "util.h" #define TIMEOUT_NO_IO_NSEC 10000000 /*10ms = 10000000ns*/ #define FLAKY_PATHFAIL_THRESHOLD 2 #define CONCUR_NR_EVENT 32 #define NR_IOSTAT_PATHS 32 #define PATH_IO_ERR_IN_CHECKING -1 #define PATH_IO_ERR_WAITING_TO_CHECK -2 #define io_err_stat_log(prio, fmt, args...) \ condlog(prio, "io error statistic: " fmt, ##args) struct dio_ctx { struct timespec io_starttime; unsigned int blksize; void *buf; struct iocb io; }; struct io_err_stat_path { char devname[FILE_NAME_SIZE]; int fd; struct dio_ctx *dio_ctx_array; int io_err_nr; int io_nr; struct timespec start_time; int total_time; int err_rate_threshold; }; static pthread_t io_err_stat_thr; static pthread_mutex_t io_err_thread_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t io_err_thread_cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t io_err_pathvec_lock = PTHREAD_MUTEX_INITIALIZER; static int io_err_thread_running = 0; static vector io_err_pathvec; struct vectors *vecs; io_context_t ioctx; static void cancel_inflight_io(struct io_err_stat_path *pp); static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } static struct io_err_stat_path *find_err_path_by_dev(vector pathvec, char *dev) { int i; struct io_err_stat_path *pp; if (!pathvec) return NULL; vector_foreach_slot(pathvec, pp, i) if (!strcmp(pp->devname, dev)) return pp; io_err_stat_log(4, "%s: not found in check queue", dev); return NULL; } static int init_each_dio_ctx(struct dio_ctx *ct, int blksize, unsigned long pgsize) { ct->blksize = blksize; if (posix_memalign(&ct->buf, pgsize, blksize)) return 1; memset(ct->buf, 0, blksize); ct->io_starttime.tv_sec = 0; ct->io_starttime.tv_nsec = 0; return 0; } static int deinit_each_dio_ctx(struct dio_ctx *ct) { if (!ct->buf) return 0; if (ct->io_starttime.tv_sec != 0 || ct->io_starttime.tv_nsec != 0) return 1; free(ct->buf); return 0; } static int setup_directio_ctx(struct io_err_stat_path *p) { unsigned long pgsize = getpagesize(); char fpath[PATH_MAX]; unsigned int blksize = 0; int i; if (snprintf(fpath, PATH_MAX, "/dev/%s", p->devname) >= PATH_MAX) return 1; if (p->fd < 0) p->fd = open(fpath, O_RDONLY | O_DIRECT); if (p->fd < 0) return 1; p->dio_ctx_array = calloc(1, sizeof(struct dio_ctx) * CONCUR_NR_EVENT); if (!p->dio_ctx_array) goto fail_close; if (ioctl(p->fd, BLKBSZGET, &blksize) < 0) { io_err_stat_log(4, "%s:cannot get blocksize, set default 512", p->devname); blksize = 512; } if (!blksize) goto free_pdctx; for (i = 0; i < CONCUR_NR_EVENT; i++) { if (init_each_dio_ctx(p->dio_ctx_array + i, blksize, pgsize)) goto deinit; } return 0; deinit: for (i = 0; i < CONCUR_NR_EVENT; i++) deinit_each_dio_ctx(p->dio_ctx_array + i); free_pdctx: free(p->dio_ctx_array); p->dio_ctx_array = NULL; fail_close: close(p->fd); return 1; } static void free_io_err_stat_path(struct io_err_stat_path *p) { int i; int inflight = 0; if (!p) return; if (!p->dio_ctx_array) goto free_path; for (i = 0; i < CONCUR_NR_EVENT; i++) inflight += deinit_each_dio_ctx(p->dio_ctx_array + i); if (!inflight) free(p->dio_ctx_array); else io_err_stat_log(2, "%s: can't free aio space of %s, %d IOs in flight", __func__, p->devname, inflight); if (p->fd > 0) close(p->fd); free_path: free(p); } static struct io_err_stat_path *alloc_io_err_stat_path(void) { struct io_err_stat_path *p; p = (struct io_err_stat_path *)calloc(1, sizeof(*p)); if (!p) return NULL; memset(p->devname, 0, sizeof(p->devname)); p->io_err_nr = 0; p->io_nr = 0; p->total_time = 0; p->start_time.tv_sec = 0; p->start_time.tv_nsec = 0; p->err_rate_threshold = 0; p->fd = -1; return p; } static void free_io_err_pathvec(void) { struct io_err_stat_path *path; int i; pthread_mutex_lock(&io_err_pathvec_lock); pthread_cleanup_push(cleanup_mutex, &io_err_pathvec_lock); if (!io_err_pathvec) goto out; /* io_cancel() is a noop, but maybe in the future it won't be */ vector_foreach_slot(io_err_pathvec, path, i) { if (path && path->dio_ctx_array) cancel_inflight_io(path); } /* This blocks until all I/O is finished */ io_destroy(ioctx); vector_foreach_slot(io_err_pathvec, path, i) free_io_err_stat_path(path); vector_free(io_err_pathvec); io_err_pathvec = NULL; out: pthread_cleanup_pop(1); } /* * return value * 0: enqueue OK * 1: fails because of internal error */ static int enqueue_io_err_stat_by_path(struct path *path) { struct io_err_stat_path *p; pthread_mutex_lock(&io_err_pathvec_lock); p = find_err_path_by_dev(io_err_pathvec, path->dev); if (p) { pthread_mutex_unlock(&io_err_pathvec_lock); return 0; } pthread_mutex_unlock(&io_err_pathvec_lock); p = alloc_io_err_stat_path(); if (!p) return 1; memcpy(p->devname, path->dev, sizeof(p->devname)); p->total_time = path->mpp->marginal_path_err_sample_time; p->err_rate_threshold = path->mpp->marginal_path_err_rate_threshold; if (setup_directio_ctx(p)) goto free_ioerr_path; pthread_mutex_lock(&io_err_pathvec_lock); if (!vector_alloc_slot(io_err_pathvec)) goto unlock_pathvec; vector_set_slot(io_err_pathvec, p); pthread_mutex_unlock(&io_err_pathvec_lock); io_err_stat_log(3, "%s: enqueue path %s to check", path->mpp->alias, path->dev); return 0; unlock_pathvec: pthread_mutex_unlock(&io_err_pathvec_lock); free_ioerr_path: free_io_err_stat_path(p); return 1; } int io_err_stat_handle_pathfail(struct path *path) { struct timespec curr_time; if (uatomic_read(&io_err_thread_running) == 0) return 0; if (path->io_err_disable_reinstate) { io_err_stat_log(3, "%s: reinstate is already disabled", path->dev); return 0; } if (path->io_err_pathfail_cnt < 0) return 0; if (!path->mpp) return 0; if (!marginal_path_check_enabled(path->mpp)) return 0; /* * The test should only be started for paths that have failed * repeatedly in a certain time frame, so that we have reason * to assume they're flaky. Without bother the admin to configure * the repeated count threshold and time frame, we assume a path * which fails at least twice within 60 seconds is flaky. */ get_monotonic_time(&curr_time); if (path->io_err_pathfail_cnt == 0) { path->io_err_pathfail_cnt++; path->io_err_pathfail_starttime = curr_time.tv_sec; io_err_stat_log(5, "%s: start path flakiness pre-checking", path->dev); return 0; } if ((curr_time.tv_sec - path->io_err_pathfail_starttime) > path->mpp->marginal_path_double_failed_time) { path->io_err_pathfail_cnt = 0; path->io_err_pathfail_starttime = curr_time.tv_sec; io_err_stat_log(5, "%s: restart path flakiness pre-checking", path->dev); } path->io_err_pathfail_cnt++; if (path->io_err_pathfail_cnt >= FLAKY_PATHFAIL_THRESHOLD) { path->io_err_disable_reinstate = 1; path->io_err_pathfail_cnt = PATH_IO_ERR_WAITING_TO_CHECK; /* enqueue path as soon as it comes up */ path->io_err_dis_reinstate_time = 0; if (path->state != PATH_DOWN) { struct config *conf; int oldstate = path->state; unsigned int checkint; conf = get_multipath_config(); checkint = conf->checkint; put_multipath_config(conf); io_err_stat_log(2, "%s: mark as failed", path->dev); path->mpp->stat_path_failures++; path->state = PATH_DOWN; path->dmstate = PSTATE_FAILED; if (oldstate == PATH_UP || oldstate == PATH_GHOST) update_queue_mode_del_path(path->mpp); if (path->tick > checkint) path->tick = checkint; } } return 0; } int need_io_err_check(struct path *pp) { struct timespec curr_time; int r; if (uatomic_read(&io_err_thread_running) == 0) return 0; if (count_active_paths(pp->mpp) <= 0) { io_err_stat_log(2, "%s: no paths. recovering early", pp->dev); goto recover; } if (pp->io_err_pathfail_cnt != PATH_IO_ERR_WAITING_TO_CHECK) return 1; get_monotonic_time(&curr_time); if ((curr_time.tv_sec - pp->io_err_dis_reinstate_time) > pp->mpp->marginal_path_err_recheck_gap_time || pp->io_err_dis_reinstate_time == 0) { io_err_stat_log(4, "%s: reschedule checking after %d seconds", pp->dev, pp->mpp->marginal_path_err_recheck_gap_time); r = enqueue_io_err_stat_by_path(pp); /* * Enqueue fails because of internal error. * In this case , we recover this path * Or else, return 1 to set path state to PATH_SHAKY */ if (r == 1) { io_err_stat_log(2, "%s: enqueue failed. recovering early", pp->dev); goto recover; } else pp->io_err_pathfail_cnt = PATH_IO_ERR_IN_CHECKING; } return 1; recover: pp->io_err_pathfail_cnt = 0; pp->io_err_disable_reinstate = 0; return 0; } static void account_async_io_state(struct io_err_stat_path *pp, int rc) { switch (rc) { case PATH_DOWN: case PATH_TIMEOUT: pp->io_err_nr++; break; case PATH_UNCHECKED: case PATH_UP: case PATH_PENDING: break; default: break; } } static int io_err_stat_time_up(struct io_err_stat_path *pp) { struct timespec currtime, difftime; get_monotonic_time(&currtime); timespecsub(&currtime, &pp->start_time, &difftime); if (difftime.tv_sec < pp->total_time) return 0; return 1; } static void end_io_err_stat(struct io_err_stat_path *pp) { struct timespec currtime; struct path *path; double err_rate; get_monotonic_time(&currtime); io_err_stat_log(4, "%s: check end", pp->devname); err_rate = pp->io_nr == 0 ? 0 : (pp->io_err_nr * 1000.0f) / pp->io_nr; io_err_stat_log(3, "%s: IO error rate (%.1f/1000)", pp->devname, err_rate); pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); path = find_path_by_dev(vecs->pathvec, pp->devname); if (!path) { io_err_stat_log(4, "path %s not found'", pp->devname); } else if (err_rate <= pp->err_rate_threshold) { path->io_err_pathfail_cnt = 0; path->io_err_disable_reinstate = 0; io_err_stat_log(3, "%s: (%d/%d) good to enable reinstating", pp->devname, pp->io_err_nr, pp->io_nr); /* * schedule path check as soon as possible to * update path state. Do NOT reinstate dm path here */ path->tick = 1; } else if (path->mpp && count_active_paths(path->mpp) > 0) { io_err_stat_log(3, "%s: keep failing the dm path %s", path->mpp->alias, path->dev); path->io_err_pathfail_cnt = PATH_IO_ERR_WAITING_TO_CHECK; path->io_err_disable_reinstate = 1; path->io_err_dis_reinstate_time = currtime.tv_sec; io_err_stat_log(3, "%s: disable reinstating of %s", path->mpp->alias, path->dev); } else { path->io_err_pathfail_cnt = 0; path->io_err_disable_reinstate = 0; io_err_stat_log(3, "%s: there is orphan path, enable reinstating", pp->devname); } lock_cleanup_pop(vecs->lock); } static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) { int rc; if (ct->io_starttime.tv_nsec == 0 && ct->io_starttime.tv_sec == 0) { struct iocb *ios[1] = { &ct->io }; get_monotonic_time(&ct->io_starttime); io_prep_pread(&ct->io, fd, ct->buf, ct->blksize, 0); if ((rc = io_submit(ioctx, 1, ios)) != 1) { io_err_stat_log(2, "%s: io_submit error %s", dev, strerror(-rc)); return -1; } return 0; } return -1; } static void send_batch_async_ios(struct io_err_stat_path *pp) { int i; struct dio_ctx *ct; struct timespec currtime, difftime; get_monotonic_time(&currtime); /* * Give a free time for all IO to complete or timeout */ if (pp->start_time.tv_sec != 0) { timespecsub(&currtime, &pp->start_time, &difftime); if (difftime.tv_sec + IOTIMEOUT_SEC >= pp->total_time) return; } for (i = 0; i < CONCUR_NR_EVENT; i++) { ct = pp->dio_ctx_array + i; if (!send_each_async_io(ct, pp->fd, pp->devname)) pp->io_nr++; } if (pp->start_time.tv_sec == 0 && pp->start_time.tv_nsec == 0) get_monotonic_time(&pp->start_time); } static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, char *dev) { struct timespec difftime; struct io_event event; int rc = PATH_UNCHECKED; int r; if (ct->io_starttime.tv_sec == 0 && ct->io_starttime.tv_nsec == 0) return rc; timespecsub(t, &ct->io_starttime, &difftime); if (difftime.tv_sec > IOTIMEOUT_SEC) { struct iocb *ios[1] = { &ct->io }; io_err_stat_log(5, "%s: abort check on timeout", dev); r = io_cancel(ioctx, ios[0], &event); if (r) io_err_stat_log(5, "%s: io_cancel error %s", dev, strerror(-r)); rc = PATH_TIMEOUT; } else { rc = PATH_PENDING; } return rc; } static void poll_async_io_timeout(void) { struct io_err_stat_path *pp; struct timespec curr_time; int rc = PATH_UNCHECKED; int i, j; get_monotonic_time(&curr_time); vector_foreach_slot(io_err_pathvec, pp, i) { for (j = 0; j < CONCUR_NR_EVENT; j++) { rc = try_to_cancel_timeout_io(pp->dio_ctx_array + j, &curr_time, pp->devname); account_async_io_state(pp, rc); } } } static void cancel_inflight_io(struct io_err_stat_path *pp) { struct io_event event; int i; for (i = 0; i < CONCUR_NR_EVENT; i++) { struct dio_ctx *ct = pp->dio_ctx_array + i; struct iocb *ios[1] = { &ct->io }; if (ct->io_starttime.tv_sec == 0 && ct->io_starttime.tv_nsec == 0) continue; io_err_stat_log(5, "%s: abort infligh io", pp->devname); io_cancel(ioctx, ios[0], &event); } } static inline int handle_done_dio_ctx(struct dio_ctx *ct, struct io_event *ev) { ct->io_starttime.tv_sec = 0; ct->io_starttime.tv_nsec = 0; return (ev->res == ct->blksize) ? PATH_UP : PATH_DOWN; } static void handle_async_io_done_event(struct io_event *io_evt) { struct io_err_stat_path *pp; struct dio_ctx *ct; int rc = PATH_UNCHECKED; int i, j; vector_foreach_slot(io_err_pathvec, pp, i) { for (j = 0; j < CONCUR_NR_EVENT; j++) { ct = pp->dio_ctx_array + j; if (&ct->io == io_evt->obj) { rc = handle_done_dio_ctx(ct, io_evt); account_async_io_state(pp, rc); return; } } } } static void process_async_ios_event(int timeout_nsecs, char *dev) { struct io_event events[CONCUR_NR_EVENT]; int i, n; struct timespec timeout = { .tv_nsec = timeout_nsecs }; pthread_testcancel(); n = io_getevents(ioctx, 1L, CONCUR_NR_EVENT, events, &timeout); if (n < 0) { io_err_stat_log(3, "%s: io_getevents returned %s", dev, strerror(-n)); } else { for (i = 0; i < n; i++) handle_async_io_done_event(&events[i]); } } static void service_paths(void) { struct vector_s _pathvec = { .allocated = 0 }; /* avoid gcc warnings that &_pathvec will never be NULL in vector ops */ struct vector_s * const tmp_pathvec = &_pathvec; struct io_err_stat_path *pp; int i; pthread_mutex_lock(&io_err_pathvec_lock); pthread_cleanup_push(cleanup_mutex, &io_err_pathvec_lock); vector_foreach_slot(io_err_pathvec, pp, i) { send_batch_async_ios(pp); process_async_ios_event(TIMEOUT_NO_IO_NSEC, pp->devname); poll_async_io_timeout(); if (io_err_stat_time_up(pp)) { if (!vector_alloc_slot(tmp_pathvec)) continue; vector_del_slot(io_err_pathvec, i--); vector_set_slot(tmp_pathvec, pp); } } pthread_cleanup_pop(1); vector_foreach_slot_backwards(tmp_pathvec, pp, i) { end_io_err_stat(pp); vector_del_slot(tmp_pathvec, i); free_io_err_stat_path(pp); } vector_reset(tmp_pathvec); } static void cleanup_exited(__attribute__((unused)) void *arg) { uatomic_set(&io_err_thread_running, 0); } static void *io_err_stat_loop(void *data) { sigset_t set; vecs = (struct vectors *)data; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); pthread_cleanup_push(cleanup_exited, NULL); sigfillset(&set); sigdelset(&set, SIGUSR2); mlockall(MCL_CURRENT | MCL_FUTURE); pthread_mutex_lock(&io_err_thread_lock); uatomic_set(&io_err_thread_running, 1); pthread_cond_broadcast(&io_err_thread_cond); pthread_mutex_unlock(&io_err_thread_lock); while (1) { struct timespec ts; service_paths(); ts.tv_sec = 0; ts.tv_nsec = 100 * 1000 * 1000; /* * pselect() with no fds, a timeout, and a sigmask: * sleep for 100ms and react on SIGUSR2. */ pselect(1, NULL, NULL, NULL, &ts, &set); } pthread_cleanup_pop(1); pthread_cleanup_pop(1); return NULL; } int start_io_err_stat_thread(void *data) { int ret; pthread_attr_t io_err_stat_attr; if (uatomic_read(&io_err_thread_running) == 1) return 0; if ((ret = io_setup(NR_IOSTAT_PATHS * CONCUR_NR_EVENT, &ioctx)) != 0) { io_err_stat_log(1, "io_setup failed: %s, increase /proc/sys/fs/aio-nr ?", strerror(-ret)); return 1; } pthread_mutex_lock(&io_err_pathvec_lock); io_err_pathvec = vector_alloc(); if (!io_err_pathvec) { pthread_mutex_unlock(&io_err_pathvec_lock); goto destroy_ctx; } pthread_mutex_unlock(&io_err_pathvec_lock); setup_thread_attr(&io_err_stat_attr, 32 * 1024, 0); pthread_mutex_lock(&io_err_thread_lock); pthread_cleanup_push(cleanup_mutex, &io_err_thread_lock); ret = pthread_create(&io_err_stat_thr, &io_err_stat_attr, io_err_stat_loop, data); while (!ret && !uatomic_read(&io_err_thread_running) && pthread_cond_wait(&io_err_thread_cond, &io_err_thread_lock) == 0); pthread_cleanup_pop(1); pthread_attr_destroy(&io_err_stat_attr); if (ret) { io_err_stat_log(0, "cannot create io_error statistic thread"); goto out_free; } io_err_stat_log(2, "io_error statistic thread started"); return 0; out_free: pthread_mutex_lock(&io_err_pathvec_lock); vector_free(io_err_pathvec); io_err_pathvec = NULL; pthread_mutex_unlock(&io_err_pathvec_lock); destroy_ctx: io_destroy(ioctx); io_err_stat_log(0, "failed to start io_error statistic thread"); return 1; } void stop_io_err_stat_thread(void) { if (io_err_stat_thr == (pthread_t)0) return; if (uatomic_read(&io_err_thread_running) == 1) pthread_cancel(io_err_stat_thr); pthread_join(io_err_stat_thr, NULL); free_io_err_pathvec(); } multipath-tools-0.11.1/libmultipath/io_err_stat.h000066400000000000000000000005371475246302400221340ustar00rootroot00000000000000#ifndef IO_ERR_STAT_H_INCLUDED #define IO_ERR_STAT_H_INCLUDED #include "vector.h" #include "lock.h" extern pthread_attr_t io_err_stat_attr; int start_io_err_stat_thread(void *data); void stop_io_err_stat_thread(void); int io_err_stat_handle_pathfail(struct path *path); int need_io_err_check(struct path *pp); #endif /* IO_ERR_STAT_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/libmultipath.version000066400000000000000000000125551475246302400235610ustar00rootroot00000000000000/* * Copyright (c) 2020 SUSE LLC * SPDX-License-Identifier: GPL-2.0-or-later * * libmultipath ABI * * libmultipath doesn't have a stable ABI in the usual sense. In particular, * the library does not attempt to ship different versions of the same symbol * for backward compatibility. * * The ABI versioning only serves to avoid linking with a non-matching ABI, to * cut down the set of exported symbols, and to describe it. * The version string is LIBMULTIPATH_$MAJOR.$MINOR.$REL. * * Policy: * * * Bump $MAJOR for incompatible changes, like: * - symbols removed * - parameter list or return values changed for existing functions * - externally visible data structures changed in incompatible ways * (like offsets of previously existing struct members) * In this case, the new version doesn't inherit the previous versions, * because the new library doesn't provide the full previous ABI anymore. * All predecessors are merged into the new version. * * * Bump $MINOR for compatible changes, like adding symbols. * The new version inherits the previous ones. * * * Bump $REL to describe deviations from upstream, e.g. in * multipath-tools packages shipped by distributions. * The new version inherits the previous ones. */ /* * Symbols exported by both libmpathutil and libmultipath * libmpathutil exports just dummy symbols, intended to be overridden * by those in libmultipath. * CAUTION - the version in libmpathutil.version and libmultipath.version * must be THE SAME, otherwise the overriding will fail! */ LIBMPATHCOMMON_1.0.0 { get_multipath_config; put_multipath_config; }; LIBMULTIPATH_28.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; add_map_with_path; adopt_paths; alloc_multipath; alloc_multipath_layout; alloc_path; alloc_path_layout; alloc_path_with_pathinfo; change_foreign; check_alias_settings; check_daemon; checker_clear_message; checker_disable; checker_enable; checker_message; checker_name; checker_need_wait; checker_state_name; check_foreign; cleanup_bindings; cleanup_lock; cleanup_multipath_and_paths; coalesce_paths; count_active_paths; delete_all_foreign; delete_foreign; dm_cancel_deferred_remove; dm_enablegroup; dm_fail_path; dm_find_map_by_wwid; dm_flush_map__; dm_flush_map_nopaths; dm_flush_maps; dm_geteventnr; dm_get_major_minor; dm_get_maps; dm_get_multipath; dm_is_mpath; dm_mapname; dm_prereq; dm_queue_if_no_path; dm_reassign; dm_reinstate_path; dm_simplecmd_noflush; dm_switchgroup; domap; ensure_directories_exist; extract_hwe_from_path; filter_devnode; filter_path; filter_wwid; find_mp_by_alias; find_mp_by_minor; find_mp_by_str; find_mp_by_wwid; find_mpe; find_path_by_dev; find_path_by_devt; foreign_multipath_layout; foreign_path_layout; free_config; free_multipath; free_multipathvec; free_path; free_pathvec; get_multipath_layout; get_path_layout; get_pgpolicy_id; get_refwwid; get_state; get_udev_device; get_uid; get_used_hwes; get_vpd_sgio; group_by_prio; handle_bindings_file_inotify; has_dm_info; init_checkers; init_config; init_foreign; init_prio; io_err_stat_handle_pathfail; is_path_valid; libmp_dm_task_create; libmp_get_version; libmp_get_multipath_config; libmp_dm_task_run; libmp_mapinfo; libmp_put_multipath_config; libmp_udev_set_sync_support; libmultipath_exit; libmultipath_init; load_config; mpath_in_use; need_io_err_check; orphan_path; parse_prkey_flags; pathcount; path_discovery; path_get_tpgs; pathinfo; path_sysfs_state; print_all_paths; print_foreign_topology; print_multipath_topology__; reinstate_paths; remember_wwid; remove_feature; remove_map; remove_map_by_alias; remove_map_callback; remove_maps; remove_wwid; replace_wwids; reset_checker_classes; start_checker; select_all_tg_pt; select_action; select_find_multipaths_timeout; select_no_path_retry; select_path_group; select_reservation_key; set_no_path_retry; set_path_removed; set_prkey; setup_map; should_multipath; skip_libmp_dm_init; snprint_blacklist_report; snprint_config__; snprint_config; snprint_devices; snprint_foreign_multipaths; snprint_foreign_paths; snprint_foreign_topology; snprint_multipath__; snprint_multipath_header; snprint_multipath_map_json; snprint_multipath_topology__; snprint_multipath_topology_json; snprint_path__; snprint_path_header; snprint_status; snprint_wildcards; stop_io_err_stat_thread; store_path; store_pathinfo; sync_map_state; sysfs_get_size; sysfs_is_multipathed; trigger_path_udev_change; trigger_paths_udev_change; udev; uevent_dispatch; uevent_get_dm_str; uevent_get_env_positive_int; uevent_is_mpath; uevent_listen; uninit_config; update_mpp_paths; update_multipath_strings; update_multipath_table__; update_multipath_table; update_queue_mode_add_path; update_queue_mode_del_path; valid_alias; verify_paths; /* checkers */ checker_is_sync; sg_read; start_checker_thread; /* prioritizers */ get_asymmetric_access_state; get_prio_timeout_ms; get_target_port_group; get_target_port_group_support; libmp_nvme_ana_log; libmp_nvme_get_nsid; libmp_nvme_identify_ns; log_nvme_errcode; nvme_id_ctrl_ana; set_wakeup_fn; snprint_host_wwnn; snprint_host_wwpn; snprint_path_serial; snprint_tgt_wwnn; snprint_tgt_wwpn; sysfs_attr_set_value; sysfs_attr_get_value; sysfs_get_asymmetric_access_state; /* foreign */ local: *; }; multipath-tools-0.11.1/libmultipath/libsg.c000066400000000000000000000042641475246302400207160ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui */ #include #include #include #include #include "checkers.h" #include "libsg.h" #include "../libmultipath/sg_include.h" int sg_read (int sg_fd, unsigned char * buff, int buff_len, unsigned char * sense, int sense_len, unsigned int timeout) { /* defaults */ int blocks; long long start_block = 0; int bs = 512; int cdbsz = 10; unsigned char rdCmd[cdbsz]; unsigned char *sbb = sense; struct sg_io_hdr io_hdr; int res; int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88}; int sz_ind; struct stat filestatus; int retry_count = 3; if (fstat(sg_fd, &filestatus) != 0) return PATH_DOWN; bs = (filestatus.st_blksize > 4096)? 4096: filestatus.st_blksize; blocks = buff_len / bs; memset(rdCmd, 0, cdbsz); sz_ind = 1; rdCmd[0] = rd_opcode[sz_ind]; rdCmd[2] = (unsigned char)((start_block >> 24) & 0xff); rdCmd[3] = (unsigned char)((start_block >> 16) & 0xff); rdCmd[4] = (unsigned char)((start_block >> 8) & 0xff); rdCmd[5] = (unsigned char)(start_block & 0xff); rdCmd[7] = (unsigned char)((blocks >> 8) & 0xff); rdCmd[8] = (unsigned char)(blocks & 0xff); memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = cdbsz; io_hdr.cmdp = rdCmd; io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = bs * blocks; io_hdr.dxferp = buff; io_hdr.mx_sb_len = sense_len; io_hdr.sbp = sense; io_hdr.timeout = timeout * 1000; io_hdr.pack_id = (int)start_block; retry: memset(sense, 0, sense_len); while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) && (EINTR == errno)); if (res < 0) { if (ENOMEM == errno) { return PATH_UP; } return PATH_DOWN; } if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) { return PATH_UP; } else { int key = 0; if (io_hdr.sb_len_wr > 3) { if (sbb[0] == 0x72 || sbb[0] == 0x73) key = sbb[1] & 0x0f; else if (io_hdr.sb_len_wr > 13 && ((sbb[0] & 0x7f) == 0x70 || (sbb[0] & 0x7f) == 0x71)) key = sbb[2] & 0x0f; } /* * Retry if UNIT_ATTENTION check condition. */ if (key == 0x6) { if (--retry_count) goto retry; } return PATH_DOWN; } } multipath-tools-0.11.1/libmultipath/libsg.h000066400000000000000000000003541475246302400207170ustar00rootroot00000000000000#ifndef LIBSG_H_INCLUDED #define LIBSG_H_INCLUDED #define SENSE_BUFF_LEN 32 int sg_read (int sg_fd, unsigned char * buff, int buff_len, unsigned char * sense, int sense_len, unsigned int timeout); #endif /* LIBSG_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/list.h000066400000000000000000000312661475246302400206000ustar00rootroot00000000000000/* * Copied from the Linux kernel source tree, version 2.6.0-test1. * * Licensed under the GPL v2 as per the whole kernel source tree. * */ #ifndef LIST_H_INCLUDED #define LIST_H_INCLUDED #include /** * container_of - cast a member of a structure out to the containing structure * * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of_const(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (const type *)( (const char *)__mptr - offsetof(type,member) );}) #define container_of(ptr, type, member) ({ \ typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty on entry does not return true after this, the entry is * in an undefined state. */ static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(struct list_head *head) { return head->next == head; } static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } /** * list_splice - join two lists * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } /** * list_splice_tail - join two lists, each list being a queue * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } /** * list_splice_tail_init - join two lists and reinitialise the emptied list * @list: the new list to add. * @head: the place to add it in the first list. * * Each of the lists is a queue. * The list at @list is reinitialised */ static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head->prev, head); INIT_LIST_HEAD(list); } } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_pop - unlink and return the first list element * @head: the &struct list_head pointer. */ static inline struct list_head *list_pop(struct list_head *head) { struct list_head *tmp; if (list_empty(head)) return NULL; tmp = head->next; list_del_init(tmp); return tmp; } /** * list_pop_entry - unlink and return the entry of the first list element * @head: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_pop_entry(head, type, member) \ ({ \ struct list_head *__h = list_pop(head); \ \ (__h ? container_of(__h, type, member) : NULL); \ }) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) /** * __list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. * * This variant differs from list_for_each() in that it's the * simplest possible list iteration code. * Use this for code that knows the list to be very short (empty * or 1 entry) most of the time. */ #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop counter. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop counter. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop counter. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_reverse_safe - iterate backwards over list of given type safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse_safe(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member);\ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) /** * list_for_some_entry - iterate list from the given begin node to the given end node * @pos: the type * to use as a loop counter. * @from: the begin node of the iteration. * @to: the end node of the iteration. * @member: the name of the list_struct within the struct. */ #define list_for_some_entry(pos, from, to, member) \ for (pos = list_entry((from)->next, typeof(*pos), member); \ &pos->member != (to); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_some_entry_reverse - iterate backwards list from the given begin node to the given end node * @pos: the type * to use as a loop counter. * @from: the begin node of the iteration. * @to: the end node of the iteration. * @member: the name of the list_struct within the struct. */ #define list_for_some_entry_reverse(pos, from, to, member) \ for (pos = list_entry((from)->prev, typeof(*pos), member); \ &pos->member != (to); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_for_some_entry_safe - iterate list from the given begin node to the given end node safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @from: the begin node of the iteration. * @to: the end node of the iteration. * @member: the name of the list_struct within the struct. */ #define list_for_some_entry_safe(pos, n, from, to, member) \ for (pos = list_entry((from)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (to); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_some_entry_reverse_safe - iterate backwards list from the given begin node to the given end node safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @from: the begin node of the iteration. * @to: the end node of the iteration. * @member: the name of the list_struct within the struct. */ #define list_for_some_entry_reverse_safe(pos, n, from, to, member) \ for (pos = list_entry((from)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (to); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) #endif /* LIST_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/lock.c000066400000000000000000000004101475246302400205330ustar00rootroot00000000000000#include "lock.h" void cleanup_lock (void * data) { struct mutex_lock *lock = data; wakeup_fn *fn = lock->wakeup; unlock__(lock); if (fn) fn(); } void set_wakeup_fn(struct mutex_lock *lck, wakeup_fn *fn) { lock(lck); lck->wakeup = fn; unlock__(lck); } multipath-tools-0.11.1/libmultipath/lock.h000066400000000000000000000026471475246302400205560ustar00rootroot00000000000000#ifndef LOCK_H_INCLUDED #define LOCK_H_INCLUDED #include #include #include typedef void (wakeup_fn)(void); struct mutex_lock { pthread_mutex_t mutex; wakeup_fn *wakeup; int waiters; /* uatomic access only */ }; static inline void init_lock(struct mutex_lock *a) { pthread_mutex_init(&a->mutex, NULL); uatomic_set(&a->waiters, 0); } #if defined(__GNUC__) && __GNUC__ == 12 && URCU_VERSION < 0xe00 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" #endif static inline int uatomic_xchg_int(int *ptr, int val) { return uatomic_xchg(ptr, val); } static inline void lock(struct mutex_lock *a) { uatomic_inc(&a->waiters); pthread_mutex_lock(&a->mutex); uatomic_dec(&a->waiters); } #if defined(__GNUC__) && __GNUC__ == 12 && URCU_VERSION < 0xe00 #pragma GCC diagnostic pop #endif static inline int trylock(struct mutex_lock *a) { return pthread_mutex_trylock(&a->mutex); } static inline int timedlock(struct mutex_lock *a, struct timespec *tmo) { return pthread_mutex_timedlock(&a->mutex, tmo); } static inline void unlock__(struct mutex_lock *a) { pthread_mutex_unlock(&a->mutex); } static inline bool lock_has_waiters(struct mutex_lock *a) { return (uatomic_read(&a->waiters) > 0); } #define lock_cleanup_pop(a) pthread_cleanup_pop(1) void cleanup_lock (void * data); void set_wakeup_fn(struct mutex_lock *lock, wakeup_fn *fn); #endif /* LOCK_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme-lib.c000066400000000000000000000020121475246302400213140ustar00rootroot00000000000000#include /* avoid inclusion of standard API */ #define NVME_LIB_C 1 #include "nvme-lib.h" #include "nvme-ioctl.c" #include "debug.h" int log_nvme_errcode(int err, const char *dev, const char *msg) { if (err > 0) condlog(3, "%s: %s: NVMe status %d", dev, msg, err); else if (err < 0) condlog(3, "%s: %s: %s", dev, msg, strerror(errno)); return err; } int libmp_nvme_get_nsid(int fd) { return nvme_get_nsid(fd); } int libmp_nvme_identify_ctrl(int fd, struct nvme_id_ctrl *ctrl) { return nvme_identify_ctrl(fd, ctrl); } int libmp_nvme_identify_ns(int fd, __u32 nsid, bool present, struct nvme_id_ns *ns) { return nvme_identify_ns(fd, nsid, present, ns); } int libmp_nvme_ana_log(int fd, void *ana_log, size_t ana_log_len, int rgo) { return nvme_ana_log(fd, ana_log, ana_log_len, rgo); } int nvme_id_ctrl_ana(int fd, struct nvme_id_ctrl *ctrl) { int rc; struct nvme_id_ctrl c; rc = nvme_identify_ctrl(fd, &c); if (rc < 0) return rc; if (ctrl) *ctrl = c; return c.cmic & (1 << 3) ? 1 : 0; } multipath-tools-0.11.1/libmultipath/nvme-lib.h000066400000000000000000000021741475246302400213320ustar00rootroot00000000000000#ifndef NVME_LIB_H_INCLUDED #define NVME_LIB_H_INCLUDED #include "nvme.h" int log_nvme_errcode(int err, const char *dev, const char *msg); int libmp_nvme_get_nsid(int fd); int libmp_nvme_identify_ctrl(int fd, struct nvme_id_ctrl *ctrl); int libmp_nvme_identify_ns(int fd, __u32 nsid, bool present, struct nvme_id_ns *ns); int libmp_nvme_ana_log(int fd, void *ana_log, size_t ana_log_len, int rgo); /* * Identify controller, and return true if ANA is supported * ctrl will be filled in if controller is identified, even w/o ANA * ctrl may be NULL */ int nvme_id_ctrl_ana(int fd, struct nvme_id_ctrl *ctrl); #ifndef NVME_LIB_C /* * In all files except nvme-lib.c, the nvme functions can be called * by their usual name. */ #define nvme_get_nsid libmp_nvme_get_nsid #define nvme_identify_ctrl libmp_nvme_identify_ctrl #define nvme_identify_ns libmp_nvme_identify_ns #define nvme_ana_log libmp_nvme_ana_log /* * Undefine these to avoid clashes with libmultipath's byteorder.h */ #undef cpu_to_le16 #undef cpu_to_le32 #undef cpu_to_le64 #undef le16_to_cpu #undef le32_to_cpu #undef le64_to_cpu #endif #endif /* NVME_LIB_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme/000077500000000000000000000000001475246302400204115ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/nvme/argconfig.h000066400000000000000000000053211475246302400225220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////// // // Copyright 2014 PMC-Sierra, Inc. // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // Author: Logan Gunthorpe // Logan Gunthorpe // // Date: Oct 23 2014 // // Description: // Header file for argconfig.c // //////////////////////////////////////////////////////////////////////// #ifndef NVME_ARGCONFIG_H_INCLUDED #define NVME_ARGCONFIG_H_INCLUDED #include #include #include enum argconfig_types { CFG_NONE, CFG_STRING, CFG_INT, CFG_SIZE, CFG_LONG, CFG_LONG_SUFFIX, CFG_DOUBLE, CFG_BOOL, CFG_BYTE, CFG_SHORT, CFG_POSITIVE, CFG_INCREMENT, CFG_SUBOPTS, CFG_FILE_A, CFG_FILE_W, CFG_FILE_R, CFG_FILE_AP, CFG_FILE_WP, CFG_FILE_RP, }; struct argconfig_commandline_options { const char *option; const char short_option; const char *meta; enum argconfig_types config_type; void *default_value; int argument_type; const char *help; }; #define CFG_MAX_SUBOPTS 500 #define MAX_HELP_FUNC 20 #ifdef __cplusplus extern "C" { #endif typedef void argconfig_help_func(); void argconfig_append_usage(const char *str); void argconfig_print_help(const char *program_desc, const struct argconfig_commandline_options *options); int argconfig_parse(int argc, char *argv[], const char *program_desc, const struct argconfig_commandline_options *options, void *config_out, size_t config_size); int argconfig_parse_subopt_string(char *string, char **options, size_t max_options); unsigned argconfig_parse_comma_sep_array(char *string, int *ret, unsigned max_length); unsigned argconfig_parse_comma_sep_array_long(char *string, unsigned long long *ret, unsigned max_length); void argconfig_register_help_func(argconfig_help_func * f); void print_word_wrapped(const char *s, int indent, int start); #ifdef __cplusplus } #endif #endif multipath-tools-0.11.1/libmultipath/nvme/json.h000066400000000000000000000053241475246302400215370ustar00rootroot00000000000000#ifndef NVME_JSON_H_INCLUDED #define NVME_JSON_H_INCLUDED struct json_object; struct json_array; struct json_pair; #define JSON_TYPE_STRING 0 #define JSON_TYPE_INTEGER 1 #define JSON_TYPE_FLOAT 2 #define JSON_TYPE_OBJECT 3 #define JSON_TYPE_ARRAY 4 #define JSON_TYPE_UINT 5 #define JSON_PARENT_TYPE_PAIR 0 #define JSON_PARENT_TYPE_ARRAY 1 struct json_value { int type; union { long long integer_number; unsigned long long uint_number; long double float_number; char *string; struct json_object *object; struct json_array *array; }; int parent_type; union { struct json_pair *parent_pair; struct json_array *parent_array; }; }; struct json_array { struct json_value **values; int value_cnt; struct json_value *parent; }; struct json_object { struct json_pair **pairs; int pair_cnt; struct json_value *parent; }; struct json_pair { char *name; struct json_value *value; struct json_object *parent; }; struct json_object *json_create_object(void); struct json_array *json_create_array(void); void json_free_object(struct json_object *obj); int json_object_add_value_type(struct json_object *obj, const char *name, int type, ...); #define json_object_add_value_int(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_INTEGER, (long long) (val)) #define json_object_add_value_uint(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_UINT, (unsigned long long) (val)) #define json_object_add_value_float(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_FLOAT, (val)) #define json_object_add_value_string(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_STRING, (val)) #define json_object_add_value_object(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_OBJECT, (val)) #define json_object_add_value_array(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_ARRAY, (val)) int json_array_add_value_type(struct json_array *array, int type, ...); #define json_array_add_value_int(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_INTEGER, (val)) #define json_array_add_value_uint(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_UINT, (val)) #define json_array_add_value_float(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_FLOAT, (val)) #define json_array_add_value_string(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_STRING, (val)) #define json_array_add_value_object(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_OBJECT, (val)) #define json_array_add_value_array(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_ARRAY, (val)) #define json_array_last_value_object(obj) \ (obj->values[obj->value_cnt - 1]->object) void json_print_object(struct json_object *obj, void *); #endif multipath-tools-0.11.1/libmultipath/nvme/linux/000077500000000000000000000000001475246302400215505ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/nvme/linux/nvme.h000066400000000000000000001054731475246302400227000ustar00rootroot00000000000000/* * Definitions for the NVM Express interface * Copyright (c) 2011-2014, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #ifndef LINUX_NVME_H_INCLUDED #define LINUX_NVME_H_INCLUDED #include #include /* NQN names in commands fields specified one size */ #define NVMF_NQN_FIELD_LEN 256 /* However the max length of a qualified name is another size */ #define NVMF_NQN_SIZE 223 #define NVMF_TRSVCID_SIZE 32 #define NVMF_TRADDR_SIZE 256 #define NVMF_TSAS_SIZE 256 #define NVME_DISC_SUBSYS_NAME "nqn.2014-08.org.nvmexpress.discovery" #define NVME_RDMA_IP_PORT 4420 #define NVME_NSID_ALL 0xffffffff enum nvme_subsys_type { NVME_NQN_DISC = 1, /* Discovery type target subsystem */ NVME_NQN_NVME = 2, /* NVME type target subsystem */ }; /* Address Family codes for Discovery Log Page entry ADRFAM field */ enum { NVMF_ADDR_FAMILY_PCI = 0, /* PCIe */ NVMF_ADDR_FAMILY_IP4 = 1, /* IP4 */ NVMF_ADDR_FAMILY_IP6 = 2, /* IP6 */ NVMF_ADDR_FAMILY_IB = 3, /* InfiniBand */ NVMF_ADDR_FAMILY_FC = 4, /* Fibre Channel */ }; /* Transport Type codes for Discovery Log Page entry TRTYPE field */ enum { NVMF_TRTYPE_RDMA = 1, /* RDMA */ NVMF_TRTYPE_FC = 2, /* Fibre Channel */ NVMF_TRTYPE_TCP = 3, /* TCP */ NVMF_TRTYPE_LOOP = 254, /* Reserved for host usage */ NVMF_TRTYPE_MAX, }; /* Transport Requirements codes for Discovery Log Page entry TREQ field */ enum { NVMF_TREQ_NOT_SPECIFIED = 0, /* Not specified */ NVMF_TREQ_REQUIRED = 1, /* Required */ NVMF_TREQ_NOT_REQUIRED = 2, /* Not Required */ NVMF_TREQ_DISABLE_SQFLOW = (1 << 2), /* SQ flow control disable supported */ }; /* RDMA QP Service Type codes for Discovery Log Page entry TSAS * RDMA_QPTYPE field */ enum { NVMF_RDMA_QPTYPE_CONNECTED = 1, /* Reliable Connected */ NVMF_RDMA_QPTYPE_DATAGRAM = 2, /* Reliable Datagram */ }; /* RDMA QP Service Type codes for Discovery Log Page entry TSAS * RDMA_QPTYPE field */ enum { NVMF_RDMA_PRTYPE_NOT_SPECIFIED = 1, /* No Provider Specified */ NVMF_RDMA_PRTYPE_IB = 2, /* InfiniBand */ NVMF_RDMA_PRTYPE_ROCE = 3, /* InfiniBand RoCE */ NVMF_RDMA_PRTYPE_ROCEV2 = 4, /* InfiniBand RoCEV2 */ NVMF_RDMA_PRTYPE_IWARP = 5, /* IWARP */ }; /* RDMA Connection Management Service Type codes for Discovery Log Page * entry TSAS RDMA_CMS field */ enum { NVMF_RDMA_CMS_RDMA_CM = 1, /* Sockets based endpoint addressing */ }; /* TCP port security type for Discovery Log Page entry TSAS */ enum { NVMF_TCP_SECTYPE_NONE = 0, /* No Security */ NVMF_TCP_SECTYPE_TLS = 1, /* Transport Layer Security */ }; #define NVME_AQ_DEPTH 32 #define NVME_NR_AEN_COMMANDS 1 #define NVME_AQ_BLK_MQ_DEPTH (NVME_AQ_DEPTH - NVME_NR_AEN_COMMANDS) /* * Subtract one to leave an empty queue entry for 'Full Queue' condition. See * NVM-Express 1.2 specification, section 4.1.2. */ #define NVME_AQ_MQ_TAG_DEPTH (NVME_AQ_BLK_MQ_DEPTH - 1) enum { NVME_REG_CAP = 0x0000, /* Controller Capabilities */ NVME_REG_VS = 0x0008, /* Version */ NVME_REG_INTMS = 0x000c, /* Interrupt Mask Set */ NVME_REG_INTMC = 0x0010, /* Interrupt Mask Clear */ NVME_REG_CC = 0x0014, /* Controller Configuration */ NVME_REG_CSTS = 0x001c, /* Controller Status */ NVME_REG_NSSR = 0x0020, /* NVM Subsystem Reset */ NVME_REG_AQA = 0x0024, /* Admin Queue Attributes */ NVME_REG_ASQ = 0x0028, /* Admin SQ Base Address */ NVME_REG_ACQ = 0x0030, /* Admin CQ Base Address */ NVME_REG_CMBLOC = 0x0038, /* Controller Memory Buffer Location */ NVME_REG_CMBSZ = 0x003c, /* Controller Memory Buffer Size */ NVME_REG_BPINFO = 0x0040, /* Boot Partition Information */ NVME_REG_BPRSEL = 0x0044, /* Boot Partition Read Select */ NVME_REG_BPMBL = 0x0048, /* Boot Partition Memory Buffer Location */ NVME_REG_PMRCAP = 0x0e00, /* Persistent Memory Capabilities */ NVME_REG_PMRCTL = 0x0e04, /* Persistent Memory Region Control */ NVME_REG_PMRSTS = 0x0e08, /* Persistent Memory Region Status */ NVME_REG_DBS = 0x1000, /* SQ 0 Tail Doorbell */ }; #define NVME_CAP_MQES(cap) ((cap) & 0xffff) #define NVME_CAP_TIMEOUT(cap) (((cap) >> 24) & 0xff) #define NVME_CAP_STRIDE(cap) (((cap) >> 32) & 0xf) #define NVME_CAP_NSSRC(cap) (((cap) >> 36) & 0x1) #define NVME_CAP_MPSMIN(cap) (((cap) >> 48) & 0xf) #define NVME_CAP_MPSMAX(cap) (((cap) >> 52) & 0xf) #define NVME_CMB_BIR(cmbloc) ((cmbloc) & 0x7) #define NVME_CMB_OFST(cmbloc) (((cmbloc) >> 12) & 0xfffff) #define NVME_CMB_SZ(cmbsz) (((cmbsz) >> 12) & 0xfffff) #define NVME_CMB_SZU(cmbsz) (((cmbsz) >> 8) & 0xf) #define NVME_CMB_WDS(cmbsz) ((cmbsz) & 0x10) #define NVME_CMB_RDS(cmbsz) ((cmbsz) & 0x8) #define NVME_CMB_LISTS(cmbsz) ((cmbsz) & 0x4) #define NVME_CMB_CQS(cmbsz) ((cmbsz) & 0x2) #define NVME_CMB_SQS(cmbsz) ((cmbsz) & 0x1) /* * Submission and Completion Queue Entry Sizes for the NVM command set. * (In bytes and specified as a power of two (2^n)). */ #define NVME_NVM_IOSQES 6 #define NVME_NVM_IOCQES 4 enum { NVME_CC_ENABLE = 1 << 0, NVME_CC_CSS_NVM = 0 << 4, NVME_CC_EN_SHIFT = 0, NVME_CC_CSS_SHIFT = 4, NVME_CC_MPS_SHIFT = 7, NVME_CC_AMS_SHIFT = 11, NVME_CC_SHN_SHIFT = 14, NVME_CC_IOSQES_SHIFT = 16, NVME_CC_IOCQES_SHIFT = 20, NVME_CC_AMS_RR = 0 << NVME_CC_AMS_SHIFT, NVME_CC_AMS_WRRU = 1 << NVME_CC_AMS_SHIFT, NVME_CC_AMS_VS = 7 << NVME_CC_AMS_SHIFT, NVME_CC_SHN_NONE = 0 << NVME_CC_SHN_SHIFT, NVME_CC_SHN_NORMAL = 1 << NVME_CC_SHN_SHIFT, NVME_CC_SHN_ABRUPT = 2 << NVME_CC_SHN_SHIFT, NVME_CC_SHN_MASK = 3 << NVME_CC_SHN_SHIFT, NVME_CC_IOSQES = NVME_NVM_IOSQES << NVME_CC_IOSQES_SHIFT, NVME_CC_IOCQES = NVME_NVM_IOCQES << NVME_CC_IOCQES_SHIFT, NVME_CSTS_RDY = 1 << 0, NVME_CSTS_CFS = 1 << 1, NVME_CSTS_NSSRO = 1 << 4, NVME_CSTS_PP = 1 << 5, NVME_CSTS_SHST_NORMAL = 0 << 2, NVME_CSTS_SHST_OCCUR = 1 << 2, NVME_CSTS_SHST_CMPLT = 2 << 2, NVME_CSTS_SHST_MASK = 3 << 2, }; struct nvme_id_power_state { __le16 max_power; /* centiwatts */ __u8 rsvd2; __u8 flags; __le32 entry_lat; /* microseconds */ __le32 exit_lat; /* microseconds */ __u8 read_tput; __u8 read_lat; __u8 write_tput; __u8 write_lat; __le16 idle_power; __u8 idle_scale; __u8 rsvd19; __le16 active_power; __u8 active_work_scale; __u8 rsvd23[9]; }; enum { NVME_PS_FLAGS_MAX_POWER_SCALE = 1 << 0, NVME_PS_FLAGS_NON_OP_STATE = 1 << 1, }; struct nvme_id_ctrl { __le16 vid; __le16 ssvid; char sn[20]; char mn[40]; char fr[8]; __u8 rab; __u8 ieee[3]; __u8 cmic; __u8 mdts; __le16 cntlid; __le32 ver; __le32 rtd3r; __le32 rtd3e; __le32 oaes; __le32 ctratt; __le16 rrls; __u8 rsvd102[26]; __le16 crdt1; __le16 crdt2; __le16 crdt3; __u8 rsvd134[122]; __le16 oacs; __u8 acl; __u8 aerl; __u8 frmw; __u8 lpa; __u8 elpe; __u8 npss; __u8 avscc; __u8 apsta; __le16 wctemp; __le16 cctemp; __le16 mtfa; __le32 hmpre; __le32 hmmin; __u8 tnvmcap[16]; __u8 unvmcap[16]; __le32 rpmbs; __le16 edstt; __u8 dsto; __u8 fwug; __le16 kas; __le16 hctma; __le16 mntmt; __le16 mxtmt; __le32 sanicap; __le32 hmminds; __le16 hmmaxd; __le16 nsetidmax; __u8 rsvd340[2]; __u8 anatt; __u8 anacap; __le32 anagrpmax; __le32 nanagrpid; __u8 rsvd352[160]; __u8 sqes; __u8 cqes; __le16 maxcmd; __le32 nn; __le16 oncs; __le16 fuses; __u8 fna; __u8 vwc; __le16 awun; __le16 awupf; __u8 nvscc; __u8 nwpc; __le16 acwu; __u8 rsvd534[2]; __le32 sgls; __le32 mnan; __u8 rsvd544[224]; char subnqn[256]; __u8 rsvd1024[768]; __le32 ioccsz; __le32 iorcsz; __le16 icdoff; __u8 ctrattr; __u8 msdbd; __u8 rsvd1804[244]; struct nvme_id_power_state psd[32]; __u8 vs[1024]; }; enum { NVME_CTRL_ONCS_COMPARE = 1 << 0, NVME_CTRL_ONCS_WRITE_UNCORRECTABLE = 1 << 1, NVME_CTRL_ONCS_DSM = 1 << 2, NVME_CTRL_ONCS_WRITE_ZEROES = 1 << 3, NVME_CTRL_ONCS_TIMESTAMP = 1 << 6, NVME_CTRL_VWC_PRESENT = 1 << 0, NVME_CTRL_OACS_SEC_SUPP = 1 << 0, NVME_CTRL_OACS_DIRECTIVES = 1 << 5, NVME_CTRL_OACS_DBBUF_SUPP = 1 << 8, NVME_CTRL_LPA_CMD_EFFECTS_LOG = 1 << 1, NVME_CTRL_CTRATT_128_ID = 1 << 0, NVME_CTRL_CTRATT_NON_OP_PSP = 1 << 1, NVME_CTRL_CTRATT_NVM_SETS = 1 << 2, NVME_CTRL_CTRATT_READ_RECV_LVLS = 1 << 3, NVME_CTRL_CTRATT_ENDURANCE_GROUPS = 1 << 4, NVME_CTRL_CTRATT_PREDICTABLE_LAT = 1 << 5, NVME_CTRL_CTRATT_NAMESPACE_GRANULARITY = 1 << 7, NVME_CTRL_CTRATT_UUID_LIST = 1 << 9, }; struct nvme_lbaf { __le16 ms; __u8 ds; __u8 rp; }; struct nvme_id_ns { __le64 nsze; __le64 ncap; __le64 nuse; __u8 nsfeat; __u8 nlbaf; __u8 flbas; __u8 mc; __u8 dpc; __u8 dps; __u8 nmic; __u8 rescap; __u8 fpi; __u8 dlfeat; __le16 nawun; __le16 nawupf; __le16 nacwu; __le16 nabsn; __le16 nabo; __le16 nabspf; __le16 noiob; __u8 nvmcap[16]; __le16 npwg; __le16 npwa; __le16 npdg; __le16 npda; __le16 nows; __u8 rsvd74[18]; __le32 anagrpid; __u8 rsvd96[3]; __u8 nsattr; __le16 nvmsetid; __le16 endgid; __u8 nguid[16]; __u8 eui64[8]; struct nvme_lbaf lbaf[16]; __u8 rsvd192[192]; __u8 vs[3712]; }; enum { NVME_ID_CNS_NS = 0x00, NVME_ID_CNS_CTRL = 0x01, NVME_ID_CNS_NS_ACTIVE_LIST = 0x02, NVME_ID_CNS_NS_DESC_LIST = 0x03, NVME_ID_CNS_NVMSET_LIST = 0x04, NVME_ID_CNS_NS_PRESENT_LIST = 0x10, NVME_ID_CNS_NS_PRESENT = 0x11, NVME_ID_CNS_CTRL_NS_LIST = 0x12, NVME_ID_CNS_CTRL_LIST = 0x13, NVME_ID_CNS_SCNDRY_CTRL_LIST = 0x15, NVME_ID_CNS_NS_GRANULARITY = 0x16, NVME_ID_CNS_UUID_LIST = 0x17, }; enum { NVME_DIR_IDENTIFY = 0x00, NVME_DIR_STREAMS = 0x01, NVME_DIR_SND_ID_OP_ENABLE = 0x01, NVME_DIR_SND_ST_OP_REL_ID = 0x01, NVME_DIR_SND_ST_OP_REL_RSC = 0x02, NVME_DIR_RCV_ID_OP_PARAM = 0x01, NVME_DIR_RCV_ST_OP_PARAM = 0x01, NVME_DIR_RCV_ST_OP_STATUS = 0x02, NVME_DIR_RCV_ST_OP_RESOURCE = 0x03, NVME_DIR_ENDIR = 0x01, }; enum { NVME_NS_FEAT_THIN = 1 << 0, NVME_NS_FLBAS_LBA_MASK = 0xf, NVME_NS_FLBAS_META_EXT = 0x10, NVME_LBAF_RP_BEST = 0, NVME_LBAF_RP_BETTER = 1, NVME_LBAF_RP_GOOD = 2, NVME_LBAF_RP_DEGRADED = 3, NVME_NS_DPC_PI_LAST = 1 << 4, NVME_NS_DPC_PI_FIRST = 1 << 3, NVME_NS_DPC_PI_TYPE3 = 1 << 2, NVME_NS_DPC_PI_TYPE2 = 1 << 1, NVME_NS_DPC_PI_TYPE1 = 1 << 0, NVME_NS_DPS_PI_FIRST = 1 << 3, NVME_NS_DPS_PI_MASK = 0x7, NVME_NS_DPS_PI_TYPE1 = 1, NVME_NS_DPS_PI_TYPE2 = 2, NVME_NS_DPS_PI_TYPE3 = 3, }; struct nvme_ns_id_desc { __u8 nidt; __u8 nidl; __le16 reserved; }; #define NVME_NIDT_EUI64_LEN 8 #define NVME_NIDT_NGUID_LEN 16 #define NVME_NIDT_UUID_LEN 16 enum { NVME_NIDT_EUI64 = 0x01, NVME_NIDT_NGUID = 0x02, NVME_NIDT_UUID = 0x03, }; #define NVME_MAX_NVMSET 31 struct nvme_nvmset_attr_entry { __le16 id; __le16 endurance_group_id; __u8 rsvd4[4]; __le32 random_4k_read_typical; __le32 opt_write_size; __u8 total_nvmset_cap[16]; __u8 unalloc_nvmset_cap[16]; __u8 rsvd48[80]; }; struct nvme_id_nvmset { __u8 nid; __u8 rsvd1[127]; struct nvme_nvmset_attr_entry ent[NVME_MAX_NVMSET]; }; struct nvme_id_ns_granularity_list_entry { __le64 namespace_size_granularity; __le64 namespace_capacity_granularity; }; struct nvme_id_ns_granularity_list { __le32 attributes; __u8 num_descriptors; __u8 rsvd[27]; struct nvme_id_ns_granularity_list_entry entry[16]; }; #define NVME_MAX_UUID_ENTRIES 128 struct nvme_id_uuid_list_entry { __u8 header; __u8 rsvd1[15]; __u8 uuid[16]; }; struct nvme_id_uuid_list { struct nvme_id_uuid_list_entry entry[NVME_MAX_UUID_ENTRIES]; }; /** * struct nvme_telemetry_log_page_hdr - structure for telemetry log page * @lpi: Log page identifier * @iee_oui: IEEE OUI Identifier * @dalb1: Data area 1 last block * @dalb2: Data area 2 last block * @dalb3: Data area 3 last block * @ctrlavail: Controller initiated data available * @ctrldgn: Controller initiated telemetry Data Generation Number * @rsnident: Reason Identifier * @telemetry_dataarea: Contains telemetry data block * * This structure can be used for both telemetry host-initiated log page * and controller-initiated log page. */ struct nvme_telemetry_log_page_hdr { __u8 lpi; __u8 rsvd[4]; __u8 iee_oui[3]; __le16 dalb1; __le16 dalb2; __le16 dalb3; __u8 rsvd1[368]; __u8 ctrlavail; __u8 ctrldgn; __u8 rsnident[128]; __u8 telemetry_dataarea[0]; }; struct nvme_endurance_group_log { __u32 rsvd0; __u8 avl_spare_threshold; __u8 percent_used; __u8 rsvd6[26]; __u8 endurance_estimate[16]; __u8 data_units_read[16]; __u8 data_units_written[16]; __u8 media_units_written[16]; __u8 rsvd96[416]; }; struct nvme_smart_log { __u8 critical_warning; __u8 temperature[2]; __u8 avail_spare; __u8 spare_thresh; __u8 percent_used; __u8 rsvd6[26]; __u8 data_units_read[16]; __u8 data_units_written[16]; __u8 host_reads[16]; __u8 host_writes[16]; __u8 ctrl_busy_time[16]; __u8 power_cycles[16]; __u8 power_on_hours[16]; __u8 unsafe_shutdowns[16]; __u8 media_errors[16]; __u8 num_err_log_entries[16]; __le32 warning_temp_time; __le32 critical_comp_time; __le16 temp_sensor[8]; __le32 thm_temp1_trans_count; __le32 thm_temp2_trans_count; __le32 thm_temp1_total_time; __le32 thm_temp2_total_time; __u8 rsvd232[280]; }; struct nvme_self_test_res { __u8 device_self_test_status; __u8 segment_num; __u8 valid_diagnostic_info; __u8 rsvd; __le64 power_on_hours; __le32 nsid; __le64 failing_lba; __u8 status_code_type; __u8 status_code; __u8 vendor_specific[2]; } __attribute__((packed)); struct nvme_self_test_log { __u8 crnt_dev_selftest_oprn; __u8 crnt_dev_selftest_compln; __u8 rsvd[2]; struct nvme_self_test_res result[20]; } __attribute__((packed)); struct nvme_fw_slot_info_log { __u8 afi; __u8 rsvd1[7]; __le64 frs[7]; __u8 rsvd64[448]; }; struct nvme_lba_status_desc { __u64 dslba; __u32 nlb; __u8 rsvd_12; __u8 status; __u8 rsvd_15_14[2]; }; struct nvme_lba_status { __u32 nlsd; __u8 cmpc; __u8 rsvd_7_5[3]; struct nvme_lba_status_desc descs[0]; }; /* NVMe Namespace Write Protect State */ enum { NVME_NS_NO_WRITE_PROTECT = 0, NVME_NS_WRITE_PROTECT, NVME_NS_WRITE_PROTECT_POWER_CYCLE, NVME_NS_WRITE_PROTECT_PERMANENT, }; #define NVME_MAX_CHANGED_NAMESPACES 1024 struct nvme_changed_ns_list_log { __le32 log[NVME_MAX_CHANGED_NAMESPACES]; }; enum { NVME_CMD_EFFECTS_CSUPP = 1 << 0, NVME_CMD_EFFECTS_LBCC = 1 << 1, NVME_CMD_EFFECTS_NCC = 1 << 2, NVME_CMD_EFFECTS_NIC = 1 << 3, NVME_CMD_EFFECTS_CCC = 1 << 4, NVME_CMD_EFFECTS_CSE_MASK = 3 << 16, NVME_CMD_EFFECTS_UUID_SEL = 1 << 19, }; struct nvme_effects_log { __le32 acs[256]; __le32 iocs[256]; __u8 resv[2048]; }; enum nvme_ana_state { NVME_ANA_OPTIMIZED = 0x01, NVME_ANA_NONOPTIMIZED = 0x02, NVME_ANA_INACCESSIBLE = 0x03, NVME_ANA_PERSISTENT_LOSS = 0x04, NVME_ANA_CHANGE = 0x0f, }; struct nvme_ana_group_desc { __le32 grpid; __le32 nnsids; __le64 chgcnt; __u8 state; __u8 rsvd17[15]; __le32 nsids[]; }; /* flag for the log specific field of the ANA log */ #define NVME_ANA_LOG_RGO (1 << 0) struct nvme_ana_rsp_hdr { __le64 chgcnt; __le16 ngrps; __le16 rsvd10[3]; }; enum { NVME_SMART_CRIT_SPARE = 1 << 0, NVME_SMART_CRIT_TEMPERATURE = 1 << 1, NVME_SMART_CRIT_RELIABILITY = 1 << 2, NVME_SMART_CRIT_MEDIA = 1 << 3, NVME_SMART_CRIT_VOLATILE_MEMORY = 1 << 4, }; enum { NVME_AER_ERROR = 0, NVME_AER_SMART = 1, NVME_AER_CSS = 6, NVME_AER_VS = 7, }; struct nvme_lba_range_type { __u8 type; __u8 attributes; __u8 rsvd2[14]; __u64 slba; __u64 nlb; __u8 guid[16]; __u8 rsvd48[16]; }; enum { NVME_LBART_TYPE_FS = 0x01, NVME_LBART_TYPE_RAID = 0x02, NVME_LBART_TYPE_CACHE = 0x03, NVME_LBART_TYPE_SWAP = 0x04, NVME_LBART_ATTRIB_TEMP = 1 << 0, NVME_LBART_ATTRIB_HIDE = 1 << 1, }; /* Predictable Latency Mode - Deterministic Threshold Configuration Data */ struct nvme_plm_config { __le16 enable_event; __u8 rsvd2[30]; __le64 dtwin_reads_thresh; __le64 dtwin_writes_thresh; __le64 dtwin_time_thresh; __u8 rsvd56[456]; }; struct nvme_reservation_status { __le32 gen; __u8 rtype; __u8 regctl[2]; __u8 resv5[2]; __u8 ptpls; __u8 resv10[13]; struct { __le16 cntlid; __u8 rcsts; __u8 resv3[5]; __le64 hostid; __le64 rkey; } regctl_ds[]; }; struct nvme_reservation_status_ext { __le32 gen; __u8 rtype; __u8 regctl[2]; __u8 resv5[2]; __u8 ptpls; __u8 resv10[14]; __u8 resv24[40]; struct { __le16 cntlid; __u8 rcsts; __u8 resv3[5]; __le64 rkey; __u8 hostid[16]; __u8 resv32[32]; } regctl_eds[]; }; enum nvme_async_event_type { NVME_AER_TYPE_ERROR = 0, NVME_AER_TYPE_SMART = 1, NVME_AER_TYPE_NOTICE = 2, }; /* I/O commands */ enum nvme_opcode { nvme_cmd_flush = 0x00, nvme_cmd_write = 0x01, nvme_cmd_read = 0x02, nvme_cmd_write_uncor = 0x04, nvme_cmd_compare = 0x05, nvme_cmd_write_zeroes = 0x08, nvme_cmd_dsm = 0x09, nvme_cmd_verify = 0x0c, nvme_cmd_resv_register = 0x0d, nvme_cmd_resv_report = 0x0e, nvme_cmd_resv_acquire = 0x11, nvme_cmd_resv_release = 0x15, }; /* * Descriptor subtype - lower 4 bits of nvme_(keyed_)sgl_desc identifier * * @NVME_SGL_FMT_ADDRESS: absolute address of the data block * @NVME_SGL_FMT_OFFSET: relative offset of the in-capsule data block * @NVME_SGL_FMT_TRANSPORT_A: transport defined format, value 0xA * @NVME_SGL_FMT_INVALIDATE: RDMA transport specific remote invalidation * request subtype */ enum { NVME_SGL_FMT_ADDRESS = 0x00, NVME_SGL_FMT_OFFSET = 0x01, NVME_SGL_FMT_TRANSPORT_A = 0x0A, NVME_SGL_FMT_INVALIDATE = 0x0f, }; /* * Descriptor type - upper 4 bits of nvme_(keyed_)sgl_desc identifier * * For struct nvme_sgl_desc: * @NVME_SGL_FMT_DATA_DESC: data block descriptor * @NVME_SGL_FMT_SEG_DESC: sgl segment descriptor * @NVME_SGL_FMT_LAST_SEG_DESC: last sgl segment descriptor * * For struct nvme_keyed_sgl_desc: * @NVME_KEY_SGL_FMT_DATA_DESC: keyed data block descriptor * * Transport-specific SGL types: * @NVME_TRANSPORT_SGL_DATA_DESC: Transport SGL data dlock descriptor */ enum { NVME_SGL_FMT_DATA_DESC = 0x00, NVME_SGL_FMT_SEG_DESC = 0x02, NVME_SGL_FMT_LAST_SEG_DESC = 0x03, NVME_KEY_SGL_FMT_DATA_DESC = 0x04, NVME_TRANSPORT_SGL_DATA_DESC = 0x05, }; struct nvme_sgl_desc { __le64 addr; __le32 length; __u8 rsvd[3]; __u8 type; }; struct nvme_keyed_sgl_desc { __le64 addr; __u8 length[3]; __u8 key[4]; __u8 type; }; union nvme_data_ptr { struct { __le64 prp1; __le64 prp2; }; struct nvme_sgl_desc sgl; struct nvme_keyed_sgl_desc ksgl; }; /* * Lowest two bits of our flags field (FUSE field in the spec): * * @NVME_CMD_FUSE_FIRST: Fused Operation, first command * @NVME_CMD_FUSE_SECOND: Fused Operation, second command * * Highest two bits in our flags field (PSDT field in the spec): * * @NVME_CMD_PSDT_SGL_METABUF: Use SGLS for this transfer, * If used, MPTR contains addr of single physical buffer (byte aligned). * @NVME_CMD_PSDT_SGL_METASEG: Use SGLS for this transfer, * If used, MPTR contains an address of an SGL segment containing * exactly 1 SGL descriptor (qword aligned). */ enum { NVME_CMD_FUSE_FIRST = (1 << 0), NVME_CMD_FUSE_SECOND = (1 << 1), NVME_CMD_SGL_METABUF = (1 << 6), NVME_CMD_SGL_METASEG = (1 << 7), NVME_CMD_SGL_ALL = NVME_CMD_SGL_METABUF | NVME_CMD_SGL_METASEG, }; struct nvme_common_command { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __le32 cdw2[2]; __le64 metadata; union nvme_data_ptr dptr; __le32 cdw10[6]; }; struct nvme_rw_command { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2; __le64 metadata; union nvme_data_ptr dptr; __le64 slba; __le16 length; __le16 control; __le32 dsmgmt; __le32 reftag; __le16 apptag; __le16 appmask; }; enum { NVME_RW_LR = 1 << 15, NVME_RW_FUA = 1 << 14, NVME_RW_DEAC = 1 << 9, NVME_RW_DSM_FREQ_UNSPEC = 0, NVME_RW_DSM_FREQ_TYPICAL = 1, NVME_RW_DSM_FREQ_RARE = 2, NVME_RW_DSM_FREQ_READS = 3, NVME_RW_DSM_FREQ_WRITES = 4, NVME_RW_DSM_FREQ_RW = 5, NVME_RW_DSM_FREQ_ONCE = 6, NVME_RW_DSM_FREQ_PREFETCH = 7, NVME_RW_DSM_FREQ_TEMP = 8, NVME_RW_DSM_LATENCY_NONE = 0 << 4, NVME_RW_DSM_LATENCY_IDLE = 1 << 4, NVME_RW_DSM_LATENCY_NORM = 2 << 4, NVME_RW_DSM_LATENCY_LOW = 3 << 4, NVME_RW_DSM_SEQ_REQ = 1 << 6, NVME_RW_DSM_COMPRESSED = 1 << 7, NVME_RW_PRINFO_PRCHK_REF = 1 << 10, NVME_RW_PRINFO_PRCHK_APP = 1 << 11, NVME_RW_PRINFO_PRCHK_GUARD = 1 << 12, NVME_RW_PRINFO_PRACT = 1 << 13, NVME_RW_DTYPE_STREAMS = 1 << 4, }; struct nvme_dsm_cmd { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[2]; union nvme_data_ptr dptr; __le32 nr; __le32 attributes; __u32 rsvd12[4]; }; enum { NVME_DSMGMT_IDR = 1 << 0, NVME_DSMGMT_IDW = 1 << 1, NVME_DSMGMT_AD = 1 << 2, }; #define NVME_DSM_MAX_RANGES 256 struct nvme_dsm_range { __le32 cattr; __le32 nlb; __le64 slba; }; struct nvme_write_zeroes_cmd { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2; __le64 metadata; union nvme_data_ptr dptr; __le64 slba; __le16 length; __le16 control; __le32 dsmgmt; __le32 reftag; __le16 apptag; __le16 appmask; }; /* Features */ struct nvme_feat_auto_pst { __le64 entries[32]; }; enum { NVME_HOST_MEM_ENABLE = (1 << 0), NVME_HOST_MEM_RETURN = (1 << 1), }; /* Admin commands */ enum nvme_admin_opcode { nvme_admin_delete_sq = 0x00, nvme_admin_create_sq = 0x01, nvme_admin_get_log_page = 0x02, nvme_admin_delete_cq = 0x04, nvme_admin_create_cq = 0x05, nvme_admin_identify = 0x06, nvme_admin_abort_cmd = 0x08, nvme_admin_set_features = 0x09, nvme_admin_get_features = 0x0a, nvme_admin_async_event = 0x0c, nvme_admin_ns_mgmt = 0x0d, nvme_admin_activate_fw = 0x10, nvme_admin_download_fw = 0x11, nvme_admin_dev_self_test = 0x14, nvme_admin_ns_attach = 0x15, nvme_admin_keep_alive = 0x18, nvme_admin_directive_send = 0x19, nvme_admin_directive_recv = 0x1a, nvme_admin_virtual_mgmt = 0x1c, nvme_admin_nvme_mi_send = 0x1d, nvme_admin_nvme_mi_recv = 0x1e, nvme_admin_dbbuf = 0x7C, nvme_admin_format_nvm = 0x80, nvme_admin_security_send = 0x81, nvme_admin_security_recv = 0x82, nvme_admin_sanitize_nvm = 0x84, nvme_admin_get_lba_status = 0x86, }; enum { NVME_QUEUE_PHYS_CONTIG = (1 << 0), NVME_CQ_IRQ_ENABLED = (1 << 1), NVME_SQ_PRIO_URGENT = (0 << 1), NVME_SQ_PRIO_HIGH = (1 << 1), NVME_SQ_PRIO_MEDIUM = (2 << 1), NVME_SQ_PRIO_LOW = (3 << 1), NVME_FEAT_ARBITRATION = 0x01, NVME_FEAT_POWER_MGMT = 0x02, NVME_FEAT_LBA_RANGE = 0x03, NVME_FEAT_TEMP_THRESH = 0x04, NVME_FEAT_ERR_RECOVERY = 0x05, NVME_FEAT_VOLATILE_WC = 0x06, NVME_FEAT_NUM_QUEUES = 0x07, NVME_FEAT_IRQ_COALESCE = 0x08, NVME_FEAT_IRQ_CONFIG = 0x09, NVME_FEAT_WRITE_ATOMIC = 0x0a, NVME_FEAT_ASYNC_EVENT = 0x0b, NVME_FEAT_AUTO_PST = 0x0c, NVME_FEAT_HOST_MEM_BUF = 0x0d, NVME_FEAT_TIMESTAMP = 0x0e, NVME_FEAT_KATO = 0x0f, NVME_FEAT_HCTM = 0X10, NVME_FEAT_NOPSC = 0X11, NVME_FEAT_RRL = 0x12, NVME_FEAT_PLM_CONFIG = 0x13, NVME_FEAT_PLM_WINDOW = 0x14, NVME_FEAT_HOST_BEHAVIOR = 0x16, NVME_FEAT_SANITIZE = 0x17, NVME_FEAT_SW_PROGRESS = 0x80, NVME_FEAT_HOST_ID = 0x81, NVME_FEAT_RESV_MASK = 0x82, NVME_FEAT_RESV_PERSIST = 0x83, NVME_FEAT_WRITE_PROTECT = 0x84, NVME_LOG_ERROR = 0x01, NVME_LOG_SMART = 0x02, NVME_LOG_FW_SLOT = 0x03, NVME_LOG_CHANGED_NS = 0x04, NVME_LOG_CMD_EFFECTS = 0x05, NVME_LOG_DEVICE_SELF_TEST = 0x06, NVME_LOG_TELEMETRY_HOST = 0x07, NVME_LOG_TELEMETRY_CTRL = 0x08, NVME_LOG_ENDURANCE_GROUP = 0x09, NVME_LOG_ANA = 0x0c, NVME_LOG_DISC = 0x70, NVME_LOG_RESERVATION = 0x80, NVME_LOG_SANITIZE = 0x81, NVME_FWACT_REPL = (0 << 3), NVME_FWACT_REPL_ACTV = (1 << 3), NVME_FWACT_ACTV = (2 << 3), }; enum { NVME_NO_LOG_LSP = 0x0, NVME_NO_LOG_LPO = 0x0, NVME_LOG_ANA_LSP_RGO = 0x1, NVME_TELEM_LSP_CREATE = 0x1, }; /* Sanitize and Sanitize Monitor/Log */ enum { /* Sanitize */ NVME_SANITIZE_NO_DEALLOC = 0x00000200, NVME_SANITIZE_OIPBP = 0x00000100, NVME_SANITIZE_OWPASS_SHIFT = 0x00000004, NVME_SANITIZE_AUSE = 0x00000008, NVME_SANITIZE_ACT_CRYPTO_ERASE = 0x00000004, NVME_SANITIZE_ACT_OVERWRITE = 0x00000003, NVME_SANITIZE_ACT_BLOCK_ERASE = 0x00000002, NVME_SANITIZE_ACT_EXIT = 0x00000001, /* Sanitize Monitor/Log */ NVME_SANITIZE_LOG_DATA_LEN = 0x0014, NVME_SANITIZE_LOG_GLOBAL_DATA_ERASED = 0x0100, NVME_SANITIZE_LOG_NUM_CMPLTED_PASS_MASK = 0x00F8, NVME_SANITIZE_LOG_STATUS_MASK = 0x0007, NVME_SANITIZE_LOG_NEVER_SANITIZED = 0x0000, NVME_SANITIZE_LOG_COMPLETED_SUCCESS = 0x0001, NVME_SANITIZE_LOG_IN_PROGRESS = 0x0002, NVME_SANITIZE_LOG_COMPLETED_FAILED = 0x0003, NVME_SANITIZE_LOG_ND_COMPLETED_SUCCESS = 0x0004, }; enum { /* Self-test log Validation bits */ NVME_SELF_TEST_VALID_NSID = 1 << 0, NVME_SELF_TEST_VALID_FLBA = 1 << 1, NVME_SELF_TEST_VALID_SCT = 1 << 2, NVME_SELF_TEST_VALID_SC = 1 << 3, NVME_SELF_TEST_REPORTS = 20, }; struct nvme_identify { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[2]; union nvme_data_ptr dptr; __u8 cns; __u8 rsvd3; __le16 ctrlid; __u32 rsvd11[5]; }; #define NVME_IDENTIFY_DATA_SIZE 4096 struct nvme_features { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[2]; union nvme_data_ptr dptr; __le32 fid; __le32 dword11; __le32 dword12; __le32 dword13; __le32 dword14; __le32 dword15; }; struct nvme_host_mem_buf_desc { __le64 addr; __le32 size; __u32 rsvd; }; struct nvme_create_cq { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[5]; __le64 prp1; __u64 rsvd8; __le16 cqid; __le16 qsize; __le16 cq_flags; __le16 irq_vector; __u32 rsvd12[4]; }; struct nvme_create_sq { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[5]; __le64 prp1; __u64 rsvd8; __le16 sqid; __le16 qsize; __le16 sq_flags; __le16 cqid; __u32 rsvd12[4]; }; struct nvme_delete_queue { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[9]; __le16 qid; __u16 rsvd10; __u32 rsvd11[5]; }; struct nvme_abort_cmd { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[9]; __le16 sqid; __u16 cid; __u32 rsvd11[5]; }; struct nvme_download_firmware { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[5]; union nvme_data_ptr dptr; __le32 numd; __le32 offset; __u32 rsvd12[4]; }; struct nvme_format_cmd { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[4]; __le32 cdw10; __u32 rsvd11[5]; }; struct nvme_get_log_page_command { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[2]; union nvme_data_ptr dptr; __u8 lid; __u8 lsp; __le16 numdl; __le16 numdu; __u16 rsvd11; __le32 lpol; __le32 lpou; __u32 rsvd14[2]; }; struct nvme_directive_cmd { __u8 opcode; __u8 flags; __u16 command_id; __le32 nsid; __u64 rsvd2[2]; union nvme_data_ptr dptr; __le32 numd; __u8 doper; __u8 dtype; __le16 dspec; __u8 endir; __u8 tdtype; __u16 rsvd15; __u32 rsvd16[3]; }; /* Sanitize Log Page */ struct nvme_sanitize_log_page { __le16 progress; __le16 status; __le32 cdw10_info; __le32 est_ovrwrt_time; __le32 est_blk_erase_time; __le32 est_crypto_erase_time; __le32 est_ovrwrt_time_with_no_deallocate; __le32 est_blk_erase_time_with_no_deallocate; __le32 est_crypto_erase_time_with_no_deallocate; }; /* * Fabrics subcommands. */ enum nvmf_fabrics_opcode { nvme_fabrics_command = 0x7f, }; enum nvmf_capsule_command { nvme_fabrics_type_property_set = 0x00, nvme_fabrics_type_connect = 0x01, nvme_fabrics_type_property_get = 0x04, }; struct nvmf_common_command { __u8 opcode; __u8 resv1; __u16 command_id; __u8 fctype; __u8 resv2[35]; __u8 ts[24]; }; /* * The legal cntlid range a NVMe Target will provide. * Note that cntlid of value 0 is considered illegal in the fabrics world. * Devices based on earlier specs did not have the subsystem concept; * therefore, those devices had their cntlid value set to 0 as a result. */ #define NVME_CNTLID_MIN 1 #define NVME_CNTLID_MAX 0xffef #define NVME_CNTLID_DYNAMIC 0xffff #define MAX_DISC_LOGS 255 /* Discovery log page entry */ struct nvmf_disc_rsp_page_entry { __u8 trtype; __u8 adrfam; __u8 subtype; __u8 treq; __le16 portid; __le16 cntlid; __le16 asqsz; __u8 resv8[22]; char trsvcid[NVMF_TRSVCID_SIZE]; __u8 resv64[192]; char subnqn[NVMF_NQN_FIELD_LEN]; char traddr[NVMF_TRADDR_SIZE]; union tsas { char common[NVMF_TSAS_SIZE]; struct rdma { __u8 qptype; __u8 prtype; __u8 cms; __u8 resv3[5]; __u16 pkey; __u8 resv10[246]; } rdma; struct tcp { __u8 sectype; } tcp; } tsas; }; /* Discovery log page header */ struct nvmf_disc_rsp_page_hdr { __le64 genctr; __le64 numrec; __le16 recfmt; __u8 resv14[1006]; struct nvmf_disc_rsp_page_entry entries[0]; }; struct nvmf_connect_command { __u8 opcode; __u8 resv1; __u16 command_id; __u8 fctype; __u8 resv2[19]; union nvme_data_ptr dptr; __le16 recfmt; __le16 qid; __le16 sqsize; __u8 cattr; __u8 resv3; __le32 kato; __u8 resv4[12]; }; struct nvmf_connect_data { uuid_t hostid; __le16 cntlid; char resv4[238]; char subsysnqn[NVMF_NQN_FIELD_LEN]; char hostnqn[NVMF_NQN_FIELD_LEN]; char resv5[256]; }; struct nvmf_property_set_command { __u8 opcode; __u8 resv1; __u16 command_id; __u8 fctype; __u8 resv2[35]; __u8 attrib; __u8 resv3[3]; __le32 offset; __le64 value; __u8 resv4[8]; }; struct nvmf_property_get_command { __u8 opcode; __u8 resv1; __u16 command_id; __u8 fctype; __u8 resv2[35]; __u8 attrib; __u8 resv3[3]; __le32 offset; __u8 resv4[16]; }; struct nvme_dbbuf { __u8 opcode; __u8 flags; __u16 command_id; __u32 rsvd1[5]; __le64 prp1; __le64 prp2; __u32 rsvd12[6]; }; struct streams_directive_params { __le16 msl; __le16 nssa; __le16 nsso; __u8 rsvd[10]; __le32 sws; __le16 sgs; __le16 nsa; __le16 nso; __u8 rsvd2[6]; }; struct nvme_command { union { struct nvme_common_command common; struct nvme_rw_command rw; struct nvme_identify identify; struct nvme_features features; struct nvme_create_cq create_cq; struct nvme_create_sq create_sq; struct nvme_delete_queue delete_queue; struct nvme_download_firmware dlfw; struct nvme_format_cmd format; struct nvme_dsm_cmd dsm; struct nvme_write_zeroes_cmd write_zeroes; struct nvme_abort_cmd abort; struct nvme_get_log_page_command get_log_page; struct nvmf_common_command fabrics; struct nvmf_connect_command connect; struct nvmf_property_set_command prop_set; struct nvmf_property_get_command prop_get; struct nvme_dbbuf dbbuf; struct nvme_directive_cmd directive; }; }; static inline bool nvme_is_write(struct nvme_command *cmd) { /* * What a mess... * * Why can't we simply have a Fabrics In and Fabrics out command? */ if (unlikely(cmd->common.opcode == nvme_fabrics_command)) return cmd->fabrics.fctype & 1; return cmd->common.opcode & 1; } enum { NVME_SCT_GENERIC = 0x0, NVME_SCT_CMD_SPECIFIC = 0x1, NVME_SCT_MEDIA = 0x2, }; enum { /* * Generic Command Status: */ NVME_SC_SUCCESS = 0x0, NVME_SC_INVALID_OPCODE = 0x1, NVME_SC_INVALID_FIELD = 0x2, NVME_SC_CMDID_CONFLICT = 0x3, NVME_SC_DATA_XFER_ERROR = 0x4, NVME_SC_POWER_LOSS = 0x5, NVME_SC_INTERNAL = 0x6, NVME_SC_ABORT_REQ = 0x7, NVME_SC_ABORT_QUEUE = 0x8, NVME_SC_FUSED_FAIL = 0x9, NVME_SC_FUSED_MISSING = 0xa, NVME_SC_INVALID_NS = 0xb, NVME_SC_CMD_SEQ_ERROR = 0xc, NVME_SC_SGL_INVALID_LAST = 0xd, NVME_SC_SGL_INVALID_COUNT = 0xe, NVME_SC_SGL_INVALID_DATA = 0xf, NVME_SC_SGL_INVALID_METADATA = 0x10, NVME_SC_SGL_INVALID_TYPE = 0x11, NVME_SC_SGL_INVALID_OFFSET = 0x16, NVME_SC_SGL_INVALID_SUBTYPE = 0x17, NVME_SC_SANITIZE_FAILED = 0x1C, NVME_SC_SANITIZE_IN_PROGRESS = 0x1D, NVME_SC_NS_WRITE_PROTECTED = 0x20, NVME_SC_CMD_INTERRUPTED = 0x21, NVME_SC_LBA_RANGE = 0x80, NVME_SC_CAP_EXCEEDED = 0x81, NVME_SC_NS_NOT_READY = 0x82, NVME_SC_RESERVATION_CONFLICT = 0x83, /* * Command Specific Status: */ NVME_SC_CQ_INVALID = 0x100, NVME_SC_QID_INVALID = 0x101, NVME_SC_QUEUE_SIZE = 0x102, NVME_SC_ABORT_LIMIT = 0x103, NVME_SC_ABORT_MISSING = 0x104, NVME_SC_ASYNC_LIMIT = 0x105, NVME_SC_FIRMWARE_SLOT = 0x106, NVME_SC_FIRMWARE_IMAGE = 0x107, NVME_SC_INVALID_VECTOR = 0x108, NVME_SC_INVALID_LOG_PAGE = 0x109, NVME_SC_INVALID_FORMAT = 0x10a, NVME_SC_FW_NEEDS_CONV_RESET = 0x10b, NVME_SC_INVALID_QUEUE = 0x10c, NVME_SC_FEATURE_NOT_SAVEABLE = 0x10d, NVME_SC_FEATURE_NOT_CHANGEABLE = 0x10e, NVME_SC_FEATURE_NOT_PER_NS = 0x10f, NVME_SC_FW_NEEDS_SUBSYS_RESET = 0x110, NVME_SC_FW_NEEDS_RESET = 0x111, NVME_SC_FW_NEEDS_MAX_TIME = 0x112, NVME_SC_FW_ACTIVATE_PROHIBITED = 0x113, NVME_SC_OVERLAPPING_RANGE = 0x114, NVME_SC_NS_INSUFFICIENT_CAP = 0x115, NVME_SC_NS_ID_UNAVAILABLE = 0x116, NVME_SC_NS_ALREADY_ATTACHED = 0x118, NVME_SC_NS_IS_PRIVATE = 0x119, NVME_SC_NS_NOT_ATTACHED = 0x11a, NVME_SC_THIN_PROV_NOT_SUPP = 0x11b, NVME_SC_CTRL_LIST_INVALID = 0x11c, NVME_SC_BP_WRITE_PROHIBITED = 0x11e, NVME_SC_PMR_SAN_PROHIBITED = 0x123, /* * I/O Command Set Specific - NVM commands: */ NVME_SC_BAD_ATTRIBUTES = 0x180, NVME_SC_INVALID_PI = 0x181, NVME_SC_READ_ONLY = 0x182, NVME_SC_ONCS_NOT_SUPPORTED = 0x183, /* * I/O Command Set Specific - Fabrics commands: */ NVME_SC_CONNECT_FORMAT = 0x180, NVME_SC_CONNECT_CTRL_BUSY = 0x181, NVME_SC_CONNECT_INVALID_PARAM = 0x182, NVME_SC_CONNECT_RESTART_DISC = 0x183, NVME_SC_CONNECT_INVALID_HOST = 0x184, NVME_SC_DISCOVERY_RESTART = 0x190, NVME_SC_AUTH_REQUIRED = 0x191, /* * Media and Data Integrity Errors: */ NVME_SC_WRITE_FAULT = 0x280, NVME_SC_READ_ERROR = 0x281, NVME_SC_GUARD_CHECK = 0x282, NVME_SC_APPTAG_CHECK = 0x283, NVME_SC_REFTAG_CHECK = 0x284, NVME_SC_COMPARE_FAILED = 0x285, NVME_SC_ACCESS_DENIED = 0x286, NVME_SC_UNWRITTEN_BLOCK = 0x287, /* * Path-related Errors: */ NVME_SC_ANA_PERSISTENT_LOSS = 0x301, NVME_SC_ANA_INACCESSIBLE = 0x302, NVME_SC_ANA_TRANSITION = 0x303, NVME_SC_CRD = 0x1800, NVME_SC_DNR = 0x4000, }; struct nvme_completion { /* * Used by Admin and Fabrics commands to return data: */ union nvme_result { __le16 u16; __le32 u32; __le64 u64; } result; __le16 sq_head; /* how much of this queue may be reclaimed */ __le16 sq_id; /* submission queue that generated this entry */ __u16 command_id; /* of the command which completed */ __le16 status; /* did the command fail, and if so, why? */ }; #define NVME_VS(major, minor, tertiary) \ (((major) << 16) | ((minor) << 8) | (tertiary)) #define NVME_MAJOR(ver) ((ver) >> 16) #define NVME_MINOR(ver) (((ver) >> 8) & 0xff) #define NVME_TERTIARY(ver) ((ver) & 0xff) #endif /* LINUX_NVME_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme/linux/nvme_ioctl.h000066400000000000000000000031401475246302400240560ustar00rootroot00000000000000/* * Definitions for the NVM Express ioctl interface * Copyright (c) 2011-2014, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #ifndef LINUX_NVME_IOCTL_H_INCLUDED #define LINUX_NVME_IOCTL_H_INCLUDED #include #include struct nvme_user_io { __u8 opcode; __u8 flags; __u16 control; __u16 nblocks; __u16 rsvd; __u64 metadata; __u64 addr; __u64 slba; __u32 dsmgmt; __u32 reftag; __u16 apptag; __u16 appmask; }; struct nvme_passthru_cmd { __u8 opcode; __u8 flags; __u16 rsvd1; __u32 nsid; __u32 cdw2; __u32 cdw3; __u64 metadata; __u64 addr; __u32 metadata_len; __u32 data_len; __u32 cdw10; __u32 cdw11; __u32 cdw12; __u32 cdw13; __u32 cdw14; __u32 cdw15; __u32 timeout_ms; __u32 result; }; #define nvme_admin_cmd nvme_passthru_cmd #define NVME_IOCTL_ID _IO('N', 0x40) #define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct nvme_admin_cmd) #define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct nvme_user_io) #define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd) #define NVME_IOCTL_RESET _IO('N', 0x44) #define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) #define NVME_IOCTL_RESCAN _IO('N', 0x46) #endif /* LINUX_NVME_IOCTL_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme/nvme-ioctl.c000066400000000000000000000562061475246302400226430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvme-ioctl.h" static int nvme_verify_chr(int fd) { static struct stat nvme_stat; int err = fstat(fd, &nvme_stat); if (err < 0) { perror("fstat"); return errno; } if (!S_ISCHR(nvme_stat.st_mode)) { fprintf(stderr, "Error: requesting reset on non-controller handle\n"); return ENOTBLK; } return 0; } int nvme_subsystem_reset(int fd) { int ret; ret = nvme_verify_chr(fd); if (ret) return ret; return ioctl(fd, NVME_IOCTL_SUBSYS_RESET); } int nvme_reset_controller(int fd) { int ret; ret = nvme_verify_chr(fd); if (ret) return ret; return ioctl(fd, NVME_IOCTL_RESET); } int nvme_ns_rescan(int fd) { int ret; ret = nvme_verify_chr(fd); if (ret) return ret; return ioctl(fd, NVME_IOCTL_RESCAN); } int nvme_get_nsid(int fd) { static struct stat nvme_stat; int err = fstat(fd, &nvme_stat); if (err < 0) return -errno; if (!S_ISBLK(nvme_stat.st_mode)) { fprintf(stderr, "Error: requesting namespace-id from non-block device\n"); errno = ENOTBLK; return -errno; } return ioctl(fd, NVME_IOCTL_ID); } int nvme_submit_passthru(int fd, unsigned long ioctl_cmd, struct nvme_passthru_cmd *cmd) { return ioctl(fd, ioctl_cmd, cmd); } static int nvme_submit_admin_passthru(int fd, struct nvme_passthru_cmd *cmd) { return ioctl(fd, NVME_IOCTL_ADMIN_CMD, cmd); } static int nvme_submit_io_passthru(int fd, struct nvme_passthru_cmd *cmd) { return ioctl(fd, NVME_IOCTL_IO_CMD, cmd); } int nvme_passthru(int fd, unsigned long ioctl_cmd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, void *metadata, __u32 timeout_ms, __u32 *result) { struct nvme_passthru_cmd cmd = { .opcode = opcode, .flags = flags, .rsvd1 = rsvd, .nsid = nsid, .cdw2 = cdw2, .cdw3 = cdw3, .metadata = (__u64)(uintptr_t) metadata, .addr = (__u64)(uintptr_t) data, .metadata_len = metadata_len, .data_len = data_len, .cdw10 = cdw10, .cdw11 = cdw11, .cdw12 = cdw12, .cdw13 = cdw13, .cdw14 = cdw14, .cdw15 = cdw15, .timeout_ms = timeout_ms, .result = 0, }; int err; err = nvme_submit_passthru(fd, ioctl_cmd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_io(int fd, __u8 opcode, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata) { struct nvme_user_io io = { .opcode = opcode, .flags = 0, .control = control, .nblocks = nblocks, .rsvd = 0, .metadata = (__u64)(uintptr_t) metadata, .addr = (__u64)(uintptr_t) data, .slba = slba, .dsmgmt = dsmgmt, .reftag = reftag, .appmask = appmask, .apptag = apptag, }; return ioctl(fd, NVME_IOCTL_SUBMIT_IO, &io); } int nvme_read(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata) { return nvme_io(fd, nvme_cmd_read, slba, nblocks, control, dsmgmt, reftag, apptag, appmask, data, metadata); } int nvme_write(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata) { return nvme_io(fd, nvme_cmd_write, slba, nblocks, control, dsmgmt, reftag, apptag, appmask, data, metadata); } int nvme_compare(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata) { return nvme_io(fd, nvme_cmd_compare, slba, nblocks, control, dsmgmt, reftag, apptag, appmask, data, metadata); } int nvme_verify(int fd, __u32 nsid, __u64 slba, __u16 nblocks, __u16 control, __u32 reftag, __u16 apptag, __u16 appmask) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_verify, .nsid = nsid, .cdw10 = slba & 0xffffffff, .cdw11 = slba >> 32, .cdw12 = nblocks | (control << 16), .cdw14 = reftag, .cdw15 = apptag | (appmask << 16), }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_passthru_io(int fd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, void *metadata, __u32 timeout_ms) { return nvme_passthru(fd, NVME_IOCTL_IO_CMD, opcode, flags, rsvd, nsid, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, cdw14, cdw15, data_len, data, metadata_len, metadata, timeout_ms, NULL); } int nvme_write_zeros(int fd, __u32 nsid, __u64 slba, __u16 nlb, __u16 control, __u32 reftag, __u16 apptag, __u16 appmask) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_write_zeroes, .nsid = nsid, .cdw10 = slba & 0xffffffff, .cdw11 = slba >> 32, .cdw12 = nlb | (control << 16), .cdw14 = reftag, .cdw15 = apptag | (appmask << 16), }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_write_uncorrectable(int fd, __u32 nsid, __u64 slba, __u16 nlb) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_write_uncor, .nsid = nsid, .cdw10 = slba & 0xffffffff, .cdw11 = slba >> 32, .cdw12 = nlb, }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_flush(int fd, __u32 nsid) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_flush, .nsid = nsid, }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_dsm(int fd, __u32 nsid, __u32 cdw11, struct nvme_dsm_range *dsm, __u16 nr_ranges) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_dsm, .nsid = nsid, .addr = (__u64)(uintptr_t) dsm, .data_len = nr_ranges * sizeof(*dsm), .cdw10 = nr_ranges - 1, .cdw11 = cdw11, }; return nvme_submit_io_passthru(fd, &cmd); } struct nvme_dsm_range *nvme_setup_dsm_range(__u32 *ctx_attrs, __u32 *llbas, __u64 *slbas, __u16 nr_ranges) { int i; struct nvme_dsm_range *dsm = malloc(nr_ranges * sizeof(*dsm)); if (!dsm) { fprintf(stderr, "malloc: %s\n", strerror(errno)); return NULL; } for (i = 0; i < nr_ranges; i++) { dsm[i].cattr = cpu_to_le32(ctx_attrs[i]); dsm[i].nlb = cpu_to_le32(llbas[i]); dsm[i].slba = cpu_to_le64(slbas[i]); } return dsm; } int nvme_resv_acquire(int fd, __u32 nsid, __u8 rtype, __u8 racqa, bool iekey, __u64 crkey, __u64 nrkey) { __le64 payload[2] = { cpu_to_le64(crkey), cpu_to_le64(nrkey) }; __u32 cdw10 = (racqa & 0x7) | (iekey ? 1 << 3 : 0) | rtype << 8; struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_resv_acquire, .nsid = nsid, .cdw10 = cdw10, .addr = (__u64)(uintptr_t) (payload), .data_len = sizeof(payload), }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_resv_register(int fd, __u32 nsid, __u8 rrega, __u8 cptpl, bool iekey, __u64 crkey, __u64 nrkey) { __le64 payload[2] = { cpu_to_le64(crkey), cpu_to_le64(nrkey) }; __u32 cdw10 = (rrega & 0x7) | (iekey ? 1 << 3 : 0) | cptpl << 30; struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_resv_register, .nsid = nsid, .cdw10 = cdw10, .addr = (__u64)(uintptr_t) (payload), .data_len = sizeof(payload), }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_resv_release(int fd, __u32 nsid, __u8 rtype, __u8 rrela, bool iekey, __u64 crkey) { __le64 payload[1] = { cpu_to_le64(crkey) }; __u32 cdw10 = (rrela & 0x7) | (iekey ? 1 << 3 : 0) | rtype << 8; struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_resv_release, .nsid = nsid, .cdw10 = cdw10, .addr = (__u64)(uintptr_t) (payload), .data_len = sizeof(payload), }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_resv_report(int fd, __u32 nsid, __u32 numd, __u32 cdw11, void *data) { struct nvme_passthru_cmd cmd = { .opcode = nvme_cmd_resv_report, .nsid = nsid, .cdw10 = numd, .cdw11 = cdw11, .addr = (__u64)(uintptr_t) data, .data_len = (numd + 1) << 2, }; return nvme_submit_io_passthru(fd, &cmd); } int nvme_identify13(int fd, __u32 nsid, __u32 cdw10, __u32 cdw11, void *data) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_identify, .nsid = nsid, .addr = (__u64)(uintptr_t) data, .data_len = NVME_IDENTIFY_DATA_SIZE, .cdw10 = cdw10, .cdw11 = cdw11, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_identify(int fd, __u32 nsid, __u32 cdw10, void *data) { return nvme_identify13(fd, nsid, cdw10, 0, data); } int nvme_identify_ctrl(int fd, void *data) { return nvme_identify(fd, 0, 1, data); } int nvme_identify_ns(int fd, __u32 nsid, bool present, void *data) { int cns = present ? NVME_ID_CNS_NS_PRESENT : NVME_ID_CNS_NS; return nvme_identify(fd, nsid, cns, data); } int nvme_identify_ns_list(int fd, __u32 nsid, bool all, void *data) { int cns = all ? NVME_ID_CNS_NS_PRESENT_LIST : NVME_ID_CNS_NS_ACTIVE_LIST; return nvme_identify(fd, nsid, cns, data); } int nvme_identify_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data) { int cns = nsid ? NVME_ID_CNS_CTRL_NS_LIST : NVME_ID_CNS_CTRL_LIST; return nvme_identify(fd, nsid, (cntid << 16) | cns, data); } int nvme_identify_secondary_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data) { return nvme_identify(fd, nsid, (cntid << 16) | NVME_ID_CNS_SCNDRY_CTRL_LIST, data); } int nvme_identify_ns_descs(int fd, __u32 nsid, void *data) { return nvme_identify(fd, nsid, NVME_ID_CNS_NS_DESC_LIST, data); } int nvme_identify_nvmset(int fd, __u16 nvmset_id, void *data) { return nvme_identify13(fd, 0, NVME_ID_CNS_NVMSET_LIST, nvmset_id, data); } int nvme_identify_ns_granularity(int fd, void *data) { return nvme_identify13(fd, 0, NVME_ID_CNS_NS_GRANULARITY, 0, data); } int nvme_identify_uuid(int fd, void *data) { return nvme_identify(fd, 0, NVME_ID_CNS_UUID_LIST, data); } int nvme_get_log14(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, __u16 lsi, bool rae, __u8 uuid_ix, __u32 data_len, void *data) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_get_log_page, .nsid = nsid, .addr = (__u64)(uintptr_t) data, .data_len = data_len, }; __u32 numd = (data_len >> 2) - 1; __u16 numdu = numd >> 16, numdl = numd & 0xffff; cmd.cdw10 = log_id | (numdl << 16) | (rae ? 1 << 15 : 0); if (lsp) cmd.cdw10 |= lsp << 8; cmd.cdw11 = numdu | (lsi << 16); cmd.cdw12 = lpo; cmd.cdw13 = (lpo >> 32); cmd.cdw14 = uuid_ix; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_get_log(int fd, __u32 nsid, __u8 log_id, bool rae, __u32 data_len, void *data) { void *ptr = data; __u32 offset = 0, xfer_len = data_len; int ret; /* * 4k is the smallest possible transfer unit, so by * restricting ourselves for 4k transfers we avoid having * to check the MDTS value of the controller. */ do { xfer_len = data_len - offset; if (xfer_len > 4096) xfer_len = 4096; ret = nvme_get_log13(fd, nsid, log_id, NVME_NO_LOG_LSP, offset, 0, rae, xfer_len, ptr); if (ret) return ret; offset += xfer_len; ptr += xfer_len; } while (offset < data_len); return 0; } int nvme_get_telemetry_log(int fd, void *lp, int generate_report, int ctrl_init, size_t log_page_size, __u64 offset) { if (ctrl_init) return nvme_get_log13(fd, NVME_NSID_ALL, NVME_LOG_TELEMETRY_CTRL, NVME_NO_LOG_LSP, offset, 0, 1, log_page_size, lp); if (generate_report) return nvme_get_log13(fd, NVME_NSID_ALL, NVME_LOG_TELEMETRY_HOST, NVME_TELEM_LSP_CREATE, offset, 0, 1, log_page_size, lp); else return nvme_get_log13(fd, NVME_NSID_ALL, NVME_LOG_TELEMETRY_HOST, NVME_NO_LOG_LSP, offset, 0, 1, log_page_size, lp); } int nvme_fw_log(int fd, struct nvme_firmware_log_page *fw_log) { return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_FW_SLOT, true, sizeof(*fw_log), fw_log); } int nvme_changed_ns_list_log(int fd, struct nvme_changed_ns_list_log *changed_ns_list_log) { return nvme_get_log(fd, 0, NVME_LOG_CHANGED_NS, true, sizeof(changed_ns_list_log->log), changed_ns_list_log->log); } int nvme_error_log(int fd, int entries, struct nvme_error_log_page *err_log) { return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_ERROR, false, entries * sizeof(*err_log), err_log); } int nvme_endurance_log(int fd, __u16 group_id, struct nvme_endurance_group_log *endurance_log) { return nvme_get_log13(fd, 0, NVME_LOG_ENDURANCE_GROUP, 0, 0, group_id, 0, sizeof(*endurance_log), endurance_log); } int nvme_smart_log(int fd, __u32 nsid, struct nvme_smart_log *smart_log) { return nvme_get_log(fd, nsid, NVME_LOG_SMART, false, sizeof(*smart_log), smart_log); } int nvme_ana_log(int fd, void *ana_log, size_t ana_log_len, int rgo) { __u64 lpo = 0; return nvme_get_log13(fd, NVME_NSID_ALL, NVME_LOG_ANA, rgo, lpo, 0, true, ana_log_len, ana_log); } int nvme_self_test_log(int fd, struct nvme_self_test_log *self_test_log) { return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_DEVICE_SELF_TEST, false, sizeof(*self_test_log), self_test_log); } int nvme_effects_log(int fd, struct nvme_effects_log_page *effects_log) { return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_CMD_EFFECTS, false, sizeof(*effects_log), effects_log); } int nvme_discovery_log(int fd, struct nvmf_disc_rsp_page_hdr *log, __u32 size) { return nvme_get_log(fd, 0, NVME_LOG_DISC, false, size, log); } int nvme_sanitize_log(int fd, struct nvme_sanitize_log_page *sanitize_log) { return nvme_get_log(fd, 0, NVME_LOG_SANITIZE, false, sizeof(*sanitize_log), sanitize_log); } int nvme_feature(int fd, __u8 opcode, __u32 nsid, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 data_len, void *data, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = opcode, .nsid = nsid, .cdw10 = cdw10, .cdw11 = cdw11, .cdw12 = cdw12, .addr = (__u64)(uintptr_t) data, .data_len = data_len, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_set_feature(int fd, __u32 nsid, __u8 fid, __u32 value, __u32 cdw12, bool save, __u32 data_len, void *data, __u32 *result) { __u32 cdw10 = fid | (save ? 1 << 31 : 0); return nvme_feature(fd, nvme_admin_set_features, nsid, cdw10, value, cdw12, data_len, data, result); } /* * Perform the opposite operation of the byte-swapping code at the start of the * kernel function nvme_user_cmd(). */ static void nvme_to_passthru_cmd(struct nvme_passthru_cmd *pcmd, const struct nvme_command *ncmd) { assert(sizeof(*ncmd) < sizeof(*pcmd)); memset(pcmd, 0, sizeof(*pcmd)); pcmd->opcode = ncmd->common.opcode; pcmd->flags = ncmd->common.flags; pcmd->rsvd1 = ncmd->common.command_id; pcmd->nsid = le32_to_cpu(ncmd->common.nsid); pcmd->cdw2 = le32_to_cpu(ncmd->common.cdw2[0]); pcmd->cdw3 = le32_to_cpu(ncmd->common.cdw2[1]); /* Skip metadata and addr */ pcmd->cdw10 = le32_to_cpu(ncmd->common.cdw10[0]); pcmd->cdw11 = le32_to_cpu(ncmd->common.cdw10[1]); pcmd->cdw12 = le32_to_cpu(ncmd->common.cdw10[2]); pcmd->cdw13 = le32_to_cpu(ncmd->common.cdw10[3]); pcmd->cdw14 = le32_to_cpu(ncmd->common.cdw10[4]); pcmd->cdw15 = le32_to_cpu(ncmd->common.cdw10[5]); } int nvme_get_property(int fd, int offset, uint64_t *value) { struct nvme_passthru_cmd pcmd; struct nvmf_property_get_command pg = { .opcode = nvme_fabrics_command, .fctype = nvme_fabrics_type_property_get, .offset = cpu_to_le32(offset), .attrib = is_64bit_reg(offset), }; struct nvme_command gcmd; int err; gcmd.prop_get = pg; nvme_to_passthru_cmd(&pcmd, &gcmd); err = nvme_submit_admin_passthru(fd, &pcmd); if (!err) { /* * nvme_submit_admin_passthru() stores the lower 32 bits * of the property value in pcmd.result using CPU endianness. */ *value = pcmd.result; } return err; } int nvme_get_properties(int fd, void **pbar) { int offset; uint64_t value; int err; int size = getpagesize(); *pbar = malloc(size); if (!*pbar) { fprintf(stderr, "malloc: %s\n", strerror(errno)); return -ENOMEM; } memset(*pbar, 0xff, size); for (offset = NVME_REG_CAP; offset <= NVME_REG_CMBSZ;) { err = nvme_get_property(fd, offset, &value); if (err > 0 && (err & 0xff) == NVME_SC_INVALID_FIELD) { err = 0; value = -1; } else if (err) { free(*pbar); break; } if (is_64bit_reg(offset)) { *(uint64_t *)(*pbar + offset) = value; offset += 8; } else { *(uint32_t *)(*pbar + offset) = value; offset += 4; } } return err; } int nvme_set_property(int fd, int offset, uint64_t value) { struct nvmf_property_set_command ps = { .opcode = nvme_fabrics_command, .fctype = nvme_fabrics_type_property_set, .offset = cpu_to_le32(offset), .value = cpu_to_le64(value), .attrib = is_64bit_reg(offset), }; struct nvme_command scmd; struct nvme_passthru_cmd pcmd; scmd.prop_set = ps; nvme_to_passthru_cmd(&pcmd, &scmd); return nvme_submit_admin_passthru(fd, &pcmd); } int nvme_get_feature(int fd, __u32 nsid, __u8 fid, __u8 sel, __u32 cdw11, __u32 data_len, void *data, __u32 *result) { __u32 cdw10 = fid | sel << 8; return nvme_feature(fd, nvme_admin_get_features, nsid, cdw10, cdw11, 0, data_len, data, result); } int nvme_format(int fd, __u32 nsid, __u8 lbaf, __u8 ses, __u8 pi, __u8 pil, __u8 ms, __u32 timeout) { __u32 cdw10 = lbaf | ms << 4 | pi << 5 | pil << 8 | ses << 9; struct nvme_admin_cmd cmd = { .opcode = nvme_admin_format_nvm, .nsid = nsid, .cdw10 = cdw10, .timeout_ms = timeout, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, __u8 dps, __u8 nmic, __u32 timeout, __u32 *result) { struct nvme_id_ns ns = { .nsze = cpu_to_le64(nsze), .ncap = cpu_to_le64(ncap), .flbas = flbas, .dps = dps, .nmic = nmic, }; struct nvme_admin_cmd cmd = { .opcode = nvme_admin_ns_mgmt, .addr = (__u64)(uintptr_t) ((void *)&ns), .cdw10 = 0, .data_len = 0x1000, .timeout_ms = timeout, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_ns_delete(int fd, __u32 nsid, __u32 timeout) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_ns_mgmt, .nsid = nsid, .cdw10 = 1, .timeout_ms = timeout, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_ns_attachment(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist, bool attach) { int i; __u8 buf[0x1000]; struct nvme_controller_list *cntlist = (struct nvme_controller_list *)buf; struct nvme_admin_cmd cmd = { .opcode = nvme_admin_ns_attach, .nsid = nsid, .addr = (__u64)(uintptr_t) cntlist, .cdw10 = attach ? 0 : 1, .data_len = 0x1000, }; memset(buf, 0, sizeof(buf)); cntlist->num = cpu_to_le16(num_ctrls); for (i = 0; i < num_ctrls; i++) cntlist->identifier[i] = cpu_to_le16(ctrlist[i]); return nvme_submit_admin_passthru(fd, &cmd); } int nvme_ns_attach_ctrls(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist) { return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, true); } int nvme_ns_detach_ctrls(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist) { return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, false); } int nvme_fw_download(int fd, __u32 offset, __u32 data_len, void *data) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_download_fw, .addr = (__u64)(uintptr_t) data, .data_len = data_len, .cdw10 = (data_len >> 2) - 1, .cdw11 = offset >> 2, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_fw_commit(int fd, __u8 slot, __u8 action, __u8 bpid) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_activate_fw, .cdw10 = (bpid << 31) | (action << 3) | slot, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_sec_send(int fd, __u32 nsid, __u8 nssf, __u16 spsp, __u8 secp, __u32 tl, __u32 data_len, void *data, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_security_send, .addr = (__u64)(uintptr_t) data, .data_len = data_len, .nsid = nsid, .cdw10 = secp << 24 | spsp << 8 | nssf, .cdw11 = tl, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_sec_recv(int fd, __u32 nsid, __u8 nssf, __u16 spsp, __u8 secp, __u32 al, __u32 data_len, void *data, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_security_recv, .nsid = nsid, .cdw10 = secp << 24 | spsp << 8 | nssf, .cdw11 = al, .addr = (__u64)(uintptr_t) data, .data_len = data_len, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_get_lba_status(int fd, __u64 slba, __u32 mndw, __u8 atype, __u16 rl, void *data) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_get_lba_status, .addr = (__u64)(uintptr_t) data, .cdw10 = slba & 0xffffffff, .cdw11 = slba >> 32, .cdw12 = mndw, .cdw13 = (atype << 24) | rl, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_dir_send(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_directive_send, .addr = (__u64)(uintptr_t) data, .data_len = data_len, .nsid = nsid, .cdw10 = data_len? (data_len >> 2) - 1 : 0, .cdw11 = dspec << 16 | dtype << 8 | doper, .cdw12 = dw12, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_dir_recv(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_directive_recv, .addr = (__u64)(uintptr_t) data, .data_len = data_len, .nsid = nsid, .cdw10 = data_len? (data_len >> 2) - 1 : 0, .cdw11 = dspec << 16 | dtype << 8 | doper, .cdw12 = dw12, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } int nvme_sanitize(int fd, __u8 sanact, __u8 ause, __u8 owpass, __u8 oipbp, __u8 no_dealloc, __u32 ovrpat) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_sanitize_nvm, .cdw10 = no_dealloc << 9 | oipbp << 8 | owpass << NVME_SANITIZE_OWPASS_SHIFT | ause << 3 | sanact, .cdw11 = ovrpat, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_self_test_start(int fd, __u32 nsid, __u32 cdw10) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_dev_self_test, .nsid = nsid, .cdw10 = cdw10, }; return nvme_submit_admin_passthru(fd, &cmd); } int nvme_virtual_mgmt(int fd, __u32 cdw10, __u32 cdw11, __u32 *result) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_virtual_mgmt, .cdw10 = cdw10, .cdw11 = cdw11, }; int err; err = nvme_submit_admin_passthru(fd, &cmd); if (!err && result) *result = cmd.result; return err; } multipath-tools-0.11.1/libmultipath/nvme/nvme-ioctl.h000066400000000000000000000156051475246302400226460ustar00rootroot00000000000000#ifndef NVME_NVME_IOCTL_H_INCLUDED #define NVME_NVME_IOCTL_H_INCLUDED #include #include #include "linux/nvme_ioctl.h" #include "nvme.h" #define NVME_IOCTL_TIMEOUT 120000 /* in milliseconds */ int nvme_get_nsid(int fd); /* Generic passthrough */ int nvme_submit_passthru(int fd, unsigned long ioctl_cmd, struct nvme_passthru_cmd *cmd); int nvme_passthru(int fd, unsigned long ioctl_cmd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, void *metadata, __u32 timeout_ms, __u32 *result); /* NVME_SUBMIT_IO */ int nvme_io(int fd, __u8 opcode, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata); int nvme_read(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata); int nvme_write(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata); int nvme_compare(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata); int nvme_verify(int fd, __u32 nsid, __u64 slba, __u16 nblocks, __u16 control, __u32 reftag, __u16 apptag, __u16 appmask); /* NVME_IO_CMD */ int nvme_passthru_io(int fd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, void *metadata, __u32 timeout); int nvme_write_zeros(int fd, __u32 nsid, __u64 slba, __u16 nlb, __u16 control, __u32 reftag, __u16 apptag, __u16 appmask); int nvme_write_uncorrectable(int fd, __u32 nsid, __u64 slba, __u16 nlb); int nvme_flush(int fd, __u32 nsid); int nvme_dsm(int fd, __u32 nsid, __u32 cdw11, struct nvme_dsm_range *dsm, __u16 nr_ranges); struct nvme_dsm_range *nvme_setup_dsm_range(__u32 *ctx_attrs, __u32 *llbas, __u64 *slbas, __u16 nr_ranges); int nvme_resv_acquire(int fd, __u32 nsid, __u8 rtype, __u8 racqa, bool iekey, __u64 crkey, __u64 nrkey); int nvme_resv_register(int fd, __u32 nsid, __u8 rrega, __u8 cptpl, bool iekey, __u64 crkey, __u64 nrkey); int nvme_resv_release(int fd, __u32 nsid, __u8 rtype, __u8 rrela, bool iekey, __u64 crkey); int nvme_resv_report(int fd, __u32 nsid, __u32 numd, __u32 cdw11, void *data); int nvme_identify13(int fd, __u32 nsid, __u32 cdw10, __u32 cdw11, void *data); int nvme_identify(int fd, __u32 nsid, __u32 cdw10, void *data); int nvme_identify_ctrl(int fd, void *data); int nvme_identify_ns(int fd, __u32 nsid, bool present, void *data); int nvme_identify_ns_list(int fd, __u32 nsid, bool all, void *data); int nvme_identify_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data); int nvme_identify_ns_descs(int fd, __u32 nsid, void *data); int nvme_identify_nvmset(int fd, __u16 nvmset_id, void *data); int nvme_identify_uuid(int fd, void *data); int nvme_identify_secondary_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data); int nvme_identify_ns_granularity(int fd, void *data); int nvme_get_log(int fd, __u32 nsid, __u8 log_id, bool rae, __u32 data_len, void *data); int nvme_get_log14(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, __u16 group_id, bool rae, __u8 uuid_ix, __u32 data_len, void *data); static inline int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, __u16 lsi, bool rae, __u32 data_len, void *data) { return nvme_get_log14(fd, nsid, log_id, lsp, lpo, lsi, rae, 0, data_len, data); } int nvme_get_telemetry_log(int fd, void *lp, int generate_report, int ctrl_gen, size_t log_page_size, __u64 offset); int nvme_fw_log(int fd, struct nvme_firmware_log_page *fw_log); int nvme_changed_ns_list_log(int fd, struct nvme_changed_ns_list_log *changed_ns_list_log); int nvme_error_log(int fd, int entries, struct nvme_error_log_page *err_log); int nvme_smart_log(int fd, __u32 nsid, struct nvme_smart_log *smart_log); int nvme_ana_log(int fd, void *ana_log, size_t ana_log_len, int rgo); int nvme_effects_log(int fd, struct nvme_effects_log_page *effects_log); int nvme_discovery_log(int fd, struct nvmf_disc_rsp_page_hdr *log, __u32 size); int nvme_sanitize_log(int fd, struct nvme_sanitize_log_page *sanitize_log); int nvme_endurance_log(int fd, __u16 group_id, struct nvme_endurance_group_log *endurance_log); int nvme_feature(int fd, __u8 opcode, __u32 nsid, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 data_len, void *data, __u32 *result); int nvme_set_feature(int fd, __u32 nsid, __u8 fid, __u32 value, __u32 cdw12, bool save, __u32 data_len, void *data, __u32 *result); int nvme_get_feature(int fd, __u32 nsid, __u8 fid, __u8 sel, __u32 cdw11, __u32 data_len, void *data, __u32 *result); int nvme_format(int fd, __u32 nsid, __u8 lbaf, __u8 ses, __u8 pi, __u8 pil, __u8 ms, __u32 timeout); int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, __u8 dps, __u8 nmic, __u32 timeout, __u32 *result); int nvme_ns_delete(int fd, __u32 nsid, __u32 timeout); int nvme_ns_attachment(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist, bool attach); int nvme_ns_attach_ctrls(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist); int nvme_ns_detach_ctrls(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist); int nvme_fw_download(int fd, __u32 offset, __u32 data_len, void *data); int nvme_fw_commit(int fd, __u8 slot, __u8 action, __u8 bpid); int nvme_sec_send(int fd, __u32 nsid, __u8 nssf, __u16 spsp, __u8 secp, __u32 tl, __u32 data_len, void *data, __u32 *result); int nvme_sec_recv(int fd, __u32 nsid, __u8 nssf, __u16 spsp, __u8 secp, __u32 al, __u32 data_len, void *data, __u32 *result); int nvme_subsystem_reset(int fd); int nvme_reset_controller(int fd); int nvme_ns_rescan(int fd); int nvme_get_lba_status(int fd, __u64 slba, __u32 mndw, __u8 atype, __u16 rl, void *data); int nvme_dir_send(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result); int nvme_dir_recv(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result); int nvme_get_properties(int fd, void **pbar); int nvme_set_property(int fd, int offset, uint64_t value); int nvme_get_property(int fd, int offset, uint64_t *value); int nvme_sanitize(int fd, __u8 sanact, __u8 ause, __u8 owpass, __u8 oipbp, __u8 no_dealloc, __u32 ovrpat); int nvme_self_test_start(int fd, __u32 nsid, __u32 cdw10); int nvme_self_test_log(int fd, struct nvme_self_test_log *self_test_log); int nvme_virtual_mgmt(int fd, __u32 cdw10, __u32 cdw11, __u32 *result); #endif /* NVME_NVME_IOCTL_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme/nvme.h000066400000000000000000000124201475246302400215260ustar00rootroot00000000000000/* * Definitions for the NVM Express interface * Copyright (c) 2011-2014, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #ifndef NVME_NVME_H_INCLUDED #define NVME_NVME_H_INCLUDED #include #include #include #include "plugin.h" #include "json.h" #define unlikely(x) x #ifdef LIBUUID #include #else typedef struct { uint8_t b[16]; } uuid_t; #endif #include "linux/nvme.h" struct nvme_effects_log_page { __le32 acs[256]; __le32 iocs[256]; __u8 resv[2048]; }; struct nvme_error_log_page { __le64 error_count; __le16 sqid; __le16 cmdid; __le16 status_field; __le16 parm_error_location; __le64 lba; __le32 nsid; __u8 vs; __u8 resv[3]; __le64 cs; __u8 resv2[24]; }; struct nvme_firmware_log_page { __u8 afi; __u8 resv[7]; __u64 frs[7]; __u8 resv2[448]; }; /* idle and active power scales occupy the last 2 bits of the field */ #define POWER_SCALE(s) ((s) >> 6) struct nvme_host_mem_buffer { __u32 hsize; __u32 hmdlal; __u32 hmdlau; __u32 hmdlec; __u8 rsvd16[4080]; }; struct nvme_auto_pst { __u32 data; __u32 rsvd32; }; struct nvme_timestamp { __u8 timestamp[6]; __u8 attr; __u8 rsvd; }; struct nvme_controller_list { __le16 num; __le16 identifier[]; }; struct nvme_secondary_controller_entry { __le16 scid; /* Secondary Controller Identifier */ __le16 pcid; /* Primary Controller Identifier */ __u8 scs; /* Secondary Controller State */ __u8 rsvd5[3]; __le16 vfn; /* Virtual Function Number */ __le16 nvq; /* Number of VQ Flexible Resources Assigned */ __le16 nvi; /* Number of VI Flexible Resources Assigned */ __u8 rsvd14[18]; }; struct nvme_secondary_controllers_list { __u8 num; __u8 rsvd[31]; struct nvme_secondary_controller_entry sc_entry[127]; }; struct nvme_bar_cap { __u16 mqes; __u8 ams_cqr; __u8 to; __u16 bps_css_nssrs_dstrd; __u8 mpsmax_mpsmin; __u8 rsvd_pmrs; }; #ifdef __CHECKER__ #define __force __attribute__((force)) #else #define __force #endif static inline __le16 cpu_to_le16(uint16_t x) { return (__force __le16)htole16(x); } static inline __le32 cpu_to_le32(uint32_t x) { return (__force __le32)htole32(x); } static inline __le64 cpu_to_le64(uint64_t x) { return (__force __le64)htole64(x); } static inline uint16_t le16_to_cpu(__le16 x) { return le16toh((__force __u16)x); } static inline uint32_t le32_to_cpu(__le32 x) { return le32toh((__force __u32)x); } static inline uint64_t le64_to_cpu(__le64 x) { return le64toh((__force __u64)x); } #define MAX_LIST_ITEMS 256 struct list_item { char node[1024]; struct nvme_id_ctrl ctrl; int nsid; struct nvme_id_ns ns; unsigned block; }; struct ctrl_list_item { char *name; char *address; char *transport; char *state; char *ana_state; char *subsysnqn; char *traddr; char *trsvcid; char *host_traddr; }; struct subsys_list_item { char *name; char *subsysnqn; int nctrls; struct ctrl_list_item *ctrls; }; enum { NORMAL, JSON, BINARY, }; struct connect_args { char *subsysnqn; char *transport; char *traddr; char *trsvcid; char *host_traddr; }; #define SYS_NVME "/sys/class/nvme" bool ctrl_matches_connectargs(char *name, struct connect_args *args); char *find_ctrl_with_connectargs(struct connect_args *args); char *__parse_connect_arg(char *conargs, const char delim, const char *fieldnm); extern const char *conarg_nqn; extern const char *conarg_transport; extern const char *conarg_traddr; extern const char *conarg_trsvcid; extern const char *conarg_host_traddr; void register_extension(struct plugin *plugin); #include "argconfig.h" int parse_and_open(int argc, char **argv, const char *desc, const struct argconfig_commandline_options *clo, void *cfg, size_t size); extern const char *devicename; int __id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin, void (*vs)(__u8 *vs, struct json_object *root)); int validate_output_format(char *format); struct subsys_list_item *get_subsys_list(int *subcnt, char *subsysnqn, __u32 nsid); void free_subsys_list(struct subsys_list_item *slist, int n); char *nvme_char_from_block(char *block); /* * is_64bit_reg - It checks whether given offset of the controller register is * 64bit or not. * @offset: offset of controller register field in bytes * * It gives true if given offset is 64bit register, otherwise it returns false. * * Notes: This function does not care about transport so that the offset is * not going to be checked inside of this function for the unsupported fields * in a specific transport. For example, BPMBL(Boot Partition Memory Buffer * Location) register is not supported by fabrics, but it can be checked here. */ static inline bool is_64bit_reg(__u32 offset) { if (offset == NVME_REG_CAP || offset == NVME_REG_ASQ || offset == NVME_REG_ACQ || offset == NVME_REG_BPMBL) return true; return false; } #endif /* NVME_NVME_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/nvme/plugin.h000066400000000000000000000013061475246302400220600ustar00rootroot00000000000000#ifndef NVME_PLUGIN_H_INCLUDED #define NVME_PLUGIN_H_INCLUDED #include struct program { const char *name; const char *version; const char *usage; const char *desc; const char *more; struct command **commands; struct plugin *extensions; }; struct plugin { const char *name; const char *desc; struct command **commands; struct program *parent; struct plugin *next; struct plugin *tail; }; struct command { char *name; char *help; int (*fn)(int argc, char **argv, struct command *command, struct plugin *plugin); char *alias; }; void usage(struct plugin *plugin); void general_help(struct plugin *plugin); int handle_plugin(int argc, char **argv, struct plugin *plugin); #endif multipath-tools-0.11.1/libmultipath/pgpolicies.c000066400000000000000000000151521475246302400217520ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui */ #include #include #include #include #include "checkers.h" #include "util.h" #include "vector.h" #include "structs.h" #include "pgpolicies.h" #include "switchgroup.h" int get_pgpolicy_id(char * str) { if (0 == strncmp(str, "failover", 8)) return FAILOVER; if (0 == strncmp(str, "multibus", 8)) return MULTIBUS; if (0 == strncmp(str, "group_by_serial", 15)) return GROUP_BY_SERIAL; if (0 == strncmp(str, "group_by_prio", 13)) return GROUP_BY_PRIO; if (0 == strncmp(str, "group_by_node_name", 18)) return GROUP_BY_NODE_NAME; if (0 == strncmp(str, "group_by_tpg", 12)) return GROUP_BY_TPG; return IOPOLICY_UNDEF; } const char *get_pgpolicy_name(int id) { switch (id) { case FAILOVER: return "failover"; case MULTIBUS: return "multibus"; case GROUP_BY_SERIAL: return "group_by_serial"; case GROUP_BY_PRIO: return "group_by_prio"; case GROUP_BY_NODE_NAME: return "group_by_node_name"; case GROUP_BY_TPG: return "group_by_tpg"; } return "undefined"; /* IOPOLICY_UNDEF */ } void sort_pathgroups (struct multipath *mp) { int i, j; struct pathgroup * pgp1, * pgp2; if (!mp->pg) return; vector_foreach_slot(mp->pg, pgp1, i) { path_group_prio_update(pgp1); for (j = i - 1; j >= 0; j--) { pgp2 = VECTOR_SLOT(mp->pg, j); if (!pgp2) continue; if (pgp2->marginal < pgp1->marginal || (pgp2->marginal == pgp1->marginal && (pgp2->priority > pgp1->priority || (pgp2->priority == pgp1->priority && pgp2->enabled_paths >= pgp1->enabled_paths)))) { vector_move_up(mp->pg, i, j + 1); break; } } if (j < 0 && i != 0) vector_move_up(mp->pg, i, 0); } } static int split_marginal_paths(vector paths, vector *normal_p, vector *marginal_p) { int i; int has_marginal = 0; int has_normal = 0; struct path *pp; vector normal = NULL; vector marginal = NULL; *normal_p = *marginal_p = NULL; vector_foreach_slot(paths, pp, i) { if (pp->marginal) has_marginal = 1; else has_normal = 1; } if (!has_marginal || !has_normal) return -1; normal = vector_alloc(); marginal = vector_alloc(); if (!normal || !marginal) goto fail; vector_foreach_slot(paths, pp, i) { if (pp->marginal) { if (store_path(marginal, pp)) goto fail; } else { if (store_path(normal, pp)) goto fail; } } *normal_p = normal; *marginal_p = marginal; return 0; fail: vector_free(normal); vector_free(marginal); return -1; } int group_paths(struct multipath *mp, int marginal_pathgroups) { vector normal, marginal; struct path *pp; int i; if (!mp->pg) mp->pg = vector_alloc(); if (!mp->pg) return 1; if (VECTOR_SIZE(mp->paths) == 0) goto out; if (!mp->pgpolicyfn) goto fail; /* Reset pgindex, we're going to invalidate it */ vector_foreach_slot(mp->paths, pp, i) pp->pgindex = 0; if (!marginal_pathgroups || split_marginal_paths(mp->paths, &normal, &marginal) != 0) { if (mp->pgpolicyfn(mp, mp->paths) != 0) goto fail; } else { if (mp->pgpolicyfn(mp, normal) != 0) goto fail_marginal; if (mp->pgpolicyfn(mp, marginal) != 0) goto fail_marginal; vector_free(normal); vector_free(marginal); } sort_pathgroups(mp); out: vector_free(mp->paths); mp->paths = NULL; return 0; fail_marginal: vector_free(normal); vector_free(marginal); fail: vector_free(mp->pg); mp->pg = NULL; return 1; } typedef bool (path_match_fn)(struct path *pp1, struct path *pp2); bool node_names_match(struct path *pp1, struct path *pp2) { return (strncmp(pp1->tgt_node_name, pp2->tgt_node_name, NODE_NAME_SIZE) == 0); } bool serials_match(struct path *pp1, struct path *pp2) { return (strncmp(pp1->serial, pp2->serial, SERIAL_SIZE) == 0); } bool prios_match(struct path *pp1, struct path *pp2) { return (pp1->priority == pp2->priority); } bool tpg_match(struct path *pp1, struct path *pp2) { return (pp1->tpg_id == pp2->tpg_id); } int group_by_match(struct multipath * mp, vector paths, bool (*path_match_fn)(struct path *, struct path *)) { int i, j; struct bitfield *bitmap; struct path * pp; struct pathgroup * pgp; struct path * pp2; /* init the bitmap */ bitmap = alloc_bitfield(VECTOR_SIZE(paths)); if (!bitmap) goto out; for (i = 0; i < VECTOR_SIZE(paths); i++) { if (is_bit_set_in_bitfield(i, bitmap)) continue; pp = VECTOR_SLOT(paths, i); /* here, we really got a new pg */ pgp = alloc_pathgroup(); if (!pgp) goto out1; if (add_pathgroup(mp, pgp)) goto out2; /* feed the first path */ if (store_path(pgp->paths, pp)) goto out1; set_bit_in_bitfield(i, bitmap); for (j = i + 1; j < VECTOR_SIZE(paths); j++) { if (is_bit_set_in_bitfield(j, bitmap)) continue; pp2 = VECTOR_SLOT(paths, j); if (path_match_fn(pp, pp2)) { if (store_path(pgp->paths, pp2)) goto out1; set_bit_in_bitfield(j, bitmap); } } } free(bitmap); return 0; out2: free_pathgroup(pgp, KEEP_PATHS); out1: free(bitmap); out: free_pgvec(mp->pg, KEEP_PATHS); mp->pg = NULL; return 1; } /* * One path group per unique tgt_node_name present in the path vector */ int group_by_node_name(struct multipath * mp, vector paths) { return group_by_match(mp, paths, node_names_match); } /* * One path group per unique serial number present in the path vector */ int group_by_serial(struct multipath * mp, vector paths) { return group_by_match(mp, paths, serials_match); } /* * One path group per priority present in the path vector */ int group_by_prio(struct multipath *mp, vector paths) { return group_by_match(mp, paths, prios_match); } /* * One path group per alua target port group present in the path vector */ int group_by_tpg(struct multipath *mp, vector paths) { return group_by_match(mp, paths, tpg_match); } int one_path_per_group(struct multipath *mp, vector paths) { int i; struct path * pp; struct pathgroup * pgp; for (i = 0; i < VECTOR_SIZE(paths); i++) { pp = VECTOR_SLOT(paths, i); pgp = alloc_pathgroup(); if (!pgp) goto out; if (add_pathgroup(mp, pgp)) goto out1; if (store_path(pgp->paths, pp)) goto out; } return 0; out1: free_pathgroup(pgp, KEEP_PATHS); out: free_pgvec(mp->pg, KEEP_PATHS); mp->pg = NULL; return 1; } int one_group(struct multipath *mp, vector paths) /* aka multibus */ { int i; struct path * pp; struct pathgroup * pgp; pgp = alloc_pathgroup(); if (!pgp) goto out; if (add_pathgroup(mp, pgp)) goto out1; for (i = 0; i < VECTOR_SIZE(paths); i++) { pp = VECTOR_SLOT(paths, i); if (store_path(pgp->paths, pp)) goto out; } return 0; out1: free_pathgroup(pgp, KEEP_PATHS); out: free_pgvec(mp->pg, KEEP_PATHS); mp->pg = NULL; return 1; } multipath-tools-0.11.1/libmultipath/pgpolicies.h000066400000000000000000000012541475246302400217550ustar00rootroot00000000000000#ifndef PGPOLICIES_H_INCLUDED #define PGPOLICIES_H_INCLUDED #define POLICY_NAME_SIZE 32 /* Storage controllers capabilities */ enum iopolicies { IOPOLICY_UNDEF, FAILOVER, MULTIBUS, GROUP_BY_SERIAL, GROUP_BY_PRIO, GROUP_BY_NODE_NAME, GROUP_BY_TPG, }; int get_pgpolicy_id(char *); const char *get_pgpolicy_name (int); int group_paths(struct multipath *, int); /* * policies */ int one_path_per_group(struct multipath *, vector); int one_group(struct multipath *, vector); int group_by_serial(struct multipath *, vector); int group_by_prio(struct multipath *, vector); int group_by_node_name(struct multipath *, vector); int group_by_tpg(struct multipath *, vector); #endif multipath-tools-0.11.1/libmultipath/print.c000066400000000000000000001517361475246302400207610ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "dmparser.h" #include "config.h" #include "configure.h" #include "pgpolicies.h" #include "print.h" #include "defaults.h" #include "parser.h" #include "blacklist.h" #include "switchgroup.h" #include "devmapper.h" #include "uevent.h" #include "debug.h" #include "discovery.h" #include "util.h" #include "foreign.h" #include "strbuf.h" #include "sysfs.h" #define PRINT_PATH_LONG "%w %i %d %D %p %t %T %s %o" #define PRINT_PATH_INDENT "%i %d %D %t %T %o" #define PRINT_MAP_PROPS "size=%S features='%f' hwhandler='%h' wp=%r" #define PRINT_PG_INDENT "policy='%s' prio=%p status=%t" #define PRINT_JSON_MULTIPLIER 5 #define PRINT_JSON_MAJOR_VERSION 0 #define PRINT_JSON_MINOR_VERSION 1 #define PRINT_JSON_START_VERSION " \"major_version\": %d,\n" \ " \"minor_version\": %d,\n" #define PRINT_JSON_START_ELEM "{\n" #define PRINT_JSON_START_MAP " \"map\":" #define PRINT_JSON_START_MAPS "\"maps\": [" #define PRINT_JSON_START_PATHS "\"paths\": [" #define PRINT_JSON_START_GROUPS "\"path_groups\": [" #define PRINT_JSON_END_ELEM "}," #define PRINT_JSON_END_LAST_ELEM "}" #define PRINT_JSON_END_LAST "}\n" #define PRINT_JSON_END_ARRAY "]\n" #define PRINT_JSON_INDENT_N 3 #define PRINT_JSON_MAP "{\n" \ " \"name\" : \"%n\",\n" \ " \"uuid\" : \"%w\",\n" \ " \"sysfs\" : \"%d\",\n" \ " \"failback\" : \"%F\",\n" \ " \"queueing\" : \"%Q\",\n" \ " \"paths\" : %N,\n" \ " \"write_prot\" : \"%r\",\n" \ " \"dm_st\" : \"%t\",\n" \ " \"features\" : \"%f\",\n" \ " \"hwhandler\" : \"%h\",\n" \ " \"action\" : \"%A\",\n" \ " \"path_faults\" : %0,\n" \ " \"vend\" : \"%v\",\n" \ " \"prod\" : \"%p\",\n" \ " \"rev\" : \"%e\",\n" \ " \"switch_grp\" : %1,\n" \ " \"map_loads\" : %2,\n" \ " \"total_q_time\" : %3,\n" \ " \"q_timeouts\" : %4," #define PRINT_JSON_GROUP "{\n" \ " \"selector\" : \"%s\",\n" \ " \"pri\" : %p,\n" \ " \"dm_st\" : \"%t\",\n" \ " \"marginal_st\" : \"%M\"," #define PRINT_JSON_GROUP_NUM " \"group\" : %d,\n" #define PRINT_JSON_PATH "{\n" \ " \"dev\" : \"%d\",\n"\ " \"dev_t\" : \"%D\",\n" \ " \"dm_st\" : \"%t\",\n" \ " \"dev_st\" : \"%o\",\n" \ " \"chk_st\" : \"%T\",\n" \ " \"checker\" : \"%c\",\n" \ " \"pri\" : %p,\n" \ " \"host_wwnn\" : \"%N\",\n" \ " \"target_wwnn\" : \"%n\",\n" \ " \"host_wwpn\" : \"%R\",\n" \ " \"target_wwpn\" : \"%r\",\n" \ " \"host_adapter\" : \"%a\",\n" \ " \"lun_hex\" : \"%L\",\n" \ " \"marginal_st\" : \"%M\"" #define PROGRESS_LEN 10 struct path_data { char wildcard; char * header; int (*snprint)(struct strbuf *, const struct path * pp); }; struct multipath_data { char wildcard; char * header; int (*snprint)(struct strbuf *, const struct multipath * mpp); }; struct pathgroup_data { char wildcard; char * header; int (*snprint)(struct strbuf *, const struct pathgroup * pgp); }; #define MAX(x,y) (((x) > (y)) ? (x) : (y)) #define MIN(x,y) (((x) > (y)) ? (y) : (x)) /* * information printing helpers */ static int snprint_str(struct strbuf *buff, const char *str) { return append_strbuf_str(buff, str); } static int snprint_int (struct strbuf *buff, int val) { return print_strbuf(buff, "%i", val); } static int snprint_uint (struct strbuf *buff, unsigned int val) { return print_strbuf(buff, "%u", val); } static int snprint_size (struct strbuf *buff, unsigned long long size) { float s = (float)(size >> 1); /* start with KB */ char units[] = {'K','M','G','T','P'}; char *u = units; while (s >= 1024 && *u != 'P') { s = s / 1024; u++; } return print_strbuf(buff, "%.*f%c", s < 10, s, *u); } /* * multipath info printing functions */ static int snprint_name (struct strbuf *buff, const struct multipath * mpp) { if (mpp->alias) return append_strbuf_str(buff, mpp->alias); else return append_strbuf_str(buff, mpp->wwid); } static int snprint_sysfs (struct strbuf *buff, const struct multipath * mpp) { if (has_dm_info(mpp)) return print_strbuf(buff, "dm-%i", mpp->dmi.minor); else return append_strbuf_str(buff, "undef"); } static int snprint_ro (struct strbuf *buff, const struct multipath * mpp) { if (!has_dm_info(mpp)) return append_strbuf_str(buff, "undef"); if (mpp->dmi.read_only) return append_strbuf_str(buff, "ro"); else return append_strbuf_str(buff, "rw"); } static int snprint_progress (struct strbuf *buff, int cur, int total) { size_t initial_len = get_strbuf_len(buff); int rc; if (total > 0) { int i = PROGRESS_LEN * cur / total; int j = PROGRESS_LEN - i; if ((rc = fill_strbuf(buff, 'X', i)) < 0 || (rc = fill_strbuf(buff, '.', j) < 0)) { truncate_strbuf(buff, initial_len); return rc; } } if ((rc = print_strbuf(buff, " %i/%i", cur, total)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_failback (struct strbuf *buff, const struct multipath * mpp) { if (mpp->pgfailback == -FAILBACK_IMMEDIATE) return append_strbuf_str(buff, "immediate"); if (mpp->pgfailback == -FAILBACK_FOLLOWOVER) return append_strbuf_str(buff, "followover"); if (mpp->pgfailback == -FAILBACK_MANUAL) return append_strbuf_str(buff, "manual"); if (mpp->pgfailback == FAILBACK_UNDEF) return append_strbuf_str(buff, "undef"); if (!mpp->failback_tick) return print_strbuf(buff, "deferred:%i", mpp->pgfailback); else return snprint_progress(buff, mpp->failback_tick, mpp->pgfailback); } static int snprint_queueing (struct strbuf *buff, const struct multipath * mpp) { if (mpp->no_path_retry == NO_PATH_RETRY_FAIL) return append_strbuf_str(buff, "off"); else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE) return append_strbuf_str(buff, "on"); else if (mpp->no_path_retry == NO_PATH_RETRY_UNDEF) return append_strbuf_str(buff, "-"); else if (mpp->no_path_retry > 0) { if (mpp->retry_tick > 0) return print_strbuf(buff, "%i sec", mpp->retry_tick); else if (mpp->retry_tick == 0 && count_active_paths(mpp) > 0) return print_strbuf(buff, "%i chk", mpp->no_path_retry); else return append_strbuf_str(buff, "off"); } return 0; } static int snprint_nb_paths (struct strbuf *buff, const struct multipath * mpp) { return snprint_int(buff, count_active_paths(mpp)); } static int snprint_dm_map_state (struct strbuf *buff, const struct multipath * mpp) { if (!has_dm_info(mpp)) return append_strbuf_str(buff, "undef"); else if (mpp->dmi.suspended) return append_strbuf_str(buff, "suspend"); else return append_strbuf_str(buff, "active"); } static int snprint_multipath_size (struct strbuf *buff, const struct multipath * mpp) { return snprint_size(buff, mpp->size); } static int snprint_features (struct strbuf *buff, const struct multipath * mpp) { return snprint_str(buff, mpp->features); } static int snprint_hwhandler (struct strbuf *buff, const struct multipath * mpp) { return snprint_str(buff, mpp->hwhandler); } static int snprint_path_faults (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_path_failures); } static int snprint_switch_grp (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_switchgroup); } static int snprint_map_loads (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_map_loads); } static int snprint_total_q_time (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_total_queueing_time); } static int snprint_q_timeouts (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_queueing_timeouts); } static int snprint_map_failures (struct strbuf *buff, const struct multipath * mpp) { return snprint_uint(buff, mpp->stat_map_failures); } static int snprint_multipath_uuid (struct strbuf *buff, const struct multipath * mpp) { return snprint_str(buff, mpp->wwid); } static int snprint_multipath_vp (struct strbuf *buff, const struct multipath * mpp) { struct pathgroup * pgp; struct path * pp; int i, j; vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) { if (strlen(pp->vendor_id) && strlen(pp->product_id)) return print_strbuf(buff, "%s,%s", pp->vendor_id, pp->product_id); } } return append_strbuf_str(buff, "##,##"); } static int snprint_multipath_vend (struct strbuf *buff, const struct multipath * mpp) { struct pathgroup * pgp; struct path * pp; int i, j; vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) { if (strlen(pp->vendor_id)) return append_strbuf_str(buff, pp->vendor_id); } } return append_strbuf_str(buff, "##"); } static int snprint_multipath_prod (struct strbuf *buff, const struct multipath * mpp) { struct pathgroup * pgp; struct path * pp; int i, j; vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) { if (strlen(pp->product_id)) return append_strbuf_str(buff, pp->product_id); } } return append_strbuf_str(buff, "##"); } static int snprint_multipath_rev (struct strbuf *buff, const struct multipath * mpp) { struct pathgroup * pgp; struct path * pp; int i, j; vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) { if (strlen(pp->rev)) return append_strbuf_str(buff, pp->rev); } } return append_strbuf_str(buff, "##"); } static int snprint_multipath_foreign (struct strbuf *buff, __attribute__((unused)) const struct multipath * pp) { return append_strbuf_str(buff, "--"); } static int snprint_action (struct strbuf *buff, const struct multipath * mpp) { switch (mpp->action) { case ACT_REJECT: return snprint_str(buff, ACT_REJECT_STR); case ACT_RENAME: return snprint_str(buff, ACT_RENAME_STR); case ACT_RELOAD: return snprint_str(buff, ACT_RELOAD_STR); case ACT_CREATE: return snprint_str(buff, ACT_CREATE_STR); case ACT_SWITCHPG: return snprint_str(buff, ACT_SWITCHPG_STR); default: return 0; } } static int snprint_multipath_vpd_data(struct strbuf *buff, const struct multipath * mpp) { struct pathgroup * pgp; struct path * pp; int i, j; vector_foreach_slot(mpp->pg, pgp, i) vector_foreach_slot(pgp->paths, pp, j) if (pp->vpd_data) return append_strbuf_str(buff, pp->vpd_data); return append_strbuf_str(buff, "[undef]"); } static int snprint_multipath_max_sectors_kb(struct strbuf *buff, const struct multipath *mpp) { char buf[11]; int max_sectors_kb; struct udev_device *udd __attribute__((cleanup(cleanup_udev_device))) = get_udev_for_mpp(mpp); if (!udd || sysfs_attr_get_value(udd, "queue/max_sectors_kb", buf, sizeof(buf)) <= 0 || sscanf(buf, "%d\n", &max_sectors_kb) != 1) return print_strbuf(buff, "n/a"); return print_strbuf(buff, "%d", max_sectors_kb); } /* * path info printing functions */ static int snprint_path_uuid (struct strbuf *buff, const struct path * pp) { return snprint_str(buff, pp->wwid); } static int snprint_hcil (struct strbuf *buff, const struct path * pp) { if (!pp || pp->sg_id.host_no < 0) return append_strbuf_str(buff, "#:#:#:#"); return print_strbuf(buff, "%i:%i:%i:%" PRIu64, pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun); } static int snprint_path_lunhex (struct strbuf *buff, const struct path * pp) { uint64_t lunhex = SCSI_INVALID_LUN, scsilun; if (!pp || pp->sg_id.host_no < 0) return print_strbuf(buff, "0x%016" PRIx64, lunhex); scsilun = pp->sg_id.lun; /* cf. Linux kernel function int_to_scsilun() */ lunhex = ((scsilun & 0x000000000000ffffULL) << 48) | ((scsilun & 0x00000000ffff0000ULL) << 16) | ((scsilun & 0x0000ffff00000000ULL) >> 16) | ((scsilun & 0xffff000000000000ULL) >> 48); return print_strbuf(buff, "0x%016" PRIx64, lunhex); } static int snprint_dev (struct strbuf *buff, const struct path * pp) { if (!pp || !strlen(pp->dev)) return append_strbuf_str(buff, "-"); else return snprint_str(buff, pp->dev); } static int snprint_dev_t (struct strbuf *buff, const struct path * pp) { if (!pp || !strlen(pp->dev)) return append_strbuf_str(buff, "#:#"); else return snprint_str(buff, pp->dev_t); } static int snprint_offline (struct strbuf *buff, const struct path * pp) { if (!pp || !pp->mpp) return append_strbuf_str(buff, "unknown"); else if (pp->sysfs_state == PATH_DOWN) return append_strbuf_str(buff, "offline"); else return append_strbuf_str(buff, "running"); } static int snprint_chk_state (struct strbuf *buff, const struct path * pp) { if (!pp || !pp->mpp) return append_strbuf_str(buff, "undef"); switch (pp->state) { case PATH_UP: return append_strbuf_str(buff, "ready"); case PATH_DOWN: return append_strbuf_str(buff, "faulty"); case PATH_SHAKY: return append_strbuf_str(buff, "shaky"); case PATH_GHOST: return append_strbuf_str(buff, "ghost"); case PATH_PENDING: return append_strbuf_str(buff, "i/o pending"); case PATH_TIMEOUT: return append_strbuf_str(buff, "i/o timeout"); case PATH_DELAYED: return append_strbuf_str(buff, "delayed"); default: return append_strbuf_str(buff, "undef"); } } static int snprint_dm_path_state (struct strbuf *buff, const struct path * pp) { if (!pp) return append_strbuf_str(buff, "undef"); switch (pp->dmstate) { case PSTATE_ACTIVE: return append_strbuf_str(buff, "active"); case PSTATE_FAILED: return append_strbuf_str(buff, "failed"); default: return append_strbuf_str(buff, "undef"); } } static int snprint_initialized(struct strbuf *buff, const struct path * pp) { static const char *init_state_name[] = { [INIT_NEW] = "new", [INIT_FAILED] = "failed", [INIT_MISSING_UDEV] = "udev-missing", [INIT_REQUESTED_UDEV] = "udev-requested", [INIT_OK] = "ok", [INIT_REMOVED] = "removed", [INIT_PARTIAL] = "partial", }; const char *str; if (pp->initialized < INIT_NEW || pp->initialized >= INIT_LAST__) str = "undef"; else str = init_state_name[pp->initialized]; return append_strbuf_str(buff, str); } static int snprint_vpr (struct strbuf *buff, const struct path * pp) { return print_strbuf(buff, "%s,%s,%s", strlen(pp->vendor_id) ? pp->vendor_id : "##", strlen(pp->product_id) ? pp->product_id : "##", strlen(pp->rev) ? pp->rev : "##"); } static int snprint_next_check (struct strbuf *buff, const struct path * pp) { if (!pp || !pp->mpp) return append_strbuf_str(buff, "orphan"); return snprint_progress(buff, pp->tick, pp->checkint); } static int snprint_pri (struct strbuf *buff, const struct path * pp) { return snprint_int(buff, pp ? pp->priority : -1); } static int snprint_pg_selector (struct strbuf *buff, const struct pathgroup * pgp) { const char *s = pgp->mpp->selector; return snprint_str(buff, s ? s : ""); } static int snprint_pg_pri (struct strbuf *buff, const struct pathgroup * pgp) { return snprint_int(buff, pgp->priority); } static int snprint_pg_state (struct strbuf *buff, const struct pathgroup * pgp) { switch (pgp->status) { case PGSTATE_ENABLED: return append_strbuf_str(buff, "enabled"); case PGSTATE_DISABLED: return append_strbuf_str(buff, "disabled"); case PGSTATE_ACTIVE: return append_strbuf_str(buff, "active"); default: return append_strbuf_str(buff, "undef"); } } static int snprint_pg_marginal (struct strbuf *buff, const struct pathgroup * pgp) { if (pgp->marginal) return append_strbuf_str(buff, "marginal"); return append_strbuf_str(buff, "normal"); } static int snprint_path_size (struct strbuf *buff, const struct path * pp) { return snprint_size(buff, pp->size); } int snprint_path_serial (struct strbuf *buff, const struct path * pp) { return snprint_str(buff, pp->serial); } static int snprint_path_mpp (struct strbuf *buff, const struct path * pp) { if (!pp->mpp) return append_strbuf_str(buff, "[orphan]"); if (!pp->mpp->alias) return append_strbuf_str(buff, "[unknown]"); return snprint_str(buff, pp->mpp->alias); } static int snprint_host_attr (struct strbuf *buff, const struct path * pp, char *attr) { struct udev_device *host_dev = NULL; char host_id[32]; const char *value = NULL; int ret; if (pp->bus != SYSFS_BUS_SCSI || pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) return append_strbuf_str(buff, "[undef]"); sprintf(host_id, "host%d", pp->sg_id.host_no); host_dev = udev_device_new_from_subsystem_sysname(udev, "fc_host", host_id); if (!host_dev) { condlog(1, "%s: No fc_host device for '%s'", pp->dev, host_id); goto out; } value = udev_device_get_sysattr_value(host_dev, attr); if (value) ret = snprint_str(buff, value); udev_device_unref(host_dev); out: if (!value) ret = append_strbuf_str(buff, "[unknown]"); return ret; } int snprint_host_wwnn (struct strbuf *buff, const struct path * pp) { return snprint_host_attr(buff, pp, "node_name"); } int snprint_host_wwpn (struct strbuf *buff, const struct path * pp) { return snprint_host_attr(buff, pp, "port_name"); } int snprint_tgt_wwpn (struct strbuf *buff, const struct path * pp) { struct udev_device *rport_dev = NULL; char rport_id[42]; const char *value = NULL; int ret; if (pp->bus != SYSFS_BUS_SCSI || pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) return append_strbuf_str(buff, "[undef]"); sprintf(rport_id, "rport-%d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); rport_dev = udev_device_new_from_subsystem_sysname(udev, "fc_remote_ports", rport_id); if (!rport_dev) { condlog(1, "%s: No fc_remote_port device for '%s'", pp->dev, rport_id); goto out; } value = udev_device_get_sysattr_value(rport_dev, "port_name"); if (value) ret = snprint_str(buff, value); udev_device_unref(rport_dev); out: if (!value) ret = append_strbuf_str(buff, "[unknown]"); return ret; } int snprint_tgt_wwnn (struct strbuf *buff, const struct path * pp) { if (pp->tgt_node_name[0] == '\0') return append_strbuf_str(buff, "[undef]"); return snprint_str(buff, pp->tgt_node_name); } static int snprint_host_adapter (struct strbuf *buff, const struct path * pp) { char adapter[SLOT_NAME_SIZE]; if (sysfs_get_host_adapter_name(pp, adapter)) return append_strbuf_str(buff, "[undef]"); return snprint_str(buff, adapter); } static int snprint_path_checker (struct strbuf *buff, const struct path * pp) { const char * n = checker_name(&pp->checker); if (n) return snprint_str(buff, n); else return snprint_str(buff, "(null)"); } static int snprint_path_foreign (struct strbuf *buff, __attribute__((unused)) const struct path * pp) { return append_strbuf_str(buff, "--"); } static int snprint_path_failures(struct strbuf *buff, const struct path * pp) { return snprint_int(buff, pp->failcount); } /* if you add a protocol string bigger than "scsi:unspec" you must * also change PROTOCOL_BUF_SIZE */ int snprint_path_protocol(struct strbuf *buff, const struct path * pp) { const char *pn = protocol_name[bus_protocol_id(pp)]; assert(pn != NULL); return append_strbuf_str(buff, pn); } static int snprint_path_marginal(struct strbuf *buff, const struct path * pp) { if (pp->marginal) return append_strbuf_str(buff, "marginal"); return append_strbuf_str(buff, "normal"); } static int snprint_path_vpd_data(struct strbuf *buff, const struct path * pp) { if (pp->vpd_data) return append_strbuf_str(buff, pp->vpd_data); return append_strbuf_str(buff, "[undef]"); } static int snprint_alua_tpg(struct strbuf *buff, const struct path * pp) { if (pp->tpg_id < 0) return append_strbuf_str(buff, "[undef]"); return print_strbuf(buff, "0x%04x", pp->tpg_id); } static int snprint_path_max_sectors_kb(struct strbuf *buff, const struct path *pp) { char buf[11]; int max_sectors_kb; if (!pp->udev || sysfs_attr_get_value(pp->udev, "queue/max_sectors_kb", buf, sizeof(buf)) <= 0 || sscanf(buf, "%d\n", &max_sectors_kb) != 1) return print_strbuf(buff, "n/a"); return print_strbuf(buff, "%d", max_sectors_kb); } static const struct multipath_data mpd[] = { {'n', "name", snprint_name}, {'w', "uuid", snprint_multipath_uuid}, {'d', "sysfs", snprint_sysfs}, {'F', "failback", snprint_failback}, {'Q', "queueing", snprint_queueing}, {'N', "paths", snprint_nb_paths}, {'r', "write_prot", snprint_ro}, {'t', "dm-st", snprint_dm_map_state}, {'S', "size", snprint_multipath_size}, {'f', "features", snprint_features}, {'x', "failures", snprint_map_failures}, {'h', "hwhandler", snprint_hwhandler}, {'A', "action", snprint_action}, {'0', "path_faults", snprint_path_faults}, {'1', "switch_grp", snprint_switch_grp}, {'2', "map_loads", snprint_map_loads}, {'3', "total_q_time", snprint_total_q_time}, {'4', "q_timeouts", snprint_q_timeouts}, {'s', "vend/prod", snprint_multipath_vp}, {'v', "vend", snprint_multipath_vend}, {'p', "prod", snprint_multipath_prod}, {'e', "rev", snprint_multipath_rev}, {'G', "foreign", snprint_multipath_foreign}, {'g', "vpd page data", snprint_multipath_vpd_data}, {'k', "max_sectors_kb",snprint_multipath_max_sectors_kb}, }; static const struct path_data pd[] = { {'w', "uuid", snprint_path_uuid}, {'i', "hcil", snprint_hcil}, {'d', "dev", snprint_dev}, {'D', "dev_t", snprint_dev_t}, {'t', "dm_st", snprint_dm_path_state}, {'o', "dev_st", snprint_offline}, {'T', "chk_st", snprint_chk_state}, {'s', "vend/prod/rev", snprint_vpr}, {'c', "checker", snprint_path_checker}, {'C', "next_check", snprint_next_check}, {'p', "pri", snprint_pri}, {'S', "size", snprint_path_size}, {'z', "serial", snprint_path_serial}, {'M', "marginal_st", snprint_path_marginal}, {'m', "multipath", snprint_path_mpp}, {'N', "host WWNN", snprint_host_wwnn}, {'n', "target WWNN", snprint_tgt_wwnn}, {'R', "host WWPN", snprint_host_wwpn}, {'r', "target WWPN", snprint_tgt_wwpn}, {'a', "host adapter", snprint_host_adapter}, {'G', "foreign", snprint_path_foreign}, {'g', "vpd page data", snprint_path_vpd_data}, {'0', "failures", snprint_path_failures}, {'P', "protocol", snprint_path_protocol}, {'I', "init_st", snprint_initialized}, {'L', "LUN hex", snprint_path_lunhex}, {'A', "TPG", snprint_alua_tpg}, {'k', "max_sectors_kb",snprint_path_max_sectors_kb}, }; static const struct pathgroup_data pgd[] = { {'s', "selector", snprint_pg_selector}, {'p', "pri", snprint_pg_pri}, {'t', "dm_st", snprint_pg_state}, {'M', "marginal_st", snprint_pg_marginal}, }; int snprint_wildcards(struct strbuf *buff) { int initial_len = get_strbuf_len(buff); unsigned int i; int rc; if ((rc = append_strbuf_str(buff, "multipath format wildcards:\n")) < 0) return rc; for (i = 0; i < ARRAY_SIZE(mpd); i++) if ((rc = print_strbuf(buff, "%%%c %s\n", mpd[i].wildcard, mpd[i].header)) < 0) return rc; if ((rc = append_strbuf_str(buff, "\npath format wildcards:\n")) < 0) return rc; for (i = 0; i < ARRAY_SIZE(pd); i++) if ((rc = print_strbuf(buff, "%%%c %s\n", pd[i].wildcard, pd[i].header)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } fieldwidth_t *alloc_path_layout(void) { return calloc(ARRAY_SIZE(pd), sizeof(fieldwidth_t)); } void get_path_layout(vector pathvec, int header, fieldwidth_t *width) { vector gpvec = vector_convert(NULL, pathvec, struct path, dm_path_to_gen); get_path_layout__(gpvec, header ? LAYOUT_RESET_HEADER : LAYOUT_RESET_ZERO, width); vector_free(gpvec); } static void reset_width(fieldwidth_t *width, enum layout_reset reset, const char *header) { switch (reset) { case LAYOUT_RESET_HEADER: *width = strlen(header); break; case LAYOUT_RESET_ZERO: *width = 0; break; default: /* don't reset */ break; } } void get_path_layout__ (const struct vector_s *gpvec, enum layout_reset reset, fieldwidth_t *width) { unsigned int i, j; const struct gen_path *gp; if (width == NULL) return; for (j = 0; j < ARRAY_SIZE(pd); j++) { STRBUF_ON_STACK(buff); reset_width(&width[j], reset, pd[j].header); if (gpvec == NULL) continue; vector_foreach_slot (gpvec, gp, i) { gp->ops->snprint(gp, &buff, pd[j].wildcard); width[j] = MAX(width[j], MIN(get_strbuf_len(&buff), MAX_FIELD_WIDTH)); truncate_strbuf(&buff, 0); } } } fieldwidth_t *alloc_multipath_layout(void) { return calloc(ARRAY_SIZE(mpd), sizeof(fieldwidth_t)); } void get_multipath_layout (vector mpvec, int header, fieldwidth_t *width) { vector gmvec = vector_convert(NULL, mpvec, struct multipath, dm_multipath_to_gen); get_multipath_layout__(gmvec, header ? LAYOUT_RESET_HEADER : LAYOUT_RESET_ZERO, width); vector_free(gmvec); } void get_multipath_layout__ (const struct vector_s *gmvec, enum layout_reset reset, fieldwidth_t *width) { unsigned int i, j; const struct gen_multipath * gm; if (width == NULL) return; for (j = 0; j < ARRAY_SIZE(mpd); j++) { STRBUF_ON_STACK(buff); reset_width(&width[j], reset, mpd[j].header); if (gmvec == NULL) continue; vector_foreach_slot (gmvec, gm, i) { gm->ops->snprint(gm, &buff, mpd[j].wildcard); width[j] = MAX(width[j], MIN(get_strbuf_len(&buff), MAX_FIELD_WIDTH)); truncate_strbuf(&buff, 0); } condlog(4, "%s: width %d", mpd[j].header, width[j]); } } static int mpd_lookup(char wildcard) { unsigned int i; for (i = 0; i < ARRAY_SIZE(mpd); i++) if (mpd[i].wildcard == wildcard) return i; return -1; } int snprint_multipath_attr(const struct gen_multipath* gm, struct strbuf *buf, char wildcard) { const struct multipath *mpp = gen_multipath_to_dm(gm); int i = mpd_lookup(wildcard); if (i == -1) return 0; return mpd[i].snprint(buf, mpp); } static int pd_lookup(char wildcard) { unsigned int i; for (i = 0; i < ARRAY_SIZE(pd); i++) if (pd[i].wildcard == wildcard) return i; return -1; } int snprint_path_attr(const struct gen_path* gp, struct strbuf *buf, char wildcard) { const struct path *pp = gen_path_to_dm(gp); int i = pd_lookup(wildcard); if (i == -1) return 0; return pd[i].snprint(buf, pp); } static int pgd_lookup(char wildcard) { unsigned int i; for (i = 0; i < ARRAY_SIZE(pgd); i++) if (pgd[i].wildcard == wildcard) return i; return -1; } int snprint_pathgroup_attr(const struct gen_pathgroup* gpg, struct strbuf *buf, char wildcard) { const struct pathgroup *pg = gen_pathgroup_to_dm(gpg); int i = pgd_lookup(wildcard); if (i == -1) return 0; return pgd[i].snprint(buf, pg); } int snprint_multipath_header(struct strbuf *line, const char *format, const fieldwidth_t *width) { int initial_len = get_strbuf_len(line); const char *f; const struct multipath_data * data; int rc; for (f = strchr(format, '%'); f; f = strchr(++format, '%')) { int iwc; if ((rc = append_strbuf_str__(line, format, f - format)) < 0) return rc; format = f + 1; if ((iwc = mpd_lookup(*format)) == -1) continue; /* unknown wildcard */ data = &mpd[iwc]; if ((rc = append_strbuf_str(line, data->header)) < 0) return rc; else if ((unsigned int)rc < width[iwc]) if ((rc = fill_strbuf(line, ' ', width[iwc] - rc)) < 0) return rc; } if ((rc = print_strbuf(line, "%s\n", format)) < 0) return rc; return get_strbuf_len(line) - initial_len; } int snprint_multipath__(const struct gen_multipath *gmp, struct strbuf *line, const char *format, const fieldwidth_t *width) { int initial_len = get_strbuf_len(line); const char *f; int rc; for (f = strchr(format, '%'); f; f = strchr(++format, '%')) { int iwc; if ((rc = append_strbuf_str__(line, format, f - format)) < 0) return rc; format = f + 1; if ((iwc = mpd_lookup(*format)) == -1) continue; /* unknown wildcard */ if ((rc = gmp->ops->snprint(gmp, line, *format)) < 0) return rc; else if (width != NULL && (unsigned int)rc < width[iwc]) if ((rc = fill_strbuf(line, ' ', width[iwc] - rc)) < 0) return rc; } if ((rc = print_strbuf(line, "%s\n", format)) < 0) return rc; return get_strbuf_len(line) - initial_len; } int snprint_path_header(struct strbuf *line, const char *format, const fieldwidth_t *width) { int initial_len = get_strbuf_len(line); const char *f; const struct path_data *data; int rc; for (f = strchr(format, '%'); f; f = strchr(++format, '%')) { int iwc; if ((rc = append_strbuf_str__(line, format, f - format)) < 0) return rc; format = f + 1; if ((iwc = pd_lookup(*format)) == -1) continue; /* unknown wildcard */ data = &pd[iwc]; if ((rc = append_strbuf_str(line, data->header)) < 0) return rc; else if ((unsigned int)rc < width[iwc]) if ((rc = fill_strbuf(line, ' ', width[iwc] - rc)) < 0) return rc; } if ((rc = print_strbuf(line, "%s\n", format)) < 0) return rc; return get_strbuf_len(line) - initial_len; } int snprint_path__(const struct gen_path *gp, struct strbuf *line, const char *format, const fieldwidth_t *width) { int initial_len = get_strbuf_len(line); const char *f; int rc; for (f = strchr(format, '%'); f; f = strchr(++format, '%')) { int iwc; if ((rc = append_strbuf_str__(line, format, f - format)) < 0) return rc; format = f + 1; if ((iwc = pd_lookup(*format)) == -1) continue; /* unknown wildcard */ if ((rc = gp->ops->snprint(gp, line, *format)) < 0) return rc; else if (width != NULL && (unsigned int)rc < width[iwc]) if ((rc = fill_strbuf(line, ' ', width[iwc] - rc)) < 0) return rc; } if ((rc = print_strbuf(line, "%s\n", format)) < 0) return rc; return get_strbuf_len(line) - initial_len; } int snprint_pathgroup__(const struct gen_pathgroup *ggp, struct strbuf *line, const char *format) { int initial_len = get_strbuf_len(line); const char *f; int rc; for (f = strchr(format, '%'); f; f = strchr(++format, '%')) { if ((rc = append_strbuf_str__(line, format, f - format)) < 0) return rc; format = f + 1; if ((rc = ggp->ops->snprint(ggp, line, *format)) < 0) return rc; } if ((rc = print_strbuf(line, "%s\n", format)) < 0) return rc; return get_strbuf_len(line) - initial_len; } #define snprint_pathgroup(line, fmt, pgp) \ snprint_pathgroup__(dm_pathgroup_to_gen(pgp), line, fmt) void print_multipath_topology__(const struct gen_multipath *gmp, int verbosity) { STRBUF_ON_STACK(buff); fieldwidth_t *p_width __attribute__((cleanup(cleanup_ucharp))) = NULL; const struct gen_pathgroup *gpg; const struct vector_s *pgvec, *pathvec; int j; p_width = alloc_path_layout(); pgvec = gmp->ops->get_pathgroups(gmp); if (pgvec != NULL) { vector_foreach_slot (pgvec, gpg, j) { pathvec = gpg->ops->get_paths(gpg); if (pathvec == NULL) continue; get_path_layout__(pathvec, LAYOUT_RESET_NOT, p_width); gpg->ops->rel_paths(gpg, pathvec); } gmp->ops->rel_pathgroups(gmp, pgvec); } snprint_multipath_topology__(gmp, &buff, verbosity, p_width); printf("%s", get_strbuf_str(&buff)); } int snprint_multipath_style(const struct gen_multipath *gmp, struct strbuf *style, int verbosity) { const struct multipath *mpp = gen_multipath_to_dm(gmp); bool need_action = (verbosity > 1 && mpp->action != ACT_NOTHING && mpp->action != ACT_UNDEF && mpp->action != ACT_IMPOSSIBLE); bool need_wwid = (strncmp(mpp->alias, mpp->wwid, WWID_SIZE)); return print_strbuf(style, "%s%s%s%s", need_action ? "%A: " : "", "%n", need_wwid ? " (%w)" : "", " %d %s"); } int snprint_multipath_topology__(const struct gen_multipath *gmp, struct strbuf *buff, int verbosity, const fieldwidth_t *p_width) { int j, i, rc; const struct vector_s *pgvec; const struct gen_pathgroup *gpg; STRBUF_ON_STACK(style); size_t initial_len = get_strbuf_len(buff); fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if (verbosity <= 0) return 0; if ((width = alloc_multipath_layout()) == NULL) return -ENOMEM; if (verbosity == 1) return snprint_multipath__(gmp, buff, "%n", width); if(isatty(1) && (rc = print_strbuf(&style, "%c[%dm", 0x1B, 1)) < 0) /* bold on */ return rc; if ((rc = gmp->ops->style(gmp, &style, verbosity)) < 0) return rc; if(isatty(1) && (rc = print_strbuf(&style, "%c[%dm", 0x1B, 0)) < 0) /* bold off */ return rc; if ((rc = snprint_multipath__(gmp, buff, get_strbuf_str(&style), width)) < 0 || (rc = snprint_multipath__(gmp, buff, PRINT_MAP_PROPS, width)) < 0) return rc; pgvec = gmp->ops->get_pathgroups(gmp); if (pgvec == NULL) goto out; vector_foreach_slot (pgvec, gpg, j) { const struct vector_s *pathvec; struct gen_path *gp; bool last_group = j + 1 == VECTOR_SIZE(pgvec); if ((rc = print_strbuf(buff, "%c-+- ", last_group ? '`' : '|')) < 0 || (rc = snprint_pathgroup__(gpg, buff, PRINT_PG_INDENT)) < 0) return rc; pathvec = gpg->ops->get_paths(gpg); if (pathvec == NULL) continue; vector_foreach_slot (pathvec, gp, i) { if ((rc = print_strbuf(buff, "%c %c- ", last_group ? ' ' : '|', i + 1 == VECTOR_SIZE(pathvec) ? '`': '|')) < 0 || (rc = snprint_path__(gp, buff, PRINT_PATH_INDENT, p_width)) < 0) return rc; } gpg->ops->rel_paths(gpg, pathvec); } gmp->ops->rel_pathgroups(gmp, pgvec); out: return get_strbuf_len(buff) - initial_len; } static int snprint_json(struct strbuf *buff, int indent, const char *json_str) { int rc; if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0) return rc; return append_strbuf_str(buff, json_str); } static int snprint_json_header(struct strbuf *buff) { int rc; if ((rc = snprint_json(buff, 0, PRINT_JSON_START_ELEM)) < 0) return rc; return print_strbuf(buff, PRINT_JSON_START_VERSION, PRINT_JSON_MAJOR_VERSION, PRINT_JSON_MINOR_VERSION); } static int snprint_json_elem_footer(struct strbuf *buff, int indent, bool last) { int rc; if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0) return rc; if (last) return append_strbuf_str(buff, PRINT_JSON_END_LAST_ELEM); else return append_strbuf_str(buff, PRINT_JSON_END_ELEM); } static int snprint_multipath_fields_json(struct strbuf *buff, const struct multipath *mpp, int last) { int i, j, rc; struct path *pp; struct pathgroup *pgp; size_t initial_len = get_strbuf_len(buff); if ((rc = snprint_multipath(buff, PRINT_JSON_MAP, mpp, 0)) < 0 || (rc = snprint_json(buff, 2, PRINT_JSON_START_GROUPS)) < 0) return rc; vector_foreach_slot (mpp->pg, pgp, i) { if ((rc = snprint_pathgroup(buff, PRINT_JSON_GROUP, pgp)) < 0 || (rc = print_strbuf(buff, PRINT_JSON_GROUP_NUM, i + 1)) < 0 || (rc = snprint_json(buff, 3, PRINT_JSON_START_PATHS)) < 0) return rc; vector_foreach_slot (pgp->paths, pp, j) { if ((rc = snprint_path(buff, PRINT_JSON_PATH, pp, 0)) < 0 || (rc = snprint_json_elem_footer( buff, 3, j + 1 == VECTOR_SIZE(pgp->paths))) < 0) return rc; } if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 || (rc = snprint_json_elem_footer( buff, 2, i + 1 == VECTOR_SIZE(mpp->pg))) < 0) return rc; } if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 || (rc = snprint_json_elem_footer(buff, 1, last)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } int snprint_multipath_map_json(struct strbuf *buff, const struct multipath * mpp) { size_t initial_len = get_strbuf_len(buff); int rc; if ((rc = snprint_json_header(buff)) < 0 || (rc = snprint_json(buff, 0, PRINT_JSON_START_MAP)) < 0) return rc; if ((rc = snprint_multipath_fields_json(buff, mpp, 1)) < 0) return rc; if ((rc = snprint_json(buff, 0, "\n")) < 0 || (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } int snprint_multipath_topology_json (struct strbuf *buff, const struct vectors * vecs) { int i; struct multipath * mpp; size_t initial_len = get_strbuf_len(buff); int rc; if ((rc = snprint_json_header(buff)) < 0 || (rc = snprint_json(buff, 1, PRINT_JSON_START_MAPS)) < 0) return rc; vector_foreach_slot(vecs->mpvec, mpp, i) { if ((rc = snprint_multipath_fields_json( buff, mpp, i + 1 == VECTOR_SIZE(vecs->mpvec))) < 0) return rc; } if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 || (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_pcentry (const struct config *conf, struct strbuf *buff, const struct pcentry *pce) { int i, rc; struct keyword *kw; struct keyword * rootkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "overrides"); assert(rootkw && rootkw->sub); rootkw = find_keyword(conf->keywords, rootkw->sub, "protocol"); assert(rootkw); if ((rc = append_strbuf_str(buff, "\tprotocol {\n")) < 0) return rc; iterate_sub_keywords(rootkw, kw, i) { if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, pce)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "\t}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_pctable (const struct config *conf, struct strbuf *buff, const struct vector_s *pctable) { int i, rc; struct pcentry *pce; struct keyword * rootkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "overrides"); assert(rootkw); vector_foreach_slot(pctable, pce, i) { if ((rc = snprint_pcentry(conf, buff, pce)) < 0) return rc; } return get_strbuf_len(buff) - initial_len; } static int snprint_hwentry (const struct config *conf, struct strbuf *buff, const struct hwentry * hwe) { int i, rc; struct keyword * kw; struct keyword * rootkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "devices"); assert(rootkw && rootkw->sub); rootkw = find_keyword(conf->keywords, rootkw->sub, "device"); assert(rootkw); if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0) return rc; iterate_sub_keywords(rootkw, kw, i) { if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, hwe)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "\t}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_hwtable(const struct config *conf, struct strbuf *buff, const struct vector_s *hwtable) { int i, rc; struct hwentry * hwe; struct keyword * rootkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "devices"); assert(rootkw); if ((rc = append_strbuf_str(buff, "devices {\n")) < 0) return rc; vector_foreach_slot (hwtable, hwe, i) { if ((rc = snprint_hwentry(conf, buff, hwe)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_mpentry (const struct config *conf, struct strbuf *buff, const struct mpentry * mpe, const struct vector_s *mpvec) { int i, rc; struct keyword * kw; struct keyword * rootkw; struct multipath *mpp = NULL; size_t initial_len = get_strbuf_len(buff); if (mpvec != NULL && (mpp = find_mp_by_wwid(mpvec, mpe->wwid)) == NULL) return 0; rootkw = find_keyword(conf->keywords, NULL, "multipath"); assert(rootkw); if ((rc = append_strbuf_str(buff, "\tmultipath {\n")) < 0) return rc; iterate_sub_keywords(rootkw, kw, i) { if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, mpe)) < 0) return rc; } /* * This mpp doesn't have alias defined. Add the alias in a comment. */ if (mpp != NULL && strcmp(mpp->alias, mpp->wwid) && (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n", mpp->alias)) < 0) return rc; if ((rc = append_strbuf_str(buff, "\t}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_mptable(const struct config *conf, struct strbuf *buff, const struct vector_s *mpvec) { int i, rc; struct mpentry * mpe; struct keyword * rootkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "multipaths"); assert(rootkw); if ((rc = append_strbuf_str(buff, "multipaths {\n")) < 0) return rc; vector_foreach_slot (conf->mptable, mpe, i) { if ((rc = snprint_mpentry(conf, buff, mpe, mpvec)) < 0) return rc; } if (mpvec != NULL) { struct multipath *mpp; vector_foreach_slot(mpvec, mpp, i) { if (find_mpe(conf->mptable, mpp->wwid) != NULL) continue; if ((rc = print_strbuf(buff, "\tmultipath {\n\t\twwid \"%s\"\n", mpp->wwid)) < 0) return rc; /* * This mpp doesn't have alias defined in * multipath.conf - otherwise find_mpe would have * found it. Add the alias in a comment. */ if (strcmp(mpp->alias, mpp->wwid) && (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n", mpp->alias)) < 0) return rc; if ((rc = append_strbuf_str(buff, "\t}\n")) < 0) return rc; } } if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_overrides(const struct config *conf, struct strbuf *buff, const struct hwentry *overrides) { int i, rc; struct keyword *rootkw; struct keyword *kw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "overrides"); assert(rootkw); if ((rc = append_strbuf_str(buff, "overrides {\n")) < 0) return rc; if (!overrides) goto out; iterate_sub_keywords(rootkw, kw, i) { if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0) return rc; } if (overrides->pctable && (rc = snprint_pctable(conf, buff, overrides->pctable)) < 0) return rc; out: if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_defaults(const struct config *conf, struct strbuf *buff) { int i, rc; struct keyword *rootkw; struct keyword *kw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "defaults"); assert(rootkw); if ((rc = append_strbuf_str(buff, "defaults {\n")) < 0) return rc; iterate_sub_keywords(rootkw, kw, i) { if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_blacklist_group(struct strbuf *buff, vector *vec) { struct blentry * ble; size_t initial_len = get_strbuf_len(buff); int rc, i; if (!VECTOR_SIZE(*vec)) { if ((rc = append_strbuf_str(buff, " \n")) < 0) return rc; } else vector_foreach_slot (*vec, ble, i) { rc = print_strbuf(buff, " %s %s\n", ble->origin == ORIGIN_CONFIG ? "(config file rule)" : "(default rule) ", ble->str); if (rc < 0) return rc; } return get_strbuf_len(buff) - initial_len; } static int snprint_blacklist_devgroup (struct strbuf *buff, vector *vec) { struct blentry_device * bled; size_t initial_len = get_strbuf_len(buff); int rc, i; if (!VECTOR_SIZE(*vec)) { if ((rc = append_strbuf_str(buff, " \n")) < 0) return rc; } else vector_foreach_slot (*vec, bled, i) { rc = print_strbuf(buff, " %s %s:%s\n", bled->origin == ORIGIN_CONFIG ? "(config file rule)" : "(default rule) ", bled->vendor, bled->product); if (rc < 0) return rc; } return get_strbuf_len(buff) - initial_len; } int snprint_blacklist_report(struct config *conf, struct strbuf *buff) { size_t initial_len = get_strbuf_len(buff); int rc; if ((rc = append_strbuf_str(buff, "device node rules:\n- blacklist:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->blist_devnode)) < 0) return rc; if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->elist_devnode)) < 0) return rc; if ((rc = append_strbuf_str(buff, "udev property rules:\n- blacklist:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->blist_property)) < 0) return rc; if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->elist_property)) < 0) return rc; if ((rc = append_strbuf_str(buff, "protocol rules:\n- blacklist:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->blist_protocol)) < 0) return rc; if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->elist_protocol)) < 0) return rc; if ((rc = append_strbuf_str(buff, "wwid rules:\n- blacklist:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->blist_wwid)) < 0) return rc; if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0) return rc; if ((rc = snprint_blacklist_group(buff, &conf->elist_wwid)) < 0) return rc; if ((rc = append_strbuf_str(buff, "device rules:\n- blacklist:\n")) < 0) return rc; if ((rc = snprint_blacklist_devgroup(buff, &conf->blist_device)) < 0) return rc; if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0) return rc; if ((rc = snprint_blacklist_devgroup(buff, &conf->elist_device)) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_blacklist(const struct config *conf, struct strbuf *buff) { int i, rc; struct blentry * ble; struct blentry_device * bled; struct keyword *rootkw; struct keyword *kw, *pkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "blacklist"); assert(rootkw); if ((rc = append_strbuf_str(buff, "blacklist {\n")) < 0) return rc; vector_foreach_slot (conf->blist_devnode, ble, i) { kw = find_keyword(conf->keywords, rootkw->sub, "devnode"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0) return rc; } vector_foreach_slot (conf->blist_wwid, ble, i) { kw = find_keyword(conf->keywords, rootkw->sub, "wwid"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0) return rc; } vector_foreach_slot (conf->blist_property, ble, i) { kw = find_keyword(conf->keywords, rootkw->sub, "property"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0) return rc; } vector_foreach_slot (conf->blist_protocol, ble, i) { kw = find_keyword(conf->keywords, rootkw->sub, "protocol"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0) return rc; } rootkw = find_keyword(conf->keywords, rootkw->sub, "device"); assert(rootkw); kw = find_keyword(conf->keywords, rootkw->sub, "vendor"); pkw = find_keyword(conf->keywords, rootkw->sub, "product"); assert(kw && pkw); vector_foreach_slot (conf->blist_device, bled, i) { if ((rc = snprint_keyword(buff, "\tdevice {\n\t\t%k %v\n", kw, bled)) < 0) return rc; if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n", pkw, bled)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } static int snprint_blacklist_except(const struct config *conf, struct strbuf *buff) { int i, rc; struct blentry * ele; struct blentry_device * eled; struct keyword *rootkw; struct keyword *kw, *pkw; size_t initial_len = get_strbuf_len(buff); rootkw = find_keyword(conf->keywords, NULL, "blacklist_exceptions"); assert(rootkw); if ((rc = append_strbuf_str(buff, "blacklist_exceptions {\n")) < 0) return rc; vector_foreach_slot (conf->elist_devnode, ele, i) { kw = find_keyword(conf->keywords, rootkw->sub, "devnode"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0) return rc; } vector_foreach_slot (conf->elist_wwid, ele, i) { kw = find_keyword(conf->keywords, rootkw->sub, "wwid"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0) return rc; } vector_foreach_slot (conf->elist_property, ele, i) { kw = find_keyword(conf->keywords, rootkw->sub, "property"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0) return rc; } vector_foreach_slot (conf->elist_protocol, ele, i) { kw = find_keyword(conf->keywords, rootkw->sub, "protocol"); assert(kw); if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0) return rc; } rootkw = find_keyword(conf->keywords, rootkw->sub, "device"); assert(rootkw); kw = find_keyword(conf->keywords, rootkw->sub, "vendor"); pkw = find_keyword(conf->keywords, rootkw->sub, "product"); assert(kw && pkw); vector_foreach_slot (conf->elist_device, eled, i) { if ((rc = snprint_keyword(buff, "\tdevice {\n\t\t%k %v\n", kw, eled)) < 0) return rc; if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n", pkw, eled)) < 0) return rc; } if ((rc = append_strbuf_str(buff, "}\n")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } int snprint_config__(const struct config *conf, struct strbuf *buff, const struct vector_s *hwtable, const struct vector_s *mpvec) { int rc; if ((rc = snprint_defaults(conf, buff)) < 0 || (rc = snprint_blacklist(conf, buff)) < 0 || (rc = snprint_blacklist_except(conf, buff)) < 0 || (rc = snprint_hwtable(conf, buff, hwtable ? hwtable : conf->hwtable)) < 0 || (rc = snprint_overrides(conf, buff, conf->overrides)) < 0) return rc; if (VECTOR_SIZE(conf->mptable) > 0 || (mpvec != NULL && VECTOR_SIZE(mpvec) > 0)) if ((rc = snprint_mptable(conf, buff, mpvec)) < 0) return rc; return 0; } char *snprint_config(const struct config *conf, int *len, const struct vector_s *hwtable, const struct vector_s *mpvec) { STRBUF_ON_STACK(buff); char *reply; int rc = snprint_config__(conf, &buff, hwtable, mpvec); if (rc < 0) return NULL; if (len) *len = get_strbuf_len(&buff); reply = steal_strbuf_str(&buff); return reply; } int snprint_status(struct strbuf *buff, const struct vectors *vecs) { int i, rc; unsigned int count[PATH_MAX_STATE] = {0}; int monitored_count = 0; struct path * pp; size_t initial_len = get_strbuf_len(buff); vector_foreach_slot (vecs->pathvec, pp, i) { count[pp->state]++; } if ((rc = append_strbuf_str(buff, "path checker states:\n")) < 0) return rc; for (i = 0; i < PATH_MAX_STATE; i++) { if (!count[i]) continue; if ((rc = print_strbuf(buff, "%-20s%u\n", checker_state_name(i), count[i])) < 0) return rc; } vector_foreach_slot(vecs->pathvec, pp, i) if (pp->fd >= 0) monitored_count++; if ((rc = print_strbuf(buff, "\npaths: %d\nbusy: %s\n", monitored_count, is_uevent_busy()? "True" : "False")) < 0) return rc; return get_strbuf_len(buff) - initial_len; } int snprint_devices(struct config *conf, struct strbuf *buff, const struct vectors *vecs) { int r; struct udev_enumerate *enm; struct udev_list_entry *item, *first; struct path * pp; size_t initial_len = get_strbuf_len(buff); enm = udev_enumerate_new(udev); if (!enm) return 1; udev_enumerate_add_match_subsystem(enm, "block"); if ((r = append_strbuf_str(buff, "available block devices:\n")) < 0) goto out; r = udev_enumerate_scan_devices(enm); if (r < 0) goto out; first = udev_enumerate_get_list_entry(enm); udev_list_entry_foreach(item, first) { const char *path, *devname, *status; struct udev_device *u_dev; path = udev_list_entry_get_name(item); if (!path) continue; u_dev = udev_device_new_from_syspath(udev, path); if (!u_dev) continue; devname = udev_device_get_sysname(u_dev); if (!devname) { udev_device_unref(u_dev); continue; } pp = find_path_by_dev(vecs->pathvec, devname); if (!pp) { const char *hidden; hidden = udev_device_get_sysattr_value(u_dev, "hidden"); if (hidden && !strcmp(hidden, "1")) status = "hidden, unmonitored"; else if (is_claimed_by_foreign(u_dev)) status = "foreign, monitored"; else { r = filter_devnode(conf->blist_devnode, conf->elist_devnode, devname); if (r > 0) status = "devnode blacklisted, unmonitored"; else status = "devnode whitelisted, unmonitored"; } } else status = " devnode whitelisted, monitored"; r = print_strbuf(buff, " %s %s\n", devname, status); udev_device_unref(u_dev); if (r < 0) break; } out: udev_enumerate_unref(enm); if (r < 0) return r; return get_strbuf_len(buff) - initial_len; } /* * stdout printing helpers */ static void print_all_paths_custo(vector pathvec, int banner, const char *fmt) { int i; struct path * pp; STRBUF_ON_STACK(line); fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if (!VECTOR_SIZE(pathvec)) { if (banner) fprintf(stdout, "===== no paths =====\n"); return; } if ((width = alloc_path_layout()) == NULL) return; get_path_layout(pathvec, 1, width); if (banner) append_strbuf_str(&line, "===== paths list =====\n"); snprint_path_header(&line, fmt, width); vector_foreach_slot (pathvec, pp, i) snprint_path(&line, fmt, pp, width); printf("%s", get_strbuf_str(&line)); } void print_all_paths(vector pathvec, int banner) { print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG); } multipath-tools-0.11.1/libmultipath/print.h000066400000000000000000000065741475246302400207650ustar00rootroot00000000000000#ifndef PRINT_H_INCLUDED #define PRINT_H_INCLUDED #include "dm-generic.h" #define PRINT_PATH_CHECKER "%i %d %D %p %t %T %o %C" #define PRINT_MAP_STATUS "%n %F %Q %N %t %r" #define PRINT_MAP_STATS "%n %0 %1 %2 %3 %4" #define PRINT_MAP_NAMES "%n %d %w" struct strbuf; enum layout_reset { LAYOUT_RESET_NOT, LAYOUT_RESET_ZERO, LAYOUT_RESET_HEADER, }; /* fieldwidth_t is defined in generic.h */ fieldwidth_t *alloc_path_layout(void); void get_path_layout__ (const struct vector_s *gpvec, enum layout_reset, fieldwidth_t *width); void get_path_layout (vector pathvec, int header, fieldwidth_t *width); fieldwidth_t *alloc_multipath_layout(void); void get_multipath_layout__ (const struct vector_s *gmvec, enum layout_reset, fieldwidth_t *width); void get_multipath_layout (vector mpvec, int header, fieldwidth_t *width); int snprint_path_header(struct strbuf *, const char *, const fieldwidth_t *); int snprint_multipath_header(struct strbuf *, const char *, const fieldwidth_t *); int snprint_path__ (const struct gen_path *, struct strbuf *, const char *, const fieldwidth_t *); #define snprint_path(buf, fmt, pp, w) \ snprint_path__(dm_path_to_gen(pp), buf, fmt, w) int snprint_multipath__ (const struct gen_multipath *, struct strbuf *, const char *, const fieldwidth_t *); #define snprint_multipath(buf, fmt, mp, w) \ snprint_multipath__(dm_multipath_to_gen(mp), buf, fmt, w) int snprint_multipath_topology__ (const struct gen_multipath *, struct strbuf *, int verbosity, const fieldwidth_t *); #define snprint_multipath_topology(buf, mpp, v, w) \ snprint_multipath_topology__ (dm_multipath_to_gen(mpp), buf, v, w) int snprint_multipath_topology_json(struct strbuf *, const struct vectors *vecs); int snprint_config__(const struct config *conf, struct strbuf *buff, const struct vector_s *hwtable, const struct vector_s *mpvec); char *snprint_config(const struct config *conf, int *len, const struct vector_s *hwtable, const struct vector_s *mpvec); int snprint_multipath_map_json(struct strbuf *, const struct multipath *mpp); int snprint_blacklist_report(struct config *, struct strbuf *); int snprint_wildcards(struct strbuf *); int snprint_status(struct strbuf *, const struct vectors *); int snprint_devices(struct config *, struct strbuf *, const struct vectors *); int snprint_path_serial(struct strbuf *, const struct path *); int snprint_host_wwnn(struct strbuf *, const struct path *); int snprint_host_wwpn(struct strbuf *, const struct path *); int snprint_tgt_wwnn(struct strbuf *, const struct path *); int snprint_tgt_wwpn(struct strbuf *, const struct path *); #define PROTOCOL_BUF_SIZE sizeof("scsi:unspec") int snprint_path_protocol(struct strbuf *, const struct path *); void print_multipath_topology__ (const struct gen_multipath * gmp, int verbosity); #define print_multipath_topology(mpp, v) \ print_multipath_topology__(dm_multipath_to_gen(mpp), v) void print_all_paths (vector pathvec, int banner); int snprint_path_attr(const struct gen_path* gp, struct strbuf *buf, char wildcard); int snprint_pathgroup_attr(const struct gen_pathgroup* gpg, struct strbuf *buf, char wildcard); int snprint_multipath_attr(const struct gen_multipath* gm, struct strbuf *buf, char wildcard); int snprint_multipath_style(const struct gen_multipath *gmp, struct strbuf *style, int verbosity); #endif /* PRINT_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/prio.c000066400000000000000000000075411475246302400205700ustar00rootroot00000000000000#include #include #include #include #include #include #include "debug.h" #include "util.h" #include "prio.h" #include "structs.h" #include "discovery.h" static const char * const prio_dir = MULTIPATH_DIR; static LIST_HEAD(prioritizers); unsigned int get_prio_timeout_ms(const struct path *pp) { if (pp->state == PATH_DOWN) return 10; else if (pp->checker_timeout) return pp->checker_timeout * 1000; else return DEF_TIMEOUT; } int init_prio(void) { #ifdef LOAD_ALL_SHARED_LIBS static const char *const all_prios[] = { PRIO_ALUA, PRIO_CONST, PRIO_DATACORE, PRIO_EMC, PRIO_HDS, PRIO_HP_SW, PRIO_ONTAP, PRIO_RANDOM, PRIO_RDAC, PRIO_WEIGHTED_PATH, PRIO_SYSFS, PRIO_PATH_LATENCY, PRIO_ANA, }; unsigned int i; for (i = 0; i < ARRAY_SIZE(all_prios); i++) add_prio(all_prios[i]); #else if (!add_prio(DEFAULT_PRIO)) return 1; #endif 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) { 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) { if (dlclose(p->handle) != 0) { condlog(0, "Cannot unload prioritizer %s: %s", p->name, dlerror()); } } free(p); } 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); } } static struct prio *prio_lookup(const char *name) { struct prio * p; if (!name || !strlen(name)) return NULL; list_for_each_entry(p, &prioritizers, node) { if (!strncmp(name, p->name, PRIO_NAME_LEN)) return p; } return NULL; } int prio_set_args (struct prio * p, const char * args) { return snprintf(p->args, PRIO_ARGS_LEN, "%s", args); } struct prio *add_prio (const char *name) { char libname[LIB_PRIO_NAMELEN]; struct stat stbuf; struct prio * p; char *errstr; p = alloc_prio(); if (!p) return NULL; snprintf(p->name, PRIO_NAME_LEN, "%s", name); snprintf(libname, LIB_PRIO_NAMELEN, "%s/libprio%s.so", prio_dir, name); if (stat(libname,&stbuf) < 0) { condlog(0,"Prioritizer '%s' not found in %s", name, prio_dir); goto out; } condlog(3, "loading %s prioritizer", libname); p->handle = dlopen(libname, RTLD_NOW); if (!p->handle) { if ((errstr = dlerror()) != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); goto out; } p->getprio = (int (*)(struct path *, char *)) dlsym(p->handle, "getprio"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); if (!p->getprio) goto out; list_add(&p->node, &prioritizers); return p; out: free_prio(p); return NULL; } int prio_getprio (struct prio * p, struct path * pp) { return p->getprio(pp, p->args); } int prio_selected (const struct prio * p) { if (!p) return 0; return (p->getprio) ? 1 : 0; } const char * prio_name (const struct prio * p) { return p->name; } const char * prio_args (const struct prio * p) { return p->args; } void prio_get(struct prio *dst, const char *name, const char *args) { struct prio * src = NULL; if (!dst) return; if (name && strlen(name)) { src = prio_lookup(name); if (!src) src = add_prio(name); } if (!src) { dst->getprio = NULL; return; } strncpy(dst->name, src->name, PRIO_NAME_LEN); if (args) strlcpy(dst->args, args, PRIO_ARGS_LEN); dst->getprio = src->getprio; dst->handle = NULL; src->refcount++; } void prio_put (struct prio * dst) { struct prio * src; if (!dst || !dst->getprio) return; src = prio_lookup(dst->name); memset(dst, 0x0, sizeof(struct prio)); free_prio(src); } multipath-tools-0.11.1/libmultipath/prio.h000066400000000000000000000032261475246302400205710ustar00rootroot00000000000000#ifndef PRIO_H_INCLUDED #define PRIO_H_INCLUDED /* * knowing about path struct gives flexibility to prioritizers */ #include "checkers.h" #include "vector.h" /* forward declaration to avoid circular dependency */ struct path; #include "list.h" #include "defaults.h" /* * Known prioritizers for use in hwtable.c */ #define PRIO_ALUA "alua" #define PRIO_CONST "const" #define PRIO_DATACORE "datacore" #define PRIO_EMC "emc" #define PRIO_HDS "hds" #define PRIO_HP_SW "hp_sw" #define PRIO_IET "iet" #define PRIO_ONTAP "ontap" #define PRIO_RANDOM "random" #define PRIO_RDAC "rdac" #define PRIO_WEIGHTED_PATH "weightedpath" #define PRIO_SYSFS "sysfs" #define PRIO_PATH_LATENCY "path_latency" #define PRIO_ANA "ana" /* * Value used to mark the fact prio was not defined */ #define PRIO_UNDEF -1 /* * strings lengths */ #define LIB_PRIO_NAMELEN 255 #define PRIO_NAME_LEN 16 #define PRIO_ARGS_LEN 255 struct prio { void *handle; int refcount; struct list_head node; char name[PRIO_NAME_LEN]; char args[PRIO_ARGS_LEN]; int (*getprio)(struct path *, char *); }; unsigned int get_prio_timeout_ms(const struct path *); int init_prio(void); void cleanup_prio (void); struct prio * add_prio (const char *); int prio_getprio (struct prio *, struct path *); void prio_get (struct prio *, const char *, const char *); void prio_put (struct prio *); int prio_selected (const struct prio *); const char * prio_name (const struct prio *); const char * prio_args (const struct prio *); int prio_set_args (struct prio *, const char *); /* The only function exported by prioritizer dynamic libraries (.so) */ int getprio(struct path *, char *); #endif /* PRIO_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/prioritizers/000077500000000000000000000000001475246302400222115ustar00rootroot00000000000000multipath-tools-0.11.1/libmultipath/prioritizers/Makefile000066400000000000000000000022031475246302400236460ustar00rootroot00000000000000# # Copyright (C) 2007 Christophe Varoqui, # TOPDIR=../.. include ../../Makefile.inc CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) CFLAGS += $(LIB_CFLAGS) LDFLAGS += -L$(multipathdir) -L$(mpathutildir) LIBDEPS = -lmultipath -lmpathutil -lm -lpthread -lrt # If you add or remove a prioritizer also update multipath/multipath.conf.5 LIBS = \ libprioalua.so \ libprioconst.so \ libpriodatacore.so \ libprioemc.so \ libpriohds.so \ libpriohp_sw.so \ libprioiet.so \ libprioontap.so \ libpriorandom.so \ libpriordac.so \ libprioweightedpath.so \ libpriopath_latency.so \ libpriosysfs.so ifeq ($(ANA_SUPPORT),1) LIBS += libprioana.so CPPFLAGS += -I../nvme endif all: $(LIBS) libprio%.so: %.o $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS) install: $(LIBS) $(Q)$(INSTALL_PROGRAM) -m 755 libprio*.so $(DESTDIR)$(plugindir) uninstall: $(Q)for file in $(LIBS); do $(RM) $(DESTDIR)$(plugindir)/$$file; done clean: dep_clean $(Q)$(RM) core *.a *.o *.gz *.so OBJS = $(LIBS:libprio%.so=%.o) alua_rtpg.o .SECONDARY: $(OBJS) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/libmultipath/prioritizers/alua.c000066400000000000000000000064651475246302400233120ustar00rootroot00000000000000/* * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. * * main.c * * Tool to make use of a SCSI-feature called Asymmetric Logical Unit Access. * It determines the ALUA state of a device and prints a priority value to * stdout. * * Author(s): Jan Kunigk * S. Bader * * This file is released under the GPL. */ #include #include "debug.h" #include "prio.h" #include "structs.h" #include "alua.h" #define ALUA_PRIO_NOT_SUPPORTED 1 #define ALUA_PRIO_RTPG_FAILED 2 #define ALUA_PRIO_GETAAS_FAILED 3 #define ALUA_PRIO_TPGS_FAILED 4 #define ALUA_PRIO_NO_INFORMATION 5 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_OFFLINE] = "offline", [AAS_TRANSITIONING] = "transitioning between states", }; static const char *aas_print_string(int rc) { rc &= 0x7f; if (rc & 0x70) return aas_string[AAS_RESERVED]; rc &= 0x0f; if (rc > AAS_RESERVED && rc < AAS_OFFLINE) return aas_string[AAS_RESERVED]; else return aas_string[rc]; } int get_alua_info(struct path * pp) { int rc; int tpg; bool diff_tpg; tpg = get_target_port_group(pp); if (tpg < 0) { rc = get_target_port_group_support(pp); if (rc < 0) return -ALUA_PRIO_TPGS_FAILED; if (rc == TPGS_NONE) return -ALUA_PRIO_NOT_SUPPORTED; return -ALUA_PRIO_RTPG_FAILED; } diff_tpg = (pp->tpg_id != GROUP_ID_UNDEF && pp->tpg_id != tpg); pp->tpg_id = tpg; condlog((diff_tpg) ? 2 : 4, "%s: reported target port group is %i", pp->dev, tpg); rc = get_asymmetric_access_state(pp, tpg); if (rc < 0) { condlog(2, "%s: get_asymmetric_access_state returned %d", __func__, rc); return -ALUA_PRIO_GETAAS_FAILED; } condlog(3, "%s: aas = %02x [%s]%s", pp->dev, rc, aas_print_string(rc), (rc & 0x80) ? " [preferred]" : ""); return rc; } int get_exclusive_pref_arg(char *args) { char *ptr; if (args == NULL) return 0; ptr = strstr(args, "exclusive_pref_bit"); if (!ptr) return 0; if (ptr[18] != '\0' && ptr[18] != ' ' && ptr[18] != '\t') return 0; if (ptr != args && ptr[-1] != ' ' && ptr[-1] != '\t') return 0; return 1; } int getprio (struct path * pp, char * args) { int rc; int aas; int priopath; int exclusive_pref; if (pp->fd < 0) return -ALUA_PRIO_NO_INFORMATION; exclusive_pref = get_exclusive_pref_arg(args); rc = get_alua_info(pp); if (rc >= 0) { aas = (rc & 0x0f); priopath = (rc & 0x80); switch(aas) { case AAS_OPTIMIZED: rc = 50; break; case AAS_NON_OPTIMIZED: rc = 10; break; case AAS_LBA_DEPENDENT: rc = 5; break; case AAS_STANDBY: rc = 1; break; default: rc = 0; } if (priopath && (aas != AAS_OPTIMIZED || exclusive_pref)) rc += 80; } else { switch(-rc) { case ALUA_PRIO_NOT_SUPPORTED: condlog(0, "%s: alua not supported", pp->dev); break; case ALUA_PRIO_RTPG_FAILED: condlog(0, "%s: couldn't get target port group", pp->dev); break; case ALUA_PRIO_GETAAS_FAILED: condlog(0, "%s: couldn't get asymmetric access state", pp->dev); break; case ALUA_PRIO_TPGS_FAILED: condlog(3, "%s: couldn't get supported alua states", pp->dev); break; } } return rc; } multipath-tools-0.11.1/libmultipath/prioritizers/alua.h000066400000000000000000000002131475246302400233000ustar00rootroot00000000000000#ifndef ALUA_H_INCLUDED #define ALUA_H_INCLUDED #include "alua_rtpg.h" #define PRIO_ALUA "alua" int prio_alua(struct path * pp); #endif multipath-tools-0.11.1/libmultipath/prioritizers/alua_rtpg.c000066400000000000000000000223131475246302400243340ustar00rootroot00000000000000/* * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. * * rtpg.c * * Tool to make use of a SCSI-feature called Asymmetric Logical Unit Access. * It determines the ALUA state of a device and prints a priority value to * stdout. * * Author(s): Jan Kunigk * S. Bader * * This file is released under the GPL. */ #include #include #include #include #include #include #include #include #include #include "../structs.h" #include "../prio.h" #include "../discovery.h" #include "debug.h" #include "alua_rtpg.h" #define SENSE_BUFF_LEN 32 #define SGIO_TIMEOUT 60000 #define PRINT_DEBUG(f, a...) \ condlog(4, "alua: " f, ##a) /* * Optionally print the commands sent and the data received a hex dump. */ #if DEBUG > 0 #if DEBUG_DUMPHEX > 0 #define PRINT_HEX(p, l) print_hex(p, l) void print_hex(unsigned char *p, unsigned long len) { int i; for(i = 0; i < len; i++) { if (i % 16 == 0) printf("%04x: ", i); printf("%02x%s", p[i], (((i + 1) % 16) == 0) ? "\n" : " "); } printf("\n"); } #else #define PRINT_HEX(p, l) #endif #else #define PRINT_HEX(p, l) #endif /* * Returns 0 if the SCSI command either was successful or if the an error was * recovered, otherwise 1. (definitions taken from sg_err.h) */ #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define NOT_READY 0x2 #define UNIT_ATTENTION 0x6 enum scsi_disposition { SCSI_GOOD = 0, SCSI_ERROR, SCSI_RETRY, }; static int scsi_error(struct sg_io_hdr *hdr, int opcode) { int sense_key, asc, ascq; /* Treat SG_ERR here to get rid of sg_err.[ch] */ hdr->status &= 0x7e; if ( (hdr->status == 0) && (hdr->host_status == 0) && (hdr->driver_status == 0) ) { return SCSI_GOOD; } sense_key = asc = ascq = -1; if ( (hdr->status == SCSI_CHECK_CONDITION) || (hdr->status == SCSI_COMMAND_TERMINATED) || ((hdr->driver_status & 0xf) == SG_ERR_DRIVER_SENSE) ) { if (hdr->sbp && (hdr->sb_len_wr > 2)) { unsigned char * sense_buffer = hdr->sbp; if (sense_buffer[0] & 0x2) { sense_key = sense_buffer[1] & 0xf; if (hdr->sb_len_wr > 3) asc = sense_buffer[2]; if (hdr->sb_len_wr > 4) ascq = sense_buffer[3]; } else { sense_key = sense_buffer[2] & 0xf; if (hdr->sb_len_wr > 13) asc = sense_buffer[12]; if (hdr->sb_len_wr > 14) ascq = sense_buffer[13]; } if (sense_key == RECOVERED_ERROR) return SCSI_GOOD; } } PRINT_DEBUG("alua: SCSI error for command %02x: status %02x, sense %02x/%02x/%02x", opcode, hdr->status, sense_key, asc, ascq); if (sense_key == UNIT_ATTENTION || sense_key == NOT_READY) return SCSI_RETRY; else return SCSI_ERROR; } /* * Helper function to setup and run a SCSI inquiry command. */ static int do_inquiry_sg(int fd, int evpd, unsigned int codepage, void *resp, int resplen, unsigned int timeout_ms) { struct inquiry_command cmd; struct sg_io_hdr hdr; unsigned char sense[SENSE_BUFF_LEN]; int rc, retry_count = 3; retry: memset(&cmd, 0, sizeof(cmd)); cmd.op = OPERATION_CODE_INQUIRY; if (evpd) { inquiry_command_set_evpd(&cmd); cmd.page = codepage; } put_unaligned_be16(resplen, cmd.length); PRINT_HEX((unsigned char *) &cmd, sizeof(cmd)); memset(&hdr, 0, sizeof(hdr)); hdr.interface_id = 'S'; hdr.cmdp = (unsigned char *) &cmd; hdr.cmd_len = sizeof(cmd); hdr.dxfer_direction = SG_DXFER_FROM_DEV; hdr.dxferp = resp; hdr.dxfer_len = resplen; hdr.sbp = sense; hdr.mx_sb_len = sizeof(sense); hdr.timeout = timeout_ms; if (ioctl(fd, SG_IO, &hdr) < 0) { PRINT_DEBUG("do_inquiry: IOCTL failed!"); return -RTPG_INQUIRY_FAILED; } rc = scsi_error(&hdr, OPERATION_CODE_INQUIRY); if (rc == SCSI_ERROR) { PRINT_DEBUG("do_inquiry: SCSI error!"); return -RTPG_INQUIRY_FAILED; } else if (rc == SCSI_RETRY) { if (--retry_count >= 0) goto retry; PRINT_DEBUG("do_inquiry: retries exhausted!"); return -RTPG_INQUIRY_FAILED; } PRINT_HEX((unsigned char *) resp, resplen); return 0; } int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, void *resp, int resplen) { struct udev_device *ud = NULL; if (pp->udev) ud = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_device"); if (ud != NULL) { int rc; if (!evpd) rc = sysfs_get_inquiry(ud, resp, resplen); else rc = sysfs_get_vpd(ud, codepage, resp, resplen); if (rc >= 0) { PRINT_HEX((unsigned char *) resp, resplen); return 0; } } return do_inquiry_sg(pp->fd, evpd, codepage, resp, resplen, get_prio_timeout_ms(pp)); } /* * This function returns the support for target port groups by evaluating the * data returned by the standard inquiry command. */ int get_target_port_group_support(const struct path *pp) { struct inquiry_data inq; int rc; memset((unsigned char *)&inq, 0, sizeof(inq)); rc = do_inquiry(pp, 0, 0x00, &inq, sizeof(inq)); if (!rc) { rc = inquiry_data_get_tpgs(&inq); } return rc; } int get_target_port_group(const struct path * pp) { unsigned char *buf; const struct vpd83_data * vpd83; const struct vpd83_dscr * dscr; int rc; int buflen, scsi_buflen; buflen = VPD_BUFLEN; buf = (unsigned char *)malloc(buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate" "%u bytes", buflen); return -RTPG_RTPG_FAILED; } memset(buf, 0, buflen); rc = do_inquiry(pp, 1, 0x83, buf, buflen); if (rc < 0) goto out; scsi_buflen = get_unaligned_be16(&buf[2]) + 4; if (scsi_buflen >= USHRT_MAX) scsi_buflen = USHRT_MAX; if (buflen < scsi_buflen) { free(buf); buf = (unsigned char *)malloc(scsi_buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate" "%u bytes", scsi_buflen); return -RTPG_RTPG_FAILED; } buflen = scsi_buflen; memset(buf, 0, buflen); rc = do_inquiry(pp, 1, 0x83, buf, buflen); if (rc < 0) goto out; } vpd83 = (struct vpd83_data *) buf; rc = -RTPG_NO_TPG_IDENTIFIER; FOR_EACH_VPD83_DSCR(vpd83, dscr) { if (vpd83_dscr_istype(dscr, IDTYPE_TARGET_PORT_GROUP)) { const struct vpd83_tpg_dscr *p; if (rc != -RTPG_NO_TPG_IDENTIFIER) { PRINT_DEBUG("get_target_port_group: more " "than one TPG identifier found!"); continue; } p = (const struct vpd83_tpg_dscr *)dscr->data; rc = get_unaligned_be16(p->tpg); } } if (rc == -RTPG_NO_TPG_IDENTIFIER) { PRINT_DEBUG("get_target_port_group: " "no TPG identifier found!"); } out: free(buf); return rc; } int do_rtpg(int fd, void* resp, long resplen, unsigned int timeout_ms) { struct rtpg_command cmd; struct sg_io_hdr hdr; unsigned char sense[SENSE_BUFF_LEN]; int retry_count = 3, rc; retry: memset(&cmd, 0, sizeof(cmd)); cmd.op = OPERATION_CODE_RTPG; rtpg_command_set_service_action(&cmd); put_unaligned_be32(resplen, cmd.length); PRINT_HEX((unsigned char *) &cmd, sizeof(cmd)); memset(&hdr, 0, sizeof(hdr)); hdr.interface_id = 'S'; hdr.cmdp = (unsigned char *) &cmd; hdr.cmd_len = sizeof(cmd); hdr.dxfer_direction = SG_DXFER_FROM_DEV; hdr.dxferp = resp; hdr.dxfer_len = resplen; hdr.mx_sb_len = sizeof(sense); hdr.sbp = sense; hdr.timeout = timeout_ms; if (ioctl(fd, SG_IO, &hdr) < 0) { condlog(2, "%s: sg ioctl failed: %s", __func__, strerror(errno)); return -RTPG_RTPG_FAILED; } rc = scsi_error(&hdr, OPERATION_CODE_RTPG); if (rc == SCSI_ERROR) { PRINT_DEBUG("do_rtpg: SCSI error!"); return -RTPG_RTPG_FAILED; } else if (rc == SCSI_RETRY) { if (--retry_count >= 0) goto retry; PRINT_DEBUG("do_rtpg: retries exhausted!"); return -RTPG_RTPG_FAILED; } PRINT_HEX(resp, resplen); return 0; } int get_asymmetric_access_state(const struct path *pp, unsigned int tpg) { unsigned char *buf; struct rtpg_data * tpgd; struct rtpg_tpg_dscr * dscr; int rc; unsigned int buflen; uint64_t scsi_buflen; unsigned int timeout_ms = get_prio_timeout_ms(pp); int fd = pp->fd; buflen = VPD_BUFLEN; buf = (unsigned char *)malloc(buflen); if (!buf) { PRINT_DEBUG ("malloc failed: could not allocate" "%u bytes", buflen); return -RTPG_RTPG_FAILED; } memset(buf, 0, buflen); rc = do_rtpg(fd, buf, buflen, timeout_ms); if (rc < 0) { PRINT_DEBUG("%s: do_rtpg returned %d", __func__, rc); goto out; } scsi_buflen = get_unaligned_be32(&buf[0]) + 4; if (scsi_buflen > UINT_MAX) scsi_buflen = UINT_MAX; if (buflen < scsi_buflen) { free(buf); buf = (unsigned char *)malloc(scsi_buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate %" PRIu64 " bytes", scsi_buflen); return -RTPG_RTPG_FAILED; } buflen = scsi_buflen; memset(buf, 0, buflen); rc = do_rtpg(fd, buf, buflen, timeout_ms); if (rc < 0) goto out; } tpgd = (struct rtpg_data *) buf; rc = -RTPG_TPG_NOT_FOUND; RTPG_FOR_EACH_PORT_GROUP(tpgd, dscr) { if (get_unaligned_be16(dscr->tpg) == tpg) { if (rc != -RTPG_TPG_NOT_FOUND) { PRINT_DEBUG("get_asymmetric_access_state: " "more than one entry with same port " "group."); } else { condlog(5, "pref=%i", dscr->b0); rc = rtpg_tpg_dscr_get_aas(dscr); } } } if (rc == -RTPG_TPG_NOT_FOUND) condlog(2, "%s: port group %d not found", __func__, tpg); out: free(buf); return rc; } multipath-tools-0.11.1/libmultipath/prioritizers/alua_rtpg.h000066400000000000000000000014571475246302400243470ustar00rootroot00000000000000/* * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. * * rtpg.h * * Tool to make use of a SCSI-feature called Asymmetric Logical Unit Access. * It determines the ALUA state of a device and prints a priority value to * stdout. * * Author(s): Jan Kunigk * S. Bader * * This file is released under the GPL. */ #ifndef ALUA_RTPG_H_INCLUDED #define ALUA_RTPG_H_INCLUDED #include "alua_spc3.h" #define RTPG_SUCCESS 0 #define RTPG_INQUIRY_FAILED 1 #define RTPG_NO_TPG_IDENTIFIER 2 #define RTPG_RTPG_FAILED 3 #define RTPG_TPG_NOT_FOUND 4 int get_target_port_group_support(const struct path *pp); int get_target_port_group(const struct path *pp); int get_asymmetric_access_state(const struct path *pp, unsigned int tpg); #endif /* ALUA_RTPG_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/prioritizers/alua_spc3.h000066400000000000000000000244231475246302400242410ustar00rootroot00000000000000/* * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. * * spc3.h * * Tool to make use of a SCSI-feature called Asymmetric Logical Unit Access. * It determines the ALUA state of a device and prints a priority value to * stdout. * * Author(s): Jan Kunigk * S. Bader * * This file is released under the GPL. */ #ifndef ALUA_SPC3_H_INCLUDED #define ALUA_SPC3_H_INCLUDED #include "../unaligned.h" /*============================================================================= * Definitions to support the standard inquiry command as defined in SPC-3. * If the evpd (enable vital product data) bit is set the data that will be * returned is selected by the page field. This field must be 0 if the evpd * bit is not set. *============================================================================= */ #define OPERATION_CODE_INQUIRY 0x12 struct inquiry_command { unsigned char op; unsigned char b1; /* xxxxxx.. = reserved */ /* ......x. = obsolete */ /* .......x = evpd */ unsigned char page; unsigned char length[2]; unsigned char control; } __attribute__((packed)); static inline void inquiry_command_set_evpd(struct inquiry_command *ic) { ic->b1 |= 1; } /*----------------------------------------------------------------------------- * Data returned by the standard inquiry command. *----------------------------------------------------------------------------- * * Peripheral qualifier codes. */ #define PQ_CONNECTED 0x0 #define PQ_DISCONNECTED 0x1 #define PQ_UNSUPPORTED 0x3 /* Defined peripheral device types. */ #define PDT_DIRECT_ACCESS 0x00 #define PDT_SEQUENTIAL_ACCESS 0x01 #define PDT_PRINTER 0x02 #define PDT_PROCESSOR 0x03 #define PDT_WRITE_ONCE 0x04 #define PDT_CD_DVD 0x05 #define PDT_SCANNER 0x06 #define PDT_OPTICAL_MEMORY 0x07 #define PDT_MEDIUM_CHANGER 0x08 #define PDT_COMMUNICATIONS 0x09 #define PDT_STORAGE_ARRAY_CONTROLLER 0x0c #define PDT_ENCLOSURE_SERVICES 0x0d #define PDT_SIMPLIFIED_DIRECT_ACCESS 0x0e #define PDT_OPTICAL_CARD_READER_WRITER 0x0f #define PDT_BRIDGE_CONTROLLER 0x10 #define PDT_OBJECT_BASED 0x11 #define PDT_AUTOMATION_INTERFACE 0x12 #define PDT_LUN 0x1e #define PDT_UNKNOWN 0x1f /* Defined version codes. */ #define VERSION_NONE 0x00 #define VERSION_SPC 0x03 #define VERSION_SPC2 0x04 #define VERSION_SPC3 0x05 /* Defined TPGS field values. */ #define TPGS_UNDEF -1 #define TPGS_NONE 0x0 #define TPGS_IMPLICIT 0x1 #define TPGS_EXPLICIT 0x2 #define TPGS_BOTH 0x3 struct inquiry_data { unsigned char b0; /* xxx..... = peripheral_qualifier */ /* ...xxxxx = peripheral_device_type */ unsigned char b1; /* x....... = removable medium */ /* .xxxxxxx = reserved */ unsigned char version; unsigned char b3; /* xx...... = obsolete */ /* ..x..... = normal aca supported */ /* ...x.... = hirarchichal lun supp. */ /* ....xxxx = response format */ /* 2 is spc-3 format */ unsigned char length; unsigned char b5; /* x....... = storage controller */ /* component supported */ /* .x...... = access controls coord. */ /* ..xx.... = target port group supp.*/ /* ....x... = third party copy supp. */ /* .....xx. = reserved */ /* .......x = protection info supp. */ unsigned char b6; /* x....... = bque */ /* .x...... = enclosure services sup.*/ /* ..x..... = vs1 */ /* ...x.... = multiport support */ /* ....x... = medium changer */ /* .....xx. = obsolete */ /* .......x = add16 */ unsigned char b7; /* xx...... = obsolete */ /* ..x..... = wbus16 */ /* ...x.... = sync */ /* ....x... = linked commands supp. */ /* .....x.. = obsolete */ /* ......x. = command queue support */ /* .......x = vs2 */ unsigned char vendor_identification[8]; unsigned char product_identification[16]; unsigned char product_revision[4]; unsigned char vendor_specific[20]; unsigned char b56; /* xxxx.... = reserved */ /* ....xx.. = clocking */ /* ......x. = qas */ /* .......x = ius */ unsigned char reserved4; unsigned char version_descriptor[8][2]; unsigned char reserved5[22]; unsigned char vendor_parameters[0]; } __attribute__((packed)); static inline int inquiry_data_get_tpgs(struct inquiry_data *id) { return (id->b5 >> 4) & 3; } /*----------------------------------------------------------------------------- * Inquiry data returned when requesting vital product data page 0x83. *----------------------------------------------------------------------------- */ #define CODESET_BINARY 0x1 #define CODESET_ASCII 0x2 #define CODESET_UTF8 0x3 #define ASSOCIATION_UNIT 0x0 #define ASSOCIATION_PORT 0x1 #define ASSOCIATION_DEVICE 0x2 #define IDTYPE_VENDOR_SPECIFIC 0x0 #define IDTYPE_T10_VENDOR_ID 0x1 #define IDTYPE_EUI64 0x2 #define IDTYPE_NAA 0x3 #define IDTYPE_RELATIVE_TPG_ID 0x4 #define IDTYPE_TARGET_PORT_GROUP 0x5 #define IDTYPE_LUN_GROUP 0x6 #define IDTYPE_MD5_LUN_ID 0x7 #define IDTYPE_SCSI_NAME_STRING 0x8 struct vpd83_tpg_dscr { unsigned char reserved1[2]; unsigned char tpg[2]; } __attribute__((packed)); struct vpd83_dscr { unsigned char b0; /* xxxx.... = protocol id */ /* ....xxxx = codeset */ unsigned char b1; /* x....... = protocol id valid */ /* .x...... = reserved */ /* ..xx.... = association */ /* ....xxxx = id type */ unsigned char reserved2; unsigned char length; /* size-4 */ unsigned char data[0]; } __attribute__((packed)); static inline int vpd83_dscr_istype(const struct vpd83_dscr *d, unsigned char type) { return ((d->b1 & 7) == type); } struct vpd83_data { unsigned char b0; /* xxx..... = peripheral_qualifier */ /* ...xxxxx = peripheral_device_type */ unsigned char page_code; /* 0x83 */ unsigned char length[2]; /* size-4 */ struct vpd83_dscr data[0]; } __attribute__((packed)); #define VPD_BUFLEN 4096 /* Returns the max byte offset in the VPD page from the start of the page */ static inline unsigned int vpd83_max_offs(const struct vpd83_data *p) { uint16_t len = get_unaligned_be16(p->length) + 4; return len <= VPD_BUFLEN ? len : VPD_BUFLEN; } static inline bool vpd83_descr_fits(const struct vpd83_dscr *d, const struct vpd83_data *p) { ptrdiff_t max_offs = vpd83_max_offs(p); ptrdiff_t offs = ((const char *)d - (const char *)p); /* make sure we can read d->length */ if (offs < 0 || offs > max_offs - 4) return false; offs += d->length + 4; return offs <= max_offs; } static inline const struct vpd83_dscr * vpd83_next_dscr(const struct vpd83_dscr *d, const struct vpd83_data *p) { ptrdiff_t offs = ((const char *)d - (const char *)p) + d->length + 4; return (const struct vpd83_dscr *)((const char *)p + offs); } /*----------------------------------------------------------------------------- * This macro should be used to walk through all identification descriptors * defined in the code page 0x83. * The argument p is a pointer to the code page 0x83 data and d is used to * point to the current descriptor. *----------------------------------------------------------------------------- */ #define FOR_EACH_VPD83_DSCR(p, d) \ for( \ d = p->data; \ vpd83_descr_fits(d, p); \ d = vpd83_next_dscr(d, p) \ ) /*============================================================================= * The following structures and macros are used to call the report target port * groups command defined in SPC-3. * This command is used to get information about the target port groups (which * states are supported, which ports belong to this group, and so on) and the * current state of each target port group. *============================================================================= */ #define OPERATION_CODE_RTPG 0xa3 #define SERVICE_ACTION_RTPG 0x0a struct rtpg_command { unsigned char op; /* 0xa3 */ unsigned char b1; /* xxx..... = reserved */ /* ...xxxxx = service action (0x0a) */ unsigned char reserved2[4]; unsigned char length[4]; unsigned char reserved3; unsigned char control; } __attribute__((packed)); static inline void rtpg_command_set_service_action(struct rtpg_command *cmd) { cmd->b1 = (cmd->b1 & 0xe0) | SERVICE_ACTION_RTPG; } struct rtpg_tp_dscr { unsigned char obsolete1[2]; /* The Relative Target Port Identifier of a target port. */ unsigned char rtpi[2]; } __attribute__((packed)); #define AAS_OPTIMIZED 0x0 #define AAS_NON_OPTIMIZED 0x1 #define AAS_STANDBY 0x2 #define AAS_UNAVAILABLE 0x3 #define AAS_LBA_DEPENDENT 0x4 #define AAS_RESERVED 0x5 #define AAS_OFFLINE 0xe #define AAS_TRANSITIONING 0xf #define TPG_STATUS_NONE 0x0 #define TPG_STATUS_SET 0x1 #define TPG_STATUS_IMPLICIT_CHANGE 0x2 struct rtpg_tpg_dscr { unsigned char b0; /* x....... = pref(ered) port */ /* .xxx.... = reserved */ /* ....xxxx = asymmetric access state*/ unsigned char b1; /* xxx..... = reserved */ /* ...x.... = LBA dependent support */ /* ....x... = unavailable support */ /* .....x.. = standby support */ /* ......x. = non-optimized support */ /* .......x = optimized support */ unsigned char tpg[2]; unsigned char reserved3; unsigned char status; unsigned char vendor_unique; unsigned char port_count; struct rtpg_tp_dscr data[0]; } __attribute__((packed)); static inline int rtpg_tpg_dscr_get_aas(struct rtpg_tpg_dscr *d) { return (d->b0 & 0x8f); } struct rtpg_data { unsigned char length[4]; /* size-4 */ struct rtpg_tpg_dscr data[0]; } __attribute__((packed)); #define RTPG_FOR_EACH_PORT_GROUP(p, g) \ for( \ g = &(p->data[0]); \ ((char *) g) < ((char *) p) + get_unaligned_be32(p->length); \ g = (struct rtpg_tpg_dscr *) ( \ ((char *) g) + \ sizeof(struct rtpg_tpg_dscr) + \ g->port_count * sizeof(struct rtpg_tp_dscr) \ ) \ ) #endif /* ALUA_SPC3_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/prioritizers/ana.c000066400000000000000000000151411475246302400231160ustar00rootroot00000000000000/* * (C) Copyright HUAWEI Technology Corp. 2017 All Rights Reserved. * * ana.c * Version 1.00 * * Tool to make use of a NVMe-feature called Asymmetric Namespace Access. * It determines the ANA state of a device and prints a priority value to stdout. * * Author(s): Cheng Jike * Li Jie * * This file is released under the GPL version 2, or any later version. */ #include #include #include #include #include #include #include "debug.h" #include "nvme-lib.h" #include "prio.h" #include "util.h" #include "structs.h" enum { ANA_ERR_GETCTRL_FAILED = 1, ANA_ERR_NOT_NVME, ANA_ERR_NOT_SUPPORTED, ANA_ERR_GETANAS_OVERFLOW, ANA_ERR_GETANAS_NOTFOUND, ANA_ERR_GETANALOG_FAILED, ANA_ERR_GETNSID_FAILED, ANA_ERR_GETNS_FAILED, ANA_ERR_NO_MEMORY, ANA_ERR_NO_INFORMATION, }; static const char *ana_errmsg[] = { [ANA_ERR_GETCTRL_FAILED] = "couldn't get ctrl info", [ANA_ERR_NOT_NVME] = "not an NVMe device", [ANA_ERR_NOT_SUPPORTED] = "ANA not supported", [ANA_ERR_GETANAS_OVERFLOW] = "buffer overflow in ANA log", [ANA_ERR_GETANAS_NOTFOUND] = "NSID or ANAGRPID not found", [ANA_ERR_GETANALOG_FAILED] = "couldn't get ana log", [ANA_ERR_GETNSID_FAILED] = "couldn't get NSID", [ANA_ERR_GETNS_FAILED] = "couldn't get namespace info", [ANA_ERR_NO_MEMORY] = "out of memory", [ANA_ERR_NO_INFORMATION] = "invalid fd", }; static const char *anas_string[] = { [NVME_ANA_OPTIMIZED] = "ANA Optimized State", [NVME_ANA_NONOPTIMIZED] = "ANA Non-Optimized State", [NVME_ANA_INACCESSIBLE] = "ANA Inaccessible State", [NVME_ANA_PERSISTENT_LOSS] = "ANA Persistent Loss State", [NVME_ANA_CHANGE] = "ANA Change state", }; static const char *aas_print_string(int rc) { rc &= 0xff; if (rc >= 0 && rc < (int)ARRAY_SIZE(anas_string) && anas_string[rc] != NULL) return anas_string[rc]; return "invalid ANA state"; } static int get_ana_state(__u32 nsid, __u32 anagrpid, void *ana_log, size_t ana_log_len) { void *base = ana_log; struct nvme_ana_rsp_hdr *hdr = base; struct nvme_ana_group_desc *ana_desc; size_t offset = sizeof(struct nvme_ana_rsp_hdr); __u32 nr_nsids; size_t nsid_buf_size; int i; unsigned int j; for (i = 0; i < le16_to_cpu(hdr->ngrps); i++) { ana_desc = base + offset; offset += sizeof(*ana_desc); if (offset > ana_log_len) return -ANA_ERR_GETANAS_OVERFLOW; nr_nsids = le32_to_cpu(ana_desc->nnsids); nsid_buf_size = nr_nsids * sizeof(__le32); offset += nsid_buf_size; if (offset > ana_log_len) return -ANA_ERR_GETANAS_OVERFLOW; for (j = 0; j < nr_nsids; j++) { if (nsid == le32_to_cpu(ana_desc->nsids[j])) return ana_desc->state; } if (anagrpid != 0 && anagrpid == le32_to_cpu(ana_desc->grpid)) return ana_desc->state; } return -ANA_ERR_GETANAS_NOTFOUND; } static int get_ana_info(struct path * pp) { int rc; __u32 nsid; struct nvme_id_ctrl ctrl; struct nvme_id_ns ns; void *ana_log; size_t ana_log_len; bool is_anagrpid_const; rc = nvme_id_ctrl_ana(pp->fd, &ctrl); if (rc < 0) { log_nvme_errcode(rc, pp->dev, "nvme_identify_ctrl"); return -ANA_ERR_GETCTRL_FAILED; } else if (rc == 0) return -ANA_ERR_NOT_SUPPORTED; nsid = nvme_get_nsid(pp->fd); if (nsid <= 0) { log_nvme_errcode(rc, pp->dev, "nvme_get_nsid"); return -ANA_ERR_GETNSID_FAILED; } is_anagrpid_const = ctrl.anacap & (1 << 6); /* * Code copied from nvme-cli/nvme.c. We don't need to allocate an * [nanagrpid*mnan] array of NSIDs because each NSID can occur at most * in one ANA group. */ ana_log_len = sizeof(struct nvme_ana_rsp_hdr) + le32_to_cpu(ctrl.nanagrpid) * sizeof(struct nvme_ana_group_desc); if (is_anagrpid_const) { rc = nvme_identify_ns(pp->fd, nsid, 0, &ns); if (rc) { log_nvme_errcode(rc, pp->dev, "nvme_identify_ns"); return -ANA_ERR_GETNS_FAILED; } } else ana_log_len += le32_to_cpu(ctrl.mnan) * sizeof(__le32); ana_log = malloc(ana_log_len); if (!ana_log) return -ANA_ERR_NO_MEMORY; pthread_cleanup_push(free, ana_log); rc = nvme_ana_log(pp->fd, ana_log, ana_log_len, is_anagrpid_const ? NVME_ANA_LOG_RGO : 0); if (rc) { log_nvme_errcode(rc, pp->dev, "nvme_ana_log"); rc = -ANA_ERR_GETANALOG_FAILED; } else rc = get_ana_state(nsid, is_anagrpid_const ? le32_to_cpu(ns.anagrpid) : 0, ana_log, ana_log_len); pthread_cleanup_pop(1); if (rc >= 0) condlog(4, "%s: ana state = %02x [%s]", pp->dev, rc, aas_print_string(rc)); return rc; } /* * Priorities modeled roughly after the ALUA model (alua.c/sysfs.c) * Reference: ANA Base Protocol (NVMe TP 4004a, 11/13/2018). * * Differences: * * - The ANA base spec defines no implicit or explicit (STPG) state management. * If a state is encountered that doesn't allow normal I/O (all except * OPTIMIZED and NON_OPTIMIZED), we can't do anything but either wait for a * Access State Change Notice (can't do that in multipathd as we don't receive * those), or retry commands in regular time intervals until ANATT is expired * (not implemented). Mapping UNAVAILABLE state to ALUA STANDBY is the best we * can currently do. * * FIXME: Waiting for ANATT could be implemented with a "delayed failback" * mechanism. The current "failback" method can't be used, as it would * affect failback to every state, and here only failback to UNAVAILABLE * should be delayed. * * - PERSISTENT_LOSS state is even below ALUA's UNAVAILABLE state. * FIXME: According to the ANA TP, accessing paths in PERSISTENT_LOSS state * in any way makes no sense (e.g. §8.19.6 - paths in this state shouldn't * even be checked under "all paths down" conditions). Device mapper can, * and will, select a PG for IO if it has non-failed paths, even if the * PG has priority 0. We could avoid that only with an "ANA path checker". * * - ALUA has no CHANGE state. The ANA TP §8.18.3 / §8.19.4 suggests * that CHANGE state should be treated in roughly the same way as * INACCESSIBLE. Therefore we assign the same prio to it. * * - ALUA's LBA-dependent state has no ANA equivalent. */ int getprio(struct path *pp, __attribute__((unused)) char *args) { int rc; if (pp->fd < 0) rc = -ANA_ERR_NO_INFORMATION; else rc = get_ana_info(pp); switch (rc) { case NVME_ANA_OPTIMIZED: return 50; case NVME_ANA_NONOPTIMIZED: return 10; case NVME_ANA_INACCESSIBLE: case NVME_ANA_CHANGE: return 1; case NVME_ANA_PERSISTENT_LOSS: return 0; default: break; } if (rc < 0 && -rc < (int)ARRAY_SIZE(ana_errmsg)) condlog(2, "%s: ANA error: %s", pp->dev, ana_errmsg[-rc]); else condlog(1, "%s: invalid ANA rc code %d", pp->dev, rc); return -1; } multipath-tools-0.11.1/libmultipath/prioritizers/const.c000066400000000000000000000002261475246302400235030ustar00rootroot00000000000000#include #include "prio.h" int getprio(__attribute__((unused)) struct path * pp, __attribute__((unused)) char * args) { return 1; } multipath-tools-0.11.1/libmultipath/prioritizers/datacore.c000066400000000000000000000046471475246302400241520ustar00rootroot00000000000000/* * (C) 2010 Christophe Varoqui * (C) 2009 Dembach Goo Informatik GmbH & Co KG * Manon Goo * * datacore.c * Version 0.9 * * This program was inspired by work from * Matthias Rudolph * * This work is made available on the basis of the * GPLv2 for details see . * * Manon Goo 2009 * * */ #include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #define INQ_REPLY_LEN 255 #define INQ_CMD_CODE 0x12 #define INQ_CMD_LEN 6 #define dc_log(prio, msg) condlog(prio, "%s: datacore prio: " msg, dev) int datacore_prio (const char *dev, int sg_fd, char * args, unsigned int timeout_ms) { int k; char sdsname[32]; unsigned char inqCmdBlk[INQ_CMD_LEN] = { INQ_CMD_CODE, 0, 0, 0, INQ_REPLY_LEN, 0 }; unsigned char inqBuff[INQ_REPLY_LEN]; unsigned char *inqBuffp = inqBuff; unsigned char sense_buffer[32]; sg_io_hdr_t io_hdr; char preferredsds_buff[255] = ""; char * preferredsds = &preferredsds_buff[0]; if (!args) { dc_log(0, "need prio_args with preferredsds set"); 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 { dc_log(0, "unexpected prio_args format"); return 0; } // on error just return prio 0 if (strlen(preferredsds) <= 1) { dc_log(0, "prio args: preferredsds too short (1 character min)"); return 0; } if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) return 0; memset (&io_hdr, 0, sizeof (sg_io_hdr_t)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_buffer); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = INQ_REPLY_LEN; io_hdr.dxferp = inqBuff; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_buffer; io_hdr.timeout = timeout_ms; // on error just return prio 0 if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) return 0; if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) return 0; snprintf(sdsname, sizeof(sdsname), "%.16s", inqBuffp + 112); if (strstr(sdsname , preferredsds)) return 1; return 0; } int getprio(struct path * pp, char * args) { return datacore_prio(pp->dev, pp->fd, args, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/emc.c000066400000000000000000000044771475246302400231350ustar00rootroot00000000000000#include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 #define pp_emc_log(prio, msg) condlog(prio, "%s: emc prio: " msg, dev) int emc_clariion_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char sense_buffer[128]; unsigned char sb[128]; unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {INQUIRY_CMD, 1, 0xC0, 0, sizeof(sense_buffer), 0}; struct sg_io_hdr io_hdr; int ret = PRIO_UNDEF; memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(&sense_buffer, 0, 128); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = sizeof (sense_buffer); io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) { pp_emc_log(0, "sending query command failed"); goto out; } if (io_hdr.info & SG_INFO_OK_MASK) { pp_emc_log(0, "query command indicates error"); goto out; } if (/* Verify the code page - right page & revision */ sense_buffer[1] != 0xc0 || sense_buffer[9] != 0x00) { pp_emc_log(0, "path unit report page in unknown format"); goto out; } if ( /* Effective initiator type */ sense_buffer[27] != 0x03 /* * Failover mode should be set to 1 (PNR failover mode) * or 4 (ALUA failover mode). */ || (((sense_buffer[28] & 0x07) != 0x04) && ((sense_buffer[28] & 0x07) != 0x06)) /* Arraycommpath should be set to 1 */ || (sense_buffer[30] & 0x04) != 0x04) { pp_emc_log(0, "path not correctly configured for failover"); goto out; } if ( /* LUN operations should indicate normal operations */ sense_buffer[48] != 0x00) { pp_emc_log(0, "path not available for normal operations"); goto out; } /* LUN state: unbound, bound, or owned */ ret = sense_buffer[4]; /* Is the default owner equal to this path? */ /* Note this will switch to the default priority group, even if * it is not the currently active one. */ if (sense_buffer[5] == sense_buffer[8]) ret+=2; out: return(ret); } int getprio (struct path *pp, __attribute__((unused)) char *args) { return emc_clariion_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/hds.c000066400000000000000000000130401475246302400231310ustar00rootroot00000000000000/* * (C) Copyright HDS GmbH 2006. All Rights Reserved. * * pp_hds_modular.c * Version 2.00 * * Prioritizer for Device Mapper Multipath and HDS Storage * * Hitachis Modular Storage contains two controllers for redundancy. The * Storage internal LUN (LDEV) will normally allocated via two paths to the * server (one path per controller). For performance reasons should the server * access to a LDEV only via one controller. The other path to the other * controller is stand-by. It is also possible to allocate more as one path * for a LDEV per controller. Here is active/active access allowed. The other * paths via the other controller are stand-by. * * This prioritizer checks with inquiry command the represented LDEV and * Controller number and gives back a priority followed by this scheme: * * CONTROLLER ODD and LDEV ODD: PRIORITY 1 * CONTROLLER ODD and LDEV EVEN: PRIORITY 0 * CONTROLLER EVEN and LDEV ODD: PRIORITY 0 * CONTROLLER EVEN and LDEV EVEN: PRIORITY 1 * * In the storage you can define for each LDEV a owner controller. If the * server makes IOs via the other controller the storage will switch the * ownership automatically. In this case you can see in the storage that the * current controller is different from the default controller, but this is * absolutely no problem. * * With this prioritizer it is possible to establish a static load balancing. * Half of the LUNs are accessed via one HBA/storage controller and the other * half via the other HBA/storage controller. * * In cluster environments (RAC) it also guarantees that all cluster nodes have * access to the LDEVs via the same controller. * * You can run the prioritizer manually in verbose mode: * # pp_hds_modular -v 8:224 * VENDOR: HITACHI * PRODUCT: DF600F-CM * SERIAL: 0x0105 * LDEV: 0x00C6 * CTRL: 1 * PORT: B * CTRL ODD, LDEV EVEN, PRIO 0 * * To compile this source please execute # cc pp_hds_modular.c -o /sbin/mpath_prio_hds_modular * * Changes 2006-07-16: * - Changed to forward declaration of functions * - The switch-statement was changed to a logical expression * - unlinking of the devpath now also occurs at the end of * hds_modular_prio to avoid old /tmp/.pp_balance.%u.%u.devnode * entries in /tmp-Directory * - The for-statements for passing variables where changed to * snprintf-commands in verbose mode * Changes 2006-08-10: * - Back to the old switch statements because the regular expression does * 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 #include #include #include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #define INQ_REPLY_LEN 255 #define INQ_CMD_CODE 0x12 #define INQ_CMD_LEN 6 #define pp_hds_log(prio, fmt, args...) \ condlog(prio, "%s: hds prio: " fmt, dev, ##args) int hds_modular_prio (const char *dev, int fd, unsigned int timeout_ms) { int k; char vendor[9]; char product[32]; char serial[32]; char ldev[32]; char ctrl[32]; char port[32]; unsigned char inqCmdBlk[INQ_CMD_LEN] = { INQ_CMD_CODE, 0, 0, 0, INQ_REPLY_LEN, 0 }; unsigned char inqBuff[INQ_REPLY_LEN]; unsigned char *inqBuffp = inqBuff; unsigned char sense_buffer[32]; sg_io_hdr_t io_hdr; if ((ioctl (fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { pp_hds_log(0, "can't use SG ioctl interface"); return -1; } memset (&io_hdr, 0, sizeof (sg_io_hdr_t)); memset (inqBuff, 0, INQ_REPLY_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_buffer); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = INQ_REPLY_LEN; io_hdr.dxferp = inqBuff; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_buffer; io_hdr.timeout = timeout_ms; if (ioctl (fd, SG_IO, &io_hdr) < 0) { pp_hds_log(0, "SG_IO error"); return -1; } if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) { pp_hds_log(0, "SCSI error"); return -1; } snprintf (vendor, 9, "%.8s", inqBuffp + 8); snprintf (product, 17, "%.16s", inqBuffp + 16); snprintf (serial, 5, "%.4s", inqBuffp + 40); snprintf (ldev, 5, "%.4s", inqBuffp + 44); snprintf (ctrl, 2, "%.1s", inqBuffp + 49); snprintf (port, 2, "%.1s", inqBuffp + 50); pp_hds_log(4, "VENDOR: %s", vendor); pp_hds_log(4, "PRODUCT: %s", product); pp_hds_log(4, "SERIAL: 0x%s", serial); pp_hds_log(4, "LDEV: 0x%s", ldev); pp_hds_log(4, "CTRL: %s", ctrl); pp_hds_log(4, "PORT: %s", port); switch (ctrl[0]) { case '0': case '2': case '4': case '6': case '8': switch (ldev[3]) { case '0': case '2': case '4': case '6': case '8': case 'A': case 'C': case 'E': pp_hds_log(4, "CTRL EVEN, LDEV EVEN, PRIO 1"); return 1; break; case '1': case '3': case '5': case '7': case '9': case 'B': case 'D': case 'F': pp_hds_log(4, "CTRL EVEN, LDEV ODD, PRIO 0"); return 0; break; } break; case '1': case '3': case '5': case '7': case '9': switch (ldev[3]) { case '0': case '2': case '4': case '6': case '8': case 'A': case 'C': case 'E': pp_hds_log(4, "CTRL ODD, LDEV EVEN, PRIO 0"); return 0; break; case '1': case '3': case '5': case '7': case '9': case 'B': case 'D': case 'F': pp_hds_log(4, "CTRL ODD, LDEV ODD, PRIO 1"); return 1; break; } break; } return -1; } int getprio (struct path * pp, __attribute__((unused)) char *args) { return hds_modular_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/hp_sw.c000066400000000000000000000051161475246302400235000ustar00rootroot00000000000000/* * Path priority checker for HP active/standby controller * * Check the path state and sort them into groups. * There is actually a preferred path in the controller; * we should ask HP on how to retrieve that information. */ #include #include #include #include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #define TUR_CMD_LEN 6 #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define NOT_READY 0x02 #define UNIT_ATTENTION 0x06 #define HP_PATH_ACTIVE 0x04 #define HP_PATH_STANDBY 0x02 #define HP_PATH_FAILED 0x00 #define pp_hp_sw_log(prio, fmt, args...) \ condlog(prio, "%s: hp_sw prio: " fmt, dev, ##args) int hp_sw_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; unsigned char sb[128]; struct sg_io_hdr io_hdr; int ret = HP_PATH_FAILED; memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (turCmdBlk); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_NONE; io_hdr.cmdp = turCmdBlk; io_hdr.sbp = sb; io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; retry: if (ioctl(fd, SG_IO, &io_hdr) < 0) { pp_hp_sw_log(0, "sending tur command failed"); goto out; } io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) { /* Command completed normally, path is active */ ret = HP_PATH_ACTIVE; } if ((SCSI_CHECK_CONDITION == io_hdr.status) || (SCSI_COMMAND_TERMINATED == io_hdr.status) || (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { int sense_key, asc, asq; unsigned char * sense_buffer = io_hdr.sbp; if (sense_buffer[0] & 0x2) { sense_key = sense_buffer[1] & 0xf; asc = sense_buffer[2]; asq = sense_buffer[3]; } else { sense_key = sense_buffer[2] & 0xf; asc = sense_buffer[12]; asq = sense_buffer[13]; } if(RECOVERED_ERROR == sense_key) ret = HP_PATH_ACTIVE; if(NOT_READY == sense_key) { if (asc == 0x04 && asq == 0x02) { /* This is a standby path */ ret = HP_PATH_STANDBY; } } if(UNIT_ATTENTION == sense_key) { if (asc == 0x29) { /* Retry for device reset */ goto retry; } } } } out: return(ret); } int getprio (struct path *pp, __attribute__((unused)) char *args) { return hp_sw_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/iet.c000066400000000000000000000067761475246302400231560ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "prio.h" #include "debug.h" #include #include "structs.h" // // This prioritizer suits iSCSI needs, makes it possible to prefer one path. // // (It's a bit of a misnomer since supports the client side [eg. open-iscsi] // instead of just "iet".) // // Usage: // 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) // // 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) { int err; regex_t preg; err = regcomp(&preg, regex, REG_EXTENDED); if (err == 0) { int match; size_t nmatch = 0; regmatch_t *pmatch = NULL; nmatch = preg.re_nsub; pmatch = malloc(sizeof(*pmatch) * nmatch); if (pmatch) { match = regexec(&preg, string, nmatch, pmatch, 0); regfree(&preg); if (match == 0) { char *result = NULL; int start = pmatch[0].rm_so; int end = pmatch[0].rm_eo; size_t size = end - start; result = malloc (sizeof(*result) * (size + 1)); if (result) { strncpy(result, &string[start], size); result[size] = '\0'; free(pmatch); return result; } } free(pmatch); } } return NULL; } // // name: inet_prio // @param // @return prio int iet_prio(const char *dev, char * args) { char preferredip_buff[255] = ""; char *preferredip = &preferredip_buff[0]; // Phase 1 : checks. If anyone fails, return prio 0. // check if args exists if (!args) { dc_log(0, "need prio_args with preferredip set"); return 0; } // check if args format is OK if (sscanf(args, "preferredip=%s", preferredip) ==1) {} else { dc_log(0, "unexpected prio_args format"); return 0; } // check if ip is not too short if (strlen(preferredip) <= 7) { dc_log(0, "prio args: preferredip too short"); 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); // 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"); } } } // nothing found, low prio closedir(dir_p); return 10; } int getprio(struct path * pp, char * args) { return iet_prio(pp->dev, args); } multipath-tools-0.11.1/libmultipath/prioritizers/ontap.c000066400000000000000000000140461475246302400235030ustar00rootroot00000000000000/* * Copyright 2005 Network Appliance, Inc., All Rights Reserved * Author: David Wysochanski available at davidw@netapp.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License v2 for more details. */ #include #include #include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #include "unaligned.h" #include "strbuf.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 #define DEFAULT_PRIOVAL 10 #define RESULTS_MAX 256 #define pp_ontap_log(prio, fmt, args...) \ condlog(prio, "%s: ontap prio: " fmt, dev, ##args) static void dump_cdb(unsigned char *cdb, int size) { int i; STRBUF_ON_STACK(buf); for (i = 0; i < size; i++) { if (print_strbuf(&buf, "0x%02x ", cdb[i]) < 0) return; } condlog(0, "- SCSI CDB: %s", get_strbuf_str(&buf)); } static void process_sg_error(struct sg_io_hdr *io_hdr) { int i; STRBUF_ON_STACK(buf); condlog(0, "- masked_status=0x%02x, host_status=0x%02x, " "driver_status=0x%02x", io_hdr->masked_status, io_hdr->host_status, io_hdr->driver_status); if (io_hdr->sb_len_wr > 0) { for (i = 0; i < io_hdr->sb_len_wr; i++) { if (print_strbuf(&buf, "0x%02x ", io_hdr->sbp[i]) < 0) return; } condlog(0, "- SCSI sense data: %s", get_strbuf_str(&buf)); } } /* * Returns: * -1: error, errno set * 0: success */ static int send_gva(const char *dev, int fd, unsigned char pg, unsigned char *results, int *results_size, unsigned int timeout_ms) { unsigned char sb[128]; unsigned char cdb[10] = {0xc0, 0, 0x1, 0xa, 0x98, 0xa, pg, sizeof(sb), 0, 0}; struct sg_io_hdr io_hdr; int ret = -1; memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(results, 0, *results_size); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (cdb); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = *results_size; io_hdr.dxferp = results; io_hdr.cmdp = cdb; io_hdr.sbp = sb; io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) { pp_ontap_log(0, "SG_IO ioctl failed, errno=%d", errno); dump_cdb(cdb, sizeof(cdb)); goto out; } if (io_hdr.info & SG_INFO_OK_MASK) { pp_ontap_log(0, "SCSI error"); dump_cdb(cdb, sizeof(cdb)); process_sg_error(&io_hdr); goto out; } if (results[4] != 0x0a || results[5] != 0x98 || results[6] != 0x0a ||results[7] != 0x01) { dump_cdb(cdb, sizeof(cdb)); pp_ontap_log(0, "GVA return wrong format "); pp_ontap_log(0, "results[4-7] = 0x%02x 0x%02x 0x%02x 0x%02x", results[4], results[5], results[6], results[7]); goto out; } ret = 0; out: return(ret); } /* * Returns: * -1: Unable to obtain proxy info * 0: Device _not_ proxy path * 1: Device _is_ proxy path */ static int get_proxy(const char *dev, int fd, unsigned int timeout_ms) { unsigned char results[256]; unsigned char sb[128]; unsigned char cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 1, 0xc1, 0, sizeof(sb), 0}; struct sg_io_hdr io_hdr; int ret = -1; memset(&results, 0, sizeof (results)); memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (cdb); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = sizeof (results); io_hdr.dxferp = results; io_hdr.cmdp = cdb; io_hdr.sbp = sb; io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) { pp_ontap_log(0, "ioctl sending inquiry command failed, " "errno=%d", errno); dump_cdb(cdb, sizeof(cdb)); goto out; } if (io_hdr.info & SG_INFO_OK_MASK) { pp_ontap_log(0, "SCSI error"); dump_cdb(cdb, sizeof(cdb)); process_sg_error(&io_hdr); goto out; } if (results[1] != 0xc1 || results[8] != 0x0a || results[9] != 0x98 || results[10] != 0x0a || results[11] != 0x0 || results[12] != 0xc1 || results[13] != 0x0) { pp_ontap_log(0,"proxy info page in unknown format - "); pp_ontap_log(0,"results[8-13]=0x%02x 0x%02x 0x%02x 0x%02x " "0x%02x 0x%02x", results[8], results[9], results[10], results[11], results[12], results[13]); dump_cdb(cdb, sizeof(cdb)); goto out; } ret = (results[19] & 0x02) >> 1; out: return(ret); } /* * Returns priority of device based on device info. * * 4: FCP non-proxy, FCP proxy unknown, or unable to determine protocol * 3: iSCSI HBA * 2: iSCSI software * 1: FCP proxy */ static int ontap_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char results[RESULTS_MAX]; int results_size=RESULTS_MAX; int rc; int is_proxy; int is_iscsi_software; int is_iscsi_hardware; int tot_len; is_iscsi_software = is_iscsi_hardware = is_proxy = 0; memset(&results, 0, sizeof (results)); rc = send_gva(dev, fd, 0x41, results, &results_size, timeout_ms); if (rc >= 0) { tot_len = get_unaligned_be32(&results[0]); if (tot_len <= 8) { goto try_fcp_proxy; } if (results[8] != 0x41) { pp_ontap_log(0, "GVA page 0x41 error - " "results[8] = 0x%x", results[8]); goto try_fcp_proxy; } if ((strncmp((char *)&results[12], "ism_sw", 6) == 0) || (strncmp((char *)&results[12], "iswt", 4) == 0)) { is_iscsi_software = 1; goto prio_select; } else if (strncmp((char *)&results[12], "ism_sn", 6) == 0) { is_iscsi_hardware = 1; goto prio_select; } } else { return 0; } try_fcp_proxy: rc = get_proxy(dev, fd, timeout_ms); if (rc >= 0) { is_proxy = rc; } prio_select: if (is_iscsi_hardware) { return 3; } else if (is_iscsi_software) { return 2; } else { if (is_proxy) { return 1; } else { /* Either non-proxy, or couldn't get proxy info */ return 4; } } } int getprio (struct path *pp, __attribute__((unused)) char *args) { return ontap_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/path_latency.c000066400000000000000000000162341475246302400250360ustar00rootroot00000000000000/* * (C) Copyright HUAWEI Technology Corp. 2017, All Rights Reserved. * * path_latency.c * * Prioritizer for device mapper multipath, where the corresponding priority * values of specific paths are provided by a latency algorithm. And the * latency algorithm is dependent on arguments("io_num" and "base_num"). * * The principle of the algorithm as follows: * 1. By sending a certain number "io_num" of read IOs to the current path * continuously, the IOs' average latency can be calculated. * 2. Max value and min value of average latency are constant. According to * the average latency of each path and the "base_num" of logarithmic * scale, the priority "rc" of each path can be provided. * * Author(s): Yang Feng * Revised: Guan Junxiong * * This file is released under the GPL version 2, or any later version. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "debug.h" #include "prio.h" #include "structs.h" #include "util.h" #include "time-util.h" #define pp_pl_log(prio, fmt, args...) condlog(prio, "path_latency prio: " fmt, ##args) #define MAX_IO_NUM 200 #define MIN_IO_NUM 20 #define DEF_IO_NUM 100 #define MAX_BASE_NUM 10 #define MIN_BASE_NUM 1.1 // This is 10**(1/4). 4 prio steps correspond to a factor of 10. #define DEF_BASE_NUM 1.77827941004 #define MAX_AVG_LATENCY 100000000. /* Unit: us */ #define MIN_AVG_LATENCY 1. /* Unit: us */ #define DEFAULT_PRIORITY 0 #define USEC_PER_SEC 1000000LL #define NSEC_PER_USEC 1000LL #define DEF_BLK_SIZE 4096 static int prepare_directio_read(int fd, int *blksz, char **pbuf, int *restore_flags) { unsigned long pgsize = getpagesize(); long flags; if (ioctl(fd, BLKBSZGET, blksz) < 0) { pp_pl_log(3,"cannot get blocksize, set default"); *blksz = DEF_BLK_SIZE; } if (posix_memalign((void **)pbuf, pgsize, *blksz)) return -1; flags = fcntl(fd, F_GETFL); if (flags < 0) goto free_out; if (!(flags & O_DIRECT)) { flags |= O_DIRECT; if (fcntl(fd, F_SETFL, flags) < 0) goto free_out; *restore_flags = 1; } return 0; free_out: free(*pbuf); return -1; } static void cleanup_directio_read(int fd, char *buf, int restore_flags) { long flags; free(buf); if (!restore_flags) return; if ((flags = fcntl(fd, F_GETFL)) >= 0) { int ret __attribute__ ((unused)); flags &= ~O_DIRECT; /* No point in checking for errors */ ret = fcntl(fd, F_SETFL, flags); } } static int do_directio_read(int fd, unsigned int timeout_ms, char *buf, int sz) { fd_set read_fds; struct timeval tm = { .tv_sec = timeout_ms / 1000}; int ret; int num_read; if (lseek(fd, 0, SEEK_SET) == -1) return -1; FD_ZERO(&read_fds); FD_SET(fd, &read_fds); ret = select(fd+1, &read_fds, NULL, NULL, &tm); if (ret <= 0) return -1; num_read = read(fd, buf, sz); if (num_read != sz) return -1; return 0; } int check_args_valid(int io_num, double base_num) { if ((io_num < MIN_IO_NUM) || (io_num > MAX_IO_NUM)) { pp_pl_log(0, "args io_num is outside the valid range"); return 0; } if ((base_num < MIN_BASE_NUM) || (base_num > MAX_BASE_NUM)) { pp_pl_log(0, "args base_num is outside the valid range"); return 0; } return 1; } /* * In multipath.conf, args form: io_num=n base_num=m. For example, args are * "io_num=20 base_num=10", this function can get io_num value 20 and * base_num value 10. */ static int get_ionum_and_basenum(char *args, int *ionum, double *basenum) { char split_char[] = " \t"; char *arg, *temp; char *str, *str_inval; int i; int flag_io = 0, flag_base = 0; if ((args == NULL) || (ionum == NULL) || (basenum == NULL)) { pp_pl_log(0, "args string is NULL"); return 0; } arg = temp = strdup(args); if (!arg) return 0; for (i = 0; i < 2; i++) { str = get_next_string(&temp, split_char); if (!str) goto out; if (!strncmp(str, "io_num=", 7) && strlen(str) > 7) { *ionum = (int)strtoul(str + 7, &str_inval, 10); if (str == str_inval) goto out; flag_io = 1; } else if (!strncmp(str, "base_num=", 9) && strlen(str) > 9) { *basenum = strtod(str + 9, &str_inval); if (str == str_inval) goto out; flag_base = 1; } } if (!flag_io || !flag_base) goto out; if (check_args_valid(*ionum, *basenum) == 0) goto out; free(arg); return 1; out: free(arg); return 0; } /* * Do not scale the priority in a certain range such as [0, 1024] * because scaling will eliminate the effect of base_num. */ int calcPrio(double lg_avglatency, double lg_maxavglatency, double lg_minavglatency) { if (lg_avglatency <= lg_minavglatency) return lg_maxavglatency - lg_minavglatency; if (lg_avglatency >= lg_maxavglatency) return 0; return lg_maxavglatency - lg_avglatency; } int getprio(struct path *pp, char *args) { int rc, temp; int io_num = 0; double base_num = 0; double lg_avglatency, lg_maxavglatency, lg_minavglatency; double standard_deviation; double lg_toldelay = 0; int blksize; char *buf; int restore_flags = 0; double lg_base; double sum_squares = 0; if (pp->fd < 0) return -1; if (get_ionum_and_basenum(args, &io_num, &base_num) == 0) { io_num = DEF_IO_NUM; base_num = DEF_BASE_NUM; pp_pl_log(0, "%s: fails to get path_latency args, set default:" "io_num=%d base_num=%.3lf", pp->dev, io_num, base_num); } lg_base = log(base_num); lg_maxavglatency = log(MAX_AVG_LATENCY) / lg_base; lg_minavglatency = log(MIN_AVG_LATENCY) / lg_base; if (prepare_directio_read(pp->fd, &blksize, &buf, &restore_flags) < 0) return PRIO_UNDEF; temp = io_num; while (temp-- > 0) { struct timespec tv_before, tv_after, tv_diff; double diff, reldiff; (void)clock_gettime(CLOCK_MONOTONIC, &tv_before); if (do_directio_read(pp->fd, get_prio_timeout_ms(pp), buf, blksize)) { pp_pl_log(0, "%s: path down", pp->dev); cleanup_directio_read(pp->fd, buf, restore_flags); return -1; } (void)clock_gettime(CLOCK_MONOTONIC, &tv_after); timespecsub(&tv_after, &tv_before, &tv_diff); diff = tv_diff.tv_sec * 1000 * 1000 + tv_diff.tv_nsec / 1000; if (diff == 0) /* * Avoid taking log(0). * This unlikely case is treated as minimum - * the sums don't increase */ continue; /* we scale by lg_base here */ reldiff = log(diff) / lg_base; /* * We assume that the latency complies with Log-normal * distribution. The logarithm of latency is in normal * distribution. */ lg_toldelay += reldiff; sum_squares += reldiff * reldiff; } cleanup_directio_read(pp->fd, buf, restore_flags); lg_avglatency = lg_toldelay / (long long)io_num; if (lg_avglatency > lg_maxavglatency) { pp_pl_log(2, "%s: average latency (%lld us) is outside the threshold (%lld us)", pp->dev, (long long)pow(base_num, lg_avglatency), (long long)MAX_AVG_LATENCY); return DEFAULT_PRIORITY; } standard_deviation = sqrt((sum_squares - lg_toldelay * lg_avglatency) / (io_num - 1)); rc = calcPrio(lg_avglatency, lg_maxavglatency, lg_minavglatency); pp_pl_log(3, "%s: latency avg=%.2e uncertainty=%.1f prio=%d\n", pp->dev, exp(lg_avglatency * lg_base), exp(standard_deviation * lg_base), rc); return rc; } multipath-tools-0.11.1/libmultipath/prioritizers/random.c000066400000000000000000000005041475246302400236340ustar00rootroot00000000000000#include #include #include #include #include "prio.h" int getprio(__attribute__((unused)) struct path *pp, __attribute__((unused)) char *args) { struct timeval tv; gettimeofday(&tv, NULL); srand((unsigned int)tv.tv_usec); return 1+(int) (10.0*rand()/(RAND_MAX+1.0)); } multipath-tools-0.11.1/libmultipath/prioritizers/rdac.c000066400000000000000000000044641475246302400232760ustar00rootroot00000000000000#include #include #include #include "sg_include.h" #include "debug.h" #include "prio.h" #include "structs.h" #define INQUIRY_CMD 0x12 #define INQUIRY_CMDLEN 6 #define pp_rdac_log(prio, msg) condlog(prio, "%s: rdac prio: " msg, dev) int rdac_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char sense_buffer[128]; unsigned char sb[128]; unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {INQUIRY_CMD, 1, 0xC9, 0, sizeof(sense_buffer), 0}; struct sg_io_hdr io_hdr; int ret = 0; memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_buffer, 0, 128); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sb); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = sizeof (sense_buffer); io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; if (ioctl(fd, SG_IO, &io_hdr) < 0) { pp_rdac_log(0, "sending inquiry command failed"); goto out; } if (io_hdr.info & SG_INFO_OK_MASK) { pp_rdac_log(0, "inquiry command indicates error"); goto out; } if (/* Verify the code page - right page & page identifier */ sense_buffer[1] != 0xc9 || sense_buffer[3] != 0x2c || sense_buffer[4] != 'v' || sense_buffer[5] != 'a' || sense_buffer[6] != 'c' ) { pp_rdac_log(0, "volume access control page in unknown format"); goto out; } if ( /* Current Volume Path Bit */ ( sense_buffer[8] & 0x01) == 0x01 ) { /* * This volume was owned by the controller receiving * the inquiry command. */ ret |= 0x02; } /* Volume Preferred Path Priority */ switch ( sense_buffer[9] & 0x0F ) { case 0x01: /* * Access to this volume is most preferred through * this path and other paths with this value. */ ret |= 0x04; break; case 0x02: /* * Access to this volume through this path is to be used * as a secondary path. Typically this path would be used * for fail-over situations. */ ret |= 0x01; break; default: /* Reserved values */ break; } /* For ioship mode set the bit 3 (00001000) */ if ((sense_buffer[8] >> 5) & 0x01) ret |= 0x08; out: return(ret); } int getprio (struct path *pp, __attribute__((unused)) char *args) { return rdac_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } multipath-tools-0.11.1/libmultipath/prioritizers/sysfs.c000066400000000000000000000022421475246302400235240ustar00rootroot00000000000000/* * sysfs.c * * Copyright(c) 2016 Hannes Reinecke, SUSE Linux GmbH */ #include #include "structs.h" #include "discovery.h" #include "prio.h" static const struct { unsigned char value; char *name; } sysfs_access_state_map[] = { { 50, "active/optimized" }, { 10, "active/non-optimized" }, { 5, "lba-dependent" }, { 1, "standby" }, }; int get_exclusive_pref_arg(char *args) { char *ptr; if (args == NULL) return 0; ptr = strstr(args, "exclusive_pref_bit"); if (!ptr) return 0; if (ptr[18] != '\0' && ptr[18] != ' ' && ptr[18] != '\t') return 0; if (ptr != args && ptr[-1] != ' ' && ptr[-1] != '\t') return 0; return 1; } int getprio (struct path * pp, char *args) { int prio = 0, rc, i; char buff[512]; int exclusive_pref; exclusive_pref = get_exclusive_pref_arg(args); rc = sysfs_get_asymmetric_access_state(pp, buff, 512); if (rc < 0) return PRIO_UNDEF; prio = 0; for (i = 0; i < 4; i++) { if (!strncmp(buff, sysfs_access_state_map[i].name, strlen(sysfs_access_state_map[i].name))) { prio = sysfs_access_state_map[i].value; break; } } if (rc > 0 && (prio != 50 || exclusive_pref)) prio += 80; return prio; } multipath-tools-0.11.1/libmultipath/prioritizers/weightedpath.c000066400000000000000000000061511475246302400250350ustar00rootroot00000000000000/* * * (C) Copyright 2008 Hewlett-Packard Development Company, L.P * * This file is released under the GPL */ /* * Prioritizer for device mapper multipath, where specific paths and the * corresponding priority values are provided as arguments. * * This prioritizer assigns the priority value provided in the configuration * file based on the comparison made between the specified paths and the path * instance for which this is called. * Paths can be specified as a regular expression of devname of the path or * as hbtl information of the path. * * Examples: * prio "weightedpath hbtl 1:.:.:. 2 4:.:.:. 4" * prio "weightedpath devname sda 10 sde 20" * * Returns zero as the default priority. */ #include #include #include "prio.h" #include "weightedpath.h" #include "config.h" #include "structs.h" #include "debug.h" #include #include "structs_vec.h" #include "print.h" #include "util.h" #include "strbuf.h" static int build_serial_path(struct path *pp, struct strbuf *buf) { int rc = snprint_path_serial(buf, pp); return rc < 0 ? rc : 0; } static int build_wwn_path(struct path *pp, struct strbuf *buf) { int rc; if ((rc = snprint_host_wwnn(buf, pp)) < 0 || (rc = fill_strbuf(buf, ':', 1)) < 0 || (rc = snprint_host_wwpn(buf, pp)) < 0 || (rc = fill_strbuf(buf, ':', 1)) < 0 || (rc = snprint_tgt_wwnn(buf, pp) < 0) || (rc = fill_strbuf(buf, ':', 1) < 0) || (rc = snprint_tgt_wwpn(buf, pp) < 0)) return rc; return 0; } /* main priority routine */ int prio_path_weight(struct path *pp, char *prio_args) { STRBUF_ON_STACK(path); char *arg __attribute__((cleanup(cleanup_charp))) = NULL; char *temp, *regex, *prio; char split_char[] = " \t"; int priority = DEFAULT_PRIORITY, path_found = 0; regex_t pathe; /* Return default priority if there is no argument */ if (!prio_args) return priority; arg = strdup(prio_args); temp = arg; regex = get_next_string(&temp, split_char); /* Return default priority if the argument is not parseable */ if (!regex) { return priority; } if (!strcmp(regex, HBTL)) { if (print_strbuf(&path, "%d:%d:%d:%" PRIu64, pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun) < 0) return priority; } else if (!strcmp(regex, DEV_NAME)) { if (append_strbuf_str(&path, pp->dev) < 0) return priority; } else if (!strcmp(regex, SERIAL)) { if (build_serial_path(pp, &path) != 0) return priority; } else if (!strcmp(regex, WWN)) { if (build_wwn_path(pp, &path) != 0) return priority; } else { condlog(0, "%s: %s - Invalid arguments", pp->dev, pp->prio.name); return priority; } while (!path_found) { if (!temp) break; if (!(regex = get_next_string(&temp, split_char))) break; if (!(prio = get_next_string(&temp, split_char))) break; if (!regcomp(&pathe, regex, REG_EXTENDED|REG_NOSUB)) { if (!regexec(&pathe, get_strbuf_str(&path), 0, NULL, 0)) { path_found = 1; priority = atoi(prio); } regfree(&pathe); } } return priority; } int getprio(struct path *pp, char *args) { return prio_path_weight(pp, args); } multipath-tools-0.11.1/libmultipath/prioritizers/weightedpath.h000066400000000000000000000004401475246302400250350ustar00rootroot00000000000000#ifndef WEIGHTEDPATH_H_INCLUDED #define WEIGHTEDPATH_H_INCLUDED #define PRIO_WEIGHTED_PATH "weightedpath" #define HBTL "hbtl" #define DEV_NAME "devname" #define SERIAL "serial" #define WWN "wwn" #define DEFAULT_PRIORITY 0 int prio_path_weight(struct path *pp, char *prio_args); #endif multipath-tools-0.11.1/libmultipath/prkey.c000066400000000000000000000122631475246302400207460ustar00rootroot00000000000000#include "structs.h" #include "file.h" #include "debug.h" #include "config.h" #include "util.h" #include "propsel.h" #include "strbuf.h" #include "prkey.h" #include #include #include #include #include #include #include /* MPATH_F_APTPL_MASK is publicly defined in mpath_persist.h */ #include <../libmpathpersist/mpath_persist.h> #define PRKEY_READ 0 #define PRKEY_WRITE 1 int print_reservation_key(struct strbuf *buff, struct be64 key, uint8_t flags, int source) { char *flagstr = ""; if (source == PRKEY_SOURCE_NONE) return 0; if (source == PRKEY_SOURCE_FILE) return append_strbuf_quoted(buff, "file"); if (flags & MPATH_F_APTPL_MASK) flagstr = ":aptpl"; return print_strbuf(buff, "0x%" PRIx64 "%s", get_be64(key), flagstr); } static int parse_prkey(const char *ptr, uint64_t *prkey) { if (!ptr) return 1; if (*ptr == '0') ptr++; if (*ptr == 'x' || *ptr == 'X') ptr++; if (*ptr == '\0' || strlen(ptr) > 16) return 1; if (strlen(ptr) != strspn(ptr, "0123456789aAbBcCdDeEfF")) return 1; if (sscanf(ptr, "%" SCNx64 "", prkey) != 1) return 1; return 0; } int parse_prkey_flags(const char *ptr, uint64_t *prkey, uint8_t *flags) { char *flagstr; flagstr = strchr(ptr, ':'); *flags = 0; if (flagstr) { *flagstr++ = '\0'; if (strlen(flagstr) == 5 && strcmp(flagstr, "aptpl") == 0) *flags = MPATH_F_APTPL_MASK; } return parse_prkey(ptr, prkey); } static int do_prkey(int fd, char *wwid, char *keystr, int cmd) { char buf[4097]; char *ptr; off_t start = 0; int bytes; while (1) { if (lseek(fd, start, SEEK_SET) < 0) { condlog(0, "prkey file read lseek failed : %s", strerror(errno)); return 1; } bytes = read(fd, buf, 4096); if (bytes < 0) { if (errno == EINTR || errno == EAGAIN) continue; condlog(0, "failed to read from prkey file : %s", strerror(errno)); return 1; } if (!bytes) { ptr = NULL; break; } buf[bytes] = '\0'; ptr = strstr(buf, wwid); while (ptr) { if (ptr == buf || *(ptr - 1) != ' ' || *(ptr + strlen(wwid)) != '\n') ptr = strstr(ptr + strlen(wwid), wwid); else break; } if (ptr) { condlog(3, "found prkey for '%s'", wwid); ptr[strlen(wwid)] = '\0'; if (ptr - PRKEY_SIZE < buf || (ptr - PRKEY_SIZE != buf && *(ptr - PRKEY_SIZE - 1) != '\n')) { condlog(0, "malformed prkey file line for wwid: '%s'", ptr); return 1; } ptr = ptr - PRKEY_SIZE; break; } ptr = strrchr(buf, '\n'); if (ptr == NULL) { condlog(4, "couldn't file newline, assuming end of file"); break; } start = start + (ptr - buf) + 1; } if (cmd == PRKEY_READ) { if (!ptr || *ptr == '#') return 1; memcpy(keystr, ptr, PRKEY_SIZE - 1); keystr[PRKEY_SIZE - 1] = '\0'; return 0; } if (!ptr && !keystr) return 0; if (ptr) { if (lseek(fd, start + (ptr - buf), SEEK_SET) < 0) { condlog(0, "prkey write lseek failed : %s", strerror(errno)); return 1; } } if (!keystr) { if (safe_write(fd, "#", 1) < 0) { condlog(0, "failed to write to prkey file : %s", strerror(errno)); return 1; } return 0; } if (!ptr) { if (lseek(fd, 0, SEEK_END) < 0) { condlog(0, "prkey write lseek failed : %s", strerror(errno)); return 1; } } bytes = sprintf(buf, "%s %s\n", keystr, wwid); if (safe_write(fd, buf, bytes) < 0) { condlog(0, "failed to write to prkey file: %s", strerror(errno)); return 1; } return 0; } int get_prkey(struct multipath *mpp, uint64_t *prkey, uint8_t *sa_flags) { int fd; int unused; int ret = 1; char keystr[PRKEY_SIZE]; if (!strlen(mpp->wwid)) goto out; fd = open_file(DEFAULT_PRKEYS_FILE, &unused, PRKEYS_FILE_HEADER); if (fd < 0) goto out; ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_READ); if (ret) goto out_file; *sa_flags = 0; if (strchr(keystr, 'X')) *sa_flags = MPATH_F_APTPL_MASK; ret = !!parse_prkey(keystr, prkey); out_file: close(fd); out: return ret; } int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, uint8_t sa_flags) { int fd; int can_write = 1; int ret = 1; char keystr[PRKEY_SIZE]; if (!strlen(mpp->wwid)) goto out; if (sa_flags & ~MPATH_F_APTPL_MASK) { condlog(0, "unsupported pr flags, 0x%x", sa_flags & ~MPATH_F_APTPL_MASK); sa_flags &= MPATH_F_APTPL_MASK; } fd = open_file(DEFAULT_PRKEYS_FILE, &can_write, PRKEYS_FILE_HEADER); if (fd < 0) goto out; if (!can_write) { condlog(0, "cannot set prkey, prkeys file is read-only"); goto out_file; } if (prkey) { /* using the capitalization of the 'x' is a hack, but * it's unlikely that mpath_persist will support more options * since sg_persist doesn't, and this lets us keep the * same file format as before instead of needing to change * the format of the prkeys file */ if (sa_flags) snprintf(keystr, PRKEY_SIZE, "0X%016" PRIx64, prkey); else snprintf(keystr, PRKEY_SIZE, "0x%016" PRIx64, prkey); keystr[PRKEY_SIZE - 1] = '\0'; ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_WRITE); } else ret = do_prkey(fd, mpp->wwid, NULL, PRKEY_WRITE); if (ret == 0) select_reservation_key(conf, mpp); if (get_be64(mpp->reservation_key) != prkey) ret = 1; out_file: close(fd); out: return ret; } multipath-tools-0.11.1/libmultipath/prkey.h000066400000000000000000000013701475246302400207500ustar00rootroot00000000000000#ifndef PRKEY_H_INCLUDED #define PRKEY_H_INCLUDED #include "structs.h" #include #define PRKEYS_FILE_HEADER \ "# Multipath persistent reservation keys, Version : 1.0\n" \ "# NOTE: this file is automatically maintained by the multipathd program.\n" \ "# You should not need to edit this file in normal circumstances.\n" \ "#\n" \ "# Format:\n" \ "# prkey wwid\n" \ "#\n" int print_reservation_key(struct strbuf *buff, struct be64 key, uint8_t flags, int source); int parse_prkey_flags(const char *ptr, uint64_t *prkey, uint8_t *flags); int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, uint8_t sa_flags); int get_prkey(struct multipath *mpp, uint64_t *prkey, uint8_t *sa_flags); #endif /* PRKEY_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/propsel.c000066400000000000000000001202071475246302400212760ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Kiyoshi Ueda, NEC */ #include #include "autoconfig.h" #include "nvme-lib.h" #include "checkers.h" #include "vector.h" #include "structs.h" #include "config.h" #include "debug.h" #include "pgpolicies.h" #include "alias.h" #include "defaults.h" #include "devmapper.h" #include "prio.h" #include "discovery.h" #include "dict.h" #include "util.h" #include "sysfs.h" #include "prioritizers/alua_rtpg.h" #include "prkey.h" #include "propsel.h" #include "strbuf.h" #include #include #include pgpolicyfn *pgpolicies[] = { NULL, one_path_per_group, one_group, group_by_serial, group_by_prio, group_by_node_name, group_by_tpg, }; #define do_set(var, src, dest, msg) \ do { \ if (src && src->var) { \ dest = src->var; \ origin = msg; \ goto out; \ } \ } while(0) #define do_set_from_vec__(type, var, src, dest) \ ({ \ type *_p; \ bool _found = false; \ int i; \ \ vector_foreach_slot(src, _p, i) { \ if (_p->var) { \ dest = _p->var; \ _found = true; \ break; \ } \ } \ _found; \ }) #define do_set_from_hwe__(var, src, dest) \ do_set_from_vec__(struct hwentry, var, (src)->hwe, dest) #define do_set_from_hwe(var, src, dest, msg) \ if (src->hwe && do_set_from_hwe__(var, src, dest)) { \ origin = msg; \ goto out; \ } static const char default_origin[] = "(setting: multipath internal)"; static const char hwe_origin[] = "(setting: storage device configuration)"; static const char multipaths_origin[] = "(setting: multipath.conf multipaths section)"; static const char conf_origin[] = "(setting: multipath.conf defaults/devices section)"; static const char overrides_origin[] = "(setting: multipath.conf overrides section)"; static const char overrides_pce_origin[] = "(setting: multipath.conf overrides protocol section)"; static const char cmdline_origin[] = "(setting: multipath command line [-p] flag)"; static const char autodetect_origin[] = "(setting: storage device autodetected)"; static const char fpin_marginal_path_origin[] = "(setting: overridden by marginal_path_fpin)"; static const char marginal_path_origin[] = "(setting: implied by marginal_path check)"; static const char delay_watch_origin[] = "(setting: implied by delay_watch_checks)"; static const char delay_wait_origin[] = "(setting: implied by delay_wait_checks)"; #define do_default(dest, value) \ do { \ dest = value; \ origin = default_origin; \ } while(0) #define mp_set_mpe(var) \ do_set(var, mp->mpe, mp->var, multipaths_origin) #define mp_set_hwe(var) \ do_set_from_hwe(var, mp, mp->var, hwe_origin) #define mp_set_ovr(var) \ do_set(var, conf->overrides, mp->var, overrides_origin) #define mp_set_conf(var) \ do_set(var, conf, mp->var, conf_origin) #define mp_set_default(var, value) \ do_default(mp->var, value) #define pp_set_mpe(var) \ do_set(var, mpe, pp->var, multipaths_origin) #define pp_set_hwe(var) \ do_set_from_hwe(var, pp, pp->var, hwe_origin) #define pp_set_conf(var) \ do_set(var, conf, pp->var, conf_origin) #define pp_set_ovr(var) \ do_set(var, conf->overrides, pp->var, overrides_origin) #define pp_set_default(var, value) \ do_default(pp->var, value) #define do_attr_set(var, src, shift, msg) \ do { \ if (src && (src->attribute_flags & (1 << shift))) { \ mp->attribute_flags |= (1 << shift); \ mp->var = src->var; \ origin = msg; \ goto out; \ } \ } while(0) #define set_attr_mpe(var, shift) \ do_attr_set(var, mp->mpe, shift, "(setting: multipath.conf multipaths section)") #define set_attr_conf(var, shift) \ do_attr_set(var, conf, shift, "(setting: multipath.conf defaults/devices section)") #define do_prkey_set(src, msg) \ do { \ if (src && src->prkey_source != PRKEY_SOURCE_NONE) { \ mp->prkey_source = src->prkey_source; \ mp->reservation_key = src->reservation_key; \ mp->sa_flags = src->sa_flags; \ origin = msg; \ goto out; \ } \ } while (0) #define pp_set_ovr_pce(var) \ do { \ struct pcentry *_pce; \ int _i; \ \ if (conf->overrides) { \ vector_foreach_slot(conf->overrides->pctable, _pce, _i) { \ if (_pce->type == (int)bus_protocol_id(pp) && _pce->var) { \ pp->var = _pce->var; \ origin = overrides_pce_origin; \ goto out; \ } \ } \ if (conf->overrides->var) { \ pp->var = conf->overrides->var; \ origin = overrides_origin; \ goto out; \ } \ } \ } while (0) int select_mode(struct config *conf, struct multipath *mp) { const char *origin; set_attr_mpe(mode, ATTR_MODE); set_attr_conf(mode, ATTR_MODE); mp->attribute_flags &= ~(1 << ATTR_MODE); return 0; out: condlog(3, "%s: mode = 0%o %s", mp->alias, mp->mode, origin); return 0; } int select_uid(struct config *conf, struct multipath *mp) { const char *origin; set_attr_mpe(uid, ATTR_UID); set_attr_conf(uid, ATTR_UID); mp->attribute_flags &= ~(1 << ATTR_UID); return 0; out: condlog(3, "%s: uid = 0%o %s", mp->alias, mp->uid, origin); return 0; } int select_gid(struct config *conf, struct multipath *mp) { const char *origin; set_attr_mpe(gid, ATTR_GID); set_attr_conf(gid, ATTR_GID); mp->attribute_flags &= ~(1 << ATTR_GID); return 0; out: condlog(3, "%s: gid = 0%o %s", mp->alias, mp->gid, origin); 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; STRBUF_ON_STACK(buff); mp_set_mpe(pgfailback); mp_set_ovr(pgfailback); mp_set_hwe(pgfailback); mp_set_conf(pgfailback); mp_set_default(pgfailback, DEFAULT_FAILBACK); out: print_pgfailback(&buff, mp->pgfailback); condlog(3, "%s: failback = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } static bool verify_alua_prio(struct multipath *mp) { int i; struct path *pp; bool assume_alua = false; vector_foreach_slot(mp->paths, pp, i) { const char *name = prio_name(&pp->prio); if (!prio_selected(&pp->prio)) continue; if (strncmp(name, PRIO_ALUA, PRIO_NAME_LEN) && strncmp(name, PRIO_SYSFS, PRIO_NAME_LEN)) return false; assume_alua = true; } return assume_alua; } int select_detect_pgpolicy(struct config *conf, struct multipath *mp) { const char *origin; mp_set_ovr(detect_pgpolicy); mp_set_hwe(detect_pgpolicy); mp_set_conf(detect_pgpolicy); mp_set_default(detect_pgpolicy, DEFAULT_DETECT_PGPOLICY); out: condlog(3, "%s: detect_pgpolicy = %s %s", mp->alias, (mp->detect_pgpolicy == DETECT_PGPOLICY_ON)? "yes" : "no", origin); return 0; } int select_detect_pgpolicy_use_tpg(struct config *conf, struct multipath *mp) { const char *origin; mp_set_ovr(detect_pgpolicy_use_tpg); mp_set_hwe(detect_pgpolicy_use_tpg); mp_set_conf(detect_pgpolicy_use_tpg); mp_set_default(detect_pgpolicy_use_tpg, DEFAULT_DETECT_PGPOLICY_USE_TPG); out: condlog(3, "%s: detect_pgpolicy_use_tpg = %s %s", mp->alias, (mp->detect_pgpolicy_use_tpg == DETECT_PGPOLICY_USE_TPG_ON)? "yes" : "no", origin); return 0; } int select_pgpolicy(struct config *conf, struct multipath * mp) { const char *origin; int log_prio = 3; if (conf->pgpolicy_flag > 0) { mp->pgpolicy = conf->pgpolicy_flag; origin = cmdline_origin; goto out; } if (mp->detect_pgpolicy == DETECT_PGPOLICY_ON && verify_alua_prio(mp)) { if (mp->detect_pgpolicy_use_tpg == DETECT_PGPOLICY_USE_TPG_ON) mp->pgpolicy = GROUP_BY_TPG; else mp->pgpolicy = GROUP_BY_PRIO; origin = autodetect_origin; goto out; } mp_set_mpe(pgpolicy); mp_set_ovr(pgpolicy); mp_set_hwe(pgpolicy); mp_set_conf(pgpolicy); mp_set_default(pgpolicy, DEFAULT_PGPOLICY); out: if (mp->pgpolicy == GROUP_BY_TPG && origin != autodetect_origin && !verify_alua_prio(mp)) { mp->pgpolicy = DEFAULT_PGPOLICY; origin = "(setting: emergency fallback - not all paths use alua prio)"; log_prio = 1; } mp->pgpolicyfn = pgpolicies[mp->pgpolicy]; condlog(log_prio, "%s: path_grouping_policy = %s %s", mp->alias, get_pgpolicy_name(mp->pgpolicy), origin); return 0; } int select_selector(struct config *conf, struct multipath * mp) { const char *origin; mp_set_mpe(selector); mp_set_ovr(selector); mp_set_hwe(selector); mp_set_conf(selector); mp_set_default(selector, DEFAULT_SELECTOR); out: mp->selector = strdup(mp->selector); condlog(3, "%s: path_selector = \"%s\" %s", mp->alias, mp->selector, origin); return 0; } static void select_alias_prefix (struct config *conf, struct multipath * mp) { const char *origin; mp_set_ovr(alias_prefix); mp_set_hwe(alias_prefix); mp_set_conf(alias_prefix); mp_set_default(alias_prefix, DEFAULT_ALIAS_PREFIX); out: condlog(3, "%s: alias_prefix = %s %s", mp->wwid, mp->alias_prefix, origin); } static int want_user_friendly_names(struct config *conf, struct multipath * mp) { const char *origin; int user_friendly_names; do_set(user_friendly_names, mp->mpe, user_friendly_names, multipaths_origin); do_set(user_friendly_names, conf->overrides, user_friendly_names, overrides_origin); do_set_from_hwe(user_friendly_names, mp, user_friendly_names, hwe_origin); do_set(user_friendly_names, conf, user_friendly_names, conf_origin); do_default(user_friendly_names, DEFAULT_USER_FRIENDLY_NAMES); out: condlog(3, "%s: user_friendly_names = %s %s", mp->wwid, (user_friendly_names == USER_FRIENDLY_NAMES_ON)? "yes" : "no", origin); return (user_friendly_names == USER_FRIENDLY_NAMES_ON); } int select_alias(struct config *conf, struct multipath * mp) { const char *origin = NULL; if (mp->mpe && mp->mpe->alias) { mp->alias = strdup(mp->mpe->alias); origin = multipaths_origin; goto out; } mp->alias = NULL; if (!want_user_friendly_names(conf, mp)) goto out; select_alias_prefix(conf, mp); mp->alias = get_user_friendly_alias(mp->wwid, mp->alias_old, mp->alias_prefix, conf->bindings_read_only); if (mp->alias && !strncmp(mp->alias, mp->alias_old, WWID_SIZE)) origin = "(setting: using existing alias)"; else if (mp->alias) origin = "(setting: user_friendly_name)"; memset (mp->alias_old, 0, WWID_SIZE); out: if (mp->alias == NULL) { mp->alias = strdup(mp->wwid); origin = "(setting: default to WWID)"; } if (mp->alias) condlog(3, "%s: alias = %s %s", mp->wwid, mp->alias, origin); return mp->alias ? 0 : 1; } void reconcile_features_with_options(const char *id, char **features, int* no_path_retry, int *retain_hwhandler) { static const char q_i_n_p[] = "queue_if_no_path"; static const char r_a_h_h[] = "retain_attached_hw_handler"; STRBUF_ON_STACK(buff); if (*features == NULL) return; if (id == NULL) id = "UNKNOWN"; /* * We only use no_path_retry internally. The "queue_if_no_path" * device-mapper feature is derived from it when the map is loaded. * For consistency, "queue_if_no_path" is removed from the * internal libmultipath features string. * For backward compatibility we allow 'features "1 queue_if_no_path"'; * it's translated into "no_path_retry queue" here. */ if (strstr(*features, q_i_n_p)) { condlog(0, "%s: option 'features \"1 %s\"' is deprecated, " "please use 'no_path_retry queue' instead", id, q_i_n_p); if (*no_path_retry == NO_PATH_RETRY_UNDEF) { *no_path_retry = NO_PATH_RETRY_QUEUE; print_no_path_retry(&buff, *no_path_retry); condlog(3, "%s: no_path_retry = %s (inherited setting from feature '%s')", id, get_strbuf_str(&buff), q_i_n_p); }; /* Warn only if features string is overridden */ if (*no_path_retry != NO_PATH_RETRY_QUEUE) { print_no_path_retry(&buff, *no_path_retry); condlog(2, "%s: ignoring feature '%s' because no_path_retry is set to '%s'", id, q_i_n_p, get_strbuf_str(&buff)); } remove_feature(features, q_i_n_p); } if (strstr(*features, r_a_h_h)) { condlog(0, "%s: option 'features \"1 %s\"' is deprecated", id, r_a_h_h); if (*retain_hwhandler == RETAIN_HWHANDLER_UNDEF) { condlog(3, "%s: %s = on (inherited setting from feature '%s')", id, r_a_h_h, r_a_h_h); *retain_hwhandler = RETAIN_HWHANDLER_ON; } else if (*retain_hwhandler == RETAIN_HWHANDLER_OFF) condlog(2, "%s: ignoring feature '%s' because %s is set to 'off'", id, r_a_h_h, r_a_h_h); remove_feature(features, r_a_h_h); } } static void reconcile_features_with_queue_mode(struct multipath *mp) { char *space = NULL, *val = NULL, *mode_str = NULL, *feat; int features_mode = QUEUE_MODE_UNDEF; if (!mp->features) return; pthread_cleanup_push(cleanup_free_ptr, &space); pthread_cleanup_push(cleanup_free_ptr, &val); pthread_cleanup_push(cleanup_free_ptr, &mode_str); if (!(feat = strstr(mp->features, "queue_mode")) || feat == mp->features || !isspace(*(feat - 1)) || sscanf(feat, "queue_mode%m[ \f\n\r\t\v]%ms", &space, &val) != 2) goto sync_mode; if (asprintf(&mode_str, "queue_mode%s%s", space, val) < 0) { condlog(1, "failed to allocate space for queue_mode feature string"); mode_str = NULL; /* value undefined on failure */ goto exit; } if (!strcmp(val, "rq") || !strcmp(val, "mq")) features_mode = QUEUE_MODE_RQ; else if (!strcmp(val, "bio")) features_mode = QUEUE_MODE_BIO; if (features_mode == QUEUE_MODE_UNDEF) { condlog(2, "%s: ignoring invalid feature '%s'", mp->alias, mode_str); goto sync_mode; } if (mp->queue_mode == QUEUE_MODE_UNDEF) mp->queue_mode = features_mode; if (mp->queue_mode == features_mode) goto exit; condlog(2, "%s: ignoring feature '%s' because queue_mode is set to '%s'", mp->alias, mode_str, (mp->queue_mode == QUEUE_MODE_RQ)? "rq" : "bio"); sync_mode: if (mode_str) remove_feature(&mp->features, mode_str); if (mp->queue_mode == QUEUE_MODE_BIO) add_feature(&mp->features, "queue_mode bio"); exit: pthread_cleanup_pop(1); pthread_cleanup_pop(1); pthread_cleanup_pop(1); } int select_features(struct config *conf, struct multipath *mp) { const char *origin; mp_set_mpe(features); mp_set_ovr(features); mp_set_hwe(features); mp_set_conf(features); mp_set_default(features, DEFAULT_FEATURES); out: mp->features = strdup(mp->features); reconcile_features_with_options(mp->alias, &mp->features, &mp->no_path_retry, &mp->retain_hwhandler); reconcile_features_with_queue_mode(mp); condlog(3, "%s: features = \"%s\" %s", mp->alias, mp->features, origin); return 0; } static int get_dh_state(struct path *pp, char *value, size_t value_len) { struct udev_device *ud; ssize_t rc; if (pp->udev == NULL) return -1; ud = udev_device_get_parent_with_subsystem_devtype( pp->udev, "scsi", "scsi_device"); if (ud == NULL) return -1; rc = sysfs_attr_get_value(ud, "dh_state", value, value_len); if (!sysfs_attr_value_ok(rc, value_len)) return -1; return rc; } int select_hwhandler(struct config *conf, struct multipath *mp) { const char *origin; struct path *pp; /* dh_state is no longer than "detached" */ char handler[12]; static char alua_name[] = "1 alua"; static const char tpgs_origin[]= "(setting: autodetected from TPGS)"; char *dh_state; int i; bool all_tpgs = true, one_tpgs = false; dh_state = &handler[2]; /* * TPGS_UNDEF means that ALUA support couldn't determined either way * yet, probably because the path was always down. * If at least one path does have TPGS support, and no path has * TPGS_NONE, assume that TPGS would be supported by all paths if * all were up. */ vector_foreach_slot(mp->paths, pp, i) { int tpgs = path_get_tpgs(pp); all_tpgs = all_tpgs && tpgs != TPGS_NONE; one_tpgs = one_tpgs || (tpgs != TPGS_NONE && tpgs != TPGS_UNDEF); } all_tpgs = all_tpgs && one_tpgs; if (mp->retain_hwhandler != RETAIN_HWHANDLER_OFF) { vector_foreach_slot(mp->paths, pp, i) { if (get_dh_state(pp, dh_state, sizeof(handler) - 2) > 0 && strcmp(dh_state, "detached")) { memcpy(handler, "1 ", 2); mp->hwhandler = handler; origin = "(setting: retained by kernel driver)"; goto out; } } } mp_set_hwe(hwhandler); mp_set_conf(hwhandler); mp_set_default(hwhandler, DEFAULT_HWHANDLER); out: if (all_tpgs && !strcmp(mp->hwhandler, DEFAULT_HWHANDLER) && origin == default_origin) { mp->hwhandler = alua_name; origin = tpgs_origin; } else if (!all_tpgs && !strcmp(mp->hwhandler, alua_name)) { mp->hwhandler = DEFAULT_HWHANDLER; origin = tpgs_origin; } mp->hwhandler = strdup(mp->hwhandler); condlog(3, "%s: hardware_handler = \"%s\" %s", mp->alias, mp->hwhandler, origin); return 0; } int select_checker_timeout(struct config *conf, struct path *pp) { const char *origin; pp_set_conf(checker_timeout); if (sysfs_get_timeout(pp, &pp->checker_timeout) > 0) { origin = "(setting: kernel sysfs)"; goto out; } pp_set_default(checker_timeout, DEF_TIMEOUT); out: condlog(3, "%s checker timeout = %u s %s", pp->dev, pp->checker_timeout, origin); return 0; } /* * Current RDAC (NetApp E/EF Series) firmware relies * on periodic REPORT TARGET PORT GROUPS for * internal load balancing. * Using the sysfs priority checker defeats this purpose. * * Moreover, NetApp would also prefer the RDAC checker over ALUA. * (https://listman.redhat.com/archives/dm-devel/2017-September/msg00326.html) */ static int check_rdac(struct path * pp) { int len; char buff[44]; const char *checker_name = NULL; if (pp->bus != SYSFS_BUS_SCSI) return 0; /* Avoid checking 0xc9 if this is likely not an RDAC array */ if (!do_set_from_hwe__(checker_name, pp, checker_name) && !is_vpd_page_supported(pp->fd, 0xC9)) return 0; if (checker_name && strcmp(checker_name, RDAC)) return 0; len = get_vpd_sgio(pp->fd, 0xC9, 0, buff, 44); if (len <= 0) return 0; return !(memcmp(buff + 4, "vac1", 4)); } int select_checker(struct config *conf, struct path *pp) { const char *origin; char *ckr_name; struct checker * c = &pp->checker; if (!pp->checker_timeout) select_checker_timeout(conf, pp); if (pp->detect_checker == DETECT_CHECKER_ON) { origin = autodetect_origin; if (check_rdac(pp)) { ckr_name = RDAC; goto out; } (void)path_get_tpgs(pp); if (pp->tpgs != TPGS_NONE && pp->tpgs != TPGS_UNDEF) { ckr_name = TUR; goto out; } } do_set(checker_name, conf->overrides, ckr_name, overrides_origin); do_set_from_hwe(checker_name, pp, ckr_name, hwe_origin); do_set(checker_name, conf, ckr_name, conf_origin); do_default(ckr_name, DEFAULT_CHECKER); out: checker_get(c, ckr_name); condlog(3, "%s: path_checker = %s %s", pp->dev, checker_name(c), origin); c->timeout = pp->checker_timeout; c->path_state = PATH_UNCHECKED; return 0; } int select_getuid(struct config *conf, struct path *pp) { const char *origin; pp->uid_attribute = get_uid_attribute_by_attrs(conf, pp->dev); if (pp->uid_attribute) { origin = "(setting: multipath.conf defaults section / uid_attrs)"; goto out; } pp_set_ovr(uid_attribute); pp_set_hwe(uid_attribute); pp_set_conf(uid_attribute); pp_set_default(uid_attribute, DEFAULT_UID_ATTRIBUTE); out: if (pp->uid_attribute) condlog(3, "%s: uid_attribute = %s %s", pp->dev, pp->uid_attribute, origin); return 0; } /* must be called after select_getuid */ int select_recheck_wwid(struct config *conf, struct path * pp) { const char *origin; pp_set_ovr(recheck_wwid); pp_set_hwe(recheck_wwid); pp_set_conf(recheck_wwid); pp_set_default(recheck_wwid, DEFAULT_RECHECK_WWID); out: if (pp->recheck_wwid == RECHECK_WWID_ON && (pp->bus != SYSFS_BUS_SCSI || !has_uid_fallback(pp))) { pp->recheck_wwid = RECHECK_WWID_OFF; origin = "(setting: unsupported by device type/config)"; } condlog(3, "%s: recheck_wwid = %i %s", pp->dev, pp->recheck_wwid, origin); return 0; } void detect_prio(struct path *pp) { struct prio *p = &pp->prio; char buff[512]; char *default_prio; int tpgs; switch(pp->bus) { case SYSFS_BUS_NVME: if (nvme_id_ctrl_ana(pp->fd, NULL) == 0) return; default_prio = PRIO_ANA; break; case SYSFS_BUS_SCSI: tpgs = path_get_tpgs(pp); if (tpgs == TPGS_NONE) return; if ((tpgs == TPGS_EXPLICIT || !check_rdac(pp)) && sysfs_get_asymmetric_access_state(pp, buff, 512) >= 0) default_prio = PRIO_SYSFS; else default_prio = PRIO_ALUA; break; default: return; } prio_get(p, default_prio, DEFAULT_PRIO_ARGS); } #define set_prio(src, msg) \ do { \ if (src && src->prio_name) { \ prio_get(p, src->prio_name, src->prio_args); \ origin = msg; \ goto out; \ } \ } while(0) #define set_prio_from_vec(type, src, msg, p) \ do { \ type *_p; \ int i; \ char *prio_name = NULL, *prio_args = NULL; \ \ vector_foreach_slot(src, _p, i) { \ if (prio_name == NULL && _p->prio_name) \ prio_name = _p->prio_name; \ if (prio_args == NULL && _p->prio_args) \ prio_args = _p->prio_args; \ } \ if (prio_name != NULL) { \ prio_get(p, prio_name, prio_args); \ origin = msg; \ goto out; \ } \ } while (0) int select_prio(struct config *conf, struct path *pp) { const char *origin; struct mpentry * mpe; struct prio * p = &pp->prio; int log_prio = 3; if (!pp->checker_timeout) select_checker_timeout(conf, pp); if (pp->detect_prio == DETECT_PRIO_ON) { detect_prio(pp); if (prio_selected(p)) { origin = autodetect_origin; goto out; } } mpe = find_mpe(conf->mptable, pp->wwid); set_prio(mpe, multipaths_origin); set_prio(conf->overrides, overrides_origin); set_prio_from_vec(struct hwentry, pp->hwe, hwe_origin, p); set_prio(conf, conf_origin); prio_get(p, DEFAULT_PRIO, DEFAULT_PRIO_ARGS); origin = default_origin; out: /* * fetch tpgs mode for alua, if its not already obtained */ if (!strncmp(prio_name(p), PRIO_ALUA, PRIO_NAME_LEN)) { int tpgs = path_get_tpgs(pp); if (tpgs == TPGS_NONE) { prio_get(p, DEFAULT_PRIO, DEFAULT_PRIO_ARGS); origin = "(setting: emergency fallback - alua failed)"; log_prio = 1; } } condlog(log_prio, "%s: prio = %s %s", pp->dev, prio_name(p), origin); condlog(3, "%s: prio args = \"%s\" %s", pp->dev, prio_args(p), origin); return 0; } int select_no_path_retry(struct config *conf, struct multipath *mp) { const char *origin = NULL; STRBUF_ON_STACK(buff); if (mp->disable_queueing) { condlog(0, "%s: queueing disabled", mp->alias); mp->no_path_retry = NO_PATH_RETRY_FAIL; return 0; } mp_set_mpe(no_path_retry); mp_set_ovr(no_path_retry); mp_set_hwe(no_path_retry); mp_set_conf(no_path_retry); out: print_no_path_retry(&buff, mp->no_path_retry); if (origin) condlog(3, "%s: no_path_retry = %s %s", mp->alias, get_strbuf_str(&buff), origin); else condlog(3, "%s: no_path_retry = undef %s", mp->alias, default_origin); 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) { const char *origin; mp_set_mpe(minio); mp_set_ovr(minio); mp_set_hwe(minio); mp_set_conf(minio); mp_set_default(minio, DEFAULT_MINIO); out: condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin); return 0; } int select_minio(struct config *conf, struct multipath *mp) { unsigned int minv_dmrq[3] = {1, 1, 0}, version[3]; if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) && VERSION_GE(version, minv_dmrq)) return select_minio_rq(conf, mp); else return select_minio_bio(conf, mp); } int select_fast_io_fail(struct config *conf, struct path *pp) { const char *origin; STRBUF_ON_STACK(buff); pp_set_ovr_pce(fast_io_fail); pp_set_hwe(fast_io_fail); pp_set_conf(fast_io_fail); pp_set_default(fast_io_fail, DEFAULT_FAST_IO_FAIL); out: print_undef_off_zero(&buff, pp->fast_io_fail); condlog(3, "%s: fast_io_fail_tmo = %s %s", pp->dev, get_strbuf_str(&buff), origin); return 0; } int select_dev_loss(struct config *conf, struct path *pp) { const char *origin; STRBUF_ON_STACK(buff); pp_set_ovr_pce(dev_loss); pp_set_hwe(dev_loss); pp_set_conf(dev_loss); pp->dev_loss = DEV_LOSS_TMO_UNSET; return 0; out: print_dev_loss(&buff, pp->dev_loss); condlog(3, "%s: dev_loss_tmo = %s %s", pp->dev, get_strbuf_str(&buff), origin); return 0; } int select_eh_deadline(struct config *conf, struct path *pp) { const char *origin; STRBUF_ON_STACK(buff); pp_set_ovr_pce(eh_deadline); pp_set_hwe(eh_deadline); pp_set_conf(eh_deadline); pp->eh_deadline = EH_DEADLINE_UNSET; /* not changing sysfs in default cause, so don't print anything */ return 0; out: print_undef_off_zero(&buff, pp->eh_deadline); condlog(3, "%s: eh_deadline = %s %s", pp->dev, get_strbuf_str(&buff), origin); return 0; } int select_flush_on_last_del(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); mp_set_mpe(flush_on_last_del); mp_set_ovr(flush_on_last_del); mp_set_hwe(flush_on_last_del); mp_set_conf(flush_on_last_del); mp_set_default(flush_on_last_del, DEFAULT_FLUSH); out: print_flush_on_last_del(&buff, mp->flush_on_last_del); condlog(3, "%s: flush_on_last_del = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_reservation_key(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); char *from_file = ""; uint64_t prkey = 0; do_prkey_set(mp->mpe, multipaths_origin); do_prkey_set(conf, conf_origin); put_be64(mp->reservation_key, 0); mp->sa_flags = 0; mp->prkey_source = PRKEY_SOURCE_NONE; return 0; out: if (mp->prkey_source == PRKEY_SOURCE_FILE) { from_file = " (from prkeys file)"; if (get_prkey(mp, &prkey, &mp->sa_flags) != 0) put_be64(mp->reservation_key, 0); else put_be64(mp->reservation_key, prkey); } print_reservation_key(&buff, mp->reservation_key, mp->sa_flags, mp->prkey_source); condlog(3, "%s: reservation_key = %s %s%s", mp->alias, get_strbuf_str(&buff), origin, from_file); return 0; } int select_retain_hwhandler(struct config *conf, struct multipath *mp) { const char *origin; unsigned int minv_dm_retain[3] = {1, 5, 0}, version[3]; if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) && !VERSION_GE(version, minv_dm_retain)) { mp->retain_hwhandler = RETAIN_HWHANDLER_OFF; origin = "(setting: WARNING, requires kernel dm-mpath version >= 1.5.0)"; goto out; } if (get_linux_version_code() >= KERNEL_VERSION(4, 3, 0)) { mp->retain_hwhandler = RETAIN_HWHANDLER_ON; origin = "(setting: implied in kernel >= 4.3.0)"; goto out; } mp_set_ovr(retain_hwhandler); mp_set_hwe(retain_hwhandler); mp_set_conf(retain_hwhandler); mp_set_default(retain_hwhandler, DEFAULT_RETAIN_HWHANDLER); out: condlog(3, "%s: retain_attached_hw_handler = %s %s", mp->alias, (mp->retain_hwhandler == RETAIN_HWHANDLER_ON)? "yes" : "no", origin); return 0; } int select_detect_prio(struct config *conf, struct path *pp) { const char *origin; pp_set_ovr(detect_prio); pp_set_hwe(detect_prio); pp_set_conf(detect_prio); pp_set_default(detect_prio, DEFAULT_DETECT_PRIO); out: condlog(3, "%s: detect_prio = %s %s", pp->dev, (pp->detect_prio == DETECT_PRIO_ON)? "yes" : "no", origin); return 0; } int select_detect_checker(struct config *conf, struct path *pp) { const char *origin; pp_set_ovr(detect_checker); pp_set_hwe(detect_checker); pp_set_conf(detect_checker); pp_set_default(detect_checker, DEFAULT_DETECT_CHECKER); out: condlog(3, "%s: detect_checker = %s %s", pp->dev, (pp->detect_checker == DETECT_CHECKER_ON)? "yes" : "no", origin); return 0; } int select_deferred_remove(struct config *conf, struct multipath *mp) { const char *origin; #ifndef LIBDM_API_DEFERRED mp->deferred_remove = DEFERRED_REMOVE_OFF; origin = "(setting: WARNING, not compiled with support)"; goto out; #endif if (mp->deferred_remove == DEFERRED_REMOVE_IN_PROGRESS) { condlog(3, "%s: deferred remove in progress", mp->alias); return 0; } mp_set_mpe(deferred_remove); mp_set_ovr(deferred_remove); mp_set_hwe(deferred_remove); mp_set_conf(deferred_remove); mp_set_default(deferred_remove, DEFAULT_DEFERRED_REMOVE); out: condlog(3, "%s: deferred_remove = %s %s", mp->alias, (mp->deferred_remove == DEFERRED_REMOVE_ON)? "yes" : "no", origin); return 0; } static inline int san_path_check_options_set(const struct multipath *mp) { return mp->san_path_err_threshold > 0 || mp->san_path_err_forget_rate > 0 || mp->san_path_err_recovery_time > 0; } static int use_delay_watch_checks(struct config *conf, struct multipath *mp) { int value = NU_UNDEF; const char *origin = default_origin; STRBUF_ON_STACK(buff); do_set(delay_watch_checks, mp->mpe, value, multipaths_origin); do_set(delay_watch_checks, conf->overrides, value, overrides_origin); do_set_from_hwe(delay_watch_checks, mp, value, hwe_origin); do_set(delay_watch_checks, conf, value, conf_origin); out: if (print_off_int_undef(&buff, value) > 0) condlog(3, "%s: delay_watch_checks = %s %s", mp->alias, get_strbuf_str(&buff), origin); return value; } static int use_delay_wait_checks(struct config *conf, struct multipath *mp) { int value = NU_UNDEF; const char *origin = default_origin; STRBUF_ON_STACK(buff); do_set(delay_wait_checks, mp->mpe, value, multipaths_origin); do_set(delay_wait_checks, conf->overrides, value, overrides_origin); do_set_from_hwe(delay_wait_checks, mp, value, hwe_origin); do_set(delay_wait_checks, conf, value, conf_origin); out: if (print_off_int_undef(&buff, value) > 0) condlog(3, "%s: delay_wait_checks = %s %s", mp->alias, get_strbuf_str(&buff), origin); return value; } int select_delay_checks(struct config *conf, struct multipath *mp) { int watch_checks, wait_checks; STRBUF_ON_STACK(buff); watch_checks = use_delay_watch_checks(conf, mp); wait_checks = use_delay_wait_checks(conf, mp); if (watch_checks <= 0 && wait_checks <= 0) return 0; if (san_path_check_options_set(mp)) { condlog(3, "%s: both marginal_path and delay_checks error detection options selected", mp->alias); condlog(3, "%s: ignoring delay_checks options", mp->alias); return 0; } mp->san_path_err_threshold = 1; condlog(3, "%s: san_path_err_threshold = 1 %s", mp->alias, (watch_checks > 0)? delay_watch_origin : delay_wait_origin); if (watch_checks > 0) { mp->san_path_err_forget_rate = watch_checks; print_off_int_undef(&buff, mp->san_path_err_forget_rate); condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias, get_strbuf_str(&buff), delay_watch_origin); reset_strbuf(&buff); } if (wait_checks > 0) { mp->san_path_err_recovery_time = wait_checks * conf->max_checkint; print_off_int_undef(&buff, mp->san_path_err_recovery_time); condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias, get_strbuf_str(&buff), delay_wait_origin); } return 0; } static int san_path_deprecated_warned; #define warn_san_path_deprecated(v, x) \ do { \ if (v->x > 0 && !san_path_deprecated_warned) { \ san_path_deprecated_warned = 1; \ condlog(1, "WARNING: option %s is deprecated, " \ "please use marginal_path options instead", \ #x); \ } \ } while(0) int select_san_path_err_threshold(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) { mp->san_path_err_threshold = NU_NO; if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) origin = fpin_marginal_path_origin; else origin = marginal_path_origin; goto out; } mp_set_mpe(san_path_err_threshold); mp_set_ovr(san_path_err_threshold); mp_set_hwe(san_path_err_threshold); mp_set_conf(san_path_err_threshold); mp_set_default(san_path_err_threshold, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->san_path_err_threshold) > 0) condlog(3, "%s: san_path_err_threshold = %s %s", mp->alias, get_strbuf_str(&buff), origin); warn_san_path_deprecated(mp, san_path_err_threshold); return 0; } int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) { mp->san_path_err_forget_rate = NU_NO; if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) origin = fpin_marginal_path_origin; else origin = marginal_path_origin; goto out; } mp_set_mpe(san_path_err_forget_rate); mp_set_ovr(san_path_err_forget_rate); mp_set_hwe(san_path_err_forget_rate); mp_set_conf(san_path_err_forget_rate); mp_set_default(san_path_err_forget_rate, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->san_path_err_forget_rate) > 0) condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias, get_strbuf_str(&buff), origin); warn_san_path_deprecated(mp, san_path_err_forget_rate); return 0; } int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) { mp->san_path_err_recovery_time = NU_NO; if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) origin = fpin_marginal_path_origin; else origin = marginal_path_origin; goto out; } mp_set_mpe(san_path_err_recovery_time); mp_set_ovr(san_path_err_recovery_time); mp_set_hwe(san_path_err_recovery_time); mp_set_conf(san_path_err_recovery_time); mp_set_default(san_path_err_recovery_time, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->san_path_err_recovery_time) != 0) condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias, get_strbuf_str(&buff), origin); warn_san_path_deprecated(mp, san_path_err_recovery_time); return 0; } int select_marginal_path_err_sample_time(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) { mp->marginal_path_err_sample_time = NU_NO; origin = fpin_marginal_path_origin; goto out; } mp_set_mpe(marginal_path_err_sample_time); mp_set_ovr(marginal_path_err_sample_time); mp_set_hwe(marginal_path_err_sample_time); mp_set_conf(marginal_path_err_sample_time); mp_set_default(marginal_path_err_sample_time, DEFAULT_ERR_CHECKS); out: if (mp->marginal_path_err_sample_time > 0 && mp->marginal_path_err_sample_time < 2 * IOTIMEOUT_SEC) { condlog(2, "%s: configuration error: marginal_path_err_sample_time must be >= %d", mp->alias, 2 * IOTIMEOUT_SEC); mp->marginal_path_err_sample_time = 2 * IOTIMEOUT_SEC; } if (print_off_int_undef(&buff, mp->marginal_path_err_sample_time) > 0) condlog(3, "%s: marginal_path_err_sample_time = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) { mp->marginal_path_err_rate_threshold = NU_NO; origin = fpin_marginal_path_origin; goto out; } mp_set_mpe(marginal_path_err_rate_threshold); mp_set_ovr(marginal_path_err_rate_threshold); mp_set_hwe(marginal_path_err_rate_threshold); mp_set_conf(marginal_path_err_rate_threshold); mp_set_default(marginal_path_err_rate_threshold, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->marginal_path_err_rate_threshold) > 0) condlog(3, "%s: marginal_path_err_rate_threshold = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) { mp->marginal_path_err_recheck_gap_time = NU_NO; origin = fpin_marginal_path_origin; goto out; } mp_set_mpe(marginal_path_err_recheck_gap_time); mp_set_ovr(marginal_path_err_recheck_gap_time); mp_set_hwe(marginal_path_err_recheck_gap_time); mp_set_conf(marginal_path_err_recheck_gap_time); mp_set_default(marginal_path_err_recheck_gap_time, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->marginal_path_err_recheck_gap_time) > 0) condlog(3, "%s: marginal_path_err_recheck_gap_time = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp) { const char *origin; STRBUF_ON_STACK(buff); if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) { mp->marginal_path_double_failed_time = NU_NO; origin = fpin_marginal_path_origin; goto out; } mp_set_mpe(marginal_path_double_failed_time); mp_set_ovr(marginal_path_double_failed_time); mp_set_hwe(marginal_path_double_failed_time); mp_set_conf(marginal_path_double_failed_time); mp_set_default(marginal_path_double_failed_time, DEFAULT_ERR_CHECKS); out: if (print_off_int_undef(&buff, mp->marginal_path_double_failed_time) > 0) condlog(3, "%s: marginal_path_double_failed_time = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_skip_kpartx (struct config *conf, struct multipath * mp) { const char *origin; mp_set_mpe(skip_kpartx); mp_set_ovr(skip_kpartx); mp_set_hwe(skip_kpartx); mp_set_conf(skip_kpartx); mp_set_default(skip_kpartx, DEFAULT_SKIP_KPARTX); out: condlog(3, "%s: skip_kpartx = %s %s", mp->alias, (mp->skip_kpartx == SKIP_KPARTX_ON)? "yes" : "no", origin); return 0; } int select_max_sectors_kb(struct config *conf, struct multipath * mp) { const char *origin; mp_set_mpe(max_sectors_kb); mp_set_ovr(max_sectors_kb); mp_set_hwe(max_sectors_kb); mp_set_conf(max_sectors_kb); mp_set_default(max_sectors_kb, DEFAULT_MAX_SECTORS_KB); /* * In the default case, we will not modify max_sectors_kb in sysfs * (see sysfs_set_max_sectors_kb()). * Don't print a log message here to avoid user confusion. */ return 0; out: condlog(3, "%s: max_sectors_kb = %i %s", mp->alias, mp->max_sectors_kb, origin); return 0; } int select_ghost_delay (struct config *conf, struct multipath * mp) { const char *origin; STRBUF_ON_STACK(buff); mp_set_mpe(ghost_delay); mp_set_ovr(ghost_delay); mp_set_hwe(ghost_delay); mp_set_conf(ghost_delay); mp_set_default(ghost_delay, DEFAULT_GHOST_DELAY); out: if (print_off_int_undef(&buff, mp->ghost_delay) != 0) condlog(3, "%s: ghost_delay = %s %s", mp->alias, get_strbuf_str(&buff), origin); return 0; } int select_find_multipaths_timeout(struct config *conf, struct path *pp) { const char *origin; pp_set_conf(find_multipaths_timeout); pp_set_default(find_multipaths_timeout, DEFAULT_FIND_MULTIPATHS_TIMEOUT); out: /* * If configured value is negative, and this "unknown" hardware * (no hwentry), use very small timeout to avoid delays. */ if (pp->find_multipaths_timeout < 0) { pp->find_multipaths_timeout = -pp->find_multipaths_timeout; if (VECTOR_SIZE(pp->hwe) == 0) { pp->find_multipaths_timeout = DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT; origin = "(default for unknown hardware)"; } } condlog(3, "%s: timeout for find_multipaths \"smart\" = %ds %s", pp->dev, pp->find_multipaths_timeout, origin); return 0; } int select_all_tg_pt (struct config *conf, struct multipath * mp) { const char *origin; mp_set_ovr(all_tg_pt); mp_set_hwe(all_tg_pt); mp_set_conf(all_tg_pt); mp_set_default(all_tg_pt, DEFAULT_ALL_TG_PT); out: condlog(3, "%s: all_tg_pt = %s %s", mp->alias, (mp->all_tg_pt == ALL_TG_PT_ON)? "yes" : "no", origin); return 0; } int select_vpd_vendor_id (struct path *pp) { const char *origin; pp_set_hwe(vpd_vendor_id); pp_set_default(vpd_vendor_id, 0); out: if (pp->vpd_vendor_id < 0 || pp->vpd_vendor_id >= VPD_VP_ARRAY_SIZE) { condlog(3, "%s: vpd_vendor_id = %d (invalid, setting to 0)", pp->dev, pp->vpd_vendor_id); pp->vpd_vendor_id = 0; } condlog(3, "%s: vpd_vendor_id = %d \"%s\" %s", pp->dev, pp->vpd_vendor_id, vpd_vendor_pages[pp->vpd_vendor_id].name, origin); return 0; } multipath-tools-0.11.1/libmultipath/propsel.h000066400000000000000000000057041475246302400213070ustar00rootroot00000000000000#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); int select_pgpolicy (struct config *conf, struct multipath * mp); int select_selector (struct config *conf, struct multipath * mp); int select_alias (struct config *conf, struct multipath * mp); int select_features (struct config *conf, struct multipath * mp); int select_hwhandler (struct config *conf, struct multipath * mp); int select_checker(struct config *conf, struct path *pp); int select_getuid (struct config *conf, struct path * pp); int select_recheck_wwid(struct config *conf, struct path * pp); int select_prio (struct config *conf, struct path * pp); int select_find_multipaths_timeout(struct config *conf, struct path *pp); int select_no_path_retry(struct config *conf, struct multipath *mp); int select_flush_on_last_del(struct config *conf, struct multipath *mp); int select_minio(struct config *conf, struct multipath *mp); int select_mode(struct config *conf, struct multipath *mp); int select_uid(struct config *conf, struct multipath *mp); int select_gid(struct config *conf, struct multipath *mp); int select_fast_io_fail(struct config *conf, struct path *pp); int select_dev_loss(struct config *conf, struct path *pp); int select_eh_deadline(struct config *conf, struct path *pp); int select_reservation_key(struct config *conf, struct multipath *mp); int select_retain_hwhandler (struct config *conf, struct multipath * mp); int select_detect_prio(struct config *conf, struct path * pp); int select_detect_checker(struct config *conf, struct path * pp); int select_deferred_remove(struct config *conf, struct multipath *mp); int select_delay_checks(struct config *conf, struct multipath * mp); int select_skip_kpartx (struct config *conf, struct multipath * mp); int select_max_sectors_kb (struct config *conf, struct multipath * mp); int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp); int select_san_path_err_threshold(struct config *conf, struct multipath *mp); int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp); int select_marginal_path_err_sample_time(struct config *conf, struct multipath *mp); int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp); int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp); int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp); int select_ghost_delay(struct config *conf, struct multipath * mp); void reconcile_features_with_options(const char *id, char **features, int* no_path_retry, int *retain_hwhandler); int select_all_tg_pt (struct config *conf, struct multipath * mp); int select_vpd_vendor_id (struct path *pp); #endif multipath-tools-0.11.1/libmultipath/sg_include.h000066400000000000000000000027201475246302400217320ustar00rootroot00000000000000#ifndef SG_INCLUDE_H_INCLUDED #define SG_INCLUDE_H_INCLUDED #include #ifndef DID_OK #define DID_OK 0x00 /* NO error */ #define DID_NO_CONNECT 0x01 /* Couldn't connect before timeout period */ #define DID_BUS_BUSY 0x02 /* BUS stayed busy through time out period */ #define DID_TIME_OUT 0x03 /* TIMED OUT for other reason */ #define DID_BAD_TARGET 0x04 /* BAD target. */ #define DID_ABORT 0x05 /* Told to abort for some other reason */ #define DID_PARITY 0x06 /* Parity error */ #define DID_ERROR 0x07 /* Internal error */ #define DID_RESET 0x08 /* Reset by somebody. */ #define DID_BAD_INTR 0x09 /* Got an interrupt we weren't expecting. */ #define DID_PASSTHROUGH 0x0a /* Force command past mid-layer */ #define DID_SOFT_ERROR 0x0b /* The low level driver just wish a retry */ #define DID_IMM_RETRY 0x0c /* Retry without decrementing retry count */ #define DID_REQUEUE 0x0d /* Requeue command (no immediate retry) also * without decrementing the retry count */ #define DID_TRANSPORT_DISRUPTED 0x0e /* Transport error disrupted execution * and the driver blocked the port to * recover the link. Transport class will * retry or fail IO */ #define DID_TRANSPORT_FAILFAST 0x0f /* Transport class fastfailed the io */ #endif #endif multipath-tools-0.11.1/libmultipath/structs.c000066400000000000000000000363251475246302400213300ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2004 Stefan Bader, IBM */ #include #include #include #include #include #include "checkers.h" #include "vector.h" #include "util.h" #include "structs.h" #include "config.h" #include "debug.h" #include "structs_vec.h" #include "blacklist.h" #include "prio.h" #include "prioritizers/alua_spc3.h" #include "dm-generic.h" #include "devmapper.h" const char * const protocol_name[LAST_BUS_PROTOCOL_ID + 1] = { [SYSFS_BUS_UNDEF] = "undef", [SYSFS_BUS_CCW] = "ccw", [SYSFS_BUS_CCISS] = "cciss", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_FCP] = "scsi:fcp", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SPI] = "scsi:spi", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SSA] = "scsi:ssa", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SBP] = "scsi:sbp", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SRP] = "scsi:srp", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_ISCSI] = "scsi:iscsi", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SAS] = "scsi:sas", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_ADT] = "scsi:adt", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_ATA] = "scsi:ata", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_USB] = "scsi:usb", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_UNSPEC] = "scsi:unspec", [SYSFS_BUS_NVME + NVME_PROTOCOL_PCIE] = "nvme:pcie", [SYSFS_BUS_NVME + NVME_PROTOCOL_RDMA] = "nvme:rdma", [SYSFS_BUS_NVME + NVME_PROTOCOL_FC] = "nvme:fc", [SYSFS_BUS_NVME + NVME_PROTOCOL_TCP] = "nvme:tcp", [SYSFS_BUS_NVME + NVME_PROTOCOL_LOOP] = "nvme:loop", [SYSFS_BUS_NVME + NVME_PROTOCOL_APPLE_NVME] = "nvme:apple-nvme", [SYSFS_BUS_NVME + NVME_PROTOCOL_UNSPEC] = "nvme:unspec", }; struct adapter_group * alloc_adaptergroup(void) { struct adapter_group *agp; agp = (struct adapter_group *)calloc(1, sizeof(struct adapter_group)); if (!agp) return NULL; agp->host_groups = vector_alloc(); if (!agp->host_groups) { free(agp); agp = NULL; } return agp; } void free_adaptergroup(vector adapters) { int i; struct adapter_group *agp; vector_foreach_slot(adapters, agp, i) { free_hostgroup(agp->host_groups); free(agp); } vector_free(adapters); } void free_hostgroup(vector hostgroups) { int i; struct host_group *hgp; if (!hostgroups) return; vector_foreach_slot(hostgroups, hgp, i) { vector_free(hgp->paths); free(hgp); } vector_free(hostgroups); } struct host_group * alloc_hostgroup(void) { struct host_group *hgp; hgp = (struct host_group *)calloc(1, sizeof(struct host_group)); if (!hgp) return NULL; hgp->paths = vector_alloc(); if (!hgp->paths) { free(hgp); hgp = NULL; } return hgp; } struct path * alloc_path (void) { struct path * pp; pp = (struct path *)calloc(1, sizeof(struct path)); if (pp) { pp->initialized = INIT_NEW; pp->sg_id.host_no = -1; pp->sg_id.channel = -1; pp->sg_id.scsi_id = -1; pp->sg_id.lun = SCSI_INVALID_LUN; pp->sg_id.proto_id = PROTOCOL_UNSET; pp->fd = -1; pp->tpgs = TPGS_UNDEF; pp->tpg_id = GROUP_ID_UNDEF; pp->priority = PRIO_UNDEF; pp->checkint = CHECKINT_UNDEF; checker_clear(&pp->checker); dm_path_to_gen(pp)->ops = &dm_gen_path_ops; pp->hwe = vector_alloc(); if (pp->hwe == NULL) { free(pp); return NULL; } } return pp; } void uninitialize_path(struct path *pp) { if (!pp) return; pp->dmstate = PSTATE_UNDEF; pp->state = PATH_UNCHECKED; pp->uid_attribute = NULL; pp->checker_timeout = 0; pp->pending_ticks = 0; if (checker_selected(&pp->checker)) checker_put(&pp->checker); if (prio_selected(&pp->prio)) prio_put(&pp->prio); if (pp->fd >= 0) { close(pp->fd); pp->fd = -1; } } void free_path (struct path * pp) { if (!pp) return; uninitialize_path(pp); if (pp->udev) { udev_device_unref(pp->udev); pp->udev = NULL; } if (pp->vpd_data) free(pp->vpd_data); vector_free(pp->hwe); free(pp); } void free_pathvec (vector vec, enum free_path_mode free_paths) { int i; struct path * pp; if (!vec) return; if (free_paths == FREE_PATHS) vector_foreach_slot(vec, pp, i) free_path(pp); vector_free(vec); } struct pathgroup * alloc_pathgroup (void) { struct pathgroup * pgp; pgp = (struct pathgroup *)calloc(1, sizeof(struct pathgroup)); if (!pgp) return NULL; pgp->paths = vector_alloc(); if (!pgp->paths) { free(pgp); return NULL; } dm_pathgroup_to_gen(pgp)->ops = &dm_gen_pathgroup_ops; return pgp; } void free_pathgroup (struct pathgroup * pgp, enum free_path_mode free_paths) { if (!pgp) return; free_pathvec(pgp->paths, free_paths); free(pgp); } void free_pgvec (vector pgvec, enum free_path_mode free_paths) { int i; struct pathgroup * pgp; if (!pgvec) return; vector_foreach_slot(pgvec, pgp, i) { /* paths are going to be re-grouped, reset pgindex */ if (free_paths != FREE_PATHS) { struct path *pp; int j; vector_foreach_slot(pgp->paths, pp, j) pp->pgindex = 0; } free_pathgroup(pgp, free_paths); } vector_free(pgvec); } struct multipath * alloc_multipath (void) { struct multipath * mpp; mpp = (struct multipath *)calloc(1, sizeof(struct multipath)); if (mpp) { mpp->bestpg = 1; mpp->mpcontext = NULL; mpp->no_path_retry = NO_PATH_RETRY_UNDEF; dm_multipath_to_gen(mpp)->ops = &dm_gen_multipath_ops; } return mpp; } void *set_mpp_hwe(struct multipath *mpp, const struct path *pp) { if (!mpp || !pp || !pp->hwe) return NULL; if (mpp->hwe) return mpp->hwe; mpp->hwe = vector_convert(NULL, pp->hwe, struct hwentry, identity); return mpp->hwe; } void free_multipath_attributes(struct multipath *mpp) { if (!mpp) return; if (mpp->selector) { free(mpp->selector); mpp->selector = NULL; } if (mpp->features) { free(mpp->features); mpp->features = NULL; } if (mpp->hwhandler) { free(mpp->hwhandler); mpp->hwhandler = NULL; } } void free_multipath (struct multipath * mpp, enum free_path_mode free_paths) { if (!mpp) return; free_multipath_attributes(mpp); if (mpp->alias) { free(mpp->alias); mpp->alias = NULL; } if (!free_paths && mpp->pg) { struct pathgroup *pgp; struct path *pp; int i, j; /* * Make sure paths carry no reference to this mpp any more */ vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot(pgp->paths, pp, j) if (pp->mpp == mpp) pp->mpp = NULL; } } free_pathvec(mpp->paths, free_paths); free_pgvec(mpp->pg, free_paths); if (mpp->hwe) { vector_free(mpp->hwe); mpp->hwe = NULL; } free(mpp->mpcontext); free(mpp); } void cleanup_multipath(struct multipath **pmpp) { if (*pmpp) free_multipath(*pmpp, KEEP_PATHS); } void cleanup_multipath_and_paths(struct multipath **pmpp) { if (*pmpp) free_multipath(*pmpp, FREE_PATHS); } void drop_multipath (vector mpvec, char * wwid, enum free_path_mode free_paths) { int i; struct multipath * mpp; if (!mpvec) return; vector_foreach_slot (mpvec, mpp, i) { if (!strncmp(mpp->wwid, wwid, WWID_SIZE)) { free_multipath(mpp, free_paths); vector_del_slot(mpvec, i); return; } } } void free_multipathvec (vector mpvec, enum free_path_mode free_paths) { int i; struct multipath * mpp; if (!mpvec) return; vector_foreach_slot (mpvec, mpp, i) free_multipath(mpp, free_paths); vector_free(mpvec); } int store_path (vector pathvec, struct path * pp) { int err = 0; if (!strlen(pp->dev_t)) { condlog(2, "%s: Empty device number", pp->dev); err++; } if (!strlen(pp->dev)) { condlog(3, "%s: Empty device name", pp->dev_t); err++; } if (err > 1) return 1; if (!vector_alloc_slot(pathvec)) return 1; vector_set_slot(pathvec, pp); return 0; } int add_pathgroup(struct multipath *mpp, struct pathgroup *pgp) { if (!vector_alloc_slot(mpp->pg)) return 1; vector_set_slot(mpp->pg, pgp); pgp->mpp = mpp; return 0; } int store_hostgroup(vector hostgroupvec, struct host_group * hgp) { if (!vector_alloc_slot(hostgroupvec)) return 1; vector_set_slot(hostgroupvec, hgp); return 0; } int store_adaptergroup(vector adapters, struct adapter_group * agp) { if (!vector_alloc_slot(adapters)) return 1; vector_set_slot(adapters, agp); return 0; } struct multipath * find_mp_by_minor (const struct vector_s *mpvec, unsigned int minor) { int i; struct multipath * mpp; if (!mpvec) return NULL; vector_foreach_slot (mpvec, mpp, i) { if (!has_dm_info(mpp)) continue; if (mpp->dmi.minor == minor) return mpp; } return NULL; } struct multipath * find_mp_by_wwid (const struct vector_s *mpvec, const char * wwid) { int i; struct multipath * mpp; if (!mpvec || strlen(wwid) >= WWID_SIZE) return NULL; vector_foreach_slot (mpvec, mpp, i) if (!strncmp(mpp->wwid, wwid, WWID_SIZE)) return mpp; return NULL; } struct multipath * find_mp_by_alias (const struct vector_s *mpvec, const char * alias) { int i; size_t len; struct multipath * mpp; if (!mpvec) return NULL; len = strlen(alias); if (!len) return NULL; vector_foreach_slot (mpvec, mpp, i) { if (strlen(mpp->alias) == len && !strncmp(mpp->alias, alias, len)) return mpp; } return NULL; } struct multipath * find_mp_by_str (const struct vector_s *mpvec, const char * str) { int minor; char dummy; struct multipath *mpp = NULL; if (sscanf(str, "dm-%d%c", &minor, &dummy) == 1) mpp = find_mp_by_minor(mpvec, minor); if (!mpp) mpp = find_mp_by_alias(mpvec, str); if (!mpp) mpp = find_mp_by_wwid(mpvec, str); if (!mpp) condlog(2, "%s: invalid map name.", str); return mpp; } struct path * find_path_by_dev (const struct vector_s *pathvec, const char *dev) { int i; struct path * pp; if (!pathvec || !dev) return NULL; vector_foreach_slot (pathvec, pp, i) if (!strcmp(pp->dev, dev)) return pp; condlog(4, "%s: dev not found in pathvec", dev); return NULL; } struct path * find_path_by_devt (const struct vector_s *pathvec, const char * dev_t) { int i; struct path * pp; if (!pathvec) return NULL; vector_foreach_slot (pathvec, pp, i) if (!strcmp(pp->dev_t, dev_t)) return pp; condlog(4, "%s: dev_t not found in pathvec", dev_t); return NULL; } struct path *mp_find_path_by_devt(const struct multipath *mpp, const char *devt) { struct path *pp; struct pathgroup *pgp; unsigned int i, j; pp = find_path_by_devt(mpp->paths, devt); if (pp) return pp; vector_foreach_slot (mpp->pg, pgp, i){ vector_foreach_slot (pgp->paths, pp, j){ if (!strcmp(pp->dev_t, devt)) return pp; } } return NULL; } static int do_pathcount(const struct multipath *mpp, const int *states, unsigned int nr_states) { struct pathgroup *pgp; struct path *pp; int count = 0; unsigned int i, j, k; if (!mpp->pg || !nr_states) return count; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { for (k = 0; k < nr_states; k++) { if (pp->state == states[k]) { count++; break; } } } } return count; } int pathcount(const struct multipath *mpp, int state) { return do_pathcount(mpp, &state, 1); } int count_active_paths(const struct multipath *mpp) { struct pathgroup *pgp; struct path *pp; int count = 0; int i, j; if (!mpp->pg) return 0; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (pp->state == PATH_UP || pp->state == PATH_GHOST) count++; } } return count; } int count_active_pending_paths(const struct multipath *mpp) { int states[] = {PATH_UP, PATH_GHOST, PATH_PENDING}; return do_pathcount(mpp, states, 3); } int pathcmp(const struct pathgroup *pgp, const struct pathgroup *cpgp) { int i, j; struct path *pp, *cpp; int pnum = 0, found = 0; vector_foreach_slot(pgp->paths, pp, i) { pnum++; vector_foreach_slot(cpgp->paths, cpp, j) { if ((long)pp == (long)cpp) { found++; break; } } } return pnum - found; } struct path * first_path (const struct multipath * mpp) { struct pathgroup * pgp; if (!mpp->pg) return NULL; pgp = VECTOR_SLOT(mpp->pg, 0); return pgp?VECTOR_SLOT(pgp->paths, 0):NULL; } int add_feature(char **features_p, const char *new_feat) { int count = 0, new_count, len; char *tmp, *feats; const char *ptr; if (!features_p) return 1; /* Nothing to do */ if (!new_feat || *new_feat == '\0') return 0; len = strlen(new_feat); if (isspace(*new_feat) || isspace(*(new_feat + len - 1))) { condlog(0, "internal error: feature \"%s\" has leading or trailing spaces", new_feat); return 1; } ptr = new_feat; new_count = 1; while (*ptr != '\0') { if (isspace(*ptr) && !isspace(*(ptr + 1)) && *(ptr + 1) != '\0') new_count++; ptr++; } /* default feature is null */ if(!*features_p) { len = asprintf(&feats, "%0d %s", new_count, new_feat); if(len == -1) return 1; *features_p = feats; return 0; } /* Check if feature is already present */ tmp = *features_p; while ((tmp = strstr(tmp, new_feat)) != NULL) { if (isspace(*(tmp - 1)) && (isspace(*(tmp + len)) || *(tmp + len) == '\0')) return 0; tmp += len; } /* Get feature count */ count = strtoul(*features_p, &tmp, 10); if (*features_p == tmp || (!isspace(*tmp) && *tmp != '\0')) { condlog(0, "parse error in feature string \"%s\"", *features_p); return 1; } count += new_count; if (asprintf(&feats, "%0d%s %s", count, tmp, new_feat) < 0) return 1; free(*features_p); *features_p = feats; return 0; } int remove_feature(char **features_p, const char *old_feat) { int count = 0, len; char *feats_start, *ptr, *new; if (!features_p || !*features_p) return 1; /* Nothing to do */ if (!old_feat || *old_feat == '\0') return 0; len = strlen(old_feat); if (isspace(*old_feat) || isspace(*(old_feat + len - 1))) { condlog(0, "internal error: feature \"%s\" has leading or trailing spaces", old_feat); return 1; } /* Check if present and not part of a larger feature token*/ ptr = *features_p + 1; while ((ptr = strstr(ptr, old_feat)) != NULL) { if (isspace(*(ptr - 1)) && (isspace(*(ptr + len)) || *(ptr + len) == '\0')) break; ptr += len; } if (!ptr) return 0; /* Get feature count */ count = strtoul(*features_p, &feats_start, 10); if (*features_p == feats_start || !isspace(*feats_start)) { condlog(0, "parse error in feature string \"%s\"", *features_p); return 1; } /* Update feature count */ count--; while (*old_feat != '\0') { if (isspace(*old_feat) && !isspace(*(old_feat + 1)) && *(old_feat + 1) != '\0') count--; old_feat++; } /* Quick exit if all features have been removed */ if (count == 0) { new = malloc(2); if (!new) return 1; strcpy(new, "0"); goto out; } /* Update feature count space */ new = malloc(strlen(*features_p) - len + 1); if (!new) return 1; /* Copy the feature count */ sprintf(new, "%0d", count); /* * Copy existing features up to the feature * about to be removed */ strncat(new, feats_start, (size_t)(ptr - feats_start)); /* Skip feature to be removed */ ptr += len; /* Copy remaining features */ while (isspace(*ptr)) ptr++; if (*ptr != '\0') strcat(new, ptr); else strchop(new); out: free(*features_p); *features_p = new; return 0; } unsigned int bus_protocol_id(const struct path *pp) { if (!pp || pp->bus < 0 || pp->bus > SYSFS_BUS_NVME) return SYSFS_BUS_UNDEF; if (pp->bus != SYSFS_BUS_SCSI && pp->bus != SYSFS_BUS_NVME) return pp->bus; if (pp->sg_id.proto_id < 0) return SYSFS_BUS_UNDEF; if (pp->bus == SYSFS_BUS_SCSI && pp->sg_id.proto_id > SCSI_PROTOCOL_UNSPEC) return SYSFS_BUS_UNDEF; if (pp->bus == SYSFS_BUS_NVME && pp->sg_id.proto_id > NVME_PROTOCOL_UNSPEC) return SYSFS_BUS_UNDEF; return pp->bus + pp->sg_id.proto_id; } multipath-tools-0.11.1/libmultipath/structs.h000066400000000000000000000324711475246302400213330ustar00rootroot00000000000000#ifndef STRUCTS_H_INCLUDED #define STRUCTS_H_INCLUDED #include #include #include #include #include "prio.h" #include "byteorder.h" #include "generic.h" #define WWID_SIZE 128 #define SERIAL_SIZE 128 #define NODE_NAME_SIZE 224 #define PATH_STR_SIZE 16 #define FILE_NAME_SIZE 256 #define CALLOUT_MAX_SIZE 256 #define BLK_DEV_SIZE 33 #define NAME_SIZE 512 #define HOST_NAME_LEN 16 #define SLOT_NAME_SIZE 40 #define PRKEY_SIZE 19 #define VPD_DATA_SIZE 128 #define SCSI_VENDOR_SIZE 9 #define SCSI_PRODUCT_SIZE 17 #define SCSI_STATE_SIZE 19 #define NVME_MODEL_SIZE 41 #define NVME_REV_SIZE 9 /* This must be the maximum of SCSI and NVME sizes */ #define PATH_PRODUCT_SIZE NVME_MODEL_SIZE #define PATH_REV_SIZE NVME_REV_SIZE #define NO_PATH_RETRY_UNDEF 0 #define NO_PATH_RETRY_FAIL -1 #define NO_PATH_RETRY_QUEUE -2 enum free_path_mode { KEEP_PATHS, FREE_PATHS }; enum rr_weight_mode { RR_WEIGHT_UNDEF, RR_WEIGHT_NONE, RR_WEIGHT_PRIO }; enum failback_mode { FAILBACK_UNDEF, FAILBACK_MANUAL, FAILBACK_IMMEDIATE, FAILBACK_FOLLOWOVER }; enum pathstates { PSTATE_UNDEF, PSTATE_FAILED, PSTATE_ACTIVE }; enum pgstates { PGSTATE_UNDEF, PGSTATE_ENABLED, PGSTATE_DISABLED, PGSTATE_ACTIVE }; enum yes_no_states { YN_NO, YN_YES, }; enum queue_without_daemon_states { QUE_NO_DAEMON_OFF = YN_NO, QUE_NO_DAEMON_ON = YN_YES, QUE_NO_DAEMON_FORCE, }; enum attribute_bits { ATTR_UID, ATTR_GID, ATTR_MODE, }; enum yes_no_undef_states { YNU_UNDEF, YNU_NO, YNU_YES, }; enum find_multipaths_states { FIND_MULTIPATHS_UNDEF = YNU_UNDEF, FIND_MULTIPATHS_OFF = YNU_NO, FIND_MULTIPATHS_ON = YNU_YES, FIND_MULTIPATHS_GREEDY, FIND_MULTIPATHS_SMART, FIND_MULTIPATHS_STRICT, FIND_MULTIPATHS_LAST__, }; enum marginal_pathgroups_mode { MARGINAL_PATHGROUP_OFF = YN_NO, MARGINAL_PATHGROUP_ON = YN_YES, MARGINAL_PATHGROUP_FPIN, }; enum flush_states { FLUSH_UNDEF, FLUSH_NEVER, FLUSH_ALWAYS, FLUSH_UNUSED, }; enum log_checker_err_states { LOG_CHKR_ERR_ALWAYS, LOG_CHKR_ERR_ONCE, }; enum user_friendly_names_states { USER_FRIENDLY_NAMES_UNDEF = YNU_UNDEF, USER_FRIENDLY_NAMES_OFF = YNU_NO, USER_FRIENDLY_NAMES_ON = YNU_YES, }; enum retain_hwhandler_states { RETAIN_HWHANDLER_UNDEF = YNU_UNDEF, RETAIN_HWHANDLER_OFF = YNU_NO, RETAIN_HWHANDLER_ON = YNU_YES, }; enum detect_prio_states { DETECT_PRIO_UNDEF = YNU_UNDEF, DETECT_PRIO_OFF = YNU_NO, DETECT_PRIO_ON = YNU_YES, }; enum detect_checker_states { DETECT_CHECKER_UNDEF = YNU_UNDEF, DETECT_CHECKER_OFF = YNU_NO, DETECT_CHECKER_ON = YNU_YES, }; enum detect_pgpolicy_states { DETECT_PGPOLICY_UNDEF = YNU_UNDEF, DETECT_PGPOLICY_OFF = YNU_NO, DETECT_PGPOLICY_ON = YNU_YES, }; enum detect_pgpolicy_use_tpg_states { DETECT_PGPOLICY_USE_TPG_UNDEF = YNU_UNDEF, DETECT_PGPOLICY_USE_TPG_OFF = YNU_NO, DETECT_PGPOLICY_USE_TPG_ON = YNU_YES, }; enum deferred_remove_states { DEFERRED_REMOVE_UNDEF = YNU_UNDEF, DEFERRED_REMOVE_OFF = YNU_NO, DEFERRED_REMOVE_ON = YNU_YES, DEFERRED_REMOVE_IN_PROGRESS, }; enum skip_kpartx_states { SKIP_KPARTX_UNDEF = YNU_UNDEF, SKIP_KPARTX_OFF = YNU_NO, SKIP_KPARTX_ON = YNU_YES, }; enum max_sectors_kb_states { MAX_SECTORS_KB_UNDEF = 0, MAX_SECTORS_KB_MIN = 4, /* can't be smaller than page size */ }; enum queue_mode_states { QUEUE_MODE_UNDEF = 0, QUEUE_MODE_BIO, QUEUE_MODE_RQ, }; enum auto_resize_state { AUTO_RESIZE_NEVER, AUTO_RESIZE_GROW_ONLY, AUTO_RESIZE_GROW_SHRINK, }; #define PROTOCOL_UNSET -1 enum scsi_protocol { SCSI_PROTOCOL_FCP = 0, /* Fibre Channel */ SCSI_PROTOCOL_SPI = 1, /* parallel SCSI */ SCSI_PROTOCOL_SSA = 2, /* Serial Storage Architecture - Obsolete */ SCSI_PROTOCOL_SBP = 3, /* firewire */ SCSI_PROTOCOL_SRP = 4, /* Infiniband RDMA */ SCSI_PROTOCOL_ISCSI = 5, SCSI_PROTOCOL_SAS = 6, SCSI_PROTOCOL_ADT = 7, /* Media Changers */ SCSI_PROTOCOL_ATA = 8, SCSI_PROTOCOL_USB = 9, /* USB Attached SCSI (UAS), and others */ SCSI_PROTOCOL_UNSPEC = 0xa, /* No specific protocol */ SCSI_PROTOCOL_END = 0xb, /* offset of the next sysfs_buses entry */ }; /* values from /sys/class/nvme/nvmeX */ enum nvme_protocol { NVME_PROTOCOL_PCIE = 0, NVME_PROTOCOL_RDMA = 1, NVME_PROTOCOL_FC = 2, NVME_PROTOCOL_TCP = 3, NVME_PROTOCOL_LOOP = 4, NVME_PROTOCOL_APPLE_NVME = 5, NVME_PROTOCOL_UNSPEC = 6, /* unknown protocol */ }; enum sysfs_buses { SYSFS_BUS_UNDEF, SYSFS_BUS_CCW, SYSFS_BUS_CCISS, SYSFS_BUS_SCSI, SYSFS_BUS_NVME = SYSFS_BUS_SCSI + SCSI_PROTOCOL_END, }; /* * Linear ordering of bus/protocol */ #define LAST_BUS_PROTOCOL_ID (SYSFS_BUS_NVME + NVME_PROTOCOL_UNSPEC) unsigned int bus_protocol_id(const struct path *pp); extern const char * const protocol_name[]; #define SCSI_INVALID_LUN ~0ULL enum no_undef_states { NU_NO = -1, NU_UNDEF = 0, }; enum ghost_delay_states { GHOST_DELAY_OFF = NU_NO, GHOST_DELAY_UNDEF = NU_UNDEF, }; enum initialized_states { INIT_NEW, INIT_FAILED, INIT_MISSING_UDEV, INIT_REQUESTED_UDEV, INIT_OK, /* * INIT_REMOVED: supposed to be removed from pathvec, but still * mapped by some multipath map because of map reload failure. */ INIT_REMOVED, /* * INIT_PARTIAL: paths added by update_pathvec_from_dm() will not * be fully initialized. This will be handled when an add or * change uevent is received. */ INIT_PARTIAL, INIT_LAST__, }; enum prkey_sources { PRKEY_SOURCE_NONE, PRKEY_SOURCE_CONF, PRKEY_SOURCE_FILE, }; enum all_tg_pt_states { ALL_TG_PT_UNDEF = YNU_UNDEF, ALL_TG_PT_OFF = YNU_NO, ALL_TG_PT_ON = YNU_YES, }; enum vpd_vendor_ids { VPD_VP_UNDEF, VPD_VP_HP3PAR, VPD_VP_ARRAY_SIZE, /* This must remain the last entry */ }; /* * Multipath treats 0 as undefined for optional config parameters. * Use this for cases where 0 is a valid option for systems multipath * is communicating with */ enum undefined_off_zero { UOZ_UNDEF = 0, UOZ_OFF = -1, UOZ_ZERO = -2, }; enum fast_io_fail_states { MP_FAST_IO_FAIL_UNSET = UOZ_UNDEF, MP_FAST_IO_FAIL_OFF = UOZ_OFF, MP_FAST_IO_FAIL_ZERO = UOZ_ZERO, }; enum eh_deadline_states { EH_DEADLINE_UNSET = UOZ_UNDEF, EH_DEADLINE_OFF = UOZ_OFF, EH_DEADLINE_ZERO = UOZ_ZERO, }; enum max_retries_states { MAX_RETRIES_UNSET = UOZ_UNDEF, MAX_RETRIES_OFF = UOZ_OFF, MAX_RETRIES_ZERO = UOZ_ZERO, }; enum recheck_wwid_states { RECHECK_WWID_UNDEF = YNU_UNDEF, RECHECK_WWID_OFF = YNU_NO, RECHECK_WWID_ON = YNU_YES, }; enum check_path_states { CHECK_PATH_UNCHECKED, CHECK_PATH_STARTED, CHECK_PATH_CHECKED, CHECK_PATH_NEW_UP, CHECK_PATH_SKIPPED, CHECK_PATH_REMOVED, }; struct vpd_vendor_page { int pg; const char *name; }; extern struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE]; struct sg_id { int host_no; int channel; int scsi_id; uint64_t lun; short h_cmd_per_lun; short d_queue_depth; int proto_id; int transport_id; }; # ifndef HDIO_GETGEO # define HDIO_GETGEO 0x0301 /* get device geometry */ struct hd_geometry { unsigned char heads; unsigned char sectors; unsigned short cylinders; unsigned long start; }; #endif #define GROUP_ID_UNDEF -1 struct path { char dev[FILE_NAME_SIZE]; char dev_t[BLK_DEV_SIZE]; struct udev_device *udev; struct sg_id sg_id; struct hd_geometry geom; char wwid[WWID_SIZE]; char vendor_id[SCSI_VENDOR_SIZE]; char product_id[PATH_PRODUCT_SIZE]; char rev[PATH_REV_SIZE]; char serial[SERIAL_SIZE]; char tgt_node_name[NODE_NAME_SIZE]; char *vpd_data; unsigned long long size; unsigned int checkint; unsigned int tick; unsigned int pending_ticks; int bus; int sysfs_state; int state; int dmstate; int chkrstate; int oldstate; int failcount; int priority; int pgindex; int detect_prio; int detect_checker; int tpgs; const char *uid_attribute; struct prio prio; struct checker checker; struct multipath * mpp; int fd; int initialized; int retriggers; int partial_retrigger_delay; unsigned int path_failures; time_t dis_reinstate_time; int disable_reinstate; int san_path_err_forget_rate; time_t io_err_dis_reinstate_time; int io_err_disable_reinstate; int io_err_pathfail_cnt; int io_err_pathfail_starttime; int find_multipaths_timeout; int marginal; int vpd_vendor_id; int recheck_wwid; int fast_io_fail; unsigned int dev_loss; int eh_deadline; enum check_path_states is_checked; bool can_use_env_uid; unsigned int checker_timeout; /* configlet pointers */ vector hwe; struct gen_path generic_path; int tpg_id; }; typedef int (pgpolicyfn) (struct multipath *, vector); enum prflag_value { PRFLAG_UNKNOWN, PRFLAG_UNSET, PRFLAG_SET, }; enum prio_update_type { PRIO_UPDATE_NONE, PRIO_UPDATE_NORMAL, PRIO_UPDATE_NEW_PATH, PRIO_UPDATE_MARGINAL, }; struct multipath { char wwid[WWID_SIZE]; char alias_old[WWID_SIZE]; int detect_pgpolicy; int detect_pgpolicy_use_tpg; int pgpolicy; pgpolicyfn *pgpolicyfn; int nextpg; int bestpg; int queuedio; int action; int wait_for_udev; 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; int minio; int flush_on_last_del; int attribute_flags; int retain_hwhandler; int deferred_remove; bool in_recovery; bool need_reload; int san_path_err_threshold; int san_path_err_forget_rate; int san_path_err_recovery_time; int marginal_path_err_sample_time; int marginal_path_err_rate_threshold; int marginal_path_err_recheck_gap_time; int marginal_path_double_failed_time; int skip_kpartx; int max_sectors_kb; int force_readonly; int force_udev_reload; int needs_paths_uevent; int ghost_delay; int ghost_delay_tick; int queue_mode; unsigned int sync_tick; int synced_count; enum prio_update_type prio_update; uid_t uid; gid_t gid; mode_t mode; unsigned long long size; vector paths; vector pg; struct dm_info dmi; /* configlet pointers */ char * alias; char * alias_prefix; char * selector; char * features; char * hwhandler; struct mpentry * mpe; vector hwe; /* threads */ pthread_t waiter; /* stats */ unsigned int stat_switchgroup; unsigned int stat_path_failures; unsigned int stat_map_loads; unsigned int stat_total_queueing_time; unsigned int stat_queueing_timeouts; unsigned int stat_map_failures; /* checkers shared data */ void * mpcontext; /* persistent management data*/ int prkey_source; struct be64 reservation_key; uint8_t sa_flags; int prflag; int all_tg_pt; struct gen_multipath generic_mp; bool fpin_must_reload; }; static inline int marginal_path_check_enabled(const struct multipath *mpp) { return mpp->marginal_path_double_failed_time > 0 && mpp->marginal_path_err_sample_time > 0 && mpp->marginal_path_err_recheck_gap_time > 0 && mpp->marginal_path_err_rate_threshold >= 0; } static inline int san_path_check_enabled(const struct multipath *mpp) { return mpp->san_path_err_threshold > 0 && mpp->san_path_err_forget_rate > 0 && mpp->san_path_err_recovery_time > 0; } struct pathgroup { long id; int status; int priority; int enabled_paths; int marginal; vector paths; struct multipath *mpp; struct gen_pathgroup generic_pg; }; struct adapter_group { char adapter_name[SLOT_NAME_SIZE]; struct pathgroup *pgp; int num_hosts; vector host_groups; int next_host_index; }; struct host_group { int host_no; int num_paths; vector paths; }; struct path * alloc_path (void); struct pathgroup * alloc_pathgroup (void); struct multipath * alloc_multipath (void); void *set_mpp_hwe(struct multipath *mpp, const struct path *pp); void uninitialize_path(struct path *pp); void free_path (struct path *); void free_pathvec (vector vec, enum free_path_mode free_paths); void free_pathgroup (struct pathgroup * pgp, enum free_path_mode free_paths); void free_pgvec (vector pgvec, enum free_path_mode free_paths); void free_multipath (struct multipath *, enum free_path_mode free_paths); void cleanup_multipath(struct multipath **pmpp); void cleanup_multipath_and_paths(struct multipath **pmpp); void free_multipath_attributes (struct multipath *); void drop_multipath (vector mpvec, char * wwid, enum free_path_mode free_paths); void free_multipathvec (vector mpvec, enum free_path_mode free_paths); struct adapter_group * alloc_adaptergroup(void); struct host_group * alloc_hostgroup(void); void free_adaptergroup(vector adapters); void free_hostgroup(vector hostgroups); int store_adaptergroup(vector adapters, struct adapter_group *agp); int store_hostgroup(vector hostgroupvec, struct host_group *hgp); int store_path (vector pathvec, struct path * pp); int add_pathgroup(struct multipath*, struct pathgroup *); struct multipath * find_mp_by_alias (const struct vector_s *mp, const char *alias); struct multipath * find_mp_by_wwid (const struct vector_s *mp, const char *wwid); struct multipath * find_mp_by_str (const struct vector_s *mp, const char *wwid); struct multipath * find_mp_by_minor (const struct vector_s *mp, unsigned int minor); struct path * find_path_by_devt (const struct vector_s *pathvec, const char *devt); struct path * find_path_by_dev (const struct vector_s *pathvec, const char *dev); struct path * first_path (const struct multipath *mpp); struct path *mp_find_path_by_devt(const struct multipath *mpp, const char *devt); int pathcount (const struct multipath *, int); int count_active_paths(const struct multipath *); int count_active_pending_paths(const struct multipath *); int pathcmp (const struct pathgroup *, const struct pathgroup *); int add_feature (char **, const char *); int remove_feature (char **, const char *); #endif /* STRUCTS_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/structs_vec.c000066400000000000000000000554421475246302400221660ustar00rootroot00000000000000#include #include #include #include #include "util.h" #include "checkers.h" #include "vector.h" #include "defaults.h" #include "debug.h" #include "config.h" #include "structs.h" #include "structs_vec.h" #include "sysfs.h" #include "devmapper.h" #include "dmparser.h" #include "propsel.h" #include "discovery.h" #include "prio.h" #include "configure.h" #include "libdevmapper.h" #include "io_err_stat.h" #include "switchgroup.h" /* * creates or updates mpp->paths reading mpp->pg */ int update_mpp_paths(struct multipath *mpp, vector pathvec) { struct pathgroup * pgp; struct path * pp; int i,j; bool store_failure = false; if (!mpp || !mpp->pg) return 0; if (!mpp->paths && !(mpp->paths = vector_alloc())) return 1; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (!find_path_by_devt(mpp->paths, pp->dev_t)) { struct path *pp1; /* * Avoid adding removed paths to the map again * when we reload it. Such paths may exist in * ev_remove_paths() or if it returns failure. */ pp1 = find_path_by_devt(pathvec, pp->dev_t); if (pp1 && pp->initialized != INIT_REMOVED && store_path(mpp->paths, pp)) store_failure = true; } } } return store_failure; } static bool guess_mpp_wwid(struct multipath *mpp) { int i, j; struct pathgroup *pgp; struct path *pp; if (strlen(mpp->wwid) || !mpp->pg) return true; vector_foreach_slot(mpp->pg, pgp, i) { if (!pgp->paths) continue; vector_foreach_slot(pgp->paths, pp, j) { if (pp->initialized == INIT_OK && strlen(pp->wwid)) { strlcpy(mpp->wwid, pp->wwid, sizeof(mpp->wwid)); condlog(2, "%s: guessed WWID %s from path %s", mpp->alias, mpp->wwid, pp->dev); return true; } } } condlog(1, "%s: unable to guess WWID", mpp->alias); return false; } /* * update_pathvec_from_dm() - update pathvec after disassemble_map() * * disassemble_map() may return block devices that are members in * multipath maps but haven't been discovered. Check whether they * need to be added to pathvec or discarded. * * Returns: true if immediate map reload is desirable * * Side effects: * - may delete non-existing paths and empty pathgroups from mpp * - may set pp->wwid and / or mpp->wwid * - calls pathinfo() on existing paths is pathinfo_flags is not 0 */ static bool update_pathvec_from_dm(vector pathvec, struct multipath *mpp, int pathinfo_flags) { int i, j; struct pathgroup *pgp; struct path *pp; struct config *conf; bool mpp_has_wwid; bool must_reload = false; bool pg_deleted = false; if (!mpp->pg) return false; /* * This will initialize mpp->wwid with an educated guess, * either from the dm uuid or from a member path with properly * determined WWID. */ mpp_has_wwid = guess_mpp_wwid(mpp); vector_foreach_slot(mpp->pg, pgp, i) { if (!pgp->paths) goto delete_pg; vector_foreach_slot(pgp->paths, pp, j) { /* A pathgroup has been deleted before. Invalidate pgindex */ if (pg_deleted) pp->pgindex = 0; if (pp->mpp && pp->mpp != mpp) { condlog(0, "BUG: %s: found path %s which is already in %s", mpp->alias, pp->dev, pp->mpp->alias); /* * Either we added this path to the other mpp * explicitly, or we came by here earlier and * decided it belonged there. In both cases, * the path should remain in the other map, * and be deleted here. */ must_reload = true; dm_fail_path(mpp->alias, pp->dev_t); vector_del_slot(pgp->paths, j--); /* * pp->pgindex has been set in disassemble_map(), * which has probably been called just before for * mpp. So he pgindex relates to mpp and may be * wrong for pp->mpp. Invalidate it. */ pp->pgindex = 0; continue; } pp->mpp = mpp; /* * The way disassemble_map() works: If it encounters a * path device which isn't found in pathvec, it adds an * uninitialized struct path to pgp->paths, with only * pp->dev_t filled in. Thus if pp->udev is set here, * we know that the path is in pathvec already. */ if (pp->udev) { if (pathinfo_flags & ~DI_NOIO) { conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); if (pathinfo(pp, conf, pathinfo_flags) != PATHINFO_OK) condlog(2, "%s: pathinfo failed for existing path %s (flags=0x%x)", __func__, pp->dev, pathinfo_flags); pthread_cleanup_pop(1); } } else { /* If this fails, the device is not in sysfs */ pp->udev = get_udev_device(pp->dev_t, DEV_DEVT); if (!pp->udev) { condlog(2, "%s: discarding non-existing path %s", mpp->alias, pp->dev_t); vector_del_slot(pgp->paths, j--); free_path(pp); must_reload = true; continue; } else { int rc; strlcpy(pp->dev, udev_device_get_sysname(pp->udev), sizeof(pp->dev)); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); pp->checkint = conf->checkint; rc = pathinfo(pp, conf, DI_SYSFS|DI_WWID|DI_BLACKLIST|DI_NOFALLBACK|pathinfo_flags); pthread_cleanup_pop(1); if (rc != PATHINFO_OK) { condlog(1, "%s: error %d in pathinfo, discarding path", pp->dev, rc); vector_del_slot(pgp->paths, j--); free_path(pp); must_reload = true; continue; } condlog(2, "%s: adding new path %s", mpp->alias, pp->dev); pp->initialized = INIT_PARTIAL; pp->partial_retrigger_delay = 180; store_path(pathvec, pp); pp->tick = 1; } } /* We don't set the map WWID from paths here */ if (!mpp_has_wwid) continue; /* * At this point, pp->udev is valid and pp->wwid * is the best we could get */ if (*pp->wwid && strcmp(mpp->wwid, pp->wwid)) { condlog(0, "%s: path %s WWID %s doesn't match, removing from map", mpp->wwid, pp->dev_t, pp->wwid); /* * This path exists, but in the wrong map. * We can't reload the map from here. * Make sure it isn't used in this map * anymore, and let the checker re-add * it as it sees fit. */ dm_fail_path(mpp->alias, pp->dev_t); vector_del_slot(pgp->paths, j--); orphan_path(pp, "WWID mismatch"); pp->tick = 1; must_reload = true; } else if (!*pp->wwid) { condlog(3, "%s: setting wwid from map: %s", pp->dev, mpp->wwid); strlcpy(pp->wwid, mpp->wwid, sizeof(pp->wwid)); } } if (VECTOR_SIZE(pgp->paths) != 0) continue; delete_pg: condlog(2, "%s: removing empty pathgroup %d", mpp->alias, i); vector_del_slot(mpp->pg, i--); free_pathgroup(pgp, KEEP_PATHS); must_reload = true; /* Invalidate pgindex for all other pathgroups */ pg_deleted = true; } mpp->need_reload = mpp->need_reload || must_reload; return must_reload; } static bool set_path_max_sectors_kb(const struct path *pp, int max_sectors_kb) { char buf[11]; ssize_t len; int ret, current; bool rc = false; if (max_sectors_kb == MAX_SECTORS_KB_UNDEF) return rc; if (sysfs_attr_get_value(pp->udev, "queue/max_sectors_kb", buf, sizeof(buf)) <= 0 || sscanf(buf, "%d\n", ¤t) != 1) current = MAX_SECTORS_KB_UNDEF; if (current == max_sectors_kb) return rc; len = snprintf(buf, sizeof(buf), "%d", max_sectors_kb); ret = sysfs_attr_set_value(pp->udev, "queue/max_sectors_kb", buf, len); if (ret != len) log_sysfs_attr_set_value(3, ret, "failed setting max_sectors_kb on %s", pp->dev); else { condlog(3, "%s: set max_sectors_kb to %d for %s", __func__, max_sectors_kb, pp->dev); rc = true; } return rc; } int adopt_paths(vector pathvec, struct multipath *mpp, const struct multipath *current_mpp) { int i, ret; struct path * pp; struct config *conf; if (!mpp) return 0; if (update_mpp_paths(mpp, pathvec)) return 1; vector_foreach_slot (pathvec, pp, i) { if (!strncmp(mpp->wwid, pp->wwid, WWID_SIZE)) { if (pp->size != 0 && mpp->size != 0 && pp->size != mpp->size) { condlog(3, "%s: size mismatch for %s, not adding path", pp->dev, mpp->alias); continue; } if (pp->initialized == INIT_REMOVED) continue; if (mpp->queue_mode == QUEUE_MODE_RQ && pp->bus == SYSFS_BUS_NVME && pp->sg_id.proto_id == NVME_PROTOCOL_TCP) { condlog(2, "%s: multipath device %s created with request queue_mode. Unable to add nvme:tcp paths", pp->dev, mpp->alias); continue; } if (!mpp->paths && !(mpp->paths = vector_alloc())) goto err; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = pathinfo(pp, conf, DI_PRIO | DI_CHECKER); pthread_cleanup_pop(1); if (ret) { condlog(3, "%s: pathinfo failed for %s", __func__, pp->dev); continue; } if (!find_path_by_devt(mpp->paths, pp->dev_t)) { if (store_path(mpp->paths, pp)) goto err; /* * Setting max_sectors_kb on live paths is dangerous. * But we can do it here on a path that isn't yet part * of the map. If this value is lower than the current * max_sectors_kb and the map is reloaded, the map's * max_sectors_kb will be safely adjusted by the kernel. * * We must make sure that the path is not part of the * map yet. Normally we can check this in mpp->paths. * But if adopt_paths is called from coalesce_paths, * we need to check the separate struct multipath that * has been obtained from map_discovery(). */ if (!current_mpp || !mp_find_path_by_devt(current_mpp, pp->dev_t)) mpp->need_reload = set_path_max_sectors_kb(pp, mpp->max_sectors_kb) || mpp->need_reload; } pp->mpp = mpp; condlog(3, "%s: ownership set to %s", pp->dev, mpp->alias); } } return 0; err: condlog(1, "error setting ownership of %s to %s", pp->dev, mpp->alias); return 1; } void orphan_path(struct path *pp, const char *reason) { condlog(3, "%s: orphan path, %s", pp->dev, reason); pp->mpp = NULL; pp->pgindex = 0; uninitialize_path(pp); } static void orphan_paths(vector pathvec, struct multipath *mpp, const char *reason) { int i; struct path * pp; vector_foreach_slot (pathvec, pp, i) { if (pp->mpp == mpp) { if (pp->initialized == INIT_REMOVED || pp->initialized == INIT_PARTIAL) { condlog(3, "%s: freeing path in %s state", pp->dev, pp->initialized == INIT_REMOVED ? "removed" : "partial"); vector_del_slot(pathvec, i--); free_path(pp); } else orphan_path(pp, reason); } } } void set_path_removed(struct path *pp) { struct multipath *mpp = pp->mpp; orphan_path(pp, "removed"); /* * Keep link to mpp. It will be removed when the path * is successfully removed from the map. */ if (!mpp) condlog(0, "%s: internal error: mpp == NULL", pp->dev); pp->mpp = mpp; pp->initialized = INIT_REMOVED; } void remove_map_callback(struct multipath *mpp __attribute__((unused))) { } void remove_map(struct multipath *mpp, vector pathvec, vector mpvec) { int i; remove_map_callback(mpp); free_pathvec(mpp->paths, KEEP_PATHS); free_pgvec(mpp->pg, KEEP_PATHS); mpp->paths = mpp->pg = NULL; /* * clear references to this map */ orphan_paths(pathvec, mpp, "map removed internally"); if (mpvec && (i = find_slot(mpvec, (void *)mpp)) != -1) vector_del_slot(mpvec, i); /* * final free */ free_multipath(mpp, KEEP_PATHS); } void remove_map_by_alias(const char *alias, struct vectors * vecs) { struct multipath * mpp = find_mp_by_alias(vecs->mpvec, alias); if (mpp) { condlog(2, "%s: removing map by alias", alias); remove_map(mpp, vecs->pathvec, vecs->mpvec); } } void remove_maps(struct vectors * vecs) { int i; struct multipath * mpp; if (!vecs) return; vector_foreach_slot (vecs->mpvec, mpp, i) remove_map(mpp, vecs->pathvec, NULL); vector_free(vecs->mpvec); vecs->mpvec = NULL; } void extract_hwe_from_path(struct multipath * mpp) { struct path * pp = NULL; int i; if (mpp->hwe || !mpp->paths) return; condlog(4, "%s: searching paths for valid hwe", mpp->alias); /* doing this in two passes seems like paranoia to me */ vector_foreach_slot(mpp->paths, pp, i) { if (pp->state == PATH_UP && pp->initialized != INIT_PARTIAL && pp->initialized != INIT_REMOVED && pp->hwe) goto done; } vector_foreach_slot(mpp->paths, pp, i) { if ((pp->state != PATH_UP || pp->initialized == INIT_PARTIAL) && pp->initialized != INIT_REMOVED && pp->hwe) goto done; } done: if (i < VECTOR_SIZE(mpp->paths)) (void)set_mpp_hwe(mpp, pp); if (mpp->hwe) condlog(3, "%s: got hwe from path %s", mpp->alias, pp->dev); else condlog(2, "%s: no hwe found", mpp->alias); } int update_multipath_table__ (struct multipath *mpp, vector pathvec, int flags, const char *params, const char *status) { if (disassemble_map(pathvec, params, mpp)) { condlog(2, "%s: cannot disassemble map", mpp->alias); return DMP_ERR; } if (disassemble_status(status, mpp)) condlog(2, "%s: cannot disassemble status", mpp->alias); /* FIXME: we should deal with the return value here */ update_pathvec_from_dm(pathvec, mpp, flags); return DMP_OK; } int update_multipath_table (struct multipath *mpp, vector pathvec, int flags) { int r = DMP_ERR; char __attribute__((cleanup(cleanup_charp))) *params = NULL; char __attribute__((cleanup(cleanup_charp))) *status = NULL; /* only set the actual mpp->dmi if libmp_mapinfo returns DMP_OK */ struct dm_info dmi; unsigned long long size; struct config *conf; if (!mpp) return r; size = mpp->size; conf = get_multipath_config(); mpp->sync_tick = conf->max_checkint; put_multipath_config(conf); mpp->synced_count++; r = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = mpp->alias }, (mapinfo_t) { .target = ¶ms, .status = &status, .size = &mpp->size, .dmi = &dmi, }); if (r != DMP_OK) { condlog(2, "%s: %s", mpp->alias, dmp_errstr(r)); return r; } else if (size != mpp->size) condlog(0, "%s: size changed from %llu to %llu", mpp->alias, size, mpp->size); mpp->dmi = dmi; return update_multipath_table__(mpp, pathvec, flags, params, status); } static struct path *find_devt_in_pathgroups(const struct multipath *mpp, const char *dev_t) { struct pathgroup *pgp; struct path *pp; int j; vector_foreach_slot(mpp->pg, pgp, j) { pp = find_path_by_devt(pgp->paths, dev_t); if (pp) return pp; } return NULL; } static void check_removed_paths(const struct multipath *mpp, vector pathvec) { struct path *pp; int i; vector_foreach_slot(pathvec, pp, i) { if (pp->mpp == mpp && (pp->initialized == INIT_REMOVED || pp->initialized == INIT_PARTIAL) && !find_devt_in_pathgroups(mpp, pp->dev_t)) { condlog(2, "%s: %s: freeing path in %s state", __func__, pp->dev, pp->initialized == INIT_REMOVED ? "removed" : "partial"); vector_del_slot(pathvec, i--); free_path(pp); } } } void sync_paths(struct multipath *mpp, vector pathvec) { struct path *pp; struct pathgroup *pgp; int found, i, j; vector_foreach_slot (mpp->paths, pp, i) { found = 0; vector_foreach_slot(mpp->pg, pgp, j) { if (find_slot(pgp->paths, (void *)pp) != -1) { found = 1; break; } } if (!found) { condlog(3, "%s dropped path %s", mpp->alias, pp->dev); vector_del_slot(mpp->paths, i--); orphan_path(pp, "path removed externally"); } } check_removed_paths(mpp, pathvec); update_mpp_paths(mpp, pathvec); vector_foreach_slot (mpp->paths, pp, i) pp->mpp = mpp; } int update_multipath_strings(struct multipath *mpp, vector pathvec) { struct pathgroup *pgp; int i, r = DMP_ERR; if (!mpp) return r; update_mpp_paths(mpp, pathvec); condlog(4, "%s: %s", mpp->alias, __FUNCTION__); free_multipath_attributes(mpp); free_pgvec(mpp->pg, KEEP_PATHS); mpp->pg = NULL; r = update_multipath_table(mpp, pathvec, 0); if (r != DMP_OK) return r; sync_paths(mpp, pathvec); vector_foreach_slot(mpp->pg, pgp, i) if (pgp->paths) path_group_prio_update(pgp); return DMP_OK; } static void enter_recovery_mode(struct multipath *mpp) { unsigned int checkint; struct config *conf; if (mpp->in_recovery || mpp->no_path_retry <= 0) return; conf = get_multipath_config(); checkint = conf->checkint; put_multipath_config(conf); /* * Enter retry mode. * meaning of +1: retry_tick may be decremented in checkerloop before * starting retry. */ mpp->in_recovery = true; mpp->stat_queueing_timeouts++; mpp->retry_tick = mpp->no_path_retry * checkint + 1; condlog(1, "%s: Entering recovery mode: max_retries=%d", mpp->alias, mpp->no_path_retry); } static void leave_recovery_mode(struct multipath *mpp) { bool recovery = mpp->in_recovery; mpp->in_recovery = false; mpp->retry_tick = 0; /* * in_recovery is only ever set if mpp->no_path_retry > 0 * (see enter_recovery_mode()). But no_path_retry may have been * changed while the map was recovering, so test it here again. */ if (recovery && (mpp->no_path_retry == NO_PATH_RETRY_QUEUE || mpp->no_path_retry > 0)) { dm_queue_if_no_path(mpp, 1); condlog(2, "%s: queue_if_no_path enabled", mpp->alias); condlog(1, "%s: Recovered to normal mode", mpp->alias); } } void set_no_path_retry(struct multipath *mpp) { bool is_queueing = false; /* assign a value to make gcc happy */ if (mpp->features) is_queueing = strstr(mpp->features, "queue_if_no_path"); switch (mpp->no_path_retry) { case NO_PATH_RETRY_UNDEF: break; case NO_PATH_RETRY_FAIL: if (!mpp->features || is_queueing) dm_queue_if_no_path(mpp, 0); break; case NO_PATH_RETRY_QUEUE: if (!mpp->features || !is_queueing) dm_queue_if_no_path(mpp, 1); break; default: if (count_active_paths(mpp) > 0) { /* * If in_recovery is set, leave_recovery_mode() takes * care of dm_queue_if_no_path. Otherwise, do it here. */ if ((!mpp->features || !is_queueing) && !mpp->in_recovery) dm_queue_if_no_path(mpp, 1); leave_recovery_mode(mpp); } else { /* * If in_recovery is set, enter_recovery_mode does * nothing. If the device is already in recovery * mode and has already timed out, manually call * dm_queue_if_no_path to stop it from queueing. */ if ((!mpp->features || is_queueing) && mpp->in_recovery && mpp->retry_tick == 0) dm_queue_if_no_path(mpp, 0); if (pathcount(mpp, PATH_PENDING) == 0) enter_recovery_mode(mpp); } break; } } void sync_map_state(struct multipath *mpp) { struct pathgroup *pgp; struct path *pp; unsigned int i, j; if (!mpp->pg) return; vector_foreach_slot (mpp->pg, pgp, i){ vector_foreach_slot (pgp->paths, pp, j){ if (pp->initialized == INIT_REMOVED || pp->state == PATH_UNCHECKED || pp->state == PATH_WILD || pp->state == PATH_DELAYED) continue; if (mpp->ghost_delay_tick > 0) continue; if ((pp->dmstate == PSTATE_FAILED || pp->dmstate == PSTATE_UNDEF) && (pp->state == PATH_UP || pp->state == PATH_GHOST)) dm_reinstate_path(mpp->alias, pp->dev_t); else if ((pp->dmstate == PSTATE_ACTIVE || pp->dmstate == PSTATE_UNDEF) && (pp->state == PATH_DOWN || pp->state == PATH_SHAKY)) { condlog(2, "sync_map_state: failing %s state %d dmstate %d", pp->dev, pp->state, pp->dmstate); dm_fail_path(mpp->alias, pp->dev_t); } } } } static void find_existing_alias (struct multipath * mpp, struct vectors *vecs) { struct multipath * mp; int i; vector_foreach_slot (vecs->mpvec, mp, i) if (strncmp(mp->wwid, mpp->wwid, WWID_SIZE - 1) == 0) { strlcpy(mpp->alias_old, mp->alias, WWID_SIZE); return; } } struct multipath *add_map_with_path(struct vectors *vecs, struct path *pp, int add_vec, const struct multipath *current_mpp) { struct multipath * mpp; struct config *conf = NULL; if (!strlen(pp->wwid)) return NULL; if (!(mpp = alloc_multipath())) return NULL; conf = get_multipath_config(); mpp->mpe = find_mpe(conf->mptable, pp->wwid); put_multipath_config(conf); /* * We need to call this before select_alias(), * because that accesses hwe properties. */ if (pp->hwe && !set_mpp_hwe(mpp, pp)) goto out; strcpy(mpp->wwid, pp->wwid); find_existing_alias(mpp, vecs); if (select_alias(conf, mpp)) goto out; mpp->size = pp->size; if (adopt_paths(vecs->pathvec, mpp, current_mpp) || pp->mpp != mpp || find_slot(mpp->paths, pp) == -1) goto out; if (add_vec) { if (!vector_alloc_slot(vecs->mpvec)) goto out; vector_set_slot(vecs->mpvec, mpp); } return mpp; out: remove_map(mpp, vecs->pathvec, vecs->mpvec); return NULL; } int verify_paths(struct multipath *mpp) { struct path * pp; int count = 0; int i; if (!mpp) return 0; vector_foreach_slot (mpp->paths, pp, i) { /* * see if path is in sysfs */ if (!pp->udev || sysfs_attr_get_value(pp->udev, "dev", pp->dev_t, BLK_DEV_SIZE) < 0) { if (pp->state != PATH_DOWN) { condlog(1, "%s: removing valid path %s in state %d", mpp->alias, pp->dev, pp->state); } else { condlog(2, "%s: failed to access path %s", mpp->alias, pp->dev); } count++; vector_del_slot(mpp->paths, i); i--; /* * Don't delete path from pathvec yet. We'll do this * after the path has been removed from the map, in * sync_paths(). */ set_path_removed(pp); } else { condlog(4, "%s: verified path %s dev_t %s", mpp->alias, pp->dev, pp->dev_t); } } return count; } /* * mpp->no_path_retry: * -2 (QUEUE) : queue_if_no_path enabled, never turned off * -1 (FAIL) : fail_if_no_path * 0 (UNDEF) : nothing * >0 : queue_if_no_path enabled, turned off after polling n times * * Since this will only be called when fail_path(), update_multipath(), or * io_err_stat_handle_pathfail() are failing a previously active path, the * device cannot already be in recovery mode, so there will never be a need * to disable queueing here. */ void update_queue_mode_del_path(struct multipath *mpp) { int active = count_active_paths(mpp); bool is_queueing = mpp->features && strstr(mpp->features, "queue_if_no_path"); if (active == 0) { enter_recovery_mode(mpp); if (mpp->no_path_retry == NO_PATH_RETRY_FAIL || (mpp->no_path_retry == NO_PATH_RETRY_UNDEF && !is_queueing)) mpp->stat_map_failures++; } condlog(2, "%s: remaining active paths: %d", mpp->alias, active); } /* * Since this will only be called from check_path() -> reinstate_path() after * the queueing state has been updated in set_no_path_retry, this does not * need to worry about modifying the queueing state except when actually * leaving recovery mode. */ void update_queue_mode_add_path(struct multipath *mpp) { int active = count_active_paths(mpp); if (active > 0) leave_recovery_mode(mpp); condlog(2, "%s: remaining active paths: %d", mpp->alias, active); } vector get_used_hwes(const struct vector_s *pathvec) { int i, j; struct path *pp; struct hwentry *hwe; vector v = vector_alloc(); if (v == NULL) return NULL; vector_foreach_slot(pathvec, pp, i) { vector_foreach_slot_backwards(pp->hwe, hwe, j) { vector_find_or_add_slot(v, hwe); } } return v; } multipath-tools-0.11.1/libmultipath/structs_vec.h000066400000000000000000000027741475246302400221730ustar00rootroot00000000000000#ifndef STRUCTS_VEC_H_INCLUDED #define STRUCTS_VEC_H_INCLUDED #include "vector.h" #include "config.h" #include "lock.h" struct vectors { vector pathvec; vector mpvec; struct mutex_lock lock; /* defined in lock.h */ }; void set_no_path_retry(struct multipath *mpp); int adopt_paths (vector pathvec, struct multipath *mpp, const struct multipath *current_mpp); void orphan_path (struct path * pp, const char *reason); void set_path_removed(struct path *pp); int verify_paths(struct multipath *mpp); int update_mpp_paths(struct multipath * mpp, vector pathvec); int update_multipath_strings (struct multipath *mpp, vector pathvec); void extract_hwe_from_path(struct multipath * mpp); void remove_map (struct multipath *mpp, vector pathvec, vector mpvec); void remove_map_by_alias(const char *alias, struct vectors * vecs); void remove_maps (struct vectors * vecs); void sync_map_state (struct multipath *); struct multipath * add_map_with_path (struct vectors * vecs, struct path * pp, int add_vec, const struct multipath *current_mpp); void update_queue_mode_del_path(struct multipath *mpp); void update_queue_mode_add_path(struct multipath *mpp); int update_multipath_table__ (struct multipath *mpp, vector pathvec, int flags, const char *params, const char *status); int update_multipath_table (struct multipath *mpp, vector pathvec, int flags); int update_multipath_status (struct multipath *mpp); vector get_used_hwes(const struct vector_s *pathvec); #endif /* STRUCTS_VEC_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/switchgroup.c000066400000000000000000000033311475246302400221660ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Edward Goggin, EMC */ #include "checkers.h" #include "vector.h" #include "structs.h" #include "switchgroup.h" void path_group_prio_update(struct pathgroup *pgp) { int i; int priority = 0; int marginal = 0; int defined_prios = 0; struct path * pp; pgp->enabled_paths = 0; if (!pgp->paths) { pgp->priority = 0; return; } vector_foreach_slot (pgp->paths, pp, i) { if (pp->marginal) marginal++; if (pp->state == PATH_UP || pp->state == PATH_GHOST) { if (pp->priority != PRIO_UNDEF) { defined_prios++; priority += pp->priority; } pgp->enabled_paths++; } } if (defined_prios) pgp->priority = priority / defined_prios; else if (pgp->enabled_paths) pgp->priority = PRIO_UNDEF; else pgp->priority = 0; if (marginal && marginal == i) pgp->marginal = 1; } int select_path_group(struct multipath *mpp) { int i; int normal_pgp = 0; int max_priority = 0; int bestpg = 1; int max_enabled_paths = 1; struct pathgroup * pgp; if (!mpp->pg) return 1; vector_foreach_slot (mpp->pg, pgp, i) { if (!pgp->paths) continue; path_group_prio_update(pgp); if (pgp->marginal && normal_pgp) continue; if (pgp->enabled_paths) { if (!pgp->marginal && !normal_pgp) { normal_pgp = 1; max_priority = pgp->priority; max_enabled_paths = pgp->enabled_paths; bestpg = i + 1; } else if (pgp->priority > max_priority) { max_priority = pgp->priority; max_enabled_paths = pgp->enabled_paths; bestpg = i + 1; } else if (pgp->priority == max_priority) { if (pgp->enabled_paths > max_enabled_paths) { max_enabled_paths = pgp->enabled_paths; bestpg = i + 1; } } } } return bestpg; } multipath-tools-0.11.1/libmultipath/switchgroup.h000066400000000000000000000002551475246302400221750ustar00rootroot00000000000000#ifndef SWITCHGROUP_H_INCLUDED #define SWITCHGROUP_H_INCLUDED void path_group_prio_update (struct pathgroup * pgp); int select_path_group (struct multipath * mpp); #endif multipath-tools-0.11.1/libmultipath/sysfs.c000066400000000000000000000214211475246302400207570ustar00rootroot00000000000000/* * Copyright (C) 2005-2006 Kay Sievers * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "vector.h" #include "structs.h" #include "sysfs.h" #include "list.h" #include "util.h" #include "debug.h" #include "devmapper.h" #include "config.h" /* * When we modify an attribute value we cannot rely on libudev for now, * as libudev lacks the capability to update an attribute value. * So for modified attributes we need to implement our own function. */ static ssize_t sysfs_attr_get_value__(struct udev_device *dev, const char *attr_name, char *value, size_t value_len, bool binary) { const char *syspath; char devpath[PATH_MAX]; int fd = -1; ssize_t size = -1; if (!dev || !attr_name || !value || !value_len) { condlog(1, "%s: invalid parameters", __func__); return -EINVAL; } syspath = udev_device_get_syspath(dev); if (!syspath) { condlog(3, "%s: invalid udevice", __func__); return -EINVAL; } if (safe_sprintf(devpath, "%s/%s", syspath, attr_name)) { condlog(3, "%s: devpath overflow", __func__); return -EOVERFLOW; } condlog(4, "open '%s'", devpath); /* read attribute value */ fd = open(devpath, O_RDONLY); if (fd < 0) { condlog(3, "%s: attribute '%s' cannot be opened: %s", __func__, devpath, strerror(errno)); return -errno; } pthread_cleanup_push(cleanup_fd_ptr, &fd); size = read(fd, value, value_len); if (size < 0) { size = -errno; condlog(3, "%s: read from %s failed: %s", __func__, devpath, strerror(errno)); if (!binary) value[0] = '\0'; } else if (!binary && size == (ssize_t)value_len) { condlog(3, "%s: overflow reading from %s (required len: %zu)", __func__, devpath, size); value[size - 1] = '\0'; } else if (!binary) { value[size] = '\0'; size = strchop(value); } pthread_cleanup_pop(1); return size; } ssize_t sysfs_attr_get_value(struct udev_device *dev, const char *attr_name, char *value, size_t value_len) { return sysfs_attr_get_value__(dev, attr_name, value, value_len, false); } ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name, unsigned char *value, size_t value_len) { return sysfs_attr_get_value__(dev, attr_name, (char *)value, value_len, true); } ssize_t sysfs_attr_set_value(struct udev_device *dev, const char *attr_name, const char * value, size_t value_len) { const char *syspath; char devpath[PATH_MAX]; int fd = -1; ssize_t size = -1; if (!dev || !attr_name || !value || !value_len) { condlog(1, "%s: invalid parameters", __func__); return -EINVAL; } syspath = udev_device_get_syspath(dev); if (!syspath) { condlog(3, "%s: invalid udevice", __func__); return -EINVAL; } if (safe_sprintf(devpath, "%s/%s", syspath, attr_name)) { condlog(3, "%s: devpath overflow", __func__); return -EOVERFLOW; } condlog(4, "open '%s'", devpath); /* write attribute value */ fd = open(devpath, O_WRONLY); if (fd < 0) { condlog(3, "%s: attribute '%s' cannot be opened: %s", __func__, devpath, strerror(errno)); return -errno; } pthread_cleanup_push(cleanup_fd_ptr, &fd); size = write(fd, value, value_len); if (size < 0) { size = -errno; condlog(3, "%s: write to %s failed: %s", __func__, devpath, strerror(errno)); } else if (size < (ssize_t)value_len) condlog(3, "%s: underflow writing %zu bytes to %s. Wrote %zd bytes", __func__, value_len, devpath, size); pthread_cleanup_pop(1); return size; } int sysfs_get_size (struct path *pp, unsigned long long * size) { char attr[255]; int r; if (!pp->udev || !size) return 1; attr[0] = '\0'; if (!sysfs_attr_get_value_ok(pp->udev, "size", attr, sizeof(attr))) { condlog(3, "%s: No size attribute in sysfs", pp->dev); return 1; } r = sscanf(attr, "%llu\n", size); if (r != 1) { condlog(3, "%s: Cannot parse size attribute", pp->dev); return 1; } return 0; } int devt2devname(char *devname, int devname_len, const char *devt) { struct udev_device *u_dev; const char * dev_name; int r; if (!devname || !devname_len || !devt) return 1; u_dev = udev_device_new_from_devnum(udev, 'b', parse_devt(devt)); if (!u_dev) { condlog(0, "\"%s\": invalid major/minor numbers, not found in sysfs", devt); return 1; } dev_name = udev_device_get_sysname(u_dev); if (!dev_name) { udev_device_unref(u_dev); return 1; } r = strlcpy(devname, dev_name, devname_len); udev_device_unref(u_dev); return !(r < devname_len); } int sysfs_check_holders(char * check_devt, char * new_devt) { unsigned int major, new_minor, table_minor; char path[PATH_MAX], check_dev[FILE_NAME_SIZE]; char * table_name; DIR *dirfd; struct dirent *holder; if (sscanf(new_devt,"%d:%d", &major, &new_minor) != 2) { condlog(1, "invalid device number %s", new_devt); return 0; } if (devt2devname(check_dev, sizeof(check_dev), check_devt)) { condlog(1, "can't get devname for %s", check_devt); return 0; } condlog(3, "%s: checking holder", check_dev); snprintf(path, sizeof(path), "/sys/block/%s/holders", check_dev); dirfd = opendir(path); if (dirfd == NULL) { condlog(3, "%s: failed to open directory %s (%d)", check_dev, path, errno); return 0; } while ((holder = readdir(dirfd)) != NULL) { if ((strcmp(holder->d_name,".") == 0) || (strcmp(holder->d_name,"..") == 0)) continue; if (sscanf(holder->d_name, "dm-%d", &table_minor) != 1) { condlog(3, "%s: %s is not a dm-device", check_dev, holder->d_name); continue; } if (table_minor == new_minor) { condlog(3, "%s: holder already correct", check_dev); continue; } table_name = dm_mapname(major, table_minor); if (!table_name) { condlog(2, "%s: mapname not found for %d:%d", check_dev, major, table_minor); continue; } condlog(0, "%s: reassign table %s old %s new %s", check_dev, table_name, check_devt, new_devt); dm_reassign_table(table_name, check_devt, new_devt); free(table_name); } closedir(dirfd); return 0; } static int select_dm_devs(const struct dirent *di) { return fnmatch("dm-*", di->d_name, FNM_FILE_NAME) == 0; } bool sysfs_is_multipathed(struct path *pp, bool set_wwid) { char pathbuf[PATH_MAX]; struct scandir_result sr; struct dirent **di; int n, r, i; bool found = false; n = snprintf(pathbuf, sizeof(pathbuf), "/sys/block/%s/holders", pp->dev); if (n < 0 || (size_t)n >= sizeof(pathbuf)) { condlog(1, "%s: pathname overflow", __func__); return false; } r = scandir(pathbuf, &di, select_dm_devs, alphasort); if (r == 0) return false; else if (r < 0) { condlog(1, "%s: error scanning %s", __func__, pathbuf); return false; } sr.di = di; sr.n = r; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < r && !found; i++) { int fd = -1; int nr; char uuid[WWID_SIZE + UUID_PREFIX_LEN]; if (safe_snprintf(pathbuf + n, sizeof(pathbuf) - n, "/%s/dm/uuid", di[i]->d_name)) continue; fd = open(pathbuf, O_RDONLY); if (fd == -1) { condlog(1, "%s: error opening %s", __func__, pathbuf); continue; } pthread_cleanup_push(cleanup_fd_ptr, &fd); nr = read(fd, uuid, sizeof(uuid)); if (nr > (int)UUID_PREFIX_LEN && !memcmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN)) { found = true; if (set_wwid) { nr -= UUID_PREFIX_LEN; memcpy(pp->wwid, uuid + UUID_PREFIX_LEN, nr); if (nr == WWID_SIZE) { condlog(4, "%s: overflow while reading from %s", __func__, pathbuf); pp->wwid[0] = '\0'; } else { pp->wwid[nr] = '\0'; strchop(pp->wwid); } } } else if (nr < 0) condlog(1, "%s: error reading from %s: %m", __func__, pathbuf); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return found; } struct udev_device *get_udev_for_mpp(const struct multipath *mpp) { dev_t devnum; struct udev_device *udd; if (!mpp || !has_dm_info(mpp)) { condlog(1, "%s called with empty mpp", __func__); return NULL; } devnum = makedev(mpp->dmi.major, mpp->dmi.minor); udd = udev_device_new_from_devnum(udev, 'b', devnum); if (!udd) { condlog(1, "failed to get udev device for %s", mpp->alias); return NULL; } return udd; } multipath-tools-0.11.1/libmultipath/sysfs.h000066400000000000000000000026551475246302400207740ustar00rootroot00000000000000/* * sysfs.h */ #ifndef SYSFS_H_INCLUDED #define SYSFS_H_INCLUDED #include #include "strbuf.h" int devt2devname (char *, int, const char *); ssize_t sysfs_attr_set_value(struct udev_device *dev, const char *attr_name, const char * value, size_t value_len); ssize_t sysfs_attr_get_value(struct udev_device *dev, const char *attr_name, char * value, size_t value_len); ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name, unsigned char * value, size_t value_len); #define sysfs_attr_value_ok(rc, value_len) \ ({ \ ssize_t __r = rc; \ __r >= 0 && (size_t)__r < (size_t)value_len; \ }) #define sysfs_attr_get_value_ok(dev, attr, val, len) \ ({ \ size_t __l = (len); \ ssize_t __rc = sysfs_attr_get_value(dev, attr, val, __l); \ sysfs_attr_value_ok(__rc, __l); \ }) #define log_sysfs_attr_set_value(prio, rc, fmt, __args...) \ do { \ STRBUF_ON_STACK(__buf); \ if (print_strbuf(&__buf, fmt, ##__args) >= 0 && \ print_strbuf(&__buf, ": %s", rc < 0 ? strerror(-rc) : \ "write underflow") >= 0) \ condlog(prio, "%s", get_strbuf_str(&__buf)); \ } while(0) int sysfs_get_size (struct path *pp, unsigned long long * size); int sysfs_check_holders(char * check_devt, char * new_devt); bool sysfs_is_multipathed(struct path *pp, bool set_wwid); struct multipath; struct udev_device *get_udev_for_mpp(const struct multipath *mpp); #endif multipath-tools-0.11.1/libmultipath/uevent.c000066400000000000000000000463471475246302400211340ustar00rootroot00000000000000/* * uevent.c - trigger upon netlink uevents from the kernel * * Only kernels from version 2.6.10* on provide the uevent netlink socket. * Until the libc-kernel-headers are updated, you need to compile with: * * gcc -I /lib/modules/`uname -r`/build/include -o uevent_listen uevent_listen.c * * Copyright (C) 2004 Kay Sievers * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "autoconfig.h" #include "debug.h" #include "list.h" #include "uevent.h" #include "vector.h" #include "structs.h" #include "util.h" #include "config.h" #include "blacklist.h" #include "devmapper.h" #include "strbuf.h" typedef int (uev_trigger)(struct uevent *, void * trigger_data); static LIST_HEAD(uevq); static pthread_mutex_t uevq_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t *uevq_lockp = &uevq_lock; static pthread_cond_t uev_cond = PTHREAD_COND_INITIALIZER; static pthread_cond_t *uev_condp = &uev_cond; static uev_trigger *my_uev_trigger; static void *my_trigger_data; static int servicing_uev; struct uevent_filter_state { struct list_head uevq; struct list_head *old_tail; struct config *conf; unsigned long added; unsigned long discarded; unsigned long filtered; unsigned long merged; }; static void reset_filter_state(struct uevent_filter_state *st) { st->added = st->discarded = st->filtered = st->merged = 0; } int is_uevent_busy(void) { int empty, servicing; pthread_mutex_lock(uevq_lockp); empty = list_empty(&uevq); servicing = servicing_uev; pthread_mutex_unlock(uevq_lockp); return (!empty || servicing); } struct uevent * alloc_uevent (void) { struct uevent *uev = calloc(1, sizeof(struct uevent)); if (uev) { INIT_LIST_HEAD(&uev->node); INIT_LIST_HEAD(&uev->merge_node); } return uev; } static void uevq_cleanup(struct list_head *tmpq); static void cleanup_uev(void *arg) { struct uevent *uev = arg; uevq_cleanup(&uev->merge_node); if (uev->udev) udev_device_unref(uev->udev); free(uev); } static void uevq_cleanup(struct list_head *tmpq) { struct uevent *uev, *tmp; list_for_each_entry_safe(uev, tmp, tmpq, node) { list_del_init(&uev->node); cleanup_uev(uev); } } static const char* uevent_get_env_var(const struct uevent *uev, const char *attr) { int i; size_t len; const char *p = NULL; if (attr == NULL) goto invalid; len = strlen(attr); if (len == 0) goto invalid; for (i = 0; uev->envp[i] != NULL; i++) { const char *var = uev->envp[i]; if (strlen(var) > len && !memcmp(var, attr, len) && var[len] == '=') { p = var + len + 1; break; } } condlog(4, "%s: %s -> '%s'", __func__, attr, p ?: "(null)"); return p; invalid: condlog(2, "%s: empty variable name", __func__); return NULL; } int uevent_get_env_positive_int(const struct uevent *uev, const char *attr) { const char *p = uevent_get_env_var(uev, attr); char *q; int ret; if (p == NULL || *p == '\0') return -1; ret = strtoul(p, &q, 10); if (*q != '\0' || ret < 0) { condlog(2, "%s: invalid %s: '%s'", __func__, attr, p); return -1; } return ret; } void uevent_get_wwid(struct uevent *uev, const struct config *conf) { const char *uid_attribute; const char *val; uid_attribute = get_uid_attribute_by_attrs(conf, uev->kernel); val = uevent_get_env_var(uev, uid_attribute); if (val) uev->wwid = val; } static bool uevent_need_merge(const struct config *conf) { return VECTOR_SIZE(&conf->uid_attrs) > 0; } static bool uevent_can_discard(struct uevent *uev, const struct config *conf) { /* * do not filter dm devices by devnode */ if (!strncmp(uev->kernel, "dm-", 3)) return false; /* * filter paths devices by devnode */ if (filter_devnode(conf->blist_devnode, conf->elist_devnode, uev->kernel) > 0) return true; return false; } static bool uevent_can_filter(struct uevent *earlier, struct uevent *later) { if (!strncmp(later->kernel, "dm-", 3) || strcmp(earlier->kernel, later->kernel)) return false; /* * filter earlier uvents if path has removed later. Eg: * "add path1 |chang path1 |add path2 |remove path1" * can filter as: * "add path2 |remove path1" * uevents "add path1" and "chang path1" are filtered out */ if (!strcmp(later->action, "remove")) return true; /* * filter change uvents if add uevents exist. Eg: * "change path1| add path1 |add path2" * can filter as: * "add path1 |add path2" * uevent "chang path1" is filtered out */ if (!strcmp(earlier->action, "change") && !strcmp(later->action, "add")) return true; return false; } static bool merge_need_stop(struct uevent *earlier, struct uevent *later) { /* * dm uevent do not try to merge with left uevents */ if (!strncmp(later->kernel, "dm-", 3)) return true; /* * we cannot make a jugement without wwid, * so it is sensible to stop merging */ if (!earlier->wwid || !later->wwid) return true; /* * uevents merging stopped * when we meet an opposite action uevent from the same LUN to AVOID * "add path1 |remove path1 |add path2 |remove path2 |add path3" * to merge as "remove path1, path2" and "add path1, path2, path3" * OR * "remove path1 |add path1 |remove path2 |add path2 |remove path3" * to merge as "add path1, path2" and "remove path1, path2, path3" * SO * when we meet a non-change uevent from the same LUN * with the same wwid and different action * it would be better to stop merging. */ if (strcmp(earlier->action, later->action) && strcmp(earlier->action, "change") && strcmp(later->action, "change") && !strcmp(earlier->wwid, later->wwid)) return true; return false; } static bool uevent_can_merge(struct uevent *earlier, struct uevent *later) { /* merge paths uevents * whose wwids exist and are same * and actions are same, * and actions are addition or deletion */ if (earlier->wwid && later->wwid && strncmp(earlier->kernel, "dm-", 3) && !strcmp(earlier->action, later->action) && (!strcmp(earlier->action, "add") || !strcmp(earlier->action, "remove")) && !strcmp(earlier->wwid, later->wwid)) return true; return false; } static void uevent_delete_from_list(struct uevent *to_delete, struct uevent **previous, struct list_head **old_tail) { /* * "old_tail" is the list_head before the last list element to which * the caller iterates (the list anchor if the caller iterates over * the entire list). If this element is removed (which can't happen * for the anchor), "old_tail" must be moved. It can happen that * "old_tail" ends up pointing at the anchor. */ if (*old_tail == &to_delete->node) *old_tail = to_delete->node.prev; list_del_init(&to_delete->node); /* * The "to_delete" uevent has been merged with other uevents * previously. Re-insert them into the list, at the point we're * currently at. This must be done after the list_del_init() above, * otherwise previous->next would still point to to_delete. */ if (!list_empty(&to_delete->merge_node)) { struct uevent *last = list_entry(to_delete->merge_node.prev, typeof(*last), node); condlog(3, "%s: deleted uevent \"%s %s\" with merged uevents", __func__, to_delete->action, to_delete->kernel); list_splice(&to_delete->merge_node, &(*previous)->node); *previous = last; } if (to_delete->udev) udev_device_unref(to_delete->udev); free(to_delete); } /* * Use this function to delete events that are known not to * be equal to old_tail, and have an empty merge_node list. * For others, use uevent_delete_from_list(). */ static void uevent_delete_simple(struct uevent *to_delete) { list_del_init(&to_delete->node); if (to_delete->udev) udev_device_unref(to_delete->udev); free(to_delete); } static void uevent_prepare(struct uevent_filter_state *st) { struct uevent *uev, *tmp; list_for_some_entry_reverse_safe(uev, tmp, &st->uevq, st->old_tail, node) { st->added++; if (uevent_can_discard(uev, st->conf)) { uevent_delete_simple(uev); st->discarded++; continue; } if (strncmp(uev->kernel, "dm-", 3) && uevent_need_merge(st->conf)) uevent_get_wwid(uev, st->conf); } } static void uevent_filter(struct uevent *later, struct uevent_filter_state *st) { struct uevent *earlier, *tmp; list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { /* * filter unnecessary earlier uevents * by the later uevent */ if (!list_empty(&earlier->merge_node)) { struct uevent *mn, *t; list_for_each_entry_reverse_safe(mn, t, &earlier->merge_node, node) { if (uevent_can_filter(mn, later)) { condlog(4, "uevent: \"%s %s\" (merged into \"%s %s\") filtered by \"%s %s\"", mn->action, mn->kernel, earlier->action, earlier->kernel, later->action, later->kernel); uevent_delete_simple(mn); st->filtered++; } } } if (uevent_can_filter(earlier, later)) { condlog(4, "uevent: \"%s %s\" filtered by \"%s %s\"", earlier->action, earlier->kernel, later->action, later->kernel); uevent_delete_from_list(earlier, &tmp, &st->old_tail); st->filtered++; } } } static void uevent_merge(struct uevent *later, struct uevent_filter_state *st) { struct uevent *earlier, *tmp; list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { if (merge_need_stop(earlier, later)) break; /* * merge earlier uevents to the later uevent */ if (uevent_can_merge(earlier, later)) { condlog(4, "uevent: \"%s %s\" merged with \"%s %s\" for WWID %s", earlier->action, earlier->kernel, later->action, later->kernel, later->wwid); /* See comment in uevent_delete_from_list() */ if (&earlier->node == st->old_tail) st->old_tail = earlier->node.prev; list_move(&earlier->node, &later->merge_node); list_splice_init(&earlier->merge_node, &later->merge_node); st->merged++; } } } static void merge_uevq(struct uevent_filter_state *st) { struct uevent *later; uevent_prepare(st); list_for_some_entry_reverse(later, &st->uevq, st->old_tail, node) uevent_filter(later, st); if(uevent_need_merge(st->conf)) list_for_some_entry_reverse(later, &st->uevq, st->old_tail, node) uevent_merge(later, st); } static void print_uev(struct strbuf *buf, struct uevent *uev) { print_strbuf(buf, "\"%s %s\"", uev->action, uev->kernel); if (!list_empty(&uev->merge_node)) { struct uevent *u; append_strbuf_str(buf, "["); list_for_each_entry(u, &uev->merge_node, node) print_strbuf(buf, "\"%s %s \"", u->action, u->kernel); append_strbuf_str(buf, "]"); } append_strbuf_str(buf, " "); } static void print_uevq(const char *msg, struct list_head *uevq) { struct uevent *uev; int i = 0; STRBUF_ON_STACK(buf); if (4 > MAX_VERBOSITY || 4 > libmp_verbosity) return; if (list_empty(uevq)) append_strbuf_str(&buf, "*empty*"); else list_for_each_entry(uev, uevq, node) { print_strbuf(&buf, "%d:", i++); print_uev(&buf, uev); } condlog(4, "uevent queue (%s): %s", msg, steal_strbuf_str(&buf)); } static void service_uevq(struct list_head *tmpq) { struct uevent *uev = list_pop_entry(tmpq, typeof(*uev), node); if (uev == NULL) return; condlog(4, "servicing uevent '%s %s'", uev->action, uev->kernel); pthread_cleanup_push(cleanup_uev, uev); if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) condlog(0, "uevent trigger error"); pthread_cleanup_pop(1); } static void uevent_cleanup(void *arg) { struct udev *udev = arg; condlog(3, "Releasing uevent_listen() resources"); udev_unref(udev); } static void monitor_cleanup(void *arg) { struct udev_monitor *monitor = arg; condlog(3, "Releasing uevent_monitor() resources"); udev_monitor_unref(monitor); } static void cleanup_uevq(void *arg) { uevq_cleanup(arg); } static void cleanup_global_uevq(void *arg __attribute__((unused))) { pthread_mutex_lock(uevq_lockp); uevq_cleanup(&uevq); pthread_mutex_unlock(uevq_lockp); } static void log_filter_state(const struct uevent_filter_state *st) { if (st->added == 0 && st->filtered == 0 && st->merged == 0) return; condlog(3, "uevents: %lu added, %lu discarded, %lu filtered, %lu merged", st->added, st->discarded, st->filtered, st->merged); } /* * Service the uevent queue. */ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), void * trigger_data) { struct uevent_filter_state filter_state; INIT_LIST_HEAD(&filter_state.uevq); my_uev_trigger = uev_trigger; my_trigger_data = trigger_data; mlockall(MCL_CURRENT | MCL_FUTURE); pthread_cleanup_push(cleanup_uevq, &filter_state.uevq); while (1) { pthread_cleanup_push(cleanup_mutex, uevq_lockp); pthread_mutex_lock(uevq_lockp); servicing_uev = !list_empty(&filter_state.uevq); while (list_empty(&filter_state.uevq) && list_empty(&uevq)) { condlog(4, "%s: waiting for events", __func__); pthread_cond_wait(uev_condp, uevq_lockp); condlog(4, "%s: waking up", __func__); } servicing_uev = 1; /* * "old_tail" is the list element towards which merge_uevq() * will iterate: the last element of uevq before * appending new uevents. If uveq empty, uevq.prev * equals &uevq, which is what we need. */ filter_state.old_tail = filter_state.uevq.prev; list_splice_tail_init(&uevq, &filter_state.uevq); pthread_cleanup_pop(1); if (!my_uev_trigger) break; reset_filter_state(&filter_state); pthread_cleanup_push(put_multipath_config, filter_state.conf); print_uevq("append", &filter_state.uevq); filter_state.conf = get_multipath_config(); merge_uevq(&filter_state); pthread_cleanup_pop(1); log_filter_state(&filter_state); print_uevq("merge", &filter_state.uevq); service_uevq(&filter_state.uevq); } pthread_cleanup_pop(1); condlog(3, "Terminating uev service queue"); return 0; } static struct uevent *uevent_from_udev_device(struct udev_device *dev) { struct uevent *uev; int i = 0; char *pos, *end; struct udev_list_entry *list_entry; uev = alloc_uevent(); if (!uev) { udev_device_unref(dev); condlog(1, "lost uevent, oom"); return NULL; } pos = uev->buffer; end = pos + HOTPLUG_BUFFER_SIZE + OBJECT_SIZE - 1; udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(dev)) { const char *name, *value; int bytes; name = udev_list_entry_get_name(list_entry); if (!name) name = "(null)"; value = udev_list_entry_get_value(list_entry); if (!value) value = "(null)"; bytes = snprintf(pos, end - pos, "%s=%s", name, value); if (pos + bytes >= end) { condlog(2, "buffer overflow for uevent"); break; } uev->envp[i] = pos; pos += bytes; *pos = '\0'; pos++; if (strcmp(name, "DEVPATH") == 0) uev->devpath = uev->envp[i] + 8; if (strcmp(name, "ACTION") == 0) uev->action = uev->envp[i] + 7; i++; if (i == HOTPLUG_NUM_ENVP - 1) break; } if (!uev->devpath || ! uev->action) { udev_device_unref(dev); condlog(1, "uevent missing necessary fields"); free(uev); return NULL; } uev->udev = dev; uev->envp[i] = NULL; condlog(3, "uevent '%s' from '%s'", uev->action, uev->devpath); uev->kernel = strrchr(uev->devpath, '/'); if (uev->kernel) uev->kernel++; /* print payload environment */ for (i = 0; uev->envp[i] != NULL; i++) condlog(5, "%s", uev->envp[i]); return uev; } #define MAX_UEVENTS 1000 static int uevent_receive_events(int fd, struct list_head *tmpq, struct udev_monitor *monitor) { struct pollfd ev_poll; int n = 0; do { struct uevent *uev; struct udev_device *dev; dev = udev_monitor_receive_device(monitor); if (!dev) { condlog(0, "failed getting udev device"); break; } uev = uevent_from_udev_device(dev); if (!uev) break; list_add_tail(&uev->node, tmpq); n++; condlog(4, "received uevent \"%s %s\"", uev->action, uev->kernel); ev_poll.fd = fd; ev_poll.events = POLLIN; } while (n < MAX_UEVENTS && poll(&ev_poll, 1, 0) > 0); return n; } int uevent_listen(struct udev *udev) { int err = 2; struct udev_monitor *monitor = NULL; int fd, socket_flags; LIST_HEAD(uevlisten_tmp); /* * Queue uevents for service by dedicated thread so that the uevent * listening thread does not block on multipathd locks (vecs->lock) * thereby not getting to empty the socket's receive buffer queue * often enough. */ if (!udev) { condlog(1, "no udev context"); return 1; } udev_ref(udev); pthread_cleanup_push(uevent_cleanup, udev); monitor = udev_monitor_new_from_netlink(udev, "udev"); if (!monitor) { condlog(2, "failed to create udev monitor"); goto out_udev; } pthread_cleanup_push(monitor_cleanup, monitor); #ifdef LIBUDEV_API_RECVBUF if (udev_monitor_set_receive_buffer_size(monitor, 128 * 1024 * 1024) < 0) condlog(2, "failed to increase buffer size"); #endif fd = udev_monitor_get_fd(monitor); if (fd < 0) { condlog(2, "failed to get monitor fd"); goto out; } socket_flags = fcntl(fd, F_GETFL); if (socket_flags < 0) { condlog(2, "failed to get monitor socket flags : %s", strerror(errno)); goto out; } if (fcntl(fd, F_SETFL, socket_flags & ~O_NONBLOCK) < 0) { condlog(2, "failed to set monitor socket flags : %s", strerror(errno)); goto out; } err = udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", "disk"); if (err) condlog(2, "failed to create filter : %s", strerror(-err)); err = udev_monitor_enable_receiving(monitor); if (err) { condlog(2, "failed to enable receiving : %s", strerror(-err)); goto out; } pthread_cleanup_push(cleanup_global_uevq, NULL); pthread_cleanup_push(cleanup_uevq, &uevlisten_tmp); while (1) { int fdcount, events; struct pollfd ev_poll = { .fd = fd, .events = POLLIN, }; fdcount = poll(&ev_poll, 1, -1); if (fdcount < 0) { if (errno == EINTR) continue; condlog(0, "error receiving uevent message: %m"); err = -errno; break; } events = uevent_receive_events(fd, &uevlisten_tmp, monitor); if (events <= 0) continue; condlog(4, "Forwarding %d uevents", events); pthread_mutex_lock(uevq_lockp); list_splice_tail_init(&uevlisten_tmp, &uevq); pthread_cond_signal(uev_condp); pthread_mutex_unlock(uevq_lockp); } pthread_cleanup_pop(1); pthread_cleanup_pop(1); out: pthread_cleanup_pop(1); out_udev: pthread_cleanup_pop(1); return err; } char *uevent_get_dm_str(const struct uevent *uev, char *attr) { const char *tmp = uevent_get_env_var(uev, attr); if (tmp == NULL) return NULL; return strdup(tmp); } bool uevent_is_mpath(const struct uevent *uev) { const char *uuid = uevent_get_env_var(uev, "DM_UUID"); if (uuid == NULL) return false; if (strncmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN)) return false; return uuid[UUID_PREFIX_LEN] != '\0'; } multipath-tools-0.11.1/libmultipath/uevent.h000066400000000000000000000033141475246302400211240ustar00rootroot00000000000000#ifndef UEVENT_H_INCLUDED #define UEVENT_H_INCLUDED /* * buffer for environment variables, the kernel's size in * lib/kobject_uevent.c should fit in */ #define HOTPLUG_BUFFER_SIZE 2048 #define HOTPLUG_NUM_ENVP 32 #define OBJECT_SIZE 512 struct udev; struct config; struct uevent { struct list_head node; struct list_head merge_node; struct udev_device *udev; char buffer[HOTPLUG_BUFFER_SIZE + OBJECT_SIZE]; char *devpath; char *action; char *kernel; const char *wwid; unsigned long seqnum; char *envp[HOTPLUG_NUM_ENVP]; }; struct uevent *alloc_uevent(void); int is_uevent_busy(void); int uevent_listen(struct udev *udev); int uevent_dispatch(int (*store_uev)(struct uevent *, void * trigger_data), void * trigger_data); bool uevent_is_mpath(const struct uevent *uev); void uevent_get_wwid(struct uevent *uev, const struct config *conf); int uevent_get_env_positive_int(const struct uevent *uev, const char *attr); static inline int uevent_get_major(const struct uevent *uev) { return uevent_get_env_positive_int(uev, "MAJOR"); } static inline int uevent_get_minor(const struct uevent *uev) { return uevent_get_env_positive_int(uev, "MINOR"); } static inline int uevent_get_disk_ro(const struct uevent *uev) { return uevent_get_env_positive_int(uev, "DISK_RO"); } char *uevent_get_dm_str(const struct uevent *uev, char *attr); static inline char *uevent_get_dm_name(const struct uevent *uev) { return uevent_get_dm_str(uev, "DM_NAME"); } static inline char *uevent_get_dm_path(const struct uevent *uev) { return uevent_get_dm_str(uev, "DM_PATH"); } static inline char *uevent_get_dm_action(const struct uevent *uev) { return uevent_get_dm_str(uev, "DM_ACTION"); } #endif /* UEVENT_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/unaligned.h000066400000000000000000000017261475246302400215710ustar00rootroot00000000000000#ifndef UNALIGNED_H_INCLUDED #define UNALIGNED_H_INCLUDED #include static inline uint16_t get_unaligned_be16(const void *ptr) { const uint8_t *p = ptr; return p[0] << 8 | p[1]; } static inline uint32_t get_unaligned_be32(const void *ptr) { const uint8_t *p = ptr; return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; } static inline uint64_t get_unaligned_be64(const void *ptr) { uint32_t low = get_unaligned_be32(ptr + 4); uint64_t high = get_unaligned_be32(ptr); return high << 32 | low; } static inline void put_unaligned_be16(uint16_t val, void *ptr) { uint8_t *p = ptr; p[0] = val >> 8; p[1] = val; } static inline void put_unaligned_be32(uint32_t val, void *ptr) { uint8_t *p = ptr; p[0] = val >> 24; p[1] = val >> 16; p[2] = val >> 8; p[3] = val; } static inline void put_unaligned_be64(uint64_t val, void *ptr) { uint8_t *p = ptr; put_unaligned_be32(val >> 32, p); put_unaligned_be32(val, p + 4); } #endif /* UNALIGNED_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/valid.c000066400000000000000000000206261475246302400207150ustar00rootroot00000000000000/* Copyright (c) 2020 Benjamin Marzinski, IBM This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "vector.h" #include "config.h" #include "debug.h" #include "util.h" #include "devmapper.h" #include "discovery.h" #include "wwids.h" #include "sysfs.h" #include "blacklist.h" #include "mpath_cmd.h" #include "valid.h" static int subdir_filter(const struct dirent *ent) { unsigned int j; static char const *const skip[] = { ".", "..", "holders", "integrity", "mq", "power", "queue", "slaves", "trace", }; if (ent->d_type != DT_DIR) return 0; for (j = 0; j < ARRAY_SIZE(skip); j++) if (!strcmp(skip[j], ent->d_name)) return 0; return 1; } static int read_partitions(const char *syspath, vector parts) { struct scandir_result sr = { .n = 0 }; char path[PATH_MAX], *last; char *prop; int i; strlcpy(path, syspath, sizeof(path)); sr.n = scandir(path, &sr.di, subdir_filter, NULL); if (sr.n == -1) return -errno; pthread_cleanup_push_cast(free_scandir_result, &sr); /* parts[0] is the whole disk */ if ((prop = strdup(strrchr(path, '/') + 1)) != NULL) { if (vector_alloc_slot(parts)) vector_set_slot(parts, prop); else free(prop); } last = path + strlen(path); for (i = 0; i < sr.n; i++) { struct stat st; /* only add dirs that have the "partition" attribute */ snprintf(last, sizeof(path) - (last - path), "/%s/partition", sr.di[i]->d_name); if (stat(path, &st) == 0 && (prop = strdup(sr.di[i]->d_name)) != NULL) { if (vector_alloc_slot(parts)) vector_set_slot(parts, prop); else free(prop); } } pthread_cleanup_pop(1); return 0; } static int no_dots(const struct dirent *ent) { const char *name = ent->d_name; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) return 0; return 1; } static int check_holders(const char *syspath) { struct scandir_result __attribute__((cleanup(free_scandir_result))) sr = { .n = 0 }; sr.n = scandir(syspath, &sr.di, no_dots, NULL); if (sr.n > 0) condlog(4, "%s: found holders under %s", __func__, syspath); return sr.n; } static int check_all_holders(const struct vector_s *parts) { char syspath[PATH_MAX]; const char *sysname; unsigned int j; if (VECTOR_SIZE(parts) == 0) return 0; if (safe_sprintf(syspath, "/sys/class/block/%s/holders", (const char *)VECTOR_SLOT(parts, 0))) return -EOVERFLOW; if (check_holders(syspath) > 0) return 1; j = 1; vector_foreach_slot_after(parts, sysname, j) { if (safe_sprintf(syspath, "/sys/class/block/%s/%s/holders", (const char *)VECTOR_SLOT(parts, 0), sysname)) return -EOVERFLOW; if (check_holders(syspath) > 0) return 1; } return 0; } static void cleanup_table(void *arg) { if (arg) mnt_free_table((struct libmnt_table *)arg); } static void cleanup_cache(void *arg) { if (arg) #ifdef LIBMOUNT_HAS_MNT_UNREF_CACHE mnt_unref_cache((struct libmnt_cache *)arg); #else mnt_free_cache((struct libmnt_cache *)arg); #endif } /* * Passed a vector of partitions and a libmount table, * check if any of the partitions in the vector is referenced in the table. * Note that mnt_table_find_srcpath() also resolves mounts by symlinks. */ static int check_mnt_table(const struct vector_s *parts, struct libmnt_table *tbl, const char *table_name) { unsigned int i; const char *sysname; char devpath[PATH_MAX]; vector_foreach_slot(parts, sysname, i) { if (!safe_sprintf(devpath, "/dev/%s", sysname) && mnt_table_find_srcpath(tbl, devpath, MNT_ITER_FORWARD) != NULL) { condlog(4, "%s: found %s in %s", __func__, sysname, table_name); return 1; } } return 0; } static int check_mountinfo(const struct vector_s *parts) { static const char mountinfo[] = "/proc/self/mountinfo"; struct libmnt_table *tbl; struct libmnt_cache *cache; FILE *stream; int used = 0, ret; tbl = mnt_new_table(); if (!tbl ) return -errno; pthread_cleanup_push(cleanup_table, tbl); cache = mnt_new_cache(); if (cache) { pthread_cleanup_push(cleanup_cache, cache); if (mnt_table_set_cache(tbl, cache) == 0) { stream = fopen(mountinfo, "r"); if (stream != NULL) { pthread_cleanup_push(cleanup_fclose, stream); ret = mnt_table_parse_stream(tbl, stream, mountinfo); pthread_cleanup_pop(1); if (ret == 0) used = check_mnt_table(parts, tbl, "mountinfo"); } } pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return used; } #ifdef LIBMOUNT_SUPPORTS_SWAP static int check_swaps(const struct vector_s *parts) { struct libmnt_table *tbl; struct libmnt_cache *cache; int used = 0, ret; tbl = mnt_new_table(); if (!tbl ) return -errno; pthread_cleanup_push(cleanup_table, tbl); cache = mnt_new_cache(); if (cache) { pthread_cleanup_push(cleanup_cache, cache); if (mnt_table_set_cache(tbl, cache) == 0) { ret = mnt_table_parse_swaps(tbl, NULL); if (ret == 0) used = check_mnt_table(parts, tbl, "swaps"); } pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return used; } #else static int check_swaps(const struct vector_s *parts __attribute__((unused))) { return 0; } #endif /* * Given a block device, check if the device itself or any of its * partitions is in use * - by sysfs holders (e.g. LVM) * - mounted according to /proc/self/mountinfo * - used as swap */ static int is_device_in_use(struct udev_device *udevice) { const char *syspath; vector parts; int used = 0, ret; syspath = udev_device_get_syspath(udevice); if (!syspath) return -ENOMEM; parts = vector_alloc(); if (!parts) return -ENOMEM; pthread_cleanup_push_cast(free_strvec, parts); if ((ret = read_partitions(syspath, parts)) == 0) used = check_all_holders(parts) > 0 || check_mountinfo(parts) > 0 || check_swaps(parts) > 0; pthread_cleanup_pop(1); if (ret < 0) return ret; condlog(3, "%s: %s is %sin use", __func__, syspath, used ? "" : "not "); return used; } int is_path_valid(const char *name, struct config *conf, struct path *pp, bool check_multipathd) { int r; int fd; const char *prop; if (!pp || !name || !conf) return PATH_IS_ERROR; if (conf->find_multipaths <= FIND_MULTIPATHS_UNDEF || conf->find_multipaths >= FIND_MULTIPATHS_LAST__) return PATH_IS_ERROR; if (safe_sprintf(pp->dev, "%s", name)) return PATH_IS_ERROR; if (sysfs_is_multipathed(pp, true)) { if (pp->wwid[0] == '\0') return PATH_IS_ERROR; return PATH_IS_VALID_NO_CHECK; } if (check_multipathd) { fd = mpath_connect__(1); if (fd < 0) { if (errno != EAGAIN) { condlog(3, "multipathd not running"); return PATH_IS_NOT_VALID; } } else mpath_disconnect(fd); } pp->udev = udev_device_new_from_subsystem_sysname(udev, "block", name); if (!pp->udev) return PATH_IS_ERROR; prop = udev_device_get_property_value(pp->udev, "DEVTYPE"); if (prop == NULL || strcmp(prop, "disk")) return PATH_IS_NOT_VALID; r = pathinfo(pp, conf, DI_SYSFS | DI_WWID | DI_BLACKLIST); if (r == PATHINFO_SKIPPED) return PATH_IS_NOT_VALID; else if (r) return PATH_IS_ERROR; if (pp->wwid[0] == '\0') return PATH_IS_NOT_VALID; r = is_failed_wwid(pp->wwid); if (r != WWID_IS_NOT_FAILED) { if (r == WWID_IS_FAILED) return PATH_IS_NOT_VALID; return PATH_IS_ERROR; } if ((conf->find_multipaths == FIND_MULTIPATHS_GREEDY || conf->find_multipaths == FIND_MULTIPATHS_SMART) && is_device_in_use(pp->udev) > 0) return PATH_IS_NOT_VALID; if (conf->find_multipaths == FIND_MULTIPATHS_GREEDY) return PATH_IS_VALID; if (check_wwids_file(pp->wwid, 0) == 0) return PATH_IS_VALID_NO_CHECK; if (dm_find_map_by_wwid(pp->wwid, NULL, NULL) == DMP_OK) return PATH_IS_VALID; /* all these act like FIND_MULTIPATHS_STRICT for finding if a * path is valid */ if (conf->find_multipaths != FIND_MULTIPATHS_SMART) return PATH_IS_NOT_VALID; return PATH_IS_MAYBE_VALID; } multipath-tools-0.11.1/libmultipath/valid.h000066400000000000000000000027421475246302400207210ustar00rootroot00000000000000/* Copyright (c) 2020 Benjamin Marzinski, IBM This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VALID_H_INCLUDED #define VALID_H_INCLUDED /* * PATH_IS_VALID_NO_CHECK is returned when multipath should claim * the path, regardless of whether is has been released to systemd * already. * PATH_IS_VALID is returned by is_path_valid, when the path is * valid only if it hasn't been released to systemd already. * PATH_IS_MAYBE_VALID is returned when the path would be valid * if other paths with the same wwid existed. It is up to the caller * to check for these other paths. */ enum is_path_valid_result { PATH_IS_ERROR = -1, PATH_IS_NOT_VALID, PATH_IS_VALID, PATH_IS_VALID_NO_CHECK, PATH_IS_MAYBE_VALID, PATH_MAX_VALID_RESULT, /* only for bounds checking */ }; int is_path_valid(const char *name, struct config *conf, struct path *pp, bool check_multipathd); #endif /* VALID_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/version.h000066400000000000000000000024371475246302400213100ustar00rootroot00000000000000/* * Soft: multipath device mapper target autoconfig * * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $ * * Author: Christophe Varoqui * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Copyright (c) 2006 Christophe Varoqui */ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED #define VERSION_CODE 0x000B01 /* MMDDYY, in hex */ #define DATE_CODE 0x011819 #define PROG "multipath-tools" #define MULTIPATH_VERSION(version) \ (version >> 16) & 0xFF, \ (version >> 8) & 0xFF, \ version & 0xFF #define VERSION_STRING PROG" v%d.%d.%d (%.2d/%.2d, 20%.2d)\n", \ MULTIPATH_VERSION(VERSION_CODE), \ MULTIPATH_VERSION(DATE_CODE) #endif /* VERSION_H_INCLUDED */ multipath-tools-0.11.1/libmultipath/wwids.c000066400000000000000000000226311475246302400207510ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "util.h" #include "checkers.h" #include "vector.h" #include "structs.h" #include "debug.h" #include "uxsock.h" #include "file.h" #include "wwids.h" #include "defaults.h" #include "config.h" #include "devmapper.h" /* * Copyright (c) 2010 Benjamin Marzinski, Redhat */ static int lookup_wwid(FILE *f, char *wwid) { int c; char buf[LINE_MAX]; int count; while ((c = fgetc(f)) != EOF){ if (c != '/') { if (fgets(buf, LINE_MAX, f) == NULL) return 0; else continue; } count = 0; while ((c = fgetc(f)) != '/') { if (c == EOF) return 0; if (count >= WWID_SIZE - 1) goto next; if (wwid[count] == '\0') goto next; if (c != wwid[count++]) goto next; } if (wwid[count] == '\0') return 1; next: if (fgets(buf, LINE_MAX, f) == NULL) return 0; } return 0; } static int write_out_wwid(int fd, char *wwid) { int ret; off_t offset; char buf[WWID_SIZE + 3]; ret = snprintf(buf, WWID_SIZE + 3, "/%s/\n", wwid); if (ret >= (WWID_SIZE + 3) || ret < 0){ condlog(0, "can't format wwid for writing (%d) : %s", ret, strerror(errno)); return -1; } offset = lseek(fd, 0, SEEK_END); if (offset < 0) { condlog(0, "can't seek to the end of wwids file : %s", strerror(errno)); return -1; } if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { condlog(0, "cannot write wwid to wwids file : %s", strerror(errno)); if (ftruncate(fd, offset)) condlog(0, "cannot truncate failed wwid write : %s", strerror(errno)); return -1; } return 1; } int replace_wwids(vector mp) { int i, can_write; int fd = -1; struct multipath * mpp; size_t len; int ret = -1; fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) goto out; pthread_cleanup_push(cleanup_fd_ptr, &fd); if (!can_write) { condlog(0, "cannot replace wwids. wwids file is read-only"); goto out_file; } if (ftruncate(fd, 0) < 0) { condlog(0, "cannot truncate wwids file : %s", strerror(errno)); goto out_file; } if (lseek(fd, 0, SEEK_SET) < 0) { condlog(0, "cannot seek to the start of the file : %s", strerror(errno)); goto out_file; } len = strlen(WWIDS_FILE_HEADER); if (write(fd, WWIDS_FILE_HEADER, len) != (ssize_t)len) { condlog(0, "Can't write wwid file header : %s", strerror(errno)); /* cleanup partially written header */ if (ftruncate(fd, 0) < 0) condlog(0, "Cannot truncate header : %s", strerror(errno)); goto out_file; } if (!mp || !mp->allocated) { ret = 0; goto out_file; } vector_foreach_slot(mp, mpp, i) { if (write_out_wwid(fd, mpp->wwid) < 0) goto out_file; } ret = 0; out_file: pthread_cleanup_pop(1); out: return ret; } int do_remove_wwid(int fd, char *str) { char buf[4097]; char *ptr; off_t start = 0; int bytes; while (1) { if (lseek(fd, start, SEEK_SET) < 0) { condlog(0, "wwid file read lseek failed : %s", strerror(errno)); return -1; } bytes = read(fd, buf, 4096); if (bytes < 0) { if (errno == EINTR || errno == EAGAIN) continue; condlog(0, "failed to read from wwids file : %s", strerror(errno)); return -1; } if (!bytes) /* didn't find wwid to remove */ return 1; buf[bytes] = '\0'; ptr = strstr(buf, str); if (ptr != NULL) { condlog(3, "found '%s'", str); if (lseek(fd, start + (ptr - buf), SEEK_SET) < 0) { condlog(0, "write lseek failed : %s", strerror(errno)); return -1; } while (1) { if (write(fd, "#", 1) < 0) { if (errno == EINTR || errno == EAGAIN) continue; condlog(0, "failed to write to wwids file : %s", strerror(errno)); return -1; } return 0; } } ptr = strrchr(buf, '\n'); if (ptr == NULL) { /* shouldn't happen, assume it is EOF */ condlog(4, "couldn't find newline, assuming end of file"); return 1; } start = start + (ptr - buf) + 1; } } int remove_wwid(char *wwid) { int fd = -1; int len, can_write; char *str; int ret = -1; len = strlen(wwid) + 4; /* two slashes the newline and a zero byte */ str = malloc(len); if (str == NULL) { condlog(0, "can't allocate memory to remove wwid : %s", strerror(errno)); return -1; } pthread_cleanup_push(free, str); if (snprintf(str, len, "/%s/\n", wwid) >= len) { condlog(0, "string overflow trying to remove wwid"); ret = -1; goto out; } condlog(3, "removing line '%s' from wwids file", str); fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) { ret = -1; goto out; } pthread_cleanup_push(cleanup_fd_ptr, &fd); if (!can_write) { ret = -1; condlog(0, "cannot remove wwid. wwids file is read-only"); } else ret = do_remove_wwid(fd, str); pthread_cleanup_pop(1); out: /* free(str) */ pthread_cleanup_pop(1); return ret; } int check_wwids_file(char *wwid, int write_wwid) { int fd, can_write, found, ret; FILE *f; fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) return -1; f = fdopen(fd, "r"); if (!f) { condlog(0,"can't fdopen wwids file : %s", strerror(errno)); close(fd); return -1; } found = lookup_wwid(f, wwid); if (found) { ret = 0; goto out; } if (!write_wwid) { ret = -1; goto out; } if (!can_write) { condlog(0, "wwids file is read-only. Can't write wwid"); ret = -1; goto out; } if (fflush(f) != 0) { condlog(0, "cannot fflush wwids file stream : %s", strerror(errno)); ret = -1; goto out; } ret = write_out_wwid(fd, wwid); out: fclose(f); return ret; } int should_multipath(struct path *pp1, vector pathvec, vector mpvec) { int i, find_multipaths; struct path *pp2; struct config *conf; conf = get_multipath_config(); find_multipaths = conf->find_multipaths; put_multipath_config(conf); if (find_multipaths == FIND_MULTIPATHS_OFF || find_multipaths == FIND_MULTIPATHS_GREEDY) return 1; condlog(4, "checking if %s should be multipathed", pp1->dev); if (find_multipaths != FIND_MULTIPATHS_STRICT) { char tmp_wwid[WWID_SIZE]; struct multipath *mp = find_mp_by_wwid(mpvec, pp1->wwid); if (mp != NULL && dm_get_wwid(mp->alias, tmp_wwid, WWID_SIZE) == DMP_OK && !strncmp(tmp_wwid, pp1->wwid, WWID_SIZE)) { condlog(3, "wwid %s is already multipathed, keeping it", pp1->wwid); return 1; } vector_foreach_slot(pathvec, pp2, i) { if (pp1->dev == pp2->dev) continue; if (strncmp(pp1->wwid, pp2->wwid, WWID_SIZE) == 0) { condlog(3, "found multiple paths with wwid %s, " "multipathing %s", pp1->wwid, pp1->dev); return 1; } } } if (check_wwids_file(pp1->wwid, 0) < 0) { condlog(3, "wwid %s not in wwids file, skipping %s", pp1->wwid, pp1->dev); return 0; } condlog(3, "found wwid %s in wwids file, multipathing %s", pp1->wwid, pp1->dev); return 1; } int remember_wwid(char *wwid) { int ret = check_wwids_file(wwid, 1); if (ret < 0){ condlog(3, "failed writing wwid %s to wwids file", wwid); return -1; } if (ret == 1) condlog(3, "wrote wwid %s to wwids file", wwid); else condlog(4, "wwid %s already in wwids file", wwid); return ret; } static const char shm_dir[] = MULTIPATH_SHM_BASE "failed_wwids"; static void print_failed_wwid_result(const char * msg, const char *wwid, int r) { switch(r) { case WWID_FAILED_ERROR: condlog(1, "%s: %s: %m", msg, wwid); return; case WWID_IS_FAILED: case WWID_IS_NOT_FAILED: condlog(4, "%s: %s is %s", msg, wwid, r == WWID_IS_FAILED ? "failed" : "good"); return; case WWID_FAILED_CHANGED: condlog(3, "%s: %s", msg, wwid); } } int is_failed_wwid(const char *wwid) { struct stat st; char path[PATH_MAX]; int r; if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { condlog(1, "%s: path name overflow", __func__); return WWID_FAILED_ERROR; } if (lstat(path, &st) == 0) r = WWID_IS_FAILED; else if (errno == ENOENT) r = WWID_IS_NOT_FAILED; else r = WWID_FAILED_ERROR; print_failed_wwid_result("is_failed", wwid, r); return r; } int mark_failed_wwid(const char *wwid) { char tmpfile[WWID_SIZE + 2 * sizeof(long) + 1]; int r = WWID_FAILED_ERROR, fd, dfd; dfd = open(shm_dir, O_RDONLY|O_DIRECTORY); if (dfd == -1 && errno == ENOENT) { char path[sizeof(shm_dir) + 2]; /* arg for ensure_directories_exist() must not end with "/" */ safe_sprintf(path, "%s/_", shm_dir); ensure_directories_exist(path, 0700); dfd = open(shm_dir, O_RDONLY|O_DIRECTORY); } if (dfd == -1) { condlog(1, "%s: can't setup %s: %m", __func__, shm_dir); return WWID_FAILED_ERROR; } safe_sprintf(tmpfile, "%s.%lx", wwid, (long)getpid()); fd = openat(dfd, tmpfile, O_RDONLY | O_CREAT | O_EXCL, S_IRUSR); if (fd >= 0) close(fd); else goto out_closedir; if (linkat(dfd, tmpfile, dfd, wwid, 0) == 0) r = WWID_FAILED_CHANGED; else if (errno == EEXIST) r = WWID_FAILED_UNCHANGED; else r = WWID_FAILED_ERROR; if (unlinkat(dfd, tmpfile, 0) == -1) condlog(2, "%s: failed to unlink %s/%s: %m", __func__, shm_dir, tmpfile); out_closedir: close(dfd); print_failed_wwid_result("mark_failed", wwid, r); return r; } int unmark_failed_wwid(const char *wwid) { char path[PATH_MAX]; int r; if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { condlog(1, "%s: path name overflow", __func__); return WWID_FAILED_ERROR; } if (unlink(path) == 0) r = WWID_FAILED_CHANGED; else if (errno == ENOENT) r = WWID_FAILED_UNCHANGED; else r = WWID_FAILED_ERROR; print_failed_wwid_result("unmark_failed", wwid, r); return r; } multipath-tools-0.11.1/libmultipath/wwids.h000066400000000000000000000015071475246302400207550ustar00rootroot00000000000000/* * Copyright (c) 2010 Benjamin Marzinski, Redhat */ #ifndef WWIDS_H_INCLUDED #define WWIDS_H_INCLUDED #define WWIDS_FILE_HEADER \ "# Multipath wwids, Version : 1.0\n" \ "# NOTE: This file is automatically maintained by multipath and multipathd.\n" \ "# You should not need to edit this file in normal circumstances.\n" \ "#\n" \ "# Valid WWIDs:\n" int should_multipath(struct path *pp, vector pathvec, vector mpvec); int remember_wwid(char *wwid); int check_wwids_file(char *wwid, int write_wwid); int remove_wwid(char *wwid); int replace_wwids(vector mp); enum { WWID_IS_NOT_FAILED = 0, WWID_IS_FAILED, WWID_FAILED_UNCHANGED, WWID_FAILED_CHANGED, WWID_FAILED_ERROR = -1, }; int is_failed_wwid(const char *wwid); int mark_failed_wwid(const char *wwid); int unmark_failed_wwid(const char *wwid); #endif /* WWIDS_H_INCLUDED */ multipath-tools-0.11.1/mpathpersist/000077500000000000000000000000001475246302400174715ustar00rootroot00000000000000multipath-tools-0.11.1/mpathpersist/Makefile000066400000000000000000000016571475246302400211420ustar00rootroot00000000000000include ../Makefile.inc CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) CFLAGS += $(BIN_CFLAGS) LDFLAGS += $(BIN_LDFLAGS) LIBDEPS += -L$(mpathpersistdir) -lmpathpersist -L$(multipathdir) -lmultipath \ -L$(mpathutildir) -lmpathutil -L$(mpathcmddir) -lmpathcmd -lpthread -ldevmapper -ludev EXEC = mpathpersist MANPAGES := mpathpersist.8 OBJS = main.o all: $(EXEC) $(MANPAGES) $(EXEC): $(OBJS) $(Q)$(CC) $(OBJS) -o $(EXEC) $(LDFLAGS) $(CFLAGS) $(LIBDEPS) install: $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir) $(Q)$(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir)/ $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 clean: dep_clean $(Q)$(RM) core *.o $(EXEC) $(MANPAGES) include $(wildcard $(OBJS:.o=.d)) uninstall: $(Q)$(RM) $(DESTDIR)$(bindir)/$(EXEC) $(Q)$(RM) $(DESTDIR)$(mandir)/man8/$(EXEC).8 dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/mpathpersist/main.c000066400000000000000000000616471475246302400205770ustar00rootroot00000000000000#include #include #include #include #include #include "checkers.h" #include "vector.h" #include "config.h" #include "structs.h" #include #include #include "mpath_persist.h" #include "mpath_persist_int.h" #include "main.h" #include "debug.h" #include #include #include #include #include "version.h" static const char * pr_type_strs[] = { "obsolete [0]", "Write Exclusive", "obsolete [2]", "Exclusive Access", "obsolete [4]", "Write Exclusive, registrants only", "Exclusive Access, registrants only", "Write Exclusive, all registrants", "Exclusive Access, all registrants", "obsolete [9]", "obsolete [0xa]", "obsolete [0xb]", "obsolete [0xc]", "obsolete [0xd]", "obsolete [0xe]", "obsolete [0xf]", }; int get_transportids_length(unsigned char * transportid_arr, int max_transportid, int num_transportids); void mpath_print_buf_readcap(struct prin_resp *pr_buff); void mpath_print_buf_readfullstat(struct prin_resp *pr_buff); void mpath_print_buf_readresv(struct prin_resp *pr_buff); void mpath_print_buf_readkeys(struct prin_resp *pr_buff); void dumpHex(const char* str, int len, int no_ascii); void * mpath_alloc_prin_response(int prin_sa); void mpath_print_transport_id(struct prin_fulldescr *fdesc); int construct_transportid(const char * inp, struct transportid transid[], int num_transportids); void rcu_register_thread_memb(void) {} void rcu_unregister_thread_memb(void) {} static int verbose, loglevel, noisy; static int handle_args(int argc, char * argv[], int line); static int do_batch_file(const char *batch_fn) { char command[] = "mpathpersist"; const int ARGV_CHUNK = 2; const char delims[] = " \t\n"; size_t len = 0; char *line = NULL; ssize_t n; int nline = 0; int argl = ARGV_CHUNK; FILE *fl; char **argv = calloc(argl, sizeof(*argv)); int ret = MPATH_PR_SUCCESS; if (argv == NULL) return MPATH_PR_OTHER; fl = fopen(batch_fn, "r"); if (fl == NULL) { fprintf(stderr, "unable to open %s: %s\n", batch_fn, strerror(errno)); free(argv); return MPATH_PR_SYNTAX_ERROR; } else { if (verbose >= 2) fprintf(stderr, "running batch file %s\n", batch_fn); } while ((n = getline(&line, &len, fl)) != -1) { char *_token, *token; int argc = 0; int rv; nline++; argv[argc++] = command; if (line[n-1] == '\n') line[n-1] = '\0'; if (verbose >= 3) fprintf(stderr, "processing line %d: %s\n", nline, line); for (token = strtok_r(line, delims, &_token); token != NULL && *token != '#'; token = strtok_r(NULL, delims, &_token)) { if (argc >= argl) { int argn = argl + ARGV_CHUNK; char **tmp; tmp = realloc(argv, argn * sizeof(*argv)); if (tmp == NULL) break; argv = tmp; argl = argn; } if (argc == 1 && !strcmp(token, command)) continue; argv[argc++] = token; } if (argc <= 1) continue; if (verbose >= 2) { int i; fprintf(stderr, "## file %s line %d:", batch_fn, nline); for (i = 0; i < argc; i++) fprintf(stderr, " %s", argv[i]); fprintf(stderr, "\n"); } optind = 0; rv = handle_args(argc, argv, nline); if (rv != MPATH_PR_SUCCESS) ret = rv; } fclose(fl); free(argv); free(line); return ret; } static struct prout_param_descriptor * alloc_prout_param_descriptor(int num_transportid) { struct prout_param_descriptor *paramp; if (num_transportid < 0 || num_transportid > MPATH_MX_TIDS) return NULL; paramp= malloc(sizeof(struct prout_param_descriptor) + (sizeof(struct transportid *) * num_transportid)); if (!paramp) return NULL; memset(paramp, 0, sizeof(struct prout_param_descriptor) + (sizeof(struct transportid *) * num_transportid)); return paramp; } static void free_prout_param_descriptor(struct prout_param_descriptor *paramp) { unsigned int i; if (!paramp) return; for (i = 0; i < paramp->num_transportid; i++) free(paramp->trnptid_list[i]); free(paramp); } static int handle_args(int argc, char * argv[], int nline) { int c; int fd = -1; const char *device_name = NULL; int num_prin_sa = 0; int num_prout_sa = 0; int prin_flag = 0; int prout_flag = 0; int ret = 0; int hex = 0; uint64_t param_sark = 0; unsigned int prout_type = 0; int param_alltgpt = 0; int param_aptpl = 0; uint64_t param_rk = 0; unsigned int param_rtp = 0; int num_transportids = 0; struct transportid transportids[MPATH_MX_TIDS]; int prout = 1; int prin = 1; int prin_sa = -1; int prout_sa = -1; char *batch_fn = NULL; void *resp = NULL; memset(transportids, 0, MPATH_MX_TIDS * sizeof(struct transportid)); while (1) { int option_index = 0; c = getopt_long (argc, argv, "v:Cd:hHioYZK:S:PAT:skrGILcRX:l:f:", long_options, &option_index); if (c == -1) break; switch (c) { case 'f': if (nline != 0) { fprintf(stderr, "ERROR: -f option not allowed in batch file\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } if (batch_fn != NULL) { fprintf(stderr, "ERROR: -f option can be used at most once\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } batch_fn = strdup(optarg); break; case 'v': if (nline == 0 && 1 != sscanf (optarg, "%d", &loglevel)) { fprintf (stderr, "bad argument to '--verbose'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } break; case 'C': prout_sa = MPATH_PROUT_CLEAR_SA; ++num_prout_sa; break; case 'd': device_name = optarg; break; case 'h': usage (); free(batch_fn); return 0; case 'H': hex=1; break; case 'i': prin_flag = 1; break; case 'o': prout_flag = 1; break; case 'Y': param_alltgpt = 1; break; case 'Z': param_aptpl = 1; break; case 'K': if (1 != sscanf (optarg, "%" SCNx64 "", ¶m_rk)) { fprintf (stderr, "bad argument to '--param-rk'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } break; case 'S': if (1 != sscanf (optarg, "%" SCNx64 "", ¶m_sark)) { fprintf (stderr, "bad argument to '--param-sark'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } break; case 'P': prout_sa = MPATH_PROUT_PREE_SA; ++num_prout_sa; break; case 'A': prout_sa = MPATH_PROUT_PREE_AB_SA; ++num_prout_sa; break; case 'T': if (1 != sscanf (optarg, "%x", &prout_type)) { fprintf (stderr, "bad argument to '--prout-type'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } break; case 's': prin_sa = MPATH_PRIN_RFSTAT_SA; ++num_prin_sa; break; case 'k': prin_sa = MPATH_PRIN_RKEY_SA; ++num_prin_sa; break; case 'r': prin_sa = MPATH_PRIN_RRES_SA; ++num_prin_sa; break; case 'G': prout_sa = MPATH_PROUT_REG_SA; ++num_prout_sa; break; case 'I': prout_sa = MPATH_PROUT_REG_IGN_SA; ++num_prout_sa; break; case 'L': prout_sa = MPATH_PROUT_REL_SA; ++num_prout_sa; break; case 'c': prin_sa = MPATH_PRIN_RCAP_SA; ++num_prin_sa; break; case 'R': prout_sa = MPATH_PROUT_RES_SA; ++num_prout_sa; break; case 'X': if (0 != construct_transportid(optarg, transportids, num_transportids)) { fprintf(stderr, "bad argument to '--transport-id'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } ++num_transportids; break; case 'l': if (1 != sscanf(optarg, "%u", &mpath_mx_alloc_len)) { fprintf(stderr, "bad argument to '--alloc-length'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } else if (MPATH_MAX_PARAM_LEN < mpath_mx_alloc_len) { fprintf(stderr, "'--alloc-length' argument exceeds maximum" " limit(%d)\n", MPATH_MAX_PARAM_LEN); ret = MPATH_PR_SYNTAX_ERROR; goto out; } break; default: fprintf(stderr, "unrecognised switch " "code 0x%x ??\n", c); ret = MPATH_PR_SYNTAX_ERROR; goto out; } } if (optind < argc) { if (NULL == device_name) { device_name = argv[optind]; ++optind; } if (optind < argc) { for (; optind < argc; ++optind) fprintf (stderr, "Unexpected extra argument: %s\n", argv[optind]); ret = MPATH_PR_SYNTAX_ERROR; goto out; } } if (nline == 0) { /* set verbosity */ noisy = (loglevel >= 3) ? 1 : hex; verbose = (loglevel >= 3)? 3: loglevel; ret = mpath_persistent_reserve_init_vecs(verbose); if (ret != MPATH_PR_SUCCESS) goto out; } if ((prout_flag + prin_flag) == 0 && batch_fn == NULL) { fprintf (stderr, "choose either '--in' or '--out' \n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } if ((prout_flag + prin_flag) > 1) { fprintf (stderr, "choose either '--in' or '--out' \n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } else if (prout_flag) { /* syntax check on PROUT arguments */ prin = 0; if ((1 != num_prout_sa) || (0 != num_prin_sa)) { fprintf (stderr, " For Persistent Reserve Out only one " "appropriate\n service action must be " "chosen \n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } } else if (prin_flag) { /* syntax check on PRIN arguments */ prout = 0; if (num_prout_sa > 0) { fprintf (stderr, " When a service action for Persistent " "Reserve Out is chosen the\n" " '--out' option must be given \n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } if (0 == num_prin_sa) { fprintf (stderr, " No service action given for Persistent Reserve IN\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } else if (num_prin_sa > 1) { fprintf (stderr, " Too many service actions given; choose " "one only\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } } else { if (batch_fn == NULL) ret = MPATH_PR_SYNTAX_ERROR; goto out; } if ((param_rtp) && (MPATH_PROUT_REG_MOV_SA != prout_sa)) { fprintf (stderr, " --relative-target-port" " only useful with --register-move\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } if (((MPATH_PROUT_RES_SA == prout_sa) || (MPATH_PROUT_REL_SA == prout_sa) || (MPATH_PROUT_PREE_SA == prout_sa) || (MPATH_PROUT_PREE_AB_SA == prout_sa)) && (0 == prout_type)) { fprintf(stderr, "Warning: --prout-type probably needs to be " "given\n"); } if ((verbose > 2) && num_transportids) { fprintf (stderr, "number of transport-ids decoded from " "command line : %d\n", num_transportids); } if (device_name == NULL) { fprintf (stderr, "No device name given \n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } /* open device */ if ((fd = open (device_name, O_RDONLY)) < 0) { fprintf (stderr, "%s: error opening file (rw) fd=%d\n", device_name, fd); ret = MPATH_PR_FILE_ERROR; goto out; } if (prin) { resp = mpath_alloc_prin_response(prin_sa); if (!resp) { fprintf (stderr, "failed to allocate PRIN response buffer\n"); ret = MPATH_PR_OTHER; goto out_fd; } ret = mpath_persistent_reserve_in__(fd, prin_sa, resp, noisy); if (ret != MPATH_PR_SUCCESS ) { fprintf (stderr, "Persistent Reserve IN command failed\n"); free(resp); goto out_fd; } switch(prin_sa) { case MPATH_PRIN_RKEY_SA: mpath_print_buf_readkeys(resp); break; case MPATH_PRIN_RRES_SA: mpath_print_buf_readresv(resp); break; case MPATH_PRIN_RCAP_SA: mpath_print_buf_readcap(resp); break; case MPATH_PRIN_RFSTAT_SA: mpath_print_buf_readfullstat(resp); break; } free(resp); } else if (prout) { int j; struct prout_param_descriptor *paramp; paramp = alloc_prout_param_descriptor(num_transportids); if (!paramp) { fprintf(stderr, "malloc paramp failed\n"); ret = MPATH_PR_OTHER; goto out_fd; } for (j = 7; j >= 0; --j) { paramp->key[j] = (param_rk & 0xff); param_rk >>= 8; } for (j = 7; j >= 0; --j) { paramp->sa_key[j] = (param_sark & 0xff); param_sark >>= 8; } if (param_alltgpt) paramp->sa_flags |= MPATH_F_ALL_TG_PT_MASK; if (param_aptpl) paramp->sa_flags |= MPATH_F_APTPL_MASK; if (num_transportids) { paramp->sa_flags |= MPATH_F_SPEC_I_PT_MASK; paramp->num_transportid = num_transportids; for (j = 0 ; j < num_transportids; j++) { paramp->trnptid_list[j] = (struct transportid *)malloc(sizeof(struct transportid)); if (!paramp->trnptid_list[j]) { fprintf(stderr, "malloc paramp->trnptid_list[%d] failed.\n", j); ret = MPATH_PR_OTHER; free_prout_param_descriptor(paramp); goto out_fd; } memcpy(paramp->trnptid_list[j], &transportids[j],sizeof(struct transportid)); } } /* PROUT commands other than 'register and move' */ ret = mpath_persistent_reserve_out__(fd, prout_sa, 0, prout_type, paramp, noisy); free_prout_param_descriptor(paramp); } if (ret != MPATH_PR_SUCCESS) { switch(ret) { case MPATH_PR_SENSE_UNIT_ATTENTION: printf("persistent reserve out: scsi status: Unit Attention\n"); break; case MPATH_PR_RESERV_CONFLICT: printf("persistent reserve out: scsi status: Reservation Conflict\n"); break; } printf("PR out: command failed\n"); } out_fd: close (fd); out : if (ret == MPATH_PR_SYNTAX_ERROR) { free(batch_fn); if (nline == 0) usage(); else fprintf(stderr, "syntax error on line %d in batch file\n", nline); } else if (batch_fn != NULL) { int rv = do_batch_file(batch_fn); free(batch_fn); ret = ret == 0 ? rv : ret; } if (nline == 0) mpath_persistent_reserve_free_vecs(); return (ret >= 0) ? ret : MPATH_PR_OTHER; } int main(int argc, char *argv[]) { int ret; if (optind == argc) { fprintf (stderr, "No parameter used\n"); usage (); exit (1); } if (getuid () != 0) { fprintf (stderr, "need to be root\n"); exit (1); } if (libmpathpersist_init()) { exit(1); } if (atexit((void(*)(void))libmpathpersist_exit)) fprintf(stderr, "failed to register cleanup handler for libmpathpersist: %m"); ret = handle_args(argc, argv, 0); return (ret >= 0) ? ret : MPATH_PR_OTHER; } int get_transportids_length(unsigned char * transportid_arr, int max_transportid, int num_transportids) { int compact_len = 0; unsigned char * ucp = transportid_arr; int k, off, protocol_id, len; for (k = 0, off = 0; ((k < num_transportids) && (k < max_transportid)); ++k, off += MPATH_MX_TID_LEN) { protocol_id = ucp[off] & 0xf; if (5 == protocol_id) { len = (ucp[off + 2] << 8) + ucp[off + 3] + 4; if (len < 24) len = 24; if (off > compact_len) memmove(ucp + compact_len, ucp + off, len); compact_len += len; } else { if (off > compact_len) memmove(ucp + compact_len, ucp + off, 24); compact_len += 24; } } return compact_len; } void mpath_print_buf_readkeys( struct prin_resp *pr_buff) { int i,j,k, num; unsigned char *keyp; uint64_t prkey; printf(" PR generation=0x%x, ", pr_buff->prin_descriptor.prin_readkeys.prgeneration); num = pr_buff->prin_descriptor.prin_readkeys.additional_length / 8; if (0 == num) { printf(" 0 registered reservation key.\n"); return; } else if (1 == num) printf(" 1 registered reservation key follows:\n"); else printf(" %d registered reservation keys follow:\n", num); keyp = (unsigned char *)&pr_buff->prin_descriptor.prin_readkeys.key_list[0]; for (i = 0; i < num ; i++) { prkey = 0; for (j = 0; j < 8; ++j) { if (j > 0) prkey <<= 8; prkey |= keyp[j]; } printf(" 0x%" PRIx64 "\n", prkey); k=8*i+j; keyp = (unsigned char *)&pr_buff->prin_descriptor.prin_readkeys.key_list[k]; } } void mpath_print_buf_readresv( struct prin_resp *pr_buff) { int j, num, scope=0, type=0; unsigned char *keyp; uint64_t prkey; num = pr_buff->prin_descriptor.prin_readresv.additional_length / 8; if (0 == num) { printf(" PR generation=0x%x, there is NO reservation held \n", pr_buff->prin_descriptor.prin_readresv.prgeneration); return ; } else printf(" PR generation=0x%x, Reservation follows:\n", pr_buff->prin_descriptor.prin_readresv.prgeneration); keyp = (unsigned char *)&pr_buff->prin_descriptor.prin_readkeys.key_list[0]; prkey = 0; for (j = 0; j < 8; ++j) { if (j > 0) prkey <<= 8; prkey |= keyp[j]; } printf(" Key = 0x%" PRIx64 "\n", prkey); scope = (pr_buff->prin_descriptor.prin_readresv.scope_type >> 4) & 0x0f; type = pr_buff->prin_descriptor.prin_readresv.scope_type & 0x0f; if (scope == 0) printf(" scope = LU_SCOPE, type = %s", pr_type_strs[type]); else printf(" scope = %d, type = %s", scope, pr_type_strs[type]); printf("\n"); } void mpath_print_buf_readcap( struct prin_resp *pr_buff) { if ( pr_buff->prin_descriptor.prin_readcap.length <= 2 ) { fprintf(stderr, "Unexpected response for PRIN Report " "Capabilities\n"); return; //MALFORMED; } printf("Report capabilities response:\n"); printf(" Compatible Reservation Handling(CRH): %d\n", !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x10)); printf(" Specify Initiator Ports Capable(SIP_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x8)); printf(" All Target Ports Capable(ATP_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x4 )); printf(" Persist Through Power Loss Capable(PTPL_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0])); printf(" Type Mask Valid(TMV): %d\n", !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x80)); printf(" Allow Commands: %d\n", !!(( pr_buff->prin_descriptor.prin_readcap.flags[1] >> 4) & 0x7)); printf(" Persist Through Power Loss Active(PTPL_A): %d\n", !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x1)); if(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x80) { printf(" Support indicated in Type mask:\n"); printf(" %s: %d\n", pr_type_strs[7], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x80); printf(" %s: %d\n", pr_type_strs[6], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x40); printf(" %s: %d\n", pr_type_strs[5], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x20); printf(" %s: %d\n", pr_type_strs[3], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x8); printf(" %s: %d\n", pr_type_strs[1], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x2); printf(" %s: %d\n", pr_type_strs[8], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x100); } } void mpath_print_buf_readfullstat( struct prin_resp *pr_buff) { int i,j, num; uint64_t prkey; uint16_t rel_pt_addr; unsigned char * keyp; num = pr_buff->prin_descriptor.prin_readfd.number_of_descriptor; if (0 == num) { printf(" PR generation=0x%x \n", pr_buff->prin_descriptor.prin_readfd.prgeneration); return ; } else printf(" PR generation=0x%x \n", pr_buff->prin_descriptor.prin_readfd.prgeneration); for (i = 0 ; i < num; i++) { keyp = (unsigned char *)&pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key; prkey = 0; for (j = 0; j < 8; ++j) { if (j > 0) prkey <<= 8; prkey |= *keyp; ++keyp; } printf(" Key = 0x%" PRIx64 "\n", prkey); if (pr_buff->prin_descriptor.prin_readfd.descriptors[i]->flag & 0x02) printf(" All target ports bit set\n"); else { printf(" All target ports bit clear\n"); rel_pt_addr = pr_buff->prin_descriptor.prin_readfd.descriptors[i]->rtpi; printf(" Relative port address: 0x%x\n", rel_pt_addr); } if (pr_buff->prin_descriptor.prin_readfd.descriptors[i]->flag & 0x1) { printf(" << Reservation holder >>\n"); j = ((pr_buff->prin_descriptor.prin_readfd.descriptors[i]->scope_type >> 4) & 0xf); if (0 == j) printf(" scope: LU_SCOPE, "); else printf(" scope: %d ", j); j = (pr_buff->prin_descriptor.prin_readfd.descriptors[i]->scope_type & 0xf); printf(" type: %s\n", pr_type_strs[j]); } else printf(" not reservation holder\n"); mpath_print_transport_id(pr_buff->prin_descriptor.prin_readfd.descriptors[i]); } } static void usage(void) { fprintf(stderr, VERSION_STRING); fprintf(stderr, "Usage: mpathpersist [OPTIONS] [DEVICE]\n" " Options:\n" " --verbose|-v level verbosity level\n" " 0 Critical messages\n" " 1 Error messages\n" " 2 Warning messages\n" " 3 Informational messages\n" " 4 Informational messages with trace enabled\n" " --clear|-C PR Out: Clear\n" " --device=DEVICE|-d DEVICE query or change DEVICE\n" " --batch-file|-f FILE run commands from FILE\n" " --help|-h output this usage message\n" " --hex|-H output response in hex\n" " --in|-i request PR In command \n" " --out|-o request PR Out command\n" " --param-alltgpt|-Y PR Out parameter 'ALL_TG_PT\n" " --param-aptpl|-Z PR Out parameter 'APTPL'\n" " --read-keys|-k PR In: Read Keys\n" " --param-rk=RK|-K RK PR Out parameter reservation key\n" " --param-sark=SARK|-S SARK PR Out parameter service action\n" " reservation key (SARK is in hex)\n" " --preempt|-P PR Out: Preempt\n" " --preempt-abort|-A PR Out: Preempt and Abort\n" " --prout-type=TYPE|-T TYPE PR Out command type\n" " --read-full-status|-s PR In: Read Full Status\n" " --read-keys|-k PR In: Read Keys\n" " --read-reservation|-r PR In: Read Reservation\n" " --register|-G PR Out: Register\n" " --register-ignore|-I PR Out: Register and Ignore\n" " --release|-L PR Out: Release\n" " --report-capabilities|-c PR In: Report Capabilities\n" " --reserve|-R PR Out: Reserve\n" " --transport-id=TIDS|-X TIDS TransportIDs can be mentioned\n" " in several forms\n" " --alloc-length=LEN|-l LEN PR In: maximum allocation length\n"); } void mpath_print_transport_id(struct prin_fulldescr *fdesc) { switch (fdesc->trnptid.protocol_id) { case MPATH_PROTOCOL_ID_FC: printf(" FCP-2 "); if (0 != fdesc->trnptid.format_code) printf(" [Unexpected format code: %d]\n", fdesc->trnptid.format_code); dumpHex((const char *)fdesc->trnptid.n_port_name, 8, 0); break; case MPATH_PROTOCOL_ID_ISCSI: printf(" iSCSI "); if (0 == fdesc->trnptid.format_code) { printf("name: %.*s\n", (int)sizeof(fdesc->trnptid.iscsi_name), fdesc->trnptid.iscsi_name); }else if (1 == fdesc->trnptid.format_code){ printf("world wide unique port id: %.*s\n", (int)sizeof(fdesc->trnptid.iscsi_name), fdesc->trnptid.iscsi_name); }else { printf(" [Unexpected format code: %d]\n", fdesc->trnptid.format_code); dumpHex((const char *)fdesc->trnptid.iscsi_name, (int)sizeof(fdesc->trnptid.iscsi_name), 0); } break; case MPATH_PROTOCOL_ID_SAS: printf(" SAS "); if (0 != fdesc->trnptid.format_code) printf(" [Unexpected format code: %d]\n", fdesc->trnptid.format_code); dumpHex((const char *)fdesc->trnptid.sas_address, 8, 0); break; default: return; } } int construct_transportid(const char * lcp, struct transportid transid[], int num_transportids) { int k = 0; int j, n, b, c, len, alen; const char * ecp; const char * isip; if ((0 == memcmp("fcp,", lcp, 4)) || (0 == memcmp("FCP,", lcp, 4))) { lcp += 4; k = strspn(lcp, "0123456789aAbBcCdDeEfF"); len = strlen(lcp); if (len != k) { fprintf(stderr, "badly formed symbolic FCP TransportID: %s\n", lcp); return 1; } transid[num_transportids].format_code = MPATH_PROTOCOL_ID_FC; transid[num_transportids].protocol_id = MPATH_WWUI_DEVICE_NAME; for (k = 0, j = 0, b = 0; k < 16; ++k) { c = lcp[k]; if (isdigit(c)) n = c - 0x30; else if (isupper(c)) n = c - 0x37; else n = c - 0x57; if (k & 1) { transid[num_transportids].n_port_name[j] = b | n; ++j; } else b = n << 4; } goto my_cont_b; } if ((0 == memcmp("sas,", lcp, 4)) || (0 == memcmp("SAS,", lcp, 4))) { lcp += 4; k = strspn(lcp, "0123456789aAbBcCdDeEfF"); len =strlen(lcp); if (len != k) { fprintf(stderr, "badly formed symbolic SAS TransportID: %s\n", lcp); return 1; } transid[num_transportids].format_code = MPATH_PROTOCOL_ID_SAS; transid[num_transportids].protocol_id = MPATH_WWUI_DEVICE_NAME; memcpy(&transid[num_transportids].sas_address, lcp, 8); goto my_cont_b; } if (0 == memcmp("iqn.", lcp, 4)) { ecp = strpbrk(lcp, " \t"); isip = strstr(lcp, ",i,0x"); if (ecp && (isip > ecp)) isip = NULL; len = ecp ? (ecp - lcp) : (int)strlen(lcp); transid[num_transportids].format_code = (isip ? MPATH_WWUI_PORT_IDENTIFIER:MPATH_WWUI_DEVICE_NAME); transid[num_transportids].protocol_id = MPATH_PROTOCOL_ID_ISCSI; alen = len + 1; /* at least one trailing null */ if (alen < 20) alen = 20; else if (0 != (alen % 4)) alen = ((alen / 4) + 1) * 4; if (alen > 241) { /* sam5r02.pdf A.2 (Annex) */ fprintf(stderr, "iSCSI name too long, alen=%d\n", alen); return 0; } transid[num_transportids].iscsi_name[1] = alen & 0xff; memcpy(&transid[num_transportids].iscsi_name[2], lcp, len); goto my_cont_b; } my_cont_b: if (k >= MPATH_MAX_PARAM_LEN) { fprintf(stderr, "build_transportid: array length exceeded\n"); return 1; } return 0; } multipath-tools-0.11.1/mpathpersist/main.h000066400000000000000000000016421475246302400205710ustar00rootroot00000000000000#ifndef MPATHPERSIST_MAIN_H_INCLUDED #define MPATHPERSIST_MAIN_H_INCLUDED static struct option long_options[] = { {"verbose", 1, NULL, 'v'}, {"clear", 0, NULL, 'C'}, {"device", 1, NULL, 'd'}, {"batch-file", 1, NULL, 'f' }, {"help", 0, NULL, 'h'}, {"hex", 0, NULL, 'H'}, {"in", 0, NULL, 'i'}, {"out", 0, NULL, 'o'}, {"param-alltgpt", 0, NULL, 'Y'}, {"param-aptpl", 0, NULL, 'Z'}, {"param-rk", 1, NULL, 'K'}, {"param-sark", 1, NULL, 'S'}, {"preempt", 0, NULL, 'P'}, {"preempt-abort", 0, NULL, 'A'}, {"prout-type", 1, NULL, 'T'}, {"read-full-status", 0, NULL, 's'}, {"read-keys", 0, NULL, 'k'}, {"read-reservation", 0, NULL, 'r'}, {"register", 0, NULL, 'G'}, {"register-ignore", 0, NULL, 'I'}, {"release", 0, NULL, 'L'}, {"report-capabilities", 0, NULL, 'c'}, {"reserve", 0, NULL, 'R'}, {"transport-id", 1, NULL, 'X'}, {"alloc-length", 1, NULL, 'l'}, {NULL, 0, NULL, 0} }; static void usage(void); #endif multipath-tools-0.11.1/mpathpersist/mpathpersist.8.in000066400000000000000000000202431475246302400227130ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MPATHPERSIST 8 2021-11-12 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . mpathpersist \- Manages SCSI persistent reservations on dm multipath devices. . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B mpathpersist .RB [\| OPTIONS \|] .I device . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . This utility is used to manage SCSI persistent reservations on Device Mapper Multipath devices. To be able to use this functionality, the \fIreservation_key\fR attribute must be defined in the \fI@CONFIGFILE@\fR file. Otherwise the \fBmultipathd\fR daemon will not check for persistent reservation for newly discovered paths or reinstated paths. . .LP \fBmpathpersist\fR supports the same command-line options as the \fBsg_persist\fR utility. . Consult the \fBsg_persist (8)\fR manual page for an in-depth discussion of the various options. . .\" ---------------------------------------------------------------------------- .SH OPTIONS .\" ---------------------------------------------------------------------------- . .TP .BI \-verbose|\-v " level" Verbosity: .RS .TP 5 .I 0 Critical messages. .TP .I 1 Error messages. .TP .I 2 Warning messages. .TP .I 3 Informational messages. .TP .I 4 Informational messages with trace enabled. .RE . .TP .BI \--device=\fIDEVICE\fB|\-d " DEVICE" Query or change DEVICE. . .TP .BI \--batch-file=\fIDEVICE\fB|\-f " FILE" Read commands from \fIFILE\fR. See section \(dqBATCH FILES\(dq below. This option can be given at most once. . .TP .B \--help|\-h Output this usage message. . .TP .B \--hex|\-H Output response in hex. . .TP .B \--in|\-i Request PR In command. . .TP .B \--out|\-o Request PR Out command. . .TP .B \--param-alltgpt|\-Y PR Out parameter 'ALL_TG_PT'. . .TP .B \--param-aptpl|\-Z PR Out parameter 'APTPL'. . .TP .B \--read-keys|\-k PR In: Read Keys. . .TP .BI \--param-rk=\fIRK\fB|\-K " RK" PR Out parameter reservation key (RK is in hex, up to 8 bytes). . .TP .BI \--param-sark=\fISARK\fB|\-S " SARK" PR Out parameter service action reservation key (SARK is in hex). . .TP .B \--preempt|\-P PR Out: Preempt. . .TP .B \--clear|\-C PR Out: Clear registrations. . .TP .B \--preempt-abort|\-A PR Out: Preempt and Abort. . .TP .BI \--prout-type=\fITYPE\fB|\-T " TYPE" PR Out command type. . .TP .B \--read-full-status|\-s PR In: Read Full Status. . .TP .B \--read-keys|\-k PR In: Read Keys. . .TP .B \--read-reservation|\-r PR In: Read Reservation. . .TP .B \--register|\-G PR Out: Register. . .TP .B \--register-ignore|\-I PR Out: Register and Ignore. . .TP .B \--release|\-L PR Out: Release. . .TP .B \--report-capabilities|\-c PR In: Report Capabilities. . .TP .B \--reserve|\-R PR Out: Reserve. . .TP .BI \--transport-id=\fITIDS\fB|\-X " TIDS" TransportIDs can be mentioned in several forms. . .TP .BI \--alloc-length=\fILEN\fB|\-l " LEN" 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 .RE .PP Read registered reservation keys for the /dev/mapper/mpath9 device: .RS \fBmpathpersist -i -k \fI/dev/mapper/mpath9\fR .RE .PP Create a reservation for the /dev/mapper/mpath9 device with the given reservation key: .RS \fBmpathpersist --out --reserve --param-rk=\fI123abc \fB--prout-type=\fI8 \fB-d \fI/dev/mapper/mpath9\fR .RE .PP Read the reservation status of the /dev/mapper/mpath9 device: .RS \fBmpathpersist -i -s -d \fI/dev/mapper/mpath9\fR .RE .PP Release the previously created reservation (note that the prout-type needs to be the same as above): .RS \fBmpathpersist --out --release --param-rk=\fI123abc \fB--prout-type=\fI8 \fB-d \fI/dev/mapper/mpath9\fR .RE .PP Remove the current key registered for this host (i.e. reset it to 0): .RS \fBmpathpersist --out --register-ignore -K \fI123abc\fB -S \fI0\fB \fI/dev/mapper/mpath9\fR .RE .PP Remove current reservation, and unregister all registered keys from all I_T nexuses: .RS \fBmpathpersist -oCK \fI123abc \fI/dev/mapper/mpath9\fR .RE . . .\" ---------------------------------------------------------------------------- .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 needs to scan existing paths and maps only once during startup. . .PP The input file is a text file that is parsed line by line. Every line of the file is interpreted as a command line (i.e. list of options and parameters) for \fBmpathpersist\fR. Options and parameters are separated by one or more whitespace characters (space or TAB). Lines can, but do not have to, begin with the word \(dqmpathpersist\(dq. The \(dq#\(dq character, either at the beginning of the line or following some whitespace, denotes the start of a comment that lasts until the end of the line. Empty lines are allowed. Continuation of mpathpersist commands over multiple lines is not supported. . .PP All options listed in this man page, except \fI-f\fR and \fI-v\fR, are allowed in batch files. Both short and long option formats may be used. Using the \fI-f\fR option inside the batch file is an error. The \fI-v\fR option is ignored in batch files. . .PP The multipath map on which to act must be specified on every input line, e.g. using the \fI-d\fR option. Commands acting on different multipath maps may be combined in a batch file, and multiple commands may act on the same multipath map. Commands are executed one by one, so that commands further down in the file see status changes caused by previous commands. If \fBmpathpersist\fR encounters an error while processing a line in the batch file, batch file processing is \fBnot\fR aborted; subsequent commands are executed nonetheless. The exit status of \fBmpathpersist\fR is the status of the first failed command, or 0 if all commands succeeded. . .PP If other options and parameters are used along with \fI-f\fR on the \fBmpathpersist\fR command line, the command line will be executed first, followed by the commands from the batch file. . .PP Below is an example of a valid batch input file. . .PP .RS .EX # This is an mpathpersist input file. # Short and long forms of the same command -i -k /dev/dm-1 # short form, this comment is ignored mpathpersist --in --read-keys --device=/dev/dm-1 # Mixing of long and short options, variable white space --out --register -S abcde /dev/dm-1 # Mixing of commands for different maps -ir /dev/dm-0 -ir /dev/dm-1 mpathpersist --out --param-rk abcde --reserve --prout-type 5 /dev/dm-1 # This should now show a reservation -ir /dev/dm-1 -oCK abcde /dev/dm-1 --in --read-reservation /dev/dm-1 .EE .RE . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR multipath (8), .BR multipathd (8), .BR sg_persist (8). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/multipath/000077500000000000000000000000001475246302400167555ustar00rootroot00000000000000multipath-tools-0.11.1/multipath/11-dm-mpath.rules.in000066400000000000000000000165021475246302400223700ustar00rootroot00000000000000ACTION!="add|change", GOTO="mpath_end" ENV{DM_UDEV_RULES_VSN}!="?*", GOTO="mpath_end" ENV{DM_UUID}!="mpath-?*", GOTO="mpath_end" IMPORT{db}="MPATH_DEVICE_READY" # device-mapper rules v2 compatibility ENV{.DM_SUSPENDED}!="?*", ENV{.DM_SUSPENDED}="$env{DM_SUSPENDED}" # Coldplug event while device is suspended (e.g. during a reload) ACTION=="add", ENV{DM_ACTIVATION}=="1", ENV{.DM_SUSPENDED}=="1", \ PROGRAM="@SYSDIR_BIN@/logger -t 11-dm-mpath.rules -p daemon.warning \"Coldplug event for suspended device\"", \ ENV{DM_COLDPLUG_SUSPENDED}="1", GOTO="scan_import" # Coldplug event. Import previously set properties. ACTION!="add", GOTO="mpath_coldplug_end" ENV{DM_ACTIVATION}!="1", GOTO="mpath_coldplug_end" ENV{DM_UDEV_RULES_VSN}!="1|2", ENV{.DM_SUSPENDED}!="1", GOTO="scan_import" # With DM rules < v3, DM_UDEV_DISABLE_OTHER_RULES_FLAG has been restored # from DB in 10-dm.rules. If the device is not suspended, clear the flag. # This is safe for multipath where DM_UDEV_DISABLE_OTHER_RULES_FLAG is basically # equivalent to DM_SUSPENDED==1 || DISK_RO==1 ENV{DM_UDEV_RULES_VSN}=="1|2", ENV{.DM_SUSPENDED}!="1", ENV{DISK_RO}!="1", \ ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="", GOTO="scan_import" LABEL="mpath_coldplug_end" IMPORT{db}="DM_COLDPLUG_SUSPENDED" # If this uevent didn't come from dm, don't try to update the # device state # Note that .MPATH_DEVICE_READY_OLD=="" here. Thus we won't activate the # device below at mpath_is_ready, which is correct. ENV{DM_COOKIE}!="?*", ENV{DM_ACTION}!="PATH_*", \ GOTO="check_mpath_ready" ENV{.MPATH_DEVICE_READY_OLD}="$env{MPATH_DEVICE_READY}" # If the device wasn't ready previously and is currently suspended, # we have to postpone the activation until the next event. # In this case, we have to set MPATH_DEVICE_READY=0; otherwise, the # MPATH_UNCHANGED logic will cause later rules to skipped in the next event. ENV{.MPATH_DEVICE_READY_OLD}!="1", ENV{.DM_SUSPENDED}=="1", \ ENV{MPATH_DEVICE_READY}="0", GOTO="check_mpath_unchanged" # multipath sets DM_SUBSYSTEM_UDEV_FLAG2 when it reloads a # table with no active devices. If this happens, mark the # device not ready ENV{DM_SUBSYSTEM_UDEV_FLAG2}=="1", ENV{MPATH_DEVICE_READY}="0", \ GOTO="check_mpath_unchanged" # If the last path has failed mark the device not ready # Note that DM_NR_VALID_PATHS is only set for PATH_FAILED|PATH_REINSTATED # events. # This may not be reliable, as events aren't necessarily received in order. ENV{DM_NR_VALID_PATHS}=="0", ENV{MPATH_DEVICE_READY}="0", GOTO="check_mpath_unchanged" # Don't run multipath -U during "coldplug" after switching root, # because paths are just being added to the udev db. ACTION=="add", ENV{.MPATH_DEVICE_READY_OLD}=="1", GOTO="paths_ok" # Check the map state directly with multipath -U. # This doesn't attempt I/O on the device. PROGRAM=="@BINDIR@/multipath -U -v1 %k", GOTO="paths_ok" ENV{MPATH_DEVICE_READY}="0", GOTO="check_mpath_unchanged" LABEL="paths_ok" # For PATH_FAILED events, keep the existing value of MPATH_DEVICE_READY. # If it's not PATH_FAILED, this event is either a PATH_REINSTATED or a # table reload where there are active paths. Mark the device ready. ENV{DM_ACTION}!="PATH_FAILED", ENV{MPATH_DEVICE_READY}="1" LABEL="check_mpath_unchanged" # A previous coldplug event occurred while the device was suspended. # Activation might have been partially skipped. Activate the device now, # i.e. disable the MPATH_UNCHANGED logic. ENV{DM_COLDPLUG_SUSPENDED}=="1", ENV{.DM_SUSPENDED}!="1", \ ENV{MPATH_UNCHANGED}="0", \ PROGRAM="@SYSDIR_BIN@/logger -t 11-dm-mpath.rules -p daemon.notice \"Forcing activation of previously suspended device\"", \ GOTO="check_mpath_ready" # DM_SUBSYSTEM_UDEV_FLAG0 is the "RELOAD" flag for multipath subsystem. # Set the MPATH_UNCHANGED flag here as mpath reloads tables if any of its # paths are lost/recovered. For any stack above the mpath device, this is not # something that should be reacted upon since it would be useless extra work. # It's exactly mpath's job to provide *seamless* device access to any of the # paths that are available underneath. ENV{DM_SUBSYSTEM_UDEV_FLAG0}=="1", \ ENV{MPATH_UNCHANGED}="1" # For path failed or reinstated events, set MPATH_UNCHANGED. # This is similar to the DM_SUBSYSTEM_UDEV_FLAG0 case above. ENV{DM_ACTION}=="PATH_FAILED|PATH_REINSTATED", \ ENV{MPATH_UNCHANGED}="1" LABEL="check_mpath_ready" ENV{MPATH_DEVICE_READY}!="0", GOTO="mpath_is_ready" # Do not initiate scanning if no path is available, # otherwise there would be a hang or IO error on access. # We'd like to avoid this, especially within udev processing. # This is communicated to later rules in .DM_NOSCAN. # Likewise, skip all foreign rules if no path is available. # Use DM_UDEV_DISABLE_OTHER_RULES_FLAG to communicate this # to upper layers. With dm rules < v3, save the original value here; # it will be restored in a late udev rule. ENV{DM_UDEV_RULES_VSN}=="1|2", \ ENV{.MPATH_SAVE_DISABLE_OTHER_RULES_FLAG}="$env{DM_UDEV_DISABLE_OTHER_RULES_FLAG}" ENV{.DM_NOSCAN}="1", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" GOTO="scan_import" LABEL="mpath_is_ready" # If the device comes back online, clear MPATH_UNCHANGED so that # upper layers will do a rescan. Don't do this if .MPATH_DEVICE_READY_OLD # is just empty (see comment above the DM_COOKIE test above). ENV{.MPATH_DEVICE_READY_OLD}=="0", ENV{MPATH_UNCHANGED}="0" # The code to check multipath state ends here. We need to set # properties and symlinks regardless whether the map is usable or # not. If symlinks get lost, systemd may auto-unmount file systems. LABEL="scan_import" # If DM_UDEV_PRIMARY_SOURCE_FLAG is not set, the properties below # have never been properly set. Don't import them. ENV{DM_UDEV_PRIMARY_SOURCE_FLAG}!="1", GOTO="import_end" # DM rules v3 will import missing properties on 13-dm-disk.rules. # No need to do it here. ENV{DM_UDEV_RULES_VSN}!="1|2", GOTO="import_end" # Don't import the properties from db if we will run blkid later. ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}!="1", GOTO="import_end" IMPORT{db}="ID_FS_TYPE" IMPORT{db}="ID_FS_USAGE" IMPORT{db}="ID_FS_UUID_ENC" IMPORT{db}="ID_FS_LABEL_ENC" IMPORT{db}="ID_PART_ENTRY_NAME" IMPORT{db}="ID_PART_ENTRY_UUID" IMPORT{db}="ID_PART_ENTRY_SCHEME" IMPORT{db}="ID_PART_GPT_AUTO_ROOT" LABEL="import_end" # If MPATH_UNCHANGED is set, adapt DM_ACTIVATION and DM_NOSCAN. # .DM_NOSCAN controls whether blkid will be run in 13-dm-disk.rules; # we don't want to do that if MPATH_UNCHANGED is 1. ENV{MPATH_UNCHANGED}=="0", ENV{DM_ACTIVATION}="1" ENV{MPATH_UNCHANGED}=="1", ENV{DM_ACTIVATION}="0", ENV{.DM_NOSCAN}="1" # Reset previous DM_COLDPLUG_SUSPENDED if activation happens now ENV{.DM_SUSPENDED}!="1", ENV{DM_ACTIVATION}=="1", ENV{DM_COLDPLUG_SUSPENDED}="" # device-mapper rules v2 compatibility for 13-dm-disk.rules ENV{DM_UDEV_RULES_VSN}=="1|2", ENV{DM_NOSCAN}="$env{.DM_NOSCAN}" # Multipath maps should take precedence over their members. ENV{DM_UDEV_LOW_PRIORITY_FLAG}!="1", OPTIONS+="link_priority=50" # Set some additional symlinks that typically exist for mpath # path members, too, and should be overridden. # kpartx_id is very robust, it works for suspended maps and maps # with 0 dependencies. It sets DM_TYPE, DM_PART, DM_WWN TEST=="/usr/lib/udev/kpartx_id", \ IMPORT{program}=="kpartx_id %M %m $env{DM_UUID}" ENV{DM_TYPE}=="?*", ENV{DM_SERIAL}=="?*", \ SYMLINK+="disk/by-id/$env{DM_TYPE}-$env{DM_SERIAL}" ENV{DM_WWN}=="?*", SYMLINK+="disk/by-id/wwn-$env{DM_WWN}" LABEL="mpath_end" multipath-tools-0.11.1/multipath/99-z-dm-mpath-late.rules000066400000000000000000000004531475246302400231730ustar00rootroot00000000000000# If DM_UDEV_DISABLE_OTHER_RULES_FLAG was modified in 11-dm-mpath.rules, # restore it here, lest a temporary value be saved in the udev db ACTION=="add|change", ENV{.MPATH_SAVE_DISABLE_OTHER_RULES_FLAG}=="?*", \ ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{.MPATH_SAVE_DISABLE_OTHER_RULES_FLAG}" multipath-tools-0.11.1/multipath/Makefile000066400000000000000000000047561475246302400204310ustar00rootroot00000000000000# # Copyright (C) 2003 Christophe Varoqui, # include ../Makefile.inc EXEC := multipath MANPAGES := multipath.8 multipath.conf.5 GENERATED := $(MANPAGES) multipath.rules tmpfiles.conf 11-dm-mpath.rules CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) CFLAGS += $(BIN_CFLAGS) LDFLAGS += $(BIN_LDFLAGS) LIBDEPS += -L$(multipathdir) -lmultipath -L$(mpathutildir) -lmpathutil \ -L$(mpathcmddir) -lmpathcmd -lpthread -ldevmapper -ldl -ludev OBJS := main.o all: $(EXEC) $(GENERATED) $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? $(Q)$(CC) $(CFLAGS) $(OBJS) -o $(EXEC) $(LDFLAGS) $(LIBDEPS) install: $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir) $(Q)$(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir)/ $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 11-dm-mpath.rules $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 99-z-dm-mpath-late.rules $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 multipath.rules $(DESTDIR)$(udevrulesdir)/56-multipath.rules $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(tmpfilesdir) $(Q)$(INSTALL_PROGRAM) -m 644 tmpfiles.conf $(DESTDIR)$(tmpfilesdir)/multipath.conf $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man5 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).conf.5 $(DESTDIR)$(mandir)/man5 $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(modulesloaddir) ifeq ($(MODPROBE_UNIT),) $(Q)$(INSTALL_PROGRAM) -m 644 modules-load.conf $(DESTDIR)$(modulesloaddir)/multipath.conf endif ifneq ($(SCSI_DH_MODULES_PRELOAD),) $(Q)$(INSTALL_PROGRAM) -m 644 scsi_dh.conf $(DESTDIR)$(modulesloaddir)/scsi_dh.conf $(Q)for _x in $(SCSI_DH_MODULES_PRELOAD); do echo "$$_x"; done \ >>$(DESTDIR)$(modulesloaddir)/scsi_dh.conf endif uninstall: $(Q)$(RM) $(DESTDIR)$(bindir)/$(EXEC) $(Q)$(RM) $(DESTDIR)$(udevrulesdir)/11-dm-mpath.rules $(Q)$(RM) $(DESTDIR)$(udevrulesdir)/99-z-dm-mpath-late.rules $(Q)$(RM) $(DESTDIR)$(modulesloaddir)/multipath.conf $(Q)$(RM) $(DESTDIR)$(modulesloaddir)/scsi_dh.conf $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/56-multipath.rules $(Q)$(RM) $(DESTDIR)$(mandir)/man8/$(EXEC).8 $(Q)$(RM) $(DESTDIR)$(mandir)/man5/$(EXEC).conf.5 $(Q)$(RM) $(DESTDIR)$(tmpfilesdir)/multipath.conf clean: dep_clean $(Q)$(RM) core *.o $(EXEC) $(GENERATED) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) multipath-tools-0.11.1/multipath/main.c000066400000000000000000000675411475246302400200620ustar00rootroot00000000000000/* * Soft: multipath device mapper target autoconfig * * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $ * * Author: Christophe Varoqui * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Copyright (c) 2003, 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Patrick Caulfield, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "prio.h" #include "vector.h" #include #include "devmapper.h" #include "util.h" #include "defaults.h" #include "config.h" #include "structs.h" #include "structs_vec.h" #include "dmparser.h" #include "sysfs.h" #include "blacklist.h" #include "discovery.h" #include "debug.h" #include "switchgroup.h" #include "dm-generic.h" #include "print.h" #include "alias.h" #include "configure.h" #include "pgpolicies.h" #include "version.h" #include #include "wwids.h" #include "uxsock.h" #include "mpath_cmd.h" #include "foreign.h" #include "propsel.h" #include "time-util.h" #include "file.h" #include "valid.h" /* * Return values of configure(), check_path_valid(), and main(). */ enum { RTVL_OK = 0, RTVL_FAIL = 1, RTVL_RETRY, /* returned by configure(), not by main() */ }; static int dump_config (struct config *conf, vector hwes, vector mpvec) { char * reply = snprint_config(conf, NULL, hwes, mpvec); if (reply != NULL) { printf("%s", reply); free(reply); return 0; } else return 1; } void rcu_register_thread_memb(void) {} void rcu_unregister_thread_memb(void) {} static int filter_pathvec (vector pathvec, const char *refwwid) { int i; struct path * pp; if (!refwwid || !strlen(refwwid)) return 0; vector_foreach_slot (pathvec, pp, i) { if (strncmp(pp->wwid, refwwid, WWID_SIZE) != 0) { condlog(3, "skip path %s : out of scope", pp->dev); free_path(pp); vector_del_slot(pathvec, i); i--; } } return 0; } static void usage (char * progname) { fprintf (stderr, VERSION_STRING); fprintf (stderr, "Usage:\n"); fprintf (stderr, " %s [-v level] [-B|-d|-i|-q|-r] [-b file] [-p policy] [device]\n", progname); fprintf (stderr, " %s [-v level] [-R retries] -f device\n", progname); fprintf (stderr, " %s [-v level] [-R retries] -F\n", progname); fprintf (stderr, " %s [-v level] [-l|-ll] [device]\n", progname); fprintf (stderr, " %s [-v level] [-a|-w] device\n", progname); fprintf (stderr, " %s [-v level] -W\n", progname); fprintf (stderr, " %s [-v level] [-i] [-c|-C] device\n", progname); fprintf (stderr, " %s [-v level] [-i] [-u|-U]\n", progname); fprintf (stderr, " %s [-h|-t|-T]\n", progname); fprintf (stderr, "\n" "Where:\n" " -h print this usage text\n" " -l show multipath topology (sysfs and DM info)\n" " -ll show multipath topology (maximum info)\n" " -e enable foreign libraries with -l/-ll\n" " -f flush a multipath device map\n" " -F flush all multipath device maps\n" " -a add a device wwid to the wwids file\n" " -c check if a device should be a path in a multipath device\n" " -C check if a multipath device has usable paths\n" " -q allow queue_if_no_path when multipathd is not running\n" " -d dry run, do not create or update devmaps\n" " -t display the currently used multipathd configuration\n" " -T display the multipathd configuration without builtin defaults\n" " -r force devmap reload\n" " -i ignore wwids file\n" " -B treat the bindings file as read only\n" " -b fil bindings file location\n" " -w remove a device from the wwids file\n" " -W reset the wwids file include only the current devices\n" " -R num number of times to retry removes of in-use devices\n" " -u check if the device specified in the program environment should be a\n" " path in a multipath device\n" " -U check if the device specified in the program environment is a\n" " multipath device with usable paths, see -C flag\n" " -p pol force all maps to specified path grouping policy:\n" " . failover one path per priority group\n" " . multibus all paths in one priority group\n" " . group_by_serial one priority group per serial\n" " . group_by_prio one priority group per priority lvl\n" " . group_by_node_name one priority group per target node\n" " . group_by_tpg one priority group per ALUA target port group\n" " -v lvl verbosity level:\n" " . 0 no output\n" " . 1 print created devmap names only\n" " . 2 default verbosity\n" " . 3 print debug information\n" " device action limited to:\n" " . multipath named 'device' (ex: mpath0)\n" " . multipath whose wwid is 'device' (ex: 60051...)\n" " . multipath including the path named 'device' (ex: /dev/sda or\n" " /dev/dm-0)\n" " . multipath including the path with maj:min 'device' (ex: 8:0)\n" ); } static int get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid) { int i; struct multipath * mpp; int flags = (cmd == CMD_LIST_SHORT ? DI_NOIO : DI_ALL); if (dm_get_maps(curmp)) return 1; vector_foreach_slot (curmp, mpp, i) { /* * discard out of scope maps */ if (refwwid && strlen(refwwid) && strncmp(mpp->wwid, refwwid, WWID_SIZE)) { condlog(3, "skip map %s: out of scope", mpp->alias); remove_map(mpp, pathvec, curmp); i--; continue; } if (update_multipath_table(mpp, pathvec, flags) != DMP_OK) { condlog(1, "error parsing map %s", mpp->wwid); remove_map(mpp, pathvec, curmp); i--; continue; } if (cmd == CMD_LIST_LONG) mpp->bestpg = select_path_group(mpp); if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) print_multipath_topology(mpp, libmp_verbosity); if (cmd == CMD_CREATE) reinstate_paths(mpp); } if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) print_foreign_topology(libmp_verbosity); return 0; } static int check_usable_paths(struct config *conf, const char *devpath, enum devtypes dev_type) { struct udev_device __attribute__((cleanup(cleanup_udev_device))) *ud = NULL; struct multipath __attribute__((cleanup(cleanup_multipath_and_paths))) *mpp = NULL; struct pathgroup *pg; struct path *pp; char __attribute__((cleanup(cleanup_charp))) *params = NULL; char __attribute__((cleanup(cleanup_charp))) *status = NULL; vector __attribute((cleanup(cleanup_vector))) pathvec = NULL; char uuid[DM_UUID_LEN]; dev_t devt; int r = 1, i, j; ud = get_udev_device(devpath, dev_type); if (ud == NULL) return r; devt = udev_device_get_devnum(ud); if (!dm_is_dm_major(major(devt))) { condlog(1, "%s is not a dm device", devpath); return r; } mpp = alloc_multipath(); if (!mpp) return r; if (!(mpp->alias = malloc(WWID_SIZE))) return r; /* pathvec is needed for disassemble_map */ pathvec = vector_alloc(); if (pathvec == NULL) return r; if (libmp_mapinfo(DM_MAP_BY_DEVT | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .devt = devt }, (mapinfo_t) { .name = mpp->alias, .uuid = uuid, .dmi = &mpp->dmi, .size = &mpp->size, .target = ¶ms, .status = &status, }) != DMP_OK) return r; strlcpy(mpp->wwid, uuid + UUID_PREFIX_LEN, sizeof(mpp->wwid)); if (update_multipath_table__(mpp, pathvec, 0, params, status) != DMP_OK) return r; vector_foreach_slot (mpp->pg, pg, i) { vector_foreach_slot (pg->paths, pp, j) { pp->udev = get_udev_device(pp->dev_t, DEV_DEVT); if (pp->udev == NULL) continue; if (pathinfo(pp, conf, DI_SYSFS|DI_NOIO|DI_CHECKER) != PATHINFO_OK) continue; if (pp->state == PATH_UP && pp->dmstate == PSTATE_ACTIVE) { condlog(3, "%s: path %s is usable", devpath, pp->dev); r = 0; goto found; } } } found: condlog(r == 0 ? 3 : 2, "%s:%s usable paths found", devpath, r == 0 ? "" : " no"); return r; } enum { FIND_MULTIPATHS_WAIT_DONE = 0, FIND_MULTIPATHS_WAITING = 1, FIND_MULTIPATHS_ERROR = -1, FIND_MULTIPATHS_NEVER = -2, }; static const char shm_find_mp_dir[] = MULTIPATH_SHM_BASE "find_multipaths"; /** * find_multipaths_check_timeout(wwid, tmo) * Helper for "find_multipaths smart" * * @param[in] pp: path to check / record * @param[in] tmo: configured timeout for this WWID, or value <= 0 for checking * @param[out] until: timestamp until we must wait, CLOCK_REALTIME, if return * value is FIND_MULTIPATHS_WAITING * @returns: FIND_MULTIPATHS_WAIT_DONE, if waiting has finished * @returns: FIND_MULTIPATHS_ERROR, if internal error occurred * @returns: FIND_MULTIPATHS_NEVER, if tmo is 0 and we didn't wait for this * device * @returns: FIND_MULTIPATHS_WAITING, if timeout hasn't expired */ static int find_multipaths_check_timeout(const struct path *pp, long tmo, struct timespec *until) { char path[PATH_MAX]; struct timespec now, ftimes[2], tdiff; struct stat st; int fd; int r, retries = 0; clock_gettime(CLOCK_REALTIME, &now); if (safe_sprintf(path, "%s/%s", shm_find_mp_dir, pp->dev_t)) { condlog(1, "%s: path name overflow", __func__); return FIND_MULTIPATHS_ERROR; } if (ensure_directories_exist(path, 0700)) { condlog(1, "%s: error creating directories", __func__); return FIND_MULTIPATHS_ERROR; } retry: fd = open(path, O_RDONLY); if (fd != -1) { r = fstat(fd, &st); close(fd); } else if (tmo > 0) { if (errno == ENOENT) fd = open(path, O_RDWR|O_EXCL|O_CREAT, 0644); if (fd == -1) { if (errno == EEXIST && !retries++) /* We could have raced with another process */ goto retry; condlog(1, "%s: error opening %s: %s", __func__, path, strerror(errno)); return FIND_MULTIPATHS_ERROR; }; /* * We just created the file. Set st_mtim to our desired * expiry time. */ ftimes[0].tv_sec = 0; ftimes[0].tv_nsec = UTIME_OMIT; ftimes[1].tv_sec = now.tv_sec + tmo; ftimes[1].tv_nsec = now.tv_nsec; if (futimens(fd, ftimes) != 0) { condlog(1, "%s: error in futimens(%s): %s", __func__, path, strerror(errno)); } r = fstat(fd, &st); close(fd); } else return FIND_MULTIPATHS_NEVER; if (r != 0) { condlog(1, "%s: error in fstat for %s: %m", __func__, path); return FIND_MULTIPATHS_ERROR; } timespecsub(&st.st_mtim, &now, &tdiff); if (tdiff.tv_sec <= 0) return FIND_MULTIPATHS_WAIT_DONE; *until = tdiff; return FIND_MULTIPATHS_WAITING; } static int print_cmd_valid(int k, const vector pathvec, struct config *conf) { int wait = FIND_MULTIPATHS_NEVER; struct timespec until; struct path *pp; if (k != PATH_IS_VALID && k != PATH_IS_NOT_VALID && k != PATH_IS_MAYBE_VALID) return PATH_IS_NOT_VALID; if (k == PATH_IS_MAYBE_VALID) { /* * Caller ensures that pathvec[0] is the path to * examine. */ pp = VECTOR_SLOT(pathvec, 0); select_find_multipaths_timeout(conf, pp); wait = find_multipaths_check_timeout( pp, pp->find_multipaths_timeout, &until); if (wait != FIND_MULTIPATHS_WAITING) k = PATH_IS_NOT_VALID; } else if (pathvec != NULL && (pp = VECTOR_SLOT(pathvec, 0))) wait = find_multipaths_check_timeout(pp, 0, &until); if (wait == FIND_MULTIPATHS_WAITING) printf("FIND_MULTIPATHS_WAIT_UNTIL=\"%ld.%06ld\"\n", (long)until.tv_sec, until.tv_nsec/1000); else if (wait == FIND_MULTIPATHS_WAIT_DONE) printf("FIND_MULTIPATHS_WAIT_UNTIL=\"0\"\n"); printf("DM_MULTIPATH_DEVICE_PATH=\"%d\"\n", k == PATH_IS_MAYBE_VALID ? 2 : k == PATH_IS_VALID ? 1 : 0); /* Never return RTVL_MAYBE */ return k == PATH_IS_NOT_VALID ? PATH_IS_NOT_VALID : PATH_IS_VALID; } /* * Returns true if this device has been handled before, * and released to systemd. * * This must be called before get_refwwid(), * otherwise udev_device_new_from_environment() will have * destroyed environ(7). */ static bool released_to_systemd(void) { static const char dmdp[] = "DM_MULTIPATH_DEVICE_PATH"; const char *dm_mp_dev_path = getenv(dmdp); bool ret; ret = dm_mp_dev_path != NULL && !strcmp(dm_mp_dev_path, "0"); condlog(4, "%s: %s=%s -> %d", __func__, dmdp, dm_mp_dev_path ? dm_mp_dev_path : "", ret); return ret; } static struct vectors vecs; static void cleanup_vecs(void) { free_multipathvec(vecs.mpvec, KEEP_PATHS); free_pathvec(vecs.pathvec, FREE_PATHS); } static int configure (struct config *conf, enum mpath_cmds cmd, enum devtypes dev_type, char *devpath) { vector curmp = NULL; vector pathvec = NULL; vector newmp = NULL; int r = RTVL_FAIL, rc; int di_flag = 0; char * refwwid = NULL; char * dev = NULL; fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; /* * allocate core vectors to store paths and multipaths */ curmp = vector_alloc(); pathvec = vector_alloc(); newmp = vector_alloc(); if (!curmp || !pathvec || !newmp) { condlog(0, "cannot allocate memory"); goto out; } vecs.pathvec = pathvec; vecs.mpvec = curmp; dev = convert_dev(devpath, (dev_type == DEV_DEVNODE)); /* * if we have a blacklisted device parameter, exit early */ if (dev && (dev_type == DEV_DEVNODE || dev_type == DEV_UEVENT) && cmd != CMD_REMOVE_WWID && (filter_devnode(conf->blist_devnode, conf->elist_devnode, dev) > 0)) { goto out; } /* * scope limiting must be translated into a wwid * failing the translation is fatal (by policy) */ if (devpath) { get_refwwid(cmd, devpath, dev_type, pathvec, &refwwid); if (!refwwid) { condlog(4, "%s: failed to get wwid", devpath); condlog(3, "scope is null"); goto out; } if (cmd == CMD_REMOVE_WWID) { rc = remove_wwid(refwwid); if (rc == 0) { printf("wwid '%s' removed\n", refwwid); r = RTVL_OK; } else if (rc == 1) { printf("wwid '%s' not in wwids file\n", refwwid); r = RTVL_OK; } goto out; } if (cmd == CMD_ADD_WWID) { rc = remember_wwid(refwwid); if (rc >= 0) { printf("wwid '%s' added\n", refwwid); r = RTVL_OK; } else printf("failed adding '%s' to wwids file\n", refwwid); goto out; } condlog(3, "scope limited to %s", refwwid); } /* * get a path list */ if (devpath) di_flag = DI_WWID; if (cmd == CMD_LIST_LONG) /* extended path info '-ll' */ di_flag |= DI_SYSFS | DI_CHECKER | DI_SERIAL; else if (cmd == CMD_LIST_SHORT) /* minimum path info '-l' */ di_flag |= DI_SYSFS; else /* maximum info */ di_flag = DI_ALL; if (path_discovery(pathvec, di_flag) < 0) goto out; if (libmp_verbosity > 2) print_all_paths(pathvec, 1); if ((width = alloc_path_layout()) == NULL) goto out; get_path_layout(pathvec, 0, width); foreign_path_layout(width); if (get_dm_mpvec(cmd, curmp, pathvec, refwwid)) goto out; filter_pathvec(pathvec, refwwid); if (cmd == CMD_DUMP_CONFIG) { vector hwes = get_used_hwes(pathvec); dump_config(conf, hwes, curmp); vector_free(hwes); r = RTVL_OK; goto out; } if (cmd != CMD_CREATE && cmd != CMD_DRY_RUN) { r = RTVL_OK; goto out; } /* * core logic entry point */ rc = coalesce_paths(&vecs, newmp, refwwid, conf->force_reload, cmd); r = rc == CP_RETRY ? RTVL_RETRY : rc == CP_OK ? RTVL_OK : RTVL_FAIL; out: if (r == RTVL_OK && (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG || cmd == CMD_CREATE) && (VECTOR_SIZE(curmp) > 0 || VECTOR_SIZE(newmp) > 0) && !check_daemon()) condlog(2, "Warning: multipath devices exist, but multipathd service is not running"); if (refwwid) free(refwwid); free_multipathvec(curmp, KEEP_PATHS); vecs.mpvec = NULL; free_multipathvec(newmp, KEEP_PATHS); free_pathvec(pathvec, FREE_PATHS); vecs.pathvec = NULL; return r; } static int check_path_valid(const char *name, struct config *conf, bool is_uevent) { int fd, r = PATH_IS_ERROR; struct path *pp; vector pathvec = NULL; const char *wwid; pp = alloc_path(); if (!pp) return RTVL_FAIL; if (is_uevent) pp->can_use_env_uid = true; r = is_path_valid(name, conf, pp, is_uevent); if (r <= PATH_IS_ERROR || r >= PATH_MAX_VALID_RESULT) goto fail; /* set path values if is_path_valid() didn't */ if (!pp->udev) pp->udev = udev_device_new_from_subsystem_sysname(udev, "block", name); if (!pp->udev) goto fail; if (!strlen(pp->dev_t)) { dev_t devt = udev_device_get_devnum(pp->udev); if (major(devt) == 0 && minor(devt) == 0) goto fail; snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), minor(devt)); } if ((r == PATH_IS_VALID || r == PATH_IS_MAYBE_VALID) && released_to_systemd()) r = PATH_IS_NOT_VALID; /* This state is only used to skip the released_to_systemd() check */ if (r == PATH_IS_VALID_NO_CHECK) r = PATH_IS_VALID; if (r != PATH_IS_MAYBE_VALID) goto out; /* * If opening the path with O_EXCL fails, the path * is in use (e.g. mounted during initramfs processing). * We know that it's not used by dm-multipath. * We may not set SYSTEMD_READY=0 on such devices, it * might cause systemd to umount the device. * Use O_RDONLY, because udevd would trigger another * uevent for close-after-write. * * The O_EXCL check is potentially dangerous, because it may * race with other tasks trying to access the device. Therefore * this code is only executed if the path hasn't been released * to systemd earlier (see above). */ fd = open(udev_device_get_devnode(pp->udev), O_RDONLY|O_EXCL); if (fd >= 0) close(fd); else { condlog(3, "%s: path %s is in use: %m", __func__, pp->dev); /* Check if we raced with multipathd */ if (sysfs_is_multipathed(pp, false)) r = PATH_IS_VALID; else r = PATH_IS_NOT_VALID; goto out; } pathvec = vector_alloc(); if (!pathvec) goto fail; if (store_path(pathvec, pp) != 0) { free_path(pp); pp = NULL; goto fail; } else { /* make sure path isn't freed twice */ wwid = pp->wwid; pp = NULL; } /* For find_multipaths = SMART, if there is more than one path * matching the refwwid, then the path is valid */ if (path_discovery(pathvec, DI_SYSFS | DI_WWID) < 0) goto fail; filter_pathvec(pathvec, wwid); if (VECTOR_SIZE(pathvec) > 1) r = PATH_IS_VALID; else r = PATH_IS_MAYBE_VALID; out: r = print_cmd_valid(r, pathvec, conf); /* * multipath -u must exit with status 0, otherwise udev won't * import its output. */ if (!is_uevent && r == PATH_IS_NOT_VALID) r = RTVL_FAIL; else r = RTVL_OK; goto cleanup; fail: r = RTVL_FAIL; cleanup: if (pp != NULL) free_path(pp); if (pathvec != NULL) free_pathvec(pathvec, FREE_PATHS); return r; } static int get_dev_type(char *dev) { struct stat buf; int i; if (stat(dev, &buf) == 0 && S_ISBLK(buf.st_mode)) { if (dm_is_dm_major(major(buf.st_rdev))) return DEV_DEVMAP; return DEV_DEVNODE; } else if (sscanf(dev, "%d:%d", &i, &i) == 2) return DEV_DEVT; else if (valid_alias(dev)) return DEV_DEVMAP; return DEV_NONE; } /* * Some multipath commands are dangerous to run while multipathd is running. * For example, "multipath -r" may apply a modified configuration to the kernel, * while multipathd is still using the old configuration, leading to * inconsistent state. * * It is safer to use equivalent multipathd client commands instead. */ enum { DELEGATE_OK, DELEGATE_ERROR, DELEGATE_RETRY, NOT_DELEGATED, }; int delegate_to_multipathd(enum mpath_cmds cmd, __attribute__((unused)) const char *dev, __attribute__((unused)) enum devtypes dev_type, struct config *conf) { int fd; char command[1024], *p, *reply = NULL; int n, r = DELEGATE_ERROR; p = command; *p = '\0'; n = sizeof(command); if (conf->skip_delegate) return NOT_DELEGATED; if (cmd == CMD_CREATE && conf->force_reload == FORCE_RELOAD_YES) { p += snprintf(p, n, "reconfigure all"); } else if (cmd == CMD_FLUSH_ONE && dev && dev_type == DEV_DEVMAP) { p += snprintf(p, n, "del map %s", dev); if (conf->remove_retries > 0) { r = DELEGATE_RETRY; conf->remove_retries--; } } else if (cmd == CMD_FLUSH_ALL) { p += snprintf(p, n, "del maps"); if (conf->remove_retries > 0) { r = DELEGATE_RETRY; conf->remove_retries--; } } /* Add other translations here */ if (strlen(command) == 0) /* No command found, no need to delegate */ return NOT_DELEGATED; fd = mpath_connect(); if (fd == -1) return NOT_DELEGATED; if (p >= command + sizeof(command)) { condlog(0, "internal error - command buffer overflow"); goto out; } condlog(3, "delegating command to multipathd"); if (mpath_process_cmd(fd, command, &reply, conf->uxsock_timeout) == -1) { if (errno == ETIMEDOUT) r = NOT_DELEGATED; condlog(1, "error in multipath command %s: %s", command, strerror(errno)); goto out; } if (reply != NULL && *reply != '\0') { if (strncmp(reply, "fail\n", 5)) r = DELEGATE_OK; else if (strcmp(reply, "fail\ntimeout\n") == 0) { r = NOT_DELEGATED; goto out; } if (r != DELEGATE_RETRY && strcmp(reply, "ok\n")) { /* If there is additional failure information, skip the * initial 'fail' */ if (strncmp(reply, "fail\n", 5) == 0 && strlen(reply) > 5) printf("%s", reply + 5); else printf("%s", reply); } } out: free(reply); close(fd); return r; } int main (int argc, char *argv[]) { int arg; extern char *optarg; extern int optind; int r = RTVL_FAIL; enum mpath_cmds cmd = CMD_CREATE; enum devtypes dev_type = DEV_NONE; char *dev = NULL; struct config *conf; bool enable_foreign = false; libmultipath_init(); if (atexit(dm_lib_exit) || atexit(libmultipath_exit)) condlog(1, "failed to register cleanup handler for libmultipath: %m"); logsink = LOGSINK_STDERR_WITH_TIME; if (init_config(DEFAULT_CONFIGFILE)) exit(RTVL_FAIL); if (atexit(uninit_config)) condlog(1, "failed to register cleanup handler for config: %m"); conf = get_multipath_config(); if (atexit(cleanup_vecs)) condlog(1, "failed to register cleanup handler for vecs: %m"); if (atexit(cleanup_bindings)) condlog(1, "failed to register cleanup handler for bindings: %m"); while ((arg = getopt(argc, argv, ":adDcChl::eFfM:v:p:b:BrR:itTquUwW")) != EOF ) { switch(arg) { case 'v': if (!isdigit(optarg[0])) { usage (argv[0]); exit(RTVL_FAIL); } libmp_verbosity = atoi(optarg); break; case 'b': condlog(1, "option -b ignored"); break; case 'B': conf->bindings_read_only = 1; break; case 'q': conf->allow_queueing = 1; break; case 'c': cmd = CMD_VALID_PATH; break; case 'C': cmd = CMD_USABLE_PATHS; break; case 'd': if (cmd == CMD_CREATE) cmd = CMD_DRY_RUN; break; case 'D': conf->skip_delegate = 1; break; case 'f': cmd = CMD_FLUSH_ONE; break; case 'F': cmd = CMD_FLUSH_ALL; break; case 'l': if (optarg && !strncmp(optarg, "l", 1)) cmd = CMD_LIST_LONG; else cmd = CMD_LIST_SHORT; break; case 'M': break; case 'p': conf->pgpolicy_flag = get_pgpolicy_id(optarg); if (conf->pgpolicy_flag == IOPOLICY_UNDEF) { printf("'%s' is not a valid policy\n", optarg); usage(argv[0]); exit(RTVL_FAIL); } break; case 'r': conf->force_reload = FORCE_RELOAD_YES; break; case 'i': if (conf->find_multipaths == FIND_MULTIPATHS_ON || conf->find_multipaths == FIND_MULTIPATHS_STRICT) conf->find_multipaths = FIND_MULTIPATHS_SMART; else if (conf->find_multipaths == FIND_MULTIPATHS_OFF) conf->find_multipaths = FIND_MULTIPATHS_GREEDY; break; case 't': r = dump_config(conf, NULL, NULL) ? RTVL_FAIL : RTVL_OK; goto out; case 'T': cmd = CMD_DUMP_CONFIG; break; case 'h': usage(argv[0]); exit(RTVL_OK); case 'u': cmd = CMD_VALID_PATH; dev_type = DEV_UEVENT; break; case 'U': cmd = CMD_USABLE_PATHS; dev_type = DEV_UEVENT; break; case 'w': cmd = CMD_REMOVE_WWID; break; case 'W': cmd = CMD_RESET_WWIDS; break; case 'a': cmd = CMD_ADD_WWID; break; case 'R': conf->remove_retries = atoi(optarg); break; case 'e': enable_foreign = true; break; case ':': fprintf(stderr, "Missing option argument\n"); usage(argv[0]); exit(RTVL_FAIL); case '?': fprintf(stderr, "Unknown switch: %s\n", optarg); usage(argv[0]); exit(RTVL_FAIL); default: usage(argv[0]); exit(RTVL_FAIL); } } if (getuid() != 0) { fprintf(stderr, "need to be root\n"); exit(RTVL_FAIL); } if (optind < argc) { dev = calloc(1, FILE_NAME_SIZE); if (!dev) goto out; strlcpy(dev, argv[optind], FILE_NAME_SIZE); if (dev_type != DEV_UEVENT) dev_type = get_dev_type(dev); if (dev_type == DEV_NONE) { condlog(0, "'%s' is not a valid argument\n", dev); goto out; } if (dev_type == DEV_DEVNODE || dev_type == DEV_DEVT) strchop(dev); } if (dev_type == DEV_UEVENT) { openlog("multipath", 0, LOG_DAEMON); setlogmask(LOG_UPTO(libmp_verbosity + 3)); logsink = LOGSINK_SYSLOG; } set_max_fds(conf->max_fds); libmp_udev_set_sync_support(1); if (cmd != CMD_DUMP_CONFIG) { conf->retrigger_tries = 0; conf->force_sync = 1; } if ((cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) && enable_foreign) conf->enable_foreign = strdup(""); if (cmd == CMD_USABLE_PATHS) { r = check_usable_paths(conf, dev, dev_type) ? RTVL_FAIL : RTVL_OK; goto out; } if (cmd == CMD_VALID_PATH && (!dev || dev_type == DEV_DEVMAP)) { condlog(0, "the -c option requires a path to check"); goto out; } if (cmd == CMD_VALID_PATH) { char * name = convert_dev(dev, (dev_type == DEV_DEVNODE)); r = check_path_valid(name, conf, dev_type == DEV_UEVENT); goto out; } if (cmd == CMD_REMOVE_WWID && !dev) { condlog(0, "the -w option requires a device"); goto out; } if (cmd == CMD_FLUSH_ONE && dev_type != DEV_DEVMAP) { condlog(0, "the -f option requires a map name to remove"); goto out; } while (1) { int ret = delegate_to_multipathd(cmd, dev, dev_type, conf); if (ret == DELEGATE_OK) exit(RTVL_OK); if (ret == DELEGATE_ERROR) exit(RTVL_FAIL); if (ret == DELEGATE_RETRY) sleep(1); else /* NOT_DELEGATED */ break; } if (check_alias_settings(conf)) { fprintf(stderr, "fatal configuration error, aborting\n"); exit(RTVL_FAIL); } if (init_checkers()) { condlog(0, "failed to initialize checkers"); goto out; } if (init_prio()) { condlog(0, "failed to initialize prioritizers"); goto out; } /* Failing here is non-fatal */ init_foreign(conf->enable_foreign); if (cmd == CMD_RESET_WWIDS) { struct multipath * mpp; int i; vector curmp; curmp = vector_alloc(); if (!curmp) { condlog(0, "can't allocate memory for mp list"); goto out; } if (dm_get_maps(curmp) == 0) r = replace_wwids(curmp) ? RTVL_FAIL : RTVL_OK; if (r == RTVL_OK) printf("successfully reset wwids\n"); vector_foreach_slot_backwards(curmp, mpp, i) { vector_del_slot(curmp, i); free_multipath(mpp, KEEP_PATHS); } vector_free(curmp); goto out; } if (cmd == CMD_FLUSH_ONE) { if (dm_is_mpath(dev) != DM_IS_MPATH_YES) { condlog(0, "%s is not a multipath device", dev); r = RTVL_FAIL; goto out; } r = (dm_suspend_and_flush_map(dev, conf->remove_retries) != DM_FLUSH_OK) ? RTVL_FAIL : RTVL_OK; goto out; } else if (cmd == CMD_FLUSH_ALL) { r = (dm_flush_maps(conf->remove_retries) != DM_FLUSH_OK) ? RTVL_FAIL : RTVL_OK; goto out; } while ((r = configure(conf, cmd, dev_type, dev)) == RTVL_RETRY) condlog(3, "restart multipath configuration process"); out: put_multipath_config(conf); if (dev) free(dev); if (dev_type == DEV_UEVENT) closelog(); return r; } multipath-tools-0.11.1/multipath/modules-load.conf000066400000000000000000000002121475246302400222040ustar00rootroot00000000000000# load dm-multipath early, both multipathd and multipath depend on it # (note that multipath may be called from udev rules!) dm-multipath multipath-tools-0.11.1/multipath/multipath.8.in000066400000000000000000000177251475246302400214760ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MULTIPATH 8 2023-09-08 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . multipath \- Device mapper target autoconfig. . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-B | \-d | \-i | \-q | \-r \|] .RB [\| \-b\ \c .IR file \|] .RB [\| \-p\ \c .IR policy \|] .RB [\| device \|] . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-R\ \c .IR retries \|] .B \-f device . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-R\ \c .IR retries \|] .B \-F . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-l | \-ll \|] .RB [\| device \|] . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-a | \-w \|] .B device . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .B -W . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-i \|] .RB [\| \-c | \-C \|] .B device . .LP .B multipath .RB [\| \-v\ \c .IR level \|] .RB [\| \-i \|] .RB [\| \-u | \-U \|] . .LP .B multipath .RB [\| \-h | \-t | \-T \|] . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . .B multipath is used to detect and coalesce multiple paths to devices, for fail-over or performance reasons. . .\" ---------------------------------------------------------------------------- .SH ARGUMENTS .\" ---------------------------------------------------------------------------- . The \fBdevice\fR argument restricts \fBmultipath\fR's operation to devices matching the given 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 .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 the map or its paths, depending on the operation mode. Otherwise, it selects a path device. . .TP .B device ID kernel device number specified by major:minor numbers, e.g. \fI65:16\fR. This format can only be used for path devices. . .TP .B WWID a World Wide Identifier matching a multipath map or its paths. To list WWIDs of devices present in the system, use e.g. the command "\fImultipath -d -v3 2>/dev/null\fR". . .\" ---------------------------------------------------------------------------- .SH OPERATION MODES .\" ---------------------------------------------------------------------------- . The default operation mode is to detect and set up multipath maps from the devices found in the system. . Other operation modes are chosen by using one of the following command line switches: .TP .B \-f Flush (remove) a multipath device map specified as parameter, if unused. This operation is delegated to the multipathd daemon if it's running. . .TP .B \-F Flush (remove) all unused multipath device maps. This operation is delegated to the multipathd daemon if it's running. . .TP .B \-l Show ("list") the current multipath topology from information fetched in sysfs and the device mapper. . .TP .B \-ll Show ("list") the current multipath topology from all available information (sysfs, the device mapper, path checkers ...). . .TP .B \-a Add the WWID for the specified device to the WWIDs file. . .TP .B \-w Remove the WWID for the specified device from the WWIDs file. . .TP .B \-W Reset the WWIDs file to only include the current multipath devices. . .TP .B \-c Check if a block device should be a path in a multipath device. . .TP .B \-C Check if a multipath device has usable paths. This can be used to test whether or not I/O on this device is likely to succeed. The command itself doesn't attempt to do I/O on the device. . .TP .B \-u Check if the device specified in the program environment should be a path in a multipath device. . .TP .B \-U Check if the device specified in the program environment is a multipath device with usable paths. See \fB-C\fB. . .TP .B \-h Print usage text. . .TP .B \-t Display the currently used multipathd configuration. . .TP .B \-T Display the currently used multipathd configuration, limiting the output to those devices actually present in the system. This can be used a template for creating \fI@CONFIGFILE@\fR. . .\" ---------------------------------------------------------------------------- .SH OPTIONS .\" ---------------------------------------------------------------------------- . .TP .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 .I 0 Nothing is printed. .TP .I 1 In default mode, Names/WWIDs of created or modified multipath maps are printed. In list mode, WWIDs of all multipath maps are printed. .TP .I 2 In default mode, Topology of created or modified multipath maps is printed. In list mode, topology of all multipath maps is printed. .TP .I 3 All detected paths and the topology of all multipath maps are printed. . .LP . The verbosity level also controls the level of log and debug messages printed to \fIstderr\fR. The default level corresponds to \fILOG_NOTICE\fR (important messages that shouldn't be missed in normal operation). . .RE .TP .B \-d Dry run, do not create or update devmaps. . .TP .B \-e Enable all foreign libraries. This overrides the .I enable_foreign option from \fBmultipath.conf(5)\fR. . .TP .B \-i Ignore WWIDs file when processing devices. If \fIfind_multipaths strict\fR or \fIfind_multipaths no\fR is set in \fI@CONFIGFILE@\fR, multipath only considers devices that are listed in the WWIDs file. This option overrides that behavior. For other values of \fIfind_multipaths\fR, this option has no effect. See the description of \fIfind_multipaths\fR in .BR @CONFIGFILE@ (5). This option should only be used in rare circumstances. . .TP .B \-B Treat the bindings file as read only. . .TP .BI \-b " file" (\fBdeprecated, do not use\fR) Set \fIuser_friendly_names\fR bindings file location. The default is \fI@STATE_DIR@/bindings\fR. . .TP .B \-q Don't unset the device mapper feature \fIqueue_if_no_path\fR for multipath maps. Normally, \fBmultipath\fR would do so if \fBmultipathd\fR is not running, because only a running multipath daemon guarantees that unusable paths are reinstated when they become usable again. . .TP .BI \-p " policy" Force new maps to use the specified policy, overriding the configuration in \fBmultipath.conf(5)\fR. The possible values for \fIpolicy\fR are the same as the values for \fIpath_grouping_policy\fR in \fBmultipath.conf(5)\fR. Existing maps are not modified. . .TP .B \-r Force a reload of all existing multipath maps. This command is delegated to the multipathd daemon if it's running. In this case, other command line switches of the \fImultipath\fR command have no effect. . .TP .BI \-R " retries" Number of times to retry flushing multipath devices that are in use. The default is \fI0\fR. . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR multipathd (8), .BR multipath.conf (5), .BR kpartx (8), .BR udev (8), .BR dmsetup (8), .BR hotplug (8). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/multipath/multipath.conf.5.in000066400000000000000000002044201475246302400224050ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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 2024-08-15 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . @CONFIGFILE@, @CONFIGDIR@/*.conf \- multipath daemon configuration file. . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . .B "@CONFIGFILE@" is the configuration file for the multipath daemon. It is used to overwrite the built-in configuration table of \fBmultipathd\fP. Any line whose first non-white-space character is a '#' is considered a comment line. Empty lines are ignored. .PP Currently used multipathd configuration can be displayed with the \fBmultipath -t\fR or \fBmultipathd show config\fR command. . .PP Additional configuration can be made in drop-in files under .B @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, and support all sections and keywords. If a keyword occurs in the same section in multiple files, the last occurrence will take precedence over all others. . . .\" ---------------------------------------------------------------------------- .SH SYNTAX .\" ---------------------------------------------------------------------------- . The configuration file contains entries of the form: .RS .nf .ft B .sp
{ .RS .ft B .I "..." .ft B { .RS .ft B .I "..." .RE .ft B } .RE .ft B } .ft R .fi .RE .LP Each \fIsection\fP contains one or more attributes or subsections. The recognized keywords for attributes or subsections depend on the section in which they occur. .LP . \fB\fR and \fB\fR must be on a single line. \fB\fR is one of the keywords listed in this man page. \fB\fR is either a simple word (containing no whitespace and none of the characters '\(dq', '#', and '!') or \fIone\fR string enclosed in double quotes ("..."). Outside a quoted string, text starting with '#', and '!' is regarded as a comment and ignored until the end of the line. Inside a quoted string, '#' and '!' are normal characters, and whitespace is preserved. To represent a double quote character inside a double quoted string, use two consecutive double quotes ('""'). Thus '2.5\(dq SSD' can be written as "2.5"" SSD". .LP . Opening braces ('{') must follow the (sub)section name on the same line. Closing braces ('}') that mark the end of a (sub)section must be the only non-whitespace character on the line. Whitespace is ignored except inside double quotes, thus 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 are \fBcase-sensitive\fR and \fBnot anchored\fR, thus the expression "bar" matches "barbie", "rhabarber", and "wunderbar", but not "Barbie". To avoid unwanted substring matches, standard regular expression syntax using the special characters "^" and "$" can be used. . .LP . The following \fIsection\fP keywords are recognized: .TP 17 .B defaults This section defines default values for attributes which are used whenever no values are given in the appropriate device or multipath sections. .TP .B blacklist This section defines which devices should be excluded from the multipath topology discovery. .TP .B blacklist_exceptions This section defines which devices should be included in the multipath topology discovery, despite being listed in the \fIblacklist\fR section. .TP .B multipaths This section defines the multipath topologies. They are indexed by a \fIWorld Wide Identifier\fR(WWID). For details on the WWID generation see section \fIWWID generation\fR below. Attributes set in this section take precedence over all others. .TP .B devices This section defines the device-specific settings. Devices are identified by vendor, product, and revision. .TP .B overrides This section defines values for attributes that should override the device-specific settings for all devices. .RE .LP . . .\" ---------------------------------------------------------------------------- .SH "defaults section" .\" ---------------------------------------------------------------------------- . The \fIdefaults\fR section recognizes the following keywords: . . .TP 17 .B verbosity Default verbosity. Higher values increase the verbosity level. Valid levels are between 0 and 6. .RS .TP The default is: \fB2\fR .RE . . .TP .B polling_interval Interval between two path checks in seconds. For properly functioning paths, the interval between checks will gradually increase to \fImax_polling_interval\fR. This value will be overridden by the \fIWatchdogSec\fR setting in the multipathd.service definition if systemd is used. .RS .TP The default is: \fB5\fR .RE . . .TP .B max_polling_interval Maximal interval between two path checks in seconds. .RS .TP The default is: \fB4 * polling_interval\fR .RE . . .TP .B reassign_maps Enable reassigning of device-mapper maps. With this option multipathd will remap existing device-mapper maps to always point to multipath device, not the underlying block devices. Possible values are \fIyes\fR and \fIno\fR. .RS .TP The default is: \fBno\fR .RE . . .TP .B multipath_dir (Deprecated) This option is not supported anymore, and will be ignored. . . .TP .B path_selector The default path selector algorithm to use; they are offered by the kernel multipath target: .RS .TP 12 .I "round-robin 0" Loop through every path in the path group, sending the same amount of I/O to each. Some aspects of behavior can be controlled with the attributes: \fIrr_min_io\fR, \fIrr_min_io_rq\fR and \fIrr_weight\fR. .TP .I "queue-length 0" (Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on the amount of outstanding I/O 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 the amount of outstanding I/O to the path and its relative throughput. .TP .I "historical-service-time 0" (Since 5.8 kernel) Choose the path for the next bunch of I/O based on the estimation of future service time based on the history of previous I/O submitted to each path. .TP The default is: \fBservice-time 0\fR .RE . . .TP .B path_grouping_policy (Hardware-dependent) The default path grouping policy to apply to unspecified multipaths. Possible values are: .RS .TP 12 .I failover One path per priority group. .TP .I multibus All paths in one priority group. .TP .I group_by_serial One priority group per serial number. .TP .I group_by_prio One priority group per priority value. Priorities are determined by callout programs specified as a global, per-controller or per-multipath option in the configuration file. .TP .I group_by_node_name One priority group per target node name. Target node names are fetched in \fI/sys/class/fc_transport/target*/node_name\fR. .TP .I group_by_tpg One priority group per ALUA target port group. In order to use this policy, all paths in the multipath device must have \fIprio\fR set to \fBalua\fR. .TP The default is: \fBfailover\fR .RE . . .TP .B detect_pgpolicy If set to \fIyes\fR and all path devices are configured with either the \fIalua\fR or \fIsysfs\fR prioritizer, the multipath device will automatically use the \fIgroup_by_prio\fR path_grouping_policy. If set to \fIno\fR, the path_grouping_policy will be selected as usual. .RS .TP The default is: \fByes\fR .RE . . .TP .B detect_pgpolicy_use_tpg If both this and \fIdetect_pgpolicy\fR are set to \fIyes\fR and all path devices are configured with either the \fIalua\fR or \fIsysfs\fR prioritizer, the multipath device will automatically use the \fIgroup_by_tpg\fR path_grouping_policy. If set to \fIno\fR, the path_grouping_policy will be selected by the method described for \fIdetect_pgpolicy\fR above. .RS .TP The default is: \fBno\fR .RE . . .TP .B pg_timeout (Deprecated) This option is not supported anymore, and will be ignored. . . .TP .B uid_attrs . Setting this option activates \fBmerging uevents\fR by WWID, which may improve uevent processing efficiency. Moreover, it's an alternative method to configure the udev properties to use for determining unique path identifiers (WWIDs). .RS .PP The value of this option is a space separated list of records like \(dq\fItype:ATTR\fR\(dq, where \fItype\fR is matched against the beginning of the device node name (e.g. \fIsd:ATTR\fR matches \fIsda\fR), and \fIATTR\fR is the name of the udev property to use for matching devices. .PP If this option is configured and matches the device node name of a device, it overrides any other configured methods for determining the WWID for this device. .PP This option cannot be changed during runtime with the multipathd \fBreconfigure\fR command. .PP The default is: \fB\fR. To enable uevent merging, set it e.g. to \(dqsd:ID_SERIAL dasd:ID_UID nvme:ID_WWN\(dq. .RE . . .TP .B uid_attribute The udev attribute providing a unique path identifier (WWID). If \fIuid_attribute\fR is set to the empty string, WWID determination is done using the \fIsysfs\fR method rather than using udev (not recommended in production; see \fBWWID generation\fR below). .RS .TP The default is: \fBID_SERIAL\fR, for SCSI devices .TP The default is: \fBID_UID\fR, for DASD devices .TP The default is: \fBID_WWN\fR, for NVMe devices .RE . . .TP .B getuid_callout (Deprecated) This option is not supported anymore, and will be ignored. . . .TP .B prio The name of the path priority routine. The specified routine should return a numeric value specifying the relative priority 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 .I const Return a constant priority of \fI1\fR. .TP .I sysfs Use the sysfs attributes \fIaccess_state\fR and \fIpreferred_path\fR to generate the path priority. This prioritizer accepts the optional prio_arg \fIexclusive_pref_bit\fR. .TP .I emc (Hardware-dependent) Generate the path priority for DGC class arrays as CLARiiON CX/AX and EMC VNX families with Failover Mode 1 (Passive Not Ready(PNR)). .TP .I alua (Hardware-dependent) Generate the path priority based on the SCSI-3 ALUA settings. This prioritizer accepts the optional prio_arg \fIexclusive_pref_bit\fR. .TP .I ontap (Hardware-dependent) Generate the path priority for NetApp ONTAP FAS/AFF Series and rebranded arrays, with ONTAP native mode(not ALUA). .TP .I rdac (Hardware-dependent) Generate the path priority for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF Series and rebranded arrays, with "Linux DM-MP (Kernel 3.9 or earlier)" option. .TP .I hp_sw (Hardware-dependent) Generate the path priority for HP/COMPAQ/DEC HSG80 and MSA/HSV arrays with Active/Standby mode exclusively. .TP .I hds (Hardware-dependent) Generate the path priority for Hitachi AMS families of arrays other than AMS 2000. .TP .I random Generate a random priority between 1 and 10. .TP .I weightedpath Generate the path priority based on the regular expression and the priority provided as argument. Requires prio_args keyword. .TP .I path_latency Generate the path priority based on a latency algorithm. Requires prio_args keyword. .TP .I ana (Hardware-dependent) Generate the path priority based on the NVMe ANA settings. .TP .I datacore (Hardware-dependent) Generate the path priority for some DataCore storage arrays. Requires prio_args keyword. .TP .I iet (iSCSI only) Generate path priority for iSCSI targets based on IP address. Requires prio_args keyword. .PP The default depends on the \fBdetect_prio\fR setting: If \fBdetect_prio\fR is \fByes\fR (default), the default priority algorithm is \fBsysfs\fR (except for NetAPP E/EF Series, where it is \fBalua\fR). If \fBdetect_prio\fR is \fBno\fR, the default priority algorithm is \fBconst\fR. .RE . . .TP .B prio_args Arguments to pass to to the prio function. This only applies to certain prioritizers: .RS .TP 12 .I weighted Needs a value of the form \fI" ..."\fR .RS .TP 8 .I hbtl Regex can be of SCSI H:B:T:L format. For example: 1:0:.:. , *:0:0:. .TP .I devname Regex can be of device name format. For example: sda , sd.e .TP .I serial Regex can be of serial number format. For example: .*J1FR.*324 . The serial can be looked up through sysfs or by running multipathd show paths format "%z". For example: 0395J1FR904324 .TP .I wwn 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 .I path_latency Needs a value of the form "io_num=\fI<20>\fR base_num=\fI<10>\fR" .RS .TP 8 .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]. .TP .I base_num The base number value of logarithmic scale, used to partition different priority ranks. Valid Values: Double-precision floating-point, [1.1, 10]. And Max average latency value is 100s, min average latency value is 1us. 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 .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. .TP .I sysfs If \fIexclusive_pref_bit\fR is set, paths with the \fIpreferred path\fR bit set will always be in their own path group. .TP .I datacore .RS .TP 8 .I preferredsds (Mandatory) The preferred "SDS name". .TP .I timeout (Optional) The timeout for the INQUIRY, in ms. .RE .TP 12 .I iet .RS .TP 8 .I preferredip=... (Mandatory) Th preferred IP address, in dotted decimal notation, for iSCSI targets. .RE .TP The default is: \fB\fR .RE . . .TP .B features 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 .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 feature and \fIno_path_retry\fR are set, the latter value takes precedence. See KNOWN ISSUES. .TP .I pg_init_retries (Since kernel 2.6.24) Number of times to retry pg_init, it must be between 1 and 50. .TP .I pg_init_delay_msecs (Since kernel 2.6.38) Number of msecs before pg_init retry, it must be between 0 and 60000. .TP .I queue_mode (Since kernel 4.8) Select the queueing mode per multipath device. can be \fIbio\fR, \fIrq\fR or \fImq\fR, which corresponds to bio-based, request-based, and block-multiqueue (blk-mq) request-based, respectively. Before kernel 4.20 The default depends on the kernel parameter \fBdm_mod.use_blk_mq\fR. It is \fImq\fR if the latter is set, and \fIrq\fR otherwise. Since kernel 4.20, \fIrq\fR and \fImq\fR both correspond to block-multiqueue. Once a multipath device has been created, its queue_mode cannot be changed. \fInvme:tcp\fR paths are only supported in multipath devices with queue_mode set to \fIbio\fR. multipath will automatically set this when creating a device with \fInvme:tcp\fR paths. .TP The default is: \fB0\fR .RE . . .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 .I readsector0 (Deprecated) Read the first sector of the device. This checker is being deprecated, please use \fItur\fR or \fIdirectio\fR instead. .TP .I tur (Hardware-dependent) Issue a \fITEST UNIT READY\fR command to a SCSI device. .TP .I emc_clariion (Hardware-dependent) Query the DGC/EMC specific EVPD page 0xC0 to determine the path state for CLARiiON CX/AX and EMC VNX and Unity arrays families. .TP .I hp_sw (Hardware-dependent) Check the path state for HP/COMPAQ/DEC HSG80 and MSA/HSV arrays with Active/Standby mode exclusively. .TP .I rdac (Hardware-dependent) Check the path state for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF Series, and rebranded arrays. .TP .I directio Read the first sector with direct I/O. This checker could cause spurious path failures under high load. Increasing \fIchecker_timeout\fR can help with this. .TP .I cciss_tur (Hardware-dependent) Check the path state for HP/COMPAQ Smart Array(CCISS) controllers. .TP .I none Do not check the device, fallback to use the values retrieved from sysfs .TP The default is: \fBtur\fR .RE . . .TP .B alias_prefix The \fIuser_friendly_names\fR prefix. .RS .TP The default is: \fBmpath\fR .RE . . .TP .B failback 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 .I immediate Immediately failback to the highest priority pathgroup that contains active paths. .TP .I manual Do not perform automatic failback. .TP .I followover Used to deal with multiple computers accessing the same Active/Passive storage devices. Only perform automatic failback when the first path of a pathgroup becomes active. This keeps a cluster node from automatically failing back when another node requested the failover. .TP .I values > 0 Deferred failback (time to defer in seconds). .TP The default is: \fBmanual\fR .RE . . .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 . . .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 . . .TP .B max_fds Specify the maximum number of file descriptors that can be opened by multipath and multipathd. This is equivalent to ulimit \-n. A value of \fImax\fR will set this to the system limit from \fI/proc/sys/fs/nr_open\fR. If this is not set, the maximum number of open file descriptors is taken from the calling process. It is usually 1024. To be safe, this should be set to the maximum number of paths plus 32, if that number is greater than 1024. .RS .TP The default is: \fBmax\fR .RE . . .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 . . .TP .B no_path_retry Specify what to do when all paths are down. Possible values are: .RS .TP 12 .I value > 0 Number of retries until disable I/O queueing. .TP .I fail For immediate failure (no I/O queueing). .TP .I queue For never stop I/O queueing, similar to \fIqueue_if_no_path\fR. See KNOWN ISSUES. .TP The default is: \fBfail\fR .RE . . .TP .B queue_without_daemon If set to .I no , when multipathd stops, queueing will be turned off for all devices. This is useful for devices that set no_path_retry. If a machine is shut down while all paths to a device are down, it is possible to hang waiting for I/O to return from the device after multipathd has been stopped. Without multipathd running, access to the paths cannot be restored, and the kernel cannot be told to stop queueing I/O. Setting queue_without_daemon to .I no , avoids this problem. .RS .TP The default is: \fBno\fR .RE . . .TP .B checker_timeout Specify the timeout to use for path checkers and prioritizers, in seconds. Only prioritizers that issue scsi commands use checker_timeout. If a path does not respond to the checker command after \fIchecker_timeout\fR seconds have elapsed, it is considered down. .RS .TP The default is: in \fB/sys/block//device/timeout\fR .RE . . .TP .B allow_usb_devices If set to .I no , all USB devices will be skipped during path discovery. If you intend to use multipath on USB attached devices, set this to \fIyes\fR. .RS .TP The default is: \fBno\fR .RE . . .TP .B flush_on_last_del If set to .I always , multipathd will disable queueing when the last path to a device has been deleted. If set to .I never , multipathd will not disable queueing when the last path to a device has been deleted. Since multipath cannot safely remove a device while queueing is enabled, setting this to \fInever\fR means that multipathd will not automatically remove an unused multipath device whose paths are all deleted if it is currently set to queue_if_no_path. If set to .I unused , multipathd will only disable queueing when the last path is removed if nothing currently has the multipath device or any of the kpartx partition devices on top of it open. .RS .TP The default is: \fBunused\fR .RE . . .TP .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 .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. .RS .TP The default is: \fBno\fR .RE . . .TP .B fast_io_fail_tmo Specify the number of seconds the SCSI layer will wait after a problem has been detected on a FC remote port before failing I/O to devices on that remote port. This should be smaller than dev_loss_tmo. Setting this to .I off will disable the timeout. .RS .TP The default is: \fB5\fR .RE . . .TP .B dev_loss_tmo Specify the number of seconds the SCSI layer will wait after a connection loss has been detected on a remote port before removing it from the system. This can be set to "infinity", which effectively means 136 years (2^32-1 seconds). This parameter is only applied to Fibre Channel and SAS devices. .RS .LP The value of \fIdev_loss_tmo\fR is restricted by other settings. If \fIfast_io_fail_tmo\fR is set to a positive value, \fBmultipathd\fR will make sure that the value of \fIdev_loss_tmo\fR is larger than \fIno_path_retry\fR * \fIpolling_interval\fR. If \fIfast_io_fail_tmo\fR is not set, the kernel limits the \fIdev_loss_tmo\fR value to 600 seconds. In this case, the user has to make sure that \fIno_path_retry\fR is smaller than \fIdev_loss_tmo / polling_interval\fR. In particular, \fIno_path_retry\fR must not be set to \(dq\fIqueue\fR\(dq. See KNOWN ISSUES. .LP When path devices reappear after a connection loss, it is much easier for the kernel to simply reactivate an inactive device than to re-add a previously deleted one. It is therefore recommended to set \fIdev_loss_tmo\fR to a large value within the restrictions mentioned above. .LP Fibre Channel and SAS devices have hardware-dependent defaults, which are left unchanged if \fIdev_loss_tmo\fR is not specified. For a few storage arrays, the multipath-tools built-in settings override the default. Run \fImultipath -T\fR to see the settings for your device. .TP The default is: \fB\fR .RE . . .TP .B eh_deadline Specify the maximum number of seconds the SCSI layer will spend doing error handling when scsi devices fail. After this timeout the scsi layer will perform a full HBA reset. Setting this may be necessary in cases where the rport is never lost, so \fIfast_io_fail_tmo\fR and \fIdev_loss_tmo\fR will never trigger, but (frequently due to load) scsi commands still hang. \fBNote:\fR when the scsi error handler performs the HBA reset, all target paths on that HBA will be affected. eh_deadline should only be set in cases where all targets on the affected HBAs are multipathed. .RS .TP The default is: \fB\fR .RE . . .TP .B max_retries Specify the maximum number of times the SCSI layer will retry IO commands for some types of SCSI errors before returning failure. Setting this can be helpful for cases where IO commands hang and timeout. By default, the SCSI layer will retry IOs 5 times. Reducing this value will allow multipath to retry the IO down another path sooner. Valid values are \fB0\fR through \fB5\fR. .RS .TP The default is: \fB\fR .RE . . .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 .B log_checker_err If set to .I once , multipathd logs the first path checker error at logging level 2. Any later errors are logged at level 3 until the device is restored. If set to .I always , multipathd always logs the path checker error at logging level 2. .RS .TP The default is: \fBalways\fR .RE . . .TP .B reservation_key This is the service action reservation key used by mpathpersist. It must be set for all multipath devices using persistent reservations, and it must be the same as the RESERVATION KEY field of the PERSISTENT RESERVE OUT parameter list which contains an 8-byte value provided by the application client to the device server to identify the I_T nexus. If the \fI--param-aptpl\fR option is used when registering the key with mpathpersist, \fB:aptpl\fR must be appended 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 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. .TP The default is: \fB\fR .RE . . .TP .B all_tg_pt Set the 'all targets ports' flag when registering keys with mpathpersist. Some arrays automatically set and clear registration keys on all target ports from a host, instead of per target port per host. The ALL_TG_PT flag must be set to successfully use mpathpersist on these arrays. Setting this option is identical to calling mpathpersist with \fI--param-alltgpt\fR .RS .TP The default is: \fBno\fR .RE . . .TP .B retain_attached_hw_handler (Obsolete for kernels >= 4.3) If set to .I yes and the SCSI layer has already attached a hardware_handler to the device, multipath will not force the device to use the hardware_handler specified by @CONFIGFILE@. If the SCSI layer has not attached a hardware handler, multipath will continue to use its configured hardware handler. .RS .PP The default is: \fByes\fR .PP \fBImportant Note:\fR Linux kernel 4.3 or newer always behaves as if \fB"retain_attached_hw_handler yes"\fR was set. .RE . . .TP .B detect_prio If set to .I yes , multipath will try to detect if the device supports SCSI-3 ALUA. If so, the device will automatically use the \fIsysfs\fR prioritizer if the required sysfs attributes \fIaccess_state\fR and \fIpreferred_path\fR are supported, or the \fIalua\fR prioritizer if not. If set to .I no , the prioritizer will be selected as usual. .RS .TP The default is: \fByes\fR .RE . . .TP .B detect_checker if set to .I yes , multipath will try to detect if the device supports SCSI-3 ALUA. If so, the device will automatically use the \fItur\fR checker. If set to .I no , the checker will be selected as usual. .RS .TP The default is: \fByes\fR .RE . . .TP .B force_sync 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 multipathd checkers running in parallel causes significant CPU pressure. .RS .TP The default is: \fBno\fR .RE . . .TP .B strict_timing If set to .I yes , multipathd will start a new path checker loop after exactly one second, so that each path check will occur at exactly \fIpolling_interval\fR seconds. On busy systems path checks might take longer than one second; here the missing ticks will be accounted for on the next round. A warning will be printed if path checks take longer than \fIpolling_interval\fR seconds. .RS .TP The default is: \fBno\fR .RE . . .TP .B deferred_remove If set to .I yes , multipathd will do a deferred remove instead of a regular remove when the last path device has been deleted. This means that if the multipath device is still in use, it will be freed when the last user closes it. If path is added to the multipath device before the last user closes it, the deferred remove will be canceled. .RS .TP The default is: \fBno\fR .RE . . .TP .B partition_delimiter This parameter controls how multipath chooses the names of partition devices of multipath maps if a multipath map is renamed (e.g. if a map alias is added or changed). If this parameter is set to a string other than "/UNSET/" (even the empty string), multipath inserts that string between device name and partition number to construct the partition device name. Otherwise (i.e. if this parameter is unset or has the value "/UNSET/"), the behavior depends on the map name: if it ends in a digit, a \fI"p"\fR is inserted between name and partition number; otherwise, the partition number is simply appended. Distributions may use a non-null default value for this option; in this case, the user must set it to "/UNSET/" to obtain the original \fB\fR behavior. Use \fImultipath -T\fR to check the current settings. .RS .TP The default is: \fB\fR .RE . . .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 .B san_path_err_threshold If set to a value greater than 0, multipathd will watch paths and check how many times a path has been failed due to errors.If the number of failures on a particular path is greater than the san_path_err_threshold, then the path will not reinstate till san_path_err_recovery_time. These path failures should occur within a san_path_err_forget_rate checks, if not we will consider the path is good enough to reinstate. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B san_path_err_forget_rate If set to a value greater than 0, multipathd will check whether the path failures has exceeded the san_path_err_threshold within this many checks i.e san_path_err_forget_rate . If so we will not reinstate the path till san_path_err_recovery_time. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B san_path_err_recovery_time If set to a value greater than 0, multipathd will make sure that when path failures has exceeded the san_path_err_threshold within san_path_err_forget_rate then the path will be placed in failed state for san_path_err_recovery_time duration.Once san_path_err_recovery_time has timeout we will reinstate the failed path . san_path_err_recovery_time value should be in secs. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B marginal_path_double_failed_time One of the four parameters of supporting path check based on accounting IO error such as intermittent error. When a path failed event occurs twice in \fImarginal_path_double_failed_time\fR seconds due to an IO error and all the other three parameters are set, multipathd will fail the path and enqueue this path into a queue of which members are sent a couple of continuous direct reading asynchronous IOs at a fixed sample rate of 10HZ to start IO error accounting process. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B marginal_path_err_sample_time One of the four parameters of supporting path check based on accounting IO error such as intermittent error. If it is set to a value no less than 120, when a path fail event occurs twice in \fImarginal_path_double_failed_time\fR second due to an IO error, multipathd will fail the path and enqueue this path into a queue of which members are sent a couple of continuous direct reading asynchronous IOs at a fixed sample rate of 10HZ to start the IO accounting process for the path will last for \fImarginal_path_err_sample_time\fR. If the rate of IO error on a particular path is greater than the \fImarginal_path_err_rate_threshold\fR, then the path will not reinstate for \fImarginal_path_err_recheck_gap_time\fR seconds unless there is only one active path. After \fImarginal_path_err_recheck_gap_time\fR expires, the path will be requeued for rechecking. If checking result is good enough, the path will be reinstated. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B marginal_path_err_rate_threshold The error rate threshold as a permillage (1/1000). One of the four parameters of supporting path check based on accounting IO error such as intermittent error. Refer to \fImarginal_path_err_sample_time\fR. If the rate of IO errors on a particular path is greater than this parameter, then the path will not reinstate for \fImarginal_path_err_recheck_gap_time\fR seconds unless there is only one active path. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B marginal_path_err_recheck_gap_time One of the four parameters of supporting path check based on accounting IO error such as intermittent error. Refer to \fImarginal_path_err_sample_time\fR. If this parameter is set to a positive value, the failed path of which the IO error rate is larger than \fImarginal_path_err_rate_threshold\fR will be kept in failed state for \fImarginal_path_err_recheck_gap_time\fR seconds. When \fImarginal_path_err_recheck_gap_time\fR seconds expires, the path will be requeued for checking. If checking result is good enough, the path will be reinstated, or else it will keep failed. See "Shaky paths detection" below. .RS .TP The default is: \fBno\fR .RE . . .TP .B delay_watch_checks (Deprecated) This option is \fBdeprecated\fR, and mapped to \fIsan_path_err_forget_rate\fR. If this is set to a value greater than 0 and no \fIsan_path_err\fR options are set, \fIsan_path_err_forget_rate\fR will be set to the value of \fIdelay_watch_checks\fR and \fIsan_path_err_threshold\fR will be set to 1. See the \fIsan_path_err_forget_rate\fR and \fIsan_path_err_threshold\fR options, and "Shaky paths detection" below for more information. .RS .TP The default is: \fBno\fR .RE . . .TP .B delay_wait_checks (Deprecated) This option is \fBdeprecated\fR, and mapped to \fIsan_path_err_recovery_time\fR. If this is set to a value greater than 0 and no \fIsan_path_err\fR options are set, \fIsan_path_err_recovery_time\fR will be set to the value of \fIdelay_wait_checks\fR times \fImax_polling_interval\fR. This will give approximately the same wait time as delay_wait_checks previously did. Also, \fIsan_path_err_threshold\fR will be set to 1. See the \fIsan_path_err_recovery_time\fR and \fIsan_path_err_threshold\fR options, and "Shaky paths detection" below for more information. .RS .TP The default is: \fBno\fR .RE . . .TP .B marginal_pathgroups If set to \fIoff\fR, the \fIdelay_*_checks\fR, \fImarginal_path_*\fR, and \fIsan_path_err_*\fR options will keep marginal, or \(dqshaky\(dq, paths from being reinstated until they have been monitored for some time. This can cause situations where all non-marginal paths are down, and no paths are usable until multipathd detects this and reinstates a marginal path. If the multipath device is not configured to queue IO in this case, it can cause IO errors to occur, even though there are marginal paths available. However, if this option is set to \fIon\fR, when one of the marginal path detecting methods determines that a path is marginal, it will be reinstated and placed in a separate pathgroup that will only be used after all the non-marginal pathgroups have been tried first. This prevents the possibility of IO errors occurring while marginal paths are still usable. After the path has been monitored for the configured time, and is declared healthy, it will be returned to its normal pathgroup. If this option is set to \fIfpin\fR, multipathd will receive fpin notifications, set path states to "marginal" accordingly, and regroup paths as described for \fIon\fR. This option can't be used in combination with other options for "Shaky path detection" (see below). \fBNote:\fR If this is set to \fIfpin\fR, the \fImarginal_path_*\fR and \fIsan_path_err_*\fR options are implicitly set to \fIno\fP. Also, this option cannot be switched either to or from \fIfpin\fR on a multipathd reconfigure. multipathd must be restarted for the change to take effect. See "Shaky paths detection" below for more information. .RS .TP The default is: \fBoff\fR .RE . . .TP .B find_multipaths This option controls whether multipath and multipathd try to create multipath maps over non-blacklisted devices they encounter. This matters a) when a device is encountered by \fBmultipath -u\fR during udev rule processing (a device is 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 .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. .TP .I off Multipath behaves like \fBstrict\fR. Multipathd behaves like \fBgreedy\fR. \fIno\fR or \fI0\fR is accepted as an alias for \fIoff\fR. .TP .I on Both multipathd and multipath treat a device as multipath device if the conditions for \fBstrict\fR are met, or if at least two non-blacklisted paths with the same WWID have been detected. \fIyes\fR or \fI1\fR is accepted as an alias for \fIon\fR. .TP .I greedy Both multipathd and multipath treat every non-blacklisted device as multipath device path. .TP .I smart This differs from \fIfind_multipaths yes\fR only in the way it treats new devices for which only one path has been detected yet. When such a device is first encountered in udev rules, it is treated as a multipath device. multipathd waits whether additional paths with the same WWID appears. If that happens, it sets up a multipath map. If it doesn\'t happen until a timeout expires, or if setting up the map fails, a new uevent is triggered for the device; at second encounter in the udev rules, the device will be treated as non-multipath and passed on to upper layers. \fBNote:\fR this may cause delays during device detection if there are single-path devices which aren\'t blacklisted. .TP The default is: \fBstrict\fR .RE . . .TP .B find_multipaths_timeout Timeout, in seconds, to wait for additional paths after detecting the first one, if \fIfind_multipaths "smart"\fR (see above) is set. If the value is \fBpositive\fR, this timeout is used for all unknown, non-blacklisted devices encountered. If the value is \fBnegative\fR (recommended), it's only applied to "known" devices that have an entry in multipath's hardware table, either in the built-in table or in a \fIdevice\fR section; other ("unknown") devices will use a timeout of only 1 second to avoid booting delays. The value 0 means "use the built-in default". If \fIfind_multipath\fR has a value other than \fIsmart\fR, this option has no effect. .RS .TP The default is: \fB-10\fR (10s for known and 1s for unknown hardware) .RE . . .TP .B uxsock_timeout CLI receive timeout in milliseconds. For larger systems CLI commands might timeout before the multipathd lock is released and the CLI command can be processed. This will result in errors like "timeout receiving packet" to be returned from CLI commands. In these cases it is recommended to increase the CLI timeout to avoid those issues. .RS .TP The default is: \fB4000\fR .RE . . .TP .B retrigger_tries Sets the number of times multipathd will try to retrigger a uevent to get the WWID. .RS .TP The default is: \fB3\fR .RE . . .TP .B retrigger_delay Sets the amount of time, in seconds, to wait between retriggers. .RS .TP The default is: \fB10\fR .RE . . .TP .B missing_uev_wait_timeout Controls how many seconds multipathd will wait, after a new multipath device is created, to receive a change event from udev for the device, before automatically enabling device reloads. Usually multipathd will delay reloads on a device until it receives a change uevent from the initial table load. .RS .TP The default is: \fB30\fR .RE . . .TP .B skip_kpartx If set to .I yes , kpartx will not automatically create partitions on the device. .RS .TP The default is: \fBno\fR .RE . . .TP .B disable_changed_wwids (Deprecated) This option is not supported anymore, and will be ignored. .RE . . .TP .B remove_retries This sets how may times multipath will retry removing a device that is in-use. Between each attempt, multipath will sleep 1 second. .RS .TP The default is: \fB0\fR .RE . . .TP .B max_sectors_kb Sets the \fImax_sectors_kb\fR device parameter on some path devices and the multipath device to the specified value. \fImax_sectors_kb\fR is the largest I/O size, in units of 1024 bytes, that the kernel allows for a single I/O request. For hardware devices like SCSI disks, this value is limited by the capabilities of the hardware. It is crucial that the value of a multipath map is never higher than the minimum value of of all its path devices. This is ensured by the kernel when a multipath map is loaded, but manipulating the values of a map or either of its paths while the map is live can cause race conditions and I/O errors. Therefore this value is only enforced by multipathd when a multipath map is first created, or when a path device is added to a map. In both cases, race conditions are avoided by the kernel. .RS .PP Setting \fImax_sectors_kb\fR does not guarantee that all path devices will have this value set. It is not an error if the value of a path device is higher than that of the containing multipath map. It is also not an error if the actual limit of a map is lower than the value in \fI@CONFIGFILE@\fR. This can happen if the hardware limits of one or more path devices are lower than the configured value. .PP Normally the kernel and its device drivers take care of the maximum I/O size, and administrators do not need to bother about \fImax_sectors_kb\fR. But some hardware devices may report incorrect I/O size limits, or other components in the environment (e.g. the fabric) may impose constraints that the kernel cannot detect. In such cases setting \fImax_sectors_kb\fR makes sense. It should be set when maps are first created, and not be changed thereafter. If the setting \fBmust\fR be changed for a live map, set the value in \fI@CONFIGFILE@\fR, run \fBmultipathd reconfigure\fR, and use \fBmultipathd del path \fR and \fBmultipathd add path \fR to delete and re-add the same path device. .LP The default is: \fB\fR .RE . . .TP .B ghost_delay Sets the number of seconds that multipath will wait after creating a device with only ghost paths before marking it ready for use in systemd. This gives the active paths time to appear before the multipath runs the hardware handler to switch the ghost paths to active ones. Setting this to \fI0\fR or \fIno\fR makes multipath immediately mark a device with only ghost paths as ready. .RS .TP The default is: \fBno\fR .RE . . .TP .B auto_resize Controls when multipathd will automatically resize a multipath device. If set to \fInever\fR, multipath devices must always be manually resized by either running \fBmultipathd resize map \fR. If set to \fIgrow_only\fR, when multipathd detects that all of a multipath device's paths have increased in size, it will automatically grow the multipath device to the new size. If set to \fIgrow_shrink\fR, multipathd will also automatically shrink the device once it detects all of its paths have decreased in size. .RS .TP The default is: \fBnever\fR .RE . . .TP .B enable_foreign Enables or disables foreign libraries (see section .I FOREIGN MULTIPATH SUPPORT below). The value is a regular expression; foreign libraries are loaded if their name (e.g. \(dqnvme\(dq) matches the expression. By default, no foreign libraries are enabled. Set this to \(dqnvme\(dq to enable NVMe native multipath support, or \(dq.*\(dq to enable all foreign libraries. .RS .TP The default is: \fB\(dqNONE\(dq\fR .RE . . .TP .B recheck_wwid If set to \fIyes\fR, when a failed path is restored, its wwid is rechecked. If the wwid has changed, the path is removed from the current multipath device, and re-added as a new path. Multipathd will also recheck a path's wwid if it is manually re-added. This option only works for SCSI devices that are configured to use the default uid_attribute, \fIID_SERIAL\fR, or sysfs for getting their wwid. .RS .TP The default is: \fBno\fR .RE . . .\" ---------------------------------------------------------------------------- .SH "blacklist and blacklist_exceptions sections" .\" ---------------------------------------------------------------------------- . The \fIblacklist\fR section is used to exclude specific devices from the multipath topology. It is most commonly used to exclude local disks or non-disk devices (such as LUNs for the storage array controller) from being handled by multipath-tools. .LP . . In the \fIblacklist\fR and \fIblacklist_exceptions\fR sections, starting a quoted value with an exclamation mark \fB"!"\fR will invert the matching of the rest of the regular expression. For instance, \fB"!^sd[a-z]"\fR will match all values that do not start with \fB"sd[a-z]"\fR. The exclamation mark can be escaped \fB"\\!"\fR to match a literal \fB!\fR at the start of a regular expression. \fBNote:\fR The exclamation mark must be inside quotes, otherwise it will be treated as starting a comment. .LP . . The \fIblacklist_exceptions\fR section is used to revert the actions of the \fIblacklist\fR section. This allows one to selectively include ("whitelist") devices which would normally be excluded via the \fIblacklist\fR section. A common usage is to blacklist "everything" using a catch-all regular expression, and create specific blacklist_exceptions entries for those devices that should be handled by multipath-tools. .LP . . The following keywords are recognized in both sections. The defaults are empty unless explicitly stated. .TP 17 .B devnode Regular expression matching the device nodes to be excluded/included. .RS .PP The default \fIblacklist\fR consists of the regular expression \fB"!^(sd[a-z]|dasd[a-z]|nvme[0-9])"\fR. This causes all device types other than scsi, dasd, and nvme to be excluded from multipath handling by default. .RE .TP .B wwid Regular expression for the \fIWorld Wide Identifier\fR of a device to be excluded/included. . .TP .B device Subsection for the device description. This subsection recognizes the .B vendor and .B product keywords. Both are regular expressions. For a full description of these keywords please see the \fIdevices\fR section description. .TP .B property Regular expression for an udev property. All devices that have matching udev properties will be excluded/included. The handling of the \fIproperty\fR keyword is special, because devices \fBmust\fR have at least one whitelisted udev property; otherwise they're treated as blacklisted, and the message "\fIblacklisted, udev property missing\fR" is displayed in the logs. . .RS .PP .B Note: The behavior of this option has changed in \fBmultipath-tools\fR 0.8.2 compared to previous versions. Blacklisting by missing properties is only applied to devices which do have the property specified by \fIuid_attribute\fR (e.g. \fIID_SERIAL\fR) set. Previously, it was applied to every device, possibly causing devices to be blacklisted because of temporary I/O error conditions. .PP The default \fIblacklist exception\fR is: \fB(SCSI_IDENT_|ID_WWN)\fR, causing well-behaved SCSI devices and devices that provide a WWN (World Wide Number) to be included, and all others to be excluded. .RE .TP .B protocol Regular expression for the protocol of a device to be excluded/included. .RS .PP The protocol strings that multipath recognizes are \fIscsi:fcp\fR, \fIscsi:spi\fR, \fIscsi:ssa\fR, \fIscsi:sbp\fR, \fIscsi:srp\fR, \fIscsi:iscsi\fR, \fIscsi:sas\fR, \fIscsi:adt\fR, \fIscsi:ata\fR, \fIscsi:unspec\fR, \fInvme:pcie\fR, \fInvme:rdma\fR, \fInvme:fc\fR, \fInvme:tcp\fR, \fInvme:loop\fR, \fInvme:apple-nvme\fR, \fInvme:unspec\fR, \fIccw\fR, \fIcciss\fR, and \fIundef\fR. The protocol that a path is using can be viewed by running \fBmultipathd show paths format "%d %P"\fR .RE .LP For every device, these 5 blacklist criteria are evaluated in the order "property, dev\%node, device, protocol, wwid". If a device turns out to be blacklisted by any criterion, it's excluded from handling by multipathd, and the later criteria aren't evaluated anymore. For each criterion, the whitelist takes precedence over the blacklist if a device matches both. .LP .B Note: Besides the blacklist and whitelist, other configuration options such as \fIfind_multipaths\fR have an impact on whether or not a given device is handled by multipath-tools. . . .\" ---------------------------------------------------------------------------- .SH "multipaths section" .\" ---------------------------------------------------------------------------- . The \fImultipaths\fR section allows setting attributes of multipath maps. The attributes that are set via the multipaths section (see list below) take precedence over all other configuration settings, including those from the \fIoverrides\fR section. .LP The only recognized attribute for the \fImultipaths\fR section is the \fImultipath\fR subsection. If there are multiple \fImultipath\fR subsections matching a given WWID, the contents of these sections are merged, and settings from later entries take precedence. .LP . . The \fImultipath\fR subsection recognizes the following attributes: .TP 17 .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, 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. .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 .PD .1v .RS .TP 18 .B path_grouping_policy .TP .B path_selector .TP .B prio .TP .B prio_args .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 .TP .B reservation_key .TP .B user_friendly_names .TP .B deferred_remove .TP .B san_path_err_threshold .TP .B san_path_err_forget_rate .TP .B san_path_err_recovery_time .TP .B marginal_path_err_sample_time .TP .B marginal_path_err_rate_threshold .TP .B marginal_path_err_recheck_gap_time .TP .B marginal_path_double_failed_time .TP .B delay_watch_checks .TP .B delay_wait_checks .TP .B skip_kpartx .TP .B max_sectors_kb .TP .B ghost_delay .RE .PD .LP . . .\" ---------------------------------------------------------------------------- .SH "devices section" .\" ---------------------------------------------------------------------------- . .TP 4 .B Important: The built-in hardware device table of .I multipath-tools is created by members of the Linux community in the hope that it will be useful. The existence of an entry for a given storage product in the hardware table .B does not imply that the product vendor supports, or has tested, the product with .I multipath-tools in any way. .B Always consult the vendor\(aqs official documentation for support-related information. .PP \fImultipath-tools\fR have a built-in device table with reasonable defaults 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 precedence. Inside a configuration file, later entries have higher precedence than earlier ones. .LP The only recognized attribute for the \fIdevices\fR section is the \fIdevice\fR subsection. Devices detected in the system are matched against the device entries using the \fBvendor\fR, \fBproduct\fR, and \fBrevision\fR fields, which are all POSIX Extended regular expressions (see \fBregex\fR(7)). .LP The vendor, product, and revision fields that multipath or multipathd detect for devices in a system depend on the device type. For SCSI devices, they correspond to the respective fields of the SCSI INQUIRY page. In general, the command '\fImultipathd show paths format "%d %s"\fR' command can be used to see the detected properties for all devices in the system. .LP . The \fIdevice\fR subsection recognizes the following attributes: .TP 17 .B vendor (Mandatory) Regular expression to match the vendor name. .TP .B product (Mandatory) Regular expression to match the product name. .TP .B revision Regular expression to match the product revision. If not specified, any revision matches. .TP .B product_blacklist Products with the given \fBvendor\fR matching this string are blacklisted. This is equivalent to a \fBdevice\fR entry in the \fIblacklist\fR section with the \fIvendor\fR attribute set to this entry's \fIvendor\fR, and the \fIproduct\fR attribute set to the value of \fIproduct_blacklist\fR. .TP .B alias_prefix The user_friendly_names prefix to use for this device type, instead of the default "mpath". .TP .B vpd_vendor The vendor specific vpd page information, using the vpd page abbreviation. The vpd page abbreviation can be found by running \fIsg_vpd -e\fR. multipathd will use this information to gather device specific information that can be displayed with the \fI%g\fR wildcard for the \fImultipathd show maps format\fR and \fImultipathd show paths format\fR commands. Currently only the \fBhp3par\fR vpd page is supported. .TP .B hardware_handler The hardware handler to use for this device type. The following hardware handler are implemented: .RS .TP 12 .I 1 emc (Hardware-dependent) Hardware handler for DGC class arrays as CLARiiON CX/AX and EMC VNX families with Failover Mode 1 (Passive Not Ready(PNR)). .TP .I 1 rdac (Hardware-dependent) Hardware handler for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF Series and rebranded arrays, with "Linux DM-MP (Kernel 3.9 or earlier)" option. .TP .I 1 hp_sw (Hardware-dependent) Hardware handler for HP/COMPAQ/DEC HSG80 and MSA/HSV arrays with Active/Standby mode exclusively. .TP .I 1 alua (Hardware-dependent) Hardware handler for SCSI-3 ALUA compatible arrays. .TP .I 1 ana (Hardware-dependent) Hardware handler for NVMe ANA compatible arrays. .PP The default is: \fB\fR .PP \fBImportant Note:\fR Linux kernels 4.3 and newer automatically attach a device handler to known devices (which includes all devices supporting SCSI-3 ALUA) and disallow changing the handler afterwards. Setting \fBhardware_handler\fR for such devices on these kernels has no effect. .RE . . .LP The following attributes are optional; if not set the default values are taken from the \fIdefaults\fR section: .sp 1 .PD .1v .RS .TP 18 .B path_grouping_policy .TP .B uid_attribute .TP .B path_selector .TP .B path_checker .TP .B prio .TP .B prio_args .TP .B features .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 .TP .B eh_deadline .TP .B flush_on_last_del .TP .B user_friendly_names .TP .B retain_attached_hw_handler .TP .B detect_prio .TP .B detect_checker .TP .B deferred_remove .TP .B san_path_err_threshold .TP .B san_path_err_forget_rate .TP .B san_path_err_recovery_time .TP .B marginal_path_err_sample_time .TP .B marginal_path_err_rate_threshold .TP .B marginal_path_err_recheck_gap_time .TP .B marginal_path_double_failed_time .TP .B delay_watch_checks .TP .B delay_wait_checks .TP .B skip_kpartx .TP .B max_sectors_kb .TP .B ghost_delay .TP .B all_tg_pt .RE .PD .LP . . .\" ---------------------------------------------------------------------------- .SH "overrides 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 .PD .1v .RS .TP 18 .B path_grouping_policy .TP .B uid_attribute .TP .B path_selector .TP .B path_checker .TP .B alias_prefix .TP .B features .TP .B prio .TP .B prio_args .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 .TP .B dev_loss_tmo .TP .B eh_deadline .TP .B user_friendly_names .TP .B retain_attached_hw_handler .TP .B detect_prio .TP .B detect_checker .TP .B deferred_remove .TP .B san_path_err_threshold .TP .B san_path_err_forget_rate .TP .B san_path_err_recovery_time .TP .B marginal_path_err_sample_time .TP .B marginal_path_err_rate_threshold .TP .B marginal_path_err_recheck_gap_time .TP .B marginal_path_double_failed_time .TP .B delay_watch_checks .TP .B delay_wait_checks .TP .B skip_kpartx .TP .B max_sectors_kb .TP .B ghost_delay .TP .B all_tg_pt .RE .PD .LP The overrides section also recognizes the optional \fIprotocol\fR subsection, and can contain multiple protocol subsections. Path devices are matched against the protocol subsection using the mandatory \fItype\fR attribute. Attributes in a matching protocol subsection take precedence over attributes in the rest of the overrides section. If there are multiple matching protocol subsections, later entries take precedence. .TP .B protocol subsection The protocol subsection recognizes the following mandatory attribute: .RS .TP .B type The protocol string of the path device. The possible values are \fIscsi:fcp\fR, \fIscsi:spi\fR, \fIscsi:ssa\fR, \fIscsi:sbp\fR, \fIscsi:srp\fR, \fIscsi:iscsi\fR, \fIscsi:sas\fR, \fIscsi:adt\fR, \fIscsi:ata\fR, \fIscsi:unspec\fR, \fInvme:pcie\fR, \fInvme:rdma\fR, \fInvme:fc\fR, \fInvme:tcp\fR, \fInvme:loop\fR, \fInvme:apple-nvme\fR, \fInvme:unspec\fR, \fIccw\fR, \fIcciss\fR, and \fIundef\fR. This is \fBnot\fR a regular expression. the path device protocol string must match exactly. The protocol that a path is using can be viewed by running \fBmultipathd show paths format "%d %P"\fR .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 .PD .1v .RS .TP .B fast_io_fail_tmo .TP .B dev_loss_tmo .TP .B eh_deadline .PD . . .\" ---------------------------------------------------------------------------- .SH "WWID generation" .\" ---------------------------------------------------------------------------- . Multipath uses a \fIWorld Wide Identification\fR (WWID) to determine 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 .B uid_attrs The WWID is derived from udev attributes by matching the device node name; cf \fIuid_attrs\fR above. .TP .B uid_attribute Use the value of the specified udev attribute; cf \fIuid_attribute\fR above. .TP .B sysfs Try to determine the WWID from sysfs attributes. For SCSI devices, this means reading the Vital Product Data (VPD) page \(dqDevice Identification\(dq (0x83). .PP The default settings (using udev and \fBuid_attribute\fR configured from the built-in hardware table) should work fine in most scenarios. Users who want to enable uevent merging must set \fBuid_attrs\fR. . . .\" ---------------------------------------------------------------------------- .SH "Shaky paths detection" .\" ---------------------------------------------------------------------------- . A common problem in SAN setups is the occurrence of intermittent errors: a path is unreachable, then reachable again for a short time, disappears again, and so forth. This happens typically on unstable interconnects. It is undesirable to switch pathgroups unnecessarily on such frequent, unreliable events. \fImultipathd\fR supports three different methods for detecting this situation and dealing with it. All methods share the same basic mode of operation: If a path is found to be \(dqshaky\(dq or \(dqflipping\(dq, and appears to be in healthy status, it is not reinstated (put back to use) immediately. Instead, it is placed in the \(dqdelayed\(dq state and watched for some time, and only reinstated if the healthy state appears to be stable. If the \fImarginal_pathgroups\fR option is set, the path will reinstated immediately, but placed in a special pathgroup for marginal paths. Marginal 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 .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 for more information. . .TP .B \(dqmarginal_path\(dq failure tracking If a second failure event (good->bad transition) occurs within \fImarginal_path_double_failed_time\fR seconds after a failure, high-frequency monitoring is started for the affected path: I/O is sent at a rate of 10 per second. This is done for \fImarginal_path_err_sample_time\fR seconds. During this period, the path is not reinstated. If the rate of errors remains below \fImarginal_path_err_rate_threshold\fR during the monitoring period, the path is reinstated. Otherwise, it is kept in failed state for \fImarginal_path_err_recheck_gap_time\fR, and after that, it is monitored again. For this method, time intervals are measured in seconds. .TP .B \(dqsan_path_err\(dq failure tracking multipathd counts path failures for each path. Once the number of failures exceeds the value given by \fIsan_path_err_threshold\fR, the path is not reinstated for \fIsan_path_err_recovery_time\fR seconds. While counting failures, multipathd \(dqforgets\(dq one past failure every \(dqsan_path_err_forget_rate\(dq ticks; thus if errors don't occur more often then once in the forget rate interval, the failure count doesn't 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 .LP This algorithm is superseded by the \(dqmarginal_path\(dq failure tracking, but remains supported for backward compatibility. . .RE .TP .B \(dqFPIN\(dq failure tracking Fibre channel fabrics can notify hosts about fabric-level issues such 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. It is \fBstrongly discouraged\fR to use more than one of these methods for any given multipath map, because the two concurrent methods may interact in unpredictable ways. If the \(dqmarginal_path\(dq method is active, the \(dqsan_path_err\(dq parameters are implicitly set to 0. . . .\" ---------------------------------------------------------------------------- .SH "FOREIGN MULTIPATH SUPPORT" .\" ---------------------------------------------------------------------------- . multipath and multipathd can load \(dqforeign\(dq libraries to add support for other multipathing technologies besides the Linux device mapper. Currently this support is limited to printing detected information about multipath setup. In topology output, the names of foreign maps are prefixed by the foreign library name in square brackets, as in this example: . .P .EX # multipath -ll uuid.fedcba98-3579-4567-8765-123456789abc [nvme]:nvme4n9 NVMe,Some NVMe controller,FFFFFFFF size=167772160 features='n/a' hwhandler='ANA' wp=rw |-+- policy='n/a' prio=50 status=optimized | `- 4:38:1 nvme4c38n1 0:0 n/a optimized live `-+- policy='n/a' prio=50 status=optimized `- 4:39:1 nvme4c39n1 0:0 n/a optimized live .EE . .P The \(dqnvme\(dq foreign library provides support for NVMe native multipathing in the kernel. It is part of the standard multipath package. . . .\" ---------------------------------------------------------------------------- .SH "KNOWN ISSUES" .\" ---------------------------------------------------------------------------- . The usage of \fIqueue_if_no_path\fR option can lead to \fID state\fR processes being hung and not killable in situations where all the paths to the LUN go offline. It is advisable to use the \fIno_path_retry\fR option instead. .P The use of \fIqueue_if_no_path\fR or \fIno_path_retry\fR might lead to a deadlock if the \fIdev_loss_tmo\fR setting results in a device being removed while I/O is still queued. The multipath daemon will update the \fIdev_loss_tmo\fR setting accordingly to avoid this deadlock. Hence if both values are specified the order of precedence is \fIno_path_retry, queue_if_no_path, dev_loss_tmo\fR. . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR udev (8), .BR dmsetup (8), .BR multipath (8), .BR multipathc (8), .BR multipathd (8). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui, and others. .\" EOF multipath-tools-0.11.1/multipath/multipath.rules.in000066400000000000000000000104731475246302400224520ustar00rootroot00000000000000# Set DM_MULTIPATH_DEVICE_PATH if the device should be handled by multipath SUBSYSTEM!="block", GOTO="end_mpath" KERNEL!="sd*|dasd*|nvme*", GOTO="end_mpath" ACTION=="remove", TEST=="@RUNTIME_DIR@/multipath/find_multipaths/$major:$minor", \ RUN+="@SYSDIR_BIN@/rm -f @RUNTIME_DIR@/multipath/find_multipaths/$major:$minor" ACTION!="add|change", GOTO="end_mpath" IMPORT{cmdline}="nompath" ENV{nompath}=="?*", GOTO="end_mpath" IMPORT{cmdline}="multipath" ENV{multipath}=="off", GOTO="end_mpath" ENV{DEVTYPE}!="partition", GOTO="test_dev" IMPORT{parent}="DM_MULTIPATH_DEVICE_PATH" ENV{DM_MULTIPATH_DEVICE_PATH}=="1", ENV{ID_FS_TYPE}="none", \ ENV{SYSTEMD_READY}="0" GOTO="end_mpath" LABEL="test_dev" # FIND_MULTIPATHS_WAIT_UNTIL is the timeout (in seconds after the # epoch). IMPORT{db}="FIND_MULTIPATHS_WAIT_UNTIL" ENV{.SAVED_FM_WAIT_UNTIL}="$env{FIND_MULTIPATHS_WAIT_UNTIL}" # multipath -u needs to know if this device has ever been exported IMPORT{db}="DM_MULTIPATH_DEVICE_PATH" # multipath -u sets DM_MULTIPATH_DEVICE_PATH and, # if "find_multipaths smart", also FIND_MULTIPATHS_WAIT_UNTIL. IMPORT{program}=="@BINDIR@/multipath -u %k", \ ENV{.MPATH_CHECK_PASSED}="1" # case 1: this is definitely multipath ENV{DM_MULTIPATH_DEVICE_PATH}=="1", \ ENV{ID_FS_TYPE}="mpath_member", ENV{SYSTEMD_READY}="0", \ GOTO="stop_wait" # case 2: this is definitely not multipath, or timeout has expired ENV{DM_MULTIPATH_DEVICE_PATH}!="2", \ GOTO="stop_wait" # Code below here is only run in "smart" mode. # multipath -u has indicated this is "maybe" multipath. # Note that DM_MULTIPATH_DEVICE_PATH has the value 2 at this point. # This value will never propagate to other rules files, because # it will be reset to 1 in the "pretend_multipath" section below. # This shouldn't happen, just in case. ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="?*", GOTO="end_mpath" # Be careful not to start the timer twice. ACTION!="add", GOTO="pretend_mpath" ENV{.SAVED_FM_WAIT_UNTIL}=="?*", GOTO="pretend_mpath" # At this point, we are seeing this path for the first time, and it's "maybe" multipath. # The actual start command for the timer. # # The purpose of this command is only to make sure we will receive another # uevent eventually. *Any* uevent may cause waiting to finish if it either ends # in case 1-3 above, or if it arrives after FIND_MULTIPATHS_WAIT_UNTIL. # # Note that this will try to activate multipathd if it isn't running yet. # If that fails, the unit starts and expires nonetheless. If multipathd # startup needs to wait for other services, this wait time will add up with # the --on-active timeout. # # We must trigger an "add" event because LVM2 will only act on those. RUN+="@SYSDIR_BIN@/systemd-run --unit=cancel-multipath-wait-$kernel --description 'cancel waiting for multipath siblings of $kernel' --no-block --timer-property DefaultDependencies=no --timer-property Conflicts=shutdown.target --timer-property Before=shutdown.target --timer-property Conflicts=initrd-cleanup.service --timer-property Before=initrd-cleanup.service --timer-property AccuracySec=500ms --property DefaultDependencies=no --property Conflicts=shutdown.target --property Before=shutdown.target --property Conflicts=initrd-cleanup.service --property Before=initrd-cleanup.service --on-active=$env{FIND_MULTIPATHS_WAIT_UNTIL} @SYSDIR_BIN@/udevadm trigger --action=add $sys$devpath" LABEL="pretend_mpath" ENV{DM_MULTIPATH_DEVICE_PATH}="1" ENV{SYSTEMD_READY}="0" GOTO="end_mpath" LABEL="stop_wait" # If timeout hasn't expired but we're not in "maybe" state anymore, stop timer # Do this only once, and only if the timer has been started before IMPORT{db}="FIND_MULTIPATHS_WAIT_CANCELLED" ENV{FIND_MULTIPATHS_WAIT_CANCELLED}=="?*", GOTO="end_mpath" ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="?*", GOTO="end_mpath" ENV{FIND_MULTIPATHS_WAIT_UNTIL}=="0", GOTO="end_mpath" ENV{FIND_MULTIPATHS_WAIT_CANCELLED}="1" RUN+="@SYSDIR_BIN@/systemctl stop cancel-multipath-wait-$kernel.timer" # If "multipath -u" failed, no values are imported from the program, # and we are still using the values for DM_MULTIPATH_DEVICE_PATH and # FIND_MULTIPATHS_WAIT_UNTIL that were imported from the database. # If we are in "smart" mode, we need to give up on the path now, # since this may have been the timeout event. Without the imports # from "multipath -u", we can't tell. ENV{.MPATH_CHECK_PASSED}!="?*", ENV{DM_MULTIPATH_DEVICE_PATH}="0" LABEL="end_mpath" multipath-tools-0.11.1/multipath/scsi_dh.conf000066400000000000000000000001201475246302400212310ustar00rootroot00000000000000# Load SCSI device handler modules for multipath early # This file may be empty multipath-tools-0.11.1/multipath/tmpfiles.conf.in000066400000000000000000000000531475246302400220520ustar00rootroot00000000000000d @RUNTIME_DIR@/multipath 0700 root root - multipath-tools-0.11.1/multipathd/000077500000000000000000000000001475246302400171215ustar00rootroot00000000000000multipath-tools-0.11.1/multipathd/Makefile000066400000000000000000000053071475246302400205660ustar00rootroot00000000000000include ../Makefile.inc EXEC := multipathd CLI := multipathc MANPAGES := multipathd.8 CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) -I$(mpathcmddir) -I$(thirdpartydir) \ -DBINDIR='"$(bindir)"' $(SYSTEMD_CPPFLAGS) # # debugging stuff # #CPPFLAGS += -DLCKDBG #CPPFLAGS += -DLOGDBG CFLAGS += $(BIN_CFLAGS) LDFLAGS += $(BIN_LDFLAGS) CLI_LIBDEPS := -L$(mpathutildir) -lmpathutil -L$(mpathcmddir) -lmpathcmd \ -ludev -ldl -lurcu -lpthread $(SYSTEMD_LIBDEPS) LIBDEPS := -L$(multipathdir) -lmultipath -L$(mpathpersistdir) -lmpathpersist \ -ldevmapper $(CLI_LIBDEPS) ifeq ($(READLINE),libedit) RL_CPPFLAGS := -DUSE_LIBEDIT RL_LIBDEPS := -ledit # See comment in multipathc.c ifeq ($(shell sed -En 's/.*\ #include #include #include #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "parser.h" #include "util.h" #include "version.h" #include "mpath_cmd.h" #include "cli.h" #include "cli_handlers.h" #include "debug.h" #include "strbuf.h" static vector keys; static vector handlers; vector get_keys(void) { return keys; } vector get_handlers(void) { return handlers; } /* See KEY_INVALID in cli.h */ #define INVALID_FINGERPRINT ((uint32_t)(0)) static struct key * alloc_key (void) { return (struct key *)calloc(1, sizeof(struct key)); } static struct handler * alloc_handler (void) { return (struct handler *)calloc(1, sizeof(struct handler)); } static int add_key (vector vec, char * str, uint8_t code, int has_param) { struct key * kw; kw = alloc_key(); if (!kw) return 1; kw->code = code; kw->has_param = has_param; kw->str = strdup(str); if (!kw->str) goto out; if (!vector_alloc_slot(vec)) goto out1; vector_set_slot(vec, kw); return 0; out1: free(kw->str); out: free(kw); return 1; } static struct handler *add_handler(uint32_t fp, cli_handler *fn, bool locked) { struct handler * h; h = alloc_handler(); if (h == NULL) return NULL; if (!vector_alloc_slot(handlers)) { free(h); return NULL; } vector_set_slot(handlers, h); h->fingerprint = fp; h->fn = fn; h->locked = locked; return h; } static struct handler * find_handler (uint32_t fp) { int i; struct handler *h; if (fp == INVALID_FINGERPRINT) return NULL; vector_foreach_slot (handlers, h, i) if (h->fingerprint == fp) return h; return NULL; } int set_handler_callback__ (uint32_t fp, cli_handler *fn, bool locked) { struct handler *h; assert(fp != INVALID_FINGERPRINT); assert(find_handler(fp) == NULL); h = add_handler(fp, fn, locked); if (!h) { condlog(0, "%s: failed to set handler for code %"PRIu32, __func__, fp); return 1; } return 0; } void free_key (struct key * kw) { if (kw->str) free(kw->str); if (kw->param) free(kw->param); free(kw); } void free_keys (vector vec) { int i; struct key * kw; vector_foreach_slot (vec, kw, i) free_key(kw); vector_free(vec); } void free_handlers (void) { int i; struct handler * h; vector_foreach_slot (handlers, h, i) free(h); vector_free(handlers); handlers = NULL; } int load_keys (void) { int r = 0; keys = vector_alloc(); if (!keys) return 1; r += add_key(keys, "list", VRB_LIST, 0); r += add_key(keys, "show", VRB_LIST, 0); r += add_key(keys, "add", VRB_ADD, 0); r += add_key(keys, "remove", VRB_DEL, 0); r += add_key(keys, "del", VRB_DEL, 0); r += add_key(keys, "switch", VRB_SWITCH, 0); r += add_key(keys, "switchgroup", VRB_SWITCH, 0); r += add_key(keys, "suspend", VRB_SUSPEND, 0); r += add_key(keys, "resume", VRB_RESUME, 0); r += add_key(keys, "reinstate", VRB_REINSTATE, 0); r += add_key(keys, "fail", VRB_FAIL, 0); r += add_key(keys, "resize", VRB_RESIZE, 0); r += add_key(keys, "reset", VRB_RESET, 0); r += add_key(keys, "reload", VRB_RELOAD, 0); r += add_key(keys, "forcequeueing", VRB_FORCEQ, 0); r += add_key(keys, "disablequeueing", VRB_DISABLEQ, 0); r += add_key(keys, "restorequeueing", VRB_RESTOREQ, 0); r += add_key(keys, "paths", KEY_PATHS, 0); r += add_key(keys, "maps", KEY_MAPS, 0); r += add_key(keys, "multipaths", KEY_MAPS, 0); r += add_key(keys, "path", KEY_PATH, 1); r += add_key(keys, "map", KEY_MAP, 1); r += add_key(keys, "multipath", KEY_MAP, 1); r += add_key(keys, "group", KEY_GROUP, 1); r += add_key(keys, "reconfigure", VRB_RECONFIGURE, 0); r += add_key(keys, "daemon", KEY_DAEMON, 0); r += add_key(keys, "status", KEY_STATUS, 0); r += add_key(keys, "stats", KEY_STATS, 0); r += add_key(keys, "topology", KEY_TOPOLOGY, 0); r += add_key(keys, "config", KEY_CONFIG, 0); r += add_key(keys, "blacklist", KEY_BLACKLIST, 0); r += add_key(keys, "devices", KEY_DEVICES, 0); r += add_key(keys, "raw", KEY_RAW, 0); r += add_key(keys, "wildcards", KEY_WILDCARDS, 0); r += add_key(keys, "quit", VRB_QUIT, 0); r += add_key(keys, "exit", VRB_QUIT, 0); r += add_key(keys, "shutdown", VRB_SHUTDOWN, 0); r += add_key(keys, "getprstatus", VRB_GETPRSTATUS, 0); r += add_key(keys, "setprstatus", VRB_SETPRSTATUS, 0); r += add_key(keys, "unsetprstatus", VRB_UNSETPRSTATUS, 0); r += add_key(keys, "format", KEY_FMT, 1); r += add_key(keys, "json", KEY_JSON, 0); r += add_key(keys, "getprkey", VRB_GETPRKEY, 0); r += add_key(keys, "setprkey", VRB_SETPRKEY, 0); r += add_key(keys, "unsetprkey", VRB_UNSETPRKEY, 0); r += add_key(keys, "key", KEY_KEY, 1); r += add_key(keys, "local", KEY_LOCAL, 0); r += add_key(keys, "setmarginal", VRB_SETMARGINAL, 0); r += add_key(keys, "unsetmarginal", VRB_UNSETMARGINAL, 0); r += add_key(keys, "all", KEY_ALL, 0); if (r) { free_keys(keys); keys = NULL; return 1; } return 0; } struct key *find_key (const char * str) { int i; int len, klen; struct key * kw = NULL; struct key * foundkw = NULL; len = strlen(str); vector_foreach_slot (keys, kw, i) { if (strncmp(kw->str, str, len)) continue; klen = strlen(kw->str); if (len == klen) return kw; /* exact match */ if (len < klen) { if (!foundkw) foundkw = kw; /* shortcut match */ else return NULL; /* ambiguous word */ } } return foundkw; } static void cleanup_strvec(vector *arg) { free_strvec(*arg); } static void cleanup_keys(vector *arg) { free_keys(*arg); } /* * get_cmdvec() - parse input * * @cmd: a command string to be parsed * @v: a vector of keywords with parameters * * returns: * ENOMEM: not enough memory to allocate command * ESRCH: keyword not found at end of input * ENOENT: keyword not found somewhere else * EINVAL: argument missing for command */ int get_cmdvec (char *cmd, vector *v, bool allow_incomplete) { int i; int r = 0; int get_param = 0; char * buff; struct key * kw = NULL; struct key * cmdkw = NULL; vector cmdvec __attribute__((cleanup(cleanup_keys))) = vector_alloc(); vector strvec __attribute__((cleanup(cleanup_strvec))) = alloc_strvec(cmd); if (!strvec || !cmdvec) return ENOMEM; vector_foreach_slot(strvec, buff, i) { if (is_quote(buff)) continue; if (get_param) { get_param = 0; cmdkw->param = strdup(buff); continue; } kw = find_key(buff); if (!kw) { r = i == VECTOR_SIZE(strvec) - 1 ? ESRCH : ENOENT; break; } cmdkw = alloc_key(); if (!cmdkw) { r = ENOMEM; break; } if (!vector_alloc_slot(cmdvec)) { free(cmdkw); r = ENOMEM; break; } vector_set_slot(cmdvec, cmdkw); cmdkw->code = kw->code; cmdkw->has_param = kw->has_param; if (kw->has_param) get_param = 1; } if (get_param) r = EINVAL; if (r && !allow_incomplete) return r; *v = cmdvec; cmdvec = NULL; return r; } uint32_t fingerprint(const struct vector_s *vec) { int i; uint32_t fp = 0; struct key * kw; if (!vec || VECTOR_SIZE(vec) > 4) return INVALID_FINGERPRINT; vector_foreach_slot(vec, kw, i) { if (i >= 4) break; fp |= (uint32_t)kw->code << (8 * i); } return fp; } struct handler *find_handler_for_cmdvec(const struct vector_s *v) { return find_handler(fingerprint(v)); } int alloc_handlers (void) { handlers = vector_alloc(); if (!handlers) return 1; return 0; } static int genhelp_sprint_aliases (struct strbuf *reply, vector keys, struct key * refkw) { int i; struct key * kw; size_t initial_len = get_strbuf_len(reply); vector_foreach_slot (keys, kw, i) { if (kw->code == refkw->code && kw != refkw && print_strbuf(reply, "|%s", kw->str) < 0) return -1; } return get_strbuf_len(reply) - initial_len; } static int do_genhelp(struct strbuf *reply, const char *cmd, int error) { int i, j, k; uint32_t fp; struct handler * h; struct key * kw; int rc = 0; size_t initial_len = get_strbuf_len(reply); switch(error) { case ENOMEM: rc = print_strbuf(reply, "%s: Not enough memory\n", cmd); break; case ESRCH: rc = print_strbuf(reply, "%s: not found\n", cmd); break; case EINVAL: rc = print_strbuf(reply, "%s: Missing argument\n", cmd); break; } if (rc < 0) return -1; if (print_strbuf(reply, VERSION_STRING) < 0 || append_strbuf_str(reply, "CLI commands reference:\n") < 0) return -1; vector_foreach_slot (handlers, h, i) { fp = h->fingerprint; for (k = 0; k < 4; k++, fp >>= 8) { uint32_t code = fp & 0xff; if (!code) break; vector_foreach_slot (keys, kw, j) { if ((uint32_t)kw->code == code) { if (print_strbuf(reply, " %s", kw->str) < 0 || genhelp_sprint_aliases(reply, keys, kw) < 0) return -1; if (kw->has_param) { if (print_strbuf(reply, " $%s", kw->str) < 0) return -1; } break; } } } if (append_strbuf_str(reply, "\n") < 0) return -1; } return get_strbuf_len(reply) - initial_len; } void genhelp_handler(const char *cmd, int error, struct strbuf *reply) { if (do_genhelp(reply, cmd, error) == -1) condlog(0, "genhelp_handler: out of memory"); } char * get_keyparam (vector v, uint8_t code) { struct key * kw; int i; vector_foreach_slot(v, kw, i) if (kw->code == code) return kw->param; return NULL; } int cli_init (void) { if (load_keys()) return 1; if (alloc_handlers()) return 1; init_handler_callbacks(); return 0; } void cli_exit(void) { free_handlers(); free_keys(keys); keys = NULL; } multipath-tools-0.11.1/multipathd/cli.h000066400000000000000000000071221475246302400200430ustar00rootroot00000000000000#ifndef CLI_H_INCLUDED #define CLI_H_INCLUDED #include /* * CLI commands consist of 4 bytes, a verb (byte 0) and up to * 3 qualifiers (byte 1 - 3). */ enum { /* See INVALID_FINGERPRINT in cli.c */ KEY_INVALID = 0, /* Verbs */ VRB_LIST = 1, VRB_ADD = 2, VRB_DEL = 3, VRB_RESET = 4, VRB_SWITCH = 5, VRB_RECONFIGURE = 6, VRB_SUSPEND = 7, VRB_RESUME = 8, VRB_RESIZE = 9, VRB_RELOAD = 10, VRB_FAIL = 11, VRB_REINSTATE = 12, VRB_DISABLEQ = 13, VRB_RESTOREQ = 14, VRB_FORCEQ = 15, VRB_GETPRSTATUS = 16, VRB_SETPRSTATUS = 17, VRB_UNSETPRSTATUS = 18, VRB_GETPRKEY = 19, VRB_SETPRKEY = 20, VRB_UNSETPRKEY = 21, VRB_SETMARGINAL = 22, VRB_UNSETMARGINAL = 23, VRB_SHUTDOWN = 24, VRB_QUIT = 25, /* Qualifiers, values must be different from verbs */ KEY_PATH = 65, KEY_PATHS = 66, KEY_MAP = 67, KEY_MAPS = 68, KEY_TOPOLOGY = 69, KEY_CONFIG = 70, KEY_BLACKLIST = 71, KEY_DEVICES = 72, KEY_WILDCARDS = 73, KEY_ALL = 74, KEY_DAEMON = 75, KEY_FMT = 76, KEY_RAW = 77, KEY_STATUS = 78, KEY_STATS = 79, KEY_JSON = 80, KEY_LOCAL = 81, KEY_GROUP = 82, KEY_KEY = 83, }; /* * The shifted qualifiers determine valid positions of the * keywords in the known commands. E.g. the only qualifier * that's valid in position 3 is "fmt", e.g. "list maps raw fmt". */ enum { /* byte 1: qualifier 1 */ Q1_PATH = KEY_PATH << 8, Q1_PATHS = KEY_PATHS << 8, Q1_MAP = KEY_MAP << 8, Q1_MAPS = KEY_MAPS << 8, Q1_TOPOLOGY = KEY_TOPOLOGY << 8, Q1_CONFIG = KEY_CONFIG << 8, Q1_BLACKLIST = KEY_BLACKLIST << 8, Q1_DEVICES = KEY_DEVICES << 8, Q1_WILDCARDS = KEY_WILDCARDS << 8, Q1_ALL = KEY_ALL << 8, Q1_DAEMON = KEY_DAEMON << 8, Q1_STATUS = KEY_STATUS << 8, /* byte 2: qualifier 2 */ Q2_FMT = KEY_FMT << 16, Q2_RAW = KEY_RAW << 16, Q2_STATUS = KEY_STATUS << 16, Q2_STATS = KEY_STATS << 16, Q2_TOPOLOGY = KEY_TOPOLOGY << 16, Q2_JSON = KEY_JSON << 16, Q2_LOCAL = KEY_LOCAL << 16, Q2_GROUP = KEY_GROUP << 16, Q2_KEY = KEY_KEY << 16, /* byte 3: qualifier 3 */ Q3_FMT = KEY_FMT << 24, }; #define INITIAL_REPLY_LEN 1200 #define REALLOC_REPLY(r, a, m) \ do { \ if ((a)) { \ char *tmp = (r); \ \ if (m >= MAX_REPLY_LEN) { \ condlog(1, "Warning: max reply length exceeded"); \ free(tmp); \ (r) = NULL; \ } else { \ (r) = realloc((r), (m) * 2); \ if ((r)) { \ memset((r) + (m), 0, (m)); \ (m) *= 2; \ } \ else \ free(tmp); \ } \ } \ } while (0) struct key { char * str; char * param; uint8_t code; int has_param; }; struct strbuf; typedef int (cli_handler)(void *keywords, struct strbuf *reply, void *data); struct handler { uint32_t fingerprint; int locked; cli_handler *fn; }; int alloc_handlers (void); int set_handler_callback__ (uint32_t fp, cli_handler *fn, bool locked); #define set_handler_callback(fp, fn) set_handler_callback__(fp, fn, true) #define set_unlocked_handler_callback(fp, fn) set_handler_callback__(fp, fn, false) int get_cmdvec (char *cmd, vector *v, bool allow_incomplete); struct handler *find_handler_for_cmdvec(const struct vector_s *v); void genhelp_handler (const char *cmd, int error, struct strbuf *reply); int load_keys (void); char * get_keyparam (vector v, uint8_t code); void free_key (struct key * kw); void free_keys (vector vec); void free_handlers (void); int cli_init (void); void cli_exit(void); uint32_t fingerprint(const struct vector_s *vec); vector get_keys(void); vector get_handlers(void); struct key *find_key (const char * str); #endif /* CLI_H_INCLUDED */ multipath-tools-0.11.1/multipathd/cli_handlers.c000066400000000000000000001013101475246302400217100ustar00rootroot00000000000000/* * Copyright (c) 2005 Christophe Varoqui */ #define _GNU_SOURCE #include "checkers.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include #include "devmapper.h" #include "discovery.h" #include "config.h" #include "configure.h" #include "blacklist.h" #include "debug.h" #include "dm-generic.h" #include "print.h" #include "sysfs.h" #include #include #include #include "util.h" #include "prkey.h" #include "propsel.h" #include "main.h" #include "mpath_cmd.h" #include "cli.h" #include "uevent.h" #include "foreign.h" #include "strbuf.h" #include "cli_handlers.h" static struct path * find_path_by_str(const struct vector_s *pathvec, const char *str, const char *action_str) { struct path *pp; if (!(pp = find_path_by_devt(pathvec, str))) pp = find_path_by_dev(pathvec, str); if (!pp && action_str) condlog(2, "%s: invalid path name. cannot %s", str, action_str); return pp; } static int show_paths (struct strbuf *reply, struct vectors *vecs, char *style, int pretty) { int i; struct path * pp; int hdr_len = 0; fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if (pretty) { if ((width = alloc_path_layout()) == NULL) return 1; get_path_layout(vecs->pathvec, 1, width); foreign_path_layout(width); } if (pretty && (hdr_len = snprint_path_header(reply, style, width)) < 0) return 1; vector_foreach_slot(vecs->pathvec, pp, i) { if (snprint_path(reply, style, pp, width) < 0) return 1; } if (snprint_foreign_paths(reply, style, width) < 0) return 1; if (pretty && get_strbuf_len(reply) == (size_t)hdr_len) /* No output - clear header */ truncate_strbuf(reply, 0); return 0; } static int show_path (struct strbuf *reply, struct vectors *vecs, struct path *pp, char *style) { if (snprint_path(reply, style, pp, NULL) < 0) return 1; return 0; } static int show_map_topology (struct strbuf *reply, struct multipath *mpp, struct vectors *vecs, const fieldwidth_t *width) { if (refresh_multipath(vecs, mpp)) return 1; if (snprint_multipath_topology(reply, mpp, 2, width) < 0) return 1; return 0; } static int show_maps_topology (struct strbuf *reply, struct vectors * vecs) { int i; struct multipath * mpp; fieldwidth_t *p_width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((p_width = alloc_path_layout()) == NULL) return 1; get_path_layout(vecs->pathvec, 0, p_width); foreign_path_layout(p_width); vector_foreach_slot(vecs->mpvec, mpp, i) { if (refresh_multipath(vecs, mpp)) { i--; continue; } if (snprint_multipath_topology(reply, mpp, 2, p_width) < 0) return 1; } if (snprint_foreign_topology(reply, 2, p_width) < 0) return 1; return 0; } static int show_maps_json (struct strbuf *reply, struct vectors * vecs) { int i; struct multipath * mpp; vector_foreach_slot(vecs->mpvec, mpp, i) { if (refresh_multipath(vecs, mpp)) { return 1; } } if (snprint_multipath_topology_json(reply, vecs) < 0) return 1; return 0; } static int show_map_json (struct strbuf *reply, struct multipath * mpp, struct vectors * vecs) { if (refresh_multipath(vecs, mpp)) return 1; if (snprint_multipath_map_json(reply, mpp) < 0) return 1; return 0; } static int show_config (struct strbuf *reply, const struct vector_s *hwtable, const struct vector_s *mpvec) { struct config *conf; int rc; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); rc = snprint_config__(conf, reply, hwtable, mpvec); pthread_cleanup_pop(1); if (rc < 0) return 1; return 0; } void reset_stats(struct multipath * mpp) { mpp->stat_switchgroup = 0; mpp->stat_path_failures = 0; mpp->stat_map_loads = 0; mpp->stat_total_queueing_time = 0; mpp->stat_queueing_timeouts = 0; mpp->stat_map_failures = 0; } static int cli_list_config (void *v, struct strbuf *reply, void *data) { condlog(3, "list config (operator)"); return show_config(reply, NULL, NULL); } static void v_free(void *x) { vector_free(x); } static int cli_list_config_local (void *v, struct strbuf *reply, void *data) { struct vectors *vecs = (struct vectors *)data; vector hwes; int ret; condlog(3, "list config local (operator)"); hwes = get_used_hwes(vecs->pathvec); pthread_cleanup_push(v_free, hwes); ret = show_config(reply, hwes, vecs->mpvec); pthread_cleanup_pop(1); return ret; } static int cli_list_paths (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list paths (operator)"); return show_paths(reply, vecs, PRINT_PATH_CHECKER, 1); } static int cli_list_paths_fmt (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list paths (operator)"); return show_paths(reply, vecs, fmt, 1); } static int cli_list_paths_raw (void *v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list paths (operator)"); return show_paths(reply, vecs, fmt, 0); } static int cli_list_path (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path *pp; param = convert_dev(param, 1); condlog(3, "%s: list path (operator)", param); pp = find_path_by_str(vecs->pathvec, param, "list path"); if (!pp) return -ENODEV; return show_path(reply, vecs, pp, "%o"); } static int cli_list_map_topology (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); fieldwidth_t *p_width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((p_width = alloc_path_layout()) == NULL) return 1; get_path_layout(vecs->pathvec, 0, p_width); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; condlog(3, "list multipath %s (operator)", param); return show_map_topology(reply, mpp, vecs, p_width); } static int cli_list_maps_topology (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list multipaths (operator)"); return show_maps_topology(reply, vecs); } static int cli_list_map_json (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; condlog(3, "list multipath json %s (operator)", param); return show_map_json(reply, mpp, vecs); } static int cli_list_maps_json (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list multipaths json (operator)"); return show_maps_json(reply, vecs); } static int cli_list_wildcards (void *v, struct strbuf *reply, void *data) { if (snprint_wildcards(reply) < 0) return 1; return 0; } static int show_status (struct strbuf *reply, struct vectors *vecs) { if (snprint_status(reply, vecs) < 0) return 1; return 0; } static int show_daemon (struct strbuf *reply) { const char *status; bool pending_reconfig; status = daemon_status(&pending_reconfig); if (status == NULL) return 1; if (print_strbuf(reply, "pid %d %s%s\n", daemon_pid, status, pending_reconfig ? " (pending reconfigure)" : "") < 0) return 1; return 0; } static int show_map (struct strbuf *reply, struct multipath *mpp, char *style, const fieldwidth_t *width) { if (snprint_multipath(reply, style, mpp, width) < 0) return 1; return 0; } static int show_maps (struct strbuf *reply, struct vectors *vecs, char *style, int pretty) { int i; struct multipath * mpp; int hdr_len = 0; fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if (pretty) { if ((width = alloc_multipath_layout()) == NULL) return 1; get_multipath_layout(vecs->mpvec, 1, width); foreign_multipath_layout(width); } if (pretty && (hdr_len = snprint_multipath_header(reply, style, width)) < 0) return 1; vector_foreach_slot(vecs->mpvec, mpp, i) { if (refresh_multipath(vecs, mpp)) { i--; continue; } if (snprint_multipath(reply, style, mpp, width) < 0) return 1; } if (snprint_foreign_multipaths(reply, style, width) < 0) return 1; if (pretty && get_strbuf_len(reply) == (size_t)hdr_len) /* No output - clear header */ truncate_strbuf(reply, 0); return 0; } static int cli_list_maps_fmt (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list maps (operator)"); return show_maps(reply, vecs, fmt, 1); } static int cli_list_maps_raw (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list maps (operator)"); return show_maps(reply, vecs, fmt, 0); } static int cli_list_map_fmt (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); char * fmt = get_keyparam(v, KEY_FMT); fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((width = alloc_multipath_layout()) == NULL) return 1; get_multipath_layout(vecs->mpvec, 1, width); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; condlog(3, "list map %s fmt %s (operator)", param, fmt); return show_map(reply, mpp, fmt, width); } static int cli_list_maps (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list maps (operator)"); return show_maps(reply, vecs, PRINT_MAP_NAMES, 1); } static int cli_list_status (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list status (operator)"); return show_status(reply, vecs); } static int cli_list_maps_status (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list maps status (operator)"); return show_maps(reply, vecs, PRINT_MAP_STATUS, 1); } static int cli_list_maps_stats (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list maps stats (operator)"); return show_maps(reply, vecs, PRINT_MAP_STATS, 1); } static int cli_list_daemon (void *v, struct strbuf *reply, void *data) { condlog(3, "list daemon (operator)"); return show_daemon(reply); } static int cli_reset_maps_stats (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; int i; struct multipath * mpp; condlog(3, "reset multipaths stats (operator)"); vector_foreach_slot(vecs->mpvec, mpp, i) { reset_stats(mpp); } return 0; } static int cli_reset_map_stats (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; struct multipath * mpp; char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; condlog(3, "reset multipath %s stats (operator)", param); reset_stats(mpp); return 0; } static int add_partial_path(struct path *pp, struct vectors *vecs) { char wwid[WWID_SIZE]; struct udev_device *udd; udd = get_udev_device(pp->dev_t, DEV_DEVT); if (!udd) return 0; strcpy(wwid, pp->wwid); if (get_uid(pp, pp->state, udd, 0) != 0) { strcpy(pp->wwid, wwid); udev_device_unref(udd); return 0; } if (strlen(wwid) && strncmp(wwid, pp->wwid, WWID_SIZE) != 0) { condlog(0, "%s: path wwid changed from '%s' to '%s'. removing", pp->dev, wwid, pp->wwid); if (!(ev_remove_path(pp, vecs, 1) & REMOVE_PATH_SUCCESS) && pp->mpp) { pp->dmstate = PSTATE_FAILED; dm_fail_path(pp->mpp->alias, pp->dev_t); } udev_device_unref(udd); return -1; } udev_device_unref(pp->udev); pp->udev = udd; return finish_path_init(pp, vecs); } static int cli_add_path (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path *pp; int r; struct config *conf; int invalid = 0; param = convert_dev(param, 1); condlog(2, "%s: add path (operator)", param); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); if (filter_devnode(conf->blist_devnode, conf->elist_devnode, param) > 0) invalid = 1; pthread_cleanup_pop(1); if (invalid) goto blacklisted; pp = find_path_by_str(vecs->pathvec, param, NULL); if (pp && pp->initialized != INIT_REMOVED) { condlog(2, "%s: path already in pathvec", param); if (pp->initialized == INIT_PARTIAL) { if (add_partial_path(pp, vecs) < 0) return 1; } else if (pp->recheck_wwid == RECHECK_WWID_ON && check_path_wwid_change(pp)) { condlog(0, "%s: wwid changed. Removing device", pp->dev); handle_path_wwid_change(pp, vecs); return 1; } if (pp->mpp) return 0; } else if (pp) { /* Trying to add a path in INIT_REMOVED state */ struct multipath *prev_mpp; prev_mpp = pp->mpp; if (prev_mpp == NULL) condlog(0, "Bug: %s was in INIT_REMOVED state without being a multipath member", pp->dev); pp->mpp = NULL; pp->initialized = INIT_NEW; pp->wwid[0] = '\0'; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST); pthread_cleanup_pop(1); if (prev_mpp) { /* Similar logic as in uev_add_path() */ pp->mpp = prev_mpp; if (r == PATHINFO_OK && !strncmp(prev_mpp->wwid, pp->wwid, WWID_SIZE)) { condlog(2, "%s: path re-added to %s", pp->dev, pp->mpp->alias); /* Have the checker reinstate this path asap */ pp->tick = 1; return 0; } else if (ev_remove_path(pp, vecs, true) & REMOVE_PATH_SUCCESS) /* Path removed in ev_remove_path() */ pp = NULL; else { /* Init state is now INIT_REMOVED again */ pp->dmstate = PSTATE_FAILED; dm_fail_path(pp->mpp->alias, pp->dev_t); condlog(1, "%s: failed to re-add path still mapped in %s", pp->dev, pp->mpp->alias); return 1; } } else { switch (r) { case PATHINFO_SKIPPED: goto blacklisted; case PATHINFO_OK: break; default: condlog(0, "%s: failed to get pathinfo", param); return 1; } } } if (!pp) { struct udev_device *udevice; udevice = udev_device_new_from_subsystem_sysname(udev, "block", param); if (!udevice) { condlog(0, "%s: can't find path", param); return 1; } conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = store_pathinfo(vecs->pathvec, conf, udevice, DI_ALL | DI_BLACKLIST, &pp); pthread_cleanup_pop(1); udev_device_unref(udevice); if (!pp) { if (r == 2) goto blacklisted; condlog(0, "%s: failed to store path info", param); return 1; } } return ev_add_path(pp, vecs, 1); blacklisted: append_strbuf_str(reply, "blacklisted\n"); condlog(2, "%s: path blacklisted", param); return 0; } static int cli_del_path (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path *pp; int ret; param = convert_dev(param, 1); condlog(2, "%s: remove path (operator)", param); pp = find_path_by_str(vecs->pathvec, param, NULL); if (!pp) { condlog(0, "%s: path already removed", param); return -ENODEV; } ret = ev_remove_path(pp, vecs, 1); if (ret == REMOVE_PATH_DELAY) append_strbuf_str(reply, "delayed\n"); else if (ret == REMOVE_PATH_MAP_ERROR) append_strbuf_str(reply, "map reload error. removed\n"); return (ret == REMOVE_PATH_FAILURE); } static int cli_add_map (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); char dev_path[FILE_NAME_SIZE]; char *refwwid __attribute__((cleanup(cleanup_charp))) = NULL; char alias[WWID_SIZE]; int rc; struct dm_info dmi; param = convert_dev(param, 0); condlog(2, "%s: add map (operator)", param); if (get_refwwid(CMD_NONE, param, DEV_DEVMAP, vecs->pathvec, &refwwid) == PATHINFO_SKIPPED) { append_strbuf_str(reply, "blacklisted\n"); condlog(2, "%s: map blacklisted", param); return 1; } else if (!refwwid) { condlog(2, "%s: unknown map.", param); return -ENODEV; } rc = dm_find_map_by_wwid(refwwid, alias, &dmi); if (rc == DMP_NO_MATCH) { condlog(2, "%s: wwid %s already in use by non-multipath device %s", param, refwwid, alias); return 1; } if (rc != DMP_OK) { condlog(3, "%s: map not present. creating", param); if (coalesce_paths(vecs, NULL, refwwid, FORCE_RELOAD_NONE, CMD_NONE) != CP_OK) { condlog(2, "%s: coalesce_paths failed", param); return 1; } if (dm_find_map_by_wwid(refwwid, alias, &dmi) != DMP_OK) { condlog(2, "%s: failed getting map", param); return 1; } } sprintf(dev_path, "dm-%u", dmi.minor); rc = ev_add_map(dev_path, alias, vecs); return rc; } static int cli_del_map (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); struct multipath *mpp; int r; param = convert_dev(param, 0); condlog(2, "%s: remove map (operator)", param); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; r = flush_map(mpp, vecs); if (r == DM_FLUSH_OK) return 0; else if (r == DM_FLUSH_BUSY) return -EBUSY; return 1; } static int cli_del_maps (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; struct multipath *mpp; int i, ret, r = 0; condlog(2, "remove maps (operator)"); vector_foreach_slot(vecs->mpvec, mpp, i) { ret = flush_map(mpp, vecs); if (ret == DM_FLUSH_OK) i--; else if (ret == DM_FLUSH_BUSY && r != 1) r = -EBUSY; else r = 1; } /* flush any multipath maps that aren't currently known by multipathd */ ret = dm_flush_maps(0); if (ret == DM_FLUSH_BUSY && r != 1) r = -EBUSY; else if (ret != DM_FLUSH_OK) r = 1; return r; } static int cli_reload(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; mapname = convert_dev(mapname, 0); condlog(2, "%s: reload map (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing reload", mpp->alias); return 1; } return reload_and_sync_map(mpp, vecs); } static int cli_resize(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; unsigned long long size = 0; struct pathgroup *pgp; struct path *pp; unsigned int i, j, ret; bool mismatch = false; mapname = convert_dev(mapname, 0); condlog(2, "%s: resize map (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing resize", mpp->alias); return 1; } vector_foreach_slot(mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { sysfs_get_size(pp, &pp->size); if (!pp->size) continue; if (!size) size = pp->size; else if (pp->size != size) mismatch = true; } } if (!size) { condlog(0, "%s: couldn't get size from sysfs. cannot resize", mapname); return 1; } if (mismatch) { condlog(0, "%s: path size not consistent. cannot resize", mapname); return 1; } if (size == mpp->size) { condlog(0, "%s: map is still the same size (%llu)", mapname, mpp->size); return 0; } condlog(3, "%s old size is %llu, new size is %llu", mapname, mpp->size, size); ret = resize_map(mpp, size, vecs); if (ret == 2) condlog(0, "%s: map removed while trying to resize", mapname); return (ret != 0); } static int cli_force_no_daemon_q(void * v, struct strbuf *reply, void * data) { struct config *conf; condlog(2, "force queue_without_daemon (operator)"); conf = get_multipath_config(); if (conf->queue_without_daemon == QUE_NO_DAEMON_OFF) conf->queue_without_daemon = QUE_NO_DAEMON_FORCE; put_multipath_config(conf); return 0; } static int cli_restore_no_daemon_q(void * v, struct strbuf *reply, void * data) { struct config *conf; condlog(2, "restore queue_without_daemon (operator)"); conf = get_multipath_config(); if (conf->queue_without_daemon == QUE_NO_DAEMON_FORCE) conf->queue_without_daemon = QUE_NO_DAEMON_OFF; put_multipath_config(conf); return 0; } static int cli_restore_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; struct config *conf; mapname = convert_dev(mapname, 0); condlog(2, "%s: restore map queueing (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (mpp->disable_queueing) { mpp->disable_queueing = 0; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); select_no_path_retry(conf, mpp); pthread_cleanup_pop(1); set_no_path_retry(mpp); } else condlog(2, "%s: queueing not disabled. Nothing to do", mapname); return 0; } static int cli_restore_all_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; struct multipath *mpp; int i; condlog(2, "restore queueing (operator)"); vector_foreach_slot(vecs->mpvec, mpp, i) { if (mpp->disable_queueing) { mpp->disable_queueing = 0; struct config *conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); select_no_path_retry(conf, mpp); pthread_cleanup_pop(1); set_no_path_retry(mpp); } else condlog(2, "%s: queueing not disabled. Nothing to do", mpp->alias); } return 0; } static int cli_disable_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; mapname = convert_dev(mapname, 0); condlog(2, "%s: disable map queueing (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (count_active_paths(mpp) == 0) mpp->stat_map_failures++; mpp->retry_tick = 0; mpp->no_path_retry = NO_PATH_RETRY_FAIL; mpp->disable_queueing = 1; set_no_path_retry(mpp); return 0; } static int cli_disable_all_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; struct multipath *mpp; int i; condlog(2, "disable queueing (operator)"); vector_foreach_slot(vecs->mpvec, mpp, i) { if (count_active_paths(mpp) == 0) mpp->stat_map_failures++; mpp->retry_tick = 0; mpp->no_path_retry = NO_PATH_RETRY_FAIL; mpp->disable_queueing = 1; set_no_path_retry(mpp); } return 0; } static int cli_switch_group(void * v, struct strbuf *reply, void * data) { char * mapname = get_keyparam(v, KEY_MAP); int groupnum = atoi(get_keyparam(v, KEY_GROUP)); mapname = convert_dev(mapname, 0); condlog(2, "%s: switch to path group #%i (operator)", mapname, groupnum); return dm_switchgroup(mapname, groupnum); } static int cli_reconfigure(void * v, struct strbuf *reply, void * data) { condlog(2, "reconfigure (operator)"); schedule_reconfigure(FORCE_RELOAD_WEAK); return 0; } int cli_reconfigure_all(void * v, struct strbuf *reply, void * data) { condlog(2, "reconfigure all (operator)"); schedule_reconfigure(FORCE_RELOAD_YES); return 0; } static int cli_suspend(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); int r; struct multipath * mpp; param = convert_dev(param, 0); mpp = find_mp_by_alias(vecs->mpvec, param); if (!mpp) return 1; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing suspend", mpp->alias); return 1; } r = dm_simplecmd_noflush(DM_DEVICE_SUSPEND, param, 0); condlog(2, "%s: suspend (operator)", param); if (!r) /* error */ return 1; dm_get_info(param, &mpp->dmi); return 0; } static int cli_resume(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); int r; struct multipath * mpp; uint16_t udev_flags; param = convert_dev(param, 0); mpp = find_mp_by_alias(vecs->mpvec, param); if (!mpp) return 1; udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing resume", mpp->alias); return 1; } r = dm_simplecmd_noflush(DM_DEVICE_RESUME, param, udev_flags); condlog(2, "%s: resume (operator)", param); if (!r) /* error */ return 1; dm_get_info(param, &mpp->dmi); return 0; } static int cli_reinstate(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); pp = find_path_by_str(vecs->pathvec, param, "reinstate path"); if (!pp) return -ENODEV; if (!pp->mpp || !pp->mpp->alias) return 1; condlog(2, "%s: reinstate path %s (operator)", pp->mpp->alias, pp->dev_t); checker_enable(&pp->checker); pp->tick = 1; return dm_reinstate_path(pp->mpp->alias, pp->dev_t); } static int cli_reassign (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); struct multipath *mpp; param = convert_dev(param, 0); mpp = find_mp_by_alias(vecs->mpvec, param); if (!mpp) return 1; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing reassign", mpp->alias); return 1; } condlog(3, "%s: reset devices (operator)", param); dm_reassign(param); return 0; } static int cli_fail(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path * pp; int r; param = convert_dev(param, 1); pp = find_path_by_str(vecs->pathvec, param, "fail path"); if (!pp) return -ENODEV; if (!pp->mpp || !pp->mpp->alias) return 1; condlog(2, "%s: fail path %s (operator)", pp->mpp->alias, pp->dev_t); r = dm_fail_path(pp->mpp->alias, pp->dev_t); /* * Suspend path checking to avoid auto-reinstating the path */ if (!r) checker_disable(&pp->checker); return r; } static int show_blacklist (struct strbuf *reply) { struct config *conf; bool fail; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); fail = snprint_blacklist_report(conf, reply) < 0; pthread_cleanup_pop(1); if (fail) return 1; return 0; } static int cli_list_blacklist (void * v, struct strbuf *reply, void * data) { condlog(3, "list blacklist (operator)"); return show_blacklist(reply); } static int show_devices (struct strbuf *reply, struct vectors *vecs) { struct config *conf; bool fail; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); fail = snprint_devices(conf, reply, vecs) < 0; pthread_cleanup_pop(1); if (fail) return 1; return 0; } static int cli_list_devices (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; condlog(3, "list devices (operator)"); return show_devices(reply, vecs); } static int cli_quit (void * v, struct strbuf *reply, void * data) { return 0; } static int cli_shutdown (void * v, struct strbuf *reply, void * data) { condlog(3, "shutdown (operator)"); exit_daemon(); return 0; } static int cli_getprstatus (void * v, struct strbuf *reply, void * data) { static const char * const prflag_str[] = { [PRFLAG_UNKNOWN] = "unknown\n", [PRFLAG_UNSET] = "unset\n", [PRFLAG_SET] = "set\n", }; struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; append_strbuf_str(reply, prflag_str[mpp->prflag]); condlog(3, "%s: reply = %s", param, get_strbuf_str(reply)); return 0; } static int cli_setprstatus(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; if (mpp->prflag != PRFLAG_SET) { mpp->prflag = PRFLAG_SET; condlog(2, "%s: prflag set", param); } return 0; } static int cli_unsetprstatus(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); if (!mpp) return -ENODEV; if (mpp->prflag != PRFLAG_UNSET) { mpp->prflag = PRFLAG_UNSET; condlog(2, "%s: prflag unset", param); } return 0; } static int cli_getprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char *mapname = get_keyparam(v, KEY_MAP); uint64_t key; mapname = convert_dev(mapname, 0); condlog(3, "%s: get persistent reservation key (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; key = get_be64(mpp->reservation_key); if (!key) { append_strbuf_str(reply, "none\n"); return 0; } if (print_strbuf(reply, "0x%" PRIx64 "%s\n", key, mpp->sa_flags & MPATH_F_APTPL_MASK ? ":aptpl" : "") < 0) return 1; return 0; } static int cli_unsetprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char *mapname = get_keyparam(v, KEY_MAP); int ret; struct config *conf; mapname = convert_dev(mapname, 0); condlog(3, "%s: unset persistent reservation key (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = set_prkey(conf, mpp, 0, 0); pthread_cleanup_pop(1); return ret; } static int cli_setprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; char *mapname = get_keyparam(v, KEY_MAP); char *keyparam = get_keyparam(v, KEY_KEY); uint64_t prkey; uint8_t flags; int ret; struct config *conf; mapname = convert_dev(mapname, 0); condlog(3, "%s: set persistent reservation key (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (parse_prkey_flags(keyparam, &prkey, &flags) != 0) { condlog(0, "%s: invalid prkey : '%s'", mapname, keyparam); return 1; } conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = set_prkey(conf, mpp, prkey, flags); pthread_cleanup_pop(1); return ret; } static int cli_set_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); pp = find_path_by_str(vecs->pathvec, param, "set marginal path"); if (!pp) return -ENODEV; if (!pp->mpp || !pp->mpp->alias) return 1; condlog(2, "%s: set marginal path %s (operator)", pp->mpp->alias, pp->dev_t); if (pp->mpp->wait_for_udev) { condlog(2, "%s: device not fully created, failing set marginal", pp->mpp->alias); return 1; } pp->marginal = 1; return reload_and_sync_map(pp->mpp, vecs); } static int cli_unset_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); pp = find_path_by_str(vecs->pathvec, param, "unset marginal path"); if (!pp) return -ENODEV; if (!pp->mpp || !pp->mpp->alias) return 1; condlog(2, "%s: unset marginal path %s (operator)", pp->mpp->alias, pp->dev_t); if (pp->mpp->wait_for_udev) { condlog(2, "%s: device not fully created, " "failing unset marginal", pp->mpp->alias); return 1; } pp->marginal = 0; return reload_and_sync_map(pp->mpp, vecs); } static int cli_unset_all_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; struct pathgroup * pgp; struct path * pp; unsigned int i, j; mapname = convert_dev(mapname, 0); condlog(2, "%s: unset all marginal paths (operator)", mapname); mpp = find_mp_by_str(vecs->mpvec, mapname); if (!mpp) return -ENODEV; if (mpp->wait_for_udev) { condlog(2, "%s: device not fully created, " "failing unset all marginal", mpp->alias); return 1; } vector_foreach_slot (mpp->pg, pgp, i) vector_foreach_slot (pgp->paths, pp, j) pp->marginal = 0; return reload_and_sync_map(mpp, vecs); } #define HANDLER(x) x #include "callbacks.c" multipath-tools-0.11.1/multipathd/cli_handlers.h000066400000000000000000000001541475246302400217210ustar00rootroot00000000000000#ifndef CLI_HANDLERS_H_INCLUDED #define CLI_HANDLERS_H_INCLUDED void init_handler_callbacks(void); #endif multipath-tools-0.11.1/multipathd/dmevents.c000066400000000000000000000222251475246302400211150ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Edward Goggin, EMC * Copyright (c) 2005, 2018 Benjamin Marzinski, Redhat */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "devmapper.h" #include "debug.h" #include "main.h" #include "dmevents.h" #include "util.h" #ifndef DM_DEV_ARM_POLL #define DM_DEV_ARM_POLL _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD + 1, struct dm_ioctl) #endif enum event_actions { EVENT_NOTHING, EVENT_REMOVE, EVENT_UPDATE, }; struct dev_event { char name[WWID_SIZE]; uint32_t evt_nr; enum event_actions action; }; struct dmevent_waiter { int fd; struct vectors *vecs; vector events; pthread_mutex_t events_lock; }; static struct dmevent_waiter *waiter; /* * DM_VERSION_MINOR hasn't been updated when DM_DEV_ARM_POLL * was added in kernel 4.13. 4.37.0 (4.14) has it, safely. */ static const unsigned int DM_VERSION_FOR_ARM_POLL[] = {4, 37, 0}; int dmevent_poll_supported(void) { unsigned int v[3]; if (libmp_get_version(DM_KERNEL_VERSION, v)) return 0; if (VERSION_GE(v, DM_VERSION_FOR_ARM_POLL)) return 1; return 0; } int init_dmevent_waiter(struct vectors *vecs) { if (!vecs) { condlog(0, "can't create waiter structure. invalid vectors"); goto fail; } waiter = (struct dmevent_waiter *)malloc(sizeof(struct dmevent_waiter)); if (!waiter) { condlog(0, "failed to allocate waiter structure"); goto fail; } memset(waiter, 0, sizeof(struct dmevent_waiter)); waiter->events = vector_alloc(); if (!waiter->events) { condlog(0, "failed to allocate waiter events vector"); goto fail_waiter; } waiter->fd = open("/dev/mapper/control", O_RDWR); if (waiter->fd < 0) { condlog(0, "failed to open /dev/mapper/control for waiter"); goto fail_events; } pthread_mutex_init(&waiter->events_lock, NULL); waiter->vecs = vecs; return 0; fail_events: vector_free(waiter->events); fail_waiter: free(waiter); fail: waiter = NULL; return -1; } void cleanup_dmevent_waiter(void) { struct dev_event *dev_evt; int i; if (!waiter) return; pthread_mutex_destroy(&waiter->events_lock); close(waiter->fd); vector_foreach_slot(waiter->events, dev_evt, i) free(dev_evt); vector_free(waiter->events); free(waiter); waiter = NULL; } static int arm_dm_event_poll(int fd) { struct dm_ioctl dmi; memset(&dmi, 0, sizeof(dmi)); dmi.version[0] = DM_VERSION_FOR_ARM_POLL[0]; dmi.version[1] = DM_VERSION_FOR_ARM_POLL[1]; dmi.version[2] = DM_VERSION_FOR_ARM_POLL[2]; /* This flag currently does nothing. It simply exists to * duplicate the behavior of libdevmapper */ dmi.flags = 0x4; dmi.data_start = offsetof(struct dm_ioctl, data); dmi.data_size = sizeof(dmi); return ioctl(fd, DM_DEV_ARM_POLL, &dmi); } /* * As of version 4.37.0 device-mapper stores the event number in the * dm_names structure after the name, when DM_DEVICE_LIST is called */ static uint32_t dm_event_nr(struct dm_names *n) { return *(uint32_t *)(((uintptr_t)(strchr(n->name, 0) + 1) + 7) & ~7); } static int dm_get_events(void) { struct dm_task *dmt; struct dm_names *names; struct dev_event *dev_evt; int i; if (!(dmt = libmp_dm_task_create(DM_DEVICE_LIST))) return -1; if (!libmp_dm_task_run(dmt)) { dm_log_error(3, DM_DEVICE_LIST, dmt); goto fail; } if (!(names = dm_task_get_names(dmt))) goto fail; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) dev_evt->action = EVENT_REMOVE; while (names->dev) { uint32_t event_nr; /* * Don't delete device if dm_is_mpath() fails without * checking the device type. * IOW, only delete devices from the event list for which * we positively know that they aren't multipath devices. */ if (dm_is_mpath(names->name) == DM_IS_MPATH_NO) goto next; event_nr = dm_event_nr(names); vector_foreach_slot(waiter->events, dev_evt, i) { if (!strcmp(dev_evt->name, names->name)) { if (event_nr != dev_evt->evt_nr) { dev_evt->evt_nr = event_nr; dev_evt->action = EVENT_UPDATE; } else dev_evt->action = EVENT_NOTHING; break; } } next: if (!names->next) break; names = (void *)names + names->next; } pthread_mutex_unlock(&waiter->events_lock); dm_task_destroy(dmt); return 0; fail: dm_task_destroy(dmt); return -1; } /* You must call setup_multipath() after calling this function, to * deal with any events that came in before the device was added */ int watch_dmevents(char *name) { int event_nr; struct dev_event *dev_evt, *old_dev_evt; int i; /* * We know that this is a multipath device, so only fail if * device-mapper tells us that we're wrong * IOW, don't fail for DM generic errors. */ if (dm_is_mpath(name) == DM_IS_MPATH_NO) { condlog(0, "%s: not a multipath device. can't watch events", name); return -1; } if ((event_nr = dm_geteventnr(name)) < 0) return -1; dev_evt = (struct dev_event *)malloc(sizeof(struct dev_event)); if (!dev_evt) { condlog(0, "%s: can't allocate event waiter structure", name); return -1; } strlcpy(dev_evt->name, name, WWID_SIZE); dev_evt->evt_nr = event_nr; dev_evt->action = EVENT_NOTHING; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, old_dev_evt, i){ if (!strcmp(dev_evt->name, old_dev_evt->name)) { /* caller will be updating this device */ old_dev_evt->evt_nr = event_nr; old_dev_evt->action = EVENT_NOTHING; pthread_mutex_unlock(&waiter->events_lock); condlog(2, "%s: already waiting for events on device", name); free(dev_evt); return 0; } } if (!vector_alloc_slot(waiter->events)) { pthread_mutex_unlock(&waiter->events_lock); free(dev_evt); return -1; } vector_set_slot(waiter->events, dev_evt); pthread_mutex_unlock(&waiter->events_lock); return 0; } void unwatch_all_dmevents(void) { struct dev_event *dev_evt; int i; if (!waiter) return; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) free(dev_evt); vector_reset(waiter->events); pthread_mutex_unlock(&waiter->events_lock); } static void unwatch_dmevents(char *name) { struct dev_event *dev_evt; int i; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) { if (!strcmp(dev_evt->name, name)) { vector_del_slot(waiter->events, i); free(dev_evt); break; } } pthread_mutex_unlock(&waiter->events_lock); } /* * returns the reschedule delay * negative means *stop* */ /* poll, arm, update, return */ static int dmevent_loop (void) { int r, i = 0; struct pollfd pfd; struct dev_event *dev_evt; pfd.fd = waiter->fd; pfd.events = POLLIN; r = poll(&pfd, 1, -1); if (r <= 0) { condlog(0, "failed polling for dm events: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } if (arm_dm_event_poll(waiter->fd) != 0) { condlog(0, "Cannot re-arm event polling: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } if (dm_get_events() != 0) { condlog(0, "failed getting dm events: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } /* * upon event ... */ while (1) { int done = 1; struct dev_event curr_dev; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) { if (dev_evt->action != EVENT_NOTHING) { curr_dev = *dev_evt; if (dev_evt->action == EVENT_REMOVE) { vector_del_slot(waiter->events, i); free(dev_evt); } else dev_evt->action = EVENT_NOTHING; done = 0; break; } } pthread_mutex_unlock(&waiter->events_lock); if (done) return 1; condlog(3, "%s: devmap event #%i", curr_dev.name, curr_dev.evt_nr); /* * event might be : * * 1) a table reload, which means our mpp structure is * obsolete : refresh it through update_multipath() * 2) a path failed by DM : mark as such through * update_multipath() * 3) map has gone away : stop the thread. * 4) a path reinstate : nothing to do * 5) a switch group : nothing to do */ pthread_cleanup_push(cleanup_lock, &waiter->vecs->lock); lock(&waiter->vecs->lock); pthread_testcancel(); r = 0; if (curr_dev.action == EVENT_REMOVE) remove_map_by_alias(curr_dev.name, waiter->vecs); else r = update_multipath(waiter->vecs, curr_dev.name); pthread_cleanup_pop(1); if (r) { condlog(2, "%s: stopped watching dmevents", curr_dev.name); unwatch_dmevents(curr_dev.name); } } condlog(0, "dmevent waiter thread unexpectedly quit"); return -1; /* never reach there */ } static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } void *wait_dmevents (__attribute__((unused)) void *unused) { int r; if (!waiter) { condlog(0, "dmevents waiter not initialized"); return NULL; } pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); mlockall(MCL_CURRENT | MCL_FUTURE); while (1) { r = dmevent_loop(); if (r < 0) break; sleep(r); } pthread_cleanup_pop(1); return NULL; } multipath-tools-0.11.1/multipathd/dmevents.h000066400000000000000000000005161475246302400211210ustar00rootroot00000000000000#ifndef DMEVENTS_H_INCLUDED #define DMEVENTS_H_INCLUDED #include "structs_vec.h" int dmevent_poll_supported(void); int init_dmevent_waiter(struct vectors *vecs); void cleanup_dmevent_waiter(void); int watch_dmevents(char *name); void unwatch_all_dmevents(void); void *wait_dmevents (void *unused); #endif /* DMEVENTS_H_INCLUDED */ multipath-tools-0.11.1/multipathd/fpin.h000066400000000000000000000010171475246302400202250ustar00rootroot00000000000000#ifndef FPIN_H_INCLUDED #define FPIN_H_INCLUDED #include "autoconfig.h" #ifdef FPIN_EVENT_HANDLER void *fpin_fabric_notification_receiver(void *unused); void *fpin_els_li_consumer(void *data); void fpin_clean_marginal_dev_list(__attribute__((unused)) void *arg); #else static void *fpin_fabric_notification_receiver(__attribute__((unused))void *unused) { return NULL; } static void *fpin_els_li_consumer(__attribute__((unused))void *data) { return NULL; } /* fpin_clean_marginal_dev_list() is never called */ #endif #endif multipath-tools-0.11.1/multipathd/fpin_handlers.c000066400000000000000000000451221475246302400221050ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "parser.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "main.h" #include "debug.h" #include "util.h" #include "sysfs.h" #include "fpin.h" #include "devmapper.h" static pthread_cond_t fpin_li_cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t fpin_li_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t fpin_li_marginal_dev_mutex = PTHREAD_MUTEX_INITIALIZER; static LIST_HEAD(els_marginal_list_head); static LIST_HEAD(fpin_li_marginal_dev_list_head); #define DEF_RX_BUF_SIZE 4096 #define DEV_NAME_LEN 128 #define FCH_EVT_LINKUP 0x2 #define FCH_EVT_LINK_FPIN 0x501 #define FCH_EVT_RSCN 0x5 #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) /* max ELS frame Size */ #define FC_PAYLOAD_MAXLEN 2048 struct els_marginal_list { uint32_t event_code; uint16_t host_num; uint16_t length; char payload[FC_PAYLOAD_MAXLEN]; struct list_head node; }; /* Structure to store the marginal devices info */ struct marginal_dev_list { char dev_t[BLK_DEV_SIZE]; uint32_t host_num; struct list_head node; }; static void _udev_device_unref(void *p) { udev_device_unref(p); } /*set/unset the path state to marginal*/ static void fpin_set_pathstate(struct path *pp, bool set) { const char *action = set ? "set" : "unset"; condlog(3, "%s: %s marginal path %s (fpin)", pp->mpp ? pp->mpp->alias : "orphan", action, pp->dev_t); pp->marginal = set; if (pp->mpp) pp->mpp->fpin_must_reload = true; } /* This will unset marginal state of a device*/ static void fpin_path_unsetmarginal(char *devname, struct vectors *vecs) { struct path *pp; pp = find_path_by_dev(vecs->pathvec, devname); if (!pp) pp = find_path_by_devt(vecs->pathvec, devname); if (pp) fpin_set_pathstate(pp, false); } /*This will set the marginal state of a device*/ static void fpin_path_setmarginal(struct path *pp) { fpin_set_pathstate(pp, true); } /* Unsets all the devices in the list from marginal state */ static void fpin_unset_marginal_dev(uint32_t host_num, struct vectors *vecs) { struct marginal_dev_list *tmp_marg = NULL; struct marginal_dev_list *marg = NULL; struct multipath *mpp; int ret = 0; int i; pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); pthread_mutex_lock(&fpin_li_marginal_dev_mutex); pthread_cleanup_push(cleanup_mutex, &fpin_li_marginal_dev_mutex); pthread_testcancel(); if (list_empty(&fpin_li_marginal_dev_list_head)) { condlog(4, "Marginal List is empty\n"); goto empty; } list_for_each_entry_safe(marg, tmp_marg, &fpin_li_marginal_dev_list_head, node) { if (marg->host_num != host_num) continue; condlog(4, " unsetting marginal dev: is %s %d\n", tmp_marg->dev_t, tmp_marg->host_num); fpin_path_unsetmarginal(marg->dev_t, vecs); list_del(&marg->node); free(marg); } empty: pthread_cleanup_pop(1); /* walk backwards because reload_and_sync_map() can remove mpp */ vector_foreach_slot_backwards(vecs->mpvec, mpp, i) { if (mpp->fpin_must_reload) { ret = reload_and_sync_map(mpp, vecs); if (ret == 2) condlog(2, "map removed during reload"); else mpp->fpin_must_reload = false; } } pthread_cleanup_pop(1); } /* * On Receiving the frame from HBA driver, insert the frame into link * integrity frame list which will be picked up later by consumer thread for * processing. */ static int fpin_els_add_li_frame(struct fc_nl_event *fc_event) { struct els_marginal_list *els_mrg = NULL; int ret = 0; if (fc_event->event_datalen > FC_PAYLOAD_MAXLEN) return -EINVAL; pthread_mutex_lock(&fpin_li_mutex); pthread_cleanup_push(cleanup_mutex, &fpin_li_mutex); pthread_testcancel(); els_mrg = calloc(1, sizeof(struct els_marginal_list)); if (els_mrg != NULL) { els_mrg->host_num = fc_event->host_no; els_mrg->event_code = fc_event->event_code; els_mrg->length = fc_event->event_datalen; memcpy(els_mrg->payload, &(fc_event->event_data), fc_event->event_datalen); list_add_tail(&els_mrg->node, &els_marginal_list_head); pthread_cond_signal(&fpin_li_cond); } else ret = -ENOMEM; pthread_cleanup_pop(1); return ret; } /*Sets the rport port_state to marginal*/ static void fpin_set_rport_marginal(struct udev_device *rport_dev) { char old_value[20]; /* match kernel show_fc_rport_port_state() size */ static const char marginal[] = "Marginal"; ssize_t ret; ret = sysfs_attr_get_value(rport_dev, "port_state", old_value, sizeof(old_value)); if (ret == sizeof(marginal) - 1 && strcmp(old_value, marginal) == 0) return; ret = sysfs_attr_set_value(rport_dev, "port_state", marginal, sizeof(marginal) - 1); if (ret != sizeof(marginal) - 1) log_sysfs_attr_set_value(2, ret, "%s: failed to set port_state to marginal", udev_device_get_syspath(rport_dev)); } /*Add the marginal devices info into the list and return 0 on success*/ static int fpin_add_marginal_dev_info(uint32_t host_num, char *devname) { struct marginal_dev_list *newdev = NULL; newdev = calloc(1, sizeof(struct marginal_dev_list)); if (newdev != NULL) { newdev->host_num = host_num; strlcpy(newdev->dev_t, devname, BLK_DEV_SIZE); condlog(4, "\n%s hostno %d devname %s\n", __func__, host_num, newdev->dev_t); pthread_mutex_lock(&fpin_li_marginal_dev_mutex); list_add_tail(&(newdev->node), &fpin_li_marginal_dev_list_head); pthread_mutex_unlock(&fpin_li_marginal_dev_mutex); } else return -ENOMEM; return 0; } /* * This function compares Transport Address Controller Port pn, * Host Transport Address Controller Port pn with the els wwpn ,attached_wwpn * and return 1 (match) or 0 (no match) or a negative error code */ static int extract_nvme_addresses_chk_path_pwwn(const char *address, uint64_t els_wwpn, uint64_t els_attached_wwpn) { uint64_t traddr; uint64_t host_traddr; /* * Find the position of "traddr=" and "host_traddr=" * and the address will be in the below format * "traddr=nn-0x200400110dff9400:pn-0x200400110dff9400, * host_traddr=nn-0x200400110dff9400:pn-0x200400110dff9400" */ const char *traddr_start = strstr(address, "traddr="); const char *host_traddr_start = strstr(address, "host_traddr="); if (!traddr_start || !host_traddr_start) return -EINVAL; /* Extract traddr pn */ if (sscanf(traddr_start, "traddr=nn-%*[^:]:pn-%" SCNx64, &traddr) != 1) return -EINVAL; /* Extract host_traddr pn*/ if (sscanf(host_traddr_start, "host_traddr=nn-%*[^:]:pn-%" SCNx64, &host_traddr) != 1) return -EINVAL; condlog(4, "traddr 0x%" PRIx64 " hosttraddr 0x%" PRIx64 " els_wwpn 0x%" PRIx64" els_host_traddr 0x%" PRIx64, traddr, host_traddr, els_wwpn, els_attached_wwpn); if ((host_traddr == els_attached_wwpn) && (traddr == els_wwpn)) return 1; return 0; } /* * This function check that the Transport Address Controller Port pn, * Host Transport Address Controller Port pn associated with the path matches * with the els wwpn ,attached_wwpn and sets the path state to * Marginal */ static void fpin_check_set_nvme_path_marginal(uint16_t host_num, struct path *pp, uint64_t els_wwpn, uint64_t attached_wwpn) { struct udev_device *ctl = NULL; const char *address = NULL; int ret = 0; ctl = udev_device_get_parent_with_subsystem_devtype(pp->udev, "nvme", NULL); if (ctl == NULL) { condlog(2, "%s: No parent device for ", pp->dev); return; } address = udev_device_get_sysattr_value(ctl, "address"); if (!address) { condlog(2, "%s: unable to get the address ", pp->dev); return; } condlog(4, "\n address %s: dev :%s\n", address, pp->dev); ret = extract_nvme_addresses_chk_path_pwwn(address, els_wwpn, attached_wwpn); if (ret <= 0) return; ret = fpin_add_marginal_dev_info(host_num, pp->dev); if (ret < 0) return; fpin_path_setmarginal(pp); } /* * This function check the host number, the target WWPN * associated with the path matches with the els wwpn and * sets the path and port state to Marginal */ static void fpin_check_set_scsi_path_marginal(uint16_t host_num, struct path *pp, uint64_t els_wwpn) { char rport_id[42]; const char *value = NULL; struct udev_device *rport_dev = NULL; uint64_t wwpn; int ret = 0; sprintf(rport_id, "rport-%d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); rport_dev = udev_device_new_from_subsystem_sysname(udev, "fc_remote_ports", rport_id); if (!rport_dev) { condlog(2, "%s: No fc_remote_port device for '%s'", pp->dev, rport_id); return; } pthread_cleanup_push(_udev_device_unref, rport_dev); value = udev_device_get_sysattr_value(rport_dev, "port_name"); if (!value) goto unref; wwpn = strtol(value, NULL, 16); /* * If the port wwpn matches sets the path and port state * to marginal */ if (wwpn == els_wwpn) { ret = fpin_add_marginal_dev_info(host_num, pp->dev); if (ret < 0) goto unref; fpin_path_setmarginal(pp); fpin_set_rport_marginal(rport_dev); } unref: pthread_cleanup_pop(1); return; } /* * This function goes through the vecs->pathvec, and for * each path, it checks and sets the path state to marginal * if the path's associated port wwpn ,hostnum matches with * els wwnpn ,attached_wwpn */ static int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *vecs, uint64_t els_wwpn, uint64_t attached_wwpn) { struct path *pp; struct multipath *mpp; int i, k; int ret = 0; pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); vector_foreach_slot(vecs->pathvec, pp, k) { if (!pp->mpp) continue; /*checks if the bus type is nvme and the protocol is FC-NVMe*/ if ((pp->bus == SYSFS_BUS_NVME) && (pp->sg_id.proto_id == NVME_PROTOCOL_FC)) { fpin_check_set_nvme_path_marginal(host_num, pp, els_wwpn, attached_wwpn); } else if ((pp->bus == SYSFS_BUS_SCSI) && (pp->sg_id.proto_id == SCSI_PROTOCOL_FCP) && (host_num == pp->sg_id.host_no)) { /* Checks the host number and also for the SCSI FCP */ fpin_check_set_scsi_path_marginal(host_num, pp, els_wwpn); } } /* walk backwards because reload_and_sync_map() can remove mpp */ vector_foreach_slot_backwards(vecs->mpvec, mpp, i) { if (mpp->fpin_must_reload) { ret = reload_and_sync_map(mpp, vecs); if (ret == 2) condlog(2, "map removed during reload"); else mpp->fpin_must_reload = false; } } pthread_cleanup_pop(1); return ret; } /* * This function loops around all the impacted wwns received as part of els * frame and sets the associated path and port states to marginal. */ static int fpin_parse_li_els_setpath_marginal(uint16_t host_num, struct fc_tlv_desc *tlv, struct vectors *vecs) { uint32_t wwn_count = 0, iter = 0; uint64_t wwpn; struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv; int count = 0; int ret = 0; uint64_t attached_wwpn; /* Update the wwn to list */ wwn_count = be32_to_cpu(li_desc->pname_count); attached_wwpn = be64_to_cpu(li_desc->attached_wwpn); condlog(4, "Got wwn count as %d detecting wwn 0x%" PRIx64 " attached_wwpn 0x%" PRIx64 "\n", wwn_count, be64_to_cpu(li_desc->detecting_wwpn), attached_wwpn); for (iter = 0; iter < wwn_count; iter++) { wwpn = be64_to_cpu(li_desc->pname_list[iter]); ret = fpin_chk_wwn_setpath_marginal(host_num, vecs, wwpn, attached_wwpn); if (ret < 0) condlog(2, "failed to set the path marginal associated with wwpn: 0x%" PRIx64 "\n", wwpn); count++; } return count; } /* * This function process the ELS frame received from HBA driver, * and sets the path associated with the port wwn to marginal * and also set the port state to marginal. */ static int fpin_process_els_frame(uint16_t host_num, char *fc_payload, struct vectors *vecs) { int count = -1; struct fc_els_fpin *fpin = (struct fc_els_fpin *)fc_payload; struct fc_tlv_desc *tlv; tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; /* * Parse the els frame and set the affected paths and port * state to marginal */ count = fpin_parse_li_els_setpath_marginal(host_num, tlv, vecs); if (count <= 0) condlog(4, "Could not find any WWNs, ret = %d\n", count); return count; } /* * This function process the FPIN ELS frame received from HBA driver, * and push the frame to appropriate frame list. Currently we have only FPIN * LI frame list. */ static int fpin_handle_els_frame(struct fc_nl_event *fc_event) { int ret = -1; uint32_t els_cmd; struct fc_els_fpin *fpin = (struct fc_els_fpin *)&fc_event->event_data; struct fc_tlv_desc *tlv; uint32_t dtag; els_cmd = (uint32_t)fc_event->event_data; tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; dtag = be32_to_cpu(tlv->desc_tag); condlog(4, "Got CMD in add as 0x%x fpin_cmd 0x%x dtag 0x%x\n", els_cmd, fpin->fpin_cmd, dtag); if ((fc_event->event_code == FCH_EVT_LINK_FPIN) || (fc_event->event_code == FCH_EVT_LINKUP) || (fc_event->event_code == FCH_EVT_RSCN)) { if (els_cmd == ELS_FPIN) { /* * Check the type of fpin by checking the tag info * At present we are supporting only LI events */ if (dtag == ELS_DTAG_LNK_INTEGRITY) { /*Push the Payload to FPIN frame queue. */ ret = fpin_els_add_li_frame(fc_event); if (ret != 0) condlog(0, "Failed to process LI frame with error %d\n", ret); } else { condlog(4, "Unsupported FPIN received 0x%x\n", dtag); return ret; } } else { /*Push the Payload to FPIN frame queue. */ ret = fpin_els_add_li_frame(fc_event); if (ret != 0) condlog(0, "Failed to process Linkup/RSCN event with error %d evnt %d\n", ret, fc_event->event_code); } } else condlog(4, "Invalid command received: 0x%x\n", els_cmd); return ret; } /*cleans the global marginal dev list*/ void fpin_clean_marginal_dev_list(__attribute__((unused)) void *arg) { struct marginal_dev_list *tmp_marg = NULL; pthread_mutex_lock(&fpin_li_marginal_dev_mutex); while (!list_empty(&fpin_li_marginal_dev_list_head)) { tmp_marg = list_first_entry(&fpin_li_marginal_dev_list_head, struct marginal_dev_list, node); list_del(&tmp_marg->node); free(tmp_marg); } pthread_mutex_unlock(&fpin_li_marginal_dev_mutex); } /* Cleans the global els marginal list */ static void fpin_clean_els_marginal_list(void *arg) { struct list_head *head = (struct list_head *)arg; struct els_marginal_list *els_marg; while (!list_empty(head)) { els_marg = list_first_entry(head, struct els_marginal_list, node); list_del(&els_marg->node); free(els_marg); } } static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } /* * This is the FPIN ELS consumer thread. The thread sleeps on pthread cond * variable unless notified by fpin_fabric_notification_receiver thread. * This thread is only to process FPIN-LI ELS frames. A new thread and frame * list will be added if any more ELS frames types are to be supported. */ void *fpin_els_li_consumer(void *data) { struct list_head marginal_list_head; int ret = 0; uint16_t host_num; struct els_marginal_list *els_marg; uint32_t event_code; struct vectors *vecs = (struct vectors *)data; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); pthread_cleanup_push(fpin_clean_marginal_dev_list, NULL); INIT_LIST_HEAD(&marginal_list_head); pthread_cleanup_push(fpin_clean_els_marginal_list, (void *)&marginal_list_head); for ( ; ; ) { pthread_mutex_lock(&fpin_li_mutex); pthread_cleanup_push(cleanup_mutex, &fpin_li_mutex); pthread_testcancel(); while (list_empty(&els_marginal_list_head)) pthread_cond_wait(&fpin_li_cond, &fpin_li_mutex); if (!list_empty(&els_marginal_list_head)) { condlog(4, "Invoke List splice tail\n"); list_splice_tail_init(&els_marginal_list_head, &marginal_list_head); } pthread_cleanup_pop(1); while (!list_empty(&marginal_list_head)) { els_marg = list_first_entry(&marginal_list_head, struct els_marginal_list, node); host_num = els_marg->host_num; event_code = els_marg->event_code; /* Now finally process FPIN LI ELS Frame */ condlog(4, "Got a new Payload buffer, processing it\n"); if ((event_code == FCH_EVT_LINKUP) || (event_code == FCH_EVT_RSCN)) fpin_unset_marginal_dev(host_num, vecs); else { ret = fpin_process_els_frame(host_num, els_marg->payload, vecs); if (ret <= 0) condlog(0, "ELS frame processing failed with ret %d\n", ret); } list_del(&els_marg->node); free(els_marg); } } pthread_cleanup_pop(1); pthread_cleanup_pop(1); pthread_cleanup_pop(1); return NULL; } static void receiver_cleanup_list(__attribute__((unused)) void *arg) { pthread_mutex_lock(&fpin_li_mutex); fpin_clean_els_marginal_list(&els_marginal_list_head); pthread_mutex_unlock(&fpin_li_mutex); } /* * Listen for ELS frames from driver. on receiving the frame payload, * push the payload to a list, and notify the fpin_els_li_consumer thread to * process it. Once consumer thread is notified, return to listen for more ELS * frames from driver. */ void *fpin_fabric_notification_receiver(__attribute__((unused))void *unused) { int ret; int fd = -1; uint32_t els_cmd; struct fc_nl_event *fc_event = NULL; struct sockaddr_nl fc_local; unsigned char buf[DEF_RX_BUF_SIZE] __attribute__((aligned(sizeof(uint64_t)))); size_t plen = 0; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); pthread_cleanup_push(receiver_cleanup_list, NULL); pthread_cleanup_push(cleanup_fd_ptr, &fd); fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_SCSITRANSPORT); if (fd < 0) { condlog(0, "fc socket error %d", fd); goto out; } memset(&fc_local, 0, sizeof(fc_local)); fc_local.nl_family = AF_NETLINK; fc_local.nl_groups = ~0; fc_local.nl_pid = getpid(); ret = bind(fd, (struct sockaddr *)&fc_local, sizeof(fc_local)); if (ret == -1) { condlog(0, "fc socket bind error %d\n", ret); goto out; } for ( ; ; ) { struct nlmsghdr *msghdr; condlog(4, "Waiting for ELS...\n"); ret = read(fd, buf, DEF_RX_BUF_SIZE); if (ret < 0) { condlog(0, "failed to read the els frame (%d)", ret); continue; } condlog(4, "Got a new request %d\n", ret); msghdr = (struct nlmsghdr *)buf; if (!NLMSG_OK(msghdr, (unsigned int)ret)) { condlog(0, "bad els frame read (%d)", ret); continue; } /* Push the frame to appropriate frame list */ plen = NLMSG_PAYLOAD(msghdr, 0); fc_event = (struct fc_nl_event *)NLMSG_DATA(buf); if (plen < sizeof(*fc_event)) { condlog(0, "too short (%d) to be an FC event", ret); continue; } els_cmd = (uint32_t)fc_event->event_data; condlog(4, "Got host no as %d, event 0x%x, len %d evntnum %d evntcode %d\n", fc_event->host_no, els_cmd, fc_event->event_datalen, fc_event->event_num, fc_event->event_code); fpin_handle_els_frame(fc_event); } out: pthread_cleanup_pop(1); pthread_cleanup_pop(1); pthread_cleanup_pop(1); return NULL; } multipath-tools-0.11.1/multipathd/init_unwinder.c000066400000000000000000000014241475246302400221440ustar00rootroot00000000000000#include #include #include "init_unwinder.h" static pthread_mutex_t dummy_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t dummy_cond = PTHREAD_COND_INITIALIZER; static int dummy_started; static void *dummy_thread(void *arg __attribute__((unused))) { pthread_mutex_lock(&dummy_mtx); dummy_started = 1; pthread_cond_broadcast(&dummy_cond); pthread_mutex_unlock(&dummy_mtx); pause(); return NULL; } int init_unwinder(void) { pthread_t dummy; int rc; pthread_mutex_lock(&dummy_mtx); rc = pthread_create(&dummy, NULL, dummy_thread, NULL); if (rc != 0) { pthread_mutex_unlock(&dummy_mtx); return rc; } while (!dummy_started) pthread_cond_wait(&dummy_cond, &dummy_mtx); pthread_mutex_unlock(&dummy_mtx); return pthread_cancel(dummy); } multipath-tools-0.11.1/multipathd/init_unwinder.h000066400000000000000000000013301475246302400221450ustar00rootroot00000000000000#ifndef INIT_UNWINDER_H_INCLUDED #define INIT_UNWINDER_H_INCLUDED /* * init_unwinder(): make sure unwinder symbols are loaded * * libc's implementation of pthread_cancel() loads symbols from * libgcc_s.so using dlopen() when pthread_cancel() is called * for the first time. This happens even with LD_BIND_NOW=1. * This may imply the need for file system access when a thread is * cancelled, which in the case of multipath-tools might be in a * dangerous situation where multipathd must avoid blocking. * * Call load_unwinder() during startup to make sure the dynamic * linker has all necessary symbols resolved early on. * * Return: 0 if successful, an error number otherwise. */ int init_unwinder(void); #endif multipath-tools-0.11.1/multipathd/main.c000066400000000000000000003126071475246302400202220ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include "autoconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "fpin.h" #ifdef USE_SYSTEMD #include #endif #include #include #include /* * libmultipath */ #include "time-util.h" /* * libcheckers */ #include "checkers.h" /* * libmultipath */ #include "version.h" #include "parser.h" #include "vector.h" #include "config.h" #include "util.h" #include "hwtable.h" #include "defaults.h" #include "structs.h" #include "blacklist.h" #include "structs_vec.h" #include "dmparser.h" #include "devmapper.h" #include "sysfs.h" #include "dict.h" #include "discovery.h" #include "debug.h" #include "propsel.h" #include "uevent.h" #include "switchgroup.h" #include "print.h" #include "configure.h" #include "prio.h" #include "wwids.h" #include "pgpolicies.h" #include "log.h" #include "uxsock.h" #include "alias.h" #include "mpath_cmd.h" #include "mpath_persist.h" #include "mpath_persist_int.h" #include "prioritizers/alua_rtpg.h" #include "main.h" #include "pidfile.h" #include "uxlsnr.h" #include "uxclnt.h" #include "cli.h" #include "cli_handlers.h" #include "lock.h" #include "waiter.h" #include "dmevents.h" #include "io_err_stat.h" #include "foreign.h" #include "../third-party/valgrind/drd.h" #include "init_unwinder.h" #define CMDSIZE 160 #define MSG_SIZE 32 int mpath_pr_event_handle(struct path *pp); void * mpath_pr_event_handler_fn (void * ); #define LOG_MSG(lvl, pp) \ do { \ if (pp->mpp && checker_selected(&pp->checker) && \ lvl <= libmp_verbosity) { \ if (pp->sysfs_state == PATH_DOWN) \ condlog(lvl, "%s: %s - path offline", \ pp->mpp->alias, pp->dev); \ else { \ const char *__m = \ checker_message(&pp->checker); \ \ if (strlen(__m)) \ condlog(lvl, "%s: %s - %s checker%s", \ pp->mpp->alias, \ pp->dev, \ checker_name(&pp->checker), \ __m); \ } \ } \ } while(0) struct mpath_event_param { char * devname; struct multipath *mpp; }; int uxsock_timeout; static int verbosity; static int bindings_read_only; int ignore_new_devs; #ifdef NO_DMEVENTS_POLL static int poll_dmevents = 0; #else static int poll_dmevents = 1; #endif /* Don't access this variable without holding config_lock */ static enum daemon_status running_state = DAEMON_INIT; /* Don't access this variable without holding config_lock */ static bool delayed_reconfig; /* Don't access this variable without holding config_lock */ static enum force_reload_types reconfigure_pending = FORCE_RELOAD_NONE; pid_t daemon_pid; static pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t config_cond; static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr, fpin_thr, fpin_consumer_thr; static bool check_thr_started, uevent_thr_started, uxlsnr_thr_started, uevq_thr_started, dmevent_thr_started, fpin_thr_started, fpin_consumer_thr_started; static int pid_fd = -1; static inline enum daemon_status get_running_state(bool *pending_reconfig) { enum daemon_status st; pthread_mutex_lock(&config_lock); st = running_state; if (pending_reconfig != NULL) *pending_reconfig = (reconfigure_pending != FORCE_RELOAD_NONE); pthread_mutex_unlock(&config_lock); return st; } int should_exit(void) { return get_running_state(NULL) == DAEMON_SHUTDOWN; } /* * global copy of vecs for use in sig handlers */ static struct vectors * gvecs; struct config *multipath_conf; /* Local variables */ static volatile sig_atomic_t exit_sig; static volatile sig_atomic_t reconfig_sig; static volatile sig_atomic_t log_reset_sig; static const char *daemon_status_msg[DAEMON_STATUS_SIZE] = { [DAEMON_INIT] = "init", [DAEMON_START] = "startup", [DAEMON_CONFIGURE] = "configure", [DAEMON_IDLE] = "idle", [DAEMON_RUNNING] = "running", [DAEMON_SHUTDOWN] = "shutdown", }; const char * daemon_status(bool *pending_reconfig) { int status = get_running_state(pending_reconfig); if (status < DAEMON_INIT || status >= DAEMON_STATUS_SIZE) return NULL; return daemon_status_msg[status]; } /* * I love you too, systemd ... */ #ifdef USE_SYSTEMD static void do_sd_notify(enum daemon_status old_state, enum daemon_status new_state) { char notify_msg[MSG_SIZE]; const char *msg; static bool startup_done = false; /* * Checkerloop switches back and forth between idle and running state. * No need to tell systemd each time. * These notifications cause a lot of overhead on dbus. */ if ((new_state == DAEMON_IDLE || new_state == DAEMON_RUNNING) && (old_state == DAEMON_IDLE || old_state == DAEMON_RUNNING)) return; if (new_state == DAEMON_IDLE || new_state == DAEMON_RUNNING) msg = "up"; else msg = daemon_status_msg[new_state]; if (msg && !safe_sprintf(notify_msg, "STATUS=%s", msg)) sd_notify(0, notify_msg); if (new_state == DAEMON_SHUTDOWN) { /* Tell systemd that we're not RELOADING any more */ if (old_state == DAEMON_CONFIGURE && startup_done) sd_notify(0, "READY=1"); sd_notify(0, "STOPPING=1"); } else if (new_state == DAEMON_IDLE && old_state == DAEMON_CONFIGURE) { sd_notify(0, "READY=1"); startup_done = true; } else if (new_state == DAEMON_CONFIGURE && startup_done) sd_notify(0, "RELOADING=1"); } #else static void do_sd_notify(__attribute__((unused)) enum daemon_status old_state, __attribute__((unused)) enum daemon_status new_state) {} #endif static void config_cleanup(__attribute__((unused)) void *arg) { pthread_mutex_unlock(&config_lock); } #define wait_for_state_change__(condition, ms) \ ({ \ struct timespec tmo; \ int rc = 0; \ \ if (condition) { \ get_monotonic_time(&tmo); \ tmo.tv_nsec += (ms) * 1000 * 1000; \ normalize_timespec(&tmo); \ do \ rc = pthread_cond_timedwait( \ &config_cond, &config_lock, &tmo); \ while (rc == 0 && (condition)); \ } \ rc; \ }) /* * If the current status is @oldstate, wait for at most @ms milliseconds * for the state to change, and return the new state, which may still be * @oldstate. */ enum daemon_status wait_for_state_change_if(enum daemon_status oldstate, unsigned long ms) { enum daemon_status st; if (oldstate == DAEMON_SHUTDOWN) return DAEMON_SHUTDOWN; pthread_mutex_lock(&config_lock); pthread_cleanup_push(config_cleanup, NULL); wait_for_state_change__(running_state == oldstate, ms); st = running_state; pthread_cleanup_pop(1); return st; } /* must be called with config_lock held */ static void post_config_state__(enum daemon_status state) { if (state != running_state && running_state != DAEMON_SHUTDOWN) { enum daemon_status old_state = running_state; running_state = state; pthread_cond_broadcast(&config_cond); do_sd_notify(old_state, state); condlog(4, "daemon state %s -> %s", daemon_status_msg[old_state], daemon_status_msg[state]); } } void post_config_state(enum daemon_status state) { pthread_mutex_lock(&config_lock); pthread_cleanup_push(config_cleanup, NULL); post_config_state__(state); pthread_cleanup_pop(1); } static bool unblock_reconfigure(void) { bool was_delayed; pthread_mutex_lock(&config_lock); was_delayed = delayed_reconfig; if (was_delayed) { delayed_reconfig = false; /* * In IDLE state, make sure child() is woken up * Otherwise it will wake up when state switches to IDLE */ if (running_state == DAEMON_IDLE) post_config_state__(DAEMON_CONFIGURE); } pthread_mutex_unlock(&config_lock); if (was_delayed) condlog(3, "unblocked delayed reconfigure"); return was_delayed; } /* * Make sure child() is woken up when a map is removed that multipathd * is currently waiting for. * Overrides libmultipath's weak symbol by the same name */ void remove_map_callback(struct multipath *mpp) { if (mpp->wait_for_udev > 0) unblock_reconfigure(); } void schedule_reconfigure(enum force_reload_types requested_type) { pthread_mutex_lock(&config_lock); pthread_cleanup_push(config_cleanup, NULL); enum force_reload_types type; type = (reconfigure_pending == FORCE_RELOAD_YES || requested_type == FORCE_RELOAD_YES) ? FORCE_RELOAD_YES : FORCE_RELOAD_WEAK; switch (running_state) { case DAEMON_SHUTDOWN: break; case DAEMON_IDLE: reconfigure_pending = type; post_config_state__(DAEMON_CONFIGURE); break; case DAEMON_CONFIGURE: case DAEMON_RUNNING: reconfigure_pending = type; break; default: break; } pthread_cleanup_pop(1); } static enum daemon_status set_config_state(enum daemon_status state) { int rc = 0; enum daemon_status st; pthread_cleanup_push(config_cleanup, NULL); pthread_mutex_lock(&config_lock); while (rc == 0 && running_state != state && running_state != DAEMON_SHUTDOWN && running_state != DAEMON_IDLE) { rc = pthread_cond_wait(&config_cond, &config_lock); } if (rc == 0 && running_state == DAEMON_IDLE && state != DAEMON_IDLE) post_config_state__(state); st = running_state; pthread_cleanup_pop(1); return st; } struct config *get_multipath_config(void) { rcu_read_lock(); return rcu_dereference(multipath_conf); } void put_multipath_config(__attribute__((unused)) void *arg) { rcu_read_unlock(); } /* * The path group orderings that this function finds acceptable are different * from now select_path_group determines the best pathgroup. The idea here is * to only trigger a kernel reload when it is obvious that the pathgroups would * be out of order, even if all the paths were usable. Thus pathgroups with * PRIO_UNDEF are skipped, and the number of enabled paths doesn't matter here. */ bool path_groups_in_order(struct multipath *mpp) { int i; struct pathgroup *pgp; bool seen_marginal_pg = false; int last_prio = INT_MAX; if (VECTOR_SIZE(mpp->pg) < 2) return true; vector_foreach_slot(mpp->pg, pgp, i) { if (seen_marginal_pg && !pgp->marginal) return false; /* skip pgs with PRIO_UNDEF, since this is likely temporary */ if (!pgp->paths || pgp->priority == PRIO_UNDEF) continue; if (pgp->marginal && !seen_marginal_pg) { seen_marginal_pg = true; last_prio = pgp->priority; continue; } if (pgp->priority > last_prio) return false; last_prio = pgp->priority; } return true; } static int need_switch_pathgroup (struct multipath * mpp, bool *need_reload) { int bestpg; *need_reload = false; if (!mpp) return 0; if (VECTOR_SIZE(mpp->pg) < 2) return 0; bestpg = select_path_group(mpp); if (mpp->pgfailback == -FAILBACK_MANUAL) return 0; mpp->bestpg = bestpg; *need_reload = !path_groups_in_order(mpp); return (*need_reload || mpp->bestpg != mpp->nextpg); } static void switch_pathgroup (struct multipath * mpp) { mpp->stat_switchgroup++; dm_switchgroup(mpp->alias, mpp->bestpg); condlog(2, "%s: switch to path group #%i", mpp->alias, mpp->bestpg); } static int wait_for_events(struct multipath *mpp, struct vectors *vecs) { if (poll_dmevents) return watch_dmevents(mpp->alias); else return start_waiter_thread(mpp, vecs); } static void remove_map_and_stop_waiter(struct multipath *mpp, struct vectors *vecs) { /* devices are automatically removed by the dmevent polling code, * so they don't need to be manually removed here */ condlog(3, "%s: removing map from internal tables", mpp->alias); if (!poll_dmevents) stop_waiter_thread(mpp); remove_map(mpp, vecs->pathvec, vecs->mpvec); } static void remove_maps_and_stop_waiters(struct vectors *vecs) { int i; struct multipath * mpp; if (!vecs) return; if (!poll_dmevents) { vector_foreach_slot(vecs->mpvec, mpp, i) stop_waiter_thread(mpp); } else unwatch_all_dmevents(); remove_maps(vecs); } int refresh_multipath(struct vectors *vecs, struct multipath *mpp) { if (dm_get_info(mpp->alias, &mpp->dmi) != DMP_OK) { /* Error accessing table */ condlog(2, "%s: cannot access table", mpp->alias); goto out; } if (update_multipath_strings(mpp, vecs->pathvec) != DMP_OK) { condlog(0, "%s: failed to setup multipath", mpp->alias); goto out; } return 0; out: remove_map_and_stop_waiter(mpp, vecs); return 1; } int setup_multipath(struct vectors *vecs, struct multipath *mpp) { if (refresh_multipath(vecs, mpp) != 0) return 1; set_no_path_retry(mpp); if (VECTOR_SIZE(mpp->paths) != 0) dm_cancel_deferred_remove(mpp); return 0; } int update_multipath (struct vectors *vecs, char *mapname) { struct multipath *mpp; struct pathgroup *pgp; struct path *pp; int i, j; mpp = find_mp_by_alias(vecs->mpvec, mapname); if (!mpp) { condlog(3, "%s: multipath map not found", mapname); return 2; } if (setup_multipath(vecs, mpp)) return 1; /* mpp freed in setup_multipath */ /* * compare checkers states with DM states */ vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (pp->dmstate != PSTATE_FAILED) continue; if (pp->state != PATH_DOWN) { struct config *conf; int oldstate = pp->state; unsigned int checkint; conf = get_multipath_config(); checkint = conf->checkint; put_multipath_config(conf); condlog(2, "%s: mark as failed", pp->dev); mpp->stat_path_failures++; pp->state = PATH_DOWN; if (oldstate == PATH_UP || oldstate == PATH_GHOST) update_queue_mode_del_path(mpp); /* * if opportune, * schedule the next check earlier */ if (pp->tick > checkint) pp->tick = checkint; } } } return 0; } static bool flush_map_nopaths(struct multipath *mpp, struct vectors *vecs) { int r; bool is_queueing = true; if (mpp->features) is_queueing = strstr(mpp->features, "queue_if_no_path"); /* It's not safe to do a remove of a map that has "queue_if_no_path" * set, since there could be outstanding IO which will cause * multipathd to hang while attempting the remove */ if (mpp->flush_on_last_del == FLUSH_NEVER && is_queueing) { condlog(2, "%s: map is queueing, can't remove", mpp->alias); return false; } if (mpp->flush_on_last_del == FLUSH_UNUSED && mpath_in_use(mpp->alias) && is_queueing) { condlog(2, "%s: map in use and queueing, can't remove", mpp->alias); return false; } /* * This will flush FLUSH_NEVER devices and FLUSH_UNUSED devices * that are in use, but only if they are already marked as not * queueing. That is just to make absolutely certain that they * really are not queueing, like they claim. */ condlog(is_queueing ? 2 : 3, "%s Last path deleted, disabling queueing", mpp->alias); mpp->retry_tick = 0; mpp->no_path_retry = NO_PATH_RETRY_FAIL; mpp->disable_queueing = 1; mpp->stat_map_failures++; if (dm_queue_if_no_path(mpp, 0) != 0) { condlog(0, "%s: failed to disable queueing. Not removing", mpp->alias); return false; } r = dm_flush_map_nopaths(mpp->alias, mpp->deferred_remove); if (r != DM_FLUSH_OK) { if (r == DM_FLUSH_DEFERRED) { condlog(2, "%s: devmap deferred remove", mpp->alias); mpp->deferred_remove = DEFERRED_REMOVE_IN_PROGRESS; } else condlog(0, "%s: can't flush", mpp->alias); return false; } condlog(2, "%s: map flushed after removing all paths", mpp->alias); remove_map_and_stop_waiter(mpp, vecs); return true; } static void pr_register_active_paths(struct multipath *mpp) { unsigned int i, j; struct path *pp; struct pathgroup *pgp; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) mpath_pr_event_handle(pp); } } } static int update_map (struct multipath *mpp, struct vectors *vecs, int new_map) { int retries = 3; char *params __attribute__((cleanup(cleanup_charp))) = NULL; retry: condlog(4, "%s: updating new map", mpp->alias); if (adopt_paths(vecs->pathvec, mpp, NULL)) { condlog(0, "%s: failed to adopt paths for new map update", mpp->alias); retries = -1; goto fail; } verify_paths(mpp); if (VECTOR_SIZE(mpp->paths) == 0 && flush_map_nopaths(mpp, vecs)) return 1; mpp->action = ACT_RELOAD; if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup new map in update", mpp->alias); retries = -1; goto fail; } if (domap(mpp, params, 1) == DOMAP_FAIL && retries-- > 0) { condlog(0, "%s: map_udate sleep", mpp->alias); free(params); params = NULL; sleep(1); goto retry; } fail: if (new_map && (retries < 0 || wait_for_events(mpp, vecs))) { condlog(0, "%s: failed to create new map", mpp->alias); remove_map(mpp, vecs->pathvec, vecs->mpvec); return 1; } if (setup_multipath(vecs, mpp)) return 1; sync_map_state(mpp); if (mpp->prflag != PRFLAG_SET) update_map_pr(mpp); if (mpp->prflag == PRFLAG_SET) pr_register_active_paths(mpp); if (retries < 0) condlog(0, "%s: failed reload in new map update", mpp->alias); return 0; } static int add_map_without_path (struct vectors *vecs, const char *alias) { struct multipath __attribute__((cleanup(cleanup_multipath_and_paths))) *mpp = alloc_multipath(); char __attribute__((cleanup(cleanup_charp))) *params = NULL; char __attribute__((cleanup(cleanup_charp))) *status = NULL; struct config *conf; char uuid[DM_UUID_LEN]; int rc = DMP_ERR; if (!mpp || !(mpp->alias = strdup(alias))) return DMP_ERR; if ((rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = mpp->alias }, (mapinfo_t) { .uuid = uuid, .dmi = &mpp->dmi, .size = &mpp->size, .target = ¶ms, .status = &status, })) != DMP_OK) return rc; strlcpy(mpp->wwid, uuid + UUID_PREFIX_LEN, sizeof(mpp->wwid)); if (!strlen(mpp->wwid)) condlog(1, "%s: adding map with empty WWID", mpp->alias); conf = get_multipath_config(); mpp->mpe = find_mpe(conf->mptable, mpp->wwid); put_multipath_config(conf); if ((rc = update_multipath_table__(mpp, vecs->pathvec, 0, params, status)) != DMP_OK) return DMP_ERR; if (!vector_alloc_slot(vecs->mpvec)) return DMP_ERR; vector_set_slot(vecs->mpvec, steal_ptr(mpp)); /* * We can't pass mpp here, steal_ptr() has just nullified it. * vector_set_slot() just set the last slot, use that. */ if (update_map(VECTOR_LAST_SLOT(vecs->mpvec), vecs, 1) != 0) /* map removed */ return DMP_ERR; return DMP_OK; } static int coalesce_maps(struct vectors *vecs, vector nmpv) { struct multipath * ompp; vector ompv = vecs->mpvec; int i, reassign_maps; struct config *conf; conf = get_multipath_config(); reassign_maps = conf->reassign_maps; put_multipath_config(conf); vector_foreach_slot (ompv, ompp, i) { condlog(3, "%s: coalesce map", ompp->alias); if (!find_mp_by_wwid(nmpv, ompp->wwid)) { /* * remove all current maps not allowed by the * current configuration */ if (dm_flush_map(ompp->alias) != DM_FLUSH_OK) { condlog(0, "%s: unable to flush devmap", ompp->alias); /* * may be just because the device is open */ if (setup_multipath(vecs, ompp) != 0) { i--; continue; } if (!vector_alloc_slot(nmpv)) return 1; vector_set_slot(nmpv, ompp); vector_del_slot(ompv, i); i--; } else { condlog(2, "%s devmap removed", ompp->alias); trigger_paths_udev_change(ompp, false); } } else if (reassign_maps) { condlog(3, "%s: Reassign existing device-mapper" " devices", ompp->alias); dm_reassign(ompp->alias); } } return 0; } static void sync_maps_state(vector mpvec) { unsigned int i; struct multipath *mpp; vector_foreach_slot (mpvec, mpp, i) sync_map_state(mpp); } int flush_map(struct multipath * mpp, struct vectors * vecs) { int r = dm_suspend_and_flush_map(mpp->alias, 0); if (r != DM_FLUSH_OK) { if (r == DM_FLUSH_FAIL_CANT_RESTORE) remove_feature(&mpp->features, "queue_if_no_path"); condlog(0, "%s: can't flush", mpp->alias); return r; } condlog(2, "%s: map flushed", mpp->alias); remove_map_and_stop_waiter(mpp, vecs); return 0; } static int uev_add_map (struct uevent * uev, struct vectors * vecs) { char *alias; int major = -1, minor = -1, rc; condlog(3, "%s: add map (uevent)", uev->kernel); alias = uevent_get_dm_name(uev); if (!alias) { condlog(3, "%s: No DM_NAME in uevent", uev->kernel); major = uevent_get_major(uev); minor = uevent_get_minor(uev); alias = dm_mapname(major, minor); if (!alias) { condlog(2, "%s: mapname not found for %d:%d", uev->kernel, major, minor); return 1; } } pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); rc = ev_add_map(uev->kernel, alias, vecs); lock_cleanup_pop(vecs->lock); free(alias); return rc; } /* * ev_add_map expects that the multipath device already exists in kernel * before it is called. It just adds a device to multipathd or updates an * existing device. */ int ev_add_map (char * dev, const char * alias, struct vectors * vecs) { struct multipath * mpp; int reassign_maps, rc; struct config *conf; mpp = find_mp_by_alias(vecs->mpvec, alias); if (mpp) { if (mpp->wait_for_udev > 1) { condlog(2, "%s: performing delayed actions", mpp->alias); if (update_map(mpp, vecs, 0)) /* setup multipathd removed the map */ return 1; } conf = get_multipath_config(); reassign_maps = conf->reassign_maps; put_multipath_config(conf); dm_get_info(mpp->alias, &mpp->dmi); if (mpp->wait_for_udev) { mpp->wait_for_udev = 0; if (!need_to_delay_reconfig(vecs) && unblock_reconfigure()) return 0; } /* * Not really an error -- we generate our own uevent * if we create a multipath mapped device as a result * of uev_add_path */ if (reassign_maps) { condlog(3, "%s: Reassign existing device-mapper devices", alias); dm_reassign(alias); } return 0; } condlog(2, "%s: adding map", alias); /* * now we can register the map */ if ((rc = add_map_without_path(vecs, alias)) == DMP_OK) { condlog(2, "%s: devmap %s registered", alias, dev); return 0; } else if (rc == DMP_NO_MATCH) { condlog(4, "%s: not a multipath map", alias); return 0; } else { condlog(2, "%s: ev_add_map failed", dev); return 1; } } static int uev_remove_map (struct uevent * uev, struct vectors * vecs) { char *alias; int minor; struct multipath *mpp; condlog(3, "%s: remove map (uevent)", uev->kernel); alias = uevent_get_dm_name(uev); if (!alias) { condlog(3, "%s: No DM_NAME in uevent, ignoring", uev->kernel); return 0; } minor = uevent_get_minor(uev); pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); mpp = find_mp_by_minor(vecs->mpvec, minor); if (!mpp) { condlog(2, "%s: devmap not registered, can't remove", uev->kernel); goto out; } if (strcmp(mpp->alias, alias)) { condlog(2, "%s: map alias mismatch: have \"%s\", got \"%s\")", uev->kernel, mpp->alias, alias); goto out; } dm_queue_if_no_path(mpp, 0); remove_map_and_stop_waiter(mpp, vecs); out: lock_cleanup_pop(vecs->lock); free(alias); return 0; } static void rescan_path(struct udev_device *ud) { ud = udev_device_get_parent_with_subsystem_devtype(ud, "scsi", "scsi_device"); if (ud) { ssize_t ret = sysfs_attr_set_value(ud, "rescan", "1", strlen("1")); if (ret != strlen("1")) log_sysfs_attr_set_value(1, ret, "%s: failed to trigger rescan", udev_device_get_syspath(ud)); } } /* Returns true if the path was removed */ bool handle_path_wwid_change(struct path *pp, struct vectors *vecs) { struct udev_device *udd; static const char add[] = "add"; ssize_t ret; char dev[FILE_NAME_SIZE]; bool removed = false; if (!pp || !pp->udev) return removed; strlcpy(dev, pp->dev, sizeof(dev)); udd = udev_device_ref(pp->udev); if (ev_remove_path(pp, vecs, 1) & REMOVE_PATH_SUCCESS) { removed = true; } else if (pp->mpp) { pp->dmstate = PSTATE_FAILED; dm_fail_path(pp->mpp->alias, pp->dev_t); } rescan_path(udd); ret = sysfs_attr_set_value(udd, "uevent", add, sizeof(add) - 1); udev_device_unref(udd); if (ret != sizeof(add) - 1) log_sysfs_attr_set_value(1, ret, "%s: failed to trigger add event", dev); return removed; } bool check_path_wwid_change(struct path *pp) { char wwid[WWID_SIZE]; int len = 0; size_t i; if (!strlen(pp->wwid)) return false; /* Get the real fresh device wwid by sgio. sysfs still has old * data, so only get_vpd_sgio will work to get the new wwid */ len = get_vpd_sgio(pp->fd, 0x83, 0, wwid, WWID_SIZE); if (len <= 0) { condlog(2, "%s: failed to check wwid by sgio: len = %d", pp->dev, len); return false; } /*Strip any trailing blanks */ for (i = strlen(wwid); i > 0 && wwid[i-1] == ' '; i--); /* no-op */ wwid[i] = '\0'; condlog(4, "%s: Got wwid %s by sgio", pp->dev, wwid); if (strncmp(wwid, pp->wwid, WWID_SIZE)) { condlog(0, "%s: wwid '%s' doesn't match wwid '%s' from device", pp->dev, pp->wwid, wwid); return true; } return false; } /* * uev_add_path can call uev_update_path, and uev_update_path can call * uev_add_path */ static int uev_update_path (struct uevent *uev, struct vectors * vecs); static int uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) { struct path *pp; int ret = 0, i; struct config *conf; bool partial_init = false; condlog(3, "%s: add path (uevent)", uev->kernel); if (strstr(uev->kernel, "..") != NULL) { /* * Don't allow relative device names in the pathvec */ condlog(0, "%s: path name is invalid", uev->kernel); return 1; } pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); pp = find_path_by_dev(vecs->pathvec, uev->kernel); if (pp) { int r; struct multipath *prev_mpp = NULL; if (pp->initialized == INIT_PARTIAL) { partial_init = true; goto out; } else if (pp->initialized == INIT_REMOVED) { condlog(3, "%s: re-adding removed path", pp->dev); pp->initialized = INIT_NEW; prev_mpp = pp->mpp; if (prev_mpp == NULL) condlog(0, "Bug: %s was in INIT_REMOVED state without being a multipath member", pp->dev); pp->mpp = NULL; /* make sure get_uid() is called */ pp->wwid[0] = '\0'; } else condlog(3, "%s: spurious uevent, path already in pathvec", uev->kernel); if (!pp->mpp && !strlen(pp->wwid)) { condlog(3, "%s: reinitialize path", uev->kernel); udev_device_unref(pp->udev); pp->udev = udev_device_ref(uev->udev); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST); pthread_cleanup_pop(1); if (r == PATHINFO_OK && !prev_mpp) ret = ev_add_path(pp, vecs, need_do_map); else if (r == PATHINFO_OK && !strncmp(pp->wwid, prev_mpp->wwid, WWID_SIZE)) { /* * Path was unsuccessfully removed, but now * re-added, and still belongs to the right map * - all fine, reinstate asap */ pp->mpp = prev_mpp; pp->tick = 1; ret = 0; } else if (prev_mpp) { /* * Bad: re-added path still hangs in wrong map * Make another attempt to remove the path */ pp->mpp = prev_mpp; if (!(ev_remove_path(pp, vecs, true) & REMOVE_PATH_SUCCESS)) { /* * Failure in ev_remove_path will keep * path in pathvec in INIT_REMOVED state * Fail the path to make sure it isn't * used anymore. */ pp->dmstate = PSTATE_FAILED; dm_fail_path(pp->mpp->alias, pp->dev_t); condlog(1, "%s: failed to re-add path still mapped in %s", pp->dev, pp->mpp->alias); ret = 1; } else if (r == PATHINFO_OK) /* * Path successfully freed, move on to * "new path" code path below */ pp = NULL; } else if (r == PATHINFO_SKIPPED) { condlog(3, "%s: remove blacklisted path", uev->kernel); i = find_slot(vecs->pathvec, (void *)pp); if (i != -1) vector_del_slot(vecs->pathvec, i); free_path(pp); } else { condlog(0, "%s: failed to reinitialize path", uev->kernel); ret = 1; } } } if (pp) goto out; /* * get path vital state */ conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = alloc_path_with_pathinfo(conf, uev->udev, uev->wwid, DI_ALL, &pp); pthread_cleanup_pop(1); if (!pp) { if (ret == PATHINFO_SKIPPED) ret = 0; else { condlog(3, "%s: failed to get path info", uev->kernel); ret = 1; } goto out; } ret = store_path(vecs->pathvec, pp); if (!ret) { conf = get_multipath_config(); pp->checkint = conf->checkint; put_multipath_config(conf); ret = ev_add_path(pp, vecs, need_do_map); } else { condlog(0, "%s: failed to store path info, " "dropping event", uev->kernel); free_path(pp); ret = 1; } out: lock_cleanup_pop(vecs->lock); if (partial_init) return uev_update_path(uev, vecs); return ret; } static int sysfs_get_ro (struct path *pp) { int ro; char buff[3]; /* Either "0\n\0" or "1\n\0" */ if (!pp->udev) return -1; if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); return -1; } if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { condlog(3, "%s: Cannot parse ro attribute", pp->dev); return -1; } return ro; } /* * returns: * 0: added * 1: error */ int ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) { struct multipath * mpp; char *params __attribute__((cleanup(cleanup_charp))) = NULL; int retries = 3; int start_waiter = 0; int ret; int ro; unsigned char prflag = PRFLAG_UNSET; /* * need path UID to go any further */ if (strlen(pp->wwid) == 0) { condlog(0, "%s: failed to get path uid", pp->dev); goto fail; /* leave path added to pathvec */ } mpp = find_mp_by_wwid(vecs->mpvec, pp->wwid); if (mpp && pp->size && mpp->size != pp->size) { condlog(0, "%s: failed to add new path %s, device size mismatch", mpp->alias, pp->dev); int i = find_slot(vecs->pathvec, (void *)pp); if (i != -1) vector_del_slot(vecs->pathvec, i); free_path(pp); return 1; } if (mpp) trigger_path_udev_change(pp, true); if (mpp && mpp->wait_for_udev && (pathcount(mpp, PATH_UP) > 0 || (pathcount(mpp, PATH_GHOST) > 0 && path_get_tpgs(pp) != TPGS_IMPLICIT && mpp->ghost_delay_tick <= 0))) { /* if wait_for_udev is set and valid paths exist */ condlog(3, "%s: delaying path addition until %s is fully initialized", pp->dev, mpp->alias); mpp->wait_for_udev = 2; orphan_path(pp, "waiting for create to complete"); return 0; } pp->mpp = mpp; rescan: if (mpp) { condlog(4,"%s: adopting all paths for path %s", mpp->alias, pp->dev); if (adopt_paths(vecs->pathvec, mpp, NULL) || pp->mpp != mpp || find_slot(mpp->paths, pp) == -1) goto fail; /* leave path added to pathvec */ verify_paths(mpp); mpp->action = ACT_RELOAD; prflag = mpp->prflag; mpath_pr_event_handle(pp); } else { if (!should_multipath(pp, vecs->pathvec, vecs->mpvec)) { orphan_path(pp, "only one path"); return 0; } condlog(4,"%s: creating new map", pp->dev); if ((mpp = add_map_with_path(vecs, pp, 1, NULL))) { mpp->action = ACT_CREATE; /* * We don't depend on ACT_CREATE, as domap will * set it to ACT_NOTHING when complete. */ start_waiter = 1; } else goto fail; /* leave path added to pathvec */ } /* ro check - if new path is ro, force map to be ro as well */ ro = sysfs_get_ro(pp); if (ro == 1) mpp->force_readonly = 1; if (!need_do_map) return 0; if (!dm_map_present(mpp->alias)) { mpp->action = ACT_CREATE; start_waiter = 1; } /* * push the map to the device-mapper */ if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup map for addition of new " "path %s", mpp->alias, pp->dev); goto fail_map; } /* * reload the map for the multipath mapped device */ ret = domap(mpp, params, 1); while (ret == DOMAP_RETRY && retries-- > 0) { condlog(0, "%s: retry domap for addition of new " "path %s", mpp->alias, pp->dev); sleep(1); ret = domap(mpp, params, 1); } if (ret == DOMAP_FAIL || ret == DOMAP_RETRY) { condlog(0, "%s: failed in domap for addition of new " "path %s", mpp->alias, pp->dev); /* * deal with asynchronous uevents :(( */ if (mpp->action == ACT_RELOAD && retries-- > 0) { condlog(0, "%s: ev_add_path sleep", mpp->alias); sleep(1); update_mpp_paths(mpp, vecs->pathvec); free(params); params = NULL; goto rescan; } else if (mpp->action == ACT_RELOAD) condlog(0, "%s: giving up reload", mpp->alias); else goto fail_map; } if ((mpp->action == ACT_CREATE || (mpp->action == ACT_NOTHING && start_waiter && !mpp->waiter)) && wait_for_events(mpp, vecs)) goto fail_map; /* * update our state from kernel regardless of create or reload */ if (setup_multipath(vecs, mpp)) goto fail; /* if setup_multipath fails, it removes the map */ sync_map_state(mpp); if (retries >= 0) { if (start_waiter) update_map_pr(mpp); if (mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) pr_register_active_paths(mpp); condlog(2, "%s [%s]: path added to devmap %s", pp->dev, pp->dev_t, mpp->alias); return 0; } else goto fail; fail_map: remove_map(mpp, vecs->pathvec, vecs->mpvec); fail: orphan_path(pp, "failed to add path"); return 1; } static int uev_remove_path (struct uevent *uev, struct vectors * vecs, int need_do_map) { struct path *pp; condlog(3, "%s: remove path (uevent)", uev->kernel); delete_foreign(uev->udev); pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); pp = find_path_by_dev(vecs->pathvec, uev->kernel); if (pp) ev_remove_path(pp, vecs, need_do_map); lock_cleanup_pop(vecs->lock); if (!pp) /* Not an error; path might have been purged earlier */ condlog(0, "%s: path already removed", uev->kernel); return 0; } int ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) { struct multipath * mpp; int i, retval = REMOVE_PATH_SUCCESS; char *params __attribute__((cleanup(cleanup_charp))) = NULL; /* * avoid referring to the map of an orphaned path */ if ((mpp = pp->mpp)) { char devt[BLK_DEV_SIZE]; /* * Mark the path as removed. In case of success, we * will delete it for good. Otherwise, it will be deleted * later, unless all attempts to reload this map fail. */ set_path_removed(pp); /* * transform the mp->pg vector of vectors of paths * into a mp->params string to feed the device-mapper */ if (update_mpp_paths(mpp, vecs->pathvec)) { condlog(0, "%s: failed to update paths", mpp->alias); goto fail; } /* * we have to explicitly remove pp from mpp->paths, * update_mpp_paths() doesn't do that. */ i = find_slot(mpp->paths, pp); if (i != -1) vector_del_slot(mpp->paths, i); /* * remove the map IF removing the last path. If * flush_map_nopaths succeeds, the path has been removed. */ if (VECTOR_SIZE(mpp->paths) == 0 && flush_map_nopaths(mpp, vecs)) goto out; if (mpp->wait_for_udev) { mpp->wait_for_udev = 2; retval = REMOVE_PATH_DELAY; goto out; } if (!need_do_map) { retval = REMOVE_PATH_DELAY; goto out; } if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup map for" " removal of path %s", mpp->alias, pp->dev); goto fail; } /* * reload the map */ mpp->action = ACT_RELOAD; if (domap(mpp, params, 1) == DOMAP_FAIL) { condlog(0, "%s: failed in domap for " "removal of path %s", mpp->alias, pp->dev); retval = REMOVE_PATH_FAILURE; } /* * update mpp state from kernel even if domap failed. * If the path was removed from the mpp, setup_multipath will * free the path regardless of whether it succeeds or fails */ strlcpy(devt, pp->dev_t, sizeof(devt)); if (setup_multipath(vecs, mpp)) return REMOVE_PATH_MAP_ERROR; sync_map_state(mpp); if (retval == REMOVE_PATH_SUCCESS) condlog(2, "%s: path removed from map %s", devt, mpp->alias); } else { /* mpp == NULL */ if ((i = find_slot(vecs->pathvec, (void *)pp)) != -1) vector_del_slot(vecs->pathvec, i); free_path(pp); } out: return retval; fail: condlog(0, "%s: error removing path. removing map %s", pp->dev, mpp->alias); remove_map_and_stop_waiter(mpp, vecs); return REMOVE_PATH_MAP_ERROR; } int finish_path_init(struct path *pp, struct vectors * vecs) { int r; struct config *conf; if (pp->udev && pp->uid_attribute && *pp->uid_attribute && !udev_device_get_is_initialized(pp->udev)) return 0; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = pathinfo(pp, conf, DI_ALL|DI_BLACKLIST); pthread_cleanup_pop(1); if (r == PATHINFO_OK) return 0; condlog(0, "%s: error fully initializing path, removing", pp->dev); ev_remove_path(pp, vecs, 1); return -1; } static bool needs_ro_update(struct multipath *mpp, int ro) { struct pathgroup * pgp; struct path * pp; unsigned int i, j; if (!mpp || ro < 0) return false; if (!has_dm_info(mpp)) return true; if (mpp->dmi.read_only == ro) return false; if (ro == 1) return true; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (sysfs_get_ro(pp) == 1) return false; } } return true; } int resize_map(struct multipath *mpp, unsigned long long size, struct vectors * vecs) { int ret = 0; char *params __attribute__((cleanup(cleanup_charp))) = NULL; unsigned long long orig_size = mpp->size; mpp->size = size; update_mpp_paths(mpp, vecs->pathvec); if (setup_map(mpp, ¶ms, vecs) != 0) { condlog(0, "%s: failed to setup map for resize : %s", mpp->alias, strerror(errno)); mpp->size = orig_size; ret = 1; goto out; } mpp->action = ACT_RESIZE; mpp->force_udev_reload = 1; if (domap(mpp, params, 1) == DOMAP_FAIL) { condlog(0, "%s: failed to resize map : %s", mpp->alias, strerror(errno)); mpp->size = orig_size; ret = 1; } out: if (setup_multipath(vecs, mpp) != 0) return 2; sync_map_state(mpp); return ret; } static int uev_update_path (struct uevent *uev, struct vectors * vecs) { int ro, retval = 0, rc; struct path * pp; struct config *conf; int needs_reinit = 0; switch ((rc = change_foreign(uev->udev))) { case FOREIGN_OK: /* known foreign path, ignore event */ return 0; case FOREIGN_IGNORED: break; case FOREIGN_ERR: condlog(3, "%s: error in change_foreign", __func__); break; default: condlog(1, "%s: return code %d of change_foreign is unsupported", __func__, rc); break; } pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); pp = find_path_by_dev(vecs->pathvec, uev->kernel); if (pp) { struct multipath *mpp = pp->mpp; char wwid[WWID_SIZE]; int auto_resize; conf = get_multipath_config(); auto_resize = conf->auto_resize; put_multipath_config(conf); if (pp->initialized == INIT_REQUESTED_UDEV) { needs_reinit = 1; goto out; } /* Don't deal with other types of failed initialization * now. check_path will handle it */ if (!strlen(pp->wwid) && pp->initialized != INIT_PARTIAL) goto out; strcpy(wwid, pp->wwid); rc = get_uid(pp, pp->state, uev->udev, 0); if (rc != 0) strcpy(pp->wwid, wwid); else if (strlen(wwid) && strncmp(wwid, pp->wwid, WWID_SIZE) != 0) { condlog(0, "%s: path wwid changed from '%s' to '%s'", uev->kernel, wwid, pp->wwid); ev_remove_path(pp, vecs, 1); needs_reinit = 1; goto out; } else if (pp->initialized == INIT_PARTIAL) { udev_device_unref(pp->udev); pp->udev = udev_device_ref(uev->udev); if (finish_path_init(pp, vecs) < 0) { retval = 1; goto out; } } else { udev_device_unref(pp->udev); pp->udev = udev_device_ref(uev->udev); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); if (pathinfo(pp, conf, DI_SYSFS|DI_NOIO) != PATHINFO_OK) condlog(1, "%s: pathinfo failed after change uevent", uev->kernel); pthread_cleanup_pop(1); } ro = uevent_get_disk_ro(uev); if (needs_ro_update(mpp, ro)) { condlog(2, "%s: update path write_protect to '%d' (uevent)", uev->kernel, ro); if (mpp->wait_for_udev) mpp->wait_for_udev = 2; else { if (ro == 1) pp->mpp->force_readonly = 1; retval = reload_and_sync_map(mpp, vecs); if (retval == 2) condlog(2, "%s: map removed during reload", pp->dev); else { pp->mpp->force_readonly = 0; condlog(2, "%s: map %s reloaded (retval %d)", uev->kernel, mpp->alias, retval); } } } if (auto_resize != AUTO_RESIZE_NEVER && mpp && !mpp->wait_for_udev) { struct pathgroup *pgp; struct path *pp2; unsigned int i, j; unsigned long long orig_size = mpp->size; if (!pp->size || pp->size == mpp->size || (pp->size < mpp->size && auto_resize == AUTO_RESIZE_GROW_ONLY)) goto out; vector_foreach_slot(mpp->pg, pgp, i) vector_foreach_slot (pgp->paths, pp2, j) if (pp2->size && pp2->size != pp->size) goto out; retval = resize_map(mpp, pp->size, vecs); if (retval == 2) condlog(2, "%s: map removed during resize", pp->dev); else if (retval == 0) condlog(2, "%s: resized map from %llu to %llu", mpp->alias, orig_size, pp->size); } } out: lock_cleanup_pop(vecs->lock); if (!pp) { /* If the path is blacklisted, print a debug/non-default verbosity message. */ if (uev->udev) { int flag = DI_SYSFS | DI_WWID; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); retval = alloc_path_with_pathinfo(conf, uev->udev, uev->wwid, flag, NULL); pthread_cleanup_pop(1); if (retval == PATHINFO_SKIPPED) { condlog(3, "%s: spurious uevent, path is blacklisted", uev->kernel); return 0; } } condlog(0, "%s: spurious uevent, path not found", uev->kernel); } /* pp->initialized must not be INIT_PARTIAL if needs_reinit is set */ if (needs_reinit) retval = uev_add_path(uev, vecs, 1); return retval; } static int uev_pathfail_check(struct uevent *uev, struct vectors *vecs) { char *action = NULL, *devt = NULL; struct path *pp; int r = 1; action = uevent_get_dm_action(uev); if (!action) return 1; if (strncmp(action, "PATH_FAILED", 11)) goto out; devt = uevent_get_dm_path(uev); if (!devt) { condlog(3, "%s: No DM_PATH in uevent", uev->kernel); goto out; } pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); pp = find_path_by_devt(vecs->pathvec, devt); if (!pp) goto out_lock; r = io_err_stat_handle_pathfail(pp); if (r) condlog(3, "io_err_stat: %s: cannot handle pathfail uevent", pp->dev); out_lock: lock_cleanup_pop(vecs->lock); free(devt); free(action); return r; out: free(action); return 1; } static int map_discovery (struct vectors * vecs) { struct multipath * mpp; int i; if (dm_get_maps(vecs->mpvec)) return 1; vector_foreach_slot (vecs->mpvec, mpp, i) if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK) { remove_map(mpp, vecs->pathvec, vecs->mpvec); i--; } return 0; } int uev_trigger (struct uevent * uev, void * trigger_data) { int r = 0; struct vectors * vecs; struct uevent *merge_uev, *tmp; enum daemon_status state; vecs = (struct vectors *)trigger_data; pthread_cleanup_push(config_cleanup, NULL); pthread_mutex_lock(&config_lock); while (running_state != DAEMON_IDLE && running_state != DAEMON_RUNNING && running_state != DAEMON_SHUTDOWN) pthread_cond_wait(&config_cond, &config_lock); state = running_state; pthread_cleanup_pop(1); if (state == DAEMON_SHUTDOWN) return 0; /* * device map event * Add events are ignored here as the tables * are not fully initialised then. */ if (!strncmp(uev->kernel, "dm-", 3)) { if (!uevent_is_mpath(uev)) { if (!strncmp(uev->action, "change", 6)) (void)add_foreign(uev->udev); else if (!strncmp(uev->action, "remove", 6)) (void)delete_foreign(uev->udev); goto out; } if (!strncmp(uev->action, "change", 6)) { r = uev_add_map(uev, vecs); /* * the kernel-side dm-mpath issues a PATH_FAILED event * when it encounters a path IO error. It is reason- * able be the entry of path IO error accounting pro- * cess. */ uev_pathfail_check(uev, vecs); } else if (!strncmp(uev->action, "remove", 6)) { r = uev_remove_map(uev, vecs); } goto out; } /* * path add/remove/change event, add/remove maybe merged */ list_for_each_entry_safe(merge_uev, tmp, &uev->merge_node, node) { if (!strncmp(merge_uev->action, "add", 3)) r += uev_add_path(merge_uev, vecs, 0); if (!strncmp(merge_uev->action, "remove", 6)) r += uev_remove_path(merge_uev, vecs, 0); } if (!strncmp(uev->action, "add", 3)) r += uev_add_path(uev, vecs, 1); if (!strncmp(uev->action, "remove", 6)) r += uev_remove_path(uev, vecs, 1); if (!strncmp(uev->action, "change", 6)) r += uev_update_path(uev, vecs); out: return r; } static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } static void * ueventloop (void * ap) { struct udev *udev = ap; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); if (uevent_listen(udev)) condlog(0, "error starting uevent listener"); pthread_cleanup_pop(1); return NULL; } static void * uevqloop (void * ap) { pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); if (uevent_dispatch(&uev_trigger, ap)) condlog(0, "error starting uevent dispatcher"); pthread_cleanup_pop(1); return NULL; } static void * uxlsnrloop (void * ap) { long ux_sock; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); ux_sock = ux_socket_listen(DEFAULT_SOCKET); if (ux_sock == -1) { condlog(1, "could not create uxsock: %d", errno); exit_daemon(); goto out; } pthread_cleanup_push(uxsock_cleanup, (void *)ux_sock); if (cli_init()) { condlog(1, "Failed to init uxsock listener"); exit_daemon(); goto out_sock; } /* Tell main thread that thread has started */ post_config_state(DAEMON_CONFIGURE); umask(077); /* * Wait for initial reconfiguration to finish, while * handling signals */ while (wait_for_state_change_if(DAEMON_CONFIGURE, 50) == DAEMON_CONFIGURE) handle_signals(false); uxsock_listen(ux_sock, ap); out_sock: pthread_cleanup_pop(1); /* uxsock_cleanup */ out: pthread_cleanup_pop(1); /* rcu_unregister */ return NULL; } void exit_daemon (void) { post_config_state(DAEMON_SHUTDOWN); } static void fail_path (struct path * pp, int del_active) { if (!pp->mpp) return; condlog(2, "checker failed path %s in map %s", pp->dev_t, pp->mpp->alias); dm_fail_path(pp->mpp->alias, pp->dev_t); if (del_active) update_queue_mode_del_path(pp->mpp); } /* * caller must have locked the path list before calling that function */ static void reinstate_path (struct path * pp) { if (!pp->mpp) return; if (dm_reinstate_path(pp->mpp->alias, pp->dev_t)) condlog(0, "%s: reinstate failed", pp->dev_t); else { condlog(2, "%s: reinstated", pp->dev_t); update_queue_mode_add_path(pp->mpp); } } static void enable_group(struct path * pp) { struct pathgroup * pgp; /* * if path is added through uev_add_path, pgindex can be unset. * next update_strings() will set it, upon map reload event. * * we can safely return here, because upon map reload, all * PG will be enabled. */ if (!pp->mpp->pg || !pp->pgindex) return; pgp = VECTOR_SLOT(pp->mpp->pg, pp->pgindex - 1); if (pgp->status == PGSTATE_DISABLED) { condlog(2, "%s: enable group #%i", pp->mpp->alias, pp->pgindex); dm_enablegroup(pp->mpp->alias, pp->pgindex); } } static void mpvec_garbage_collector (struct vectors * vecs) { struct multipath * mpp; int i; if (!vecs->mpvec) return; vector_foreach_slot (vecs->mpvec, mpp, i) { if (mpp && mpp->alias && !dm_map_present(mpp->alias)) { condlog(2, "%s: remove dead map", mpp->alias); remove_map_and_stop_waiter(mpp, vecs); i--; } } } /* This is called after a path has started working again. It the multipath * device for this path uses the followover failback type, and this is the * best pathgroup, and this is the first path in the pathgroup to come back * up, then switch to this pathgroup */ static int do_followover_should_failback(struct path * pp) { struct pathgroup * pgp; struct path *pp1; int i; if (pp->pgindex != pp->mpp->bestpg) return 0; pgp = VECTOR_SLOT(pp->mpp->pg, pp->pgindex - 1); vector_foreach_slot(pgp->paths, pp1, i) { if (pp1 == pp) continue; if (pp1->chkrstate != PATH_DOWN && pp1->chkrstate != PATH_SHAKY) return 0; } return 1; } static int followover_should_failback(struct multipath *mpp) { struct path *pp; struct pathgroup * pgp; int i, j; if (mpp->pgfailback != -FAILBACK_FOLLOWOVER || !mpp->pg || !mpp->bestpg) return 0; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (pp->is_checked == CHECK_PATH_NEW_UP && do_followover_should_failback(pp)) return 1; } } return 0; } static void missing_uev_wait_tick(struct vectors *vecs) { struct multipath * mpp; int i; int timed_out = 0; vector_foreach_slot (vecs->mpvec, mpp, i) { if (mpp->wait_for_udev && --mpp->uev_wait_tick <= 0) { timed_out = 1; condlog(0, "%s: timeout waiting on creation uevent. enabling reloads", mpp->alias); if (mpp->wait_for_udev > 1 && update_map(mpp, vecs, 0)) { /* update_map removed map */ i--; continue; } mpp->wait_for_udev = 0; } } if (timed_out && !need_to_delay_reconfig(vecs)) unblock_reconfigure(); } static void ghost_delay_tick(struct vectors *vecs) { struct multipath * mpp; int i; vector_foreach_slot (vecs->mpvec, mpp, i) { if (mpp->ghost_delay_tick <= 0) continue; if (--mpp->ghost_delay_tick <= 0) { condlog(0, "%s: timed out waiting for active path", mpp->alias); mpp->force_udev_reload = 1; if (update_map(mpp, vecs, 0) != 0) { /* update_map removed map */ i--; continue; } } } } static void deferred_failback_tick (struct vectors *vecs) { struct multipath * mpp; int i; bool need_reload; vector_foreach_slot (vecs->mpvec, mpp, i) { /* * deferred failback getting sooner */ if (mpp->pgfailback > 0 && mpp->failback_tick > 0) { mpp->failback_tick--; if (!mpp->failback_tick && need_switch_pathgroup(mpp, &need_reload)) { if (need_reload) { if (reload_and_sync_map(mpp, vecs) == 2) { /* multipath device removed */ i--; } } else switch_pathgroup(mpp); } } } } static void retry_count_tick(vector mpvec) { struct multipath *mpp; unsigned int i; vector_foreach_slot (mpvec, mpp, i) { if (mpp->retry_tick > 0) { mpp->stat_total_queueing_time++; condlog(4, "%s: Retrying.. No active path", mpp->alias); if(--mpp->retry_tick == 0) { mpp->stat_map_failures++; dm_queue_if_no_path(mpp, 0); condlog(2, "%s: Disable queueing", mpp->alias); } } } } static void partial_retrigger_tick(vector pathvec) { struct path *pp; unsigned int i; vector_foreach_slot (pathvec, pp, i) { if (pp->initialized == INIT_PARTIAL && pp->udev && pp->partial_retrigger_delay > 0 && --pp->partial_retrigger_delay == 0) { const char *msg = udev_device_get_is_initialized(pp->udev) ? "change" : "add"; ssize_t len = strlen(msg); ssize_t ret = sysfs_attr_set_value(pp->udev, "uevent", msg, len); if (len != ret) log_sysfs_attr_set_value(2, ret, "%s: failed to trigger %s event", pp->dev, msg); } } } #ifdef USE_SYSTEMD static int get_watchdog_interval(void) { const char *envp; long long checkint; long pid; envp = getenv("WATCHDOG_PID"); /* See sd_watchdog_enabled(3) */ if (envp && sscanf(envp, "%lu", &pid) == 1 && pid != daemon_pid) return -1; envp = getenv("WATCHDOG_USEC"); if (!envp || sscanf(envp, "%llu", &checkint) != 1 || checkint == 0) return -1; /* * Value is in microseconds, and the watchdog should be triggered * twice per interval. */ checkint /= 2000000; if (checkint > INT_MAX / 2) { condlog(1, "WatchdogSec=%lld is too high, assuming %d", checkint * 2, INT_MAX); checkint = INT_MAX / 2; } else if (checkint < 1) { condlog(1, "WatchdogSec=1 is too low, daemon will be killed by systemd!"); checkint = 1; } condlog(3, "enabling watchdog, interval %llds", checkint); return checkint; } static void watchdog_tick(const struct timespec *time) { static int watchdog_interval; static struct timespec last_time; struct timespec diff_time; if (watchdog_interval == 0) watchdog_interval = get_watchdog_interval(); if (watchdog_interval < 0) return; timespecsub(time, &last_time, &diff_time); if (diff_time.tv_sec >= watchdog_interval) { condlog(4, "%s: sending watchdog message", __func__); sd_notify(0, "WATCHDOG=1"); last_time = *time; } } #else static void watchdog_tick(const struct timespec *time __attribute__((unused))) {} #endif static bool update_prio(struct multipath *mpp, bool refresh_all) { int oldpriority; struct path *pp; struct pathgroup * pgp; int i, j; bool changed = false; bool skipped_path = false; struct config *conf; vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (pp->state != PATH_UP && pp->state != PATH_GHOST) continue; /* * refresh_all will be set if the mpp has any path * for whom pp->marginal switched values or for whom * pp->is_checked == CHECK_PATH_NEW_UP */ if (!refresh_all && pp->is_checked != CHECK_PATH_CHECKED) { skipped_path = true; continue; } oldpriority = pp->priority; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); pathinfo(pp, conf, DI_PRIO); pthread_cleanup_pop(1); if (pp->priority != oldpriority) changed = true; } } if (!changed || !skipped_path) return changed; /* * If a path changed priorities, refresh the priorities of any * paths we skipped */ vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { if (pp->state != PATH_UP && pp->state != PATH_GHOST) continue; if (pp->is_checked == CHECK_PATH_CHECKED) continue; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); pathinfo(pp, conf, DI_PRIO); pthread_cleanup_pop(1); } } return true; } static int reload_map(struct vectors *vecs, struct multipath *mpp, int is_daemon) { char *params __attribute__((cleanup(cleanup_charp))) = NULL; int r; update_mpp_paths(mpp, vecs->pathvec); if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup map", mpp->alias); return 1; } select_action(mpp, vecs->mpvec, 1); r = domap(mpp, params, is_daemon); if (r == DOMAP_FAIL || r == DOMAP_RETRY) { condlog(3, "%s: domap (%u) failure " "for reload map", mpp->alias, r); return 1; } return 0; } int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs) { int ret = 0; if (reload_map(vecs, mpp, 1)) ret = 1; if (setup_multipath(vecs, mpp) != 0) return 2; sync_map_state(mpp); return ret; } static int check_path_reinstate_state(struct path * pp) { struct timespec curr_time; /* * This function is only called when the path state changes * from "bad" to "good". pp->state reflects the *previous* state. * If this was "bad", we know that a failure must have occurred * beforehand, and count that. * Note that we count path state _changes_ this way. If a path * remains in "bad" state, failure count is not increased. */ if (!((pp->mpp->san_path_err_threshold > 0) && (pp->mpp->san_path_err_forget_rate > 0) && (pp->mpp->san_path_err_recovery_time >0))) { return 0; } if (pp->disable_reinstate) { /* If there are no other usable paths, reinstate the path */ if (count_active_paths(pp->mpp) == 0) { condlog(2, "%s : reinstating path early", pp->dev); goto reinstate_path; } get_monotonic_time(&curr_time); /* If path became failed again or continue failed, should reset * path san_path_err_forget_rate and path dis_reinstate_time to * start a new stable check. */ if ((pp->state != PATH_UP) && (pp->state != PATH_GHOST) && (pp->state != PATH_DELAYED)) { pp->san_path_err_forget_rate = pp->mpp->san_path_err_forget_rate; pp->dis_reinstate_time = curr_time.tv_sec; } if ((curr_time.tv_sec - pp->dis_reinstate_time ) > pp->mpp->san_path_err_recovery_time) { condlog(2,"%s : reinstate the path after err recovery time", pp->dev); goto reinstate_path; } return 1; } /* forget errors on a working path */ if ((pp->state == PATH_UP || pp->state == PATH_GHOST) && pp->path_failures > 0) { if (pp->san_path_err_forget_rate > 0){ pp->san_path_err_forget_rate--; } else { /* for every san_path_err_forget_rate number of * successful path checks decrement path_failures by 1 */ pp->path_failures--; pp->san_path_err_forget_rate = pp->mpp->san_path_err_forget_rate; } return 0; } /* If the path isn't recovering from a failed state, do nothing */ if (pp->state != PATH_DOWN && pp->state != PATH_SHAKY && pp->state != PATH_TIMEOUT) return 0; if (pp->path_failures == 0) pp->san_path_err_forget_rate = pp->mpp->san_path_err_forget_rate; pp->path_failures++; /* if we don't know the currently time, we don't know how long to * delay the path, so there's no point in checking if we should */ get_monotonic_time(&curr_time); /* when path failures has exceeded the san_path_err_threshold * place the path in delayed state till san_path_err_recovery_time * so that the customer can rectify the issue within this time. After * the completion of san_path_err_recovery_time it should * automatically reinstate the path * (note: we know that san_path_err_threshold > 0 here). */ if (pp->path_failures > (unsigned int)pp->mpp->san_path_err_threshold) { condlog(2, "%s : hit error threshold. Delaying path reinstatement", pp->dev); pp->dis_reinstate_time = curr_time.tv_sec; pp->disable_reinstate = 1; return 1; } else { return 0; } reinstate_path: pp->path_failures = 0; pp->disable_reinstate = 0; pp->san_path_err_forget_rate = 0; return 0; } static int should_skip_path(struct path *pp){ if (marginal_path_check_enabled(pp->mpp)) { if (pp->io_err_disable_reinstate && need_io_err_check(pp)) return 1; } else if (san_path_check_enabled(pp->mpp)) { if (check_path_reinstate_state(pp)) return 1; } return 0; } static void start_path_check(struct path *pp) { struct config *conf; if (path_sysfs_state(pp) == PATH_UP) { conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); start_checker(pp, conf, 1, PATH_UNCHECKED); pthread_cleanup_pop(1); } else { checker_clear_message(&pp->checker); condlog(3, "%s: state %s, checker not called", pp->dev, checker_state_name(pp->sysfs_state)); } } static int get_new_state(struct path *pp) { int newstate = pp->sysfs_state; struct config *conf; if (newstate == PATH_UP) newstate = get_state(pp); /* * Wait for uevent for removed paths; * some LLDDs like zfcp keep paths unavailable * without sending uevents. */ if (newstate == PATH_REMOVED) newstate = PATH_DOWN; if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) { condlog(2, "%s: unusable path (%s) - checker failed", pp->dev, checker_state_name(newstate)); LOG_MSG(2, pp); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); pathinfo(pp, conf, 0); pthread_cleanup_pop(1); } return newstate; } static void do_sync_mpp(struct vectors * vecs, struct multipath *mpp) { int i, ret; struct path *pp; ret = update_multipath_strings(mpp, vecs->pathvec); if (ret != DMP_OK) { condlog(1, "%s: %s", mpp->alias, ret == DMP_NOT_FOUND ? "device not found" : "couldn't synchronize with kernel state"); vector_foreach_slot (mpp->paths, pp, i) pp->dmstate = PSTATE_UNDEF; return; } set_no_path_retry(mpp); } static void sync_mpp(struct vectors * vecs, struct multipath *mpp, unsigned int ticks) { if (mpp->sync_tick) mpp->sync_tick -= (mpp->sync_tick > ticks) ? ticks : mpp->sync_tick; if (mpp->sync_tick) return; do_sync_mpp(vecs, mpp); } static int update_path_state (struct vectors * vecs, struct path * pp) { int newstate; int chkr_new_path_up = 0; int disable_reinstate = 0; int oldchkrstate = pp->chkrstate; unsigned int checkint, max_checkint; struct config *conf; int marginal_pathgroups; conf = get_multipath_config(); checkint = conf->checkint; max_checkint = conf->max_checkint; marginal_pathgroups = conf->marginal_pathgroups; put_multipath_config(conf); newstate = get_new_state(pp); if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) return CHECK_PATH_SKIPPED; /* * Async IO in flight. Keep the previous path state * and reschedule as soon as possible */ if (newstate == PATH_PENDING) { pp->tick = 1; return CHECK_PATH_SKIPPED; } if (pp->recheck_wwid == RECHECK_WWID_ON && (newstate == PATH_UP || newstate == PATH_GHOST) && ((pp->state != PATH_UP && pp->state != PATH_GHOST) || pp->dmstate == PSTATE_FAILED) && check_path_wwid_change(pp)) { condlog(0, "%s: path wwid change detected. Removing", pp->dev); return handle_path_wwid_change(pp, vecs)? CHECK_PATH_REMOVED : CHECK_PATH_SKIPPED; } if (pp->mpp->synced_count == 0) { do_sync_mpp(vecs, pp->mpp); /* if update_multipath_strings orphaned the path, quit early */ if (!pp->mpp) return CHECK_PATH_SKIPPED; } if ((newstate != PATH_UP && newstate != PATH_GHOST && newstate != PATH_PENDING) && (pp->state == PATH_DELAYED)) { /* If path state become failed again cancel path delay state */ pp->state = newstate; /* * path state bad again should change the check interval time * to the shortest delay */ pp->checkint = checkint; return CHECK_PATH_CHECKED; } if ((newstate == PATH_UP || newstate == PATH_GHOST) && (san_path_check_enabled(pp->mpp) || marginal_path_check_enabled(pp->mpp))) { if (should_skip_path(pp)) { if (!pp->marginal && pp->state != PATH_DELAYED) condlog(2, "%s: path is now marginal", pp->dev); if (!marginal_pathgroups) { if (marginal_path_check_enabled(pp->mpp)) /* to reschedule as soon as possible, * so that this path can be recovered * in time */ pp->tick = 1; pp->state = PATH_DELAYED; return CHECK_PATH_CHECKED; } if (!pp->marginal) { pp->marginal = 1; pp->mpp->prio_update = PRIO_UPDATE_MARGINAL; } } else { if (pp->marginal || pp->state == PATH_DELAYED) condlog(2, "%s: path is no longer marginal", pp->dev); if (marginal_pathgroups && pp->marginal) { pp->marginal = 0; pp->mpp->prio_update = PRIO_UPDATE_MARGINAL; } } } /* * don't reinstate failed path, if its in stand-by * and if target supports only implicit tpgs mode. * this will prevent unnecessary i/o by dm on stand-by * paths if there are no other active paths in map. */ disable_reinstate = (newstate == PATH_GHOST && count_active_paths(pp->mpp) == 0 && path_get_tpgs(pp) == TPGS_IMPLICIT) ? 1 : 0; pp->chkrstate = newstate; if (newstate != pp->state) { int oldstate = pp->state; pp->state = newstate; LOG_MSG(1, pp); /* * upon state change, reset the checkint * to the shortest delay */ pp->checkint = checkint; if (newstate != PATH_UP && newstate != PATH_GHOST) { /* * proactively fail path in the DM */ if (oldstate == PATH_UP || oldstate == PATH_GHOST) fail_path(pp, 1); else fail_path(pp, 0); /* * cancel scheduled failback */ pp->mpp->failback_tick = 0; pp->mpp->stat_path_failures++; return CHECK_PATH_CHECKED; } /* newstate == PATH_UP || newstate == PATH_GHOST */ if (pp->mpp->prflag != PRFLAG_UNSET) { int prflag = pp->mpp->prflag; /* * Check Persistent Reservation. */ condlog(2, "%s: checking persistent " "reservation registration", pp->dev); mpath_pr_event_handle(pp); if (pp->mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) pr_register_active_paths(pp->mpp); } /* * reinstate this path */ if (!disable_reinstate) reinstate_path(pp); if (pp->mpp->prio_update != PRIO_UPDATE_MARGINAL) pp->mpp->prio_update = PRIO_UPDATE_NEW_PATH; if (oldchkrstate != PATH_UP && oldchkrstate != PATH_GHOST) chkr_new_path_up = 1; /* * if at least one path is up in a group, and * the group is disabled, re-enable it */ if (newstate == PATH_UP) enable_group(pp); } else if (newstate == PATH_UP || newstate == PATH_GHOST) { if ((pp->dmstate == PSTATE_FAILED || pp->dmstate == PSTATE_UNDEF) && !disable_reinstate) /* Clear IO errors */ reinstate_path(pp); else { LOG_MSG(4, pp); if (pp->checkint != max_checkint) { /* * double the next check delay. * max at conf->max_checkint */ if (pp->checkint < (max_checkint / 2)) pp->checkint = 2 * pp->checkint; else pp->checkint = max_checkint; condlog(4, "%s: delay next check %is", pp->dev_t, pp->checkint); } } } else if (newstate != PATH_UP && newstate != PATH_GHOST) { if (pp->dmstate == PSTATE_ACTIVE || pp->dmstate == PSTATE_UNDEF) fail_path(pp, 0); if (newstate == PATH_DOWN) { int log_checker_err; conf = get_multipath_config(); log_checker_err = conf->log_checker_err; put_multipath_config(conf); if (log_checker_err == LOG_CHKR_ERR_ONCE) LOG_MSG(3, pp); else LOG_MSG(2, pp); } } if (pp->mpp->prio_update == PRIO_UPDATE_NONE && (newstate == PATH_UP || newstate == PATH_GHOST)) pp->mpp->prio_update = PRIO_UPDATE_NORMAL; pp->state = newstate; return chkr_new_path_up ? CHECK_PATH_NEW_UP : CHECK_PATH_CHECKED; } static int update_mpp_prio(struct vectors *vecs, struct multipath *mpp) { bool need_reload, changed; enum prio_update_type prio_update = mpp->prio_update; mpp->prio_update = PRIO_UPDATE_NONE; if (mpp->wait_for_udev || prio_update == PRIO_UPDATE_NONE) return 0; condlog(4, "prio refresh"); changed = update_prio(mpp, prio_update != PRIO_UPDATE_NORMAL); if (prio_update == PRIO_UPDATE_MARGINAL) return reload_and_sync_map(mpp, vecs); if (changed && mpp->pgpolicyfn == (pgpolicyfn *)group_by_prio && mpp->pgfailback == -FAILBACK_IMMEDIATE) { condlog(2, "%s: path priorities changed. reloading", mpp->alias); return reload_and_sync_map(mpp, vecs); } if (need_switch_pathgroup(mpp, &need_reload)) { if (mpp->pgfailback > 0 && (prio_update == PRIO_UPDATE_NEW_PATH || mpp->failback_tick <= 0)) mpp->failback_tick = mpp->pgfailback + 1; else if (mpp->pgfailback == -FAILBACK_IMMEDIATE || (prio_update == PRIO_UPDATE_NEW_PATH && followover_should_failback(mpp))) { if (need_reload) return reload_and_sync_map(mpp, vecs); else switch_pathgroup(mpp); } } return 0; } static int check_path (struct path * pp, unsigned int ticks) { if (pp->initialized == INIT_REMOVED) return CHECK_PATH_SKIPPED; if (pp->tick) pp->tick -= (pp->tick > ticks) ? ticks : pp->tick; if (pp->tick) return CHECK_PATH_SKIPPED; if (pp->checkint == CHECKINT_UNDEF) { struct config *conf; condlog(0, "%s: BUG: checkint is not set", pp->dev); conf = get_multipath_config(); pp->checkint = conf->checkint; put_multipath_config(conf); } start_path_check(pp); return CHECK_PATH_STARTED; } static int update_path(struct vectors * vecs, struct path * pp, time_t start_secs) { int r; unsigned int adjust_int, max_checkint; struct config *conf; time_t next_idx, goal_idx; r = update_path_state(vecs, pp); /* * update_path_state() removed or orphaned the path. */ if (r == CHECK_PATH_REMOVED || !pp->mpp) return r; if (pp->tick != 0) { /* the path checker is pending */ if (pp->state != PATH_DELAYED) pp->pending_ticks++; else pp->pending_ticks = 0; return r; } /* schedule the next check */ pp->tick = pp->checkint; if (pp->pending_ticks >= pp->tick) pp->tick = 1; else pp->tick -= pp->pending_ticks; pp->pending_ticks = 0; if (pp->tick == 1) return r; conf = get_multipath_config(); max_checkint = conf->max_checkint; adjust_int = conf->adjust_int; put_multipath_config(conf); /* * every mpp has a goal_idx in the range of * 0 <= goal_idx < conf->max_checkint * * The next check has an index, next_idx, in the range of * 0 <= next_idx < conf->adjust_int * * If the difference between the goal index and the next check index * is not a multiple of pp->checkint, then the device is not checking * the paths at its goal index, and pp->tick will be decremented by * one, to align it over time. */ goal_idx = (find_slot(vecs->mpvec, pp->mpp)) * max_checkint / VECTOR_SIZE(vecs->mpvec); next_idx = (start_secs + pp->tick) % adjust_int; if ((goal_idx - next_idx) % pp->checkint != 0) pp->tick--; return r; } static int check_uninitialized_path(struct path * pp, unsigned int ticks) { int retrigger_tries; struct config *conf; if (pp->initialized != INIT_NEW && pp->initialized != INIT_FAILED && pp->initialized != INIT_MISSING_UDEV) return CHECK_PATH_SKIPPED; if (pp->tick) pp->tick -= (pp->tick > ticks) ? ticks : pp->tick; if (pp->tick) return CHECK_PATH_SKIPPED; conf = get_multipath_config(); retrigger_tries = conf->retrigger_tries; pp->tick = conf->max_checkint; pp->checkint = conf->checkint; put_multipath_config(conf); if (pp->initialized == INIT_MISSING_UDEV) { if (pp->retriggers < retrigger_tries) { static const char change[] = "change"; ssize_t ret; condlog(2, "%s: triggering change event to reinitialize", pp->dev); pp->initialized = INIT_REQUESTED_UDEV; pp->retriggers++; ret = sysfs_attr_set_value(pp->udev, "uevent", change, sizeof(change) - 1); if (ret != sizeof(change) - 1) log_sysfs_attr_set_value(1, ret, "%s: failed to trigger change event", pp->dev); return CHECK_PATH_SKIPPED; } else { condlog(1, "%s: not initialized after %d udev retriggers", pp->dev, retrigger_tries); /* * Make sure that the "add missing path" code path * below may reinstate the path later, if it ever * comes up again. * The WWID needs not be cleared; if it was set, the * state hadn't been INIT_MISSING_UDEV in the first * place. */ pp->initialized = INIT_FAILED; } } start_path_check(pp); return CHECK_PATH_STARTED; } static int update_uninitialized_path(struct vectors * vecs, struct path * pp) { int newstate, ret; struct config *conf; if (pp->initialized != INIT_NEW && pp->initialized != INIT_FAILED && pp->initialized != INIT_MISSING_UDEV) return CHECK_PATH_SKIPPED; newstate = get_new_state(pp); if (!strlen(pp->wwid) && (pp->initialized == INIT_FAILED || pp->initialized == INIT_NEW) && (newstate == PATH_UP || newstate == PATH_GHOST)) { condlog(2, "%s: add missing path", pp->dev); conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST); pthread_cleanup_pop(1); /* INIT_OK implies ret == PATHINFO_OK */ if (pp->initialized == INIT_OK) { ev_add_path(pp, vecs, 1); pp->tick = 1; } else if (ret == PATHINFO_SKIPPED) { int i; condlog(1, "%s: path blacklisted. removing", pp->dev); if ((i = find_slot(vecs->pathvec, (void *)pp)) != -1) vector_del_slot(vecs->pathvec, i); free_path(pp); return CHECK_PATH_REMOVED; } } return CHECK_PATH_CHECKED; } enum checker_state { CHECKER_STARTING, CHECKER_CHECKING_PATHS, CHECKER_WAITING_FOR_PATHS, CHECKER_UPDATING_PATHS, CHECKER_FINISHED, }; static enum checker_state check_paths(struct vectors *vecs, unsigned int ticks) { unsigned int paths_checked = 0; struct timespec diff_time, start_time, end_time; struct path *pp; int i; bool need_wait = false; get_monotonic_time(&start_time); vector_foreach_slot(vecs->pathvec, pp, i) { if (pp->is_checked != CHECK_PATH_UNCHECKED) continue; if (pp->mpp) pp->is_checked = check_path(pp, ticks); else pp->is_checked = check_uninitialized_path(pp, ticks); if (pp->is_checked == CHECK_PATH_STARTED && checker_need_wait(&pp->checker)) need_wait = true; if (++paths_checked % 128 == 0 && (lock_has_waiters(&vecs->lock) || waiting_clients())) { get_monotonic_time(&end_time); timespecsub(&end_time, &start_time, &diff_time); if (diff_time.tv_sec > 0) return CHECKER_CHECKING_PATHS; } } return need_wait ? CHECKER_WAITING_FOR_PATHS : CHECKER_UPDATING_PATHS; } static enum checker_state update_paths(struct vectors *vecs, int *num_paths_p, time_t start_secs) { unsigned int paths_checked = 0; struct timespec diff_time, start_time, end_time; struct path *pp; int i, rc; get_monotonic_time(&start_time); vector_foreach_slot(vecs->pathvec, pp, i) { if (pp->is_checked != CHECK_PATH_STARTED) continue; if (pp->mpp) rc = update_path(vecs, pp, start_secs); else rc = update_uninitialized_path(vecs, pp); if (rc == CHECK_PATH_REMOVED) i--; else { pp->is_checked = rc; if (rc == CHECK_PATH_CHECKED || rc == CHECK_PATH_NEW_UP) (*num_paths_p)++; } if (++paths_checked % 128 == 0 && (lock_has_waiters(&vecs->lock) || waiting_clients())) { get_monotonic_time(&end_time); timespecsub(&end_time, &start_time, &diff_time); if (diff_time.tv_sec > 0) return CHECKER_UPDATING_PATHS; } } return CHECKER_FINISHED; } static void * checkerloop (void *ap) { struct vectors *vecs; struct path *pp; int count = 0; struct timespec last_time; struct config *conf; int foreign_tick = 0; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); mlockall(MCL_CURRENT | MCL_FUTURE); vecs = (struct vectors *)ap; /* Tweak start time for initial path check */ get_monotonic_time(&last_time); last_time.tv_sec -= 1; while (1) { struct timespec diff_time, start_time, end_time; int num_paths = 0, strict_timing; unsigned int ticks = 0; enum checker_state checker_state = CHECKER_STARTING; if (set_config_state(DAEMON_RUNNING) != DAEMON_RUNNING) /* daemon shutdown */ break; get_monotonic_time(&start_time); timespecsub(&start_time, &last_time, &diff_time); condlog(4, "tick (%ld.%06lu secs)", (long)diff_time.tv_sec, diff_time.tv_nsec / 1000); last_time = start_time; ticks = diff_time.tv_sec; watchdog_tick(&start_time); while (checker_state != CHECKER_FINISHED) { struct multipath *mpp; int i; pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); vector_foreach_slot(vecs->mpvec, mpp, i) mpp->synced_count = 0; if (checker_state == CHECKER_STARTING) { vector_foreach_slot(vecs->mpvec, mpp, i) { sync_mpp(vecs, mpp, ticks); mpp->prio_update = PRIO_UPDATE_NONE; } vector_foreach_slot(vecs->pathvec, pp, i) pp->is_checked = CHECK_PATH_UNCHECKED; checker_state = CHECKER_CHECKING_PATHS; } if (checker_state == CHECKER_CHECKING_PATHS) checker_state = check_paths(vecs, ticks); if (checker_state == CHECKER_UPDATING_PATHS) checker_state = update_paths(vecs, &num_paths, start_time.tv_sec); if (checker_state == CHECKER_FINISHED) { vector_foreach_slot(vecs->mpvec, mpp, i) { if (update_mpp_prio(vecs, mpp) == 2) { /* multipath device deleted */ i--; } } } lock_cleanup_pop(vecs->lock); if (checker_state != CHECKER_FINISHED) { /* Yield to waiters */ struct timespec wait = { .tv_nsec = 10000, }; if (checker_state == CHECKER_WAITING_FOR_PATHS) { /* wait 5ms */ wait.tv_nsec = 5 * 1000 * 1000; checker_state = CHECKER_UPDATING_PATHS; } nanosleep(&wait, NULL); } } pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); deferred_failback_tick(vecs); retry_count_tick(vecs->mpvec); missing_uev_wait_tick(vecs); ghost_delay_tick(vecs); partial_retrigger_tick(vecs->pathvec); lock_cleanup_pop(vecs->lock); if (count) count--; else { pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); condlog(4, "map garbage collection"); mpvec_garbage_collector(vecs); count = MAPGCINT; lock_cleanup_pop(vecs->lock); } get_monotonic_time(&end_time); timespecsub(&end_time, &start_time, &diff_time); if (num_paths) { unsigned int max_checkint; condlog(4, "checked %d path%s in %ld.%06lu secs", num_paths, num_paths > 1 ? "s" : "", (long)diff_time.tv_sec, diff_time.tv_nsec / 1000); conf = get_multipath_config(); max_checkint = conf->max_checkint; put_multipath_config(conf); if (diff_time.tv_sec > (time_t)max_checkint) condlog(1, "path checkers took longer " "than %ld seconds, consider " "increasing max_polling_interval", (long)diff_time.tv_sec); } if (foreign_tick == 0) { conf = get_multipath_config(); foreign_tick = conf->max_checkint; put_multipath_config(conf); } if (--foreign_tick == 0) check_foreign(); post_config_state(DAEMON_IDLE); conf = get_multipath_config(); strict_timing = conf->strict_timing; put_multipath_config(conf); if (!strict_timing) sleep(1); else { diff_time.tv_sec = 0; diff_time.tv_nsec = 1000UL * 1000 * 1000 - diff_time.tv_nsec; normalize_timespec(&diff_time); condlog(3, "waiting for %ld.%06lu secs", (long)diff_time.tv_sec, diff_time.tv_nsec / 1000); if (nanosleep(&diff_time, NULL) != 0) { condlog(3, "nanosleep failed with error %d", errno); conf = get_multipath_config(); conf->strict_timing = 0; put_multipath_config(conf); break; } } } pthread_cleanup_pop(1); return NULL; } static int configure (struct vectors * vecs, enum force_reload_types reload_type) { struct multipath * mpp; struct path * pp; vector mpvec; int i, ret; struct config *conf; if (!vecs->pathvec && !(vecs->pathvec = vector_alloc())) { condlog(0, "couldn't allocate path vec in configure"); return 1; } if (!vecs->mpvec && !(vecs->mpvec = vector_alloc())) { condlog(0, "couldn't allocate multipath vec in configure"); return 1; } if (!(mpvec = vector_alloc())) { condlog(0, "couldn't allocate new maps vec in configure"); return 1; } /* * probe for current path (from sysfs) and map (from dm) sets */ ret = path_discovery(vecs->pathvec, DI_ALL); if (ret < 0) { condlog(0, "configure failed at path discovery"); goto fail; } if (should_exit()) goto fail; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); vector_foreach_slot (vecs->pathvec, pp, i){ if (filter_path(conf, pp) > 0){ vector_del_slot(vecs->pathvec, i); free_path(pp); i--; } } pthread_cleanup_pop(1); if (map_discovery(vecs)) { condlog(0, "configure failed at map discovery"); goto fail; } if (should_exit()) goto fail; ret = coalesce_paths(vecs, mpvec, NULL, reload_type, CMD_NONE); if (ret != CP_OK) { condlog(0, "configure failed while coalescing paths"); goto fail; } if (should_exit()) goto fail; /* * may need to remove some maps which are no longer relevant * e.g., due to blacklist changes in conf file */ if (coalesce_maps(vecs, mpvec)) { condlog(0, "configure failed while coalescing maps"); goto fail; } if (should_exit()) goto fail; sync_maps_state(mpvec); vector_foreach_slot(mpvec, mpp, i){ if (remember_wwid(mpp->wwid) == 1) trigger_paths_udev_change(mpp, true); update_map_pr(mpp); if (mpp->prflag == PRFLAG_SET) pr_register_active_paths(mpp); } /* * purge dm of old maps and save new set of maps formed by * considering current path state */ remove_maps(vecs); vecs->mpvec = mpvec; /* * start dm event waiter threads for these new maps */ vector_foreach_slot(vecs->mpvec, mpp, i) { if (wait_for_events(mpp, vecs)) { remove_map(mpp, vecs->pathvec, vecs->mpvec); i--; continue; } if (setup_multipath(vecs, mpp)) i--; } return 0; fail: vector_free(mpvec); return 1; } int need_to_delay_reconfig(struct vectors * vecs) { struct multipath *mpp; int i; if (!VECTOR_SIZE(vecs->mpvec)) return 0; vector_foreach_slot(vecs->mpvec, mpp, i) { if (mpp->wait_for_udev) return 1; } return 0; } void rcu_free_config(struct rcu_head *head) { struct config *conf = container_of(head, struct config, rcu); free_config(conf); } static bool reconfigure_check_uid_attrs(const struct vector_s *old_attrs, const struct vector_s *new_attrs) { int i; char *old; if (VECTOR_SIZE(old_attrs) != VECTOR_SIZE(new_attrs)) return true; vector_foreach_slot(old_attrs, old, i) { char *new = VECTOR_SLOT(new_attrs, i); if (strcmp(old, new)) return true; } return false; } static void reconfigure_check(struct config *old, struct config *new) { int old_marginal_pathgroups; old_marginal_pathgroups = old->marginal_pathgroups; if ((old_marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) != (new->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) { condlog(1, "multipathd must be restarted to turn %s fpin marginal paths", (old_marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)? "off" : "on"); new->marginal_pathgroups = old_marginal_pathgroups; } if (reconfigure_check_uid_attrs(&old->uid_attrs, &new->uid_attrs)) { int i; void *ptr; condlog(1, "multipathd must be restarted to change uid_attrs, keeping old values"); vector_foreach_slot(&new->uid_attrs, ptr, i) free(ptr); vector_reset(&new->uid_attrs); new->uid_attrs = old->uid_attrs; /* avoid uid_attrs being freed in rcu_free_config() */ old->uid_attrs.allocated = 0; old->uid_attrs.slot = NULL; } } static int reconfigure (struct vectors *vecs, enum force_reload_types reload_type) { struct config * old, *conf; conf = load_config(DEFAULT_CONFIGFILE); if (!conf) return 1; if (verbosity) libmp_verbosity = verbosity; setlogmask(LOG_UPTO(libmp_verbosity + 3)); condlog(2, "%s: setting up paths and maps", __func__); /* * free old map and path vectors ... they use old conf state */ if (VECTOR_SIZE(vecs->mpvec)) remove_maps_and_stop_waiters(vecs); free_pathvec(vecs->pathvec, FREE_PATHS); vecs->pathvec = NULL; delete_all_foreign(); reset_checker_classes(); if (bindings_read_only) conf->bindings_read_only = bindings_read_only; if (check_alias_settings(conf)) return 1; uxsock_timeout = conf->uxsock_timeout; old = rcu_dereference(multipath_conf); reconfigure_check(old, conf); conf->sequence_nr = old->sequence_nr + 1; rcu_assign_pointer(multipath_conf, conf); call_rcu(&old->rcu, rcu_free_config); #ifdef FPIN_EVENT_HANDLER fpin_clean_marginal_dev_list(NULL); #endif configure(vecs, reload_type); return 0; } static struct vectors * init_vecs (void) { struct vectors * vecs; vecs = (struct vectors *)calloc(1, sizeof(struct vectors)); if (!vecs) return NULL; init_lock(&vecs->lock); return vecs; } static void * signal_set(int signo, void (*func) (int)) { int r; struct sigaction sig; struct sigaction osig; sig.sa_handler = func; sigemptyset(&sig.sa_mask); sig.sa_flags = 0; r = sigaction(signo, &sig, &osig); if (r < 0) return (SIG_ERR); else return (osig.sa_handler); } void handle_signals(bool nonfatal) { if (exit_sig) { condlog(3, "exit (signal)"); exit_sig = 0; exit_daemon(); } if (!nonfatal) return; if (reconfig_sig) { condlog(3, "reconfigure (signal)"); schedule_reconfigure(FORCE_RELOAD_WEAK); } if (log_reset_sig) { condlog(3, "reset log (signal)"); if (logsink == LOGSINK_SYSLOG) log_thread_reset(); } reconfig_sig = 0; log_reset_sig = 0; } static void sighup(__attribute__((unused)) int sig) { reconfig_sig = 1; } static void sigend(__attribute__((unused)) int sig) { exit_sig = 1; } static void sigusr1(__attribute__((unused)) int sig) { log_reset_sig = 1; } static void sigusr2(__attribute__((unused)) int sig) { condlog(3, "SIGUSR2 received"); } static void signal_init(void) { sigset_t set; /* block all signals */ sigfillset(&set); /* SIGPIPE occurs if logging fails */ sigdelset(&set, SIGPIPE); pthread_sigmask(SIG_SETMASK, &set, NULL); /* Other signals will be unblocked in the uxlsnr thread */ signal_set(SIGHUP, sighup); signal_set(SIGUSR1, sigusr1); signal_set(SIGUSR2, sigusr2); signal_set(SIGINT, sigend); signal_set(SIGTERM, sigend); signal_set(SIGPIPE, sigend); } static void setscheduler (void) { int res; static struct sched_param sched_param; struct rlimit rlim; if (getrlimit(RLIMIT_RTPRIO, &rlim) < 0 || rlim.rlim_max == 0) return; sched_param.sched_priority = rlim.rlim_max > INT_MAX ? INT_MAX : rlim.rlim_max; res = sched_get_priority_max(SCHED_RR); if (res > 0 && res < sched_param.sched_priority) sched_param.sched_priority = res; res = sched_setscheduler(0, SCHED_RR, &sched_param); if (res == -1) condlog(2, "Could not set SCHED_RR at priority %d", sched_param.sched_priority); return; } static void set_oom_adj(void) { FILE *fp; if (getenv("OOMScoreAdjust")) { condlog(3, "Using systemd provided OOMScoreAdjust"); return; } #ifdef OOM_SCORE_ADJ_MIN fp = fopen("/proc/self/oom_score_adj", "w"); if (fp) { fprintf(fp, "%i", OOM_SCORE_ADJ_MIN); fclose(fp); return; } #endif fp = fopen("/proc/self/oom_adj", "w"); if (fp) { fprintf(fp, "%i", OOM_ADJUST_MIN); fclose(fp); return; } condlog(0, "couldn't adjust oom score"); } static void cleanup_pidfile(void) { if (pid_fd >= 0) close(pid_fd); condlog(3, "unlink pidfile"); unlink(DEFAULT_PIDFILE); } static void cleanup_conf(void) { struct config *conf; conf = rcu_dereference(multipath_conf); if (!conf) return; rcu_assign_pointer(multipath_conf, NULL); call_rcu(&conf->rcu, rcu_free_config); } static void cleanup_maps(struct vectors *vecs) { int queue_without_daemon, i; struct multipath *mpp; struct config *conf; conf = get_multipath_config(); queue_without_daemon = conf->queue_without_daemon; put_multipath_config(conf); if (queue_without_daemon == QUE_NO_DAEMON_OFF) vector_foreach_slot(vecs->mpvec, mpp, i) dm_queue_if_no_path(mpp, 0); remove_maps_and_stop_waiters(vecs); vecs->mpvec = NULL; } static void cleanup_paths(struct vectors *vecs) { free_pathvec(vecs->pathvec, FREE_PATHS); vecs->pathvec = NULL; } static void cleanup_vecs(void) { if (!gvecs) return; /* * We can't take the vecs lock here, because exit() may * have been called from the child() thread, holding the lock already. * Anyway, by the time we get here, all threads that might access * vecs should have been joined already (in cleanup_threads). */ cleanup_maps(gvecs); cleanup_paths(gvecs); pthread_mutex_destroy(&gvecs->lock.mutex); free(gvecs); gvecs = NULL; } static void cleanup_threads(void) { stop_io_err_stat_thread(); if (check_thr_started) pthread_cancel(check_thr); if (uevent_thr_started) pthread_cancel(uevent_thr); if (uxlsnr_thr_started) pthread_cancel(uxlsnr_thr); if (uevq_thr_started) pthread_cancel(uevq_thr); if (dmevent_thr_started) pthread_cancel(dmevent_thr); if (fpin_thr_started) pthread_cancel(fpin_thr); if (fpin_consumer_thr_started) pthread_cancel(fpin_consumer_thr); if (check_thr_started) pthread_join(check_thr, NULL); if (uevent_thr_started) pthread_join(uevent_thr, NULL); if (uxlsnr_thr_started) pthread_join(uxlsnr_thr, NULL); if (uevq_thr_started) pthread_join(uevq_thr, NULL); if (dmevent_thr_started) pthread_join(dmevent_thr, NULL); if (fpin_thr_started) pthread_join(fpin_thr, NULL); if (fpin_consumer_thr_started) pthread_join(fpin_consumer_thr, NULL); /* * As all threads are joined now, and we're in DAEMON_SHUTDOWN * state, no new waiter threads will be created anymore. */ pthread_attr_destroy(&waiter_attr); } #ifndef URCU_VERSION # define URCU_VERSION 0 #endif #if (URCU_VERSION >= 0x000800) /* * Use a non-default call_rcu_data for child(). * * We do this to avoid a memory leak from liburcu. * liburcu never frees the default rcu handler (see comments on * call_rcu_data_free() in urcu-call-rcu-impl.h), its thread * can't be joined with pthread_join(), leaving a memory leak. * * Therefore we create our own, which can be destroyed and joined. * The cleanup handler needs to call rcu_barrier(), which is only * available in user-space RCU v0.8 and newer. See * https://lists.lttng.org/pipermail/lttng-dev/2021-May/029958.html */ static struct call_rcu_data *setup_rcu(void) { struct call_rcu_data *crdp; rcu_init(); rcu_register_thread(); crdp = create_call_rcu_data(0UL, -1); if (crdp != NULL) set_thread_call_rcu_data(crdp); return crdp; } static struct call_rcu_data *mp_rcu_data; static void cleanup_rcu(void) { pthread_t rcu_thread; /* Wait for any pending RCU calls */ rcu_barrier(); if (mp_rcu_data != NULL) { rcu_thread = get_call_rcu_thread(mp_rcu_data); /* detach this thread from the RCU thread */ set_thread_call_rcu_data(NULL); synchronize_rcu(); /* tell RCU thread to exit */ call_rcu_data_free(mp_rcu_data); pthread_join(rcu_thread, NULL); } rcu_unregister_thread(); } #endif /* URCU_VERSION */ static void cleanup_child(void) { cleanup_threads(); cleanup_vecs(); cleanup_bindings(); if (poll_dmevents) cleanup_dmevent_waiter(); cleanup_pidfile(); if (logsink == LOGSINK_SYSLOG) log_thread_stop(); cleanup_conf(); } static int sd_notify_exit(int err) { #ifdef USE_SYSTEMD char msg[24]; snprintf(msg, sizeof(msg), "ERRNO=%d", err); sd_notify(0, msg); #endif return err; } static int child (__attribute__((unused)) void *param) { pthread_attr_t log_attr, misc_attr, uevent_attr; struct vectors * vecs; int rc; struct config *conf; char *envp; enum daemon_status state; int exit_code = 1; int fpin_marginal_paths = 0; init_unwinder(); mlockall(MCL_CURRENT | MCL_FUTURE); signal_init(); #if (URCU_VERSION >= 0x000800) mp_rcu_data = setup_rcu(); if (atexit(cleanup_rcu)) fprintf(stderr, "failed to register RCU cleanup handler\n"); #else rcu_init(); #endif if (atexit(cleanup_child)) fprintf(stderr, "failed to register cleanup handlers\n"); setup_thread_attr(&misc_attr, 64 * 1024, 0); setup_thread_attr(&uevent_attr, DEFAULT_UEVENT_STACKSIZE * 1024, 0); setup_thread_attr(&waiter_attr, 32 * 1024, 1); if (logsink == LOGSINK_SYSLOG) { setup_thread_attr(&log_attr, 64 * 1024, 0); log_thread_start(&log_attr); pthread_attr_destroy(&log_attr); } pid_fd = pidfile_create(DEFAULT_PIDFILE, daemon_pid); if (pid_fd < 0) { condlog(1, "failed to create pidfile"); exit(1); } post_config_state(DAEMON_START); condlog(2, "multipathd v%d.%d.%d%s: start up", MULTIPATH_VERSION(VERSION_CODE), EXTRAVERSION); condlog(3, "read " DEFAULT_CONFIGFILE); if (verbosity) libmp_verbosity = verbosity; conf = load_config(DEFAULT_CONFIGFILE); if (verbosity) libmp_verbosity = verbosity; setlogmask(LOG_UPTO(libmp_verbosity + 3)); if (!conf) { condlog(0, "failed to load configuration"); goto failed; } if (bindings_read_only) conf->bindings_read_only = bindings_read_only; uxsock_timeout = conf->uxsock_timeout; rcu_assign_pointer(multipath_conf, conf); if (init_checkers()) { condlog(0, "failed to initialize checkers"); goto failed; } if (init_prio()) { condlog(0, "failed to initialize prioritizers"); goto failed; } /* Failing this is non-fatal */ init_foreign(conf->enable_foreign); if (poll_dmevents) poll_dmevents = dmevent_poll_supported(); envp = getenv("LimitNOFILE"); if (envp) condlog(2,"Using systemd provided open fds limit of %s", envp); else set_max_fds(conf->max_fds); vecs = gvecs = init_vecs(); if (!vecs) goto failed; setscheduler(); set_oom_adj(); #ifdef FPIN_EVENT_HANDLER if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) fpin_marginal_paths = 1; #endif /* * Startup done, invalidate configuration */ conf = NULL; pthread_cleanup_push(config_cleanup, NULL); pthread_mutex_lock(&config_lock); rc = pthread_create(&uxlsnr_thr, &misc_attr, uxlsnrloop, vecs); if (!rc) { /* Wait for uxlsnr startup */ while (running_state == DAEMON_START) pthread_cond_wait(&config_cond, &config_lock); state = running_state; } pthread_cleanup_pop(1); if (rc) { condlog(0, "failed to create cli listener: %d", rc); goto failed; } else { uxlsnr_thr_started = true; if (state != DAEMON_CONFIGURE) { condlog(0, "cli listener failed to start"); goto failed; } } if (poll_dmevents) { if (init_dmevent_waiter(vecs)) { condlog(0, "failed to allocate dmevents waiter info"); goto failed; } if ((rc = pthread_create(&dmevent_thr, &misc_attr, wait_dmevents, NULL))) { condlog(0, "failed to create dmevent waiter thread: %d", rc); goto failed; } else dmevent_thr_started = true; } /* * Start uevent listener early to catch events */ if ((rc = pthread_create(&uevent_thr, &uevent_attr, ueventloop, udev))) { condlog(0, "failed to create uevent thread: %d", rc); goto failed; } else uevent_thr_started = true; pthread_attr_destroy(&uevent_attr); /* * start threads */ if ((rc = pthread_create(&check_thr, &misc_attr, checkerloop, vecs))) { condlog(0,"failed to create checker loop thread: %d", rc); goto failed; } else check_thr_started = true; if ((rc = pthread_create(&uevq_thr, &misc_attr, uevqloop, vecs))) { condlog(0, "failed to create uevent dispatcher: %d", rc); goto failed; } else uevq_thr_started = true; if (fpin_marginal_paths) { if ((rc = pthread_create(&fpin_thr, &misc_attr, fpin_fabric_notification_receiver, NULL))) { condlog(0, "failed to create the fpin receiver thread: %d", rc); goto failed; } else fpin_thr_started = true; if ((rc = pthread_create(&fpin_consumer_thr, &misc_attr, fpin_els_li_consumer, vecs))) { condlog(0, "failed to create the fpin consumer thread thread: %d", rc); goto failed; } else fpin_consumer_thr_started = true; } pthread_attr_destroy(&misc_attr); while (1) { int rc = 0; pthread_cleanup_push(config_cleanup, NULL); pthread_mutex_lock(&config_lock); while (running_state != DAEMON_CONFIGURE && running_state != DAEMON_SHUTDOWN && /* * Check if another reconfigure request was scheduled * while we last ran reconfigure(). * We have to test delayed_reconfig here * to avoid a busy loop */ (reconfigure_pending == FORCE_RELOAD_NONE || delayed_reconfig)) pthread_cond_wait(&config_cond, &config_lock); if (running_state != DAEMON_CONFIGURE && running_state != DAEMON_SHUTDOWN) /* This sets running_state to DAEMON_CONFIGURE */ post_config_state__(DAEMON_CONFIGURE); state = running_state; pthread_cleanup_pop(1); if (state == DAEMON_SHUTDOWN) break; /* handle DAEMON_CONFIGURE */ pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); if (!need_to_delay_reconfig(vecs)) { enum force_reload_types reload_type; pthread_mutex_lock(&config_lock); reload_type = reconfigure_pending == FORCE_RELOAD_YES ? FORCE_RELOAD_YES : FORCE_RELOAD_WEAK; reconfigure_pending = FORCE_RELOAD_NONE; delayed_reconfig = false; pthread_mutex_unlock(&config_lock); rc = reconfigure(vecs, reload_type); } else { pthread_mutex_lock(&config_lock); delayed_reconfig = true; pthread_mutex_unlock(&config_lock); condlog(3, "delaying reconfigure()"); } lock_cleanup_pop(vecs->lock); if (!rc) post_config_state(DAEMON_IDLE); else { condlog(0, "fatal error applying configuration - aborting"); exit_daemon(); } } exit_code = 0; failed: condlog(2, "multipathd: shut down"); /* All cleanup is done in the cleanup_child() exit handler */ return sd_notify_exit(exit_code); } static void cleanup_close(int *pfd) { if (*pfd != -1 && *pfd != STDIN_FILENO && *pfd != STDOUT_FILENO && *pfd != STDERR_FILENO) close(*pfd); } static int daemonize(void) { int pid; int dev_null_fd __attribute__((cleanup(cleanup_close))) = -1; if( (pid = fork()) < 0){ fprintf(stderr, "Failed first fork : %s\n", strerror(errno)); return -1; } else if (pid != 0) return pid; setsid(); if ( (pid = fork()) < 0) fprintf(stderr, "Failed second fork : %s\n", strerror(errno)); else if (pid != 0) _exit(0); if (chdir("/") < 0) fprintf(stderr, "cannot chdir to '/', continuing\n"); dev_null_fd = open("/dev/null", O_RDWR); if (dev_null_fd < 0){ fprintf(stderr, "cannot open /dev/null for input & output : %s\n", strerror(errno)); _exit(0); } if (dup2(dev_null_fd, STDIN_FILENO) < 0) { fprintf(stderr, "cannot dup2 /dev/null to stdin : %s\n", strerror(errno)); _exit(0); } if (dup2(dev_null_fd, STDOUT_FILENO) < 0) { fprintf(stderr, "cannot dup2 /dev/null to stdout : %s\n", strerror(errno)); _exit(0); } if (dup2(dev_null_fd, STDERR_FILENO) < 0) { fprintf(stderr, "cannot dup /dev/null to stderr : %s\n", strerror(errno)); _exit(0); } daemon_pid = getpid(); return 0; } int main (int argc, char *argv[]) { extern char *optarg; extern int optind; int arg; int err = 0; int foreground = 0; struct config *conf; char *opt_k_arg = NULL; bool opt_k = false; ANNOTATE_BENIGN_RACE_SIZED(&multipath_conf, sizeof(multipath_conf), "Manipulated through RCU"); ANNOTATE_BENIGN_RACE_SIZED(&uxsock_timeout, sizeof(uxsock_timeout), "Suppress complaints about this scalar variable"); logsink = LOGSINK_SYSLOG; /* make sure we don't lock any path */ if (chdir("/") < 0) fprintf(stderr, "can't chdir to root directory : %s\n", strerror(errno)); umask(umask(077) | 022); pthread_cond_init_mono(&config_cond); if (atexit(dm_lib_exit)) condlog(3, "failed to register exit handler for libdm"); libmultipath_init(); if (atexit(libmultipath_exit)) condlog(3, "failed to register exit handler for libmultipath"); libmp_udev_set_sync_support(0); while ((arg = getopt(argc, argv, ":dsv:k::Bniw")) != EOF ) { switch(arg) { case 'd': foreground = 1; if (logsink == LOGSINK_SYSLOG) logsink = LOGSINK_STDERR_WITH_TIME; break; case 'v': if (sizeof(optarg) > sizeof(char *) || !isdigit(optarg[0])) exit(1); libmp_verbosity = verbosity = atoi(optarg); break; case 's': logsink = LOGSINK_STDERR_WITHOUT_TIME; break; case 'k': opt_k = true; opt_k_arg = optarg; break; case 'B': bindings_read_only = 1; break; case 'n': condlog(0, "WARNING: ignoring deprecated option -n, use 'ignore_wwids = no' instead"); break; case 'w': poll_dmevents = 0; break; default: fprintf(stderr, "Invalid argument '-%c'\n", optopt); exit(1); } } if (opt_k || optind < argc) { char cmd[CMDSIZE]; char * s = cmd; char * c = s; logsink = LOGSINK_STDERR_WITH_TIME; if (verbosity) libmp_verbosity = verbosity; conf = load_config(DEFAULT_CONFIGFILE); if (!conf) exit(1); if (verbosity) libmp_verbosity = verbosity; uxsock_timeout = conf->uxsock_timeout; memset(cmd, 0x0, CMDSIZE); if (opt_k) s = opt_k_arg; else { while (optind < argc) { if (strchr(argv[optind], ' ')) c += snprintf(c, s + CMDSIZE - c, "\"%s\" ", argv[optind]); else c += snprintf(c, s + CMDSIZE - c, "%s ", argv[optind]); optind++; if (c >= s + CMDSIZE) { fprintf(stderr, "multipathd command too large\n"); exit(1); } } c += snprintf(c, s + CMDSIZE - c, "\n"); } if (!s) { char tmo_buf[16]; snprintf(tmo_buf, sizeof(tmo_buf), "%d", uxsock_timeout + 100); if (execl(BINDIR "/multipathc", "multipathc", tmo_buf, NULL) == -1) { condlog(0, "ERROR: failed to execute multipathc: %m"); err = 1; } } else err = uxclnt(s, uxsock_timeout + 100); free_config(conf); return err; } if (getuid() != 0) { fprintf(stderr, "need to be root\n"); exit(1); } if (foreground) { if (!isatty(fileno(stdout))) setbuf(stdout, NULL); err = 0; daemon_pid = getpid(); } else err = daemonize(); if (err < 0) /* error */ exit(1); else if (err > 0) /* parent dies */ exit(0); else /* child lives */ return (child(NULL)); } void * mpath_pr_event_handler_fn (void * pathp ) { struct multipath * mpp; unsigned int i; int ret, isFound; struct path * pp = (struct path *)pathp; struct prout_param_descriptor *param; struct prin_resp *resp; rcu_register_thread(); mpp = pp->mpp; resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); if (!resp){ condlog(0,"%s Alloc failed for prin response", pp->dev); goto out; } mpp->prflag = PRFLAG_UNSET; ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, 0); if (ret != MPATH_PR_SUCCESS ) { condlog(0,"%s : pr in read keys service action failed. Error=%d", pp->dev, ret); goto out; } condlog(3, " event pr=%d addlen=%d",resp->prin_descriptor.prin_readkeys.prgeneration, resp->prin_descriptor.prin_readkeys.additional_length ); if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) { condlog(1, "%s: No key found. Device may not be registered.", pp->dev); goto out; } condlog(2, "Multipath reservation_key: 0x%" PRIx64 " ", get_be64(mpp->reservation_key)); isFound =0; for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) { condlog(2, "PR IN READKEYS[%d] reservation key:",i); dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i*8], 8 , -1); if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i*8], 8)) { condlog(2, "%s: pr key found in prin readkeys response", mpp->alias); isFound =1; break; } } if (!isFound) { condlog(0, "%s: Either device not registered or ", pp->dev); condlog(0, "host is not authorised for registration. Skip path"); goto out; } param = (struct prout_param_descriptor *)calloc(1, sizeof(struct prout_param_descriptor)); if (!param) goto out; param->sa_flags = mpp->sa_flags; memcpy(param->sa_key, &mpp->reservation_key, 8); param->num_transportid = 0; condlog(3, "device %s:%s", pp->dev, pp->mpp->wwid); ret = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REG_IGN_SA, 0, 0, param, 0); if (ret != MPATH_PR_SUCCESS ) { condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret); } mpp->prflag = PRFLAG_SET; free(param); out: if (resp) free(resp); rcu_unregister_thread(); return NULL; } int mpath_pr_event_handle(struct path *pp) { pthread_t thread; int rc; pthread_attr_t attr; struct multipath * mpp; if (pp->bus != SYSFS_BUS_SCSI) goto no_pr; mpp = pp->mpp; if (!get_be64(mpp->reservation_key)) goto no_pr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); rc = pthread_create(&thread, NULL , mpath_pr_event_handler_fn, pp); if (rc) { condlog(0, "%s: ERROR; return code from pthread_create() is %d", pp->dev, rc); return -1; } pthread_attr_destroy(&attr); rc = pthread_join(thread, NULL); return 0; no_pr: pp->mpp->prflag = PRFLAG_UNSET; return 0; } multipath-tools-0.11.1/multipathd/main.h000066400000000000000000000037151475246302400202240ustar00rootroot00000000000000#ifndef MAIN_H_INCLUDED #define MAIN_H_INCLUDED #define MAPGCINT 5 enum daemon_status { DAEMON_INIT = 0, DAEMON_START, DAEMON_CONFIGURE, DAEMON_IDLE, DAEMON_RUNNING, DAEMON_SHUTDOWN, DAEMON_STATUS_SIZE, }; enum remove_path_result { REMOVE_PATH_FAILURE = 0x0, /* path could not be removed. It is still * part of the kernel map, but its state * is set to INIT_REMOVED, and it will be * removed at the next possible occasion */ REMOVE_PATH_SUCCESS = 0x1, /* path was removed */ REMOVE_PATH_DELAY = 0x2, /* path is set to be removed later. it * currently still exists and is part of the * kernel map */ REMOVE_PATH_MAP_ERROR = 0x5, /* map was removed because of error. value * includes REMOVE_PATH_SUCCESS bit * because the path was also removed */ }; extern pid_t daemon_pid; extern int uxsock_timeout; void exit_daemon(void); const char *daemon_status(bool *pending_reconfig); enum daemon_status wait_for_state_change_if(enum daemon_status oldstate, unsigned long ms); void schedule_reconfigure(enum force_reload_types requested_type); int need_to_delay_reconfig (struct vectors *); int ev_add_path (struct path *, struct vectors *, int); int ev_remove_path (struct path *, struct vectors *, int); int ev_add_map (char *, const char *, struct vectors *); int flush_map(struct multipath *, struct vectors *); void handle_signals(bool); int refresh_multipath(struct vectors * vecs, struct multipath * mpp); int setup_multipath(struct vectors * vecs, struct multipath * mpp); int update_multipath(struct vectors *vecs, char *mapname); int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs); bool handle_path_wwid_change(struct path *pp, struct vectors *vecs); bool check_path_wwid_change(struct path *pp); int finish_path_init(struct path *pp, struct vectors * vecs); int resize_map(struct multipath *mpp, unsigned long long size, struct vectors *vecs); #endif /* MAIN_H_INCLUDED */ multipath-tools-0.11.1/multipathd/multipathc.8000066400000000000000000000046451475246302400213750ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MULTIPATHC 8 2022-09-03 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . multipathc \- Interactive client for multipathd . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B multipathc .RB [\| .IR timeout .RB \|] . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . The \fBmultipathc\fR tool provides an interactive shell for communicating with the \fBmultipathd\fR daemon. The command \fBmultipathd -k\fR invokes \fBmultipathc\fR. .P All commands documented in \fBmultipathd(8)\fR are supported. The available commands can be viewed by entering '\fIhelp\fR'. Use \fIquit\fR, \fIexit\fR, or \fBCTRL-D\fR to exit the shell. Keywords can be abbreviated with the first letters (for example, \fIshu\fR for \fIshutdown\fR), if the abbreviation is unique. Some commands support pretty-printing using \fBprintf\fR-style format specifiers. The supported format specifiers can be listed with the command \fBshow wildcards\fR. Depending on build options, the interactive shell may provide command completion and history expansion features. .P The optional parameter \fBtimeout\fR specifies the timeout to wait for a reply from \fBmultipathd\fR, in milliseconds. The default is 4000 ms. . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR multipathd (8) . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/multipathd/multipathc.c000066400000000000000000000155121475246302400214430ustar00rootroot00000000000000/* * Copyright (c) 2022 SUSE LLC * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include "mpath_cmd.h" #include "uxclnt.h" #include "vector.h" #include "uxsock.h" #include "util.h" #include "cli.h" #include "debug.h" #ifdef USE_LIBEDIT #include #endif #ifdef USE_LIBREADLINE #include #include #endif /* * Versions of libedit prior to 2016 were using a wrong * prototype for rl_completion_entry_function in readline.h. * Internally, libedit casts this to the correct type * (char *)(*)(const char *, int). * So we simply cast to the wrong prototype here. * See http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libedit/readline/readline.h.diff?r1=1.34&r2=1.35 * Unfortunately, this change isn't reflected in the libedit version. */ #ifdef BROKEN_RL_COMPLETION_FUNC #define RL_COMP_ENTRY_CAST(x) ((int (*)(const char *, int)) (x)) #else #define RL_COMP_ENTRY_CAST(x) (x) #endif #if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT) /* * This is the readline completion handler */ char * key_generator (const char * str, int state) { static vector completions; static int index; char *word; if (!state) { uint32_t rlfp = 0, mask = 0; int len = strlen(str), vlen = 0, i, j; struct key * kw; struct handler *h; vector handlers = get_handlers(); vector keys = get_keys(); vector v = NULL; int r = get_cmdvec(rl_line_buffer, &v, true); index = 0; if (completions) vector_free(completions); completions = vector_alloc(); if (!completions || r == ENOMEM) { if (v) vector_free(v); return NULL; } /* * Special case: get_cmdvec() ignores trailing whitespace, * readline doesn't. get_cmdvec() will return "[show]" and * ESRCH for both "show bogus\t" and "show bogus \t". * The former case will fail below. In the latter case, * We shouldn't offer completions. */ if (r == ESRCH && !len) r = ENOENT; /* * If a word completion is in progress, we don't want * to take an exact keyword match in the fingerprint. * For ex "show map[tab]" would validate "map" and discard * "maps" as a valid candidate. */ if (r != ESRCH && VECTOR_SIZE(v) && len) { kw = VECTOR_SLOT(v, VECTOR_SIZE(v) - 1); /* * If kw->param is set, we were already parsing a * parameter, not the keyword. Don't delete it. */ if (!kw->param) { free_key(kw); vector_del_slot(v, VECTOR_SIZE(v) - 1); if (r == EINVAL) r = 0; } } /* * Clean up the mess if we dropped the last slot of a 1-slot * vector */ if (v && !VECTOR_SIZE(v)) { vector_free(v); v = NULL; } /* * Compute a command fingerprint to find out possible completions. * Once done, the vector is useless. Free it. */ if (v) { rlfp = fingerprint(v); vlen = VECTOR_SIZE(v); if (vlen >= 4) mask = ~0; else mask = (uint32_t)(1U << (8 * vlen)) - 1; free_keys(v); } condlog(4, "%s: line=\"%s\" str=\"%s\" r=%d fp=%08x mask=%08x", __func__, rl_line_buffer, str, r, rlfp, mask); /* * If last keyword takes a param, don't even try to guess * Brave souls might try to add parameter completion by walking * paths and multipaths vectors. */ if (r == EINVAL) { if (len == 0 && vector_alloc_slot(completions)) vector_set_slot(completions, strdup("VALUE")); goto init_done; } if (r == ENOENT) goto init_done; vector_foreach_slot(handlers, h, i) { uint8_t code; if (rlfp != (h->fingerprint & mask)) continue; if (vlen >= 4) /* * => mask == ~0 => rlfp == h->fingerprint * Complete command. This must be the only match. */ goto init_done; else if (rlfp == h->fingerprint && r != ESRCH && !strcmp(str, "") && vector_alloc_slot(completions)) /* just completed */ vector_set_slot(completions, strdup("")); else { /* vlen must be 1, 2, or 3 */ code = (h->fingerprint >> vlen * 8); if (code == KEY_INVALID) continue; vector_foreach_slot(keys, kw, j) { if (kw->code != code || strncmp(kw->str, str, len)) continue; if (vector_alloc_slot(completions)) vector_set_slot(completions, strdup(kw->str)); } } } vector_foreach_slot(completions, word, i) condlog(4, "%s: %d -> \"%s\"", __func__, i, word); } init_done: vector_foreach_slot_after(completions, word, index) { index++; return word; } return NULL; } #endif static void print_reply(char *s) { if (!s) return; if (isatty(1)) { printf("%s", s); return; } /* strip ANSI color markers */ while (*s != '\0') { if ((*s == 0x1b) && (*(s+1) == '[')) while ((*s++ != 'm') && (*s != '\0')) {}; putchar(*s++); } } static int need_quit(char *str, size_t len) { char *ptr, *start; size_t trimed_len = len; for (ptr = str; trimed_len && isspace(*ptr); trimed_len--, ptr++) ; start = ptr; for (ptr = str + len - 1; trimed_len && isspace(*ptr); trimed_len--, ptr--) ; if ((trimed_len == 4 && !strncmp(start, "exit", 4)) || (trimed_len == 4 && !strncmp(start, "quit", 4))) return 1; return 0; } /* * process the client */ static void process(int fd, unsigned int timeout) { #if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT) rl_readline_name = "multipathd"; rl_completion_entry_function = RL_COMP_ENTRY_CAST(key_generator); #endif cli_init(); for(;;) { char *line __attribute__((cleanup(cleanup_charp))) = NULL; char *reply __attribute__((cleanup(cleanup_charp))) = NULL; ssize_t llen; int ret; #if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT) line = readline("multipathd> "); if (!line) break; llen = strlen(line); if (!llen) continue; #else size_t lsize = 0; fputs("multipathd> ", stdout); errno = 0; llen = getline(&line, &lsize, stdin); if (llen == -1) { if (errno != 0) fprintf(stderr, "Error in getline: %m"); break; } if (!llen || !strcmp(line, "\n")) continue; #endif if (need_quit(line, llen)) break; if (send_packet(fd, line) != 0) break; ret = recv_packet(fd, &reply, timeout); if (ret != 0) break; print_reply(reply); #if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT) if (line && *line) add_history(line); #endif } } int main (int argc, const char * const argv[]) { int fd; int tmo = DEFAULT_REPLY_TIMEOUT + 100; char *ep; if (argc > 2) { fprintf(stderr, "Usage: %s [timeout]\n", argv[0]); return 1; } if (argc == 2) { tmo = strtol(argv[1], &ep, 10); if (*argv[1] == '\0' || *ep != '\0' || tmo < 0) { fprintf(stderr, "ERROR: invalid timeout value\n"); return 1; } } fd = mpath_connect(); if (fd == -1) { fprintf(stderr, "ERROR: failed to connect to multipathd\n"); return 1; } process(fd, tmo); mpath_disconnect(fd); return 0; } #define HANDLER(x) NULL #include "callbacks.c" multipath-tools-0.11.1/multipathd/multipathd.8.in000066400000000000000000000472141475246302400220020ustar00rootroot00000000000000.\" ---------------------------------------------------------------------------- .\" 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. .\" ---------------------------------------------------------------------------- . .TH MULTIPATHD 8 2024-05-29 Linux . . .\" ---------------------------------------------------------------------------- .SH NAME .\" ---------------------------------------------------------------------------- . multipathd \- Multipath daemon. . . .\" ---------------------------------------------------------------------------- .SH SYNOPSIS .\" ---------------------------------------------------------------------------- . .B multipathd .RB [\| \-d \|] .RB [\| \-s \|] .RB [\| \-v\ \c .IR verbosity \|] .RB [\| \-B \|] .RB [\| \-w \|] .LP .B multipathd .RB [\| \-v\ \c .IR verbosity \|] .B -k\fIcommand\fR .LP .B multipathd .RB [\| \-v\ \c .IR verbosity \|] .B -k .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . The \fBmultipathd\fR daemon is in charge of checking for failed paths. When this happens, it will reconfigure the multipath map the path belongs to, so that this map regains its maximum performance and redundancy. With the \fB-k\fR option, \fBmultipathd\fR acts as a client utility that sends commands to a running instance of the multipathd daemon (see \fBCOMMANDS\fR below). . . .\" ---------------------------------------------------------------------------- .SH OPTIONS .\" ---------------------------------------------------------------------------- . .TP .B \-d Foreground Mode. Don't daemonize, and print all messages to stdout and stderr. . .TP .B \-s Suppress timestamps. Do not prefix logging messages with a timestamp. . .TP .BI \-v " level" Verbosity level. Print additional information while running multipathd. A level of 0 means only print errors. A level of 3 or greater prints debugging information as well. . .TP .B \-B Read-only bindings file. multipathd will not write to the \fIuser_friendly_names\fR bindings file. If a \fIuser_friendly_name\fR doesn't already exist for a device, it will use its WWID as its alias. . .TP .B \-k\fIcommand\fB multipathd executes the given command (see \fBCOMMANDS\fR below). If the command contains whitespace or shell special characters, it needs to be quoted like in \fImultipathd -k'show topology'\fR. No whitespace is allowed between the \fB-k\fR and the command string. . .TP .B \-k multipathd executes the \fBmultipathc\fR interactive shell for entering commands (see \fBCOMMANDS\fR below). . .TP .B \-n \fBIGNORED\fR. Use the option \fIfind_multipaths\fR to control the treatment of newly detected devices by multipathd. See .BR multipath.conf(5). . .TP .B \-w Since kernel 4.14 a new device-mapper event polling interface is used for updating multipath devices on dmevents. Use this flag to force it to use the old event waiting method, based on creating a separate thread for each device. . . . .\" ---------------------------------------------------------------------------- .SH COMMANDS .\" ---------------------------------------------------------------------------- . .TP The following commands can be used in interactive mode: . .TP .B list|show paths Show the paths that multipathd is monitoring, and their state. . .TP .B list|show paths [raw] format $format Show the paths that multipathd is monitoring, using a format string with path format wildcards. Adding \fIraw\fR will remove the headers and alignment padding from the output. See "Path format wildcards" below. . .TP .B list|show path $path Show whether path $path is offline or running. . .TP .B list|show maps|multipaths Show the multipath devices that the multipathd is monitoring. . .TP .B list|show maps|multipaths [raw] format $format Show the status of all multipath devices that the multipathd is monitoring, using a format string with multipath format wildcards. Adding \fIraw\fR will remove the headers and alignment padding from the output. See "Multipath format wildcards" below. . .TP .B list|show maps|multipaths status Show the status of all multipath devices that the multipathd is monitoring. . .TP .B list|show maps|multipaths stats Show some statistics of all multipath devices that the multipathd is monitoring. . .TP .B list|show maps|multipaths topology Show the current multipath topology. Same as '\fImultipath \-ll\fR'. .TP . .B list|show maps|multipaths json Show information about all multipath devices in JSON format. . .TP .B list|show topology Show the current multipath topology. Same as '\fImultipath \-ll\fR'. . .TP .B list|show map|multipath $map topology Show topology of a single multipath device specified by $map, for example 36005076303ffc56200000000000010aa. This map could be obtained from '\fIlist maps\fR'. . .TP .B list|show map|multipath $map [raw] format $format. Show the status of multipath device $map, using a format string with multipath format wildcards. Adding \fIraw\fR will remove the headers and alignment padding from the output. See "Multipath format wildcards" below. . .TP .B list|show map|multipath $map json Show information about multipath device $map in JSON format. . .TP .B list|show wildcards Show the format wildcards used in interactive commands taking $format. See "Format Wildcards" below. . .TP .B list|show config Show the currently used configuration, derived from default values and values specified within the configuration file \fI@CONFIGFILE@\fR. . .TP .B list|show config local Show the currently used configuration like \fIshow config\fR, but limiting the devices section to those devices that are actually present in the system. . .TP .B list|show blacklist Show the currently used blacklist rules, derived from default values and values specified within the configuration file \fI@CONFIGFILE@\fR. . .TP .B list|show devices Show all available block devices by name including the information if they are blacklisted or not. . .TP .B list|show status Show the number of path checkers in each possible state, the number of monitored paths, and whether multipathd is currently handling a uevent. . .TP .B list|show daemon Show the current state of the multipathd daemon. . .TP .B reset maps|multipaths stats Reset the statistics of all multipath devices. . .TP .B reset map|multipath $map stats Reset the statistics of multipath device $map. . .TP .B add path $path Add a path to the list of monitored paths. $path is as listed in /sys/block (e.g. sda). . .TP .B remove|del path $path Stop monitoring a path. $path is as listed in /sys/block (e.g. sda). . .TP .B add map|multipath $map Add a multipath device to the list of monitored devices. $map can either be a device-mapper device as listed in /sys/block (e.g. dm-0) or it can be the alias for the multipath device (e.g. mpath1) or the uid of the multipath device (e.g. 36005076303ffc56200000000000010aa). . .TP .B remove|del maps|multipaths Remove all multipath devices. . .TP .B remove|del map|multipath $map Remove the multipath device $map. . .TP .B resize map|multipath $map Resizes map $map to the given size. . .TP .B switch|switchgroup map|multipath $map group $group Force a multipath device to switch to a specific path group. $group is the path group index, starting with 1. . .TP .B reconfigure Rereads the configuration, and reloads all changed multipath devices. This also happens at startup, when the service is reload, or when a SIGHUP is received. . .TP .B reconfigure all Rereads the configuration, and reloads all multipath devices regardless of whether or not they have changed. This also happens when \fImultipath -r\fR is run. .TP .B suspend map|multipath $map Sets map $map into suspend state. . .TP .B resume map|multipath $map Resumes map $map from suspend state. . .TP .B reset map|multipath $map Reassign existing device-mapper table(s) use the multipath device, instead of its path devices. . .TP .B reload map|multipath $map Reload a multipath device. . .TP .B fail path $path Sets path $path into failed state. . .TP .B reinstate path $path Resumes path $path from failed state. . .TP .B disablequeueing maps|multipaths Disable queueing on all multipath devices. . .TP .B restorequeueing maps|multipaths Restore queueing to the configured \fIno_path_retry\fR setting on all multipath devices whose queueing has been previously disabled by the \fIdisablequeueing\fR command. \fBNote:\fR If \fIno_path_path_retry\fR is set to queue for a limited number of retries after all paths have failed, this will not enable queueing if there are no active paths. . .TP .B disablequeueing map|multipath $map Disable queuing on multipathed map $map. . .TP .B restorequeueing map|multipath $map restore queueing to the configured \fIno_path_retry\fR setting on multipathed map $map whose queueing has been previously disabled by the \fIdisablequeueing\fR command. \fBNote:\fR If \fIno_path_path_retry\fR is set to queue for a limited number of retries after all paths have failed, this will not enable queueing if there are no active paths. . .TP .B forcequeueing daemon Forces multipathd into queue_without_daemon mode, so that no_path_retry queueing will not be disabled when the daemon stops. . .TP .B restorequeueing daemon Restores configured queue_without_daemon mode. . .TP .B map|multipath $map setprstatus Enable persistent reservation management on $map. . .TP .B map|multipath $map unsetprstatus Disable persistent reservation management on $map. . .TP .B map|multipath $map getprstatus Get the current persistent reservation management status of $map. . .TP .B map|multipath $map getprkey Get the current persistent reservation key associated with $map. . .TP .B map|multipath $map setprkey key $key Set the persistent reservation key associated with $map to $key in the \fIprkeys_file\fR. This key will only be used by multipathd if \fIreservation_key\fR is set to \fBfile\fR in \fI@CONFIGFILE@\fR. . .TP .B map|multipath $map unsetprkey Remove the persistent reservation key associated with $map from the \fIprkeys_file\fR. This will only unset the key used by multipathd if \fIreservation_key\fR is set to \fBfile\fR in \fI@CONFIGFILE@\fR. . .TP .B path $path setmarginal move $path to a marginal pathgroup. The path will remain in the marginal path group until \fIunsetmarginal\fR is called. This command will only work if \fImarginal_pathgroups\fR is enabled and there is no Shaky paths detection method configured (see the multipath.conf man page for details). . .TP .B path $path unsetmarginal return marginal path $path to its normal pathgroup. This command will only work if \fImarginal_pathgroups\fR is enabled and there is no Shaky paths detection method configured (see the multipath.conf man page for details). . .TP .B map $map unsetmarginal return all marginal paths in $map to their normal pathgroups. This command will only work if \fImarginal_pathgroups\fR is enabled and there is no Shaky paths detection method configured (see the multipath.conf man page for details). . .TP .B quit|exit End interactive session. . .TP .B shutdown Stop multipathd. . . .\" ---------------------------------------------------------------------------- .SH "Format Wildcards" .\" ---------------------------------------------------------------------------- . Multipathd commands that take a $format option require a format string. This string controls how a device is printed and should include format wildcards. When the devices are printed, these wildcards will be replaced by the appropriate device information. The following wildcards are supported. .TP .B Multipath format wildcards .RS .TP 12 .B %n The device name. .TP .B %w The device WWID (uuid). .TP .B %d The device sysfs name (dm-). .TP .B %F The device \fBfailback\fR setting. For deferred failbacks, it will either include the configured time if a deferred failback is not in progress, or it will show the current progress of a deferred failback in seconds. .TP .B %Q The device \fBno_path_retry\fR setting. If no_path_retry is set to a number of retries, it will either print the configured number of checker retries if the device is not in recovery mode, the number of seconds until queueing is disabled if the device is queueing in recovery mode, or \fIoff\fR if the device has disabled queueing. .TP .B %N The number of active paths for the device. .TP .B %r The device write-protect setting, either \fIro\fR or \fIrw\fR. .TP .B %t The state of the device in device-mapper. \fIsuspend\fR if the devices is suspended, and \fIactive\fR otherwise. .TP .B %S The device size, using the suffixes \fBK\fR, \fBM\fR, \fBG\fR, \fBT\fR, and \fBP\fR, to stand for kilobytes, megabytes, gigabytes, terabytes, and petabytes, respectively. .TP .B %f The "features" string of the device-mapper table in the kernel. .TP .B %x The number of times the device has entered a state where it will fail IO. This is an alias for the \fB%4\fR wildcard. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %h The device table hardware handler string. .TP .B %A The last action multipathd took on the device. This wildcard is for debugging use, as understanding its meaning requires looking at the code. .TP .B %0 The number of times a path in the device has failed. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %1 The number of times multipathd has initiated a pathgroup switch for the device. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %2 The number of times multipathd has loaded a new table for the device. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %3 The approximate number of seconds that multipathd has spent queueing with no usable paths. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %4 The number of times the device has entered a state where it will fail IO. This is an alias for the \fB%x\fR wildcard. This value can be reset with the '\fIreset map $map stats\fR' command. .TP .B %s The vendor/product string for the device. .TP .B %v The array vendor string for the device. .TP .B %p The array product string for the device. .TP .B %e The array firmware revision string for the device. .TP .B %G The foreign library used for the device, or \fB--\fR for native device-mapper multipath devices. See "FOREIGN MULTIPATH SUPPORT" in .BR @CONFIGFILE@ (5). .TP .B %g Data from vendor specific vpd pages for the device, if any. Currently multipathd supports VPD page 0xc0 for HPE 3PAR / Primera / Alletra storage arrays. .TP .B %k The actual max_sectors_kb setting for the device (which may be different from the configured one). .RE . . .TP .B Path format wildcards .RS .TP 12 .B %w The device WWID (uuid). .TP .B %i The device Host:Channel:Id:Lun for SCSI devices. The device "Controller Instance Number":"Controller ID":"Namespace Instance Number":"Namespace ID" for NVMe devices. The Controller and Namespace Instance Numbers match the NVMe device name: "nvmen" .TP .B %d The device sysfs name. .TP .B %D The device major:minor .TP .B %t The device-mapper state of the device, either \fIactive\fR or \fIfailed\fR. .TP .B %o The offline state of the device. This shows "offline" if the device's "state" attribute in sysfs is "offline" (for SCSI) or "dead" (for NVMe). For all other sysfs states, it shows "running". .TP .B %T The multipathd path checker state of the device. The possible states are: .RS .TP 12 .I ready The device is ready to handle IO. .TP .I faulty The device is unusable. .TP .I shaky The device is not able to handle IO but can still be accessed to check the priority. .TP .I ghost The device is in stand-by state. .TP .I i/o pending The checker is in the process of determining the device state. .TP .I i/o timeout The path checker has timed out, failing the device. .TP .I delayed The device appears usable, but it being delayed for marginal path checking. .TP .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 The vendor/product/revision string for the device. .TP .B %c The name of the device's path checking algorithm .TP .B %C The progress towards the next path checker run on the device in seconds. .TP .B %p The device priority. .TP .B %S The device size, using the suffixes \fBK\fR, \fBM\fR, \fBG\fR, \fBT\fR, and \fBP\fR, to stand for kilobytes, megabytes, gigabytes, terabytes, and petabytes, respectively. .TP .B %z The device serial number. .TP .B %M The device marginal state, either \fImarginal\fR or \fInormal\fR. .TP .B %m The multipath device that this device is a path of, or \fI[orphan]\fR if it is not part of any multipath device. .TP .B %N The host World Wide Node Name (WWNN) of the device, if any. .TP .B %n The target World Wide Node Name (WWNN) of the device, if any. .TP .B %R The host World Wide Port Name (WWPN) of the device, if any. .TP .B %r The target World Wide Port Name (WWPN) of the device, if any. .TP .B %a The host adapter name for the device (only SCSI devices). .TP .B %G The foreign library used for the device, or \fB--\fR for native device-mapper multipath devices. See "FOREIGN MULTIPATH SUPPORT" in .BR @CONFIGFILE@ (5). .TP .B %g Data from vendor specific vpd pages for the device, if any. Currently multipathd supports VPD page 0xc0 for HPE 3PAR / Primera / Alletra storage arrays. .TP .B %0 The number of times this device has failed. .TP .B %P The device protocol. See .BR @CONFIGFILE@ (5). .TP .B %I The device initialization state. Devices that have been fully initialized are shown as \fIok\fR. .TP .B %L The device SCSI LUN ID in hexadecimal format. This is only meaningful for SCSI devices. .TP .B %A The ALUA Target Port Group ID for the device, if applicable. .TP .B %k The actual max_sectors_kb setting for the device (which may be different than the configured one). .RE . . .\" ---------------------------------------------------------------------------- .SH "SYSTEMD INTEGRATION" .\" ---------------------------------------------------------------------------- . When compiled with systemd support two systemd service files are installed, \fImultipathd.service\fR and \fImultipathd.socket\fR. If enabled, the \fImultipathd.socket\fR service instructs systemd to intercept the CLI command socket, so that any call to the CLI interface will start-up the daemon if required. The \fImultipathd.service\fR file carries the definitions for controlling the multipath daemon. The daemon itself uses the \fBsd_notify\fR(3) interface to communicate with systemd. The following unit keywords are recognized: . .TP .B WatchdogSec= Enables the internal watchdog from systemd. multipath will send a notification via \fBsd_notify\fR(3) to systemd to reset the watchdog. If specified the \fIpolling_interval\fR and \fImax_polling_interval\fR settings will be overridden by the watchdog settings. Please note that systemd prior to version 207 has issues which prevent the systemd-provided watchdog from working correctly. So the watchdog is not enabled per default, but has to be enabled manually by updating the \fImultipathd.service\fR file. . .TP .B OOMScoreAdjust= Overrides the internal OOM adjust mechanism. . .TP .B LimitNOFILE= Overrides the \fImax_fds\fR configuration setting. . . .\" ---------------------------------------------------------------------------- .SH "SEE ALSO" .\" ---------------------------------------------------------------------------- . .BR multipathc (8), .BR multipath (8), .BR kpartx (8) .RE .BR sd_notify (3), .BR systemd.service (5). . . .\" ---------------------------------------------------------------------------- .SH AUTHORS .\" ---------------------------------------------------------------------------- . \fImultipath-tools\fR was developed by Christophe Varoqui and others. .\" EOF multipath-tools-0.11.1/multipathd/multipathd.service.in000066400000000000000000000014471475246302400232710ustar00rootroot00000000000000[Unit] Description=Device-Mapper Multipath Device Controller Before=lvm2-activation-early.service Before=local-fs-pre.target blk-availability.service shutdown.target Wants=systemd-udevd-kernel.socket @MODPROBE_UNIT@ After=systemd-udevd-kernel.socket @MODPROBE_UNIT@ After=multipathd.socket systemd-remount-fs.service Before=initrd-cleanup.service DefaultDependencies=no Conflicts=shutdown.target Conflicts=initrd-cleanup.service ConditionKernelCommandLine=!nompath ConditionKernelCommandLine=!multipath=off ConditionVirtualization=!container StartLimitIntervalSec=30 StartLimitBurst=3 [Service] Type=notify NotifyAccess=main ExecStart=@BINDIR@/multipathd -d -s ExecReload=@BINDIR@/multipathd reconfigure Restart=on-failure TasksMax=infinity LimitRTPRIO=10 CPUWeight=1000 [Install] WantedBy=sysinit.target multipath-tools-0.11.1/multipathd/multipathd.socket000066400000000000000000000006221475246302400225060ustar00rootroot00000000000000[Unit] Description=multipathd control socket DefaultDependencies=no ConditionKernelCommandLine=!nompath ConditionKernelCommandLine=!multipath=off ConditionVirtualization=!container Before=sockets.target [Socket] ListenStream=@/org/kernel/linux/storage/multipathd [Install] # Socket activation for multipathd is disabled by default. # Activate it here if you find it useful. # WantedBy=sockets.target multipath-tools-0.11.1/multipathd/pidfile.c000066400000000000000000000033541475246302400207060ustar00rootroot00000000000000#include /* for pid_t */ #include /* for open */ #include /* for EACCESS and EAGAIN */ #include /* for snprintf() */ #include /* for memset() */ #include /* for ftruncate() */ #include /* for fcntl() */ #include "debug.h" #include "pidfile.h" int pidfile_create(const char *pidFile, pid_t pid) { char buf[20]; struct flock lock; int fd, value; if((fd = open(pidFile, O_WRONLY | O_CREAT, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) < 0) { condlog(0, "Cannot open pidfile [%s], error was [%s]", pidFile, strerror(errno)); return -errno; } lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) < 0) { if (errno != EACCES && errno != EAGAIN) condlog(0, "Cannot lock pidfile [%s], error was [%s]", pidFile, strerror(errno)); else condlog(0, "process is already running"); goto fail; } if (ftruncate(fd, 0) < 0) { condlog(0, "Cannot truncate pidfile [%s], error was [%s]", pidFile, strerror(errno)); goto fail; } memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf)-1, "%u", pid); if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { condlog(0, "Cannot write pid to pidfile [%s], error was [%s]", pidFile, strerror(errno)); goto fail; } if ((value = fcntl(fd, F_GETFD, 0)) < 0) { condlog(0, "Cannot get close-on-exec flag from pidfile [%s], " "error was [%s]", pidFile, strerror(errno)); goto fail; } value |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, value) < 0) { condlog(0, "Cannot set close-on-exec flag from pidfile [%s], " "error was [%s]", pidFile, strerror(errno)); goto fail; } return fd; fail: close(fd); return -errno; } multipath-tools-0.11.1/multipathd/pidfile.h000066400000000000000000000001621475246302400207050ustar00rootroot00000000000000#ifndef PIDFILE_H_INCLUDED #define PIDFILE_H_INCLUDED int pidfile_create(const char *pidFile, pid_t pid); #endif multipath-tools-0.11.1/multipathd/uxclnt.c000066400000000000000000000023251475246302400206040ustar00rootroot00000000000000/* * Original author : tridge@samba.org, January 2002 * * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat */ #include #include #include #include #include "mpath_cmd.h" #include "uxsock.h" #include "uxclnt.h" static int process_req(int fd, char * inbuf, unsigned int timeout) { char *reply; int ret; if (send_packet(fd, inbuf) != 0) { printf("cannot send packet\n"); return 1; } ret = recv_packet(fd, &reply, timeout); if (ret < 0) { if (ret == -ETIMEDOUT) printf("timeout receiving packet\n"); else printf("error %d receiving packet\n", ret); return 1; } else { ret = (strncmp(reply, "fail\n", 5) == 0); /* If there is additional failure information, skip the * initial 'fail' */ if (ret && strlen(reply) > 5) printf("%s", reply + 5); else printf("%s", reply); free(reply); return ret; } } /* * entry point */ int uxclnt(char * inbuf, unsigned int timeout) { int fd, ret = 0; if (!inbuf) return 1; fd = mpath_connect(); if (fd == -1) { fprintf(stderr, "ERROR: failed to connect to multipathd\n"); return 1; } ret = process_req(fd, inbuf, timeout); mpath_disconnect(fd); return ret; } multipath-tools-0.11.1/multipathd/uxclnt.h000066400000000000000000000001551475246302400206100ustar00rootroot00000000000000#ifndef UXCLNT_H_INCLUDED #define UXCLNT_H_INCLUDED int uxclnt(char * inbuf, unsigned int timeout); #endif multipath-tools-0.11.1/multipathd/uxlsnr.c000066400000000000000000000426111475246302400206240ustar00rootroot00000000000000/* * Original author : tridge@samba.org, January 2002 * * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat */ /* * A simple domain socket listener */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "debug.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "uxsock.h" #include "defaults.h" #include "config.h" #include "mpath_cmd.h" #include "time-util.h" #include "util.h" #include "main.h" #include "cli.h" #include "uxlsnr.h" #include "strbuf.h" #include "alias.h" /* state of client connection */ enum { CLT_RECV, CLT_PARSE, CLT_LOCKED_WORK, CLT_WORK, CLT_SEND, }; struct client { struct list_head node; struct timespec expires; int state; int fd; vector cmdvec; /* NUL byte at end */ char cmd[MAX_CMD_LEN + 1]; struct strbuf reply; struct handler *handler; size_t cmd_len, len; int error; bool is_root; }; /* Indices for array of poll fds */ enum { POLLFD_UX = 0, POLLFD_NOTIFY, POLLFD_IDLE, POLLFDS_BASE, }; #define POLLFD_CHUNK (4096 / sizeof(struct pollfd)) /* Minimum number of pollfds to reserve for clients */ #define MIN_POLLS (POLLFD_CHUNK - POLLFDS_BASE) /* * Max number of client connections allowed * During coldplug, there may be a large number of "multipath -u" * processes connecting. */ #define MAX_CLIENTS (16384 - POLLFDS_BASE) /* Compile-time error if POLLFD_CHUNK is too small */ static __attribute__((unused)) char ___a[-(MIN_POLLS <= 0)]; static LIST_HEAD(clients); static struct pollfd *polls; static int notify_fd = -1; static int idle_fd = -1; static bool clients_need_lock = false; static bool _socket_client_is_root(int fd) { socklen_t len = 0; struct ucred uc; len = sizeof(struct ucred); if ((fd >= 0) && (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) == 0) && (uc.uid == 0)) return true; /* Treat error as not root client */ return false; } /* * handle a new client joining */ static void new_client(int ux_sock) { struct client *c; struct sockaddr addr; socklen_t len = sizeof(addr); int fd; fd = accept(ux_sock, &addr, &len); if (fd == -1) return; c = (struct client *)calloc(1, sizeof(*c)); if (!c) { close(fd); return; } INIT_LIST_HEAD(&c->node); c->fd = fd; c->state = CLT_RECV; c->is_root = _socket_client_is_root(c->fd); /* put it in our linked list */ list_add_tail(&c->node, &clients); } /* * kill off a dead client */ static void dead_client(struct client *c) { int fd = c->fd; list_del_init(&c->node); c->fd = -1; reset_strbuf(&c->reply); if (c->cmdvec) free_keys(c->cmdvec); free(c); close(fd); } static void free_polls (void) { if (polls) free(polls); polls = NULL; } void uxsock_cleanup(void *arg) { struct client *client_loop; struct client *client_tmp; long ux_sock = (long)arg; close(ux_sock); close(notify_fd); list_for_each_entry_safe(client_loop, client_tmp, &clients, node) { dead_client(client_loop); } cli_exit(); free_polls(); } void wakeup_cleanup(void *arg) { struct mutex_lock *lck = arg; int fd = idle_fd; idle_fd = -1; set_wakeup_fn(lck, NULL); if (fd != -1) close(fd); } struct watch_descriptors { int conf_wd; int dir_wd; int mp_wd; /* /etc/multipath; for bindings file */ }; /* failing to set the watch descriptor is o.k. we just miss a warning * message */ static void reset_watch(int notify_fd, struct watch_descriptors *wds, unsigned int *sequence_nr) { struct config *conf; int dir_reset = 0; int conf_reset = 0; int mp_reset = 0; if (notify_fd == -1) return; conf = get_multipath_config(); /* instead of repeatedly try to reset the inotify watch if * the config directory or multipath.conf isn't there, just * do it once per reconfigure */ if (*sequence_nr != conf->sequence_nr) { *sequence_nr = conf->sequence_nr; if (wds->conf_wd == -1) conf_reset = 1; if (wds->dir_wd == -1) dir_reset = 1; if (wds->mp_wd == -1) mp_reset = 1; } put_multipath_config(conf); if (dir_reset) { if (wds->dir_wd != -1) { inotify_rm_watch(notify_fd, wds->dir_wd); wds->dir_wd = -1; } wds->dir_wd = inotify_add_watch(notify_fd, CONFIG_DIR, IN_CLOSE_WRITE | IN_DELETE | IN_ONLYDIR); if (wds->dir_wd == -1) condlog(3, "didn't set up notifications on %s: %m", CONFIG_DIR); } if (conf_reset) { wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); if (wds->conf_wd == -1) condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); } if (mp_reset) { wds->mp_wd = inotify_add_watch(notify_fd, STATE_DIR, IN_MOVED_TO|IN_ONLYDIR); if (wds->mp_wd == -1) condlog(3, "didn't set up notifications on %s: %m", STATE_DIR); } } static void handle_inotify(int fd, struct watch_descriptors *wds) { char buff[1024] __attribute__ ((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; ssize_t len; char *ptr; int got_notify = 0; for (;;) { len = read(fd, buff, sizeof(buff)); if (len <= 0) { if (len < 0 && errno != EAGAIN) { condlog(3, "error reading from inotify_fd"); if (wds->conf_wd != -1) inotify_rm_watch(fd, wds->conf_wd); if (wds->dir_wd != -1) inotify_rm_watch(fd, wds->dir_wd); if (wds->mp_wd != -1) inotify_rm_watch(fd, wds->mp_wd); wds->conf_wd = wds->dir_wd = wds->mp_wd = -1; } break; } for (ptr = buff; ptr < buff + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; if (event->mask & IN_IGNORED) { /* multipathd.conf may have been overwritten. * Try once to reset the notification */ if (wds->conf_wd == event->wd) wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); else if (wds->dir_wd == event->wd) wds->dir_wd = -1; else if (wds->mp_wd == event->wd) wds->mp_wd = -1; } if (wds->mp_wd != -1 && wds->mp_wd == event->wd) handle_bindings_file_inotify(event); else got_notify = 1; } } if (got_notify) condlog(1, "Multipath configuration updated.\nReload multipathd for changes to take effect"); } static const struct timespec ts_zero = { .tv_sec = 0, }; static const struct timespec ts_max = { .tv_sec = LONG_MAX, .tv_nsec = 999999999 }; static struct timespec *get_soonest_timeout(struct timespec *ts) { struct timespec ts_min = ts_max, now; bool any = false; struct client *c; list_for_each_entry(c, &clients, node) { if (timespeccmp(&c->expires, &ts_zero) != 0 && timespeccmp(&c->expires, &ts_min) < 0) { ts_min = c->expires; any = true; } } if (!any) return NULL; get_monotonic_time(&now); timespecsub(&ts_min, &now, ts); if (timespeccmp(ts, &ts_zero) < 0) *ts = ts_zero; condlog(4, "%s: next client expires in %ld.%03lds", __func__, (long)ts->tv_sec, ts->tv_nsec / 1000000); return ts; } bool waiting_clients(void) { return clients_need_lock; } static void check_for_locked_work(struct client *skip) { struct client *c; list_for_each_entry(c, &clients, node) { if (c != skip && c->state == CLT_LOCKED_WORK) { clients_need_lock = true; return; } } clients_need_lock = false; } static int parse_cmd(struct client *c) { int r; r = get_cmdvec(c->cmd, &c->cmdvec, false); if (r) return -r; c->handler = find_handler_for_cmdvec(c->cmdvec); if (!c->handler || !c->handler->fn) return -EINVAL; return r; } static int execute_handler(struct client *c, struct vectors *vecs) { if (!c->handler || !c->handler->fn) return -EINVAL; return c->handler->fn(c->cmdvec, &c->reply, vecs); } static void wakeup_listener(void) { uint64_t one = 1; if (idle_fd != -1 && write(idle_fd, &one, sizeof(one)) != sizeof(one)) condlog(1, "%s: failed", __func__); } static void drain_idle_fd(int fd) { uint64_t val; int rc; rc = read(fd, &val, sizeof(val)); condlog(4, "%s: %d, %"PRIu64, __func__, rc, val); } void default_reply(struct client *c, int r) { if (r == 0) { append_strbuf_str(&c->reply, "ok\n"); return; } append_strbuf_str(&c->reply, "fail\n"); switch(r) { case -EINVAL: case -ESRCH: case -ENOMEM: /* return codes from get_cmdvec() */ genhelp_handler(c->cmd, -r, &c->reply); break; case -EPERM: append_strbuf_str(&c->reply, "permission deny: need to be root\n"); break; case -ETIMEDOUT: append_strbuf_str(&c->reply, "timeout\n"); break; case -EBUSY: append_strbuf_str(&c->reply, "map or partition in use\n"); break; case -ENODEV: append_strbuf_str(&c->reply, "device not found\n"); } } static void set_client_state(struct client *c, int state) { switch(state) { case CLT_RECV: reset_strbuf(&c->reply); memset(c->cmd, '\0', sizeof(c->cmd)); c->error = 0; /* fallthrough */ case CLT_SEND: /* no timeout while waiting for the client or sending a reply */ c->expires = ts_zero; /* reuse these fields for next data transfer */ c->len = c->cmd_len = 0; /* cmdvec isn't needed any more */ if (c->cmdvec) { free_keys(c->cmdvec); c->cmdvec = NULL; } break; default: break; } c->state = state; } enum { STM_CONT, STM_BREAK, }; static int client_state_machine(struct client *c, struct vectors *vecs, short revents) { ssize_t n; condlog(4, "%s: cli[%d] poll=%x state=%d cmd=\"%s\" repl \"%s\"", __func__, c->fd, revents, c->state, c->cmd, get_strbuf_str(&c->reply)); switch (c->state) { case CLT_RECV: if (!(revents & POLLIN)) return STM_BREAK; if (c->cmd_len == 0) { size_t len; /* * We got POLLIN; assume that at least the length can * be read immediately. */ get_monotonic_time(&c->expires); c->expires.tv_sec += uxsock_timeout / 1000; c->expires.tv_nsec += (uxsock_timeout % 1000) * 1000000; normalize_timespec(&c->expires); n = recv(c->fd, &len, sizeof(len), 0); if (n < (ssize_t)sizeof(len)) { condlog(1, "%s: cli[%d]: failed to receive reply len: %zd", __func__, c->fd, n); c->error = -ECONNRESET; } else if (len <= 0 || len > MAX_CMD_LEN) { condlog(1, "%s: cli[%d]: invalid command length (%zu bytes)", __func__, c->fd, len); c->error = -ECONNRESET; } else { c->cmd_len = len; condlog(4, "%s: cli[%d]: connected", __func__, c->fd); } /* poll for data */ return STM_BREAK; } else if (c->len < c->cmd_len) { n = recv(c->fd, c->cmd + c->len, c->cmd_len - c->len, 0); if (n <= 0 && errno != EINTR && errno != EAGAIN) { condlog(1, "%s: cli[%d]: error in recv: %m", __func__, c->fd); c->error = -ECONNRESET; return STM_BREAK; } c->len += n; if (c->len < c->cmd_len) /* continue polling */ return STM_BREAK; } condlog(4, "cli[%d]: Got request [%s]", c->fd, c->cmd); set_client_state(c, CLT_PARSE); return STM_CONT; case CLT_PARSE: c->error = parse_cmd(c); if (!c->error) { /* Permission check */ struct key *kw = VECTOR_SLOT(c->cmdvec, 0); if (!c->is_root && kw->code != VRB_LIST) { c->error = -EPERM; condlog(0, "%s: cli[%d]: unauthorized cmd \"%s\"", __func__, c->fd, c->cmd); } } if (c->error) set_client_state(c, CLT_SEND); else if (c->handler->locked) set_client_state(c, CLT_LOCKED_WORK); else set_client_state(c, CLT_WORK); return STM_CONT; case CLT_LOCKED_WORK: if (trylock(&vecs->lock) == 0) { /* don't use cleanup_lock(), lest we wakeup ourselves */ pthread_cleanup_push_cast(unlock__, &vecs->lock); c->error = execute_handler(c, vecs); check_for_locked_work(c); pthread_cleanup_pop(1); condlog(4, "%s: cli[%d] grabbed lock", __func__, c->fd); set_client_state(c, CLT_SEND); /* Wait for POLLOUT */ return STM_BREAK; } else { condlog(4, "%s: cli[%d] waiting for lock", __func__, c->fd); return STM_BREAK; } case CLT_WORK: c->error = execute_handler(c, vecs); set_client_state(c, CLT_SEND); /* Wait for POLLOUT */ return STM_BREAK; case CLT_SEND: if (get_strbuf_len(&c->reply) == 0) default_reply(c, c->error); if (c->cmd_len == 0) { size_t len = get_strbuf_len(&c->reply) + 1; if (send(c->fd, &len, sizeof(len), MSG_NOSIGNAL) != sizeof(len)) c->error = -ECONNRESET; c->cmd_len = len; return STM_BREAK; } if (c->len < c->cmd_len) { const char *buf = get_strbuf_str(&c->reply); n = send(c->fd, buf + c->len, c->cmd_len - c->len, MSG_NOSIGNAL); if (n == -1) { if (!(errno == EAGAIN || errno == EINTR)) c->error = -ECONNRESET; } else c->len += n; } if (c->len >= c->cmd_len) { condlog(4, "cli[%d]: Reply [%zu bytes]", c->fd, c->cmd_len); set_client_state(c, CLT_RECV); } return STM_BREAK; default: return STM_BREAK; } } static void check_timeout(struct client *c) { struct timespec now; if (timespeccmp(&c->expires, &ts_zero) == 0) return; get_monotonic_time(&now); if (timespeccmp(&c->expires, &now) > 0) return; condlog(2, "%s: cli[%d]: timed out at %ld.%03ld", __func__, c->fd, (long)c->expires.tv_sec, c->expires.tv_nsec / 1000000); c->error = -ETIMEDOUT; set_client_state(c, CLT_SEND); } static void handle_client(struct client *c, struct vectors *vecs, short revents) { if (revents & (POLLHUP|POLLERR)) { c->error = -ECONNRESET; return; } check_timeout(c); while (client_state_machine(c, vecs, revents) == STM_CONT); } /* * entry point */ void *uxsock_listen(long ux_sock, void *trigger_data) { sigset_t mask; int max_pfds = MIN_POLLS + POLLFDS_BASE; /* conf->sequence_nr will be 1 when uxsock_listen is first called */ unsigned int sequence_nr = 0; struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1, .mp_wd = -1, }; struct vectors *vecs = trigger_data; condlog(3, "uxsock: startup listener"); polls = calloc(1, max_pfds * sizeof(*polls)); if (!polls) { condlog(0, "uxsock: failed to allocate poll fds"); exit_daemon(); return NULL; } notify_fd = inotify_init1(IN_NONBLOCK); if (notify_fd == -1) /* it's fine if notifications fail */ condlog(3, "failed to start up configuration notifications"); pthread_cleanup_push(wakeup_cleanup, &vecs->lock); idle_fd = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC); if (idle_fd == -1) { condlog(1, "failed to create idle fd"); exit_daemon(); } else set_wakeup_fn(&vecs->lock, wakeup_listener); sigfillset(&mask); sigdelset(&mask, SIGINT); sigdelset(&mask, SIGTERM); sigdelset(&mask, SIGHUP); sigdelset(&mask, SIGUSR1); while (1) { struct client *c, *tmp; int i, n_pfds, poll_count, num_clients; struct timespec __timeout, *timeout; /* setup for a poll */ num_clients = 0; list_for_each_entry(c, &clients, node) { num_clients++; } if (num_clients + POLLFDS_BASE > max_pfds) { struct pollfd *new; int n_new = max_pfds + POLLFD_CHUNK; new = realloc(polls, n_new * sizeof(*polls)); if (new) { max_pfds = n_new; polls = new; } else { condlog(1, "%s: realloc failure, %d clients not served", __func__, num_clients + POLLFDS_BASE - max_pfds); num_clients = max_pfds - POLLFDS_BASE; } } if (num_clients < MAX_CLIENTS) { polls[POLLFD_UX].fd = ux_sock; polls[POLLFD_UX].events = POLLIN; } else { /* * New clients can't connect, num_clients won't grow * to MAX_CLIENTS or higher */ condlog(1, "%s: max client connections reached, pausing polling", __func__); polls[POLLFD_UX].fd = -1; } reset_watch(notify_fd, &wds, &sequence_nr); polls[POLLFD_NOTIFY].fd = notify_fd; if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1 && wds.mp_wd == -1)) polls[POLLFD_NOTIFY].events = 0; else polls[POLLFD_NOTIFY].events = POLLIN; polls[POLLFD_IDLE].fd = idle_fd; check_for_locked_work(NULL); if (clients_need_lock) polls[POLLFD_IDLE].events = POLLIN; else polls[POLLFD_IDLE].events = 0; /* setup the clients */ i = POLLFDS_BASE; list_for_each_entry(c, &clients, node) { switch(c->state) { case CLT_RECV: polls[i].events = POLLIN; break; case CLT_SEND: polls[i].events = POLLOUT; break; default: /* don't poll for this client */ continue; } polls[i].fd = c->fd; i++; if (i >= max_pfds) break; } n_pfds = i; timeout = get_soonest_timeout(&__timeout); /* most of our life is spent in this call */ poll_count = ppoll(polls, n_pfds, timeout, &mask); handle_signals(false); if (poll_count == -1) { if (errno == EINTR) { handle_signals(true); continue; } /* something went badly wrong! */ condlog(0, "uxsock: poll failed with %d", errno); exit_daemon(); break; } if (polls[POLLFD_IDLE].fd != -1 && polls[POLLFD_IDLE].revents & POLLIN) drain_idle_fd(idle_fd); /* see if a client needs handling */ list_for_each_entry_safe(c, tmp, &clients, node) { short revents = 0; for (i = POLLFDS_BASE; i < n_pfds; i++) { if (polls[i].fd == c->fd) { revents = polls[i].revents; break; } } handle_client(c, trigger_data, revents); if (c->error == -ECONNRESET) { condlog(4, "cli[%d]: disconnected", c->fd); dead_client(c); if (i < n_pfds) polls[i].fd = -1; } } /* see if we got a non-fatal signal */ handle_signals(true); /* see if we got a new client */ if (polls[POLLFD_UX].revents & POLLIN) { new_client(ux_sock); } /* handle inotify events on config files */ if (polls[POLLFD_NOTIFY].revents & POLLIN) handle_inotify(notify_fd, &wds); } pthread_cleanup_pop(1); return NULL; } multipath-tools-0.11.1/multipathd/uxlsnr.h000066400000000000000000000003151475246302400206240ustar00rootroot00000000000000#ifndef UXLSNR_H_INCLUDED #define UXLSNR_H_INCLUDED #include bool waiting_clients(void); void uxsock_cleanup(void *arg); void *uxsock_listen(long ux_sock, void * trigger_data); #endif multipath-tools-0.11.1/multipathd/waiter.c000066400000000000000000000120441475246302400205610ustar00rootroot00000000000000/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include #include #include "util.h" #include "vector.h" #include "checkers.h" #include "config.h" #include "structs.h" #include "structs_vec.h" #include "devmapper.h" #include "debug.h" #include "lock.h" #include "waiter.h" #include "main.h" pthread_attr_t waiter_attr; static pthread_mutex_t waiter_lock = PTHREAD_MUTEX_INITIALIZER; static struct event_thread *alloc_waiter (void) { struct event_thread *wp; wp = (struct event_thread *)calloc(1, sizeof(struct event_thread)); return wp; } static void free_waiter (void *data) { struct event_thread *wp = (struct event_thread *)data; if (wp->dmt) dm_task_destroy(wp->dmt); rcu_unregister_thread(); free(wp); } void stop_waiter_thread (struct multipath *mpp) { pthread_t thread; if (mpp->waiter == (pthread_t)0) { condlog(3, "%s: event checker thread already stopped", mpp->alias); return; } /* Don't cancel yourself. setup_multipath is called by by the waiter thread, and may remove a multipath device */ if (pthread_equal(mpp->waiter, pthread_self())) return; condlog(3, "%s: stop event checker thread (%lu)", mpp->alias, (unsigned long)mpp->waiter); thread = mpp->waiter; mpp->waiter = (pthread_t)0; pthread_cleanup_push(cleanup_mutex, &waiter_lock); pthread_mutex_lock(&waiter_lock); pthread_kill(thread, SIGUSR2); pthread_cancel(thread); pthread_cleanup_pop(1); } /* * returns the reschedule delay * negative means *stop* */ static int waiteventloop (struct event_thread *waiter) { sigset_t set, oldset; int event_nr; int r; if (!waiter->event_nr) waiter->event_nr = dm_geteventnr(waiter->mapname); if (!(waiter->dmt = libmp_dm_task_create(DM_DEVICE_WAITEVENT))) { condlog(0, "%s: devmap event #%i dm_task_create error", waiter->mapname, waiter->event_nr); return 1; } if (!dm_task_set_name(waiter->dmt, waiter->mapname)) { condlog(0, "%s: devmap event #%i dm_task_set_name error", waiter->mapname, waiter->event_nr); dm_task_destroy(waiter->dmt); waiter->dmt = NULL; return 1; } if (waiter->event_nr && !dm_task_set_event_nr(waiter->dmt, waiter->event_nr)) { condlog(0, "%s: devmap event #%i dm_task_set_event_nr error", waiter->mapname, waiter->event_nr); dm_task_destroy(waiter->dmt); waiter->dmt = NULL; return 1; } /* wait */ sigemptyset(&set); sigaddset(&set, SIGUSR2); pthread_sigmask(SIG_UNBLOCK, &set, &oldset); pthread_testcancel(); r = libmp_dm_task_run(waiter->dmt); if (!r) dm_log_error(2, DM_DEVICE_WAITEVENT, waiter->dmt); pthread_testcancel(); pthread_sigmask(SIG_SETMASK, &oldset, NULL); dm_task_destroy(waiter->dmt); waiter->dmt = NULL; if (!r) { /* wait interrupted by signal. check for cancellation */ pthread_cleanup_push(cleanup_mutex, &waiter_lock); pthread_mutex_lock(&waiter_lock); pthread_testcancel(); pthread_cleanup_pop(1); return 1; /* If we weren't cancelled, just reschedule */ } waiter->event_nr++; /* * upon event ... */ while (1) { condlog(3, "%s: devmap event #%i", waiter->mapname, waiter->event_nr); /* * event might be : * * 1) a table reload, which means our mpp structure is * obsolete : refresh it through update_multipath() * 2) a path failed by DM : mark as such through * update_multipath() * 3) map has gone away : stop the thread. * 4) a path reinstate : nothing to do * 5) a switch group : nothing to do */ pthread_cleanup_push(cleanup_lock, &waiter->vecs->lock); lock(&waiter->vecs->lock); pthread_testcancel(); r = update_multipath(waiter->vecs, waiter->mapname); lock_cleanup_pop(waiter->vecs->lock); if (r) { condlog(2, "%s: event checker exit", waiter->mapname); return -1; /* stop the thread */ } event_nr = dm_geteventnr(waiter->mapname); if (waiter->event_nr == event_nr) return 1; /* upon problem reschedule 1s later */ waiter->event_nr = event_nr; } return -1; /* never reach there */ } static void *waitevent (void *et) { int r; struct event_thread *waiter; mlockall(MCL_CURRENT | MCL_FUTURE); waiter = (struct event_thread *)et; pthread_cleanup_push(free_waiter, et); rcu_register_thread(); while (1) { r = waiteventloop(waiter); if (r < 0) break; sleep(r); } pthread_cleanup_pop(1); return NULL; } int start_waiter_thread (struct multipath *mpp, struct vectors *vecs) { struct event_thread *wp; if (!mpp) return 0; wp = alloc_waiter(); if (!wp) goto out; strlcpy(wp->mapname, mpp->alias, WWID_SIZE); wp->vecs = vecs; if (pthread_create(&wp->thread, &waiter_attr, waitevent, wp)) { condlog(0, "%s: cannot create event checker", wp->mapname); goto out1; } mpp->waiter = wp->thread; condlog(3, "%s: event checker started", wp->mapname); return 0; out1: free_waiter(wp); mpp->waiter = (pthread_t)0; out: condlog(0, "failed to start waiter thread"); return 1; } multipath-tools-0.11.1/multipathd/waiter.h000066400000000000000000000005641475246302400205720ustar00rootroot00000000000000#ifndef WAITER_H_INCLUDED #define WAITER_H_INCLUDED extern pthread_attr_t waiter_attr; struct event_thread { struct dm_task *dmt; pthread_t thread; int event_nr; char mapname[WWID_SIZE]; struct vectors *vecs; }; void stop_waiter_thread (struct multipath *mpp); int start_waiter_thread (struct multipath *mpp, struct vectors *vecs); #endif /* WAITER_H_INCLUDED */ multipath-tools-0.11.1/rules.mk000066400000000000000000000011201475246302400164230ustar00rootroot00000000000000# Copyright (c) SUSE LLC # SPDX-License-Identifier: GPL-2.0-or-later $(DEVLIB): $(LIBS) $(Q)$(LN) $(LIBS) $@ $(LIBS): $(OBJS) $(VERSION_SCRIPT) $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ \ -Wl,--version-script=$(VERSION_SCRIPT) -o $@ $(OBJS) $(LIBDEPS) $(LIBS:%.so.$(SONAME)=%-nv.so): $(OBJS) $(NV_VERSION_SCRIPT) $(Q)$(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ \ -Wl,--version-script=$(NV_VERSION_SCRIPT) -o $@ $(OBJS) $(LIBDEPS) abi: $(LIBS:%.so.$(SONAME)=%-nv.abi) $(TOPDIR)/config.mk $(multipathdir)/autoconfig.h: $(Q)$(MAKE) -C $(TOPDIR) -f create-config.mk multipath-tools-0.11.1/tests/000077500000000000000000000000001475246302400161105ustar00rootroot00000000000000multipath-tools-0.11.1/tests/Makefile000066400000000000000000000112701475246302400175510ustar00rootroot00000000000000include ../Makefile.inc # directory where to run the tests TESTDIR := $(CURDIR) CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) -I$(daemondir) \ -DTESTCONFDIR=\"$(TESTDIR)/conf.d\" CFLAGS += $(BIN_CFLAGS) -Wno-unused-parameter $(W_MISSING_INITIALIZERS) 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 HELPERS := test-lib.o test-log.o .PRECIOUS: $(TESTS:%=%-test) all: $(TESTS:%=%.out) progs: $(TESTS:%=%-test) lib/libchecktur.so valgrind: $(TESTS:%=%.vgr) # test-specific compiler flags # XYZ-test_FLAGS: Additional compiler flags for this test mpathvalid-test_FLAGS := -I$(mpathvaliddir) features-test_FLAGS := -I$(multipathdir)/nvme # test-specific linker flags # XYZ-test_TESTDEPS: test libraries containing __wrap_xyz functions # XYZ-test_OBJDEPS: object files from libraries to link in explicitly # That may be necessary if functions called from the object file are wrapped # (wrapping works only for symbols which are undefined after processing a # linker input file). # Some object files, e.g. "config.o", are compiled separately for the # unit tests. Look for OBJS-U in libmultipath/Makefile. Make sure to use the # unit test file, e.g. "config-test.o", in XYZ-test_OBJDEPS # XYZ-test_LIBDEPS: Additional libs to link for this test dmevents-test_OBJDEPS = $(multipathdir)/devmapper.o dmevents-test_LIBDEPS = -lpthread -ldevmapper -lurcu hwtable-test_TESTDEPS := test-lib.o hwtable-test_OBJDEPS := $(multipathdir)/discovery.o $(multipathdir)/blacklist.o \ $(multipathdir)/structs_vec.o $(multipathdir)/structs.o $(multipathdir)/propsel.o hwtable-test_LIBDEPS := -ludev -lpthread -ldl blacklist-test_TESTDEPS := test-log.o blacklist-test_LIBDEPS := -ludev vpd-test_OBJDEPS := $(multipathdir)/discovery.o vpd-test_LIBDEPS := -ludev -lpthread -ldl alias-test_TESTDEPS := test-log.o alias-test_OBJDEPS := $(mpathutildir)/util.o alias-test_LIBDEPS := -ludev -lpthread -ldl valid-test_OBJDEPS := $(multipathdir)/valid.o $(multipathdir)/discovery.o valid-test_LIBDEPS := -lmount -ludev -lpthread -ldl devt-test_LIBDEPS := -ludev mpathvalid-test_LIBDEPS := -ludev -lpthread -ldl mpathvalid-test_OBJDEPS := $(mpathvaliddir)/mpath_valid.o directio-test_LIBDEPS := -laio strbuf-test_OBJDEPS := $(mpathutildir)/strbuf.o sysfs-test_TESTDEPS := test-log.o sysfs-test_OBJDEPS := $(multipathdir)/sysfs.o $(mpathutildir)/util.o sysfs-test_LIBDEPS := -ludev -lpthread -ldl features-test_LIBDEPS := -ludev -lpthread cli-test_OBJDEPS := $(daemondir)/cli.o mapinfo-test_LIBDEPS = -lpthread -ldevmapper %.o: %.c @echo building $@ because of $? $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $($*-test_FLAGS) -c -o $@ $< lib/libchecktur.so: @mkdir -p lib $(Q)cd lib && ln -s ../$(multipathdir)/*/*.so . %.out: %-test lib/libchecktur.so @echo == running $< == @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 OBJS = $(TESTS:%=%.o) $(HELPERS) test_clean: $(Q)$(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so* valgrind_clean: $(Q)$(RM) $(TESTS:%=%.vgr) clean: test_clean valgrind_clean dep_clean $(Q)$(RM) $(TESTS:%=%-test) $(OBJS) *.o.wrap $(Q)$(RM) -rf lib conf.d .SECONDARY: $(OBJS) $(foreach T,$(TESTS),$($T-test_OBJDEPS)) $(HELPERS:%=%.wrap) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) # Parse the C code for __wrap_xyz() functions and generate linker options from them. # See comment in wrap64.h %.o.wrap: %.c $(Q)$(CC) $(OPTFLAGS) $(CPPFLAGS) $($*-test_FLAGS) -E $< | \ sed -n 's/^.*__wrap_\([a-zA-Z0-9_]*\).*$$/-Wl,--wrap=\1/p' | \ sort -u | tr '\n' ' ' >$@ # Pass the original values of CFLAGS etc. to the sub-make, which will include # Makefile.in again. Otherwise, the flags would be added twice. libmultipath.so.0: $(multipathdir)/libmultipath.so.0 @CFLAGS="$(ORIG_CFLAGS)" CPPFLAGS="$(ORIG_CPPFLAGS)" LDFLAGS="$(ORIG_LDFLAGS)" \ $(MAKE) -C $(multipathdir) configdir=$(TESTDIR)/conf.d plugindir=$(TESTDIR)/lib test-lib # COLON will get expanded during second expansion below COLON:=: .SECONDEXPANSION: %-test: %.o %.o.wrap $$($$@_OBJDEPS) $$($$@_TESTDEPS) $$($$@_TESTDEPS$$(COLON).o=.o.wrap) \ libmultipath.so.0 $(mpathutildir)/libmpathutil.so.0 $(mpathcmddir)/libmpathcmd.so.0 Makefile @echo building $@ $(Q)$(CC) $(CFLAGS) -o $@ $(LDFLAGS) $< $($@_TESTDEPS) $($@_OBJDEPS) \ $(LIBDEPS) $($@_LIBDEPS) \ $(shell cat $<.wrap) $(foreach dep,$($@_TESTDEPS),$(shell cat $(dep).wrap)) multipath-tools-0.11.1/tests/README.md000066400000000000000000000111331475246302400173660ustar00rootroot00000000000000# multipath-tools unit tests Unit tests are built and run by running `make test` in the top directory, or simply `make` in the `tests` subdirectory. The test output is saved as `.out`. The test programs are called `-test`, and can be run standalone e.g. for debugging purposes. ## Running tests under valgrind The unit tests can be run under the valgrind debugger with `make valgrind` in the `tests` directory, or `make valgrind-test` in the top directory. If valgrind detects a bad memory access or leak, the test will fail. The output of the test run, including valgrind output, is stored as `.vgr`. ## Running tests manually `make test` or `make -C test "$TEST.out"` will only run the test program if the output files `$TEST.out` don't exist yet. To re-run the test, delete the output file first. In order to run a test outside `make`, set the library search path: cd tests export LD_LIBRARY_PATH=.:../libmpathutil:../libmpathcmd ./dmevents-test # or whatever other test you want to run ## Controlling verbosity for unit tests Some test programs use the environment variable `MPATHTEST_VERBOSITY` to control the log level during test execution. ## Notes on individual tests ### Tests that require root permissions The following tests must be run as root, otherwise some test items will be skipped because of missing permissions, or the test will fail outright: * `dmevents` * `directio` (if `DIO_TEST_DEV` is set, see below) To run these tests, after building the tests as non-root user, change to the `tests` directory and run `make test-clean`; then run `make` again as root. ### directio test This test includes test items that require a access to a block device. The device will be opened in read-only mode; you don't need to worry about data loss. However, the user needs to specify a device to be used. Set the environment variable `DIO_TEST_DEV` to the path of the device. After that, run `make directio.out` as root in the `tests` directory to perform the test. With a real test device, the test results may note be 100% reproducible, and sporadic test failures may occur under certain circumstances. It may be necessary to introduce a certain delay between test operations. To do so, set the environment variable `DIO_TEST_DELAY` to a positive integer that determines the delay (in microseconds) after each `io_submit()` operation. The default delay is 10 microseconds. *Note:* `DIO_TEST_DEV` doesn't have to be set during compilation of `directio-test`. This used to be the case in previous versions of multipath-tools. Previously, it was possible to set `DIO_TEST_DEV` in a file `tests/directio_test_dev`. This is not supported any more. ## Adding tests The unit tests are based on the [cmocka test framework](https://cmocka.org/), and make use of cmocka's "mock objects" feature to simulate how the code behaves for different input values. cmocka achieves this by modifying the symbol lookup at link time, substituting "wrapper functions" for the originally called function. The Makefile contains code to make sure that `__wrap_xyz()` wrapper functions are automatically passed to the linker with matching `-Wl,--wrap` command line arguments, so that tests are correctly rebuilt if wrapper functions are added or removed. ### Making sure symbol wrapping works: OBJDEPS Special care must be taken to wrap function calls inside a library. Suppose you want to wrap a function which is both defined in libmultipath and called from other functions in libmultipath, such as `checker_check()`. When `libmultipath.so` is created, the linker resolves calls to `checker_check()` inside the `.so` file. When later the test executable is built by linking the test object file with `libmultipath.so`, these calls can't be wrapped anymore, because they've already been resolved, and wrapping works only for *unresolved* symbols. Therefore, object files from libraries that contain calls to functions which need to be wrapped must be explicitly listed on the linker command line in order to make the wrapping work. To enforce this, add these object files to the `xyz-test_OBJDEPS` variable in the Makefile. ### Using wrapper function libraries: TESTDEPS Some wrapper functions are useful in multiple tests. These are maintained in separate input files, such as `test-lib.c` or `test-log.c`. List these files in the `xyz-test_TESTDEPS` variable for your test program if you need these wrappers. ### Specifying library dependencies: LIBDEPS In order to keep the tests lean, not all libraries that libmultipath normally pulls in are used for every test. Add libraries you need (such as `-lpthread`) to the `xyz-test_LIBDEPS` variable. multipath-tools-0.11.1/tests/alias.c000066400000000000000000001630171475246302400173550ustar00rootroot00000000000000#include #include #include #include #include #include "strbuf.h" #include "util.h" #include "alias.h" #include "test-log.h" #include #include #include "wrap64.h" #include "globals.c" #include "../libmultipath/alias.c" /* For verbose printing of all aliases in the ordering tests */ #define ALIAS_DEBUG 0 #if INT_MAX == 0x7fffffff /* user_friendly_name for map #INT_MAX */ #define MPATH_ID_INT_MAX "fxshrxw" /* ... and one less */ #define MPATH_ID_INT_MAX_m1 "fxshrxv" /* ... and one more */ #define MPATH_ID_INT_MAX_p1 "fxshrxx" #endif static int set_errno__(int err) { if (err >= 0) { errno = 0; return err; } else { errno = -err; return -1; } } /* * allocate_binding -> write_bindings_file() writes the entire file, i.e. the * header, any preexisting bindings, and the new binding. The complete content * depends on history and is different to predict here. Therefore we check only * the newly added binding. Because add_binding() sorts entries, this new * binding isn't necessarily the last one; receive it from will_return() and * search for it with strstr(). * If the string to be written doesn't start with the bindings file * header, it's a test of a partial write. */ ssize_t __wrap_write(int fd, const void *buf, size_t count) { const char *binding, *start; #if DEBUG_WRITE fprintf(stderr, "%s: %zx exp %zx\n===\n%s\n===\n", __func__, strlen(buf), count, (const char *)buf); #endif if (!strncmp((const char *)buf, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1)) start = (const char *)buf + sizeof(BINDINGS_FILE_HEADER) - 1; else start = buf; binding = mock_ptr_type(char *); start = strstr(start, binding); check_expected(count); assert_ptr_not_equal(start, NULL); return set_errno__(mock_type(int)); } int __wrap_rename(const char *old, const char *new) { return set_errno__(mock_type(int)); } int WRAP_FUNC(mkstemp)(char *template) { return 10; } int __wrap_dm_get_wwid(const char *name, char *uuid, int uuid_len) { int ret; check_expected(name); check_expected(uuid_len); assert_non_null(uuid); ret = mock_type(int); if (ret == DMP_OK) strcpy(uuid, mock_ptr_type(char *)); return ret; } static int lock_errors; static int bindings_locked; static int timestamp_locked; int __wrap_pthread_mutex_lock(pthread_mutex_t *mutex) { if (mutex == &bindings_mutex) { if (bindings_locked) { fprintf(stderr, "%s: bindings_mutex LOCKED\n", __func__); lock_errors++; } bindings_locked = 1; } else if (mutex == ×tamp_mutex) { if (timestamp_locked) { fprintf(stderr, "%s: timestamp_mutex LOCKED\n", __func__); lock_errors++; } timestamp_locked = 1; } else fprintf(stderr, "%s called for unknown mutex %p\n", __func__, mutex); return 0; } int __wrap_pthread_mutex_unlock(pthread_mutex_t *mutex) { if (mutex == &bindings_mutex) { if (!bindings_locked) { fprintf(stderr, "%s: bindings_mutex UNLOCKED\n", __func__); lock_errors++; } bindings_locked = 0; } else if (mutex == ×tamp_mutex) { if (!timestamp_locked) { fprintf(stderr, "%s: timestamp_mutex UNLOCKED\n", __func__); lock_errors++; } timestamp_locked = 0; } else fprintf(stderr, "%s called for unknown mutex %p\n", __func__, mutex); return 0; } #define TEST_FDNO 1234 #define TEST_FPTR ((FILE *) 0xaffe) /* strbuf wrapper for the old format_devname() */ static int format_devname__(char *name, int id, size_t len, const char *prefix) { STRBUF_ON_STACK(buf); if (append_strbuf_str(&buf, prefix) < 0 || format_devname(&buf, id) < 0 || len <= get_strbuf_len(&buf)) return -1; strcpy(name, get_strbuf_str(&buf)); return get_strbuf_len(&buf); } static void fd_mpatha(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 1, sizeof(buf), "FOO"); assert_int_equal(rc, 4); assert_string_equal(buf, "FOOa"); } static void fd_mpathz(void **state) { /* This also tests a "short" buffer, see fd_mpath_short1 */ char buf[5]; int rc; rc = format_devname__(buf, 26, sizeof(buf), "FOO"); assert_int_equal(rc, 4); assert_string_equal(buf, "FOOz"); } static void fd_mpathaa(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26 + 1, sizeof(buf), "FOO"); assert_int_equal(rc, 5); assert_string_equal(buf, "FOOaa"); } static void fd_mpathzz(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26*26 + 26, sizeof(buf), "FOO"); assert_int_equal(rc, 5); assert_string_equal(buf, "FOOzz"); } static void fd_mpathaaa(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26*26 + 27, sizeof(buf), "FOO"); assert_int_equal(rc, 6); assert_string_equal(buf, "FOOaaa"); } static void fd_mpathzzz(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26*26*26 + 26*26 + 26, sizeof(buf), "FOO"); assert_int_equal(rc, 6); assert_string_equal(buf, "FOOzzz"); } static void fd_mpathaaaa(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26*26*26 + 26*26 + 27, sizeof(buf), "FOO"); assert_int_equal(rc, 7); assert_string_equal(buf, "FOOaaaa"); } static void fd_mpathzzzz(void **state) { char buf[32]; int rc; rc = format_devname__(buf, 26*26*26*26 + 26*26*26 + 26*26 + 26, sizeof(buf), "FOO"); assert_int_equal(rc, 7); assert_string_equal(buf, "FOOzzzz"); } #ifdef MPATH_ID_INT_MAX static void fd_mpath_max(void **state) { char buf[32]; int rc; rc = format_devname__(buf, INT_MAX, sizeof(buf), ""); assert_int_equal(rc, strlen(MPATH_ID_INT_MAX)); assert_string_equal(buf, MPATH_ID_INT_MAX); } #endif static void fd_mpath_max1(void **state) { char buf[32]; int rc; rc = format_devname__(buf, INT_MIN, sizeof(buf), ""); assert_int_equal(rc, -1); } static void fd_mpath_short(void **state) { char buf[4]; int rc; rc = format_devname__(buf, 1, sizeof(buf), "FOO"); assert_int_equal(rc, -1); } static void fd_mpath_short1(void **state) { char buf[5]; int rc; rc = format_devname__(buf, 27, sizeof(buf), "FOO"); assert_int_equal(rc, -1); } static int test_format_devname(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(fd_mpatha), cmocka_unit_test(fd_mpathz), cmocka_unit_test(fd_mpathaa), cmocka_unit_test(fd_mpathzz), cmocka_unit_test(fd_mpathaaa), cmocka_unit_test(fd_mpathzzz), cmocka_unit_test(fd_mpathaaaa), cmocka_unit_test(fd_mpathzzzz), #ifdef MPATH_ID_INT_MAX cmocka_unit_test(fd_mpath_max), #endif cmocka_unit_test(fd_mpath_max1), cmocka_unit_test(fd_mpath_short), cmocka_unit_test(fd_mpath_short1), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void sd_mpatha(void **state) { int rc = scan_devname("MPATHa", "MPATH"); assert_int_equal(rc, 1); } /* * Text after whitespace is ignored. But an overlong input * errors out, even if it's just whitespace. * It's kind of strange that scan_devname() treats whitespace * like this. But I'm not sure if some corner case depends * on this behavior. */ static void sd_mpatha_spc(void **state) { int rc = scan_devname("MPATHa 00", "MPATH"); assert_int_equal(rc, 1); } static void sd_mpatha_tab(void **state) { int rc = scan_devname("MPATHa\t00", "MPATH"); assert_int_equal(rc, 1); } static void sd_overlong(void **state) { int rc = scan_devname("MPATHa ", "MPATH"); assert_int_equal(rc, -1); } static void sd_overlong1(void **state) { int rc = scan_devname("MPATHabcdefgh", "MPATH"); assert_int_equal(rc, -1); } static void sd_noprefix(void **state) { int rc = scan_devname("MPATHa", NULL); assert_int_equal(rc, -1); } static void sd_nomatchprefix(void **state) { int rc = scan_devname("MPATHa", "mpath"); assert_int_equal(rc, -1); } static void sd_eq_prefix(void **state) { int rc = scan_devname("MPATH", "MPATH"); assert_int_equal(rc, -1); } static void sd_bad_1(void **state) { int rc = scan_devname("MPATH0", "MPATH"); assert_int_equal(rc, -1); } static void sd_bad_2(void **state) { int rc = scan_devname("MPATHa0c", "MPATH"); assert_int_equal(rc, -1); } #ifdef MPATH_ID_INT_MAX static void sd_max(void **state) { int rc = scan_devname("MPATH" MPATH_ID_INT_MAX, "MPATH"); assert_int_equal(rc, INT_MAX); } static void sd_max_p1(void **state) { int rc = scan_devname("MPATH" MPATH_ID_INT_MAX_p1, "MPATH"); assert_int_equal(rc, -1); } #endif static void sd_fd_many(void **state) { char buf[32]; int rc, i; for (i = 1; i < 5000; i++) { rc = format_devname__(buf, i, sizeof(buf), "MPATH"); assert_in_range(rc, 6, 8); rc = scan_devname(buf, "MPATH"); assert_int_equal(rc, i); } } static void sd_fd_random(void **state) { char buf[32]; int rc, i, n; srandom(1); for (i = 1; i < 1000; i++) { n = random() & 0xffff; rc = format_devname__(buf, n, sizeof(buf), "MPATH"); assert_in_range(rc, 6, 9); rc = scan_devname(buf, "MPATH"); assert_int_equal(rc, n); } } static int test_scan_devname(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(sd_mpatha), cmocka_unit_test(sd_mpatha_spc), cmocka_unit_test(sd_mpatha_tab), cmocka_unit_test(sd_overlong), cmocka_unit_test(sd_overlong1), cmocka_unit_test(sd_noprefix), cmocka_unit_test(sd_nomatchprefix), cmocka_unit_test(sd_eq_prefix), cmocka_unit_test(sd_bad_1), cmocka_unit_test(sd_bad_2), #ifdef MPATH_ID_INT_MAX cmocka_unit_test(sd_max), cmocka_unit_test(sd_max_p1), #endif cmocka_unit_test(sd_fd_many), cmocka_unit_test(sd_fd_random), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void mock_unused_alias(const char *alias) { expect_string(__wrap_dm_get_wwid, name, alias); expect_value(__wrap_dm_get_wwid, uuid_len, WWID_SIZE); will_return(__wrap_dm_get_wwid, DMP_NOT_FOUND); } static void mock_self_alias(const char *alias, const char *wwid) { expect_string(__wrap_dm_get_wwid, name, alias); expect_value(__wrap_dm_get_wwid, uuid_len, WWID_SIZE); will_return(__wrap_dm_get_wwid, DMP_OK); will_return(__wrap_dm_get_wwid, wwid); } #define USED_STR(alias_str, wwid_str) wwid_str ": alias '" alias_str "' already taken, reselecting alias\n" #define NOMATCH_STR(alias_str) ("No matching alias [" alias_str "] in bindings file.\n") #define FOUND_STR(alias_str, wwid_str) \ "Found matching wwid [" wwid_str "] in bindings file." \ " Setting alias to " alias_str "\n" #define FOUND_ALIAS_STR(alias_str, wwid_str) \ "Found matching alias [" alias_str "] in bindings file." \ " Setting wwid to " wwid_str "\n" #define NOMATCH_WWID_STR(wwid_str) ("No matching wwid [" wwid_str "] in bindings file.\n") #define NEW_STR(alias_str, wwid_str) ("Created new binding [" alias_str "] for WWID [" wwid_str "]\n") #define EXISTING_STR(alias_str, wwid_str) ("Use existing binding [" alias_str "] for WWID [" wwid_str "]\n") #define ALLOC_STR(alias_str, wwid_str) ("Allocated existing binding [" alias_str "] for WWID [" wwid_str "]\n") #define BINDING_STR(alias_str, wwid_str) (alias_str " " wwid_str "\n") #define BOUND_STR(alias_str, wwid_str) ("alias "alias_str " already bound to wwid " wwid_str ", cannot reuse") #define ERR_STR(alias_str, wwid_str) ("ERROR: old alias [" alias_str "] for wwid [" wwid_str "] is used by other map\n") #define REUSE_STR(alias_str, wwid_str) ("alias " alias_str " already bound to wwid " wwid_str ", cannot reuse\n") #define NOMORE_STR "no more available user_friendly_names\n" #define mock_failed_alias(alias, wwid) \ do { \ expect_string(__wrap_dm_get_wwid, name, alias); \ expect_value(__wrap_dm_get_wwid, uuid_len, WWID_SIZE); \ will_return(__wrap_dm_get_wwid, DMP_NOT_FOUND); \ } while (0) #define mock_used_alias(alias, wwid) \ do { \ expect_string(__wrap_dm_get_wwid, name, alias); \ expect_value(__wrap_dm_get_wwid, uuid_len, WWID_SIZE); \ will_return(__wrap_dm_get_wwid, DMP_OK); \ will_return(__wrap_dm_get_wwid, "WWID_USED"); \ expect_condlog(3, USED_STR(alias, wwid)); \ } while(0) static void mock_bindings_file__(const char *content, bool conflict_ok) { char *cnt __attribute__((cleanup(cleanup_charp))) = NULL; char *token, *savep = NULL; int i; uintmax_t values[] = { BINDING_ADDED, BINDING_CONFLICT }; cnt = strdup(content); assert_ptr_not_equal(cnt, NULL); for (token = strtok_r(cnt, "\n", &savep), i = 0; token && *token; token = strtok_r(NULL, "\n", &savep), i++) { char *alias, *wwid; int rc; if (read_binding(token, i + 1, &alias, &wwid) == READ_BINDING_SKIP) continue; rc = add_binding(&global_bindings, alias, wwid); assert_in_set(rc, values, conflict_ok ? 2 : 1); } } static void mock_bindings_file(const char *content) { return mock_bindings_file__(content, false); } static int teardown_bindings(void **state) { cleanup_bindings(); return 0; } static int lookup_binding(FILE *dummy, const char *wwid, char **alias, const char *prefix, int check_if_taken) { const struct binding *bdg; int id; /* * get_free_id() always checks if aliases are taken. * Therefore if prefix is non-null, check_if_taken must be true. */ assert_true(!prefix || check_if_taken); *alias = NULL; bdg = get_binding_for_wwid(&global_bindings, wwid); if (bdg) { *alias = strdup(bdg->alias); return 0; } else if (!prefix && check_if_taken) return -1; id = get_free_id(&global_bindings, prefix, wwid); return id; } static void lb_empty(void **state) { int rc; char *alias; mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, NULL, 0); assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); } static void lb_empty_unused(void **state) { int rc; char *alias; mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); free(alias); } static void lb_empty_failed(void **state) { int rc; char *alias; mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_failed_alias("MPATHa", "WWID0"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); free(alias); } static void lb_empty_1_used(void **state) { int rc; char *alias; mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); free(alias); } static void lb_empty_1_used_self(void **state) { int rc; char *alias; mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_used_alias("MPATHa", "WWID0"); mock_self_alias("MPATHb", "WWID0"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); free(alias); } static void lb_match_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); free(alias); } static void lb_nomatch_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); mock_unused_alias("MPATHb"); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_bad_check(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, NULL, 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_unused(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_3_used_failed_self(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); mock_used_alias("MPATHb", "WWID1"); mock_used_alias("MPATHc", "WWID1"); mock_used_alias("MPATHd", "WWID1"); mock_failed_alias("MPATHe", "WWID1"); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); } static void do_lb_match_c(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHc WWID1"); expect_condlog(3, FOUND_STR("MPATHc", "WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHc"); free(alias); } static void lb_match_c(void **state) { do_lb_match_c(state); } static void lb_match_c_check(void **state) { do_lb_match_c(state); } static void lb_nomatch_a_c(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHc WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHb"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_d_unused(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHd WWID1"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_d_1_used(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHd WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_d_2_used(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHd WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_unused_alias("MPATHe"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_d_3_used(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHd WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_used_alias("MPATHe", "WWID2"); mock_unused_alias("MPATHf"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); } static void lb_nomatch_c_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHc WWID1\n" "MPATHa WWID0\n"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_d_a_unused(void **state) { int rc; char *alias; mock_bindings_file("MPATHc WWID1\n" "MPATHa WWID0\n" "MPATHd WWID0\n"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_d_a_1_used(void **state) { int rc; char *alias; mock_bindings_file("MPATHc WWID1\n" "MPATHa WWID0\n" "MPATHd WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHe"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_b(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" "MPATHb WWID1\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_b_bad(void **state) { int rc; char *alias; expect_condlog(1, "invalid line 3 in bindings file, missing WWID\n"); /* * The broken line will be ignored when constructing the bindings vector. * Thus in lookup_binding() MPATHb is never encountered, * and MPATHb appears usable. */ mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" "MPATHb\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHb"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_a_b_bad_self(void **state) { int rc; char *alias; expect_condlog(1, "invalid line 3 in bindings file, missing WWID\n"); mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" "MPATHb\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_self_alias("MPATHb", "WWID2"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } static void lb_nomatch_b_z_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" "MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } static void lb_nomatch_b_aa_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" "MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } static void fill_bindings(struct strbuf *buf, int start, int end) { int i; for (i = start; i <= end; i++) { print_strbuf(buf, "MPATH"); format_devname(buf, i + 1); print_strbuf(buf, " WWID%d\n", i); } } static void lb_nomatch_b_a_aa(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID28")); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWID28", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_b_a_aa_zz(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATHzz WWID676\n"); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID703")); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWID703", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_b_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHb WWID1\n" "MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } static void lb_nomatch_b_a_3_used(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID31")); mock_used_alias("MPATHab", "WWID31"); mock_used_alias("MPATHac", "WWID31"); mock_used_alias("MPATHad", "WWID31"); mock_unused_alias("MPATHae"); rc = lookup_binding(NULL, "WWID31", &alias, "MPATH", 1); assert_int_equal(rc, 31); assert_ptr_equal(alias, NULL); } #ifdef MPATH_ID_INT_MAX /* * The bindings will be sorted by alias. Therefore we have no chance to * simulate a "full" table. */ static void lb_nomatch_int_max(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATH%s WWIDMAX\n", MPATH_ID_INT_MAX); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_used(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 1, 26); print_strbuf(&buf, "MPATH%s WWIDMAX\n", MPATH_ID_INT_MAX); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); mock_used_alias("MPATHa", "WWIDNOMORE"); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_m1(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_m1_used(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_used_alias("MPATHab", "WWIDMAX"); mock_unused_alias("MPATHac"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, 29); assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_m1_1_used(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 1, 26); print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_used_alias("MPATHa", "WWIDMAX"); mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_m1_2_used(void **state) { int rc; char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 1, 26); print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_used_alias("MPATHa", "WWIDMAX"); mock_used_alias("MPATHab", "WWIDMAX"); mock_unused_alias("MPATHac"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, 29); assert_ptr_equal(alias, NULL); } #endif static int test_lookup_binding(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_teardown(lb_empty, teardown_bindings), cmocka_unit_test_teardown(lb_empty_unused, teardown_bindings), cmocka_unit_test_teardown(lb_empty_failed, teardown_bindings), cmocka_unit_test_teardown(lb_empty_1_used, teardown_bindings), cmocka_unit_test_teardown(lb_empty_1_used_self, teardown_bindings), cmocka_unit_test_teardown(lb_match_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_bad_check, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_unused, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_3_used_failed_self, teardown_bindings), cmocka_unit_test_teardown(lb_match_c, teardown_bindings), cmocka_unit_test_teardown(lb_match_c_check, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_c, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_d_unused, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_d_1_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_d_2_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_d_3_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_c_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_d_a_unused, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_d_a_1_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_b, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_b_bad, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_a_b_bad_self, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_z_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_aa_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_aa, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_aa_zz, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_3_used, teardown_bindings), #ifdef MPATH_ID_INT_MAX cmocka_unit_test_teardown(lb_nomatch_int_max, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_int_max_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_int_max_m1, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_int_max_m1_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_int_max_m1_1_used, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_int_max_m1_2_used, teardown_bindings), #endif }; return cmocka_run_group_tests(tests, NULL, NULL); } static int rlookup_binding(FILE *dummy, char *buf, const char *alias) { const struct binding *bdg; bdg = get_binding_for_alias(&global_bindings, alias); if (!bdg) { return -1; } strlcpy(buf, bdg->wwid, WWID_SIZE); return 0; } static void rl_empty(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; mock_bindings_file(""); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); } static void rl_match_a(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, FOUND_ALIAS_STR("MPATHa", "WWID0")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, 0); assert_string_equal(buf, "WWID0"); } static void rl_nomatch_a(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_STR("MPATHb")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); } static void rl_malformed_a(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; expect_condlog(1, "invalid line 1 in bindings file, missing WWID\n"); mock_bindings_file("MPATHa \n"); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); } static void rl_overlong_a(void **state) { int rc; char buf[WWID_SIZE]; char line[WWID_SIZE + 10]; snprintf(line, sizeof(line), "MPATHa "); memset(line + strlen(line), 'W', sizeof(line) - 2 - strlen(line)); snprintf(line + sizeof(line) - 2, 2, "\n"); buf[0] = '\0'; expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); mock_bindings_file(line); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); } static void rl_match_b(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" "MPATHb WWID2\n"); expect_condlog(3, FOUND_ALIAS_STR("MPATHb", "WWID2")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, 0); assert_string_equal(buf, "WWID2"); } static int test_rlookup_binding(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_teardown(rl_empty, teardown_bindings), cmocka_unit_test_teardown(rl_match_a, teardown_bindings), cmocka_unit_test_teardown(rl_nomatch_a, teardown_bindings), cmocka_unit_test_teardown(rl_malformed_a, teardown_bindings), cmocka_unit_test_teardown(rl_overlong_a, teardown_bindings), cmocka_unit_test_teardown(rl_match_b, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); } void check_bindings_size(int n) { /* avoid -Waddress problem */ Bindings *bindings = &global_bindings; assert_int_equal(VECTOR_SIZE(bindings), n); } static void al_a(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_rename, 0); expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, NEW_STR("MPATHa", "WWIDa")); alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); check_bindings_size(1); free(alias); } static void al_zz(void **state) { static const char ln[] = "MPATHzz WWIDzz\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_rename, 0); expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, NEW_STR("MPATHzz", "WWIDzz")); alias = allocate_binding("WWIDzz", 26*26 + 26, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHzz"); check_bindings_size(1); free(alias); } static void al_0(void **state) { char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id 0\n"); alias = allocate_binding("WWIDa", 0, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } static void al_m2(void **state) { char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id -2\n"); alias = allocate_binding("WWIDa", -2, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } static void al_write_partial(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln) - 1); expect_value(__wrap_write, count, 1); will_return(__wrap_write, ln + sizeof(ln) - 2); will_return(__wrap_write, 1); will_return(__wrap_rename, 0); expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); check_bindings_size(1); free(alias); } static void al_write_short(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln) - 1); expect_value(__wrap_write, count, 1); will_return(__wrap_write, ln + sizeof(ln) - 2); will_return(__wrap_write, 0); expect_condlog(2, "write_bindings_file: short write"); expect_condlog(1, "failed to write new bindings file"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } static void al_write_err(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, -EPERM); expect_condlog(1, "failed to write new bindings file"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } static void al_rename_err(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_rename, -EROFS); expect_condlog(0, "update_bindings_file: rename: Read-only file system"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } static int test_allocate_binding(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_teardown(al_a, teardown_bindings), cmocka_unit_test_teardown(al_zz, teardown_bindings), cmocka_unit_test_teardown(al_0, teardown_bindings), cmocka_unit_test_teardown(al_m2, teardown_bindings), cmocka_unit_test_teardown(al_write_partial, teardown_bindings), cmocka_unit_test_teardown(al_write_short, teardown_bindings), cmocka_unit_test_teardown(al_write_err, teardown_bindings), cmocka_unit_test_teardown(al_rename_err, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); } #define mock_allocate_binding_err_len(alias, wwid, len, err, msg) \ do { \ static const char ln[] = BINDING_STR(alias, wwid); \ \ expect_value(__wrap_write, count, \ strlen(BINDINGS_FILE_HEADER) + (len) + strlen(ln)); \ will_return(__wrap_write, ln); \ will_return(__wrap_write, \ strlen(BINDINGS_FILE_HEADER) + (len) + strlen(ln)); \ will_return(__wrap_rename, err); \ if (err == 0) { \ expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); \ expect_condlog(3, NEW_STR(alias, wwid)); \ } else { \ expect_condlog(0, "update_bindings_file: rename: " msg "\n"); \ expect_condlog(1, "allocate_binding: deleting binding " \ alias " for " wwid "\n"); \ } \ } while (0) #define mock_allocate_binding_err(alias, wwid, err, msg) \ mock_allocate_binding_err_len(alias, wwid, 0, err, msg) #define mock_allocate_binding(alias, wwid) \ mock_allocate_binding_err(alias, wwid, 0, "") #define mock_allocate_binding_len(alias, wwid, len) \ mock_allocate_binding_err_len(alias, wwid, len, 0, "") static void gufa_empty_new_rw(void **state) { char *alias; mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding("MPATHa", "WWID0"); alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_empty_new_ro_1(void **state) { char *alias; mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding_err("MPATHa", "WWID0", -EROFS, "Read-only file system"); alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_ptr_equal(alias, NULL); } static void gufa_empty_new_ro_2(void **state) { char *alias; mock_bindings_file(""); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHa"); alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_ptr_equal(alias, NULL); } static void gufa_match_a_unused(void **state) { char *alias; mock_bindings_file("MPATHa WWID0"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHa"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_match_a_self(void **state) { char *alias; mock_bindings_file("MPATHa WWID0"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_self_alias("MPATHa", "WWID0"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_match_a_used(void **state) { char *alias; mock_bindings_file("MPATHa WWID0"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_ptr_equal(alias, NULL); } static void gufa_nomatch_a_c(void **state) { char *alias; static const char bindings[] = ("MPATHa WWID0\n" "MPATHc WWID2\n"); mock_bindings_file(bindings); mock_unused_alias("MPATHb"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); mock_allocate_binding_len("MPATHb", "WWID1", strlen(bindings)); alias = get_user_friendly_alias("WWID1", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } static void gufa_nomatch_c_a(void **state) { char *alias; const char bindings[] = ("MPATHc WWID2\n" "MPATHa WWID0\n"); mock_bindings_file(bindings); mock_unused_alias("MPATHb"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); mock_allocate_binding_len("MPATHb", "WWID1", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID1", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } static void gufa_nomatch_c_b(void **state) { char *alias; const char bindings[] = ("MPATHc WWID2\n" "MPATHb WWID1\n"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHa"); mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_nomatch_c_b_used(void **state) { char *alias; const char bindings[] = ("MPATHc WWID2\n" "MPATHb WWID1\n"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID4")); mock_used_alias("MPATHa", "WWID4"); mock_unused_alias("MPATHd"); mock_allocate_binding_len("MPATHd", "WWID4", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID4", "", "MPATH", false); assert_string_equal(alias, "MPATHd"); free(alias); } static void gufa_nomatch_b_f_a(void **state) { char *alias; const char bindings[] = ("MPATHb WWID1\n" "MPATHf WWID6\n" "MPATHa WWID0\n"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID7")); mock_unused_alias("MPATHc"); mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID7", "", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } static void gufa_nomatch_b_aa_a(void **state) { char *alias; STRBUF_ON_STACK(buf); fill_bindings(&buf, 0, 26); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID28")); mock_unused_alias("MPATHab"); mock_allocate_binding_len("MPATHab", "WWID28", get_strbuf_len(&buf)); alias = get_user_friendly_alias("WWID28", "", "MPATH", false); assert_string_equal(alias, "MPATHab"); free(alias); } static void gufa_nomatch_b_f_a_sorted(void **state) { char *alias; const char bindings[] = ("MPATHb WWID1\n" "MPATHf WWID6\n" "MPATHa WWID0\n"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_WWID_STR("WWID7")); mock_unused_alias("MPATHc"); mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID7", "", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } static void gufa_old_empty(void **state) { char *alias; /* rlookup_binding for ALIAS */ mock_bindings_file(""); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_STR("MPATHz")); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding("MPATHz", "WWID0"); expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } static void gufa_old_match(void **state) { char *alias; mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID0"); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID0")); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } static void gufa_old_match_other(void **state) { char *alias; static const char bindings[] = "MPATHz WWID9\n"; mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHa"); mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_old_match_other_used(void **state) { char *alias; static const char bindings[] = "MPATHz WWID9\n"; mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); mock_allocate_binding_len("MPATHb", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } static void gufa_old_match_other_wwidmatch(void **state) { char *alias; static const char bindings[] = ("MPATHz WWID9\n" "MPATHc WWID2"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); mock_unused_alias("MPATHc"); expect_condlog(3, EXISTING_STR("MPATHc", "WWID2")); alias = get_user_friendly_alias("WWID2", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } static void gufa_old_match_other_wwidmatch_used(void **state) { char *alias; static const char bindings[] = ("MPATHz WWID9\n" "MPATHc WWID2"); mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); mock_used_alias("MPATHc", "WWID2"); alias = get_user_friendly_alias("WWID2", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); } static void gufa_old_nomatch_wwidmatch(void **state) { char *alias; static const char bindings[] = "MPATHa WWID0"; mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_STR("MPATHz")); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHa"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } static void gufa_old_nomatch_wwidmatch_used(void **state) { char *alias; static const char bindings[] = "MPATHa WWID0"; mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_STR("MPATHz")); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); } static void gufa_old_nomatch_nowwidmatch(void **state) { char *alias; static const char bindings[] = "MPATHb WWID1\n"; mock_bindings_file(bindings); expect_condlog(4, "_read_bindings_file: bindings are unchanged"); expect_condlog(3, NOMATCH_STR("MPATHz")); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding_len("MPATHz", "WWID0", sizeof(bindings) - 1); expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } static void gufa_check_locking(void **state) { assert_int_equal(lock_errors, 0); } static int test_get_user_friendly_alias(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_teardown(gufa_empty_new_rw, teardown_bindings), cmocka_unit_test_teardown(gufa_empty_new_ro_1, teardown_bindings), cmocka_unit_test_teardown(gufa_empty_new_ro_2, teardown_bindings), cmocka_unit_test_teardown(gufa_match_a_unused, teardown_bindings), cmocka_unit_test_teardown(gufa_match_a_self, teardown_bindings), cmocka_unit_test_teardown(gufa_match_a_used, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_a_c, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_c_a, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_c_b, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_c_b_used, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_b_f_a, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_b_aa_a, teardown_bindings), cmocka_unit_test_teardown(gufa_nomatch_b_f_a_sorted, teardown_bindings), cmocka_unit_test_teardown(gufa_old_empty, teardown_bindings), cmocka_unit_test_teardown(gufa_old_match, teardown_bindings), cmocka_unit_test_teardown(gufa_old_match_other, teardown_bindings), cmocka_unit_test_teardown(gufa_old_match_other_used, teardown_bindings), cmocka_unit_test_teardown(gufa_old_match_other_wwidmatch, teardown_bindings), cmocka_unit_test_teardown(gufa_old_match_other_wwidmatch_used, teardown_bindings), cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch, teardown_bindings), cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch_used, teardown_bindings), cmocka_unit_test_teardown(gufa_old_nomatch_nowwidmatch, teardown_bindings), cmocka_unit_test(gufa_check_locking), }; return cmocka_run_group_tests(tests, NULL, NULL); } /* Numbers 1-1000, randomly shuffled */ static const int random_numbers[1000] = { 694, 977, 224, 178, 841, 818, 914, 549, 831, 942, 263, 834, 919, 800, 111, 517, 719, 297, 988, 98, 332, 516, 754, 772, 495, 488, 331, 529, 142, 747, 848, 618, 375, 624, 74, 753, 782, 944, 623, 468, 862, 997, 417, 258, 298, 774, 673, 904, 883, 766, 867, 400, 11, 950, 14, 784, 655, 155, 396, 9, 743, 93, 651, 245, 968, 306, 785, 581, 880, 486, 168, 631, 203, 4, 663, 294, 702, 762, 619, 684, 48, 181, 21, 443, 643, 863, 1000, 327, 26, 126, 382, 765, 586, 76, 49, 925, 319, 865, 797, 876, 693, 334, 433, 243, 419, 901, 854, 326, 985, 347, 874, 527, 282, 290, 380, 167, 95, 3, 257, 936, 60, 426, 227, 345, 577, 492, 467, 580, 967, 422, 823, 718, 610, 64, 700, 412, 163, 288, 506, 828, 432, 51, 356, 348, 539, 478, 17, 945, 602, 123, 450, 660, 429, 113, 310, 358, 512, 758, 508, 19, 542, 304, 286, 446, 918, 723, 333, 603, 731, 978, 230, 697, 109, 872, 175, 853, 947, 965, 121, 222, 101, 811, 117, 601, 191, 752, 384, 415, 938, 278, 915, 715, 240, 552, 912, 838, 150, 840, 627, 29, 636, 464, 861, 481, 992, 249, 934, 82, 368, 724, 807, 593, 157, 147, 199, 637, 41, 62, 902, 505, 621, 342, 174, 260, 729, 961, 219, 311, 629, 789, 81, 739, 860, 712, 223, 165, 741, 981, 485, 363, 346, 709, 125, 369, 279, 634, 399, 162, 193, 769, 149, 314, 868, 612, 524, 675, 341, 343, 476, 606, 388, 613, 850, 264, 903, 451, 908, 779, 453, 148, 497, 46, 132, 43, 885, 955, 269, 395, 72, 128, 767, 989, 929, 423, 742, 55, 13, 79, 924, 182, 295, 563, 668, 169, 974, 154, 970, 54, 674, 52, 437, 570, 550, 531, 554, 793, 678, 218, 367, 105, 197, 315, 958, 892, 86, 47, 284, 37, 561, 522, 198, 689, 817, 573, 877, 201, 803, 501, 881, 546, 530, 523, 780, 579, 953, 135, 23, 620, 84, 698, 303, 656, 357, 323, 494, 58, 131, 913, 995, 120, 70, 1, 195, 365, 210, 25, 898, 173, 307, 239, 77, 418, 952, 963, 92, 455, 425, 12, 536, 161, 328, 933, 401, 251, 735, 725, 362, 322, 557, 681, 302, 53, 786, 801, 391, 946, 748, 133, 717, 851, 7, 372, 993, 387, 906, 373, 667, 33, 670, 389, 209, 611, 896, 652, 69, 999, 344, 845, 633, 36, 487, 192, 180, 45, 640, 427, 707, 805, 188, 152, 905, 217, 30, 252, 386, 665, 299, 541, 410, 787, 5, 857, 751, 392, 44, 595, 146, 745, 641, 957, 866, 773, 806, 815, 659, 102, 704, 430, 106, 296, 129, 847, 130, 990, 669, 236, 225, 680, 159, 213, 438, 189, 447, 600, 232, 594, 32, 56, 390, 647, 855, 428, 330, 714, 738, 706, 666, 461, 469, 482, 558, 814, 559, 177, 575, 538, 309, 383, 261, 156, 420, 761, 630, 893, 10, 116, 940, 844, 71, 377, 662, 312, 520, 244, 143, 759, 119, 186, 592, 909, 864, 376, 768, 254, 265, 394, 511, 760, 574, 6, 436, 514, 59, 226, 644, 956, 578, 825, 548, 145, 736, 597, 378, 821, 987, 897, 354, 144, 722, 895, 589, 503, 826, 498, 543, 617, 763, 231, 808, 528, 89, 479, 607, 737, 170, 404, 371, 65, 103, 340, 283, 141, 313, 858, 289, 124, 971, 687, 954, 732, 39, 926, 176, 100, 267, 519, 890, 535, 276, 448, 27, 457, 899, 385, 184, 275, 770, 544, 614, 449, 160, 658, 259, 973, 108, 604, 24, 207, 562, 757, 744, 324, 444, 962, 591, 480, 398, 409, 998, 253, 325, 445, 979, 8, 35, 118, 73, 683, 208, 85, 190, 791, 408, 871, 657, 179, 18, 556, 496, 475, 20, 894, 484, 775, 889, 463, 241, 730, 57, 907, 551, 859, 943, 185, 416, 870, 590, 435, 471, 932, 268, 381, 626, 502, 565, 273, 534, 672, 778, 292, 473, 566, 104, 172, 285, 832, 411, 329, 628, 397, 472, 271, 910, 711, 690, 969, 585, 809, 941, 923, 555, 228, 685, 242, 94, 96, 211, 140, 61, 922, 795, 869, 34, 255, 38, 984, 676, 15, 560, 632, 434, 921, 355, 582, 351, 212, 200, 819, 960, 649, 852, 75, 771, 361, 996, 238, 316, 720, 671, 462, 112, 569, 171, 664, 625, 588, 405, 553, 270, 533, 353, 842, 114, 972, 83, 937, 63, 194, 237, 537, 980, 802, 916, 959, 688, 839, 350, 917, 650, 545, 615, 151, 352, 686, 726, 266, 509, 439, 491, 935, 608, 518, 653, 339, 609, 277, 635, 836, 88, 407, 440, 642, 927, 229, 727, 360, 477, 846, 413, 454, 616, 28, 598, 567, 540, 790, 424, 247, 317, 746, 911, 798, 321, 547, 248, 734, 829, 220, 138, 756, 500, 691, 196, 740, 930, 843, 733, 221, 827, 50, 813, 949, 525, 349, 474, 134, 875, 695, 513, 414, 515, 638, 99, 366, 490, 975, 246, 465, 206, 281, 583, 256, 587, 749, 2, 951, 679, 215, 364, 458, 402, 646, 991, 335, 982, 835, 300, 900, 703, 994, 983, 234, 888, 532, 804, 584, 305, 792, 442, 291, 964, 158, 370, 452, 250, 521, 166, 948, 812, 794, 272, 699, 205, 183, 507, 301, 920, 781, 233, 824, 137, 489, 833, 887, 966, 856, 78, 830, 153, 359, 696, 526, 216, 66, 701, 403, 891, 849, 571, 308, 483, 164, 293, 928, 677, 320, 837, 441, 639, 564, 510, 648, 274, 336, 661, 878, 777, 816, 976, 493, 810, 67, 87, 91, 187, 882, 986, 80, 22, 499, 90, 705, 139, 136, 122, 708, 716, 886, 572, 127, 40, 721, 764, 16, 379, 692, 645, 456, 710, 460, 783, 97, 776, 713, 884, 115, 466, 596, 374, 406, 110, 568, 68, 214, 622, 470, 107, 504, 682, 31, 421, 576, 654, 605, 788, 799, 280, 338, 931, 873, 204, 287, 459, 755, 939, 599, 431, 796, 235, 42, 750, 262, 318, 393, 202, 822, 879, 820, 728, 337, }; static void fill_bindings_random(struct strbuf *buf, int start, int end, const char *prefix) { int i; for (i = start; i < end; i++) { print_strbuf(buf, "%s", prefix); format_devname(buf, random_numbers[i]); print_strbuf(buf, " WWID%d\n", random_numbers[i]); } } struct random_aliases { int start; int end; const char *prefix; }; static void order_test(int n, const struct random_aliases ra[], bool conflict_ok) { STRBUF_ON_STACK(buf); int i, j, prev, curr, tmp; struct binding *bdg; Bindings *bindings = &global_bindings; for (j = 0; j < n; j++) fill_bindings_random(&buf, ra[j].start, ra[j].end, ra[j].prefix); mock_bindings_file__(get_strbuf_str(&buf), conflict_ok); for (j = 0; j < n; j++) { bdg = VECTOR_SLOT(bindings, 0); if (ALIAS_DEBUG && j == 0) printf("%d: %s\n", 0, bdg->alias); prev = scan_devname(bdg->alias, ra[j].prefix); i = 1; vector_foreach_slot_after(bindings, bdg, i) { if (ALIAS_DEBUG && j == 0) printf("%d: %s\n", i, bdg->alias); tmp = scan_devname(bdg->alias, ra[j].prefix); if (tmp == -1) continue; curr = tmp; if (prev > 0) { if (curr <= prev) printf("ERROR: %d (%s) %d >= %d\n", i, bdg->alias, prev, curr); assert_true(curr > prev); } prev = curr; } } } static void order_01(void **state) { const struct random_aliases ra[] = { { 0, 1000, "MPATH" }, }; order_test(ARRAY_SIZE(ra), ra, false); } static void order_02(void **state) { const struct random_aliases ra[] = { { 0, 500, "MPATH" }, { 200, 700, "mpath" }, }; order_test(ARRAY_SIZE(ra), ra, false); } static void order_03(void **state) { const struct random_aliases ra[] = { { 500, 1000, "MPTH" }, { 0, 500, "MPATH" }, }; order_test(ARRAY_SIZE(ra), ra, false); } static void order_04(void **state) { const struct random_aliases ra[] = { { 0, 500, "mpa" }, { 250, 750, "mp" }, }; order_test(ARRAY_SIZE(ra), ra, true); } static void order_05(void **state) { const struct random_aliases ra[] = { { 0, 100, "A" }, { 0, 100, "B" }, { 0, 100, "C" }, { 0, 100, "D" }, }; order_test(ARRAY_SIZE(ra), ra, false); } static void order_06(void **state) { const struct random_aliases ra[] = { { 0, 100, "" }, { 0, 100, "a" }, { 0, 100, "aa" }, { 0, 100, "ab" }, { 0, 100, "aaa" }, }; order_test(ARRAY_SIZE(ra), ra, true); } static int test_bindings_order(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_teardown(order_01, teardown_bindings), cmocka_unit_test_teardown(order_02, teardown_bindings), cmocka_unit_test_teardown(order_03, teardown_bindings), cmocka_unit_test_teardown(order_04, teardown_bindings), cmocka_unit_test_teardown(order_05, teardown_bindings), cmocka_unit_test_teardown(order_06, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(3); /* avoid open_file() call in _read_bindings_file */ bindings_file_changed = 0; ret += test_format_devname(); ret += test_scan_devname(); ret += test_lookup_binding(); ret += test_rlookup_binding(); ret += test_allocate_binding(); ret += test_get_user_friendly_alias(); ret += test_bindings_order(); return ret; } multipath-tools-0.11.1/tests/blacklist.c000066400000000000000000000533251475246302400202340ustar00rootroot00000000000000/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include "globals.c" #include "blacklist.h" #include "test-log.h" #include "debug.h" #include "../libmultipath/blacklist.c" struct udev_device { const char *sysname; char *property_list[]; }; const char * __wrap_udev_device_get_sysname(struct udev_device *udev_device) { assert_non_null(udev_device); assert_non_null(udev_device->sysname); return udev_device->sysname; } struct udev_list_entry * __wrap_udev_device_get_properties_list_entry(struct udev_device *udev_device) { assert_non_null(udev_device); if (!*udev_device->property_list) return NULL; return (struct udev_list_entry *)udev_device->property_list; } struct udev_list_entry * __wrap_udev_list_entry_get_next(struct udev_list_entry *list_entry) { assert_non_null(list_entry); if (!*((char **)list_entry + 1)) return NULL; return (struct udev_list_entry *)(((char **)list_entry) + 1); } const char * __wrap_udev_list_entry_get_name(struct udev_list_entry *list_entry) { return *(const char **)list_entry; } vector elist_property_default; vector blist_devnode_default; vector blist_devnode_sdb; vector blist_devnode_sdb_inv; vector blist_all; vector blist_device_foo_bar; vector blist_device_foo_inv_bar; vector blist_device_foo_bar_inv; vector blist_device_all; vector blist_wwid_xyzzy; vector blist_wwid_xyzzy_inv; vector blist_protocol_fcp; vector blist_protocol_fcp_inv; vector blist_property_wwn; vector blist_property_wwn_inv; static int setup(void **state) { struct config conf; memset(&conf, 0, sizeof(conf)); conf.blist_devnode = vector_alloc(); if (!conf.blist_devnode) return -1; conf.elist_property = vector_alloc(); if (!conf.elist_property) return -1; if (setup_default_blist(&conf) != 0) return -1; elist_property_default = conf.elist_property; blist_devnode_default = conf.blist_devnode; blist_devnode_sdb = vector_alloc(); if (!blist_devnode_sdb || store_ble(blist_devnode_sdb, "sdb", ORIGIN_CONFIG)) return -1; blist_devnode_sdb_inv = vector_alloc(); if (!blist_devnode_sdb_inv || store_ble(blist_devnode_sdb_inv, "!sdb", ORIGIN_CONFIG)) return -1; blist_all = vector_alloc(); if (!blist_all || store_ble(blist_all, ".*", ORIGIN_CONFIG)) return -1; blist_device_foo_bar = vector_alloc(); if (!blist_device_foo_bar || alloc_ble_device(blist_device_foo_bar) || set_ble_device(blist_device_foo_bar, "foo", "bar", ORIGIN_CONFIG)) return -1; blist_device_foo_inv_bar = vector_alloc(); if (!blist_device_foo_inv_bar || alloc_ble_device(blist_device_foo_inv_bar) || set_ble_device(blist_device_foo_inv_bar, "!foo", "bar", ORIGIN_CONFIG)) return -1; blist_device_foo_bar_inv = vector_alloc(); if (!blist_device_foo_bar_inv || alloc_ble_device(blist_device_foo_bar_inv) || set_ble_device(blist_device_foo_bar_inv, "foo", "!bar", ORIGIN_CONFIG)) return -1; blist_device_all = vector_alloc(); if (!blist_device_all || alloc_ble_device(blist_device_all) || set_ble_device(blist_device_all, ".*", ".*", ORIGIN_CONFIG)) return -1; blist_wwid_xyzzy = vector_alloc(); if (!blist_wwid_xyzzy || store_ble(blist_wwid_xyzzy, "xyzzy", ORIGIN_CONFIG)) return -1; blist_wwid_xyzzy_inv = vector_alloc(); if (!blist_wwid_xyzzy_inv || store_ble(blist_wwid_xyzzy_inv, "!xyzzy", ORIGIN_CONFIG)) return -1; blist_protocol_fcp = vector_alloc(); if (!blist_protocol_fcp || store_ble(blist_protocol_fcp, "scsi:fcp", ORIGIN_CONFIG)) return -1; blist_protocol_fcp_inv = vector_alloc(); if (!blist_protocol_fcp_inv || store_ble(blist_protocol_fcp_inv, "!scsi:fcp", ORIGIN_CONFIG)) return -1; blist_property_wwn = vector_alloc(); if (!blist_property_wwn || store_ble(blist_property_wwn, "ID_WWN", ORIGIN_CONFIG)) return -1; blist_property_wwn_inv = vector_alloc(); if (!blist_property_wwn_inv || store_ble(blist_property_wwn_inv, "!ID_WWN", ORIGIN_CONFIG)) return -1; init_test_verbosity(4); return 0; } static int teardown(void **state) { free_blacklist(elist_property_default); free_blacklist(blist_devnode_default); free_blacklist(blist_devnode_sdb); free_blacklist(blist_devnode_sdb_inv); free_blacklist(blist_all); free_blacklist_device(blist_device_foo_bar); free_blacklist_device(blist_device_foo_inv_bar); free_blacklist_device(blist_device_foo_bar_inv); free_blacklist_device(blist_device_all); free_blacklist(blist_wwid_xyzzy); free_blacklist(blist_wwid_xyzzy_inv); free_blacklist(blist_protocol_fcp); free_blacklist(blist_protocol_fcp_inv); free_blacklist(blist_property_wwn); free_blacklist(blist_property_wwn_inv); return 0; } static int reset_blists(void **state) { conf.blist_devnode = NULL; conf.blist_wwid = NULL; conf.blist_property = NULL; conf.blist_protocol = NULL; conf.blist_device = NULL; conf.elist_devnode = NULL; conf.elist_wwid = NULL; conf.elist_property = NULL; conf.elist_protocol = NULL; conf.elist_device = NULL; return 0; } static void test_devnode_blacklist(void **state) { expect_condlog(3, "sdb: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_devnode_sdb, NULL, "sdb"), MATCH_DEVNODE_BLIST); assert_int_equal(filter_devnode(blist_devnode_sdb_inv, NULL, "sdb"), MATCH_NOTHING); expect_condlog(3, "sdc: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_devnode_sdb_inv, NULL, "sdc"), MATCH_DEVNODE_BLIST); } static void test_devnode_whitelist(void **state) { expect_condlog(3, "sdb: device node name whitelisted\n"); assert_int_equal(filter_devnode(blist_all, blist_devnode_sdb, "sdb"), MATCH_DEVNODE_BLIST_EXCEPT); expect_condlog(3, "sdc: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_all, blist_devnode_sdb, "sdc"), MATCH_DEVNODE_BLIST); } static void test_devnode_missing(void **state) { assert_int_equal(filter_devnode(blist_devnode_sdb, NULL, "sdc"), MATCH_NOTHING); } static void test_devnode_default(void **state) { assert_int_equal(filter_devnode(blist_devnode_default, NULL, "sdaa"), MATCH_NOTHING); if (nvme_multipath_enabled()) { expect_condlog(3, "nvme0n1: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_devnode_default, NULL, "nvme0n1"), MATCH_DEVNODE_BLIST); } else assert_int_equal(filter_devnode(blist_devnode_default, NULL, "nvme0n1"), MATCH_NOTHING); assert_int_equal(filter_devnode(blist_devnode_default, NULL, "dasda"), MATCH_NOTHING); expect_condlog(3, "hda: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_devnode_default, NULL, "hda"), MATCH_DEVNODE_BLIST); } static void test_device_blacklist(void **state) { expect_condlog(3, "sdb: (foo:bar) vendor/product blacklisted\n"); assert_int_equal(filter_device(blist_device_foo_bar, NULL, "foo", "bar", "sdb"), MATCH_DEVICE_BLIST); assert_int_equal(filter_device(blist_device_foo_inv_bar, NULL, "foo", "bar", "sdb"), MATCH_NOTHING); assert_int_equal(filter_device(blist_device_foo_bar_inv, NULL, "foo", "bar", "sdb"), MATCH_NOTHING); expect_condlog(3, "sdb: (baz:bar) vendor/product blacklisted\n"); assert_int_equal(filter_device(blist_device_foo_inv_bar, NULL, "baz", "bar", "sdb"), MATCH_DEVICE_BLIST); expect_condlog(3, "sdb: (foo:baz) vendor/product blacklisted\n"); assert_int_equal(filter_device(blist_device_foo_bar_inv, NULL, "foo", "baz", "sdb"), MATCH_DEVICE_BLIST); } static void test_device_whitelist(void **state) { expect_condlog(3, "sdb: (foo:bar) vendor/product whitelisted\n"); assert_int_equal(filter_device(blist_device_all, blist_device_foo_bar, "foo", "bar", "sdb"), MATCH_DEVICE_BLIST_EXCEPT); expect_condlog(3, "sdb: (foo:baz) vendor/product blacklisted\n"); assert_int_equal(filter_device(blist_device_all, blist_device_foo_bar, "foo", "baz", "sdb"), MATCH_DEVICE_BLIST); } static void test_device_missing(void **state) { assert_int_equal(filter_device(blist_device_foo_bar, NULL, "foo", "baz", "sdb"), MATCH_NOTHING); } static void test_wwid_blacklist(void **state) { expect_condlog(3, "sdb: wwid xyzzy blacklisted\n"); assert_int_equal(filter_wwid(blist_wwid_xyzzy, NULL, "xyzzy", "sdb"), MATCH_WWID_BLIST); assert_int_equal(filter_wwid(blist_wwid_xyzzy_inv, NULL, "xyzzy", "sdb"), MATCH_NOTHING); expect_condlog(3, "sdb: wwid plugh blacklisted\n"); assert_int_equal(filter_wwid(blist_wwid_xyzzy_inv, NULL, "plugh", "sdb"), MATCH_WWID_BLIST); } static void test_wwid_whitelist(void **state) { expect_condlog(3, "sdb: wwid xyzzy whitelisted\n"); assert_int_equal(filter_wwid(blist_all, blist_wwid_xyzzy, "xyzzy", "sdb"), MATCH_WWID_BLIST_EXCEPT); expect_condlog(3, "sdb: wwid plugh blacklisted\n"); assert_int_equal(filter_wwid(blist_all, blist_wwid_xyzzy, "plugh", "sdb"), MATCH_WWID_BLIST); } static void test_wwid_missing(void **state) { assert_int_equal(filter_wwid(blist_wwid_xyzzy, NULL, "plugh", "sdb"), MATCH_NOTHING); } static void test_protocol_blacklist(void **state) { struct path pp = { .dev = "sdb", .bus = SYSFS_BUS_SCSI, .sg_id.proto_id = SCSI_PROTOCOL_FCP }; expect_condlog(3, "sdb: protocol scsi:fcp blacklisted\n"); assert_int_equal(filter_protocol(blist_protocol_fcp, NULL, &pp), MATCH_PROTOCOL_BLIST); assert_int_equal(filter_protocol(blist_protocol_fcp_inv, NULL, &pp), MATCH_NOTHING); pp.sg_id.proto_id = SCSI_PROTOCOL_ATA; expect_condlog(3, "sdb: protocol scsi:ata blacklisted\n"); assert_int_equal(filter_protocol(blist_protocol_fcp_inv, NULL, &pp), MATCH_PROTOCOL_BLIST); } static void test_protocol_whitelist(void **state) { struct path pp1 = { .dev = "sdb", .bus = SYSFS_BUS_SCSI, .sg_id.proto_id = SCSI_PROTOCOL_FCP }; struct path pp2 = { .dev = "sdb", .bus = SYSFS_BUS_SCSI, .sg_id.proto_id = SCSI_PROTOCOL_ISCSI }; expect_condlog(3, "sdb: protocol scsi:fcp whitelisted\n"); assert_int_equal(filter_protocol(blist_all, blist_protocol_fcp, &pp1), MATCH_PROTOCOL_BLIST_EXCEPT); expect_condlog(3, "sdb: protocol scsi:iscsi blacklisted\n"); assert_int_equal(filter_protocol(blist_all, blist_protocol_fcp, &pp2), MATCH_PROTOCOL_BLIST); } static void test_protocol_missing(void **state) { struct path pp = { .dev = "sdb", .bus = SYSFS_BUS_SCSI, .sg_id.proto_id = SCSI_PROTOCOL_ISCSI }; assert_int_equal(filter_protocol(blist_protocol_fcp, NULL, &pp), MATCH_NOTHING); } static void test_property_blacklist(void **state) { static struct udev_device udev = { "sdb", { "ID_FOO", "ID_WWN", "ID_BAR", NULL } }; static struct udev_device udev_inv = { "sdb", { "ID_WWN", NULL } }; conf.blist_property = blist_property_wwn; expect_condlog(3, "sdb: udev property ID_WWN blacklisted\n"); assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), MATCH_PROPERTY_BLIST); conf.blist_property = blist_property_wwn_inv; expect_condlog(3, "sdb: udev property ID_FOO blacklisted\n"); assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), MATCH_PROPERTY_BLIST); assert_int_equal(filter_property(&conf, &udev_inv, 3, "ID_SERIAL"), MATCH_NOTHING); } /* the property check works different in that you check all the property * names, so setting a blacklist value will blacklist the device if any * of the property on the blacklist are found before the property names * in the whitelist. This might be worth changing. although it would * force multipath to go through the properties twice */ static void test_property_whitelist(void **state) { static struct udev_device udev = { "sdb", { "ID_FOO", "ID_WWN", "ID_BAR", NULL } }; conf.elist_property = blist_property_wwn; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), MATCH_PROPERTY_BLIST_EXCEPT); } static void test_property_missing(void **state) { static struct udev_device udev = { "sdb", { "ID_FOO", "ID_BAZ", "ID_BAR", "ID_SERIAL", NULL } }; conf.blist_property = blist_property_wwn; expect_condlog(3, "sdb: blacklisted, udev property missing\n"); assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), MATCH_PROPERTY_BLIST_MISSING); assert_int_equal(filter_property(&conf, &udev, 3, "ID_BLAH"), MATCH_NOTHING); assert_int_equal(filter_property(&conf, &udev, 3, ""), MATCH_NOTHING); assert_int_equal(filter_property(&conf, &udev, 3, NULL), MATCH_NOTHING); } struct udev_device test_udev = { "sdb", { "ID_FOO", "ID_WWN", "ID_BAR", NULL } }; struct path test_pp = { .dev = "sdb", .bus = SYSFS_BUS_SCSI, .udev = &test_udev, .sg_id.proto_id = SCSI_PROTOCOL_FCP, .vendor_id = "foo", .product_id = "bar", .wwid = "xyzzy" }; static void test_filter_path_property(void **state) { conf.blist_property = blist_property_wwn; expect_condlog(3, "sdb: udev property ID_WWN blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_PROPERTY_BLIST); } static void test_filter_path_devnode(void **state) { /* always must include property elist, to avoid "missing property" * blacklisting */ conf.elist_property = blist_property_wwn; conf.blist_devnode = blist_devnode_sdb; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_DEVNODE_BLIST); } static void test_filter_path_device(void **state) { /* always must include property elist, to avoid "missing property" * blacklisting */ conf.elist_property = blist_property_wwn; conf.blist_device = blist_device_foo_bar; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: (foo:bar) vendor/product blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_DEVICE_BLIST); } static void test_filter_path_protocol(void **state) { conf.elist_property = blist_property_wwn; conf.blist_protocol = blist_protocol_fcp; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: protocol scsi:fcp blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_PROTOCOL_BLIST); } static void test_filter_path_wwid(void **state) { conf.elist_property = blist_property_wwn; conf.blist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: wwid xyzzy blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_WWID_BLIST); } struct udev_device miss_udev = { "sdb", { "ID_FOO", "ID_BAZ", "ID_BAR", "ID_SERIAL", NULL } }; struct path miss1_pp = { .dev = "sdc", .bus = SYSFS_BUS_SCSI, .udev = &miss_udev, .uid_attribute = "ID_SERIAL", .sg_id.proto_id = SCSI_PROTOCOL_ISCSI, .vendor_id = "foo", .product_id = "baz", .wwid = "plugh" }; struct path miss2_pp = { .dev = "sdc", .bus = SYSFS_BUS_SCSI, .udev = &test_udev, .uid_attribute = "ID_SERIAL", .sg_id.proto_id = SCSI_PROTOCOL_ISCSI, .vendor_id = "foo", .product_id = "baz", .wwid = "plugh" }; struct path miss3_pp = { .dev = "sdc", .bus = SYSFS_BUS_SCSI, .udev = &miss_udev, .uid_attribute = "ID_EGGS", .sg_id.proto_id = SCSI_PROTOCOL_ISCSI, .vendor_id = "foo", .product_id = "baz", .wwid = "plugh" }; static void test_filter_path_missing1(void **state) { conf.blist_property = blist_property_wwn; conf.blist_devnode = blist_devnode_sdb; conf.blist_device = blist_device_foo_bar; conf.blist_protocol = blist_protocol_fcp; conf.blist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: blacklisted, udev property missing\n"); assert_int_equal(filter_path(&conf, &miss1_pp), MATCH_PROPERTY_BLIST_MISSING); } /* This one matches the property whitelist, to test the other missing * functions */ static void test_filter_path_missing2(void **state) { conf.elist_property = blist_property_wwn; conf.blist_devnode = blist_devnode_sdb; conf.blist_device = blist_device_foo_bar; conf.blist_protocol = blist_protocol_fcp; conf.blist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); assert_int_equal(filter_path(&conf, &miss2_pp), MATCH_NOTHING); } /* Here we use a different uid_attribute which is also missing, thus the path is not blacklisted */ static void test_filter_path_missing3(void **state) { conf.blist_property = blist_property_wwn; conf.blist_devnode = blist_devnode_sdb; conf.blist_device = blist_device_foo_bar; conf.blist_protocol = blist_protocol_fcp; conf.blist_wwid = blist_wwid_xyzzy; assert_int_equal(filter_path(&conf, &miss3_pp), MATCH_NOTHING); } static void test_filter_path_whitelist(void **state) { conf.elist_property = blist_property_wwn; conf.elist_devnode = blist_devnode_sdb; conf.elist_device = blist_device_foo_bar; conf.elist_protocol = blist_protocol_fcp; conf.elist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name whitelisted\n"); expect_condlog(3, "sdb: (foo:bar) vendor/product whitelisted\n"); expect_condlog(3, "sdb: protocol scsi:fcp whitelisted\n"); expect_condlog(3, "sdb: wwid xyzzy whitelisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_WWID_BLIST_EXCEPT); } static void test_filter_path_whitelist_property(void **state) { conf.blist_property = blist_property_wwn; conf.elist_devnode = blist_devnode_sdb; conf.elist_device = blist_device_foo_bar; conf.elist_protocol = blist_protocol_fcp; conf.elist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_PROPERTY_BLIST); } static void test_filter_path_whitelist_devnode(void **state) { conf.elist_property = blist_property_wwn; conf.blist_devnode = blist_devnode_sdb; conf.elist_device = blist_device_foo_bar; conf.elist_protocol = blist_protocol_fcp; conf.elist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_DEVNODE_BLIST); } static void test_filter_path_whitelist_device(void **state) { conf.elist_property = blist_property_wwn; conf.elist_devnode = blist_devnode_sdb; conf.blist_device = blist_device_foo_bar; conf.elist_protocol = blist_protocol_fcp; conf.elist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name whitelisted\n"); expect_condlog(3, "sdb: (foo:bar) vendor/product blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_DEVICE_BLIST); } static void test_filter_path_whitelist_protocol(void **state) { conf.elist_property = blist_property_wwn; conf.elist_devnode = blist_devnode_sdb; conf.elist_device = blist_device_foo_bar; conf.blist_protocol = blist_protocol_fcp; conf.elist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name whitelisted\n"); expect_condlog(3, "sdb: (foo:bar) vendor/product whitelisted\n"); expect_condlog(3, "sdb: protocol scsi:fcp blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_PROTOCOL_BLIST); } static void test_filter_path_whitelist_wwid(void **state) { conf.elist_property = blist_property_wwn; conf.elist_devnode = blist_devnode_sdb; conf.elist_device = blist_device_foo_bar; conf.elist_protocol = blist_protocol_fcp; conf.blist_wwid = blist_wwid_xyzzy; expect_condlog(3, "sdb: udev property ID_WWN whitelisted\n"); expect_condlog(3, "sdb: device node name whitelisted\n"); expect_condlog(3, "sdb: (foo:bar) vendor/product whitelisted\n"); expect_condlog(3, "sdb: protocol scsi:fcp whitelisted\n"); expect_condlog(3, "sdb: wwid xyzzy blacklisted\n"); assert_int_equal(filter_path(&conf, &test_pp), MATCH_WWID_BLIST); } #define test_and_reset(x) cmocka_unit_test_teardown((x), reset_blists) int test_blacklist(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_devnode_blacklist), cmocka_unit_test(test_devnode_whitelist), cmocka_unit_test(test_devnode_missing), cmocka_unit_test(test_devnode_default), cmocka_unit_test(test_device_blacklist), cmocka_unit_test(test_device_whitelist), cmocka_unit_test(test_device_missing), cmocka_unit_test(test_wwid_blacklist), cmocka_unit_test(test_wwid_whitelist), cmocka_unit_test(test_wwid_missing), cmocka_unit_test(test_protocol_blacklist), cmocka_unit_test(test_protocol_whitelist), cmocka_unit_test(test_protocol_missing), test_and_reset(test_property_blacklist), test_and_reset(test_property_whitelist), test_and_reset(test_property_missing), test_and_reset(test_filter_path_property), test_and_reset(test_filter_path_devnode), test_and_reset(test_filter_path_device), test_and_reset(test_filter_path_protocol), test_and_reset(test_filter_path_wwid), test_and_reset(test_filter_path_missing1), test_and_reset(test_filter_path_missing2), test_and_reset(test_filter_path_missing3), test_and_reset(test_filter_path_whitelist), test_and_reset(test_filter_path_whitelist_property), test_and_reset(test_filter_path_whitelist_devnode), test_and_reset(test_filter_path_whitelist_device), test_and_reset(test_filter_path_whitelist_protocol), test_and_reset(test_filter_path_whitelist_wwid), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; ret += test_blacklist(); return ret; } multipath-tools-0.11.1/tests/cli.c000066400000000000000000000204571475246302400170330ustar00rootroot00000000000000/* * Copyright (c) 2020 Martin Wilck, SUSE * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include "vector.h" #include "cli.h" #include "globals.c" #define HANDLER(x) NULL #include "callbacks.c" /* See cli.c */ #define INVALID_FINGERPRINT ((uint32_t)(0)) static int setup(void **state) { return cli_init(); } static int teardown(void **state) { cli_exit(); return 0; } /* * @NAME: test name * @CMD: test command * @R: retcode of get_cmdvec() * @FPR: fingerprint (only if R==0) * @GOOD: expect to find handler (only if R==0) */ #define client_test(NAME, CMD, R, FPR, GOOD) \ static void client_test_##NAME(void **state) \ { \ vector v = NULL; \ char cmd[] = CMD; \ \ assert_int_equal(get_cmdvec(cmd, &v, false), R); \ if (R == 0) { \ assert_ptr_not_equal(v, NULL); \ assert_int_equal(fingerprint(v), FPR); \ if (GOOD) \ assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ else \ assert_ptr_equal(find_handler_for_cmdvec(v), NULL); \ free_keys(v); \ } else \ assert_ptr_equal(v, NULL); \ } /* * @NAME: test name * @CMD: test command * @FPR: fingerprint * @PAR: value of parameter for keyword 1 */ #define client_param(NAME, CMD, FPR, PAR) \ static void client_param_##NAME(void **state) \ { \ vector v = NULL; \ char cmd[] = CMD; \ \ assert_int_equal(get_cmdvec(cmd, &v, false), 0); \ assert_ptr_not_equal(v, NULL); \ assert_int_equal(fingerprint(v), FPR); \ assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ assert_string_equal(((struct key *)VECTOR_SLOT(v, 1))->param, PAR); \ free_keys(v); \ } /* * @NAME: test name * @CMD: test command * @FPR: fingerprint * @PAR1: value of parameter for keyword 1 * @N: index of 2nd parameter keyword * @PARN: value of parameter for keyword N */ #define client_2param(NAME, CMD, FPR, PAR1, N, PARN) \ static void client_2param_##NAME(void **state) \ { \ vector v = NULL; \ char cmd[] = CMD; \ \ assert_int_equal(get_cmdvec(cmd, &v, false), 0); \ assert_ptr_not_equal(v, NULL); \ assert_int_equal(fingerprint(v), FPR); \ assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ assert_string_equal(((struct key *)VECTOR_SLOT(v, 1))->param, PAR1); \ assert_string_equal(((struct key *)VECTOR_SLOT(v, N))->param, PARN); \ free_keys(v); \ } static void client_test_null(void **state) { vector v = NULL; /* alloc_strvec() returns ENOMEM for NULL cmd */ assert_int_equal(get_cmdvec(NULL, &v, false), ENOMEM); assert_ptr_equal(v, NULL); } /* alloc_strvec() returns ENOMEM for empty string */ client_test(empty, "", ENOMEM, 0, false); client_test(bogus, "bogus", ESRCH, 0, false); client_test(list, "list", 0, VRB_LIST, false); client_test(show, "show", 0, VRB_LIST, false); client_test(s, "s", ESRCH, 0, false); /* partial match works if it's unique */ client_test(sh, "sh", ESRCH, 0, false); client_test(sho, "sho", 0, VRB_LIST, false); client_test(add, "add", 0, VRB_ADD, false); client_test(resume, "resume", 0, VRB_RESUME, false); client_test(disablequeueing, "disablequeueing", 0, VRB_DISABLEQ, false); /* "disable" -> disablequeueing, "queueing" -> not found */ client_test(disable_queueing, "disable queueing", ESRCH, 0, false); /* ENOENT because the not-found keyword is not last pos */ client_test(queueing_disable, "queueing disable", ENOENT, 0, false); client_test(disable, "disable", 0, VRB_DISABLEQ, false); client_test(setprkey, "setprkey", 0, VRB_SETPRKEY, false); client_test(quit, "quit", 0, VRB_QUIT, true); client_test(exit, "exit", 0, VRB_QUIT, true); client_test(show_maps, "show maps", 0, VRB_LIST|Q1_MAPS, true); client_test(sh_maps, "sh maps", ENOENT, 0, 0); client_test(sho_maps, "sho maps", 0, VRB_LIST|Q1_MAPS, true); client_test(sho_multipaths, "sho multipaths", 0, VRB_LIST|Q1_MAPS, true); /* Needs a parameter */ client_test(show_map, "show map", EINVAL, 0, 0); client_test(show_ma, "show ma", ESRCH, 0, 0); client_test(show_list_maps, "show list maps", 0, VRB_LIST|(VRB_LIST<<8)|(KEY_MAPS<<16), false); client_test(show_maps_list, "show maps list", 0, VRB_LIST|(VRB_LIST<<16)|(KEY_MAPS<<8), false); client_test(maps_show, "maps show ", 0, (VRB_LIST<<8)|KEY_MAPS, false); client_test(show_maps_list_json, "show maps list json", 0, VRB_LIST|(VRB_LIST<<16)|(KEY_MAPS<<8)|(KEY_JSON<<24), false); /* More than 4 keywords */ client_test(show_maps_list_json_raw, "show maps list json raw", 0, INVALID_FINGERPRINT, false); client_test(show_list_show_list, "show list show list", 0, VRB_LIST|(VRB_LIST<<8)|(VRB_LIST<<16)|(VRB_LIST<<24), false); client_test(show_list_show_list_show, "show list show list show", 0, INVALID_FINGERPRINT, false); client_test(q_q_q_q, "q q q q", 0, VRB_QUIT|(VRB_QUIT<<8)|(VRB_QUIT<<16)|(VRB_QUIT<<24), false); client_test(show_path_xy, "show path xy", 0, VRB_LIST|Q1_PATH, true); client_param(show_path_xy, "show path xy", VRB_LIST|Q1_PATH, "xy"); client_param(show_path_xy_2, "show path \"xy\"", VRB_LIST|Q1_PATH, "xy"); client_param(show_path_x_y, "show path \"x y\"", VRB_LIST|Q1_PATH, "x y"); client_param(show_path_2inch, "show path \"2\"\"\"", VRB_LIST|Q1_PATH, "2\""); /* missing closing quote */ client_param(show_path_2inch_1, "show path \"2\"\"", VRB_LIST|Q1_PATH, "2\""); client_test(show_map_xy, "show map xy", 0, VRB_LIST|Q1_MAP, false); client_test(show_map_xy_bogus, "show map xy bogus", ESRCH, 0, false); client_2param(show_map_xy_format_h, "show map xy form %h", VRB_LIST|Q1_MAP|Q2_FMT, "xy", 2, "%h"); client_2param(show_map_xy_raw_format_h, "show map xy raw form %h", VRB_LIST|Q1_MAP|Q2_RAW|Q3_FMT, "xy", 3, "%h"); client_test(show_map_xy_format_h_raw, "show map xy form %h raw", 0, VRB_LIST|(KEY_MAP<<8)|(KEY_FMT<<16)|(KEY_RAW<<24), false); client_param(list_path_sda, "list path sda", VRB_LIST|Q1_PATH, "sda"); client_param(add_path_sda, "add path sda", VRB_ADD|Q1_PATH, "sda"); client_test(list_list_path_sda, "list list path sda", 0, VRB_LIST|(VRB_LIST<<8)|(KEY_PATH<<16), false); static int client_tests(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(client_test_null), cmocka_unit_test(client_test_empty), cmocka_unit_test(client_test_bogus), cmocka_unit_test(client_test_list), cmocka_unit_test(client_test_show), cmocka_unit_test(client_test_s), cmocka_unit_test(client_test_sh), cmocka_unit_test(client_test_sho), cmocka_unit_test(client_test_add), cmocka_unit_test(client_test_resume), cmocka_unit_test(client_test_disablequeueing), cmocka_unit_test(client_test_disable_queueing), cmocka_unit_test(client_test_queueing_disable), cmocka_unit_test(client_test_disable), cmocka_unit_test(client_test_setprkey), cmocka_unit_test(client_test_quit), cmocka_unit_test(client_test_exit), cmocka_unit_test(client_test_show_maps), cmocka_unit_test(client_test_sh_maps), cmocka_unit_test(client_test_sho_maps), cmocka_unit_test(client_test_sho_multipaths), cmocka_unit_test(client_test_show_map), cmocka_unit_test(client_test_show_ma), cmocka_unit_test(client_test_maps_show), cmocka_unit_test(client_test_show_list_maps), cmocka_unit_test(client_test_show_maps_list), cmocka_unit_test(client_test_show_maps_list_json), cmocka_unit_test(client_test_show_maps_list_json_raw), cmocka_unit_test(client_test_show_list_show_list), cmocka_unit_test(client_test_show_list_show_list_show), cmocka_unit_test(client_test_q_q_q_q), cmocka_unit_test(client_test_show_map_xy), cmocka_unit_test(client_test_show_map_xy_bogus), cmocka_unit_test(client_test_show_path_xy), cmocka_unit_test(client_param_show_path_xy), cmocka_unit_test(client_param_show_path_xy_2), cmocka_unit_test(client_param_show_path_x_y), cmocka_unit_test(client_param_show_path_2inch), cmocka_unit_test(client_param_show_path_2inch_1), cmocka_unit_test(client_2param_show_map_xy_format_h), cmocka_unit_test(client_2param_show_map_xy_raw_format_h), cmocka_unit_test(client_test_show_map_xy_format_h_raw), cmocka_unit_test(client_param_list_path_sda), cmocka_unit_test(client_param_add_path_sda), cmocka_unit_test(client_test_list_list_path_sda), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += client_tests(); return ret; } multipath-tools-0.11.1/tests/devt.c000066400000000000000000000116001475246302400172140ustar00rootroot00000000000000/* * Copyright (c) 2020 Martin Wilck, SUSE * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "debug.h" struct path; #include "sysfs.h" #include "globals.c" static bool sys_dev_block_exists(void) { DIR *dir; bool rc = false; dir = opendir("/sys/dev/block"); if (dir != NULL) { struct dirent *de; while((de = readdir(dir)) != NULL) { if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { rc = true; break; } } closedir(dir); } return rc; } static int get_one_devt(char *devt, size_t len) { struct udev_enumerate *enm; int r, ret = -1; struct udev_list_entry *first; struct udev_device *u_dev; const char *path; dev_t devnum; enm = udev_enumerate_new(udev); if (!enm) return -1; r = udev_enumerate_add_match_subsystem(enm, "block"); r = udev_enumerate_scan_devices(enm); if (r < 0) goto out; first = udev_enumerate_get_list_entry(enm); if (!first) goto out; path = udev_list_entry_get_name(first); u_dev = udev_device_new_from_syspath(udev, path); if (!u_dev) goto out; devnum = udev_device_get_devnum(u_dev); snprintf(devt, len, "%d:%d", major(devnum), minor(devnum)); udev_device_unref(u_dev); condlog(3, "found block device: %s", devt); ret = 0; out: udev_enumerate_unref(enm); return ret; } int setup(void **state) { static char dev_t[BLK_DEV_SIZE]; udev = udev_new(); if (udev == NULL) return -1; *state = dev_t; return get_one_devt(dev_t, sizeof(dev_t)); } int teardown(void **state) { udev_unref(udev); return 0; } static void test_devt2devname_devt_good(void **state) { char dummy[BLK_DEV_SIZE]; if (!sys_dev_block_exists()) skip(); assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 0); } static void test_devt2devname_devname_null(void **state) { assert_int_equal(devt2devname(NULL, 0, ""), 1); } /* buffer length 0 */ static void test_devt2devname_length_0(void **state) { char dummy[] = ""; assert_int_equal(devt2devname(dummy, 0, ""), 1); } /* buffer too small */ static void test_devt2devname_length_1(void **state) { char dummy[] = ""; assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 1); } static void test_devt2devname_devt_null(void **state) { char dummy[32]; assert_int_equal(devt2devname(dummy, sizeof(dummy), NULL), 1); } static void test_devt2devname_devt_empty(void **state) { char dummy[32]; assert_int_equal(devt2devname(dummy, sizeof(dummy), ""), 1); } static void test_devt2devname_devt_invalid_1(void **state) { char dummy[32]; assert_int_equal(devt2devname(dummy, sizeof(dummy), "foo"), 1); } static void test_devt2devname_devt_invalid_2(void **state) { char dummy[32]; assert_int_equal(devt2devname(dummy, sizeof(dummy), "1234"), 1); } static void test_devt2devname_devt_invalid_3(void **state) { char dummy[32]; assert_int_equal(devt2devname(dummy, sizeof(dummy), "0:0"), 1); } static void test_devt2devname_real(void **state) { struct udev_enumerate *enm; int r; struct udev_list_entry *first, *item; unsigned int i = 0; if (!sys_dev_block_exists()) skip(); enm = udev_enumerate_new(udev); assert_non_null(enm); r = udev_enumerate_add_match_subsystem(enm, "block"); assert_in_range(r, 0, INT_MAX); r = udev_enumerate_scan_devices(enm); first = udev_enumerate_get_list_entry(enm); udev_list_entry_foreach(item, first) { const char *path = udev_list_entry_get_name(item); struct udev_device *u_dev; dev_t devnum; char devt[BLK_DEV_SIZE]; char devname[FILE_NAME_SIZE]; u_dev = udev_device_new_from_syspath(udev, path); assert_non_null(u_dev); devnum = udev_device_get_devnum(u_dev); snprintf(devt, sizeof(devt), "%d:%d", major(devnum), minor(devnum)); r = devt2devname(devname, sizeof(devname), devt); assert_int_equal(r, 0); assert_string_equal(devname, udev_device_get_sysname(u_dev)); i++; udev_device_unref(u_dev); } udev_enumerate_unref(enm); condlog(2, "devt2devname test passed for %u block devices", i); } static int devt2devname_tests(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_devt2devname_devt_good), cmocka_unit_test(test_devt2devname_devname_null), cmocka_unit_test(test_devt2devname_length_0), cmocka_unit_test(test_devt2devname_length_1), cmocka_unit_test(test_devt2devname_devt_null), cmocka_unit_test(test_devt2devname_devt_empty), cmocka_unit_test(test_devt2devname_devt_invalid_1), cmocka_unit_test(test_devt2devname_devt_invalid_2), cmocka_unit_test(test_devt2devname_devt_invalid_3), cmocka_unit_test(test_devt2devname_real), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += devt2devname_tests(); return ret; } multipath-tools-0.11.1/tests/directio.c000066400000000000000000000601011475246302400200540ustar00rootroot00000000000000/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "wrap64.h" #include "globals.c" #include "../libmultipath/checkers/directio.c" int test_fd = 111; int ioctx_count = 0; struct io_event mock_events[AIO_GROUP_SIZE]; /* same as the checker max */ int ev_off = 0; struct timespec zero_timeout = { .tv_sec = 0 }; struct timespec full_timeout = { .tv_sec = -1 }; 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) { int *blocksize = (int *)argp; if (test_dev) { mock_type(int); return REAL_IOCTL(fd, request, argp); } assert_int_equal(fd, test_fd); /* * On MUSL libc, the "request" arg is an int (glibc: unsigned long). * cmocka casts the args of assert_int_equal() to "unsigned long". * BLKSZGET = 80081270 is sign-extended to ffffffff80081270 * when cast from int to unsigned long on s390x. * BLKSZGET must be cast to "int" and back to "unsigned long", * otherwise the assertion below will fail. */ assert_int_equal(request, (ioctl_request_t)BLKBSZGET); assert_non_null(blocksize); *blocksize = mock_type(int); return 0; } int REAL_FCNTL (int fd, int cmd, long arg); int WRAP_FCNTL (int fd, int cmd, long arg) { if (test_dev) return REAL_FCNTL(fd, cmd, arg); assert_int_equal(fd, test_fd); assert_int_equal(cmd, F_GETFL); return O_DIRECT; } int __real___fxstat(int ver, int fd, struct stat *statbuf); int __wrap___fxstat(int ver, int fd, struct stat *statbuf) { if (test_dev) return __real___fxstat(ver, fd, statbuf); assert_int_equal(fd, test_fd); assert_non_null(statbuf); memset(statbuf, 0, sizeof(struct stat)); return 0; } int __real_io_setup(int maxevents, io_context_t *ctxp); int __wrap_io_setup(int maxevents, io_context_t *ctxp) { int ret = mock_type(int); if (test_dev) assert_int_equal(ret, __real_io_setup(maxevents, ctxp)); ioctx_count++; return ret; } int __real_io_destroy(io_context_t ctx); int __wrap_io_destroy(io_context_t ctx) { int ret = mock_type(int); ioctx_count--; if (test_dev) assert_int_equal(ret, __real_io_destroy(ctx)); return ret; } int __real_io_submit(io_context_t ctx, long nr, struct iocb *ios[]); int __wrap_io_submit(io_context_t ctx, long nr, struct iocb *ios[]) { int ret = mock_type(int); if (test_dev) { struct timespec dev_delay = { .tv_nsec = test_delay }; assert_int_equal(ret, __real_io_submit(ctx, nr, ios)); nanosleep(&dev_delay, NULL); } return ret; } int __real_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); int __wrap_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt) { if (test_dev) return __real_io_cancel(ctx, iocb, evt); else return 0; } int REAL_IO_GETEVENTS(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout); int WRAP_IO_GETEVENTS(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout) { int nr_evs; struct timespec *sleep_tmo; int i; struct io_event *evs; assert_non_null(timeout); nr_evs = mock_type(int); assert_true(nr_evs <= nr); if (!nr_evs) return 0; if (test_dev) { int n = 0; mock_ptr_type(struct timespec *); mock_ptr_type(struct io_event *); condlog(2, "min_nr = %ld nr_evs = %d", min_nr, nr_evs); while (n < nr_evs) { min_nr = min_nr <= nr_evs - n ? min_nr : nr_evs - n; n += REAL_IO_GETEVENTS(ctx, min_nr, nr_evs - n, events + n, timeout); } assert_int_equal(nr_evs, n); } else { sleep_tmo = mock_ptr_type(struct timespec *); if (sleep_tmo) { if (sleep_tmo->tv_sec < 0) nanosleep(timeout, NULL); else nanosleep(sleep_tmo, NULL); } if (nr_evs < 0) { errno = -nr_evs; return -1; } evs = mock_ptr_type(struct io_event *); for (i = 0; i < nr_evs; i++) events[i] = evs[i]; } ev_off -= nr_evs; return nr_evs; } static void return_io_getevents_none(void) { wrap_will_return(WRAP_IO_GETEVENTS, 0); } static void return_io_getevents_nr(struct timespec *ts, int nr, struct async_req **reqs, int *res) { int i, off = 0; for(i = 0; i < nr; i++) { mock_events[i + ev_off].obj = &reqs[i]->io; if (res[i] == 0) mock_events[i + ev_off].res = reqs[i]->blksize; } while (nr > 0) { wrap_will_return(WRAP_IO_GETEVENTS, (nr > 128)? 128 : nr); wrap_will_return(WRAP_IO_GETEVENTS, ts); wrap_will_return(WRAP_IO_GETEVENTS, &mock_events[off + ev_off]); ts = NULL; off += 128; nr -= 128; } if (nr == 0) wrap_will_return(WRAP_IO_GETEVENTS, 0); ev_off += i; } void do_check_state(struct checker *c, int sync, int chk_state) { struct directio_context * ct = (struct directio_context *)c->context; if (!is_running(ct)) will_return(__wrap_io_submit, 1); assert_int_equal(check_state(test_fd, ct, sync, c->timeout), chk_state); if (sync) { assert_int_equal(ev_off, 0); memset(mock_events, 0, sizeof(mock_events)); } } void do_libcheck_pending(struct checker *c, int chk_state) { assert_int_equal(libcheck_pending(c), chk_state); assert_int_equal(ev_off, 0); memset(mock_events, 0, sizeof(mock_events)); } void do_libcheck_reset(int nr_aio_grps) { int count = 0; struct aio_group *aio_grp; list_for_each_entry(aio_grp, &aio_grp_list, node) count++; assert_int_equal(count, nr_aio_grps); for (count = 0; count < nr_aio_grps; count++) will_return(__wrap_io_destroy, 0); libcheck_reset(); assert_true(list_empty(&aio_grp_list)); assert_int_equal(ioctx_count, 0); } static void do_libcheck_init(struct checker *c, int blocksize, int timeout, struct async_req **req) { struct directio_context * ct; c->fd = test_fd; c->timeout = timeout; wrap_will_return(WRAP_IOCTL, blocksize); assert_int_equal(libcheck_init(c), 0); ct = (struct directio_context *)c->context; assert_non_null(ct); assert_non_null(ct->aio_grp); assert_non_null(ct->req); if (req) *req = ct->req; if (!test_dev) /* don't check fake blocksize on real devices */ assert_int_equal(ct->req->blksize, blocksize); } static int is_checker_running(struct checker *c) { struct directio_context * ct = (struct directio_context *)c->context; return is_running(ct); } static struct aio_group *get_aio_grp(struct checker *c) { struct directio_context * ct = (struct directio_context *)c->context; assert_non_null(ct); return ct->aio_grp; } static void check_aio_grp(struct aio_group *aio_grp, int holders, int orphans) { int count = 0; struct list_head *item; list_for_each(item, &aio_grp->orphans) count++; assert_int_equal(holders, aio_grp->holders); assert_int_equal(orphans, count); } /* simple resetting test */ static void test_reset(void **state) { assert_true(list_empty(&aio_grp_list)); do_libcheck_reset(0); } /* tests initializing, then resetting, and then initializing again */ static void test_init_reset_init(void **state) { struct checker c = {.cls = NULL}; struct aio_group *aio_grp, *tmp_grp; assert_int_equal(libcheck_need_wait(&c), false); assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 0, NULL); assert_int_equal(libcheck_need_wait(&c), false); aio_grp = get_aio_grp(&c); check_aio_grp(aio_grp, 1, 0); list_for_each_entry(tmp_grp, &aio_grp_list, node) assert_ptr_equal(aio_grp, tmp_grp); libcheck_free(&c); assert_int_equal(libcheck_need_wait(&c), false); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 0, NULL); assert_int_equal(libcheck_need_wait(&c), false); aio_grp = get_aio_grp(&c); check_aio_grp(aio_grp, 1, 0); list_for_each_entry(tmp_grp, &aio_grp_list, node) assert_ptr_equal(aio_grp, tmp_grp); libcheck_free(&c); assert_int_equal(libcheck_need_wait(&c), false); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); } /* test initializing and then freeing 4096 checkers */ static void test_init_free(void **state) { int i, count = 0; struct checker c[4096] = {{.cls = NULL}}; struct aio_group *aio_grp = NULL; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); for (i = 0; i < 4096; i++) { struct directio_context * ct; if (i % 3 == 0) do_libcheck_init(&c[i], 512, 0, NULL); else if (i % 3 == 1) do_libcheck_init(&c[i], 1024, 0, NULL); else do_libcheck_init(&c[i], 4096, 0, NULL); ct = (struct directio_context *)c[i].context; assert_non_null(ct->aio_grp); if ((i & 1023) == 0) aio_grp = ct->aio_grp; else { assert_ptr_equal(ct->aio_grp, aio_grp); assert_int_equal(aio_grp->holders, (i & 1023) + 1); } } count = 0; list_for_each_entry(aio_grp, &aio_grp_list, node) count++; assert_int_equal(count, 4); for (i = 0; i < 4096; i++) { struct directio_context * ct = (struct directio_context *)c[i].context; aio_grp = ct->aio_grp; libcheck_free(&c[i]); assert_int_equal(aio_grp->holders, 1023 - (i & 1023)); } list_for_each_entry(aio_grp, &aio_grp_list, node) assert_int_equal(aio_grp->holders, 0); do_libcheck_reset(4); } /* check mixed initializing and freeing 4096 checkers */ static void test_multi_init_free(void **state) { int i, count; struct checker c[4096] = {{.cls = NULL}}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); for (count = 0, i = 0; i < 4096; count++) { /* usually init, but occasionally free checkers */ if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { do_libcheck_init(&c[i], 4096, 0, NULL); i++; } else { i--; libcheck_free(&c[i]); } } count = 0; list_for_each_entry(aio_grp, &aio_grp_list, node) { assert_int_equal(aio_grp->holders, 1024); count++; } assert_int_equal(count, 4); for (count = 0, i = 4096; i > 0; count++) { /* usually free, but occasionally init checkers */ if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { i--; libcheck_free(&c[i]); } else { do_libcheck_init(&c[i], 4096, 0, NULL); i++; } } do_libcheck_reset(4); } /* simple single checker sync test */ static void test_check_state_simple(void **state) { struct checker c = {.cls = NULL}; struct async_req *req; int res = 0; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 30, &req); return_io_getevents_nr(NULL, 1, &req, &res); do_check_state(&c, 1, PATH_UP); assert_int_equal(libcheck_need_wait(&c), false); libcheck_free(&c); do_libcheck_reset(1); } /* test sync timeout */ static void test_check_state_timeout(void **state) { struct checker c = {.cls = NULL}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 30, NULL); aio_grp = get_aio_grp(&c); return_io_getevents_none(); do_check_state(&c, 1, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); libcheck_free(&c); do_libcheck_reset(1); } /* test async timeout */ static void test_check_state_async_timeout(void **state) { struct checker c = {.cls = NULL}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 3, NULL); aio_grp = get_aio_grp(&c); do_check_state(&c, 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), true); return_io_getevents_none(); do_libcheck_pending(&c, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); nanosleep(&one_sec, NULL); do_check_state(&c, 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); return_io_getevents_none(); do_libcheck_pending(&c, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); nanosleep(&one_sec, NULL); do_check_state(&c, 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); return_io_getevents_none(); do_libcheck_pending(&c, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); nanosleep(&one_sec, NULL); do_check_state(&c, 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); return_io_getevents_none(); do_libcheck_pending(&c, PATH_DOWN); assert_int_equal(libcheck_need_wait(&c), false); check_aio_grp(aio_grp, 1, 0); libcheck_free(&c); do_libcheck_reset(1); } /* test freeing checkers with outstanding requests */ static void test_free_with_pending(void **state) { struct checker c[2] = {{.cls = NULL}}; struct aio_group *aio_grp; struct async_req *req; int res = 0; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c[0], 4096, 30, &req); do_libcheck_init(&c[1], 4096, 30, NULL); aio_grp = get_aio_grp(c); do_check_state(&c[0], 0, PATH_PENDING); do_check_state(&c[1], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), true); assert_int_equal(libcheck_need_wait(&c[1]), true); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), false); assert_int_equal(libcheck_need_wait(&c[1]), true); return_io_getevents_nr(NULL, 1, &req, &res); do_libcheck_pending(&c[1], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), false); assert_int_equal(libcheck_need_wait(&c[1]), false); assert_true(is_checker_running(&c[0])); assert_true(is_checker_running(&c[1])); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 1, 0); libcheck_free(&c[1]); check_aio_grp(aio_grp, 1, 1); /* cancel doesn't remove request */ do_libcheck_reset(1); } /* test removing orphaned aio_group on free */ static void test_orphaned_aio_group(void **state) { struct checker c[AIO_GROUP_SIZE] = {{.cls = NULL}}; struct aio_group *aio_grp, *tmp_grp; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < AIO_GROUP_SIZE; i++) { do_libcheck_init(&c[i], 4096, 30, NULL); do_check_state(&c[i], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[i]), true); return_io_getevents_none(); do_libcheck_pending(&c[i], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[i]), false); } aio_grp = get_aio_grp(c); check_aio_grp(aio_grp, AIO_GROUP_SIZE, 0); i = 0; list_for_each_entry(tmp_grp, &aio_grp_list, node) i++; assert_int_equal(i, 1); for (i = 0; i < AIO_GROUP_SIZE; i++) { assert_true(is_checker_running(&c[i])); if (i == AIO_GROUP_SIZE - 1) { /* remove the orphaned group and create a new one */ will_return(__wrap_io_destroy, 0); } libcheck_free(&c[i]); } do_libcheck_reset(0); } /* test sync timeout with failed cancel and cleanup by another * checker */ static void test_timeout_cancel_failed(void **state) { struct checker c[2] = {{.cls = NULL}}; struct aio_group *aio_grp; struct async_req *reqs[2]; int res[] = {0,0}; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, 30, &reqs[i]); aio_grp = get_aio_grp(c); return_io_getevents_none(); do_check_state(&c[0], 1, PATH_DOWN); assert_int_equal(libcheck_need_wait(&c[0]), false); assert_true(is_checker_running(&c[0])); check_aio_grp(aio_grp, 2, 0); return_io_getevents_none(); do_check_state(&c[0], 1, PATH_DOWN); assert_int_equal(libcheck_need_wait(&c[0]), false); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 1, &reqs[0], &res[0]); return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); do_check_state(&c[1], 1, PATH_UP); assert_int_equal(libcheck_need_wait(&c[1]), false); do_check_state(&c[0], 1, PATH_UP); assert_int_equal(libcheck_need_wait(&c[0]), false); for (i = 0; i < 2; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test async timeout with failed cancel and cleanup by another * checker */ static void test_async_timeout_cancel_failed(void **state) { struct checker c[2] = {{.cls = NULL}}; struct async_req *reqs[2]; int res[] = {0,0}; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, 2, &reqs[i]); do_check_state(&c[0], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), true); do_check_state(&c[1], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[1]), true); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), false); return_io_getevents_none(); do_libcheck_pending(&c[1], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[1]), false); nanosleep(&one_sec, NULL); do_check_state(&c[0], 0, PATH_PENDING); do_check_state(&c[1], 0, PATH_PENDING); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_PENDING); return_io_getevents_none(); do_libcheck_pending(&c[1], PATH_PENDING); nanosleep(&one_sec, NULL); do_check_state(&c[0], 0, PATH_PENDING); do_check_state(&c[1], 0, PATH_PENDING); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_DOWN); assert_int_equal(libcheck_need_wait(&c[0]), false); if (!test_dev) { /* can't pick which even gets returned on real devices */ return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); do_libcheck_pending(&c[1], PATH_UP); assert_int_equal(libcheck_need_wait(&c[1]), false); } nanosleep(&one_sec, NULL); do_check_state(&c[0], 0, PATH_PENDING); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_DOWN); assert_true(is_checker_running(&c[0])); nanosleep(&one_sec, NULL); do_check_state(&c[1], 0, PATH_PENDING); do_check_state(&c[0], 0, PATH_PENDING); return_io_getevents_nr(NULL, 2, reqs, res); do_libcheck_pending(&c[1], PATH_UP); do_libcheck_pending(&c[0], PATH_UP); for (i = 0; i < 2; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test orphaning a request, and having another checker clean it up */ static void test_orphan_checker_cleanup(void **state) { struct checker c[2] = {{.cls = NULL}}; struct async_req *reqs[2]; int res[] = {0,0}; struct aio_group *aio_grp; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, 30, &reqs[i]); aio_grp = get_aio_grp(c); do_check_state(&c[0], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), true); return_io_getevents_none(); do_libcheck_pending(&c[0], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[0]), false); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); assert_int_equal(libcheck_need_wait(&c[0]), false); check_aio_grp(aio_grp, 2, 1); do_check_state(&c[1], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[1]), true); return_io_getevents_nr(NULL, 2, reqs, res); do_libcheck_pending(&c[1], PATH_UP); assert_int_equal(libcheck_need_wait(&c[1]), false); check_aio_grp(aio_grp, 1, 0); libcheck_free(&c[1]); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); } /* test orphaning a request, and having reset clean it up */ static void test_orphan_reset_cleanup(void **state) { struct checker c; struct aio_group *orphan_aio_grp, *tmp_aio_grp; int found, count; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, 30, NULL); orphan_aio_grp = get_aio_grp(&c); do_check_state(&c, 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), true); return_io_getevents_none(); do_libcheck_pending(&c, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c), false); check_aio_grp(orphan_aio_grp, 1, 0); libcheck_free(&c); check_aio_grp(orphan_aio_grp, 1, 1); found = count = 0; list_for_each_entry(tmp_aio_grp, &aio_grp_list, node) { count++; if (tmp_aio_grp == orphan_aio_grp) found = 1; } assert_int_equal(count, 1); assert_int_equal(found, 1); do_libcheck_reset(1); } /* test checkers with different blocksizes */ static void test_check_state_blksize(void **state) { int i; struct checker c[3] = {{.cls = NULL}}; int blksize[] = {4096, 1024, 512}; struct async_req *reqs[3]; int res[] = {0,1,0}; int chk_state[] = {PATH_UP, PATH_UP, PATH_UP}; /* can't pick event return state on real devices */ if (!test_dev) chk_state[1] = PATH_DOWN; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 3; i++) do_libcheck_init(&c[i], blksize[i], 30, &reqs[i]); for (i = 0; i < 3; i++) { return_io_getevents_nr(NULL, 1, &reqs[i], &res[i]); do_check_state(&c[i], 1, chk_state[i]); assert_int_equal(libcheck_need_wait(&c[i]), false); } for (i = 0; i < 3; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test async checkers pending and getting resolved by another checker * as well as the loops for getting multiple events */ static void test_check_state_async(void **state) { int i; struct checker c[257] = {{.cls = NULL}}; struct async_req *reqs[257]; int res[257] = {0}; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 257; i++) do_libcheck_init(&c[i], 4096, 30, &reqs[i]); for (i = 0; i < 256; i++) { do_check_state(&c[i], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[i]), true); return_io_getevents_none(); do_libcheck_pending(&c[i], PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[i]), false); assert_true(is_checker_running(&c[i])); } do_check_state(&c[256], 0, PATH_PENDING); assert_int_equal(libcheck_need_wait(&c[256]), true); return_io_getevents_nr(&full_timeout, 257, reqs, res); do_libcheck_pending(&c[256], PATH_UP); assert_int_equal(libcheck_need_wait(&c[256]), false); assert_false(is_checker_running(&c[256])); libcheck_free(&c[256]); for (i = 0; i < 256; i++) { do_check_state(&c[i], 0, PATH_UP); assert_int_equal(libcheck_need_wait(&c[i]), false); assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } static int setup(void **state) { char *dl = getenv("DIO_TEST_DELAY"); test_dev = getenv("DIO_TEST_DEV"); if (test_dev) { condlog(2, "%s: opening %s", __func__, test_dev); test_fd = open(test_dev, O_RDONLY); if (dl) { char *e; long int d = strtol(dl, &e, 10); if (*e == '\0' && d >= 0 && (d * 1000) < (long)UINT_MAX) test_delay = d * 1000; else { condlog(1, "DIO_TEST_DELAY=%s is invalid", dl); return 1; } } condlog(2, "%s: delay = %u us", __func__, test_delay / 1000); } if (test_fd < 0) { fail_msg("cannot open %s: %m", test_dev); return 1; } return 0; } static int teardown(void **state) { if (test_dev) { assert_true(test_fd > 0); assert_int_equal(close(test_fd), 0); } return 0; } int test_directio(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_reset), cmocka_unit_test(test_init_reset_init), cmocka_unit_test(test_init_free), cmocka_unit_test(test_multi_init_free), cmocka_unit_test(test_check_state_simple), cmocka_unit_test(test_check_state_timeout), cmocka_unit_test(test_check_state_async_timeout), cmocka_unit_test(test_free_with_pending), cmocka_unit_test(test_timeout_cancel_failed), cmocka_unit_test(test_async_timeout_cancel_failed), cmocka_unit_test(test_orphan_checker_cleanup), cmocka_unit_test(test_orphan_reset_cleanup), cmocka_unit_test(test_check_state_blksize), cmocka_unit_test(test_check_state_async), cmocka_unit_test(test_orphaned_aio_group), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(2); ret += test_directio(); return ret; } multipath-tools-0.11.1/tests/dmevents.c000066400000000000000000000646611475246302400201160ustar00rootroot00000000000000/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include "structs.h" #include "structs_vec.h" #include "wrap64.h" #include "globals.c" /* I have to do this to get at the static variables */ #include "../multipathd/dmevents.c" /* pretend dm device */ struct dm_device { char name[WWID_SIZE]; /* is this a mpath device, or other dm device */ int is_mpath; uint32_t evt_nr; /* tracks the event number when the multipath device was updated */ uint32_t update_nr; }; struct test_data { struct vectors vecs; vector dm_devices; struct dm_names *names; }; struct test_data data; /* Add a pretend dm device, or update its event number. This is used to build * up the dm devices that the dmevents code queries with dm_task_get_names, * dm_geteventnr, and dm_is_mpath */ int add_dm_device_event(char *name, int is_mpath, uint32_t evt_nr) { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) { if (strcmp(name, dev->name) == 0) { dev->evt_nr = evt_nr; return 0; } } dev = (struct dm_device *)malloc(sizeof(struct dm_device)); if (!dev){ condlog(0, "Testing error mallocing dm_device"); return -1; } strncpy(dev->name, name, WWID_SIZE); dev->name[WWID_SIZE - 1] = 0; dev->is_mpath = is_mpath; dev->evt_nr = evt_nr; if (!vector_alloc_slot(data.dm_devices)) { condlog(0, "Testing error setting dm_devices slot"); free(dev); return -1; } vector_set_slot(data.dm_devices, dev); return 0; } /* helper function for pretend dm devices */ struct dm_device *find_dm_device(const char *name) { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) if (strcmp(name, dev->name) == 0) return dev; return NULL; } /* helper function for pretend dm devices */ int remove_dm_device_event(const char *name) { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) { if (strcmp(name, dev->name) == 0) { vector_del_slot(data.dm_devices, i); free(dev); return 0; } } return -1; } /* helper function for pretend dm devices */ void remove_all_dm_device_events(void) { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) free(dev); vector_reset(data.dm_devices); } static inline size_t align_val(size_t val) { return (val + 7) & ~7; } static inline void *align_ptr(void *ptr) { return (void *)align_val((size_t)ptr); } /* copied off of list_devices in dm-ioctl.c except that it uses * the pretend dm devices, and saves the output to the test_data * structure */ struct dm_names *build_dm_names(void) { struct dm_names *names, *np, *old_np = NULL; uint32_t *event_nr; struct dm_device *dev; int i, size = 0; if (VECTOR_SIZE(data.dm_devices) == 0) { names = (struct dm_names *)malloc(sizeof(struct dm_names)); if (!names) { condlog(0, "Testing error allocating empty dm_names"); return NULL; } names->dev = 0; names->next = 0; return names; } vector_foreach_slot(data.dm_devices, dev, i) { size += align_val(offsetof(struct dm_names, name) + strlen(dev->name) + 1); size += align_val(sizeof(uint32_t)); } names = (struct dm_names *)malloc(size); if (!names) { condlog(0, "Testing error allocating dm_names"); return NULL; } np = names; vector_foreach_slot(data.dm_devices, dev, i) { if (old_np) old_np->next = (uint32_t) ((uintptr_t) np - (uintptr_t) old_np); np->dev = 1; np->next = 0; strcpy(np->name, dev->name); old_np = np; event_nr = align_ptr(np->name + strlen(dev->name) + 1); *event_nr = dev->evt_nr; np = align_ptr(event_nr + 1); } assert_int_equal((char *)np - (char *)names, size); return names; } static bool setup_done; static int setup(void **state) { if (dmevent_poll_supported()) { data.dm_devices = vector_alloc(); *state = &data; } else *state = NULL; setup_done = true; return 0; } static int teardown(void **state) { struct dm_device *dev; int i; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) return 0; vector_foreach_slot(datap->dm_devices, dev, i) free(dev); vector_free(datap->dm_devices); datap = NULL; return 0; } int WRAP_OPEN(const char *pathname, int flags) { assert_ptr_equal(pathname, "/dev/mapper/control"); assert_int_equal(flags, O_RDWR); return mock_type(int); } /* We never check the result of the close(), so there's no need to * to mock a return value */ int __wrap_close(int fd) { assert_int_equal(fd, waiter->fd); return 0; } /* the pretend dm device code checks the input and supplies the * return value, so there's no need to do that here */ int __wrap_dm_is_mpath(const char *name) { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) if (strcmp(name, dev->name) == 0) return dev->is_mpath; return 0; } /* either get return info from the pretend dm device, or * override it to test -1 return */ int __wrap_dm_geteventnr(const char *name) { struct dm_device *dev; int fail = mock_type(int); if (fail) return -1; dev = find_dm_device(name); if (dev) { /* simulate updating device state after adding it */ dev->update_nr = dev->evt_nr; return dev->evt_nr; } return -1; } int WRAP_IOCTL(int fd, unsigned long request, void *argp) { condlog(1, "%s %ld", __func__, request); assert_int_equal(fd, waiter->fd); assert_int_equal(request, DM_DEV_ARM_POLL); return mock_type(int); } struct dm_task *__wrap_libmp_dm_task_create(int task) { assert_int_equal(task, DM_DEVICE_LIST); return mock_type(struct dm_task *); } int __real_dm_task_run(struct dm_task *dmt); int __wrap_dm_task_run(struct dm_task *dmt) { if (!setup_done) return __real_dm_task_run(dmt); assert_ptr_equal((struct test_data *)dmt, &data); return mock_type(int); } /* either get return info from the pretend dm device, or * override it to test NULL return */ struct dm_names * __wrap_dm_task_get_names(struct dm_task *dmt) { int good = mock_type(int); assert_ptr_equal((struct test_data *)dmt, &data); if (data.names) { condlog(0, "Testing error. data.names already allocated"); return NULL; } if (!good) return NULL; data.names = build_dm_names(); return data.names; } void __real_dm_task_destroy(struct dm_task *dmt); void __wrap_dm_task_destroy(struct dm_task *dmt) { if (!setup_done) return __real_dm_task_destroy(dmt); assert_ptr_equal((struct test_data *)dmt, &data); if (data.names) { free(data.names); data.names = NULL; } } int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) { assert_int_equal(nfds, 1); assert_int_equal(timeout, -1); assert_int_equal(fds->fd, waiter->fd); assert_int_equal(fds->events, POLLIN); return mock_type(int); } void __wrap_remove_map_by_alias(const char *alias, struct vectors * vecs) { check_expected(alias); assert_ptr_equal(vecs, waiter->vecs); } /* pretend update the pretend dm devices. If fail is set, it * simulates having the dm device removed. Otherwise it just sets * update_nr to record when the update happened */ int __wrap_update_multipath(struct vectors *vecs, char *mapname) { int fail; check_expected(mapname); assert_ptr_equal(vecs, waiter->vecs); fail = mock_type(int); if (fail) { assert_int_equal(remove_dm_device_event(mapname), 0); return fail; } else { struct dm_device *dev; int i; vector_foreach_slot(data.dm_devices, dev, i) { if (strcmp(mapname, dev->name) == 0) { dev->update_nr = dev->evt_nr; return 0; } } fail(); } return fail; } /* helper function used to check if the dmevents list of devices * includes a specific device. To make sure that dmevents is * in the correct state after running a function */ struct dev_event *find_dmevents(const char *name) { struct dev_event *dev_evt; int i; vector_foreach_slot(waiter->events, dev_evt, i) if (!strcmp(dev_evt->name, name)) return dev_evt; return NULL; } /* null vecs pointer when initialized dmevents */ static void test_init_waiter_bad0(void **state) { /* this boilerplate code just skips the test if * dmevents polling is not supported */ struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(init_dmevent_waiter(NULL), -1); } /* fail to open /dev/mapper/control */ static void test_init_waiter_bad1(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); wrap_will_return(WRAP_OPEN, -1); assert_int_equal(init_dmevent_waiter(&datap->vecs), -1); assert_ptr_equal(waiter, NULL); } /* waiter remains initialized after this test */ static void test_init_waiter_good0(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); wrap_will_return(WRAP_OPEN, 2); assert_int_equal(init_dmevent_waiter(&datap->vecs), 0); assert_ptr_not_equal(waiter, NULL); } /* No dm device named foo */ static void test_watch_dmevents_bad0(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(watch_dmevents("foo"), -1); assert_ptr_equal(find_dmevents("foo"), NULL); } /* foo is not a multipath device */ static void test_watch_dmevents_bad1(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(add_dm_device_event("foo", 0, 5), 0); assert_int_equal(watch_dmevents("foo"), -1); assert_ptr_equal(find_dmevents("foo"), NULL); } /* failed getting the dmevent number */ static void test_watch_dmevents_bad2(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); will_return(__wrap_dm_geteventnr, -1); assert_int_equal(watch_dmevents("foo"), -1); assert_ptr_equal(find_dmevents("foo"), NULL); } /* verify that you can watch and unwatch dm multipath device "foo" */ static void test_watch_dmevents_good0(void **state) { struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); /* verify foo is being watched */ dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); assert_int_equal(VECTOR_SIZE(waiter->events), 1); unwatch_dmevents("foo"); /* verify foo is no longer being watched */ assert_int_equal(VECTOR_SIZE(waiter->events), 0); assert_ptr_equal(find_dmevents("foo"), NULL); } /* verify that if you try to watch foo multiple times, it only * is placed on the waiter list once */ static void test_watch_dmevents_good1(void **state) { struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); assert_int_equal(add_dm_device_event("foo", 1, 6), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 6); assert_int_equal(dev_evt->action, EVENT_NOTHING); assert_int_equal(VECTOR_SIZE(waiter->events), 1); unwatch_dmevents("foo"); assert_int_equal(VECTOR_SIZE(waiter->events), 0); assert_ptr_equal(find_dmevents("foo"), NULL); } /* watch and then unwatch multiple devices */ static void test_watch_dmevents_good2(void **state) { struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); unwatch_all_dmevents(); remove_all_dm_device_events(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); assert_int_equal(add_dm_device_event("bar", 1, 7), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); assert_ptr_equal(find_dmevents("bar"), NULL); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("bar"), 0); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev_evt = find_dmevents("bar"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 7); assert_int_equal(dev_evt->action, EVENT_NOTHING); assert_int_equal(VECTOR_SIZE(waiter->events), 2); unwatch_all_dmevents(); assert_int_equal(VECTOR_SIZE(waiter->events), 0); assert_ptr_equal(find_dmevents("foo"), NULL); assert_ptr_equal(find_dmevents("bar"), NULL); } /* dm_task_create fails */ static void test_get_events_bad0(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); unwatch_all_dmevents(); remove_all_dm_device_events(); will_return(__wrap_libmp_dm_task_create, NULL); assert_int_equal(dm_get_events(), -1); } /* dm_task_run fails */ static void test_get_events_bad1(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 0); assert_int_equal(dm_get_events(), -1); } /* dm_task_get_names fails */ static void test_get_events_bad2(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 0); assert_int_equal(dm_get_events(), -1); } /* If the device isn't being watched, dm_get_events returns NULL */ static void test_get_events_good0(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); assert_int_equal(dm_get_events(), 0); assert_ptr_equal(find_dmevents("foo"), NULL); assert_int_equal(VECTOR_SIZE(waiter->events), 0); } /* There are 5 dm devices. 4 of them are multipath devices. * Only 3 of them are being watched. "foo" has a new event * "xyzzy" gets removed. Nothing happens to bar. Verify * that all the events are properly set, and that nothing * happens with the two devices that aren't being watched */ static void test_get_events_good1(void **state) { struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); assert_int_equal(add_dm_device_event("bar", 1, 7), 0); assert_int_equal(add_dm_device_event("baz", 1, 12), 0); assert_int_equal(add_dm_device_event("qux", 0, 4), 0); assert_int_equal(add_dm_device_event("xyzzy", 1, 8), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("bar"), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("xyzzy"), 0); assert_int_equal(add_dm_device_event("foo", 1, 6), 0); assert_int_equal(remove_dm_device_event("xyzzy"), 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); assert_int_equal(dm_get_events(), 0); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 6); assert_int_equal(dev_evt->action, EVENT_UPDATE); dev_evt = find_dmevents("bar"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 7); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev_evt = find_dmevents("xyzzy"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 8); assert_int_equal(dev_evt->action, EVENT_REMOVE); assert_ptr_equal(find_dmevents("baz"), NULL); assert_ptr_equal(find_dmevents("qux"), NULL); assert_int_equal(VECTOR_SIZE(waiter->events), 3); } /* poll does not return an event. nothing happens. The * devices remain after this test */ static void test_dmevent_loop_bad0(void **state) { struct dm_device *dev; struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); unwatch_all_dmevents(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); assert_int_equal(add_dm_device_event("foo", 1, 6), 0); will_return(__wrap_poll, 0); assert_int_equal(dmevent_loop(), 1); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("foo"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 6); assert_int_equal(dev->update_nr, 5); } /* arm_dm_event_poll's ioctl fails. Nothing happens */ static void test_dmevent_loop_bad1(void **state) { struct dm_device *dev; struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, -1); assert_int_equal(dmevent_loop(), 1); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("foo"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 6); assert_int_equal(dev->update_nr, 5); } /* dm_get_events fails. Nothing happens */ static void test_dmevent_loop_bad2(void **state) { struct dm_device *dev; struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, 0); will_return(__wrap_libmp_dm_task_create, NULL); assert_int_equal(dmevent_loop(), 1); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 5); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("foo"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 6); assert_int_equal(dev->update_nr, 5); } /* verify dmevent_loop runs successfully when no devices are being * watched */ static void test_dmevent_loop_good0(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); unwatch_all_dmevents(); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); assert_int_equal(dmevent_loop(), 1); } /* Watch 3 devices, where one device has an event (foo), one device is * removed (xyzzy), and one device does nothing (bar). Verify that * the device with the event gets updated, the device that is removed * gets unwatched, and the device with no events stays the same. * The devices remain after this test */ static void test_dmevent_loop_good1(void **state) { struct dm_device *dev; struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); remove_all_dm_device_events(); unwatch_all_dmevents(); assert_int_equal(add_dm_device_event("foo", 1, 5), 0); assert_int_equal(add_dm_device_event("bar", 1, 7), 0); assert_int_equal(add_dm_device_event("baz", 1, 12), 0); assert_int_equal(add_dm_device_event("xyzzy", 1, 8), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("foo"), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("bar"), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("xyzzy"), 0); assert_int_equal(add_dm_device_event("foo", 1, 6), 0); assert_int_equal(remove_dm_device_event("xyzzy"), 0); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); expect_string(__wrap_update_multipath, mapname, "foo"); will_return(__wrap_update_multipath, 0); expect_string(__wrap_remove_map_by_alias, alias, "xyzzy"); assert_int_equal(dmevent_loop(), 1); assert_int_equal(VECTOR_SIZE(waiter->events), 2); assert_int_equal(VECTOR_SIZE(data.dm_devices), 3); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 6); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("foo"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 6); assert_int_equal(dev->update_nr, 6); dev_evt = find_dmevents("bar"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 7); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("bar"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 7); assert_int_equal(dev->update_nr, 7); assert_ptr_equal(find_dmevents("xyzzy"), NULL); assert_ptr_equal(find_dm_device("xyzzy"), NULL); } /* watch another dm device and add events to two of them, so bar and * baz have new events, and foo doesn't. Set update_multipath to * fail for baz. Verify that baz is unwatched, bar is updated, and * foo stays the same. */ static void test_dmevent_loop_good2(void **state) { struct dm_device *dev; struct dev_event *dev_evt; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(add_dm_device_event("bar", 1, 9), 0); will_return(__wrap_dm_geteventnr, 0); assert_int_equal(watch_dmevents("baz"), 0); assert_int_equal(add_dm_device_event("baz", 1, 14), 0); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); expect_string(__wrap_update_multipath, mapname, "bar"); will_return(__wrap_update_multipath, 0); expect_string(__wrap_update_multipath, mapname, "baz"); will_return(__wrap_update_multipath, 1); assert_int_equal(dmevent_loop(), 1); assert_int_equal(VECTOR_SIZE(waiter->events), 2); assert_int_equal(VECTOR_SIZE(data.dm_devices), 2); dev_evt = find_dmevents("foo"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 6); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("foo"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 6); assert_int_equal(dev->update_nr, 6); dev_evt = find_dmevents("bar"); assert_ptr_not_equal(dev_evt, NULL); assert_int_equal(dev_evt->evt_nr, 9); assert_int_equal(dev_evt->action, EVENT_NOTHING); dev = find_dm_device("bar"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 9); assert_int_equal(dev->update_nr, 9); assert_ptr_equal(find_dmevents("baz"), NULL); assert_ptr_equal(find_dm_device("baz"), NULL); } /* remove dm device foo, and unwatch events on bar. Verify that * foo is cleaned up and unwatched, and bar is no longer updated */ static void test_dmevent_loop_good3(void **state) { struct dm_device *dev; struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); assert_int_equal(remove_dm_device_event("foo"), 0); unwatch_dmevents("bar"); will_return(__wrap_poll, 1); wrap_will_return(WRAP_IOCTL, 0); will_return(__wrap_libmp_dm_task_create, &data); will_return(__wrap_dm_task_run, 1); will_return(__wrap_dm_task_get_names, 1); expect_string(__wrap_remove_map_by_alias, alias, "foo"); assert_int_equal(dmevent_loop(), 1); assert_int_equal(VECTOR_SIZE(waiter->events), 0); assert_int_equal(VECTOR_SIZE(data.dm_devices), 1); dev = find_dm_device("bar"); assert_ptr_not_equal(dev, NULL); assert_int_equal(dev->evt_nr, 9); assert_int_equal(dev->update_nr, 9); assert_ptr_equal(find_dmevents("foo"), NULL); assert_ptr_equal(find_dmevents("bar"), NULL); assert_ptr_equal(find_dm_device("foo"), NULL); } /* verify that rearming the dmevents polling works */ static void test_arm_poll(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); wrap_will_return(WRAP_IOCTL, 0); assert_int_equal(arm_dm_event_poll(waiter->fd), 0); } /* verify that the waiter is cleaned up */ static void test_cleanup_waiter(void **state) { struct test_data *datap = (struct test_data *)(*state); if (datap == NULL) skip(); cleanup_dmevent_waiter(); assert_ptr_equal(waiter, NULL); } int test_dmevents(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_init_waiter_bad0), cmocka_unit_test(test_init_waiter_bad1), cmocka_unit_test(test_init_waiter_good0), cmocka_unit_test(test_watch_dmevents_bad0), cmocka_unit_test(test_watch_dmevents_bad1), cmocka_unit_test(test_watch_dmevents_bad2), cmocka_unit_test(test_watch_dmevents_good0), cmocka_unit_test(test_watch_dmevents_good1), cmocka_unit_test(test_watch_dmevents_good2), cmocka_unit_test(test_get_events_bad0), cmocka_unit_test(test_get_events_bad1), cmocka_unit_test(test_get_events_bad2), cmocka_unit_test(test_get_events_good0), cmocka_unit_test(test_get_events_good1), cmocka_unit_test(test_arm_poll), cmocka_unit_test(test_dmevent_loop_bad0), cmocka_unit_test(test_dmevent_loop_bad1), cmocka_unit_test(test_dmevent_loop_bad2), cmocka_unit_test(test_dmevent_loop_good0), cmocka_unit_test(test_dmevent_loop_good1), cmocka_unit_test(test_dmevent_loop_good2), cmocka_unit_test(test_dmevent_loop_good3), cmocka_unit_test(test_cleanup_waiter), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_dmevents(); return ret; } multipath-tools-0.11.1/tests/features.c000066400000000000000000000326661475246302400201070ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include "../libmultipath/propsel.c" #include "globals.c" static void test_af_null_features_ptr(void **state) { assert_int_equal(add_feature(NULL, "test"), 1); } static void af_helper(const char *features_start, const char *addition, const char *features_end, int result) { char *f = NULL, *orig = NULL; if (features_start) { f = strdup(features_start); assert_non_null(f); orig = f; } assert_int_equal(add_feature(&f, addition), result); if (result != 0 || features_end == NULL) assert_ptr_equal(orig, f); else assert_string_equal(f, features_end); free(f); } static void test_af_null_addition1(void **state) { af_helper("0", NULL, NULL, 0); } static void test_af_null_addition2(void **state) { af_helper("1 queue_if_no_path", NULL, NULL, 0); } static void test_af_empty_addition(void **state) { af_helper("2 pg_init_retries 5", "", NULL, 0); } static void test_af_invalid_addition1(void **state) { af_helper("2 pg_init_retries 5", " ", NULL, 1); } static void test_af_invalid_addition2(void **state) { af_helper("2 pg_init_retries 5", "\tbad", NULL, 1); } static void test_af_invalid_addition3(void **state) { af_helper("2 pg_init_retries 5", "bad ", NULL, 1); } static void test_af_invalid_addition4(void **state) { af_helper("2 pg_init_retries 5", " bad ", NULL, 1); } static void test_af_null_features1(void **state) { af_helper(NULL, "test", "1 test", 0); } static void test_af_null_features2(void **state) { af_helper(NULL, "test\t more", "2 test\t more", 0); } static void test_af_null_features3(void **state) { af_helper(NULL, "test\neven\tmore", "3 test\neven\tmore", 0); } static void test_af_already_exists1(void **state) { af_helper("4 this is a test", "test", NULL, 0); } static void test_af_already_exists2(void **state) { af_helper("5 contest testy intestine test retest", "test", NULL, 0); } static void test_af_almost_exists(void **state) { af_helper("3 contest testy intestine", "test", "4 contest testy intestine test", 0); } static void test_af_bad_features1(void **state) { af_helper("bad", "test", NULL, 1); } static void test_af_bad_features2(void **state) { af_helper("1bad", "test", NULL, 1); } static void test_af_add1(void **state) { af_helper("0", "test", "1 test", 0); } static void test_af_add2(void **state) { af_helper("0", "this is a test", "4 this is a test", 0); } static void test_af_add3(void **state) { af_helper("1 features", "more values", "3 features more values", 0); } static void test_af_add4(void **state) { af_helper("2 one\ttwo", "three\t four", "4 one\ttwo three\t four", 0); } static int test_add_features(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_af_null_features_ptr), cmocka_unit_test(test_af_null_addition1), cmocka_unit_test(test_af_null_addition2), cmocka_unit_test(test_af_empty_addition), cmocka_unit_test(test_af_invalid_addition1), cmocka_unit_test(test_af_invalid_addition2), cmocka_unit_test(test_af_invalid_addition3), cmocka_unit_test(test_af_invalid_addition4), cmocka_unit_test(test_af_null_features1), cmocka_unit_test(test_af_null_features2), cmocka_unit_test(test_af_null_features3), cmocka_unit_test(test_af_already_exists1), cmocka_unit_test(test_af_already_exists2), cmocka_unit_test(test_af_almost_exists), cmocka_unit_test(test_af_bad_features1), cmocka_unit_test(test_af_bad_features2), cmocka_unit_test(test_af_add1), cmocka_unit_test(test_af_add2), cmocka_unit_test(test_af_add3), cmocka_unit_test(test_af_add4), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void test_rf_null_features_ptr(void **state) { assert_int_equal(remove_feature(NULL, "test"), 1); } static void test_rf_null_features(void **state) { char *f = NULL; assert_int_equal(remove_feature(&f, "test"), 1); } static void rf_helper(const char *features_start, const char *removal, const char *features_end, int result) { char *f = strdup(features_start); char *orig = f; assert_non_null(f); assert_int_equal(remove_feature(&f, removal), result); if (result != 0 || features_end == NULL) assert_ptr_equal(orig, f); else assert_string_equal(f, features_end); free(f); } static void test_rf_null_removal(void **state) { rf_helper("1 feature", NULL, NULL, 0); } static void test_rf_empty_removal(void **state) { rf_helper("1 feature", "", NULL, 0); } static void test_rf_invalid_removal1(void **state) { rf_helper("1 feature", " ", NULL, 1); } static void test_rf_invalid_removal2(void **state) { rf_helper("1 feature", " bad", NULL, 1); } static void test_rf_invalid_removal3(void **state) { rf_helper("1 feature", "bad\n", NULL, 1); } static void test_rf_invalid_removal4(void **state) { rf_helper("1 feature", "\tbad \n", NULL, 1); } static void test_rf_bad_features1(void **state) { rf_helper("invalid feature test string", "test", NULL, 1); } static void test_rf_bad_features2(void **state) { rf_helper("2no space test", "test", NULL, 1); } static void test_rf_missing_removal1(void **state) { rf_helper("0", "test", NULL, 0); } static void test_rf_missing_removal2(void **state) { rf_helper("1 detest", "test", NULL, 0); } static void test_rf_missing_removal3(void **state) { rf_helper("4 testing one two three", "test", NULL, 0); } static void test_rf_missing_removal4(void **state) { rf_helper("1 contestant", "test", NULL, 0); } static void test_rf_missing_removal5(void **state) { rf_helper("3 testament protest detestable", "test", NULL, 0); } static void test_rf_remove_all_features1(void **state) { rf_helper("1 test", "test", "0", 0); } static void test_rf_remove_all_features2(void **state) { rf_helper("2 another\t test", "another\t test", "0", 0); } static void test_rf_remove1(void **state) { rf_helper("2 feature1 feature2", "feature2", "1 feature1", 0); } static void test_rf_remove2(void **state) { rf_helper("2 feature1 feature2", "feature1", "1 feature2", 0); } static void test_rf_remove3(void **state) { rf_helper("3 test1 test\ttest2", "test", "2 test1 test2", 0); } static void test_rf_remove4(void **state) { rf_helper("4 this\t is a test", "is a", "2 this\t test", 0); } static void test_rf_remove5(void **state) { rf_helper("3 one more test", "more test", "1 one", 0); } static int test_remove_features(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_rf_null_features_ptr), cmocka_unit_test(test_rf_null_features), cmocka_unit_test(test_rf_null_removal), cmocka_unit_test(test_rf_empty_removal), cmocka_unit_test(test_rf_invalid_removal1), cmocka_unit_test(test_rf_invalid_removal2), cmocka_unit_test(test_rf_invalid_removal3), cmocka_unit_test(test_rf_invalid_removal4), cmocka_unit_test(test_rf_bad_features1), cmocka_unit_test(test_rf_bad_features2), cmocka_unit_test(test_rf_missing_removal1), cmocka_unit_test(test_rf_missing_removal2), cmocka_unit_test(test_rf_missing_removal3), cmocka_unit_test(test_rf_missing_removal4), cmocka_unit_test(test_rf_missing_removal5), cmocka_unit_test(test_rf_remove_all_features1), cmocka_unit_test(test_rf_remove_all_features2), cmocka_unit_test(test_rf_remove1), cmocka_unit_test(test_rf_remove2), cmocka_unit_test(test_rf_remove3), cmocka_unit_test(test_rf_remove4), cmocka_unit_test(test_rf_remove5), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void test_cf_null_features(void **state) { struct multipath mp = { .alias = "test", }; reconcile_features_with_queue_mode(&mp); assert_null(mp.features); } static void cf_helper(const char *features_start, const char *features_end, int queue_mode_start, int queue_mode_end) { struct multipath mp = { .alias = "test", .features = strdup(features_start), .queue_mode = queue_mode_start, }; char *orig = mp.features; assert_non_null(orig); reconcile_features_with_queue_mode(&mp); if (!features_end) assert_ptr_equal(orig, mp.features); else assert_string_equal(mp.features, features_end); free(mp.features); assert_int_equal(mp.queue_mode, queue_mode_end); } static void test_cf_unset_unset1(void **state) { cf_helper("0", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_unset_unset2(void **state) { cf_helper("1 queue_mode", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_unset_unset3(void **state) { cf_helper("queue_mode", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_unset_unset4(void **state) { cf_helper("2 queue_model bio", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_unset_unset5(void **state) { cf_helper("1 queue_if_no_path", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_invalid_unset1(void **state) { cf_helper("2 queue_mode biop", "0", QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_invalid_unset2(void **state) { cf_helper("3 queue_mode rqs queue_if_no_path", "1 queue_if_no_path", QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); } static void test_cf_rq_unset1(void **state) { cf_helper("2 queue_mode rq", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_RQ); } static void test_cf_rq_unset2(void **state) { cf_helper("2 queue_mode mq", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_RQ); } static void test_cf_bio_unset(void **state) { cf_helper("2 queue_mode bio", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_BIO); } static void test_cf_unset_bio1(void **state) { cf_helper("1 queue_if_no_path", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_unset_bio2(void **state) { cf_helper("0", "2 queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_unset_bio3(void **state) { cf_helper("2 pg_init_retries 50", "4 pg_init_retries 50 queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_invalid_bio1(void **state) { cf_helper("2 queue_mode bad", "2 queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_invalid_bio2(void **state) { cf_helper("3 queue_if_no_path queue_mode\tbad", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_bio_bio1(void **state) { cf_helper("2 queue_mode bio", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_bio_bio2(void **state) { cf_helper("3 queue_if_no_path queue_mode bio", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_bio_bio3(void **state) { cf_helper("3 queue_mode\nbio queue_if_no_path", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_bio_rq1(void **state) { cf_helper("2\nqueue_mode\tbio", "0", QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_bio_rq2(void **state) { cf_helper("3 queue_if_no_path\nqueue_mode bio", "1 queue_if_no_path", QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_bio_rq3(void **state) { cf_helper("4 queue_mode bio pg_init_retries 20", "2 pg_init_retries 20", QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_unset_rq1(void **state) { cf_helper("0", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_unset_rq2(void **state) { cf_helper("2 pg_init_retries 15", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_invalid_rq1(void **state) { cf_helper("2 queue_mode bionic", "0", QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_invalid_rq2(void **state) { cf_helper("3 queue_mode b\nqueue_if_no_path", "1 queue_if_no_path", QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_rq_rq1(void **state) { cf_helper("2 queue_mode rq", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_rq_rq2(void **state) { cf_helper("3 queue_mode\t \trq\nqueue_if_no_path", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); } static void test_cf_rq_bio1(void **state) { cf_helper("2 queue_mode rq", "2 queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_rq_bio2(void **state) { cf_helper("3 queue_if_no_path\nqueue_mode rq", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static void test_cf_rq_bio3(void **state) { cf_helper("3 queue_mode rq\nqueue_if_no_path", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); } static int test_reconcile_features(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_cf_null_features), cmocka_unit_test(test_cf_unset_unset1), cmocka_unit_test(test_cf_unset_unset2), cmocka_unit_test(test_cf_unset_unset3), cmocka_unit_test(test_cf_unset_unset4), cmocka_unit_test(test_cf_unset_unset5), cmocka_unit_test(test_cf_invalid_unset1), cmocka_unit_test(test_cf_invalid_unset2), cmocka_unit_test(test_cf_rq_unset1), cmocka_unit_test(test_cf_rq_unset2), cmocka_unit_test(test_cf_bio_unset), cmocka_unit_test(test_cf_unset_bio1), cmocka_unit_test(test_cf_unset_bio2), cmocka_unit_test(test_cf_unset_bio3), cmocka_unit_test(test_cf_invalid_bio1), cmocka_unit_test(test_cf_invalid_bio2), cmocka_unit_test(test_cf_bio_bio1), cmocka_unit_test(test_cf_bio_bio2), cmocka_unit_test(test_cf_bio_bio3), cmocka_unit_test(test_cf_bio_rq1), cmocka_unit_test(test_cf_bio_rq2), cmocka_unit_test(test_cf_bio_rq3), cmocka_unit_test(test_cf_unset_rq1), cmocka_unit_test(test_cf_unset_rq2), cmocka_unit_test(test_cf_invalid_rq1), cmocka_unit_test(test_cf_invalid_rq2), cmocka_unit_test(test_cf_rq_rq1), cmocka_unit_test(test_cf_rq_rq2), cmocka_unit_test(test_cf_rq_bio1), cmocka_unit_test(test_cf_rq_bio2), cmocka_unit_test(test_cf_rq_bio3), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_add_features(); ret += test_remove_features(); ret += test_reconcile_features(); return ret; } multipath-tools-0.11.1/tests/globals.c000066400000000000000000000011141475246302400176740ustar00rootroot00000000000000#include #include #include "defaults.h" #include "structs.h" #include "config.h" #include "debug.h" struct config conf; struct config *get_multipath_config(void) { return &conf; } void put_multipath_config(void *arg) {} static __attribute__((unused)) void init_test_verbosity(int test_verbosity) { char *verb = getenv("MPATHTEST_VERBOSITY"); libmp_verbosity = test_verbosity >= 0 ? test_verbosity : DEFAULT_VERBOSITY; if (verb && *verb) { char *c; int vb; vb = strtoul(verb, &c, 10); if (!*c && vb >= 0 && vb <= 5) libmp_verbosity = vb; } } multipath-tools-0.11.1/tests/hwtable.c000066400000000000000000001500361475246302400177070ustar00rootroot00000000000000/* Set BROKEN to 1 to treat broken behavior as success */ #define BROKEN 1 #define VERBOSITY 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "structs.h" #include "structs_vec.h" #include "config.h" #include "debug.h" #include "defaults.h" #include "pgpolicies.h" #include "test-lib.h" #include "print.h" #include "util.h" #include "foreign.h" #define N_CONF_FILES 2 static const char template[] = "/tmp/hwtable-XXXXXX"; struct key_value { const char *key; const char *value; }; struct hwt_state { char *tmpname; char *dirname; FILE *config_file; FILE *conf_dir_file[N_CONF_FILES]; struct vectors *vecs; void (*test)(const struct hwt_state *); const char *test_name; }; #define SET_TEST_FUNC(hwt, func) do { \ hwt->test = func; \ hwt->test_name = #func; \ } while (0) static struct config *_conf; struct udev *udev; int logsink = LOGSINK_STDERR_WITHOUT_TIME; struct config *get_multipath_config(void) { return _conf; } void put_multipath_config(void *arg) {} void make_config_file_path(char *buf, int buflen, const struct hwt_state *hwt, int i) { static const char fn_template[] = "%s/test-%02d.conf"; if (i == -1) /* main config file */ snprintf(buf, buflen, fn_template, hwt->tmpname, 0); else snprintf(buf, buflen, fn_template, hwt->dirname, i); } static void reset_vecs(struct vectors *vecs) { remove_maps(vecs); free_pathvec(vecs->pathvec, FREE_PATHS); vecs->pathvec = vector_alloc(); assert_ptr_not_equal(vecs->pathvec, NULL); vecs->mpvec = vector_alloc(); assert_ptr_not_equal(vecs->mpvec, NULL); } static void free_hwt(struct hwt_state *hwt) { char buf[PATH_MAX]; int i; if (hwt->config_file != NULL) fclose(hwt->config_file); for (i = 0; i < N_CONF_FILES; i++) { if (hwt->conf_dir_file[i] != NULL) fclose(hwt->conf_dir_file[i]); } if (hwt->tmpname != NULL) { make_config_file_path(buf, sizeof(buf), hwt, -1); unlink(buf); rmdir(hwt->tmpname); free(hwt->tmpname); } if (hwt->dirname != NULL) { for (i = 0; i < N_CONF_FILES; i++) { make_config_file_path(buf, sizeof(buf), hwt, i); unlink(buf); } rmdir(hwt->dirname); free(hwt->dirname); } if (hwt->vecs != NULL) { if (hwt->vecs->mpvec != NULL) remove_maps(hwt->vecs); if (hwt->vecs->pathvec != NULL) free_pathvec(hwt->vecs->pathvec, FREE_PATHS); pthread_mutex_destroy(&hwt->vecs->lock.mutex); free(hwt->vecs); } free(hwt); } static int setup(void **state) { struct hwt_state *hwt; char buf[PATH_MAX]; int i; *state = NULL; hwt = calloc(1, sizeof(*hwt)); if (hwt == NULL) return -1; snprintf(buf, sizeof(buf), "%s", template); if (mkdtemp(buf) == NULL) { condlog(0, "mkdtemp: %s", strerror(errno)); goto err; } hwt->tmpname = strdup(buf); hwt->dirname = strdup(TESTCONFDIR); if (mkdir(hwt->dirname, 0744) != 0) { condlog(0, "mkdir %s: %s", hwt->dirname, strerror(errno)); goto err; } make_config_file_path(buf, sizeof(buf), hwt, -1); hwt->config_file = fopen(buf, "w+"); if (hwt->config_file == NULL) goto err; for (i = 0; i < N_CONF_FILES; i++) { make_config_file_path(buf, sizeof(buf), hwt, i); hwt->conf_dir_file[i] = fopen(buf, "w+"); if (hwt->conf_dir_file[i] == NULL) goto err; } hwt->vecs = calloc(1, sizeof(*hwt->vecs)); if (hwt->vecs == NULL) goto err; pthread_mutex_init(&hwt->vecs->lock.mutex, NULL); hwt->vecs->pathvec = vector_alloc(); hwt->vecs->mpvec = vector_alloc(); if (hwt->vecs->pathvec == NULL || hwt->vecs->mpvec == NULL) goto err; *state = hwt; return 0; err: free_hwt(hwt); return -1; } static int teardown(void **state) { if (state == NULL || *state == NULL) return -1; free_hwt(*state); *state = NULL; cleanup_prio(); cleanup_checkers(); cleanup_foreign(); return 0; } /* * Helpers for creating the config file(s) */ static void reset_config(FILE *ff) { if (ff == NULL) return; rewind(ff); if (ftruncate(fileno(ff), 0) == -1) condlog(1, "ftruncate: %s", strerror(errno)); } static void reset_configs(const struct hwt_state *hwt) { int i; reset_config(hwt->config_file); for (i = 0; i < N_CONF_FILES; i++) reset_config(hwt->conf_dir_file[i]); } static void write_key_values(FILE *ff, int nkv, const struct key_value *kv) { int i; for (i = 0; i < nkv; i++) { if (strchr(kv[i].value, ' ') == NULL && strchr(kv[i].value, '\"') == NULL) fprintf(ff, "\t%s %s\n", kv[i].key, kv[i].value); else fprintf(ff, "\t%s \"%s\"\n", kv[i].key, kv[i].value); } } static void begin_section(FILE *ff, const char *section) { fprintf(ff, "%s {\n", section); } static void end_section(FILE *ff) { fprintf(ff, "}\n"); } static void write_section(FILE *ff, const char *section, int nkv, const struct key_value *kv) { begin_section(ff, section); write_key_values(ff, nkv, kv); end_section(ff); } static void write_defaults(const struct hwt_state *hwt) { static const char bindings_name[] = "bindings"; static struct key_value defaults[] = { { "config_dir", NULL }, { "bindings_file", NULL }, { "multipath_dir", NULL }, { "detect_prio", "no" }, { "detect_checker", "no" }, }; char buf[sizeof(template) + sizeof(bindings_name)]; char dirbuf[PATH_MAX]; snprintf(buf, sizeof(buf), "%s/%s", hwt->tmpname, bindings_name); defaults[0].value = hwt->dirname; defaults[1].value = buf; assert_ptr_not_equal(getcwd(dirbuf, sizeof(dirbuf)), NULL); strncat(dirbuf, "/lib", sizeof(dirbuf) - 5); defaults[2].value = dirbuf; write_section(hwt->config_file, "defaults", ARRAY_SIZE(defaults), defaults); } static void begin_config(const struct hwt_state *hwt) { reset_configs(hwt); write_defaults(hwt); } static void begin_section_all(const struct hwt_state *hwt, const char *section) { int i; begin_section(hwt->config_file, section); for (i = 0; i < N_CONF_FILES; i++) begin_section(hwt->conf_dir_file[i], section); } static void end_section_all(const struct hwt_state *hwt) { int i; end_section(hwt->config_file); for (i = 0; i < N_CONF_FILES; i++) end_section(hwt->conf_dir_file[i]); } static void finish_config(const struct hwt_state *hwt) { int i; fflush(hwt->config_file); for (i = 0; i < N_CONF_FILES; i++) { fflush(hwt->conf_dir_file[i]); } } static void write_device(FILE *ff, int nkv, const struct key_value *kv) { write_section(ff, "device", nkv, kv); } /* * Some macros to avoid boilerplate code */ #define CHECK_STATE(state) ({ \ assert_ptr_not_equal(state, NULL); \ assert_ptr_not_equal(*(state), NULL); \ *state; }) #define WRITE_EMPTY_CONF(hwt) do { \ begin_config(hwt); \ finish_config(hwt); \ } while (0) #define WRITE_ONE_DEVICE(hwt, kv) do { \ begin_config(hwt); \ begin_section_all(hwt, "devices"); \ write_device(hwt->config_file, ARRAY_SIZE(kv), kv); \ end_section_all(hwt); \ finish_config(hwt); \ } while (0) #define WRITE_TWO_DEVICES(hwt, kv1, kv2) do { \ begin_config(hwt); \ begin_section_all(hwt, "devices"); \ write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); \ write_device(hwt->config_file, ARRAY_SIZE(kv2), kv2); \ end_section_all(hwt); \ finish_config(hwt); \ } while (0) #define WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2) do { \ begin_config(hwt); \ begin_section_all(hwt, "devices"); \ write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); \ write_device(hwt->conf_dir_file[0], \ ARRAY_SIZE(kv2), kv2); \ end_section_all(hwt); \ finish_config(hwt); \ } while (0) #define LOAD_CONFIG(hwt) ({ \ char buf[PATH_MAX]; \ struct config *__cf; \ \ make_config_file_path(buf, sizeof(buf), hwt, -1); \ __cf = load_config(buf); \ assert_ptr_not_equal(__cf, NULL); \ assert_ptr_not_equal(__cf->hwtable, NULL); \ __cf->verbosity = VERBOSITY; \ __cf; }) #define FREE_CONFIG(conf) do { \ free_config(conf); \ conf = NULL; \ } while (0) static void replace_config(const struct hwt_state *hwt, const char *conf_str) { FREE_CONFIG(_conf); reset_configs(hwt); fprintf(hwt->config_file, "%s", conf_str); fflush(hwt->config_file); _conf = LOAD_CONFIG(hwt); } #define TEST_PROP(prop, val) do { \ if (val == NULL) \ assert_ptr_equal(prop, NULL); \ else { \ assert_ptr_not_equal(prop, NULL); \ assert_string_equal(prop, val); \ } \ } while (0) #if BROKEN #define TEST_PROP_BROKEN(name, prop, bad, good) do { \ condlog(1, "%s: WARNING: Broken test for %s == \"%s\" on line %d, should be \"%s\"", \ __func__, name, bad ? bad : "NULL", \ __LINE__, good ? good : "NULL"); \ TEST_PROP(prop, bad); \ } while (0) #else #define TEST_PROP_BROKEN(name, prop, bad, good) TEST_PROP(prop, good) #endif /* * Some predefined key/value pairs */ static const char _wwid[] = "wwid"; static const char _vendor[] = "vendor"; static const char _product[] = "product"; static const char _prio[] = "prio"; 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 */ static const struct key_value vnd_foo = { _vendor, "foo" }; static const struct key_value prd_bar = { _product, "bar" }; static const struct key_value prd_bam = { _product, "bam" }; static const struct key_value prd_baq = { _product, "\"bar\"" }; static const struct key_value prd_baqq = { _product, "\"\"bar\"\"" }; static const struct key_value prd_barz = { _product, "barz" }; static const struct key_value vnd_boo = { _vendor, "boo" }; static const struct key_value prd_baz = { _product, "baz" }; static const struct key_value wwid_test = { _wwid, default_wwid }; /* Regular expressions */ static const struct key_value vnd__oo = { _vendor, ".oo" }; static const struct key_value vnd_t_oo = { _vendor, "^.oo" }; static const struct key_value prd_ba_ = { _product, "ba." }; static const struct key_value prd_ba_s = { _product, "(bar|baz|ba\\.)$" }; /* Pathological cases, see below */ static const struct key_value prd_barx = { _product, "ba[[rxy]" }; static const struct key_value prd_bazy = { _product, "ba[zy]" }; static const struct key_value prd_bazy1 = { _product, "ba(z|y)" }; /* Properties */ static const struct key_value prio_emc = { _prio, "emc" }; static const struct key_value prio_hds = { _prio, "hds" }; static const struct key_value prio_rdac = { _prio, "rdac" }; static const struct key_value chk_hp = { _checker, "hp_sw" }; static const struct key_value vpd_hp3par = { _vpd_vnd, "hp3par" }; static const struct key_value uid_baz = { _uid_attr, "BAZ_ATTR" }; 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" }; /***** BEGIN TESTS SECTION *****/ /* * Dump the configuration, substitute the dumped configuration * for the current one, and verify that the result is identical. */ static void replicate_config(const struct hwt_state *hwt, bool local) { char *cfg1, *cfg2; vector hwtable; struct config *conf; condlog(3, "--- %s: replicating %s configuration", __func__, local ? "local" : "full"); conf = get_multipath_config(); if (!local) /* "full" configuration */ cfg1 = snprint_config(conf, NULL, NULL, NULL); else { /* "local" configuration */ hwtable = get_used_hwes(hwt->vecs->pathvec); cfg1 = snprint_config(conf, NULL, hwtable, hwt->vecs->mpvec); vector_free(hwtable); } assert_non_null(cfg1); put_multipath_config(conf); replace_config(hwt, cfg1); /* * The local configuration adds multipath entries, and may move device * entries for local devices to the end of the list. Identical config * strings therefore can't be expected in the "local" case. * That doesn't matter. The important thing is that, with the reloaded * configuration, the test case still passes. */ if (local) { free(cfg1); return; } conf = get_multipath_config(); cfg2 = snprint_config(conf, NULL, NULL, NULL); assert_non_null(cfg2); put_multipath_config(conf); // #define DBG_CONFIG 1 #ifdef DBG_CONFIG #define DUMP_CFG_STR(x) do { \ FILE *tmp = fopen("/tmp/hwtable-" #x ".txt", "w"); \ fprintf(tmp, "%s", x); \ fclose(tmp); \ } while (0) DUMP_CFG_STR(cfg1); DUMP_CFG_STR(cfg2); #endif assert_int_equal(strlen(cfg2), strlen(cfg1)); assert_string_equal(cfg2, cfg1); free(cfg1); free(cfg2); } /* * Run hwt->test three times; once with the constructed configuration, * once after re-reading the full dumped configuration, and once with the * dumped local configuration. * * Expected: test passes every time. */ static void test_driver(void **state) { const struct hwt_state *hwt; hwt = CHECK_STATE(state); _conf = LOAD_CONFIG(hwt); hwt->test(hwt); replicate_config(hwt, false); reset_vecs(hwt->vecs); hwt->test(hwt); replicate_config(hwt, true); reset_vecs(hwt->vecs); hwt->test(hwt); reset_vecs(hwt->vecs); FREE_CONFIG(_conf); } /* * Sanity check for the test itself, because defaults may be changed * in libmultipath. * * Our checking for match or non-match relies on the defaults being * different from what our device sections contain. */ static void test_sanity_globals(void **state) { assert_string_not_equal(prio_emc.value, DEFAULT_PRIO); assert_string_not_equal(prio_hds.value, DEFAULT_PRIO); 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); } /* * Regression test for internal hwtable. NVME is an example of two entries * in the built-in hwtable, one if which matches a subset of the other. */ static void test_internal_nvme(const struct hwt_state *hwt) { struct path *pp; struct multipath *mp; /* * Generic NVMe: expect defaults for pgpolicy and no_path_retry */ pp = mock_path("NVME", "NoName"); mp = mock_multipath(pp); assert_ptr_not_equal(mp, NULL); TEST_PROP(checker_name(&pp->checker), NONE); TEST_PROP(pp->uid_attribute, DEFAULT_NVME_UID_ATTRIBUTE); assert_int_equal(mp->pgpolicy, GROUP_BY_PRIO); assert_int_equal(mp->no_path_retry, DEFAULT_NO_PATH_RETRY); assert_int_equal(mp->retain_hwhandler, RETAIN_HWHANDLER_OFF); /* * NetApp NVMe: expect special values for pgpolicy and no_path_retry */ pp = mock_path_wwid("NVME", "NetApp ONTAP Controller", default_wwid_1); mp = mock_multipath(pp); assert_ptr_not_equal(mp, NULL); TEST_PROP(checker_name(&pp->checker), NONE); TEST_PROP(pp->uid_attribute, "ID_WWN"); assert_int_equal(mp->pgpolicy, GROUP_BY_PRIO); assert_int_equal(mp->no_path_retry, NO_PATH_RETRY_QUEUE); assert_int_equal(mp->retain_hwhandler, RETAIN_HWHANDLER_OFF); } static int setup_internal_nvme(void **state) { struct hwt_state *hwt = CHECK_STATE(state); WRITE_EMPTY_CONF(hwt); SET_TEST_FUNC(hwt, test_internal_nvme); return 0; } /* * Device section with a simple entry with double quotes ('foo:"bar"') */ static void test_quoted_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:"bar" matches */ pp = mock_path(vnd_foo.value, prd_baq.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); /* foo:bar doesn't match */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); } static int setup_quoted_hwe(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv[] = { vnd_foo, prd_baqq, prio_emc }; WRITE_ONE_DEVICE(hwt, kv); SET_TEST_FUNC(hwt, test_quoted_hwe); return 0; } /* * Device section with a single simple entry ("foo:bar") */ static void test_string_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:bar matches */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); /* boo:bar doesn't match */ pp = mock_path(vnd_boo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); } static int setup_string_hwe(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv[] = { vnd_foo, prd_bar, prio_emc }; WRITE_ONE_DEVICE(hwt, kv); SET_TEST_FUNC(hwt, test_string_hwe); return 0; } /* * Device section with a broken entry (no product) * It should be ignored. */ static void test_broken_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:bar doesn't match, as hwentry is ignored */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); /* boo:bar doesn't match */ pp = mock_path(vnd_boo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); } static int setup_broken_hwe(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv[] = { vnd_foo, prio_emc }; WRITE_ONE_DEVICE(hwt, kv); SET_TEST_FUNC(hwt, test_broken_hwe); return 0; } /* * Like test_broken_hwe, but in config_dir file. */ static int setup_broken_hwe_dir(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv[] = { vnd_foo, prio_emc }; begin_config(hwt); begin_section_all(hwt, "devices"); write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv), kv); end_section_all(hwt); finish_config(hwt); hwt->test = test_broken_hwe; hwt->test_name = "test_broken_hwe_dir"; return 0; } /* * Device section with a single regex entry ("^.foo:(bar|baz|ba\.)$") */ static void test_regex_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:bar matches */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); /* foo:baz matches */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); /* boo:baz matches */ pp = mock_path(vnd_boo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); /* foo:BAR doesn't match */ pp = mock_path(vnd_foo.value, "BAR"); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); /* bboo:bar doesn't match */ pp = mock_path("bboo", prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); } static int setup_regex_hwe(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv[] = { vnd_t_oo, prd_ba_s, prio_emc }; WRITE_ONE_DEVICE(hwt, kv); SET_TEST_FUNC(hwt, test_regex_hwe); return 0; } /* * Two device entries, kv1 is a regex match ("^.foo:(bar|baz|ba\.)$"), * kv2 a string match (foo:bar) which matches a subset of the regex. * Both are added to the main config file. * * Expected: Devices matching both get properties from both, kv2 taking * precedence. Devices matching kv1 only just get props from kv1. */ static void test_regex_string_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:baz matches kv1 */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* boo:baz matches kv1 */ pp = mock_path(vnd_boo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .oo:ba. matches kv1 */ pp = mock_path(vnd__oo.value, prd_ba_.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .foo:(bar|baz|ba\.) doesn't match */ pp = mock_path(vnd__oo.value, prd_ba_s.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches kv2 and kv1 */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_regex_string_hwe(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; WRITE_TWO_DEVICES(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_regex_string_hwe); return 0; } /* * Two device entries, kv1 is a regex match ("^.foo:(bar|baz|ba\.)$"), * kv2 a string match (foo:bar) which matches a subset of the regex. * kv1 is added to the main config file, kv2 to a config_dir file. * This case is more important as you may think, because it's equivalent * to kv1 being in the built-in hwtable and kv2 in multipath.conf. * * Expected: Devices matching kv2 (and thus, both) get properties * from both, kv2 taking precedence. * Devices matching kv1 only just get props from kv1. */ static void test_regex_string_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz matches kv1 */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* boo:baz matches kv1 */ pp = mock_path(vnd_boo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .oo:ba. matches kv1 */ pp = mock_path(vnd__oo.value, prd_ba_.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .oo:(bar|baz|ba\.)$ doesn't match */ pp = mock_path(vnd__oo.value, prd_ba_s.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches kv2 */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); /* Later match takes prio */ TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_regex_string_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_regex_string_hwe_dir); return 0; } /* * Three device entries, kv1 is a regex match and kv2 and kv3 string * matches, where kv3 is a substring of kv2. All in different config * files. * * Expected: Devices matching kv3 get props from all, devices matching * kv2 from kv2 and kv1, and devices matching kv1 only just from kv1. */ static void test_regex_2_strings_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz matches kv1 */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* boo:baz doesn't match */ pp = mock_path(vnd_boo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches kv2 and kv1 */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(pp->uid_attribute, uid_baz.value); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* foo:barz matches kv3 and kv2 and kv1 */ pp = mock_path_flags(vnd_foo.value, prd_barz.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_rdac.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(pp->uid_attribute, uid_baz.value); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_regex_2_strings_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_foo, prd_ba_, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, uid_baz }; const struct key_value kv3[] = { vnd_foo, prd_barz, prio_rdac, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "devices"); write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv3), kv3); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_regex_2_strings_hwe_dir); return 0; } /* * Like test_regex_string_hwe_dir, but the order of kv1 and kv2 is exchanged. * * Expected: Devices matching kv1 (and thus, both) get properties * from both, kv1 taking precedence. * Devices matching kv1 only just get props from kv1. */ static void test_string_regex_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:bar matches kv2 and kv1 */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* foo:baz matches kv1 */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* boo:baz matches kv1 */ pp = mock_path(vnd_boo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .oo:ba. matches kv1 */ pp = mock_path(vnd__oo.value, prd_ba_.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* .oo:(bar|baz|ba\.)$ doesn't match */ pp = mock_path(vnd__oo.value, prd_ba_s.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); } static int setup_string_regex_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv2, kv1); SET_TEST_FUNC(hwt, test_string_regex_hwe_dir); return 0; } /* * Two identical device entries kv1 and kv2, trivial regex ("string"). * Both are added to the main config file. * These entries are NOT merged. * This could happen in a large multipath.conf file. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_strings_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_strings_hwe(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_ident_strings_hwe); return 0; } /* * Two identical device entries kv1 and kv2, trivial regex ("string"). * Both are added to an extra config file. * This could happen in a large multipath.conf file. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_strings_both_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_strings_both_dir(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "devices"); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv1), kv1); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_2_ident_strings_both_dir); return 0; } /* * Two identical device entries kv1 and kv2, trivial regex ("string"). * Both are added to an extra config file. * An empty entry kv0 with the same string exists in the main config file. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_strings_both_dir_w_prev(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_strings_both_dir_w_prev(void **state) { struct hwt_state *hwt = CHECK_STATE(state); const struct key_value kv0[] = { vnd_foo, prd_bar }; const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; begin_config(hwt); begin_section_all(hwt, "devices"); write_device(hwt->config_file, ARRAY_SIZE(kv0), kv0); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv1), kv1); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_2_ident_strings_both_dir_w_prev); return 0; } /* * Two identical device entries kv1 and kv2, trivial regex ("string"). * kv1 is added to the main config file, kv2 to a config_dir file. * These entries are merged. * This case is more important as you may think, because it's equivalent * to kv1 being in the built-in hwtable and kv2 in multipath.conf. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_strings_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_strings_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_ident_strings_hwe_dir); return 0; } /* * Like test_2_ident_strings_hwe_dir, but this time the config_dir file * contains an additional, empty entry (kv0). * * Expected: matching devices get props from kv1 and kv2, kv2 taking precedence. */ static void test_3_ident_strings_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_3_ident_strings_hwe_dir(void **state) { const struct key_value kv0[] = { vnd_foo, prd_bar }; const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "devices"); write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv0), kv0); write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_3_ident_strings_hwe_dir); return 0; } /* * Two identical device entries kv1 and kv2, non-trival regex that matches * itself (string ".oo" matches regex ".oo"). * kv1 is added to the main config file, kv2 to a config_dir file. * This case is more important as you may think, because it's equivalent * to kv1 being in the built-in hwtable and kv2 in multipath.conf. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_self_matching_re_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_self_matching_re_hwe_dir(void **state) { const struct key_value kv1[] = { vnd__oo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd__oo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_ident_self_matching_re_hwe_dir); return 0; } /* * Two identical device entries kv1 and kv2, non-trival regex that matches * itself (string ".oo" matches regex ".oo"). * kv1 and kv2 are added to the main config file. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_self_matching_re_hwe(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_self_matching_re_hwe(void **state) { const struct key_value kv1[] = { vnd__oo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd__oo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_ident_self_matching_re_hwe); return 0; } /* * Two identical device entries kv1 and kv2, non-trival regex that doesn't * match itself (string "^.oo" doesn't match regex "^.oo"). * kv1 is added to the main config file, kv2 to a config_dir file. * This case is more important as you may think, see above. * * Expected: matching devices get props from both, kv2 taking precedence. */ static void test_2_ident_not_self_matching_re_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:baz doesn't match */ pp = mock_path(vnd_foo.value, prd_baz.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); /* foo:bar matches both */ pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_ident_not_self_matching_re_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_t_oo, prd_bar, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_t_oo, prd_bar, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_ident_not_self_matching_re_hwe_dir); return 0; } /* * Two different non-trivial regexes kv1, kv2. The 1st one matches the 2nd, but * it doesn't match all possible strings matching the second. * ("ba[zy]" matches regex "ba[[rxy]", but "baz" does not). * * Expected: Devices matching both regexes get properties from both, kv2 * taking precedence. Devices matching just one regex get properties from * that one regex only. */ static void test_2_matching_res_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:bar matches k1 only */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), prio_emc.value); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* foo:bay matches k1 and k2 */ pp = mock_path_flags(vnd_foo.value, "bay", USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); /* foo:baz matches k2 only. */ pp = mock_path_flags(vnd_foo.value, prd_baz.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); } static int setup_2_matching_res_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_foo, prd_barx, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bazy, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_matching_res_hwe_dir); return 0; } /* * Two different non-trivial regexes which match the same set of strings. * But they don't match each other. * "baz" matches both regex "ba[zy]" and "ba(z|y)" * * Expected: matching devices get properties from both, kv2 taking precedence. */ static void test_2_nonmatching_res_hwe_dir(const struct hwt_state *hwt) { struct path *pp; /* foo:bar doesn't match */ pp = mock_path(vnd_foo.value, prd_bar.value); TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO); assert_int_equal(pp->vpd_vendor_id, 0); TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER); pp = mock_path_flags(vnd_foo.value, prd_baz.value, USE_VPD_VND); TEST_PROP(prio_name(&pp->prio), prio_hds.value); assert_int_equal(pp->vpd_vendor_id, VPD_VP_HP3PAR); TEST_PROP(checker_name(&pp->checker), chk_hp.value); } static int setup_2_nonmatching_res_hwe_dir(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bazy, prio_emc, chk_hp }; const struct key_value kv2[] = { vnd_foo, prd_bazy1, prio_hds, vpd_hp3par }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_2_nonmatching_res_hwe_dir); return 0; } /* * Simple blacklist test. * * NOTE: test failures in blacklisting tests will manifest as cmocka errors * "Could not get value to mock function XYZ", because pathinfo() takes * different code paths for blacklisted devices. */ static void test_blacklist(const struct hwt_state *hwt) { mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE); mock_path(vnd_foo.value, prd_baz.value); } static int setup_blacklist(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "blacklist"); write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist); return 0; } /* * Simple blacklist test with regex and exception */ static void test_blacklist_regex(const struct hwt_state *hwt) { mock_path(vnd_foo.value, prd_bar.value); mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE); mock_path(vnd_foo.value, prd_bam.value); } static int setup_blacklist_regex(void **state) { const struct key_value kv1[] = { vnd_foo, prd_ba_s }; const struct key_value kv2[] = { vnd_foo, prd_bar }; struct hwt_state *hwt = CHECK_STATE(state); hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "blacklist"); write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); end_section_all(hwt); begin_section_all(hwt, "blacklist_exceptions"); write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist_regex); return 0; } /* * Simple blacklist test with regex and exception * config file order inverted wrt test_blacklist_regex */ static int setup_blacklist_regex_inv(void **state) { const struct key_value kv1[] = { vnd_foo, prd_ba_s }; const struct key_value kv2[] = { vnd_foo, prd_bar }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "blacklist"); write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv1), kv1); end_section_all(hwt); begin_section_all(hwt, "blacklist_exceptions"); write_device(hwt->config_file, ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist_regex); return 0; } /* * Simple blacklist test with regex and exception * config file order inverted wrt test_blacklist_regex */ static void test_blacklist_regex_matching(const struct hwt_state *hwt) { mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE); mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE); mock_path(vnd_foo.value, prd_bam.value); } static int setup_blacklist_regex_matching(void **state) { const struct key_value kv1[] = { vnd_foo, prd_barx }; const struct key_value kv2[] = { vnd_foo, prd_bazy }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "blacklist"); write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist_regex_matching); return 0; } /* * Test for blacklisting by WWID * * Note that default_wwid is a substring of default_wwid_1. Because * matching is done by regex, both paths are blacklisted. */ static void test_blacklist_wwid(const struct hwt_state *hwt) { mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_WWID); mock_path_wwid_flags(vnd_foo.value, prd_baz.value, default_wwid_1, BL_BY_WWID); } static int setup_blacklist_wwid(void **state) { const struct key_value kv[] = { wwid_test }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); write_section(hwt->config_file, "blacklist", ARRAY_SIZE(kv), kv); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist_wwid); return 0; } /* * Test for blacklisting by WWID * * Here the blacklist contains only default_wwid_1. Thus the path * with default_wwid is NOT blacklisted. */ static void test_blacklist_wwid_1(const struct hwt_state *hwt) { mock_path(vnd_foo.value, prd_bar.value); mock_path_wwid_flags(vnd_foo.value, prd_baz.value, default_wwid_1, BL_BY_WWID); } static int setup_blacklist_wwid_1(void **state) { const struct key_value kv[] = { { _wwid, default_wwid_1 }, }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); write_section(hwt->config_file, "blacklist", ARRAY_SIZE(kv), kv); finish_config(hwt); SET_TEST_FUNC(hwt, test_blacklist_wwid_1); return 0; } /* * Test for product_blacklist. Two entries blacklisting each other. * * Expected: Both are blacklisted. */ static void test_product_blacklist(const struct hwt_state *hwt) { mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE); mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE); mock_path(vnd_foo.value, prd_bam.value); } static int setup_product_blacklist(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar, bl_baz }; const struct key_value kv2[] = { vnd_foo, prd_baz, bl_bar }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_product_blacklist); return 0; } /* * Test for product_blacklist. The second regex "matches" the first. * This is a pathological example. * * Expected: "foo:bar", "foo:baz" are blacklisted. */ static void test_product_blacklist_matching(const struct hwt_state *hwt) { mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE); mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE); mock_path(vnd_foo.value, prd_bam.value); } static int setup_product_blacklist_matching(void **state) { const struct key_value kv1[] = { vnd_foo, prd_bar, bl_barx }; const struct key_value kv2[] = { vnd_foo, prd_baz, bl_bazy }; struct hwt_state *hwt = CHECK_STATE(state); WRITE_TWO_DEVICES(hwt, kv1, kv2); SET_TEST_FUNC(hwt, test_product_blacklist_matching); return 0; } /* * Basic test for multipath-based configuration. * * Expected: properties, including pp->prio, are taken from multipath * section. */ static void test_multipath_config(const struct hwt_state *hwt) { struct path *pp; struct multipath *mp; pp = mock_path(vnd_foo.value, prd_bar.value); 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 */ pp = mock_path_wwid(vnd_foo.value, prd_bar.value, default_wwid_1); 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 kvp[] = { vnd_foo, prd_bar, prio_emc, uid_baz }; begin_config(hwt); begin_section_all(hwt, "devices"); write_section(hwt->conf_dir_file[0], "device", ARRAY_SIZE(kvp), kvp); end_section_all(hwt); begin_section_all(hwt, "multipaths"); write_section(hwt->config_file, "multipath", ARRAY_SIZE(kvm), kvm); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_multipath_config); return 0; } /* * Basic test for multipath-based configuration. Two sections for the same wwid. * * Expected: properties are taken from both multipath sections, later taking * precedence */ static void test_multipath_config_2(const struct hwt_state *hwt) { struct path *pp; struct multipath *mp; pp = mock_path(vnd_foo.value, prd_bar.value); mp = mock_multipath(pp); 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 }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "multipaths"); write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv1), kv1); write_section(hwt->conf_dir_file[1], "multipath", ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_multipath_config_2); return 0; } /* * Same as test_multipath_config_2, both entries in the same config file. * * Expected: properties are taken from both multipath sections. */ static void test_multipath_config_3(const struct hwt_state *hwt) { struct path *pp; struct multipath *mp; pp = mock_path(vnd_foo.value, prd_bar.value); mp = mock_multipath(pp); 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 }; struct hwt_state *hwt = CHECK_STATE(state); begin_config(hwt); begin_section_all(hwt, "multipaths"); write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv1), kv1); write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv2), kv2); end_section_all(hwt); finish_config(hwt); SET_TEST_FUNC(hwt, test_multipath_config_3); return 0; } /* * Test for device with "hidden" attribute */ static void test_hidden(const struct hwt_state *hwt) { mock_path_flags("NVME", "NoName", DEV_HIDDEN|BL_MASK); } static int setup_hidden(void **state) { struct hwt_state *hwt = CHECK_STATE(state); WRITE_EMPTY_CONF(hwt); SET_TEST_FUNC(hwt, test_hidden); return 0; } /* * Create wrapper functions around test_driver() to avoid that cmocka * always uses the same test name. That makes it easier to read test results. */ #define define_test(x) \ static void run_##x(void **state) \ { \ return test_driver(state); \ } define_test(string_hwe) define_test(broken_hwe) define_test(broken_hwe_dir) define_test(quoted_hwe) define_test(internal_nvme) define_test(regex_hwe) define_test(regex_string_hwe) define_test(regex_string_hwe_dir) define_test(regex_2_strings_hwe_dir) define_test(string_regex_hwe_dir) define_test(2_ident_strings_hwe) define_test(2_ident_strings_both_dir) define_test(2_ident_strings_both_dir_w_prev) define_test(2_ident_strings_hwe_dir) define_test(3_ident_strings_hwe_dir) define_test(2_ident_self_matching_re_hwe_dir) define_test(2_ident_self_matching_re_hwe) define_test(2_ident_not_self_matching_re_hwe_dir) define_test(2_matching_res_hwe_dir) define_test(2_nonmatching_res_hwe_dir) define_test(blacklist) define_test(blacklist_wwid) define_test(blacklist_wwid_1) define_test(blacklist_regex) define_test(blacklist_regex_inv) define_test(blacklist_regex_matching) define_test(product_blacklist) define_test(product_blacklist_matching) define_test(multipath_config) define_test(multipath_config_2) define_test(multipath_config_3) define_test(hidden) #define test_entry(x) \ cmocka_unit_test_setup(run_##x, setup_##x) static int test_hwtable(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_sanity_globals), test_entry(internal_nvme), test_entry(string_hwe), test_entry(broken_hwe), test_entry(broken_hwe_dir), test_entry(quoted_hwe), test_entry(regex_hwe), test_entry(regex_string_hwe), test_entry(regex_string_hwe_dir), test_entry(regex_2_strings_hwe_dir), test_entry(string_regex_hwe_dir), test_entry(2_ident_strings_hwe), test_entry(2_ident_strings_both_dir), test_entry(2_ident_strings_both_dir_w_prev), test_entry(2_ident_strings_hwe_dir), test_entry(3_ident_strings_hwe_dir), test_entry(2_ident_self_matching_re_hwe_dir), test_entry(2_ident_self_matching_re_hwe), test_entry(2_ident_not_self_matching_re_hwe_dir), test_entry(2_matching_res_hwe_dir), test_entry(2_nonmatching_res_hwe_dir), test_entry(blacklist), test_entry(blacklist_wwid), test_entry(blacklist_wwid_1), test_entry(blacklist_regex), test_entry(blacklist_regex_inv), test_entry(blacklist_regex_matching), test_entry(product_blacklist), test_entry(product_blacklist_matching), test_entry(multipath_config), test_entry(multipath_config_2), test_entry(multipath_config_3), test_entry(hidden), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; /* We can't use init_test_verbosity in this test */ libmp_verbosity = VERBOSITY; ret += test_hwtable(); return ret; } multipath-tools-0.11.1/tests/mapinfo.c000066400000000000000000001304751475246302400177170ustar00rootroot00000000000000/* * Copyright (c) 2024 Martin Wilck, SUSE * SPDX-License-Identifier: GPL-2.0-or-later */ /* * glibc <= 2.19 (Ubuntu Trusty, Debian Jessie) uses macros to inline strdup(), * which makes our strdup wrapper fail. */ #define _GNU_SOURCE 1 #include #include #ifndef __GLIBC_PREREQ #define __GLIBC_PREREQ(x, y) 0 #endif #if defined(__GLIBC__) && !(__GLIBC_PREREQ(2, 23)) #define __NO_STRING_INLINES 1 #endif #include #include #include #include #include #include #include #include #include #include "util.h" #include "devmapper.h" #include "globals.c" /* * We can't just use mapinfo-test_OBJDEPS because */ #include "../libmultipath/devmapper.c" static const struct dm_info __attribute__((unused)) MPATH_DMI_01 = { .exists = 1, .live_table = 1, .open_count = 1, .target_count = 1, .major = 254, .minor = 123, }; static const struct dm_info __attribute__((unused)) MPATH_DMI_02 = { .exists = 1, .live_table = 0, .open_count = 1, .target_count = 0, .major = 254, .minor = 123, }; #define TGT_ANY "any" static const char MPATH_NAME_01[] = "mpathx"; static const char MPATH_UUID_01[] = "mpath-3600a098038302d414b2b4d4453474f62"; static const char MPATH_TARGET_01[] = "2 pg_init_retries 50 1 alua 2 1 " "service-time 0 3 2 65:32 1 1 67:64 1 1 69:96 1 1 " "service-time 0 3 2 8:16 1 1 66:48 1 1 68:80 1 1 "; static const char MPATH_STATUS_01[] = "2 0 1 0 2 1 " "A 0 3 2 65:32 A 0 0 1 67:64 A 0 0 1 69:96 A 0 0 1 " "E 0 3 2 8:16 A 0 0 1 66:48 A 0 0 1 68:80 A 0 0 1 "; static const char BAD_UUID_01[] = ""; static const char BAD_UUID_02[] = "mpath3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_03[] = " mpath-3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_04[] = "-mpath-3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_05[] = "mpth-3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_06[] = "part1-mpath-3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_07[] = "mpath 3600a098038302d414b2b4d4453474f62"; static const char BAD_UUID_08[] = "mpath"; static const char BAD_UUID_09[] = "mpath-"; char *__real_strdup(const char *str); char *__wrap_strdup(const char *str) { if (mock_type(int)) return __real_strdup(str); return NULL; } void __wrap_dm_task_destroy(struct dm_task *t) { } struct dm_task *__wrap_dm_task_create(int task) { check_expected(task); return mock_ptr_type(void *); } int __wrap_dm_task_run(struct dm_task *t) { return mock_type(int); } /* * Hack for older versions of libdevmapper, where dm_task_get_errno() * is not available. */ #ifndef LIBDM_API_GET_ERRNO #define WILL_RETURN_GET_ERRNO(y) do { errno = y; } while (0) #else int __wrap_dm_task_get_errno(struct dm_task *t) { return mock_type(int); } #define WILL_RETURN_GET_ERRNO(y) will_return(__wrap_dm_task_get_errno, y) #endif int __wrap_dm_task_set_name(struct dm_task *t, const char *name) { check_expected(name); return mock_type(int); } int __wrap_dm_task_set_uuid(struct dm_task *t, const char *uuid) { check_expected(uuid); return mock_type(int); } int __wrap_dm_task_set_major(struct dm_task *t, int val) { check_expected(val); return mock_type(int); } int __wrap_dm_task_set_minor(struct dm_task *t, int val) { check_expected(val); return mock_type(int); } /* between LVM2 2.02.110 and 2.02.112, dm_task_get_info was a macro */ #ifdef dm_task_get_info #define WRAP_DM_TASK_GET_INFO(x) \ will_return(__wrap_dm_task_get_info_with_deferred_remove, x) int __wrap_dm_task_get_info_with_deferred_remove(struct dm_task *t, struct dm_info *dmi) #else #define WRAP_DM_TASK_GET_INFO(x) \ will_return(__wrap_dm_task_get_info, x) int __wrap_dm_task_get_info(struct dm_task *t, struct dm_info *dmi) #endif { int rc = mock_type(int); assert_non_null(dmi); if (rc) { struct dm_info *info = mock_ptr_type(struct dm_info *); memcpy(dmi, info, sizeof(*dmi)); } return rc; } void * __wrap_dm_get_next_target(struct dm_task *dmt, void *next, uint64_t *start, uint64_t *length, char **target_type, char **params) { *start = 0; *length = mock_type(uint64_t); *target_type = mock_ptr_type(char *); *params = mock_ptr_type(char *); return mock_ptr_type(void *); } static void mock_dm_get_next_target(uint64_t len, const char *target_type, const char *params, void *next) { will_return(__wrap_dm_get_next_target, len); will_return(__wrap_dm_get_next_target, target_type); will_return(__wrap_dm_get_next_target, params); will_return(__wrap_dm_get_next_target, next); } const char *__wrap_dm_task_get_name(struct dm_task *t) { return mock_ptr_type(const char *); } const char *__wrap_dm_task_get_uuid(struct dm_task *t) { return mock_ptr_type(const char *); } static void mock_mapinfo_name_1(int ioctl_nr, int create_rc, const char *name, int name_rc, int run_rc, int err) { expect_value(__wrap_dm_task_create, task, ioctl_nr); will_return(__wrap_dm_task_create, create_rc); if (create_rc == 0) return; expect_value(__wrap_dm_task_set_name, name, name); will_return(__wrap_dm_task_set_name, name_rc); if (name_rc == 0) return; will_return(__wrap_dm_task_run, run_rc); if (run_rc == 0) { WILL_RETURN_GET_ERRNO(err); /* for dm_log_error() */ WILL_RETURN_GET_ERRNO(err); } } static void test_mapinfo_bad_task_create_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 0, NULL, 0, 0, 0); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_mapid(void **state) { int rc; /* can't use mock_mapinfo_name() here because of invalid id type */ expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); rc = libmp_mapinfo(DM_MAP_BY_NAME + 100, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_set_name(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 0, 0, 0); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_task_run_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_task_run_02(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 0, ENXIO); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NOT_FOUND); } /* libmp_mapinfo must choose DM_DEVICE_STATUS */ static void test_mapinfo_bad_task_run_03(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_task_run_04(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 0, ENXIO); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NOT_FOUND); } /* If target is set, libmp_mapinfo must choose DM_DEVICE_TABLE */ static void test_mapinfo_bad_task_run_05(void **state) { int rc; char *params = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = ¶ms }); assert_int_equal(rc, DMP_ERR); assert_ptr_equal(params, NULL); } static void test_mapinfo_bad_task_run_06(void **state) { int rc; char *params = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 0, ENXIO); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = ¶ms }); assert_int_equal(rc, DMP_NOT_FOUND); assert_ptr_equal(params, NULL); } /* If status is set, libmp_mapinfo must choose DM_DEVICE_STATUS */ static void test_mapinfo_bad_task_run_07(void **state) { int rc; char *params = NULL; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = ¶ms }); assert_int_equal(rc, DMP_ERR); assert_ptr_equal(params, NULL); } static void test_mapinfo_bad_task_run_08(void **state) { int rc; char *params = NULL; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 0, ENXIO); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = ¶ms }); assert_int_equal(rc, DMP_NOT_FOUND); assert_ptr_equal(params, NULL); } static void test_mapinfo_bad_task_run_09(void **state) { int rc; char *params = NULL, *status = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = ¶ms, .status = &status }); assert_int_equal(rc, DMP_ERR); assert_ptr_equal(params, NULL); assert_ptr_equal(status, NULL); } static void test_mapinfo_bad_task_run_10(void **state) { int rc; char *params = NULL, *status = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 0, ENXIO); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = ¶ms, .status = &status }); assert_int_equal(rc, DMP_NOT_FOUND); assert_ptr_equal(params, NULL); assert_ptr_equal(status, NULL); } static void test_mapinfo_bad_get_info_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(0); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_get_info_02(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&dmi); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NOT_FOUND); } static void test_mapinfo_bad_get_info_03(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(0); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_get_info_04(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&dmi); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NOT_FOUND); } static void test_mapinfo_good_exists(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_bad_check_uuid_00(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_check_uuid_01(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi, .name = name, .uuid = uuid }); assert_int_equal(rc, DMP_NO_MATCH); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, BAD_UUID_01)); } static void test_mapinfo_bad_check_uuid_02(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_02); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_03(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_03); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_04(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_04); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_05(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_05); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_06(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_06); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_07(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_07); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_08(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_08); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_check_uuid_09(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, BAD_UUID_09); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_check_uuid_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_check_uuid_02(void **state) { int rc; char uuid[DM_UUID_LEN]; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .uuid = uuid }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_check_uuid_03(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_check_uuid_04(void **state) { char __attribute__((cleanup(cleanup_charp))) *target = NULL; int rc; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); will_return(__wrap_strdup, 1); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY | MAPINFO_CHECK_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_bad_set_uuid(void **state) { int rc; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_uuid, uuid, "foo"); will_return(__wrap_dm_task_set_uuid, 0); rc = libmp_mapinfo(DM_MAP_BY_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_set_dev_01(void **state) { int rc; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 0); rc = libmp_mapinfo(DM_MAP_BY_DEV, (mapid_t) { ._u = { 254, 123 } }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_set_dev_02(void **state) { int rc; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 1); expect_value(__wrap_dm_task_set_minor, val, 123); will_return(__wrap_dm_task_set_minor, 0); rc = libmp_mapinfo(DM_MAP_BY_DEV, (mapid_t) { ._u = { 254, 123 } }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_set_dev_03(void **state) { int rc; dev_t devt = makedev(254, 123); expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 0); rc = libmp_mapinfo(DM_MAP_BY_DEVT, (mapid_t) { .devt = devt }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_bad_set_dev_04(void **state) { int rc; dev_t devt = makedev(254, 123); expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 1); expect_value(__wrap_dm_task_set_minor, val, 123); will_return(__wrap_dm_task_set_minor, 0); rc = libmp_mapinfo(DM_MAP_BY_DEVT, (mapid_t) { .devt = devt }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_good_info(void **state) { int rc; struct dm_info dmi; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); } static void test_mapinfo_good_by_uuid_info(void **state) { int rc; struct dm_info dmi; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_uuid, uuid, "foo"); will_return(__wrap_dm_task_set_uuid, 1); will_return(__wrap_dm_task_run, 1); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); rc = libmp_mapinfo(DM_MAP_BY_UUID, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); } static void test_mapinfo_good_by_dev_info(void **state) { int rc; struct dm_info dmi; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 1); expect_value(__wrap_dm_task_set_minor, val, 123); will_return(__wrap_dm_task_set_minor, 1); will_return(__wrap_dm_task_run, 1); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); rc = libmp_mapinfo(DM_MAP_BY_DEV, (mapid_t) { ._u = { 254, 123 } }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); } static void test_mapinfo_good_by_devt_info(void **state) { dev_t devt = makedev(254, 123); int rc; struct dm_info dmi; expect_value(__wrap_dm_task_create, task, DM_DEVICE_INFO); will_return(__wrap_dm_task_create, 1); expect_value(__wrap_dm_task_set_major, val, 254); will_return(__wrap_dm_task_set_major, 1); expect_value(__wrap_dm_task_set_minor, val, 123); will_return(__wrap_dm_task_set_minor, 1); will_return(__wrap_dm_task_run, 1); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); rc = libmp_mapinfo(DM_MAP_BY_DEVT, (mapid_t) { .devt = devt }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); } static void test_mapinfo_bad_name(void **state) { int rc; char name[WWID_SIZE] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = name }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_good_name(void **state) { int rc; char name[WWID_SIZE] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = name }); assert_int_equal(rc, DMP_OK); assert_true(!strcmp(name, MPATH_NAME_01)); } static void test_mapinfo_bad_uuid(void **state) { int rc; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .uuid = uuid }); assert_int_equal(rc, DMP_ERR); } static void test_mapinfo_good_uuid(void **state) { int rc; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_INFO, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .uuid = uuid }); assert_int_equal(rc, DMP_OK); assert_true(!strcmp(uuid, MPATH_UUID_01)); } /* If size is set, libmp_mapinfo needs to do a DM_DEVICE_STATUS ioctl */ static void test_mapinfo_good_size(void **state) { int rc; unsigned long long size; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_ANY, MPATH_TARGET_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .size = &size }); assert_int_equal(rc, DMP_OK); assert_int_equal(size, 12345); } static void test_mapinfo_bad_next_target_01(void **state) { int rc; unsigned long long size; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); /* multiple targets */ mock_dm_get_next_target(12345, NULL, MPATH_STATUS_01, (void *)1); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .size = &size }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_next_target_02(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; unsigned long long size = 0; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_02); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); /* no targets */ mock_dm_get_next_target(0, NULL, NULL, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi, .name = name, .uuid = uuid, .size = &size }); assert_int_equal(rc, DMP_EMPTY); assert_int_equal(size, 0); assert_memory_equal(&dmi, &MPATH_DMI_02, sizeof(dmi)); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, MPATH_UUID_01)); } /* libmp_mapinfo needs to do a DM_DEVICE_STATUS ioctl */ static void test_mapinfo_bad_target_type_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, "linear", MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_target_type_02(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_NO_MATCH); } static void test_mapinfo_bad_target_type_03(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); mock_dm_get_next_target(12345, TGT_PART, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi, .name = name, .uuid = uuid }); assert_int_equal(rc, DMP_NO_MATCH); /* make sure that the ouput was filled in */ assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, MPATH_UUID_01)); } static void test_mapinfo_bad_target_type_04(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *status = NULL; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = &status }); assert_int_equal(rc, DMP_NO_MATCH); assert_null(status); } static void test_mapinfo_bad_target_type_05(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *target = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target }); assert_int_equal(rc, DMP_NO_MATCH); assert_null(target); } static void test_mapinfo_good_target_type_01(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_target_type_02(void **state) { int rc; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_PART, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_PART_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .name = NULL }); assert_int_equal(rc, DMP_OK); } static void test_mapinfo_good_target_type_03(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); } /* test for returning multiple parameters */ static void test_mapinfo_good_target_type_04(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi, .name = name, .uuid = uuid }); assert_int_equal(rc, DMP_OK); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, MPATH_UUID_01)); } static void test_mapinfo_no_table_01(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); /* DMI with no live table, MAPINFO_CHECK_UUID not set */ WRAP_DM_TASK_GET_INFO(&MPATH_DMI_02); mock_dm_get_next_target(0, NULL, NULL, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi }); assert_int_equal(rc, DMP_EMPTY); assert_memory_equal(&dmi, &MPATH_DMI_02, sizeof(dmi)); } static void test_mapinfo_no_table_02(void **state) { int rc; struct dm_info dmi = { .suspended = 0 }; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; unsigned long long size = 0; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); /* DMI with no live table, MAPINFO_CHECK_UUID set */ WRAP_DM_TASK_GET_INFO(&MPATH_DMI_02); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); mock_dm_get_next_target(0, NULL, NULL, NULL); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); rc = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_CHECK_UUID | MAPINFO_MPATH_ONLY, (mapid_t) { .str = "foo", }, (mapinfo_t) { .dmi = &dmi, .name = name, .uuid = uuid, .size = &size }); assert_int_equal(rc, DMP_EMPTY); assert_int_equal(size, 0); assert_memory_equal(&dmi, &MPATH_DMI_02, sizeof(dmi)); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, MPATH_UUID_01)); } static void test_mapinfo_good_status_01(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *status = NULL; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_strdup, 1); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = &status }); assert_int_equal(rc, DMP_OK); assert_non_null(status); assert_true(!strcmp(status, MPATH_STATUS_01)); } static void test_mapinfo_bad_strdup_01(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); will_return(__wrap_strdup, 0); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = &status, .uuid = uuid, .name = name }); assert_int_equal(rc, DMP_ERR); assert_null(status); } static void test_mapinfo_bad_get_name_01(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = &status, .uuid = uuid, .name = name }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_memory_equal(&name, &((char[WWID_SIZE]) { 0 }), WWID_SIZE); assert_memory_equal(&uuid, &((char[DM_UUID_LEN]) { 0 }), DM_UUID_LEN); } static void test_mapinfo_bad_get_uuid_01(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .status = &status, .uuid = uuid, .name = name }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_memory_equal(&name, &((char[WWID_SIZE]) { 0 }), WWID_SIZE); assert_memory_equal(&uuid, &((char[DM_UUID_LEN]) { 0 }), DM_UUID_LEN); } static void test_mapinfo_bad_task_run_11(void **state) { int rc; char *params = NULL, *status = NULL; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_ANY, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 1); /* error in 2nd dm_task_run */ mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 0, EINVAL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = ¶ms, .status = &status }); assert_int_equal(rc, DMP_ERR); assert_ptr_equal(params, NULL); assert_ptr_equal(status, NULL); } static void test_mapinfo_bad_get_name_02(void **state) { int rc; char *target = NULL, *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 1); /* 2nd ioctl */ mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target, .status = &status, .uuid = uuid, .name = name, .dmi = &dmi }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_null(target); assert_memory_equal(&dmi, &((struct dm_info) { .suspended = 0 }), sizeof(dmi)); assert_memory_equal(&name, &((char[WWID_SIZE]) { 0 }), WWID_SIZE); assert_memory_equal(&uuid, &((char[DM_UUID_LEN]) { 0 }), DM_UUID_LEN); } static void test_mapinfo_bad_get_uuid_02(void **state) { int rc; char *target = NULL, *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 1); /* 2nd ioctl */ mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, NULL); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target, .status = &status, .uuid = uuid, .name = name, .dmi = &dmi }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_null(target); assert_memory_equal(&dmi, &((struct dm_info) { .suspended = 0 }), sizeof(dmi)); assert_memory_equal(&name, &((char[WWID_SIZE]) { 0 }), WWID_SIZE); assert_memory_equal(&uuid, &((char[DM_UUID_LEN]) { 0 }), DM_UUID_LEN); } static void test_mapinfo_bad_strdup_02(void **state) { int rc; char *target = NULL, *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 1); /* 2nd ioctl */ mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); will_return(__wrap_strdup, 0); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target, .status = &status, .uuid = uuid, .name = name, .dmi = &dmi }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_null(target); } static void test_mapinfo_bad_strdup_03(void **state) { int rc; char *target = NULL, *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; struct dm_info dmi = { .suspended = 0 }; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 0); /* No 2nd ioctl, as there was an error in the 1st */ rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target, .status = &status, .uuid = uuid, .name = name, .dmi = &dmi }); assert_int_equal(rc, DMP_ERR); assert_null(status); assert_null(target); assert_memory_equal(&dmi, &((struct dm_info) { .suspended = 0 }), sizeof(dmi)); assert_memory_equal(&name, &((char[WWID_SIZE]) { 0 }), WWID_SIZE); assert_memory_equal(&uuid, &((char[DM_UUID_LEN]) { 0 }), DM_UUID_LEN); } static void test_mapinfo_good_all_01(void **state) { int rc; char __attribute__((cleanup(cleanup_charp))) *target = NULL; char __attribute__((cleanup(cleanup_charp))) *status = NULL; char name[WWID_SIZE] = { 0 }; char uuid[DM_UUID_LEN] = { 0 }; struct dm_info dmi = { .suspended = 0 }; unsigned long long size; mock_mapinfo_name_1(DM_DEVICE_TABLE, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_TARGET_01, NULL); will_return(__wrap_strdup, 1); /* 2nd ioctl */ mock_mapinfo_name_1(DM_DEVICE_STATUS, 1, "foo", 1, 1, 0); WRAP_DM_TASK_GET_INFO(1); WRAP_DM_TASK_GET_INFO(&MPATH_DMI_01); mock_dm_get_next_target(12345, TGT_MPATH, MPATH_STATUS_01, NULL); will_return(__wrap_dm_task_get_name, MPATH_NAME_01); will_return(__wrap_dm_task_get_uuid, MPATH_UUID_01); will_return(__wrap_strdup, 1); rc = libmp_mapinfo(DM_MAP_BY_NAME, (mapid_t) { .str = "foo", }, (mapinfo_t) { .target = &target, .status = &status, .uuid = uuid, .name = name, .dmi = &dmi, .size = &size }); assert_int_equal(rc, DMP_OK); assert_non_null(status); assert_non_null(target); assert_int_equal(size, 12345); assert_memory_equal(&dmi, &MPATH_DMI_01, sizeof(dmi)); assert_true(!strcmp(target, MPATH_TARGET_01)); assert_true(!strcmp(status, MPATH_STATUS_01)); assert_true(!strcmp(name, MPATH_NAME_01)); assert_true(!strcmp(uuid, MPATH_UUID_01)); } static int test_mapinfo(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_mapinfo_bad_task_create_01), cmocka_unit_test(test_mapinfo_bad_mapid), cmocka_unit_test(test_mapinfo_bad_set_name), cmocka_unit_test(test_mapinfo_bad_task_run_01), cmocka_unit_test(test_mapinfo_bad_task_run_02), cmocka_unit_test(test_mapinfo_bad_task_run_03), cmocka_unit_test(test_mapinfo_bad_task_run_04), cmocka_unit_test(test_mapinfo_bad_task_run_05), cmocka_unit_test(test_mapinfo_bad_task_run_06), cmocka_unit_test(test_mapinfo_bad_task_run_07), cmocka_unit_test(test_mapinfo_bad_task_run_08), cmocka_unit_test(test_mapinfo_bad_task_run_09), cmocka_unit_test(test_mapinfo_bad_task_run_10), cmocka_unit_test(test_mapinfo_bad_task_run_11), cmocka_unit_test(test_mapinfo_bad_get_info_01), cmocka_unit_test(test_mapinfo_bad_get_info_02), cmocka_unit_test(test_mapinfo_bad_get_info_03), cmocka_unit_test(test_mapinfo_bad_get_info_04), cmocka_unit_test(test_mapinfo_good_exists), cmocka_unit_test(test_mapinfo_bad_check_uuid_00), cmocka_unit_test(test_mapinfo_bad_check_uuid_01), cmocka_unit_test(test_mapinfo_bad_check_uuid_02), cmocka_unit_test(test_mapinfo_bad_check_uuid_03), cmocka_unit_test(test_mapinfo_bad_check_uuid_04), cmocka_unit_test(test_mapinfo_bad_check_uuid_05), cmocka_unit_test(test_mapinfo_bad_check_uuid_06), cmocka_unit_test(test_mapinfo_bad_check_uuid_07), cmocka_unit_test(test_mapinfo_bad_check_uuid_08), cmocka_unit_test(test_mapinfo_bad_check_uuid_09), cmocka_unit_test(test_mapinfo_good_check_uuid_01), cmocka_unit_test(test_mapinfo_good_check_uuid_02), cmocka_unit_test(test_mapinfo_good_check_uuid_03), cmocka_unit_test(test_mapinfo_good_check_uuid_04), cmocka_unit_test(test_mapinfo_bad_set_uuid), cmocka_unit_test(test_mapinfo_bad_set_dev_01), cmocka_unit_test(test_mapinfo_bad_set_dev_02), cmocka_unit_test(test_mapinfo_bad_set_dev_03), cmocka_unit_test(test_mapinfo_bad_set_dev_04), cmocka_unit_test(test_mapinfo_good_info), cmocka_unit_test(test_mapinfo_good_by_uuid_info), cmocka_unit_test(test_mapinfo_good_by_dev_info), cmocka_unit_test(test_mapinfo_good_by_devt_info), cmocka_unit_test(test_mapinfo_bad_name), cmocka_unit_test(test_mapinfo_good_name), cmocka_unit_test(test_mapinfo_bad_uuid), cmocka_unit_test(test_mapinfo_good_uuid), cmocka_unit_test(test_mapinfo_good_size), cmocka_unit_test(test_mapinfo_bad_next_target_01), cmocka_unit_test(test_mapinfo_bad_next_target_02), cmocka_unit_test(test_mapinfo_bad_target_type_01), cmocka_unit_test(test_mapinfo_bad_target_type_02), cmocka_unit_test(test_mapinfo_bad_target_type_03), cmocka_unit_test(test_mapinfo_bad_target_type_04), cmocka_unit_test(test_mapinfo_bad_target_type_05), cmocka_unit_test(test_mapinfo_good_target_type_01), cmocka_unit_test(test_mapinfo_good_target_type_02), cmocka_unit_test(test_mapinfo_good_target_type_03), cmocka_unit_test(test_mapinfo_good_target_type_04), cmocka_unit_test(test_mapinfo_no_table_01), cmocka_unit_test(test_mapinfo_no_table_02), cmocka_unit_test(test_mapinfo_good_status_01), cmocka_unit_test(test_mapinfo_bad_get_name_01), cmocka_unit_test(test_mapinfo_bad_get_uuid_01), cmocka_unit_test(test_mapinfo_bad_strdup_01), cmocka_unit_test(test_mapinfo_bad_get_name_02), cmocka_unit_test(test_mapinfo_bad_get_uuid_02), cmocka_unit_test(test_mapinfo_bad_strdup_02), cmocka_unit_test(test_mapinfo_bad_strdup_03), cmocka_unit_test(test_mapinfo_good_all_01), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(4); skip_libmp_dm_init(); ret += test_mapinfo(); return ret; } multipath-tools-0.11.1/tests/mpathvalid.c000066400000000000000000000311311475246302400204040ustar00rootroot00000000000000/* * Copyright (c) 2020 Benjamin Marzinski, Red Hat * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include "structs.h" #include "config.h" #include "mpath_valid.h" #include "util.h" #include "debug.h" const char *test_dev = "test_name"; #define TEST_WWID "WWID_123" #define CONF_TEMPLATE "mpathvalid-testconf-XXXXXXXX" char conf_name[] = CONF_TEMPLATE; bool initialized; #if 0 static int mode_to_findmp(unsigned int mode) { switch (mode) { case MPATH_SMART: return FIND_MULTIPATHS_SMART; case MPATH_GREEDY: return FIND_MULTIPATHS_GREEDY; case MPATH_STRICT: return FIND_MULTIPATHS_STRICT; } fail_msg("invalid mode: %u", mode); return FIND_MULTIPATHS_UNDEF; } #endif static unsigned int findmp_to_mode(int findmp) { switch (findmp) { case FIND_MULTIPATHS_SMART: return MPATH_SMART; case FIND_MULTIPATHS_GREEDY: return MPATH_GREEDY; case FIND_MULTIPATHS_STRICT: case FIND_MULTIPATHS_OFF: case FIND_MULTIPATHS_ON: return MPATH_STRICT; } fail_msg("invalid find_multipaths value: %d", findmp); return MPATH_DEFAULT; } int __wrap_is_path_valid(const char *name, struct config *conf, struct path *pp, bool check_multipathd) { int r = mock_type(int); int findmp = mock_type(int); assert_ptr_equal(name, test_dev); assert_ptr_not_equal(conf, NULL); assert_ptr_not_equal(pp, NULL); assert_true(check_multipathd); assert_int_equal(findmp, conf->find_multipaths); if (r == MPATH_IS_ERROR || r == MPATH_IS_NOT_VALID) return r; strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); return r; } int __wrap_libmultipath_init(void) { int r = mock_type(int); assert_false(initialized); if (r != 0) return r; initialized = true; return 0; } void __wrap_libmultipath_exit(void) { assert_true(initialized); initialized = false; } int __wrap_dm_prereq(unsigned int *v) { assert_ptr_not_equal(v, NULL); return mock_type(int); } int __real_init_config(const char *file); int __wrap_init_config(const char *file) { int r = mock_type(int); struct config *conf; assert_string_equal(file, DEFAULT_CONFIGFILE); if (r != 0) return r; assert_string_not_equal(conf_name, CONF_TEMPLATE); r = __real_init_config(conf_name); conf = get_multipath_config(); assert_ptr_not_equal(conf, NULL); assert_int_equal(conf->find_multipaths, mock_type(int)); return 0; } static const char * const find_multipaths_optvals[] = { [FIND_MULTIPATHS_OFF] = "off", [FIND_MULTIPATHS_ON] = "on", [FIND_MULTIPATHS_STRICT] = "strict", [FIND_MULTIPATHS_GREEDY] = "greedy", [FIND_MULTIPATHS_SMART] = "smart", }; void make_config_file(int findmp) { int r, fd; char buf[64]; assert_true(findmp > FIND_MULTIPATHS_UNDEF && findmp < FIND_MULTIPATHS_LAST__); r = snprintf(buf, sizeof(buf), "defaults {\nfind_multipaths %s\n}\n", find_multipaths_optvals[findmp]); assert_true(r > 0 && (long unsigned int)r < sizeof(buf)); memcpy(conf_name, CONF_TEMPLATE, sizeof(conf_name)); fd = mkstemp(conf_name); assert_true(fd >= 0); assert_int_equal(safe_write(fd, buf, r), 0); assert_int_equal(close(fd), 0); } int setup(void **state) { initialized = false; udev = udev_new(); if (udev == NULL) return -1; return 0; } int teardown(void **state) { struct config *conf; conf = get_multipath_config(); put_multipath_config(conf); if (conf) uninit_config(); if (strcmp(conf_name, CONF_TEMPLATE) != 0) unlink(conf_name); udev_unref(udev); udev = NULL; return 0; } static void check_config(bool valid_config) { struct config *conf; conf = get_multipath_config(); put_multipath_config(conf); if (valid_config) assert_ptr_not_equal(conf, NULL); } /* libmultipath_init fails */ static void test_mpathvalid_init_bad1(void **state) { will_return(__wrap_libmultipath_init, 1); assert_int_equal(mpathvalid_init(MPATH_LOG_PRIO_DEBUG, MPATH_LOG_STDERR), -1); assert_false(initialized); check_config(false); } /* init_config fails */ static void test_mpathvalid_init_bad2(void **state) { will_return(__wrap_libmultipath_init, 0); will_return(__wrap_init_config, 1); assert_int_equal(mpathvalid_init(MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR_TIMESTAMP), -1); assert_false(initialized); check_config(false); } /* dm_prereq fails */ static void test_mpathvalid_init_bad3(void **state) { make_config_file(FIND_MULTIPATHS_STRICT); will_return(__wrap_libmultipath_init, 0); will_return(__wrap_init_config, 0); will_return(__wrap_init_config, FIND_MULTIPATHS_STRICT); will_return(__wrap_dm_prereq, 1); assert_int_equal(mpathvalid_init(MPATH_LOG_STDERR, MPATH_LOG_PRIO_ERR), -1); assert_false(initialized); check_config(false); } static void check_mpathvalid_init(int findmp, int prio, int log_style) { make_config_file(findmp); will_return(__wrap_libmultipath_init, 0); will_return(__wrap_init_config, 0); will_return(__wrap_init_config, findmp); will_return(__wrap_dm_prereq, 0); assert_int_equal(mpathvalid_init(prio, log_style), 0); assert_true(initialized); check_config(true); assert_int_equal(logsink, log_style); assert_int_equal(libmp_verbosity, prio); assert_int_equal(findmp_to_mode(findmp), mpathvalid_get_mode()); } static void check_mpathvalid_exit(void) { assert_int_equal(mpathvalid_exit(), 0); assert_false(initialized); check_config(false); } static void test_mpathvalid_init_good1(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR_TIMESTAMP); } static void test_mpathvalid_init_good2(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_STRICT, MPATH_LOG_PRIO_DEBUG, MPATH_LOG_STDERR); } static void test_mpathvalid_init_good3(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_NOLOG, MPATH_LOG_SYSLOG); } static void test_mpathvalid_exit(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); check_mpathvalid_exit(); } /* fails if config hasn't been set */ static void test_mpathvalid_get_mode_bad(void **state) { #if 1 assert_int_equal(mpathvalid_get_mode(), MPATH_MODE_ERROR); #else assert_int_equal(mpathvalid_get_mode(), 1); #endif } /*fails if config hasn't been set */ static void test_mpathvalid_reload_config_bad1(void **state) { #if 1 will_return(__wrap_init_config, 1); #endif assert_int_equal(mpathvalid_reload_config(), -1); check_config(false); } /* init_config fails */ static void test_mpathvalid_reload_config_bad2(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_init_config, 1); assert_int_equal(mpathvalid_reload_config(), -1); check_config(false); check_mpathvalid_exit(); } static void check_mpathvalid_reload_config(int findmp) { assert_string_not_equal(conf_name, CONF_TEMPLATE); unlink(conf_name); make_config_file(findmp); will_return(__wrap_init_config, 0); will_return(__wrap_init_config, findmp); assert_int_equal(mpathvalid_reload_config(), 0); check_config(true); assert_int_equal(findmp_to_mode(findmp), mpathvalid_get_mode()); } static void test_mpathvalid_reload_config_good(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); check_mpathvalid_reload_config(FIND_MULTIPATHS_ON); check_mpathvalid_reload_config(FIND_MULTIPATHS_GREEDY); check_mpathvalid_reload_config(FIND_MULTIPATHS_SMART); check_mpathvalid_reload_config(FIND_MULTIPATHS_STRICT); check_mpathvalid_exit(); } /* NULL name */ static void test_mpathvalid_is_path_bad1(void **state) { assert_int_equal(mpathvalid_is_path(NULL, MPATH_STRICT, NULL, NULL, 0), MPATH_IS_ERROR); } /* bad mode */ static void test_mpathvalid_is_path_bad2(void **state) { assert_int_equal(mpathvalid_is_path(test_dev, MPATH_MODE_ERROR, NULL, NULL, 0), MPATH_IS_ERROR); } /* NULL path_wwids and non-zero nr_paths */ static void test_mpathvalid_is_path_bad3(void **state) { assert_int_equal(mpathvalid_is_path(test_dev, MPATH_MODE_ERROR, NULL, NULL, 1), MPATH_IS_ERROR); } /*fails if config hasn't been set */ static void test_mpathvalid_is_path_bad4(void **state) { #if 0 will_return(__wrap_is_path_valid, MPATH_IS_ERROR); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_STRICT); #endif assert_int_equal(mpathvalid_is_path(test_dev, MPATH_STRICT, NULL, NULL, 0), MPATH_IS_ERROR); } /* is_path_valid fails */ static void test_mpathvalid_is_path_bad5(void **state) { check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_ERROR); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_GREEDY); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_GREEDY, NULL, NULL, 0), MPATH_IS_ERROR); check_mpathvalid_exit(); } static void test_mpathvalid_is_path_good1(void **state) { char *wwid; check_mpathvalid_init(FIND_MULTIPATHS_STRICT, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_NOT_VALID); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_STRICT); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid, NULL, 0), MPATH_IS_NOT_VALID); assert_ptr_equal(wwid, NULL); check_mpathvalid_exit(); } static void test_mpathvalid_is_path_good2(void **state) { const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" }; char *wwid; check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_VALID); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_ON); will_return(__wrap_is_path_valid, TEST_WWID); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid, wwids, 4), MPATH_IS_VALID); assert_string_equal(wwid, TEST_WWID); free(wwid); } static void test_mpathvalid_is_path_good3(void **state) { const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" }; char *wwid; check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_VALID); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART); will_return(__wrap_is_path_valid, TEST_WWID); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_SMART, &wwid, wwids, 4), MPATH_IS_VALID); assert_string_equal(wwid, TEST_WWID); free(wwid); } /* maybe valid with no matching paths */ static void test_mpathvalid_is_path_good4(void **state) { const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" }; char *wwid; check_mpathvalid_init(FIND_MULTIPATHS_SMART, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_MAYBE_VALID); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART); will_return(__wrap_is_path_valid, TEST_WWID); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid, wwids, 4), MPATH_IS_MAYBE_VALID); assert_string_equal(wwid, TEST_WWID); free(wwid); } /* maybe valid with matching paths */ static void test_mpathvalid_is_path_good5(void **state) { const char *wwids[] = { "WWID_A", "WWID_B", TEST_WWID, "WWID_D" }; char *wwid; check_mpathvalid_init(FIND_MULTIPATHS_SMART, MPATH_LOG_PRIO_ERR, MPATH_LOG_STDERR); will_return(__wrap_is_path_valid, MPATH_IS_MAYBE_VALID); will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART); will_return(__wrap_is_path_valid, TEST_WWID); assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid, wwids, 4), MPATH_IS_VALID); assert_string_equal(wwid, TEST_WWID); free(wwid); } #define setup_test(name) \ cmocka_unit_test_setup_teardown(name, setup, teardown) int test_mpathvalid(void) { const struct CMUnitTest tests[] = { setup_test(test_mpathvalid_init_bad1), setup_test(test_mpathvalid_init_bad2), setup_test(test_mpathvalid_init_bad3), setup_test(test_mpathvalid_init_good1), setup_test(test_mpathvalid_init_good2), setup_test(test_mpathvalid_init_good3), setup_test(test_mpathvalid_exit), setup_test(test_mpathvalid_get_mode_bad), setup_test(test_mpathvalid_reload_config_bad1), setup_test(test_mpathvalid_reload_config_bad2), setup_test(test_mpathvalid_reload_config_good), setup_test(test_mpathvalid_is_path_bad1), setup_test(test_mpathvalid_is_path_bad2), setup_test(test_mpathvalid_is_path_bad3), setup_test(test_mpathvalid_is_path_bad4), setup_test(test_mpathvalid_is_path_bad5), setup_test(test_mpathvalid_is_path_good1), setup_test(test_mpathvalid_is_path_good2), setup_test(test_mpathvalid_is_path_good3), setup_test(test_mpathvalid_is_path_good4), setup_test(test_mpathvalid_is_path_good5), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int r = 0; r += test_mpathvalid(); return r; } multipath-tools-0.11.1/tests/parser.c000066400000000000000000000317631475246302400175620ustar00rootroot00000000000000/* * Copyright (c) 2018 SUSE Linux GmbH * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include // #include "list.h" #include "parser.h" #include "vector.h" #include "globals.c" /* Set these to 1 to get success for current broken behavior */ /* Strip leading whitespace between quotes */ #define LSTRIP_QUOTED_WSP 0 /* Stop parsing at 2nd quote */ #define TWO_QUOTES_ONLY 0 static char *test_file = "test.conf"; /* Missing declaration */ int validate_config_strvec(vector strvec, char *file); /* Stringify helpers */ #define _str_(x) #x #define str(x) _str_(x) static int setup(void **state) { return 0; } static int teardown(void **state) { return 0; } static void test01(void **state) { vector v = alloc_strvec("keyword value"); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 2); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test02(void **state) { vector v = alloc_strvec("keyword \"value\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "value"); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test03(void **state) { vector v = alloc_strvec("keyword value\n"); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 2); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test04(void **state) { vector v = alloc_strvec("keyword \t value \t \n "); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 2); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test05(void **state) { vector v = alloc_strvec("keyword \t value \t ! comment "); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 2); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test06(void **state) { vector v = alloc_strvec("keyword \t value # \n comment "); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 2); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test07(void **state) { vector v = alloc_strvec("keyword \t value more "); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 3); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); assert_string_equal(VECTOR_SLOT(v, 2), "more"); val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test08(void **state) { #define QUOTED08 " value more " #define QUOTED08B "value more " vector v = alloc_strvec("keyword \t \"" QUOTED08 "\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; #if LSTRIP_QUOTED_WSP assert_string_equal(VECTOR_SLOT(v, 2), QUOTED08B); #else assert_string_equal(VECTOR_SLOT(v, 2), QUOTED08); #endif assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); #if LSTRIP_QUOTED_WSP assert_string_equal(val, QUOTED08B); #else assert_string_equal(val, QUOTED08); #endif free(val); free_strvec(v); } static void test09(void **state) { #define QUOTED09 "value # more" vector v = alloc_strvec("keyword \"" QUOTED09 "\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), QUOTED09); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, QUOTED09); free(val); free_strvec(v); } static void test10(void **state) { #define QUOTED10 "value ! more" vector v = alloc_strvec("keyword \"" QUOTED10 "\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), QUOTED10); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, QUOTED10); free(val); free_strvec(v); } static void test11(void **state) { #define QUOTED11 "value comment" vector v = alloc_strvec("keyword\"" QUOTED11 "\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), QUOTED11); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, QUOTED11); free(val); free_strvec(v); } static void test12(void **state) { vector v = alloc_strvec("key\"word\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "key"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "word"); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, "word"); free(val); free_strvec(v); } static void test13(void **state) { vector v = alloc_strvec("keyword value \"quoted\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 5); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_string_equal(VECTOR_SLOT(v, 1), "value"); assert_true(is_quote(VECTOR_SLOT(v, 2)));; assert_string_equal(VECTOR_SLOT(v, 3), "quoted"); assert_true(is_quote(VECTOR_SLOT(v, 4)));; val = set_value(v); assert_string_equal(val, "value"); free(val); free_strvec(v); } static void test14(void **state) { vector v = alloc_strvec("keyword \"value \" comment\"\""); char *val; assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 7); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "value "); assert_true(is_quote(VECTOR_SLOT(v, 3)));; assert_string_equal(VECTOR_SLOT(v, 4), "comment"); assert_true(is_quote(VECTOR_SLOT(v, 5)));; assert_true(is_quote(VECTOR_SLOT(v, 6)));; val = set_value(v); assert_string_equal(val, "value "); free(val); free_strvec(v); } static void test15(void **state) { #define QUOTED15 "word value\n comment" vector v = alloc_strvec("key\"" QUOTED15 "\""); char *val; assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "key"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), QUOTED15); assert_true(is_quote(VECTOR_SLOT(v, 3)));; assert_int_equal(validate_config_strvec(v, test_file), 0); val = set_value(v); assert_string_equal(val, QUOTED15); free(val); free_strvec(v); } static void test16(void **state) { vector v = alloc_strvec("keyword \"2.5\"\" SSD\""); char *val; #if TWO_QUOTES_ONLY assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 6); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "2.5"); assert_true(is_quote(VECTOR_SLOT(v, 3)));; assert_string_equal(VECTOR_SLOT(v, 4), "SSD"); assert_true(is_quote(VECTOR_SLOT(v, 5)));; val = set_value(v); assert_string_equal(val, "2.5"); #else assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "2.5\" SSD"); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, "2.5\" SSD"); #endif free(val); free_strvec(v); } static void test17(void **state) { vector v = alloc_strvec("keyword \"\"\"\"\" is empty\""); char *val; #if TWO_QUOTES_ONLY assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 6); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_true(is_quote(VECTOR_SLOT(v, 2)));; assert_true(is_quote(VECTOR_SLOT(v, 3)));; #if LSTRIP_QUOTED_WSP assert_string_equal(VECTOR_SLOT(v, 4), "is empty"); #else assert_string_equal(VECTOR_SLOT(v, 4), " is empty"); #endif assert_true(is_quote(VECTOR_SLOT(v, 5)));; val = set_value(v); assert_string_equal(val, ""); #else assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "\"\" is empty"); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, "\"\" is empty"); #endif free(val); free_strvec(v); } static void test18(void **state) { vector v = alloc_strvec("keyword \"\"\"\""); char *val; #if TWO_QUOTES_ONLY assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 5); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_true(is_quote(VECTOR_SLOT(v, 2)));; assert_true(is_quote(VECTOR_SLOT(v, 3)));; assert_true(is_quote(VECTOR_SLOT(v, 4)));; val = set_value(v); assert_string_equal(val, ""); #else assert_int_equal(validate_config_strvec(v, test_file), 0); assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "keyword"); assert_true(is_quote(VECTOR_SLOT(v, 1)));; assert_string_equal(VECTOR_SLOT(v, 2), "\""); assert_true(is_quote(VECTOR_SLOT(v, 3)));; val = set_value(v); assert_string_equal(val, "\""); #endif free(val); free_strvec(v); } static void test19(void **state) { #define QUOTED19 "!value" vector v = alloc_strvec("key \"" QUOTED19 "\""); char *val; assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "key"); assert_true(is_quote(VECTOR_SLOT(v, 1))); assert_string_equal(VECTOR_SLOT(v, 2), QUOTED19); assert_true(is_quote(VECTOR_SLOT(v, 3))); assert_int_equal(validate_config_strvec(v, test_file), 0); val = set_value(v); assert_string_equal(val, QUOTED19); free(val); free_strvec(v); } static void test20(void **state) { #define QUOTED20 "#value" vector v = alloc_strvec("key \"" QUOTED20 "\""); char *val; assert_int_equal(VECTOR_SIZE(v), 4); assert_string_equal(VECTOR_SLOT(v, 0), "key"); assert_true(is_quote(VECTOR_SLOT(v, 1))); assert_string_equal(VECTOR_SLOT(v, 2), QUOTED20); assert_true(is_quote(VECTOR_SLOT(v, 3))); assert_int_equal(validate_config_strvec(v, test_file), 0); val = set_value(v); assert_string_equal(val, QUOTED20); free(val); free_strvec(v); } int test_config_parser(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test01), cmocka_unit_test(test02), cmocka_unit_test(test03), cmocka_unit_test(test04), cmocka_unit_test(test05), cmocka_unit_test(test06), cmocka_unit_test(test07), cmocka_unit_test(test08), cmocka_unit_test(test09), cmocka_unit_test(test10), cmocka_unit_test(test11), cmocka_unit_test(test12), cmocka_unit_test(test13), cmocka_unit_test(test14), cmocka_unit_test(test15), cmocka_unit_test(test16), cmocka_unit_test(test17), cmocka_unit_test(test18), cmocka_unit_test(test19), cmocka_unit_test(test20), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_config_parser(); return ret; } multipath-tools-0.11.1/tests/pgpolicy.c000066400000000000000000001060711475246302400201070ustar00rootroot00000000000000/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include "globals.c" #include "pgpolicies.h" struct multipath mp8, mp4, mp1, mp0, mp_null; struct path p8[8], p4[4], p1[1]; static void set_tpg(struct path *pp, int *tpg, int size) { int i; for (i = 0; i < size; i++) { pp[i].tpg_id = tpg[i]; } } static void set_priority(struct path *pp, int *prio, int size) { int i; for (i = 0; i < size; i++) { pp[i].priority = prio[i]; } } static void set_marginal(struct path *pp, int *marginal, int size) { int i; for (i = 0; i < size; i++) { pp[i].marginal = marginal[i]; } } static void set_tgt_node_name(struct path *pp, char **tgt_node_name, int size) { int i; for (i = 0; i < size; i++) { strcpy(pp[i].tgt_node_name, tgt_node_name[i]); } } static void set_serial(struct path *pp, char **serial, int size) { int i; for (i = 0; i < size; i++) { strcpy(pp[i].serial, serial[i]); } } static int setup(void **state) { int i; for (i = 0; i < 8; i++) { sprintf(p8[i].dev, "p8_%d", i); sprintf(p8[i].dev_t, "8:%d", i); p8[i].state = PATH_UP; } for (i = 0; i < 4; i++) { sprintf(p4[i].dev, "p4_%d", i); sprintf(p4[i].dev_t, "4:%d", i); p4[i].state = PATH_UP; } sprintf(p1[0].dev, "p1_0"); sprintf(p1[0].dev_t, "4:0"); p1[0].state = PATH_UP; return 0; } static int setupX(struct multipath *mp, struct path *pp, int size) { int i; int prio[8] = {10, 10, 10, 10, 10, 10, 10, 10}; int marginal[8] = {0, 0, 0, 0, 0, 0, 0, 0}; mp->paths = vector_alloc(); if (!mp->paths) return -1; for (i = 0; i < size; i++) { if (!vector_alloc_slot(mp->paths)) return -1; vector_set_slot(mp->paths, &pp[i]); } set_priority(pp, prio, size); set_marginal(pp, marginal, size); mp->pgpolicyfn = NULL; return 0; } static int setup8(void **state) { return setupX(&mp8, p8, 8); } static int setup4(void **state) { return setupX(&mp4, p4, 4); } static int setup1(void **state) { return setupX(&mp1, p1, 1); } static int setup0(void **state) { return setupX(&mp0, NULL, 0); } static int setup_null(void **state) { return 0; } static int teardownX(struct multipath *mp) { free_pgvec(mp->pg, KEEP_PATHS); mp->pg = NULL; return 0; } static int teardown8(void **state) { return teardownX(&mp8); } static int teardown4(void **state) { return teardownX(&mp4); } static int teardown1(void **state) { return teardownX(&mp1); } static int teardown0(void **state) { return teardownX(&mp0); } static int teardown_null(void **state) { return teardownX(&mp_null); } static void verify_pathgroups(struct multipath *mp, struct path *pp, int **groups, int *group_size, int *marginal, int size) { int i, j; struct pathgroup *pgp; assert_null(mp->paths); assert_non_null(mp->pg); assert_int_equal(VECTOR_SIZE(mp->pg), size); for (i = 0; i < size; i++) { pgp = VECTOR_SLOT(mp->pg, i); assert_non_null(pgp); assert_non_null(pgp->paths); assert_int_equal(VECTOR_SIZE(pgp->paths), group_size[i]); if (marginal) assert_int_equal(pgp->marginal, marginal[i]); else assert_int_equal(pgp->marginal, 0); for (j = 0; j < group_size[i]; j++) { int path_nr = groups[i][j]; struct path *pgp_path = VECTOR_SLOT(pgp->paths, j); struct path *pp_path = &pp[path_nr]; /* Test names instead of pointers to get a more * useful error message */ assert_string_equal(pgp_path->dev, pp_path->dev); /* This test is just a backup in case the * something wenth wrong naming the paths */ assert_ptr_equal(pgp_path, pp_path); } } } static void test_one_group8(void **state) { int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; mp8.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_one_group4(void **state) { int paths[] = {0,1,2,3}; int *groups[] = {paths}; int group_size[] = {4}; mp4.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 1); } static void test_one_group1(void **state) { int paths[] = {0}; int *groups[] = {paths}; int group_size[] = {1}; mp1.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp1, 0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_one_group0(void **state) { mp0.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_one_group_null(void **state) { mp_null.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_one_group_all_marginal8(void **state) { int paths[] = {0,1,2,3,4,5,6,7}; int marginal[] = {1,1,1,1,1,1,1,1}; int *groups[] = {paths}; int group_size[] = {8}; int group_marginal[] = {1}; set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 1); } static void test_one_group_half_marginal8(void **state) { int marginal[] = {1,0,1,0,1,1,0,0}; int group0[] = {1,3,6,7}; int group1[] = {0,2,4,5}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; int group_marginal[] = {0,1}; set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 2); } static void test_one_group_ignore_marginal8(void **state) { int marginal[] = {1,0,1,0,1,1,0,0}; int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_one_group_one_marginal8(void **state) { int marginal[] = {0,0,0,0,0,1,0,0}; int group0[] = {0,1,2,3,4,6,7}; int group1[] = {5}; int *groups[] = {group0, group1}; int group_size[] = {7,1}; int group_marginal[] = {0,1}; set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_group; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 2); } static void test_one_path_per_group_same8(void **state) { int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_one_path_per_group_increasing8(void **state) { int prio[] = {1,2,3,4,5,6,7,8}; int paths[] = {7,6,5,4,3,2,1,0}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_one_path_per_group_decreasing8(void **state) { int prio[] = {8,7,6,5,4,3,2,1}; int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_one_path_per_group_mixed8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int paths[] = {6,0,4,2,3,5,7,1}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_one_path_per_group4(void **state) { int paths[] = {0,1,2,3}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3]}; int group_size[] = {1,1,1,1}; mp4.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 4); } static void test_one_path_per_group1(void **state) { int paths[] = {0}; int *groups[] = {paths}; int group_size[] = {1}; mp1.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp1, 0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_one_path_per_group0(void **state) { mp0.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_one_path_per_group_null(void **state) { mp_null.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_one_path_per_group_mixed_all_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int marginal[] = {1,1,1,1,1,1,1,1}; int paths[] = {6,0,4,2,3,5,7,1}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; int group_marginal[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 8); } static void test_one_path_per_group_mixed_half_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int marginal[] = {0,1,1,0,0,0,1,1}; int paths[] = {0,4,3,5,6,2,7,1}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; int group_marginal[] = {0,0,0,0,1,1,1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = one_path_per_group; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 8); } static void test_group_by_prio_same8(void **state) { int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_group_by_prio_increasing8(void **state) { int prio[] = {1,2,3,4,5,6,7,8}; int paths[] = {7,6,5,4,3,2,1,0}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_group_by_prio_decreasing8(void **state) { int prio[] = {8,7,6,5,4,3,2,1}; int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_group_by_prio_mixed8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {5,7}; int group5[] = {1}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,1,2,2,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 6); } static void test_group_by_prio_mixed_no_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {5,7}; int group5[] = {1}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,1,2,2,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 6); } static void test_group_by_prio_2_groups8(void **state) { int prio[] = {1,2,2,1,2,1,1,2}; int group0[] = {1,2,4,7}; int group1[] = {0,3,5,6}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 2); } static void test_group_by_prio_mixed4(void **state) { int prio[] = {2,3,1,3}; int group0[] = {1,3}; int group1[] = {0}; int group2[] = {2}; int *groups[] = {group0, group1, group2}; int group_size[] = {2,1,1}; set_priority(p4, prio, 4); mp4.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 3); } static void test_group_by_prio_2_groups4(void **state) { int prio[] = {2,1,1,2}; int group0[] = {0,3}; int group1[] = {1,2}; int *groups[] = {group0, group1}; int group_size[] = {2,2}; set_priority(p4, prio, 4); mp4.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 2); } static void test_group_by_prio1(void **state) { int paths[] = {0}; int *groups[] = {paths}; int group_size[] = {1}; mp1.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp1, 0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_group_by_prio0(void **state) { mp0.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_group_by_prio_null(void **state) { mp_null.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_group_by_prio_mixed_all_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int marginal[] = {1,1,1,1,1,1,1,1}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {5,7}; int group5[] = {1}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,1,2,2,1}; int group_marginal[] = {1,1,1,1,1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 6); } static void test_group_by_prio_mixed_half_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int marginal[] = {0,0,0,1,0,1,1,1}; int group0[] = {0}; int group1[] = {4}; int group2[] = {2}; int group3[] = {1}; int group4[] = {6}; int group5[] = {3}; int group6[] = {5,7}; int *groups[] = {group0, group1, group2, group3, group4, group5, group6}; int group_size[] = {1,1,1,1,1,1,2}; int group_marginal[] = {0,0,0,0,1,1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 7); } static void test_group_by_prio_mixed_one_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int marginal[] = {0,0,0,0,0,1,0,0}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {7}; int group5[] = {1}; int group6[] = {5}; int *groups[] = {group0, group1, group2, group3, group4, group5, group6}; int group_size[] = {1,1,1,2,1,1,1}; int group_marginal[] = {0,0,0,0,0,0,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 7); } static void test_group_by_prio_mixed_undef8(void **state) { int prio[] = {7,1,3,-1,5,2,8,2}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2}; int group4[] = {5,7}; int group5[] = {1}; int group6[] = {3}; int *groups[] = {group0, group1, group2, group3, group4, group5, group6}; int group_size[] = {1,1,1,1,2,1,1}; set_priority(p8, prio, 8); mp8.pgpolicyfn = group_by_prio; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 7); } static void test_group_by_tpg_same8(void **state) { int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_group_by_tpg_different8(void **state) { int prio[] = {1,2,3,4,5,6,7,8}; int tpg[] = {3,5,4,1,8,6,7,2}; int paths[] = {7,6,5,4,3,2,1,0}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_group_by_tpg_mixed8(void **state) { int prio[] = {7,2,3,3,5,2,8,2}; int tpg[] = {1,2,3,3,4,2,5,6}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {1,5}; int group5[] = {7}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,1,2,2,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 6); } static void test_group_by_tpg_3_groups8(void **state) { int prio[] = {1,2,2,1,2,1,1,2}; int tpg[] = {1,2,2,1,3,1,1,3}; int group0[] = {1,2}; int group1[] = {4,7}; int group2[] = {0,3,5,6}; int *groups[] = {group0, group1, group2}; int group_size[] = {2,2,4}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 3); } static void test_group_by_tpg_2_groups4(void **state) { int prio[] = {2,1,1,2}; int tpg[] = {1,2,2,1}; int group0[] = {0,3}; int group1[] = {1,2}; int *groups[] = {group0, group1}; int group_size[] = {2,2}; set_priority(p4, prio, 4); set_tpg(p4, tpg, 4); mp4.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 2); } static void test_group_by_tpg1(void **state) { int paths[] = {0}; int *groups[] = {paths}; int group_size[] = {1}; mp1.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp1, 0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_group_by_tpg0(void **state) { mp0.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_group_by_tpg_null(void **state) { mp_null.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_group_by_tpg_mixed_all_marginal8(void **state) { int prio[] = {7,2,3,3,5,2,8,2}; int tpg[] = {1,2,3,3,4,2,5,6}; int marginal[] = {1,1,1,1,1,1,1,1}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {1,5}; int group5[] = {7}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,1,2,2,1}; int group_marginal[] = {1,1,1,1,1,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 6); } static void test_group_by_tpg_mixed_half_marginal8(void **state) { int prio[] = {7,1,3,3,3,2,8,2}; int tpg[] = {1,2,3,4,5,6,7,6}; int marginal[] = {0,0,0,1,0,1,1,1}; int group0[] = {0}; int group1[] = {2}; int group2[] = {4}; int group3[] = {1}; int group4[] = {6}; int group5[] = {3}; int group6[] = {5,7}; int *groups[] = {group0, group1, group2, group3, group4, group5, group6}; int group_size[] = {1,1,1,1,1,1,2}; int group_marginal[] = {0,0,0,0,1,1,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 7); } static void test_group_by_tpg_mixed_one_marginal8(void **state) { int prio[] = {7,1,3,3,5,2,8,2}; int tpg[] = {1,2,3,3,4,5,6,5}; int marginal[] = {0,0,0,0,0,1,0,0}; int group0[] = {6}; int group1[] = {0}; int group2[] = {4}; int group3[] = {2,3}; int group4[] = {7}; int group5[] = {1}; int group6[] = {5}; int *groups[] = {group0, group1, group2, group3, group4, group5, group6}; int group_size[] = {1,1,1,2,1,1,1}; int group_marginal[] = {0,0,0,0,0,0,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 7); } static void test_group_by_tpg_mixed_undef8(void **state) { int prio[] = {-1,2,3,-1,5,2,8,2}; int tpg[] = {1,2,3,3,4,2,5,6}; int group0[] = {6}; int group1[] = {4}; int group2[] = {2,3}; int group3[] = {1,5}; int group4[] = {7}; int group5[] = {0}; int *groups[] = {group0, group1, group2, group3, group4, group5}; int group_size[] = {1,1,2,2,1,1}; set_priority(p8, prio, 8); set_tpg(p8, tpg, 8); mp8.pgpolicyfn = group_by_tpg; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 6); } static void test_group_by_node_name_same8(void **state) { char *node_name[] = {"a","a","a","a","a","a","a","a"}; int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_group_by_node_name_increasing8(void **state) { char *node_name[] = {"a","b","c","d","e","f","g","h"}; int prio[] = {1,2,3,4,5,6,7,8}; int paths[] = {7,6,5,4,3,2,1,0}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_group_by_node_name_3_groups8(void **state) { char *node_name[] = {"a","b","a","c","b","c","c","a"}; int prio[] = {4,1,4,1,1,1,1,4}; int group0[] = {0,2,7}; int group1[] = {3,5,6}; int group2[] = {1,4}; int *groups[] = {group0, group1, group2}; int group_size[] = {3,3,2}; set_priority(p8, prio, 8); set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 3); } static void test_group_by_node_name_2_groups8(void **state) { char *node_name[] = {"a", "a", "b", "a", "b", "b", "b", "a"}; int prio[] = {4,1,2,1,2,2,2,1}; int group0[] = {2,4,5,6}; int group1[] = {0,1,3,7}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; set_priority(p8, prio, 8); set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 2); } static void test_group_by_node_name_3_groups4(void **state) { char *node_name[] = {"a","b","c","a"}; int prio[] = {3,1,3,1}; int group0[] = {2}; int group1[] = {0,3}; int group2[] = {1}; int *groups[] = {group0, group1, group2}; int group_size[] = {1,2,1}; set_priority(p4, prio, 4); set_tgt_node_name(p4, node_name, 4); mp4.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 3); } static void test_group_by_node_name_2_groups4(void **state) { char *node_name[] = {"a","b","b","a"}; int prio[] = {2,1,1,2}; int group0[] = {0,3}; int group1[] = {1,2}; int *groups[] = {group0, group1}; int group_size[] = {2,2}; set_priority(p4, prio, 4); set_tgt_node_name(p4, node_name, 4); mp4.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 2); } static void test_group_by_node_name1(void **state) { char *node_name[] = {"a"}; int paths[] = {0}; int *groups[] = {paths}; int group_size[] = {1}; set_tgt_node_name(p1, node_name, 1); mp1.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp1,0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_group_by_node_name0(void **state) { mp0.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_group_by_node_name_null(void **state) { mp_null.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_group_by_node_name_2_groups_all_marginal8(void **state) { char *node_name[] = {"a", "a", "b", "a", "b", "b", "b", "a"}; int prio[] = {4,1,2,1,2,2,2,1}; int marginal[] = {1,1,1,1,1,1,1,1}; int group0[] = {2,4,5,6}; int group1[] = {0,1,3,7}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; int group_marginal[] = {1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 2); } static void test_group_by_node_name_2_groups_half_marginal8(void **state) { char *node_name[] = {"a", "a", "b", "a", "b", "b", "b", "a"}; int prio[] = {4,1,2,1,2,2,2,1}; int marginal[] = {1,0,1,1,0,1,0,0}; int group0[] = {4,6}; int group1[] = {1,7}; int group2[] = {0,3}; int group3[] = {2,5}; int *groups[] = {group0, group1, group2, group3}; int group_size[] = {2,2,2,2}; int group_marginal[] = {0,0,1,1}; set_priority(p8, prio, 8); set_marginal(p8, marginal, 8); set_tgt_node_name(p8, node_name, 8); mp8.pgpolicyfn = group_by_node_name; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 4); } static void test_group_by_serial_same8(void **state) { char *serial[] = {"1","1","1","1","1","1","1","1"}; int paths[] = {0,1,2,3,4,5,6,7}; int *groups[] = {paths}; int group_size[] = {8}; set_serial(p8, serial, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 1); } static void test_group_by_serial_increasing8(void **state) { char *serial[] = {"1","2","3","4","5","6","7","8"}; int prio[] = {1,2,3,4,5,6,7,8}; int paths[] = {7,6,5,4,3,2,1,0}; int *groups[] = {&paths[0], &paths[1], &paths[2], &paths[3], &paths[4], &paths[5], &paths[6], &paths[7]}; int group_size[] = {1,1,1,1,1,1,1,1}; set_priority(p8, prio, 8); set_serial(p8, serial, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 8); } static void test_group_by_serial_3_groups8(void **state) { char *serial[] = {"1","2","1","3","2","3","2","1"}; int prio[] = {4,1,4,3,1,3,1,4}; int group0[] = {0,2,7}; int group1[] = {3,5}; int group2[] = {1,4,6}; int *groups[] = {group0, group1, group2}; int group_size[] = {3,2,3}; set_priority(p8, prio, 8); set_serial(p8, serial, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 3); } static void test_group_by_serial_2_groups8(void **state) { char *serial[] = {"1", "2", "1", "1", "2", "2", "1", "2"}; int prio[] = {3,2,2,1,2,2,1,2}; int group0[] = {1,4,5,7}; int group1[] = {0,2,3,6}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; set_priority(p8, prio, 8); set_serial(p8, serial, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 0), 0); verify_pathgroups(&mp8, p8, groups, group_size, NULL, 2); } static void test_group_by_serial_3_groups4(void **state) { char *serial[] = {"1","2","3","2"}; int prio[] = {3,1,3,1}; int group0[] = {0}; int group1[] = {2}; int group2[] = {1,3}; int *groups[] = {group0, group1, group2}; int group_size[] = {1,1,2}; set_priority(p4, prio, 4); set_serial(p4, serial, 4); mp4.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 3); } static void test_group_by_serial_2_groups4(void **state) { char *serial[] = {"1","2","1","2"}; int prio[] = {3,1,3,1}; int group0[] = {0,2}; int group1[] = {1,3}; int *groups[] = {group0, group1}; int group_size[] = {2,2}; set_priority(p4, prio, 4); set_serial(p4, serial, 4); mp4.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp4, 0), 0); verify_pathgroups(&mp4, p4, groups, group_size, NULL, 2); } static void test_group_by_serial1(void **state) { char *serial[1] = {"1"}; int paths[1] = {0}; int *groups[1] = {paths}; int group_size[1] = {1}; set_serial(p1, serial, 1); mp1.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp1, 0), 0); verify_pathgroups(&mp1, p1, groups, group_size, NULL, 1); } static void test_group_by_serial0(void **state) { mp0.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp0, 0), 0); verify_pathgroups(&mp0, NULL, NULL, NULL, NULL, 0); } static void test_group_by_serial_null(void **state) { mp_null.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp_null, 0), 0); verify_pathgroups(&mp_null, NULL, NULL, NULL, NULL, 0); } static void test_group_by_serial_2_groups8_all_marginal8(void **state) { char *serial[] = {"1", "2", "1", "1", "2", "2", "1", "2"}; int marginal[] = {1,1,1,1,1,1,1,1}; int prio[] = {3,2,2,1,2,2,1,2}; int group0[] = {1,4,5,7}; int group1[] = {0,2,3,6}; int *groups[] = {group0, group1}; int group_size[] = {4,4}; int group_marginal[] = {1,1}; set_priority(p8, prio, 8); set_serial(p8, serial, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 2); } static void test_group_by_serial_2_groups8_half_marginal8(void **state) { char *serial[] = {"1", "2", "1", "1", "2", "2", "1", "2"}; int marginal[] = {0,0,1,1,1,1,0,0}; int prio[] = {3,2,2,1,2,2,1,2}; int group0[] = {0,6}; int group1[] = {1,7}; int group2[] = {4,5}; int group3[] = {2,3}; int *groups[] = {group0, group1, group2, group3}; int group_size[] = {2,2,2,2}; int group_marginal[] = {0,0,1,1}; set_priority(p8, prio, 8); set_serial(p8, serial, 8); set_marginal(p8, marginal, 8); mp8.pgpolicyfn = group_by_serial; assert_int_equal(group_paths(&mp8, 1), 0); verify_pathgroups(&mp8, p8, groups, group_size, group_marginal, 4); } #define setup_test(name, nr) \ cmocka_unit_test_setup_teardown(name ## nr, setup ## nr, teardown ## nr) int test_pgpolicies(void) { const struct CMUnitTest tests[] = { setup_test(test_one_group, 8), setup_test(test_one_group, 4), setup_test(test_one_group, 1), setup_test(test_one_group, 0), setup_test(test_one_group, _null), setup_test(test_one_group_all_marginal, 8), setup_test(test_one_group_half_marginal, 8), setup_test(test_one_group_ignore_marginal, 8), setup_test(test_one_group_one_marginal, 8), setup_test(test_one_path_per_group_same, 8), setup_test(test_one_path_per_group_increasing, 8), setup_test(test_one_path_per_group_decreasing, 8), setup_test(test_one_path_per_group_mixed, 8), setup_test(test_one_path_per_group, 4), setup_test(test_one_path_per_group, 1), setup_test(test_one_path_per_group, 0), setup_test(test_one_path_per_group, _null), setup_test(test_one_path_per_group_mixed_all_marginal, 8), setup_test(test_one_path_per_group_mixed_half_marginal, 8), setup_test(test_group_by_prio_same, 8), setup_test(test_group_by_prio_increasing, 8), setup_test(test_group_by_prio_decreasing, 8), setup_test(test_group_by_prio_mixed, 8), setup_test(test_group_by_prio_mixed_no_marginal, 8), setup_test(test_group_by_prio_2_groups, 8), setup_test(test_group_by_prio_mixed, 4), setup_test(test_group_by_prio_2_groups, 4), setup_test(test_group_by_prio, 1), setup_test(test_group_by_prio, 0), setup_test(test_group_by_prio, _null), setup_test(test_group_by_prio_mixed_all_marginal, 8), setup_test(test_group_by_prio_mixed_half_marginal, 8), setup_test(test_group_by_prio_mixed_one_marginal, 8), setup_test(test_group_by_prio_mixed_undef, 8), setup_test(test_group_by_tpg_same, 8), setup_test(test_group_by_tpg_different, 8), setup_test(test_group_by_tpg_mixed, 8), setup_test(test_group_by_tpg_3_groups, 8), setup_test(test_group_by_tpg_2_groups, 4), setup_test(test_group_by_tpg, 1), setup_test(test_group_by_tpg, 0), setup_test(test_group_by_tpg, _null), setup_test(test_group_by_tpg_mixed_all_marginal, 8), setup_test(test_group_by_tpg_mixed_half_marginal, 8), setup_test(test_group_by_tpg_mixed_one_marginal, 8), setup_test(test_group_by_tpg_mixed_undef, 8), setup_test(test_group_by_node_name_same, 8), setup_test(test_group_by_node_name_increasing, 8), setup_test(test_group_by_node_name_3_groups, 8), setup_test(test_group_by_node_name_2_groups, 8), setup_test(test_group_by_node_name_3_groups, 4), setup_test(test_group_by_node_name_2_groups, 4), setup_test(test_group_by_node_name, 1), setup_test(test_group_by_node_name, 0), setup_test(test_group_by_node_name, _null), setup_test(test_group_by_node_name_2_groups_all_marginal, 8), setup_test(test_group_by_node_name_2_groups_half_marginal, 8), setup_test(test_group_by_serial_same, 8), setup_test(test_group_by_serial_increasing, 8), setup_test(test_group_by_serial_3_groups, 8), setup_test(test_group_by_serial_2_groups, 8), setup_test(test_group_by_serial_3_groups, 4), setup_test(test_group_by_serial_2_groups, 4), setup_test(test_group_by_serial, 1), setup_test(test_group_by_serial, 0), setup_test(test_group_by_serial, _null), setup_test(test_group_by_serial_2_groups8_all_marginal, 8), setup_test(test_group_by_serial_2_groups8_half_marginal, 8), }; return cmocka_run_group_tests(tests, setup, NULL); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_pgpolicies(); return ret; } multipath-tools-0.11.1/tests/strbuf.c000066400000000000000000000353531475246302400175720ustar00rootroot00000000000000/* * Copyright (c) 2021 SUSE LLC * SPDX-License-Identifier: GPL-2.0-only */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "strbuf.h" #include "debug.h" #include "globals.c" void *__real_realloc(void *ptr, size_t size); static bool mock_realloc = false; void *__wrap_realloc(void *ptr, size_t size) { void *p; if (!mock_realloc) return __real_realloc(ptr, size); p = mock_ptr_type(void *); condlog(4, "%s: %p, %zu -> %p", __func__, ptr, size, p); return p; } static void test_strbuf_00(void **state) { STRBUF_ON_STACK(buf); char *p; assert_ptr_equal(buf.buf, NULL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); p = steal_strbuf_str(&buf); assert_ptr_equal(p, NULL); assert_ptr_equal(buf.buf, NULL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(append_strbuf_str(&buf, "moin"), 4); assert_int_equal(get_strbuf_len(&buf), 4); assert_in_range(buf.size, 5, SIZE_MAX); assert_string_equal(get_strbuf_str(&buf), "moin"); p = steal_strbuf_str(&buf); assert_string_equal(p, "moin"); free(p); assert_ptr_equal(buf.buf, NULL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(append_strbuf_str(&buf, NULL), -EINVAL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(append_strbuf_str(&buf, ""), 0); /* appending a 0-length string allocates memory */ assert_in_range(buf.size, 1, SIZE_MAX); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); p = steal_strbuf_str(&buf); assert_string_equal(p, ""); free(p); assert_int_equal(append_strbuf_str__(&buf, "x", 0), 0); /* appending a 0-length string allocates memory */ assert_in_range(buf.size, 1, SIZE_MAX); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); } static void test_strbuf_alloc_err(void **state) { STRBUF_ON_STACK(buf); size_t sz, ofs; int rc; mock_realloc = true; will_return(__wrap_realloc, NULL); assert_int_equal(append_strbuf_str(&buf, "moin"), -ENOMEM); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); mock_realloc = false; assert_int_equal(append_strbuf_str(&buf, "moin"), 4); sz = buf.size; assert_in_range(sz, 5, SIZE_MAX); assert_int_equal(buf.offs, 4); assert_int_equal(get_strbuf_len(&buf), 4); assert_string_equal(get_strbuf_str(&buf), "moin"); mock_realloc = true; will_return(__wrap_realloc, NULL); ofs = get_strbuf_len(&buf); while ((rc = append_strbuf_str(&buf, " hello")) >= 0) { condlog(3, "%s", get_strbuf_str(&buf)); assert_int_equal(rc, 6); assert_int_equal(get_strbuf_len(&buf), ofs + 6); assert_memory_equal(get_strbuf_str(&buf), "moin", 4); assert_string_equal(get_strbuf_str(&buf) + ofs, " hello"); ofs = get_strbuf_len(&buf); } assert_int_equal(rc, -ENOMEM); assert_int_equal(buf.size, sz); assert_int_equal(get_strbuf_len(&buf), ofs); assert_memory_equal(get_strbuf_str(&buf), "moin", 4); assert_string_equal(get_strbuf_str(&buf) + ofs - 6, " hello"); reset_strbuf(&buf); assert_ptr_equal(buf.buf, NULL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); mock_realloc = false; } static void test_strbuf_overflow(void **state) { STRBUF_ON_STACK(buf); assert_int_equal(append_strbuf_str(&buf, "x"), 1); /* fake huge buffer */ buf.size = SIZE_MAX - 1; buf.offs = buf.size - 1; assert_int_equal(append_strbuf_str(&buf, "x"), -EOVERFLOW); } static void test_strbuf_big(void **state) { STRBUF_ON_STACK(buf); const char big[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n"; char *bbig; int i; /* Under valgrind, 30000 iterations need ca. 30s on my laptop */ for (i = 0; i < 30000; i++) { if (i % 1000 == 0) condlog(4, "%d", i); assert_int_equal(append_strbuf_str(&buf, big), sizeof(big) - 1); assert_int_equal(get_strbuf_len(&buf), (sizeof(big) - 1) * (i + 1)); assert_memory_equal(get_strbuf_str(&buf), big, sizeof(big) - 1); assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - (sizeof(big) - 1), big); }; bbig = steal_strbuf_str(&buf); assert_ptr_equal(buf.buf, NULL); assert_int_equal(buf.size, 0); assert_int_equal(buf.offs, 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(strlen(bbig), i * (sizeof(big) - 1)); assert_memory_equal(bbig, big, sizeof(big) - 1); free(bbig); } static void test_strbuf_nul(void **state) { STRBUF_ON_STACK(buf); char greet[] = "hello, sir!"; assert_int_equal(append_strbuf_str__(&buf, greet, 6), 6); assert_string_equal(get_strbuf_str(&buf), "hello,"); assert_int_equal(append_strbuf_str__(&buf, greet, 6), 6); assert_string_equal(get_strbuf_str(&buf), "hello,hello,"); /* overwrite comma with NUL; append_strbuf_str() stops at NUL byte */ greet[5] = '\0'; reset_strbuf(&buf); assert_int_equal(append_strbuf_str(&buf, greet), 5); assert_int_equal(get_strbuf_len(&buf), 5); assert_string_equal(get_strbuf_str(&buf), "hello"); assert_int_equal(append_strbuf_str(&buf, greet), 5); assert_int_equal(get_strbuf_len(&buf), 10); assert_string_equal(get_strbuf_str(&buf), "hellohello"); /* append_strbuf_str__() appends full memory, including NUL bytes */ reset_strbuf(&buf); assert_int_equal(append_strbuf_str__(&buf, greet, sizeof(greet) - 1), sizeof(greet) - 1); assert_int_equal(get_strbuf_len(&buf), sizeof(greet) - 1); assert_string_equal(get_strbuf_str(&buf), "hello"); assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - 5, " sir!"); assert_int_equal(append_strbuf_str__(&buf, greet, sizeof(greet) - 1), sizeof(greet) - 1); assert_string_equal(get_strbuf_str(&buf), "hello"); assert_int_equal(get_strbuf_len(&buf), 2 * (sizeof(greet) - 1)); assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - 5, " sir!"); } static void test_strbuf_quoted(void **state) { STRBUF_ON_STACK(buf); const char said[] = "She said "; const char greet[] = "hi, man!"; char *p; size_t n; assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1); assert_int_equal(append_strbuf_quoted(&buf, greet), sizeof(greet) + 1); assert_string_equal(get_strbuf_str(&buf), "She said \"hi, man!\""); n = get_strbuf_len(&buf); p = steal_strbuf_str(&buf); assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1); assert_int_equal(append_strbuf_quoted(&buf, p), n + 4); assert_string_equal(get_strbuf_str(&buf), "She said \"She said \"\"hi, man!\"\"\""); free(p); n = get_strbuf_len(&buf); p = steal_strbuf_str(&buf); assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1); assert_int_equal(append_strbuf_quoted(&buf, p), n + 8); assert_string_equal(get_strbuf_str(&buf), "She said \"She said \"\"She said \"\"\"\"hi, man!\"\"\"\"\"\"\""); free(p); } static void test_strbuf_escaped(void **state) { STRBUF_ON_STACK(buf); const char said[] = "She said \"hi, man\""; assert_int_equal(append_strbuf_quoted(&buf, said), sizeof(said) + 3); assert_string_equal(get_strbuf_str(&buf), "\"She said \"\"hi, man\"\"\""); reset_strbuf(&buf); assert_int_equal(append_strbuf_quoted(&buf, "\""), 4); assert_string_equal(get_strbuf_str(&buf), "\"\"\"\""); reset_strbuf(&buf); assert_int_equal(append_strbuf_quoted(&buf, "\"\""), 6); assert_string_equal(get_strbuf_str(&buf), "\"\"\"\"\"\""); reset_strbuf(&buf); assert_int_equal(append_strbuf_quoted(&buf, "\"Hi\""), 8); assert_string_equal(get_strbuf_str(&buf), "\"\"\"Hi\"\"\""); } #define SENTENCE "yields, preceded by itself, falsehood" static void test_print_strbuf(void **state) { STRBUF_ON_STACK(buf); char sentence[] = SENTENCE; assert_int_equal(print_strbuf(&buf, "\"%s\" %s.", sentence, sentence), 2 * (sizeof(sentence) - 1) + 4); assert_string_equal(get_strbuf_str(&buf), "\"" SENTENCE "\" " SENTENCE "."); condlog(3, "%s", get_strbuf_str(&buf)); reset_strbuf(&buf); assert_int_equal(print_strbuf(&buf, "0x%08x", 0xdeadbeef), 10); assert_string_equal(get_strbuf_str(&buf), "0xdeadbeef"); reset_strbuf(&buf); assert_int_equal(print_strbuf(&buf, "%d%% of %d is %0.2f", 5, 100, 0.05), 17); assert_string_equal(get_strbuf_str(&buf), "5% of 100 is 0.05"); } /* length of string is not a divisor of chunk size */ static void test_print_strbuf_2(void **state) { STRBUF_ON_STACK(buf); const char sentence[] = "This sentence has forty-seven (47) characters. "; const char *s; const int repeat = 100; int i; for (i = 0; i < repeat; i++) assert_int_equal(print_strbuf(&buf, "%s", sentence), sizeof(sentence) - 1); s = get_strbuf_str(&buf); condlog(3, "%s", s); assert_int_equal(strlen(s), repeat * (sizeof(sentence) - 1)); for (i = 0; i < repeat; i++) assert_int_equal(strncmp(s + i * (sizeof(sentence) - 1), sentence, sizeof(sentence) - 1), 0); } /* length of string is divisor of chunk size */ static void test_print_strbuf_3(void **state) { STRBUF_ON_STACK(buf); const char sentence[] = "This sentence has 32 characters."; const char *s; const int repeat = 100; int i; for (i = 0; i < repeat; i++) assert_int_equal(print_strbuf(&buf, "%s", sentence), sizeof(sentence) - 1); s = get_strbuf_str(&buf); condlog(3, "%s", s); assert_int_equal(strlen(s), repeat * (sizeof(sentence) - 1)); for (i = 0; i < repeat; i++) assert_int_equal(strncmp(s + i * (sizeof(sentence) - 1), sentence, sizeof(sentence) - 1), 0); } static void test_print_strbuf_4(void **state) { STRBUF_ON_STACK(buf); const char sentence[] = "This sentence has a lot of characters, " "which makes it hopefully longer than the chunk size given by " "the constant \"BUF_CHUNK\" in libmpathutil/strbuf.c. "; const char *s; const int repeat = 100; int i; for (i = 0; i < repeat; i++) assert_int_equal(print_strbuf(&buf, "%s", sentence), sizeof(sentence) - 1); s = get_strbuf_str(&buf); condlog(3, "%s", s); assert_int_equal(strlen(s), repeat * (sizeof(sentence) - 1)); for (i = 0; i < repeat; i++) assert_int_equal(strncmp(s + i * (sizeof(sentence) - 1), sentence, sizeof(sentence) - 1), 0); } static void test_truncate_strbuf(void **state) { STRBUF_ON_STACK(buf); const char str[] = "hello my dear!\n"; size_t sz, sz1; assert_int_equal(truncate_strbuf(&buf, 1), -EFAULT); assert_int_equal(truncate_strbuf(&buf, 0), -EFAULT); assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1); assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1); assert_string_equal(get_strbuf_str(&buf), str); assert_int_equal(truncate_strbuf(&buf, sizeof(str)), -ERANGE); assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1); assert_string_equal(get_strbuf_str(&buf), str); assert_int_equal(truncate_strbuf(&buf, sizeof(str) - 1), 0); assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1); assert_string_equal(get_strbuf_str(&buf), str); assert_int_equal(truncate_strbuf(&buf, sizeof(str) - 2), 0); assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 2); assert_string_not_equal(get_strbuf_str(&buf), str); assert_memory_equal(get_strbuf_str(&buf), str, sizeof(str) - 2); assert_int_equal(truncate_strbuf(&buf, 5), 0); assert_int_equal(get_strbuf_len(&buf), 5); assert_string_not_equal(get_strbuf_str(&buf), str); assert_string_equal(get_strbuf_str(&buf), "hello"); reset_strbuf(&buf); assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1); sz = buf.size; while (buf.size == sz) assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1); sz1 = buf.size; assert_in_range(get_strbuf_len(&buf), sz + 1, SIZE_MAX); assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - (sizeof(str) - 1), str); assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf) + 1), -ERANGE); assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf)), 0); assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf) - (sizeof(str) - 1)), 0); assert_in_range(get_strbuf_len(&buf), 1, sz); assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - (sizeof(str) - 1), str); assert_int_equal(buf.size, sz1); assert_int_equal(truncate_strbuf(&buf, 5), 0); assert_int_equal(get_strbuf_len(&buf), 5); assert_string_equal(get_strbuf_str(&buf), "hello"); assert_int_equal(buf.size, sz1); assert_int_equal(truncate_strbuf(&buf, 0), 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(buf.size, sz1); } static void test_fill_strbuf(void **state) { STRBUF_ON_STACK(buf); int i; char *p; assert_int_equal(fill_strbuf(&buf, '+', -5), -EINVAL); assert_int_equal(fill_strbuf(&buf, '+', 0), 0); assert_int_equal(get_strbuf_len(&buf), 0); assert_string_equal(get_strbuf_str(&buf), ""); assert_int_equal(fill_strbuf(&buf, '+', 1), 1); assert_int_equal(get_strbuf_len(&buf), 1); assert_string_equal(get_strbuf_str(&buf), "+"); assert_int_equal(fill_strbuf(&buf, '-', 3), 3); assert_int_equal(get_strbuf_len(&buf), 4); assert_string_equal(get_strbuf_str(&buf), "+---"); assert_int_equal(fill_strbuf(&buf, '\0', 3), 3); assert_int_equal(get_strbuf_len(&buf), 7); assert_string_equal(get_strbuf_str(&buf), "+---"); truncate_strbuf(&buf, 4); assert_int_equal(fill_strbuf(&buf, '+', 4), 4); assert_int_equal(get_strbuf_len(&buf), 8); assert_string_equal(get_strbuf_str(&buf), "+---++++"); reset_strbuf(&buf); assert_int_equal(fill_strbuf(&buf, 'x', 30000), 30000); assert_int_equal(get_strbuf_len(&buf), 30000); p = steal_strbuf_str(&buf); assert_int_equal(strlen(p), 30000); for (i = 0; i < 30000; i++) assert_int_equal(p[i], 'x'); free(p); } static int test_strbuf(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strbuf_00), cmocka_unit_test(test_strbuf_alloc_err), cmocka_unit_test(test_strbuf_overflow), cmocka_unit_test(test_strbuf_big), cmocka_unit_test(test_strbuf_nul), cmocka_unit_test(test_strbuf_quoted), cmocka_unit_test(test_strbuf_escaped), cmocka_unit_test(test_print_strbuf), cmocka_unit_test(test_print_strbuf_2), cmocka_unit_test(test_print_strbuf_3), cmocka_unit_test(test_print_strbuf_4), cmocka_unit_test(test_truncate_strbuf), cmocka_unit_test(test_fill_strbuf), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_strbuf(); return ret; } multipath-tools-0.11.1/tests/sysfs.c000066400000000000000000000326711475246302400174340ustar00rootroot00000000000000/* * Copyright (c) 2021 SUSE LLC * SPDX-License-Identifier: GPL-2.0-only */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "debug.h" #include "globals.c" #include "test-log.h" #include "sysfs.h" #include "util.h" #include "wrap64.h" #define TEST_FD 123 char *__wrap_udev_device_get_syspath(struct udev_device *ud) { char *val = mock_ptr_type(char *); return val; } int WRAP_OPEN(const char *pathname, int flags) { int ret; check_expected(pathname); check_expected(flags); ret = mock_type(int); return ret; } ssize_t __wrap_read(int fd, void *buf, size_t count) { ssize_t ret; char *val; check_expected(fd); check_expected(count); ret = mock_type(int); val = mock_ptr_type(char *); if (ret >= (ssize_t)count) ret = count; if (ret >= 0 && val) { fprintf(stderr, "%s: '%s' -> %zd\n", __func__, val, ret); memcpy(buf, val, ret); } return ret; } ssize_t __wrap_write(int fd, void *buf, size_t count) { ssize_t ret; check_expected(fd); check_expected(count); ret = mock_type(int); if (ret >= (ssize_t)count) ret = count; return ret; } int __real_close(int fd); int __wrap_close(int fd) { if (fd != TEST_FD) return __real_close(fd); return mock_type(int); } static int setup(void **state) { udev = udev_new(); return 0; } static int teardown(void **state) { udev_unref(udev); return 0; } static void expect_sagv_invalid(void) { expect_condlog(1, "sysfs_attr_get_value__: invalid parameters"); } static void test_sagv_invalid(void **state) { expect_sagv_invalid(); assert_int_equal(sysfs_attr_get_value(NULL, NULL, NULL, 0), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_bin_attr_get_value(NULL, NULL, NULL, 0), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_attr_get_value(NULL, (void *)state, (void *)state, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_bin_attr_get_value(NULL, (void *)state, (void *)state, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_attr_get_value((void *)state, NULL, (void *)state, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_bin_attr_get_value((void *)state, NULL, (void *)state, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_attr_get_value((void *)state, (void *)state, NULL, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_bin_attr_get_value((void *)state, (void *)state, NULL, 1), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_attr_get_value((void *)state, (void *)state, (void *)state, 0), -EINVAL); expect_sagv_invalid(); assert_int_equal(sysfs_bin_attr_get_value((void *)state, (void *)state, (void *)state, 0), -EINVAL); } static void test_sagv_bad_udev(void **state) { will_return(__wrap_udev_device_get_syspath, NULL); expect_condlog(3, "sysfs_attr_get_value__: invalid udevice"); assert_int_equal(sysfs_attr_get_value((void *)state, (void *)state, (void *)state, 1), -EINVAL); will_return(__wrap_udev_device_get_syspath, NULL); expect_condlog(3, "sysfs_attr_get_value__: invalid udevice"); assert_int_equal(sysfs_bin_attr_get_value((void *)state, (void *)state, (void *)state, 1), -EINVAL); } static void test_sagv_bad_snprintf(void **state) { char longstr[PATH_MAX + 1]; char buf[1]; memset(longstr, 'a', sizeof(longstr) - 1); longstr[sizeof(longstr) - 1] = '\0'; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(3, "sysfs_attr_get_value__: devpath overflow"); assert_int_equal(sysfs_attr_get_value((void *)state, longstr, buf, sizeof(buf)), -EOVERFLOW); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(3, "sysfs_attr_get_value__: devpath overflow"); assert_int_equal(sysfs_bin_attr_get_value((void *)state, longstr, (unsigned char *)buf, sizeof(buf)), -EOVERFLOW); } static void test_sagv_open_fail(void **state) { char buf[1]; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_RDONLY); errno = ENOENT; wrap_will_return(WRAP_OPEN, -1); expect_condlog(3, "sysfs_attr_get_value__: attribute '/foo/bar' cannot be opened"); assert_int_equal(sysfs_attr_get_value((void *)state, "bar", buf, sizeof(buf)), -ENOENT); } static void test_sagv_read_fail(void **state) { char buf[1]; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_RDONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_read, fd, TEST_FD); expect_value(__wrap_read, count, sizeof(buf)); errno = EISDIR; will_return(__wrap_read, -1); will_return(__wrap_read, NULL); expect_condlog(3, "sysfs_attr_get_value__: read from /foo/bar failed:"); will_return(__wrap_close, 0); assert_int_equal(sysfs_attr_get_value((void *)state, "bar", buf, sizeof(buf)), -EISDIR); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/baz'"); expect_string(WRAP_OPEN, pathname, "/foo/baz"); expect_value(WRAP_OPEN, flags, O_RDONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_read, fd, TEST_FD); expect_value(__wrap_read, count, sizeof(buf)); errno = EPERM; will_return(__wrap_read, -1); will_return(__wrap_read, NULL); expect_condlog(3, "sysfs_attr_get_value__: read from /foo/baz failed:"); will_return(__wrap_close, 0); assert_int_equal(sysfs_bin_attr_get_value((void *)state, "baz", (unsigned char *)buf, sizeof(buf)), -EPERM); } static void _test_sagv_read(void **state, unsigned int bufsz) { char buf[16]; char input[] = "01234567"; unsigned int n, trunc; assert_in_range(bufsz, 1, sizeof(buf)); memset(buf, '.', sizeof(buf)); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_RDONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_read, fd, TEST_FD); expect_value(__wrap_read, count, bufsz); will_return(__wrap_read, sizeof(input) - 1); will_return(__wrap_read, input); /* If the buffer is too small, input will be truncated by a 0 byte */ if (bufsz <= sizeof(input) - 1) { n = bufsz; trunc = 1; expect_condlog(3, "sysfs_attr_get_value__: overflow reading from /foo/bar"); } else { n = sizeof(input) - 1; trunc = 0; } will_return(__wrap_close, 0); assert_int_equal(sysfs_attr_get_value((void *)state, "bar", buf, bufsz), n); assert_memory_equal(buf, input, n - trunc); assert_int_equal(buf[n - trunc], '\0'); /* Binary input is not truncated */ memset(buf, '.', sizeof(buf)); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/baz'"); expect_string(WRAP_OPEN, pathname, "/foo/baz"); expect_value(WRAP_OPEN, flags, O_RDONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_read, fd, TEST_FD); expect_value(__wrap_read, count, bufsz); will_return(__wrap_read, sizeof(input) - 1); will_return(__wrap_read, input); will_return(__wrap_close, 0); n = bufsz < sizeof(input) - 1 ? bufsz : sizeof(input) - 1; assert_int_equal(sysfs_bin_attr_get_value((void *)state, "baz", (unsigned char *)buf, bufsz), n); assert_memory_equal(buf, input, n); } static void test_sagv_read_overflow_8(void **state) { _test_sagv_read(state, 8); } static void test_sagv_read_overflow_4(void **state) { _test_sagv_read(state, 4); } static void test_sagv_read_overflow_1(void **state) { _test_sagv_read(state, 1); } static void test_sagv_read_good_9(void **state) { _test_sagv_read(state, 9); } static void test_sagv_read_good_15(void **state) { _test_sagv_read(state, 15); } static void _test_sagv_read_zeroes(void **state, unsigned int bufsz) { char buf[16]; char input[] = { '\0','\0','\0','\0','\0','\0','\0','\0' }; unsigned int n; assert_in_range(bufsz, 1, sizeof(buf)); memset(buf, '.', sizeof(buf)); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_RDONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_read, fd, TEST_FD); expect_value(__wrap_read, count, bufsz); will_return(__wrap_read, sizeof(input) - 1); will_return(__wrap_read, input); if (bufsz <= sizeof(input) - 1) { n = bufsz; expect_condlog(3, "sysfs_attr_get_value__: overflow reading from /foo/bar"); } else n = 0; will_return(__wrap_close, 0); assert_int_equal(sysfs_attr_get_value((void *)state, "bar", buf, bufsz), n); /* * The return value of sysfs_attr_get_value ignores zero bytes, * but the read data should have been copied to the buffer */ assert_memory_equal(buf, input, n == 0 ? bufsz : n); } static void test_sagv_read_zeroes_4(void **state) { _test_sagv_read_zeroes(state, 4); } static void expect_sasv_invalid(void) { expect_condlog(1, "sysfs_attr_set_value: invalid parameters"); } static void test_sasv_invalid(void **state) { expect_sasv_invalid(); assert_int_equal(sysfs_attr_set_value(NULL, NULL, NULL, 0), -EINVAL); expect_sasv_invalid(); assert_int_equal(sysfs_attr_set_value(NULL, (void *)state, (void *)state, 1), -EINVAL); expect_sasv_invalid(); assert_int_equal(sysfs_attr_set_value((void *)state, NULL, (void *)state, 1), -EINVAL); expect_sasv_invalid(); assert_int_equal(sysfs_attr_set_value((void *)state, (void *)state, NULL, 1), -EINVAL); expect_sasv_invalid(); assert_int_equal(sysfs_attr_set_value((void *)state, (void *)state, (void *)state, 0), -EINVAL); } static void test_sasv_bad_udev(void **state) { will_return(__wrap_udev_device_get_syspath, NULL); expect_condlog(3, "sysfs_attr_set_value: invalid udevice"); assert_int_equal(sysfs_attr_set_value((void *)state, (void *)state, (void *)state, 1), -EINVAL); } static void test_sasv_bad_snprintf(void **state) { char longstr[PATH_MAX + 1]; char buf[1]; memset(longstr, 'a', sizeof(longstr) - 1); longstr[sizeof(longstr) - 1] = '\0'; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(3, "sysfs_attr_set_value: devpath overflow"); assert_int_equal(sysfs_attr_set_value((void *)state, longstr, buf, sizeof(buf)), -EOVERFLOW); } static void test_sasv_open_fail(void **state) { char buf[1]; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_WRONLY); errno = EPERM; wrap_will_return(WRAP_OPEN, -1); expect_condlog(3, "sysfs_attr_set_value: attribute '/foo/bar' cannot be opened"); assert_int_equal(sysfs_attr_set_value((void *)state, "bar", buf, sizeof(buf)), -EPERM); } static void test_sasv_write_fail(void **state) { char buf[1]; will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_WRONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_write, fd, TEST_FD); expect_value(__wrap_write, count, sizeof(buf)); errno = EISDIR; will_return(__wrap_write, -1); expect_condlog(3, "sysfs_attr_set_value: write to /foo/bar failed:"); will_return(__wrap_close, 0); assert_int_equal(sysfs_attr_set_value((void *)state, "bar", buf, sizeof(buf)), -EISDIR); } static void _test_sasv_write(void **state, unsigned int n_written) { char buf[8]; assert_in_range(n_written, 0, sizeof(buf)); will_return(__wrap_udev_device_get_syspath, "/foo"); expect_condlog(4, "open '/foo/bar'"); expect_string(WRAP_OPEN, pathname, "/foo/bar"); expect_value(WRAP_OPEN, flags, O_WRONLY); wrap_will_return(WRAP_OPEN, TEST_FD); expect_value(__wrap_write, fd, TEST_FD); expect_value(__wrap_write, count, sizeof(buf)); will_return(__wrap_write, n_written); if (n_written < sizeof(buf)) expect_condlog(3, "sysfs_attr_set_value: underflow writing"); will_return(__wrap_close, 0); assert_int_equal(sysfs_attr_set_value((void *)state, "bar", buf, sizeof(buf)), n_written); } static void test_sasv_write_0(void **state) { _test_sasv_write(state, 0); } static void test_sasv_write_4(void **state) { _test_sasv_write(state, 4); } static void test_sasv_write_7(void **state) { _test_sasv_write(state, 7); } static void test_sasv_write_8(void **state) { _test_sasv_write(state, 8); } static int test_sysfs(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_sagv_invalid), cmocka_unit_test(test_sagv_bad_udev), cmocka_unit_test(test_sagv_bad_snprintf), cmocka_unit_test(test_sagv_open_fail), cmocka_unit_test(test_sagv_read_fail), cmocka_unit_test(test_sagv_read_overflow_1), cmocka_unit_test(test_sagv_read_overflow_4), cmocka_unit_test(test_sagv_read_overflow_8), cmocka_unit_test(test_sagv_read_good_9), cmocka_unit_test(test_sagv_read_good_15), cmocka_unit_test(test_sagv_read_zeroes_4), cmocka_unit_test(test_sasv_invalid), cmocka_unit_test(test_sasv_bad_udev), cmocka_unit_test(test_sasv_bad_snprintf), cmocka_unit_test(test_sasv_open_fail), cmocka_unit_test(test_sasv_write_fail), cmocka_unit_test(test_sasv_write_0), cmocka_unit_test(test_sasv_write_4), cmocka_unit_test(test_sasv_write_7), cmocka_unit_test(test_sasv_write_8), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(5); ret += test_sysfs(); return ret; } multipath-tools-0.11.1/tests/test-lib.c000066400000000000000000000253471475246302400200120ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "debug.h" #include "util.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "config.h" #include "discovery.h" #include "propsel.h" #include "unaligned.h" #include "test-lib.h" #include "wrap64.h" const int default_mask = (DI_SYSFS|DI_BLACKLIST|DI_WWID|DI_CHECKER|DI_PRIO|DI_SERIAL); const char default_devnode[] = "sdxTEST"; const char default_wwid[] = "TEST-WWID"; /* default_wwid should be a substring of default_wwid_1! */ const char default_wwid_1[] = "TEST-WWID-1"; /* * Helper wrappers for mock_path(). * * We need to make pathinfo() think it has detected a device with * certain vendor/product/rev. This requires faking lots of udev * and sysfs function responses. * * This requires hwtable-test_OBJDEPS = ../libmultipath/discovery.o * in the Makefile in order to wrap calls from discovery.o. * * Note that functions that are called and defined in discovery.o can't * be wrapped this way (e.g. sysfs_get_vendor), because symbols are * resolved by the assembler before the linking stage. */ int REAL_OPEN(const char *path, int flags, int mode); static const char _mocked_filename[] = "mocked_path"; int WRAP_OPEN(const char *path, int flags, int mode) { condlog(4, "%s: %s", __func__, path); if (!strcmp(path, _mocked_filename)) return 111; return REAL_OPEN(path, flags, mode); } int __wrap_libmp_get_version(int which, unsigned int version[3]) { unsigned int *vers = mock_ptr_type(unsigned int *); condlog(4, "%s: %d", __func__, which); memcpy(version, vers, 3 * sizeof(unsigned int)); return 0; } struct udev_list_entry *__wrap_udev_device_get_properties_list_entry(struct udev_device *ud) { void *p = (void*)0x12345678; condlog(5, "%s: %p", __func__, p); return p; } struct udev_list_entry *__wrap_udev_list_entry_get_next(struct udev_list_entry *udle) { void *p = NULL; condlog(5, "%s: %p", __func__, p); return p; } const char *__wrap_udev_list_entry_get_name(struct udev_list_entry *udle) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s", __func__, val); return val; } struct udev_device *__wrap_udev_device_ref(struct udev_device *ud) { return ud; } struct udev_device *__wrap_udev_device_unref(struct udev_device *ud) { return ud; } char *__wrap_udev_device_get_subsystem(struct udev_device *ud) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s", __func__, val); return val; } char *__wrap_udev_device_get_sysname(struct udev_device *ud) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s", __func__, val); return val; } char *__wrap_udev_device_get_devnode(struct udev_device *ud) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s", __func__, val); return val; } dev_t __wrap_udev_device_get_devnum(struct udev_device *ud) { condlog(5, "%s: %p", __func__, ud); return makedev(17, 17); } char *__wrap_udev_device_get_sysattr_value(struct udev_device *ud, const char *attr) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s->%s", __func__, attr, val); return val; } char *__wrap_udev_device_get_property_value(struct udev_device *ud, const char *attr) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s->%s", __func__, attr, val); return val; } int __wrap_sysfs_get_size(struct path *pp, unsigned long long *sz) { *sz = 12345678UL; return 0; } int __wrap_sysfs_attr_set_value(struct udev_device *dev, const char *attr_name, const char * value, size_t value_len) { condlog(5, "%s: %s", __func__, value); return value_len; } void *__wrap_udev_device_get_parent_with_subsystem_devtype( struct udev_device *ud, const char *subsys, char *type) { /* return non-NULL for sysfs_get_tgt_nodename */ return type; } void *__wrap_udev_device_get_parent(struct udev_device *ud) { char *val = mock_ptr_type(void *); condlog(5, "%s: %p", __func__, val); return val; } ssize_t __wrap_sysfs_attr_get_value(struct udev_device *dev, const char *attr_name, char *value, size_t sz) { char *val = mock_ptr_type(char *); condlog(5, "%s: %s", __func__, val); strlcpy(value, val, sz); return strlen(value); } /* mock vpd_pg80 */ ssize_t __wrap_sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name, char *buf, size_t sz) { static const char serial[] = "mptest_serial"; assert_string_equal(attr_name, "vpd_pg80"); assert_in_range(sz, sizeof(serial) + 3, INT_MAX); memset(buf, 0, sizeof(serial) + 3); buf[1] = 0x80; put_unaligned_be16(sizeof(serial) - 1, &buf[2]); memcpy(&buf[4], serial, sizeof(serial) - 1); return sizeof(serial) + 3; } int __wrap_checker_check(struct checker *c, int st) { condlog(5, "%s: %d", __func__, st); return st; } int __wrap_prio_getprio(struct prio *p, struct path *pp, unsigned int tmo) { int pr = 5; condlog(5, "%s: %d", __func__, pr); return pr; } int __real_ioctl(int fd, unsigned long request, void *param); int __wrap_ioctl(int fd, unsigned long request, void *param) { condlog(5, "%s: %lu", __func__, request); if (request == HDIO_GETGEO) { static const struct hd_geometry geom = { .heads = 4, .sectors = 31, .cylinders = 64, .start = 0 }; memcpy(param, &geom, sizeof(geom)); return 0; } else if (request == SG_IO) { /* mock hp3par special VPD */ struct sg_io_hdr *hdr = param; static const char vpd_data[] = "VPD DATA"; unsigned char *buf = hdr->dxferp; /* see vpd_vendor_pages in discovery.c */ const int HP3PAR_VPD = 0xc0; if (hdr->interface_id == 'S' && hdr->cmdp[0] == 0x12 && (hdr->cmdp[1] & 1) == 1 && hdr->cmdp[2] == HP3PAR_VPD) { assert_in_range(hdr->dxfer_len, sizeof(vpd_data) + 3, INT_MAX); memset(buf, 0, hdr->dxfer_len); buf[1] = HP3PAR_VPD; put_unaligned_be16(sizeof(vpd_data), &buf[2]); memcpy(&buf[4], vpd_data, sizeof(vpd_data)); hdr->status = 0; return 0; } } return __real_ioctl(fd, request, param); } struct mocked_path *fill_mocked_path(struct mocked_path *mp, const char *vendor, const char *product, const char *rev, const char *wwid, const char *devnode, unsigned int flags) { mp->vendor = (vendor ? vendor : "noname"); mp->product = (product ? product : "noprod"); mp->rev = (rev ? rev : "0"); mp->wwid = (wwid ? wwid : default_wwid); mp->devnode = (devnode ? devnode : default_devnode); mp->flags = flags|NEED_SELECT_PRIO|NEED_FD; return mp; } struct mocked_path *mocked_path_from_path(struct mocked_path *mp, const struct path *pp) { mp->vendor = pp->vendor_id; mp->product = pp->product_id; mp->rev = pp->rev; mp->wwid = pp->wwid; mp->devnode = pp->dev; mp->flags = (prio_selected(&pp->prio) ? 0 : NEED_SELECT_PRIO) | (pp->fd < 0 ? NEED_FD : 0) | (pp->vpd_vendor_id != 0 ? USE_VPD_VND : 0); return mp; } static const char hbtl[] = "4:0:3:1"; static void mock_sysfs_pathinfo(const struct mocked_path *mp) { will_return(__wrap_udev_device_get_subsystem, "scsi"); will_return(__wrap_udev_device_get_sysname, hbtl); will_return(__wrap_udev_device_get_sysname, hbtl); will_return(__wrap_udev_device_get_sysattr_value, mp->vendor); will_return(__wrap_udev_device_get_sysname, hbtl); will_return(__wrap_udev_device_get_sysattr_value, mp->product); will_return(__wrap_udev_device_get_sysname, hbtl); will_return(__wrap_udev_device_get_sysattr_value, mp->rev); /* sysfs_get_tgt_nodename */ will_return(__wrap_udev_device_get_sysattr_value, NULL); will_return(__wrap_udev_device_get_parent, NULL); will_return(__wrap_udev_device_get_parent, NULL); will_return(__wrap_udev_device_get_sysname, "nofibre"); will_return(__wrap_udev_device_get_sysname, "noiscsi"); will_return(__wrap_udev_device_get_parent, NULL); will_return(__wrap_udev_device_get_sysname, "ata25"); } /* * Pretend we detected a SCSI device with given vendor/prod/rev */ void mock_pathinfo(int mask, const struct mocked_path *mp) { if (mp->flags & DEV_HIDDEN) { will_return(__wrap_udev_device_get_sysattr_value, "1"); return; } else will_return(__wrap_udev_device_get_sysattr_value, "0"); if (mask & DI_SYSFS) mock_sysfs_pathinfo(mp); if (mask & DI_BLACKLIST) { will_return(__wrap_udev_device_get_sysname, mp->devnode); if (mp->flags & BL_BY_PROPERTY) { will_return(__wrap_udev_list_entry_get_name, "BAZ"); return; } else will_return(__wrap_udev_list_entry_get_name, "SCSI_IDENT_LUN_NAA_EXT"); } if (mp->flags & BL_BY_DEVICE && (mask & DI_BLACKLIST && mask & DI_SYSFS)) return; /* path_offline */ will_return(__wrap_udev_device_get_subsystem, "scsi"); will_return(__wrap_sysfs_attr_get_value, "running"); if (mask & DI_NOIO) return; /* fake open() in pathinfo() */ if (mp->flags & NEED_FD) will_return(__wrap_udev_device_get_devnode, _mocked_filename); /* scsi_ioctl_pathinfo() */ if (mask & DI_SERIAL) { will_return(__wrap_udev_device_get_subsystem, "scsi"); will_return(__wrap_udev_device_get_sysname, hbtl); } if (mask & DI_WWID) { /* get_udev_uid() */ will_return(__wrap_udev_device_get_property_value, mp->wwid); } if (mask & DI_PRIO && mp->flags & NEED_SELECT_PRIO) { /* sysfs_get_timeout, again (!?) */ will_return(__wrap_udev_device_get_subsystem, "scsi"); will_return(__wrap_udev_device_get_sysattr_value, "180"); } } void mock_store_pathinfo(int mask, const struct mocked_path *mp) { will_return(__wrap_udev_device_get_sysname, mp->devnode); mock_pathinfo(mask, mp); } struct path *mock_path__(vector pathvec, const char *vnd, const char *prd, const char *rev, const char *wwid, const char *dev, unsigned int flags, int mask) { struct mocked_path mop; struct path *pp; struct config *conf; int r; fill_mocked_path(&mop, vnd, prd, rev, wwid, dev, flags); mock_store_pathinfo(mask, &mop); conf = get_multipath_config(); r = store_pathinfo(pathvec, conf, (void *)&mop, mask, &pp); put_multipath_config(conf); if (flags & BL_MASK) { assert_int_equal(r, PATHINFO_SKIPPED); return NULL; } assert_int_equal(r, PATHINFO_OK); assert_non_null(pp); return pp; } 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); /* pathinfo() call in adopt_paths */ mock_pathinfo(DI_CHECKER|DI_PRIO, &mop); mp = add_map_with_path(vecs, pp, 1, NULL); assert_ptr_not_equal(mp, NULL); /* TBD: mock setup_map() ... */ conf = get_multipath_config(); select_pgpolicy(conf, mp); 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); return mp; } multipath-tools-0.11.1/tests/test-lib.h000066400000000000000000000037451475246302400200150ustar00rootroot00000000000000#ifndef TEST_LIB_H_INCLUDED #define TEST_LIB_H_INCLUDED extern const int default_mask; extern const char default_devnode[]; extern const char default_wwid[]; extern const char default_wwid_1[]; enum { BL_BY_DEVNODE = (1 << 0), BL_BY_DEVICE = (1 << 1), BL_BY_WWID = (1 << 2), BL_BY_PROPERTY = (1 << 3), BL_MASK = BL_BY_DEVNODE|BL_BY_DEVICE|BL_BY_WWID|BL_BY_PROPERTY, NEED_SELECT_PRIO = (1 << 8), NEED_FD = (1 << 9), USE_VPD_VND = (1 << 10), DEV_HIDDEN = (1 << 11), }; struct mocked_path { const char *vendor; const char *product; const char *rev; const char *wwid; const char *devnode; unsigned int flags; }; struct mocked_path *fill_mocked_path(struct mocked_path *mp, const char *vendor, const char *product, const char *rev, const char *wwid, const char *devnode, unsigned int flags); struct mocked_path *mocked_path_from_path(struct mocked_path *mp, const struct path *pp); void mock_pathinfo(int mask, const struct mocked_path *mp); void mock_store_pathinfo(int mask, const struct mocked_path *mp); struct path *mock_path__(vector pathvec, const char *vnd, const char *prd, const char *rev, const char *wwid, const char *dev, unsigned int flags, int mask); #define mock_path(v, p) \ mock_path__(hwt->vecs->pathvec, (v), (p), "0", NULL, NULL, \ 0, default_mask) #define mock_path_flags(v, p, f) \ mock_path__(hwt->vecs->pathvec, (v), (p), "0", NULL, NULL, \ (f), default_mask) #define mock_path_blacklisted(v, p) \ mock_path__(hwt->vecs->pathvec, (v), (p), "0", NULL, NULL, \ BL_BY_DEVICE, default_mask) #define mock_path_wwid(v, p, w) \ mock_path__(hwt->vecs->pathvec, (v), (p), "0", (w), NULL, \ 0, default_mask) #define mock_path_wwid_flags(v, p, w, f) \ mock_path__(hwt->vecs->pathvec, (v), (p), "0", (w), \ NULL, (f), default_mask) struct multipath *mock_multipath__(struct vectors *vecs, struct path *pp); #define mock_multipath(pp) mock_multipath__(hwt->vecs, (pp)) #endif multipath-tools-0.11.1/tests/test-log.c000066400000000000000000000015401475246302400200120ustar00rootroot00000000000000#include #include #include #include #include #include #include "log.h" #include "test-log.h" #include "debug.h" __attribute__((format(printf, 2, 0))) void __wrap_dlog (int prio, const char * fmt, ...) { char buff[MAX_MSG_SIZE]; va_list ap; char *expected; va_start(ap, fmt); vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); va_end(ap); fprintf(stderr, "%s(%d): %s", __func__, prio, buff); expected = mock_ptr_type(char *); if (memcmp(expected, buff, strlen(expected))) fprintf(stderr, "%s(expected): %s", __func__, expected); check_expected(prio); assert_memory_equal(buff, expected, strlen(expected)); } void expect_condlog(int prio, char *string) { if (prio > MAX_VERBOSITY || prio > libmp_verbosity) return; expect_value(__wrap_dlog, prio, prio); will_return(__wrap_dlog, string); } multipath-tools-0.11.1/tests/test-log.h000066400000000000000000000003101475246302400200110ustar00rootroot00000000000000#ifndef TEST_LOG_H_INCLUDED #define TEST_LOG_H_INCLUDED __attribute__((format(printf, 2, 0))) void __wrap_dlog (int prio, const char * fmt, ...); void expect_condlog(int prio, char *string); #endif multipath-tools-0.11.1/tests/uevent.c000066400000000000000000000161671475246302400175750ustar00rootroot00000000000000/* * Copyright (c) 2018 SUSE Linux GmbH * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include "list.h" #include "uevent.h" #include "globals.c" /* Stringify helpers */ #define _str_(x) #x #define str(x) _str_(x) #define MAJOR 17 #define MINOR 217 #define DISK_RO 0 #define DM_NAME "spam" #define WWID "foo" static int setup_uev(void **state) { static char test_uid_attrs[] = "dasd:ID_SPAM sd:ID_BOGUS nvme:ID_EGGS "; struct uevent *uev = alloc_uevent(); struct config *conf; if (uev == NULL) return -1; *state = uev; uev->kernel = "sdo"; uev->envp[0] = "MAJOR=" str(MAJOR); uev->envp[1] = "ID_SPAM=nonsense"; uev->envp[1] = "ID_BOGUS=" WWID; uev->envp[2] = "MINOR=" str(MINOR); uev->envp[3] = "DM_NAME=" DM_NAME; uev->envp[4] = "DISK_RO=" str(DISK_RO); uev->envp[5] = NULL; conf = get_multipath_config(); parse_uid_attrs(test_uid_attrs, conf); put_multipath_config(conf); return 0; } static int teardown(void **state) { free(*state); return 0; } static void test_major_good(void **state) { struct uevent *uev = *state; assert_int_equal(uevent_get_major(uev), MAJOR); } static void test_minor_good(void **state) { struct uevent *uev = *state; assert_int_equal(uevent_get_minor(uev), MINOR); } static void test_ro_good(void **state) { struct uevent *uev = *state; assert_int_equal(uevent_get_disk_ro(uev), DISK_RO); } static void test_uid_attrs(void **state) { /* see test_uid_attrs above */ struct config *conf = get_multipath_config(); vector attrs = &conf->uid_attrs; assert_int_equal(VECTOR_SIZE(attrs), 3); assert_null(get_uid_attribute_by_attrs(conf, "hda")); assert_string_equal("ID_BOGUS", get_uid_attribute_by_attrs(conf, "sdaw")); assert_string_equal("ID_SPAM", get_uid_attribute_by_attrs(conf, "dasdu")); assert_string_equal("ID_EGGS", get_uid_attribute_by_attrs(conf, "nvme2n4")); put_multipath_config(conf); } static void test_wwid(void **state) { struct uevent *uev = *state; uevent_get_wwid(uev, &conf); assert_string_equal(uev->wwid, WWID); } static void test_major_bad_0(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR" str(MAJOR); assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_1(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOr=" str(MAJOR); assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_2(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJORIE=" str(MAJOR); assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_3(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR=max"; assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_4(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR=0x10"; assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_5(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJO=" str(MAJOR); assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_6(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR=" str(-MAJOR); assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_7(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR="; assert_int_equal(uevent_get_major(uev), -1); } static void test_major_bad_8(void **state) { struct uevent *uev = *state; uev->envp[0] = "MAJOR"; assert_int_equal(uevent_get_major(uev), -1); } static void test_dm_name_good(void **state) { struct uevent *uev = *state; char *name = uevent_get_dm_name(uev); assert_string_equal(name, DM_NAME); free(name); } static void test_dm_name_bad_0(void **state) { struct uevent *uev = *state; char *name; uev->envp[3] = "DM_NAME" DM_NAME; name = uevent_get_dm_name(uev); assert_ptr_equal(name, NULL); free(name); } static void test_dm_name_bad_1(void **state) { struct uevent *uev = *state; char *name; uev->envp[3] = "DM_NAMES=" DM_NAME; name = uevent_get_dm_name(uev); assert_ptr_equal(name, NULL); free(name); } static void test_dm_name_good_1(void **state) { struct uevent *uev = *state; char *name; /* Note we change index 2 here */ uev->envp[2] = "DM_NAME=" DM_NAME; name = uevent_get_dm_name(uev); assert_string_equal(name, DM_NAME); free(name); } static void test_dm_uuid_false_0(void **state) { struct uevent *uev = *state; assert_false(uevent_is_mpath(uev)); } static void test_dm_uuid_true_0(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UUID=mpath-foo"; assert_true(uevent_is_mpath(uev)); } static void test_dm_uuid_false_1(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UUID.mpath-foo"; assert_false(uevent_is_mpath(uev)); } static void test_dm_uuid_false_2(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UUID=mpath-"; assert_false(uevent_is_mpath(uev)); } static void test_dm_uuid_false_3(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UU=mpath-foo"; assert_false(uevent_is_mpath(uev)); } static void test_dm_uuid_false_4(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UUID=mpathfoo"; assert_false(uevent_is_mpath(uev)); } static void test_dm_uuid_false_5(void **state) { struct uevent *uev = *state; uev->envp[3] = "DM_UUID="; assert_false(uevent_is_mpath(uev)); } int test_uevent_get_XXX(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_major_good), cmocka_unit_test(test_minor_good), cmocka_unit_test(test_ro_good), cmocka_unit_test(test_dm_name_good), cmocka_unit_test(test_uid_attrs), cmocka_unit_test(test_wwid), cmocka_unit_test(test_major_bad_0), cmocka_unit_test(test_major_bad_1), cmocka_unit_test(test_major_bad_2), cmocka_unit_test(test_major_bad_3), cmocka_unit_test(test_major_bad_4), cmocka_unit_test(test_major_bad_5), cmocka_unit_test(test_major_bad_6), cmocka_unit_test(test_major_bad_7), cmocka_unit_test(test_major_bad_8), cmocka_unit_test(test_dm_name_bad_0), cmocka_unit_test(test_dm_name_bad_1), cmocka_unit_test(test_dm_name_good_1), cmocka_unit_test(test_dm_uuid_false_0), cmocka_unit_test(test_dm_uuid_true_0), cmocka_unit_test(test_dm_uuid_false_1), cmocka_unit_test(test_dm_uuid_false_2), cmocka_unit_test(test_dm_uuid_false_3), cmocka_unit_test(test_dm_uuid_false_4), cmocka_unit_test(test_dm_uuid_false_5), }; return cmocka_run_group_tests(tests, setup_uev, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_uevent_get_XXX(); return ret; } multipath-tools-0.11.1/tests/unaligned.c000066400000000000000000000040571475246302400202300ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "unaligned.h" #define SIZE 16 static const char memory[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; static const uint64_t intval64 = 0x0123456789abcdef; static const uint32_t intval32 = 0x01234567; static const uint16_t intval16 = 0x0123; #include "globals.c" static int setup(void **state) { return posix_memalign(state, 16, 2 * SIZE); } static int teardown(void **state) { free(*state); return 0; } #define make_test(bits, offset) \ static void test_ ## bits ## _ ## offset(void **state) \ { \ int len = bits/8; \ uint8_t *c = *state; \ uint8_t *p = *state + SIZE; \ uint64_t u; \ \ assert_in_range(len, 1, SIZE); \ assert_in_range(offset + len, 1, SIZE); \ memset(c, 0, 2 * SIZE); \ memcpy(c + offset, memory, len); \ \ u = get_unaligned_be##bits(c + offset); \ assert_int_equal(u, intval##bits); \ put_unaligned_be##bits(u, p + offset); \ assert_memory_equal(c + offset, p + offset, len); \ } make_test(16, 0); make_test(16, 1); make_test(32, 0); make_test(32, 1); make_test(32, 2); make_test(32, 3); make_test(64, 0); make_test(64, 1); make_test(64, 2); make_test(64, 3); make_test(64, 4); make_test(64, 5); make_test(64, 6); make_test(64, 7); int test_unaligned(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_16_0), cmocka_unit_test(test_16_1), cmocka_unit_test(test_32_0), cmocka_unit_test(test_32_1), cmocka_unit_test(test_32_2), cmocka_unit_test(test_32_3), cmocka_unit_test(test_64_0), cmocka_unit_test(test_64_1), cmocka_unit_test(test_64_2), cmocka_unit_test(test_64_3), cmocka_unit_test(test_64_4), cmocka_unit_test(test_64_5), cmocka_unit_test(test_64_6), cmocka_unit_test(test_64_7), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_unaligned(); return ret; } multipath-tools-0.11.1/tests/util.c000066400000000000000000000553751475246302400172500ustar00rootroot00000000000000/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "util.h" #include "globals.c" #define BITARR_SZ 4 static void test_basenamecpy_good0(void **state) { char dst[10]; assert_int_equal(basenamecpy("foobar", dst, sizeof(dst)), 6); assert_string_equal(dst, "foobar"); } static void test_basenamecpy_good1(void **state) { char dst[10]; assert_int_equal(basenamecpy("foo/bar", dst, sizeof(dst)), 3); assert_string_equal(dst, "bar"); } static void test_basenamecpy_good2(void **state) { char dst[10]; assert_int_equal(basenamecpy("/thud/blat", dst, sizeof(dst)), 4); assert_string_equal(dst, "blat"); } static void test_basenamecpy_good3(void **state) { char dst[4]; assert_int_equal(basenamecpy("foo/bar", dst, sizeof(dst)), 3); assert_string_equal(dst, "bar"); } static void test_basenamecpy_good4(void **state) { char dst[10]; assert_int_equal(basenamecpy("/xyzzy", dst, sizeof(dst)), 5); assert_string_equal(dst, "xyzzy"); } static void test_basenamecpy_good5(void **state) { char dst[4]; assert_int_equal(basenamecpy("/foo/bar\n", dst, sizeof(dst)), 3); assert_string_equal(dst, "bar"); } /* multipath expects any trailing whitespace to be stripped off the basename, * so that it will match pp->dev */ static void test_basenamecpy_good6(void **state) { char dst[6]; assert_int_equal(basenamecpy("/xyzzy/plugh ", dst, sizeof(dst)), 5); assert_string_equal(dst, "plugh"); } static void test_basenamecpy_good7(void **state) { char src[] = "/foo/bar"; char dst[10]; assert_int_equal(basenamecpy(src, dst, sizeof(dst)), 3); strcpy(src, "badbadno"); assert_string_equal(dst, "bar"); } /* buffer too small */ static void test_basenamecpy_bad0(void **state) { char dst[3]; assert_int_equal(basenamecpy("baz", dst, sizeof(dst)), 0); } /* ends in slash */ static void test_basenamecpy_bad1(void **state) { char dst[10]; assert_int_equal(basenamecpy("foo/bar/", dst, sizeof(dst)), 0); } static void test_basenamecpy_bad2(void **state) { char dst[10]; assert_int_equal(basenamecpy(NULL, dst, sizeof(dst)), 0); } static void test_basenamecpy_bad3(void **state) { char dst[10]; assert_int_equal(basenamecpy("", dst, sizeof(dst)), 0); } static void test_basenamecpy_bad4(void **state) { char dst[10]; assert_int_equal(basenamecpy("/", dst, sizeof(dst)), 0); } static void test_basenamecpy_bad5(void **state) { char dst[10]; assert_int_equal(basenamecpy("baz/qux", NULL, sizeof(dst)), 0); } static int test_basenamecpy(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_basenamecpy_good0), cmocka_unit_test(test_basenamecpy_good1), cmocka_unit_test(test_basenamecpy_good2), cmocka_unit_test(test_basenamecpy_good3), cmocka_unit_test(test_basenamecpy_good4), cmocka_unit_test(test_basenamecpy_good5), cmocka_unit_test(test_basenamecpy_good6), cmocka_unit_test(test_basenamecpy_good7), cmocka_unit_test(test_basenamecpy_bad0), cmocka_unit_test(test_basenamecpy_bad1), cmocka_unit_test(test_basenamecpy_bad2), cmocka_unit_test(test_basenamecpy_bad3), cmocka_unit_test(test_basenamecpy_bad4), cmocka_unit_test(test_basenamecpy_bad5), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void test_basename_01(void **state) { const char *path = "/foo/bar"; const char *base; base = basename(path); assert_string_equal(base, "bar"); assert_string_equal(path, "/foo/bar"); } static void test_basename_02(void **state) { const char *path = "/foo/bar/"; const char *base; base = basename(path); assert_string_equal(base, ""); assert_string_equal(path, "/foo/bar/"); } static int test_basename(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_basename_01), cmocka_unit_test(test_basename_02), }; return cmocka_run_group_tests(tests, NULL, NULL); } /* * On big endian systems, if bitfield_t is 32bit, we need * to swap the two 32 bit parts of a 64bit value to make * the tests below work. */ static uint64_t maybe_swap(uint64_t v) { uint32_t *s = (uint32_t *)&v; if (sizeof(bitfield_t) == 4) /* this is identity for little endian */ return ((uint64_t)s[1] << 32) | s[0]; else return v; } static void test_bitmask_1(void **state) { struct bitfield *bf; uint64_t *arr; int i, j, k, m, b; bf = alloc_bitfield(BITARR_SZ * 64); assert_non_null(bf); assert_int_equal(bf->len, BITARR_SZ * 64); arr = (uint64_t *)bf->bits; for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; assert_false(is_bit_set_in_bitfield(b, bf)); set_bit_in_bitfield(b, bf); for (k = 0; k < BITARR_SZ; k++) { #if 0 printf("b = %d j = %d k = %d a = %"PRIx64"\n", b, j, k, arr[k]); #endif if (k == j) assert_int_equal(maybe_swap(arr[j]), 1ULL << i); else assert_int_equal(arr[k], 0ULL); } for (m = 0; m < 64; m++) if (i == m) assert_true(is_bit_set_in_bitfield(64 * j + m, bf)); else assert_false(is_bit_set_in_bitfield(64 * j + m, bf)); clear_bit_in_bitfield(b, bf); assert_false(is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) assert_int_equal(arr[k], 0ULL); } } free(bf); } static void test_bitmask_2(void **state) { struct bitfield *bf; uint64_t *arr; int i, j, k, m, b; bf = alloc_bitfield(BITARR_SZ * 64); assert_non_null(bf); assert_int_equal(bf->len, BITARR_SZ * 64); arr = (uint64_t *)bf->bits; for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; assert_false(is_bit_set_in_bitfield(b, bf)); set_bit_in_bitfield(b, bf); for (m = 0; m < 64; m++) if (m <= i) assert_true(is_bit_set_in_bitfield(64 * j + m, bf)); else assert_false(is_bit_set_in_bitfield(64 * j + m, bf)); assert_true(is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) { if (k < j || (k == j && i == 63)) assert_int_equal(arr[k], ~0ULL); else if (k > j) assert_int_equal(arr[k], 0ULL); else assert_int_equal( maybe_swap(arr[k]), (1ULL << (i + 1)) - 1); } } } for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; assert_true(is_bit_set_in_bitfield(b, bf)); clear_bit_in_bitfield(b, bf); for (m = 0; m < 64; m++) if (m <= i) assert_false(is_bit_set_in_bitfield(64 * j + m, bf)); else assert_true(is_bit_set_in_bitfield(64 * j + m, bf)); assert_false(is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) { if (k < j || (k == j && i == 63)) assert_int_equal(arr[k], 0ULL); else if (k > j) assert_int_equal(arr[k], ~0ULL); else assert_int_equal( maybe_swap(arr[k]), ~((1ULL << (i + 1)) - 1)); } } } free(bf); } /* * Test operations on a 0-length bitfield */ static void test_bitmask_len_0(void **state) { struct bitfield *bf; bf = alloc_bitfield(0); assert_null(bf); } /* * We use uint32_t in the "small bitmask" tests below. * This means that we may have to swap 32bit words if bitfield_t * is 64bit wide. */ static unsigned int maybe_swap_idx(unsigned int i) { if (BYTE_ORDER == LITTLE_ENDIAN || sizeof(bitfield_t) == 4) return i; else /* 0<->1, 2<->3, ... */ return i + (i % 2 == 0 ? 1 : -1); } static void _test_bitmask_small(unsigned int n) { struct bitfield *bf; uint32_t *arr; unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; assert_true(sizeof(bitfield_t) == 4 || sizeof(bitfield_t) == 8); assert_in_range(n, 1, 64); bf = alloc_bitfield(n); assert_non_null(bf); assert_int_equal(bf->len, n); arr = (uint32_t *)bf->bits; for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n + 1, bf); for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n, bf); for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n - 1, bf); for (i = 0; i < size; i++) { unsigned int k = (n - 1) / 32; unsigned int j = (n - 1) - k * 32; unsigned int i1 = maybe_swap_idx(i); if (i == k) assert_int_equal(arr[i1], 1UL << j); else assert_int_equal(arr[i1], 0); } clear_bit_in_bitfield(n - 1, bf); for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(0, bf); assert_int_equal(arr[maybe_swap_idx(0)], 1); for (i = 1; i < size; i++) assert_int_equal(arr[maybe_swap_idx(i)], 0); free(bf); } static void _test_bitmask_small_2(unsigned int n) { struct bitfield *bf; uint32_t *arr; unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; assert_in_range(n, 65, 128); bf = alloc_bitfield(n); assert_non_null(bf); assert_int_equal(bf->len, n); arr = (uint32_t *)bf->bits; for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n + 1, bf); for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n, bf); for (i = 0; i < size; i++) assert_int_equal(arr[i], 0); set_bit_in_bitfield(n - 1, bf); assert_int_equal(arr[0], 0); for (i = 0; i < size; i++) { unsigned int k = (n - 1) / 32; unsigned int j = (n - 1) - k * 32; unsigned int i1 = maybe_swap_idx(i); if (i == k) assert_int_equal(arr[i1], 1UL << j); else assert_int_equal(arr[i1], 0); } set_bit_in_bitfield(0, bf); for (i = 0; i < size; i++) { unsigned int k = (n - 1) / 32; unsigned int j = (n - 1) - k * 32; unsigned int i1 = maybe_swap_idx(i); if (i == k && k == 0) assert_int_equal(arr[i1], (1UL << j) | 1); else if (i == k) assert_int_equal(arr[i1], 1UL << j); else if (i == 0) assert_int_equal(arr[i1], 1); else assert_int_equal(arr[i1], 0); } set_bit_in_bitfield(64, bf); for (i = 0; i < size; i++) { unsigned int k = (n - 1) / 32; unsigned int j = (n - 1) - k * 32; unsigned int i1 = maybe_swap_idx(i); if (i == k && (k == 0 || k == 2)) assert_int_equal(arr[i1], (1UL << j) | 1); else if (i == k) assert_int_equal(arr[i1], 1UL << j); else if (i == 2 || i == 0) assert_int_equal(arr[i1], 1); else assert_int_equal(arr[i1], 0); } clear_bit_in_bitfield(0, bf); for (i = 0; i < size; i++) { unsigned int k = (n - 1) / 32; unsigned int j = (n - 1) - k * 32; unsigned int i1 = maybe_swap_idx(i); if (i == k && k == 2) assert_int_equal(arr[i1], (1UL << j) | 1); else if (i == k) assert_int_equal(arr[i1], 1UL << j); else if (i == 2) assert_int_equal(arr[i1], 1); else assert_int_equal(arr[i1], 0); } free(bf); } static void test_bitmask_len_1(void **state) { _test_bitmask_small(1); } static void test_bitmask_len_2(void **state) { _test_bitmask_small(2); } static void test_bitmask_len_3(void **state) { _test_bitmask_small(3); } static void test_bitmask_len_23(void **state) { _test_bitmask_small(23); } static void test_bitmask_len_63(void **state) { _test_bitmask_small(63); } static void test_bitmask_len_64(void **state) { _test_bitmask_small(63); } static void test_bitmask_len_65(void **state) { _test_bitmask_small_2(65); } static void test_bitmask_len_66(void **state) { _test_bitmask_small_2(66); } static void test_bitmask_len_67(void **state) { _test_bitmask_small_2(67); } static void test_bitmask_len_103(void **state) { _test_bitmask_small_2(103); } static void test_bitmask_len_126(void **state) { _test_bitmask_small_2(126); } static void test_bitmask_len_127(void **state) { _test_bitmask_small_2(127); } static void test_bitmask_len_128(void **state) { _test_bitmask_small_2(128); } static int test_bitmasks(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_bitmask_1), cmocka_unit_test(test_bitmask_2), cmocka_unit_test(test_bitmask_len_0), cmocka_unit_test(test_bitmask_len_1), cmocka_unit_test(test_bitmask_len_2), cmocka_unit_test(test_bitmask_len_3), cmocka_unit_test(test_bitmask_len_23), cmocka_unit_test(test_bitmask_len_63), cmocka_unit_test(test_bitmask_len_64), cmocka_unit_test(test_bitmask_len_65), cmocka_unit_test(test_bitmask_len_66), cmocka_unit_test(test_bitmask_len_67), cmocka_unit_test(test_bitmask_len_103), cmocka_unit_test(test_bitmask_len_126), cmocka_unit_test(test_bitmask_len_127), cmocka_unit_test(test_bitmask_len_128), }; return cmocka_run_group_tests(tests, NULL, NULL); } #define DST_STR "Hello" static const char dst_str[] = DST_STR; /* length of src_str and dst_str should be different */ static const char src_str[] = " World"; /* Must be big enough to hold dst_str and src_str */ #define ARRSZ 16 #define FILL '@' /* strlcpy with length 0 */ static void test_strlcpy_0(void **state) { char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 0); assert_int_equal(rc, strlen(src_str)); assert_string_equal(tst, dst_str); } /* strlcpy with length 1 */ static void test_strlcpy_1(void **state) { char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 1); assert_int_equal(rc, strlen(src_str)); assert_int_equal(tst[0], '\0'); assert_string_equal(tst + 1, dst_str + 1); } /* strlcpy with length 2 */ static void test_strlcpy_2(void **state) { char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 2); assert_int_equal(rc, strlen(src_str)); assert_int_equal(tst[0], src_str[0]); assert_int_equal(tst[1], '\0'); assert_string_equal(tst + 2, dst_str + 2); } /* strlcpy with dst length < src length */ static void test_strlcpy_3(void **state) { char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, sizeof(tst)); assert_int_equal(rc, strlen(src_str)); assert_int_equal(sizeof(tst) - 1, strlen(tst)); assert_true(strncmp(tst, src_str, sizeof(tst) - 1) == 0); } /* strlcpy with dst length > src length */ static void test_strlcpy_4(void **state) { static const char old[] = "0123456789"; char *tst; int rc; tst = strdup(old); rc = strlcpy(tst, src_str, sizeof(old)); assert_int_equal(rc, strlen(src_str)); assert_string_equal(src_str, tst); assert_string_equal(tst + sizeof(src_str), old + sizeof(src_str)); free(tst); } /* strlcpy with dst length = src length, dst not terminated */ static void test_strlcpy_5(void **state) { char *tst; int rc; const int sz = sizeof(src_str); tst = malloc(sz); assert_non_null(tst); memset(tst, 'f', sizeof(src_str)); rc = strlcpy(tst, src_str, sz); assert_int_equal(rc, strlen(src_str)); assert_string_equal(src_str, tst); free(tst); } /* strlcpy with dst length > src length, dst not terminated */ static void test_strlcpy_6(void **state) { char *tst; int rc; const int sz = sizeof(src_str); tst = malloc(sz + 2); assert_non_null(tst); memset(tst, 'f', sz + 2); rc = strlcpy(tst, src_str, sz + 2); assert_int_equal(rc, strlen(src_str)); assert_string_equal(src_str, tst); assert_int_equal(tst[sz], 'f'); assert_int_equal(tst[sz + 1], 'f'); free(tst); } /* strlcpy with empty src */ static void test_strlcpy_7(void **state) { char tst[] = DST_STR; static const char empty[] = ""; int rc; rc = strlcpy(tst, empty, sizeof(tst)); assert_int_equal(rc, strlen(empty)); assert_string_equal(empty, tst); assert_string_equal(tst + 1, dst_str + 1); } /* strlcpy with empty src, length 0 */ static void test_strlcpy_8(void **state) { char tst[] = DST_STR; static const char empty[] = ""; int rc; rc = strlcpy(tst, empty, 0); assert_int_equal(rc, strlen(empty)); assert_string_equal(dst_str, tst); } static int test_strlcpy(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strlcpy_0), cmocka_unit_test(test_strlcpy_1), cmocka_unit_test(test_strlcpy_2), cmocka_unit_test(test_strlcpy_3), cmocka_unit_test(test_strlcpy_4), cmocka_unit_test(test_strlcpy_5), cmocka_unit_test(test_strlcpy_6), cmocka_unit_test(test_strlcpy_7), cmocka_unit_test(test_strlcpy_8), }; return cmocka_run_group_tests(tests, NULL, NULL); } /* 0-terminated string, filled with non-0 after the terminator */ static void prep_buf(char *buf, size_t size, const char *word) { memset(buf, FILL, size); assert_in_range(strlen(word), 0, size - 1); memcpy(buf, word, strlen(word) + 1); } /* strlcat with size 0, dst not 0-terminated */ static void test_strlcat_0(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, 0); assert_int_equal(rc, strlen(src_str)); assert_string_equal(tst, dst_str); assert_int_equal(tst[sizeof(dst_str)], FILL); } /* strlcat with length 1, dst not 0-terminated */ static void test_strlcat_1(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, 1); assert_int_equal(rc, 1 + strlen(src_str)); assert_string_equal(tst, dst_str); assert_int_equal(tst[sizeof(dst_str)], FILL); } /* strlcat with length = dst - 1 */ static void test_strlcat_2(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, strlen(dst_str)); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_string_equal(tst, dst_str); assert_int_equal(tst[sizeof(dst_str)], FILL); } /* strlcat with length = dst */ static void test_strlcat_3(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, strlen(dst_str) + 1); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_string_equal(tst, dst_str); assert_int_equal(tst[sizeof(dst_str)], FILL); } /* strlcat with len = dst + 1 */ static void test_strlcat_4(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, strlen(dst_str) + 2); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_false(strncmp(tst, dst_str, strlen(dst_str))); assert_int_equal(tst[strlen(dst_str)], src_str[0]); assert_int_equal(tst[strlen(dst_str) + 1], '\0'); assert_int_equal(tst[strlen(dst_str) + 2], FILL); } /* strlcat with len = needed - 1 */ static void test_strlcat_5(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, strlen(dst_str) + strlen(src_str)); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_false(strncmp(tst, dst_str, strlen(dst_str))); assert_false(strncmp(tst + strlen(dst_str), src_str, strlen(src_str) - 1)); assert_int_equal(tst[strlen(dst_str) + strlen(src_str) - 1], '\0'); assert_int_equal(tst[strlen(dst_str) + strlen(src_str)], FILL); } /* strlcat with exactly sufficient space */ static void test_strlcat_6(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, strlen(dst_str) + strlen(src_str) + 1); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_false(strncmp(tst, dst_str, strlen(dst_str))); assert_string_equal(tst + strlen(dst_str), src_str); assert_int_equal(tst[strlen(dst_str) + strlen(src_str) + 1], FILL); } /* strlcat with sufficient space */ static void test_strlcat_7(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, src_str, sizeof(tst)); assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); assert_false(strncmp(tst, dst_str, strlen(dst_str))); assert_string_equal(tst + strlen(dst_str), src_str); } /* strlcat with 0-length string */ static void test_strlcat_8(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), dst_str); rc = strlcat(tst, "", sizeof(tst)); assert_int_equal(rc, strlen(dst_str)); assert_string_equal(tst, dst_str); assert_int_equal(tst[sizeof(dst_str)], FILL); } /* strlcat with empty dst */ static void test_strlcat_9(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), ""); rc = strlcat(tst, src_str, ARRSZ); assert_int_equal(rc, strlen(src_str)); assert_string_equal(tst, src_str); assert_int_equal(tst[sizeof(src_str)], FILL); } /* strlcat with empty dst and src */ static void test_strlcat_10(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), ""); rc = strlcat(tst, "", ARRSZ); assert_int_equal(rc, 0); assert_string_equal(tst, ""); assert_int_equal(tst[1], FILL); } /* strlcat with no space to store 0 */ static void test_strlcat_11(void **state) { char tst[ARRSZ]; int rc; prep_buf(tst, sizeof(tst), ""); tst[0] = FILL; rc = strlcat(tst, src_str, 0); assert_int_equal(rc, strlen(src_str)); assert_int_equal(tst[0], FILL); } static int test_strlcat(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strlcat_0), cmocka_unit_test(test_strlcat_1), cmocka_unit_test(test_strlcat_2), cmocka_unit_test(test_strlcat_3), cmocka_unit_test(test_strlcat_4), cmocka_unit_test(test_strlcat_5), cmocka_unit_test(test_strlcat_6), cmocka_unit_test(test_strlcat_7), cmocka_unit_test(test_strlcat_8), cmocka_unit_test(test_strlcat_9), cmocka_unit_test(test_strlcat_10), cmocka_unit_test(test_strlcat_11), }; return cmocka_run_group_tests(tests, NULL, NULL); } static void test_strchop_nochop(void **state) { char hello[] = "hello"; assert_int_equal(strchop(hello), 5); assert_string_equal(hello, "hello"); } static void test_strchop_newline(void **state) { char hello[] = "hello\n"; assert_int_equal(strchop(hello), 5); assert_string_equal(hello, "hello"); } static void test_strchop_space(void **state) { char hello[] = " ello "; assert_int_equal(strchop(hello), 5); assert_string_equal(hello, " ello"); } static void test_strchop_mix(void **state) { char hello[] = " el\no \t \n\n \t \n"; assert_int_equal(strchop(hello), 5); assert_string_equal(hello, " el\no"); } static void test_strchop_blank(void **state) { char hello[] = " \t \n\n \t \n"; assert_int_equal(strchop(hello), 0); assert_string_equal(hello, ""); } static void test_strchop_empty(void **state) { char hello[] = ""; assert_int_equal(strchop(hello), 0); assert_string_equal(hello, ""); } static int test_strchop(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strchop_nochop), cmocka_unit_test(test_strchop_newline), cmocka_unit_test(test_strchop_space), cmocka_unit_test(test_strchop_mix), cmocka_unit_test(test_strchop_blank), cmocka_unit_test(test_strchop_empty), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_basenamecpy(); ret += test_basename(); ret += test_bitmasks(); ret += test_strlcpy(); ret += test_strlcat(); ret += test_strchop(); return ret; } multipath-tools-0.11.1/tests/valid.c000066400000000000000000000440771475246302400173670ustar00rootroot00000000000000/* * Copyright (c) 2020 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "globals.c" #include "util.h" #include "discovery.h" #include "wwids.h" #include "blacklist.h" #include "foreign.h" #include "valid.h" #define PATHINFO_REAL 9999 int test_fd; struct udev_device { int unused; } test_udev; bool __wrap_sysfs_is_multipathed(struct path *pp, bool set_wwid) { bool is_multipathed = mock_type(bool); assert_non_null(pp); assert_int_not_equal(strlen(pp->dev), 0); if (is_multipathed && set_wwid) strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); return is_multipathed; } int __wrap_mpath_connect__(int nonblocking) { bool connected = mock_type(bool); assert_int_equal(nonblocking, 1); if (connected) return test_fd; errno = mock_type(int); return -1; } /* There's no point in checking the return value here */ int __wrap_mpath_disconnect(int fd) { assert_int_equal(fd, test_fd); return 0; } struct udev_device *__wrap_udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) { bool passed = mock_type(bool); assert_string_equal(sysname, mock_ptr_type(char *)); if (passed) return &test_udev; return NULL; } /* For devtype check */ const char *__wrap_udev_device_get_property_value(struct udev_device *udev_device, const char *property) { check_expected(property); return mock_ptr_type(char *); } /* For the "hidden" check in pathinfo() */ const char *__wrap_udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) { check_expected(sysattr); return mock_ptr_type(char *); } /* For pathinfo() -> is_claimed_by_foreign() */ int __wrap_add_foreign(struct udev_device *udev_device) { return mock_type(int); } /* For is_device_used() */ const char *__wrap_udev_device_get_sysname(struct udev_device *udev_device) { return mock_ptr_type(char *); } /* called from pathinfo() */ int __wrap_filter_devnode(struct config *conf, const struct vector_s *elist, const char *vendor, const char * product, const char *dev) { return mock_type(int); } /* called from pathinfo() */ int __wrap_filter_device(const struct vector_s *blist, const struct vector_s *elist, const char *vendor, const char * product, const char *dev) { return mock_type(int); } /* for common_sysfs_pathinfo() */ dev_t __wrap_udev_device_get_devnum(struct udev_device *ud) { return mock_type(dev_t); } /* for common_sysfs_pathinfo() */ int __wrap_sysfs_get_size(struct path *pp, unsigned long long * size) { return mock_type(int); } /* called in pathinfo() before filter_property() */ int __wrap_select_getuid(struct config *conf, struct path *pp) { pp->uid_attribute = mock_ptr_type(char *); return 0; } int __real_pathinfo(struct path *pp, struct config *conf, int mask); int __wrap_pathinfo(struct path *pp, struct config *conf, int mask) { int ret = mock_type(int); assert_string_equal(pp->dev, mock_ptr_type(char *)); assert_int_equal(mask, DI_SYSFS | DI_WWID | DI_BLACKLIST); if (ret == PATHINFO_REAL) { /* for test_filter_property() */ ret = __real_pathinfo(pp, conf, mask); return ret; } else if (ret == PATHINFO_OK) { pp->uid_attribute = "ID_TEST"; strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); } else memset(pp->wwid, 0, WWID_SIZE); return ret; } int __wrap_filter_property(struct config *conf, struct udev_device *udev, int lvl, const char *uid_attribute) { int ret = mock_type(int); assert_string_equal(uid_attribute, "ID_TEST"); return ret; } int __wrap_is_failed_wwid(const char *wwid) { int ret = mock_type(int); assert_string_equal(wwid, mock_ptr_type(char *)); return ret; } const char *__wrap_udev_device_get_syspath(struct udev_device *udevice) { return mock_ptr_type(char *); } int __wrap_check_wwids_file(char *wwid, int write_wwid) { bool passed = mock_type(bool); assert_int_equal(write_wwid, 0); assert_string_equal(wwid, mock_ptr_type(char *)); if (passed) return 0; else return -1; } int __wrap_dm_find_map_by_wwid(const char *wwid, char *name, struct dm_info *dmi) { int ret = mock_type(int); assert_string_equal(wwid, mock_ptr_type(char *)); return ret; } enum { STAGE_IS_MULTIPATHED, STAGE_CHECK_MULTIPATHD, STAGE_GET_UDEV_DEVICE, STAGE_PATHINFO_REAL, STAGE_PATHINFO, STAGE_FILTER_PROPERTY, STAGE_IS_FAILED, STAGE_CHECK_WWIDS, STAGE_UUID_PRESENT, }; enum { CHECK_MPATHD_RUNNING, CHECK_MPATHD_EAGAIN, CHECK_MPATHD_SKIP, }; /* setup the test to continue past the given stage in is_path_valid() */ static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, unsigned int stage) { will_return(__wrap_sysfs_is_multipathed, false); if (stage == STAGE_IS_MULTIPATHED) return; if (check_multipathd == CHECK_MPATHD_RUNNING) will_return(__wrap_mpath_connect__, true); else if (check_multipathd == CHECK_MPATHD_EAGAIN) { will_return(__wrap_mpath_connect__, false); will_return(__wrap_mpath_connect__, EAGAIN); } /* nothing for CHECK_MPATHD_SKIP */ if (stage == STAGE_CHECK_MULTIPATHD) return; will_return(__wrap_udev_device_new_from_subsystem_sysname, true); will_return(__wrap_udev_device_new_from_subsystem_sysname, name); expect_string(__wrap_udev_device_get_property_value, property, "DEVTYPE"); will_return(__wrap_udev_device_get_property_value, "disk"); if (stage == STAGE_GET_UDEV_DEVICE) return; if (stage == STAGE_PATHINFO_REAL) { /* special case for test_filter_property() */ will_return(__wrap_pathinfo, PATHINFO_REAL); will_return(__wrap_pathinfo, name); expect_string(__wrap_udev_device_get_sysattr_value, sysattr, "hidden"); will_return(__wrap_udev_device_get_sysattr_value, NULL); will_return(__wrap_add_foreign, FOREIGN_IGNORED); will_return(__wrap_filter_devnode, MATCH_NOTHING); will_return(__wrap_udev_device_get_devnum, makedev(259, 0)); will_return(__wrap_sysfs_get_size, 0); will_return(__wrap_select_getuid, "ID_TEST"); return; } will_return(__wrap_pathinfo, PATHINFO_OK); will_return(__wrap_pathinfo, name); will_return(__wrap_pathinfo, wwid); if (stage == STAGE_PATHINFO) return; if (stage == STAGE_FILTER_PROPERTY) return; will_return(__wrap_is_failed_wwid, WWID_IS_NOT_FAILED); will_return(__wrap_is_failed_wwid, wwid); /* avoid real is_device_in_use() check */ if (conf.find_multipaths == FIND_MULTIPATHS_GREEDY || conf.find_multipaths == FIND_MULTIPATHS_SMART) will_return(__wrap_udev_device_get_syspath, NULL); if (stage == STAGE_IS_FAILED) return; will_return(__wrap_check_wwids_file, false); will_return(__wrap_check_wwids_file, wwid); if (stage == STAGE_CHECK_WWIDS) return; will_return(__wrap_dm_find_map_by_wwid, 0); will_return(__wrap_dm_find_map_by_wwid, wwid); } static void test_bad_arguments(void **state) { struct path pp; char too_long[FILE_NAME_SIZE + 1]; memset(&pp, 0, sizeof(pp)); /* test NULL pointers */ assert_int_equal(is_path_valid("test", &conf, NULL, true), PATH_IS_ERROR); assert_int_equal(is_path_valid("test", NULL, &pp, true), PATH_IS_ERROR); assert_int_equal(is_path_valid(NULL, &conf, &pp, true), PATH_IS_ERROR); /* test undefined find_multipaths */ conf.find_multipaths = FIND_MULTIPATHS_UNDEF; assert_int_equal(is_path_valid("test", &conf, &pp, true), PATH_IS_ERROR); /* test name too long */ memset(too_long, 'x', sizeof(too_long)); too_long[sizeof(too_long) - 1] = '\0'; conf.find_multipaths = FIND_MULTIPATHS_STRICT; assert_int_equal(is_path_valid(too_long, &conf, &pp, true), PATH_IS_ERROR); } static void test_sysfs_is_multipathed(void **state) { struct path pp; char *name = "test"; char *wwid = "test_wwid"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; /* test for already existing multipathed device */ will_return(__wrap_sysfs_is_multipathed, true); will_return(__wrap_sysfs_is_multipathed, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_VALID_NO_CHECK); assert_string_equal(pp.dev, name); assert_string_equal(pp.wwid, wwid); /* test for wwid device with empty wwid */ will_return(__wrap_sysfs_is_multipathed, true); will_return(__wrap_sysfs_is_multipathed, ""); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_ERROR); } static void test_check_multipathd(void **state) { struct path pp; char *name = "test"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; /* test failed check to see if multipathd is active */ will_return(__wrap_sysfs_is_multipathed, false); will_return(__wrap_mpath_connect__, false); will_return(__wrap_mpath_connect__, ECONNREFUSED); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); /* test pass because connect returned EAGAIN. fail getting udev */ setup_passing(name, NULL, CHECK_MPATHD_EAGAIN, STAGE_CHECK_MULTIPATHD); will_return(__wrap_udev_device_new_from_subsystem_sysname, false); will_return(__wrap_udev_device_new_from_subsystem_sysname, name); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_ERROR); /* test pass because connect succeeded. fail getting udev */ memset(&pp, 0, sizeof(pp)); setup_passing(name, NULL, CHECK_MPATHD_RUNNING, STAGE_CHECK_MULTIPATHD); will_return(__wrap_udev_device_new_from_subsystem_sysname, false); will_return(__wrap_udev_device_new_from_subsystem_sysname, name); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_ERROR); assert_string_equal(pp.dev, name); /* test pass because connect succeeded. succeed getting udev. Wrong DEVTYPE */ memset(&pp, 0, sizeof(pp)); setup_passing(name, NULL, CHECK_MPATHD_RUNNING, STAGE_CHECK_MULTIPATHD); will_return(__wrap_udev_device_new_from_subsystem_sysname, true); will_return(__wrap_udev_device_new_from_subsystem_sysname, name); expect_string(__wrap_udev_device_get_property_value, property, "DEVTYPE"); will_return(__wrap_udev_device_get_property_value, "partition"); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); /* test pass because connect succeeded. succeed getting udev. Bad DEVTYPE */ memset(&pp, 0, sizeof(pp)); setup_passing(name, NULL, CHECK_MPATHD_RUNNING, STAGE_CHECK_MULTIPATHD); will_return(__wrap_udev_device_new_from_subsystem_sysname, true); will_return(__wrap_udev_device_new_from_subsystem_sysname, name); expect_string(__wrap_udev_device_get_property_value, property, "DEVTYPE"); will_return(__wrap_udev_device_get_property_value, NULL); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); } static void test_pathinfo(void **state) { struct path pp; char *name = "test"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; /* Test pathinfo blacklisting device */ setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); will_return(__wrap_pathinfo, PATHINFO_SKIPPED); will_return(__wrap_pathinfo, name); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); /* Test pathinfo failing */ memset(&pp, 0, sizeof(pp)); setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); will_return(__wrap_pathinfo, PATHINFO_FAILED); will_return(__wrap_pathinfo, name); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_ERROR); /* Test blank wwid */ memset(&pp, 0, sizeof(pp)); setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); will_return(__wrap_pathinfo, PATHINFO_OK); will_return(__wrap_pathinfo, name); will_return(__wrap_pathinfo, ""); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); } static void test_filter_property(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; /* test blacklist property */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL); will_return(__wrap_filter_property, MATCH_PROPERTY_BLIST); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); assert_ptr_equal(pp.udev, &test_udev); /* test missing property */ memset(&pp, 0, sizeof(pp)); setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL); will_return(__wrap_filter_property, MATCH_PROPERTY_BLIST_MISSING); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); /* test MATCH_NOTHING fail on filter_device */ memset(&pp, 0, sizeof(pp)); setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL); will_return(__wrap_filter_property, MATCH_NOTHING); will_return(__wrap_filter_device, MATCH_DEVICE_BLIST); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); } static void test_is_failed_wwid(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; /* Test wwid failed */ setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_FILTER_PROPERTY); will_return(__wrap_is_failed_wwid, WWID_IS_FAILED); will_return(__wrap_is_failed_wwid, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); /* test is_failed_wwid error */ setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_FILTER_PROPERTY); will_return(__wrap_is_failed_wwid, WWID_FAILED_ERROR); will_return(__wrap_is_failed_wwid, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_ERROR); } static void test_greedy(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; /* test greedy success with checking multipathd */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_GREEDY; setup_passing(name, wwid, CHECK_MPATHD_RUNNING, STAGE_IS_FAILED); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); /* test greedy success without checking multipathd */ memset(&pp, 0, sizeof(pp)); setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_IS_FAILED); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_VALID); } static void test_check_wwids(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; setup_passing(name, wwid, CHECK_MPATHD_EAGAIN, STAGE_IS_FAILED); will_return(__wrap_check_wwids_file, true); will_return(__wrap_check_wwids_file, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_VALID_NO_CHECK); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); } static void test_check_uuid_present(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; setup_passing(name, wwid, CHECK_MPATHD_RUNNING, STAGE_CHECK_WWIDS); will_return(__wrap_dm_find_map_by_wwid, 1); will_return(__wrap_dm_find_map_by_wwid, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); } static void test_find_multipaths(void **state) { struct path pp; char *name = "test"; char *wwid = "test-wwid"; /* test find_multipaths = FIND_MULTIPATHS_STRICT */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); /* test find_multipaths = FIND_MULTIPATHS_OFF */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_OFF; setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); /* test find_multipaths = FIND_MULTIPATHS_ON */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_ON; setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_NOT_VALID); /* test find_multipaths = FIND_MULTIPATHS_SMART */ memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_SMART; setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); assert_int_equal(is_path_valid(name, &conf, &pp, false), PATH_IS_MAYBE_VALID); assert_string_equal(pp.dev, name); assert_ptr_equal(pp.udev, &test_udev); assert_string_equal(pp.wwid, wwid); } int test_valid(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_bad_arguments), cmocka_unit_test(test_sysfs_is_multipathed), cmocka_unit_test(test_check_multipathd), cmocka_unit_test(test_pathinfo), cmocka_unit_test(test_filter_property), cmocka_unit_test(test_is_failed_wwid), cmocka_unit_test(test_greedy), cmocka_unit_test(test_check_wwids), cmocka_unit_test(test_check_uuid_present), cmocka_unit_test(test_find_multipaths), }; return cmocka_run_group_tests(tests, NULL, NULL); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_valid(); return ret; } multipath-tools-0.11.1/tests/vpd.c000066400000000000000000000675201475246302400170570ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Copyright (c) 2019 Martin Wilck, SUSE Linux GmbH, Nuremberg */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unaligned.h" #include "debug.h" #include "vector.h" #include "structs.h" #include "discovery.h" #include "wrap64.h" #include "globals.c" #define VPD_BUFSIZ 4096 struct vpdtest { unsigned char vpdbuf[VPD_BUFSIZ]; char wwid[WWID_SIZE]; }; static regex_t space_re; static int setup(void **state) { struct vpdtest *vt = malloc(sizeof(*vt)); int rc; if (vt == NULL) return -1; *state = vt; rc = regcomp(&space_re, " +", REG_EXTENDED); assert_int_equal(rc, 0); return 0; } static int teardown(void **state) { struct vpdtest *vt = *state; free(vt); *state = NULL; regfree(&space_re); return 0; } /* vendor_id should have less than 8 chars to test space handling */ static const char vendor_id[] = "Linux"; static const char test_id[] = "A123456789AbcDefB123456789AbcDefC123456789AbcDefD123456789AbcDef"; int WRAP_IOCTL(int fd, unsigned long request, void *param) { int len; struct sg_io_hdr *io_hdr; unsigned char *val; len = mock(); io_hdr = (struct sg_io_hdr *)param; assert_in_range(len, 0, io_hdr->dxfer_len); val = mock_ptr_type(unsigned char *); io_hdr->status = 0; memcpy(io_hdr->dxferp, val, len); return 0; } /** * create_vpd80() - create a "unit serial number" VPD page. * @buf: VPD buffer * @bufsiz: length of VPD buffer * @id: input ID * @size: value for the "page length" field * @len: actual number of characters to use from @id * * If len < size, the content will be right aligned, as mandated by the * SPC spec. * * Return: VPD length. */ static int create_vpd80(unsigned char *buf, size_t bufsiz, const char *id, int size, int len) { assert_true(len <= size); memset(buf, 0, bufsiz); buf[1] = 0x80; put_unaligned_be16(size, buf + 2); memset(buf + 4, ' ', size - len); memcpy(buf + 4 + size - len, id, len); return size + 4; } static int _hex2bin(const char hx) { assert_true(isxdigit(hx)); if (hx >= '0' && hx <= '9') return hx - '0'; if (hx >= 'a' && hx <= 'f') return hx - 'a' + 10; if (hx >= 'A' && hx <= 'F') return hx - 'A' + 10; return -1; } static void hex2bin(unsigned char *dst, const char *src, size_t dstlen, size_t srclen) { const char *sc; unsigned char *ds; assert_true(srclen % 2 == 0); for (sc = src, ds = dst; sc < src + srclen && ds < dst + dstlen; sc += 2, ++ds) *ds = 16 * _hex2bin(sc[0]) + _hex2bin(sc[1]); } /** * create_t10_vendor_id_desc() - Create T10 vendor ID * @desc: descriptor buffer * @id: input ID * @n: number of characters to use for ID * (includes 8 bytes for vendor ID!) * * Create a "T10 vendor specific ID" designation descriptor. * The vendor field (8 bytes) is filled with vendor_id (above). * * Return: descriptor length. */ static int create_t10_vendor_id_desc(unsigned char *desc, const char *id, size_t n) { int vnd_len = sizeof(vendor_id) - 1; /* code set: ascii */ desc[0] = 2; /* type: 10 vendor ID */ desc[1] = 1; desc[2] = 0; desc[3] = n; memcpy(desc + 4, (const unsigned char *)vendor_id, vnd_len); memset(desc + 4 + vnd_len, ' ', 8 - vnd_len); memcpy(desc + 4 + 8, (const unsigned char *)id, n - 8); return n + 4; } /** * create_eui64_desc() - create EUI64 descriptor. * @desc, @id: see above. * @n: number of bytes (8, 12, or 16). * * Create an EUI64 designation descriptor. * * Return: descriptor length. */ static int create_eui64_desc(unsigned char *desc, const char *id, size_t n) { assert_true(n == 8 || n == 12 || n == 16); /* code set: binary */ desc[0] = 1; /* type: EUI64 */ desc[1] = 2; desc[2] = 0; desc[3] = n; hex2bin(desc + 4, id, n, 2 * n); return n + 4; } /** * create_naa_desc() - create an NAA designation descriptor * @desc, @id: see above. * @naa: Name Address Authority field (2, 3, 5, or 6). * * Return: descriptor length. */ static int create_naa_desc(unsigned char *desc, const char *id, int naa) { assert_true(naa == 2 || naa == 3 || naa == 5 || naa == 6); /* code set: binary */ desc[0] = 1; /* type: NAA */ desc[1] = 3; desc[2] = 0; desc[4] = _hex2bin(id[0]) | (naa << 4); switch (naa) { case 2: case 3: case 5: hex2bin(desc + 5, id + 1, 7, 14); desc[3] = 8; return 12; case 6: hex2bin(desc + 5, id + 1, 15, 30); desc[3] = 16; return 20; default: return 0; } } /* type and flags for SCSI name string designation descriptor */ enum { STR_EUI = 0, STR_NAA, STR_IQN, STR_MASK = 0xf, ZERO_LAST = 0x10, /* flag to zero out some bytes at string end */ }; static const char * const str_prefix[] = { [STR_EUI] = "eui.", [STR_NAA] = "naa.", [STR_IQN] = "iqn.", }; static const char byte0[] = { [STR_EUI] = '2', [STR_NAA] = '3', [STR_IQN] = '8', }; /** * create_scsi_string_desc() - create a SCSI name string descriptor. * @desc, @id: see above. * @typ: one of STR_EUI, STR_NAA, STR_IQN, possibly ORd with ZERO_LAST * @maxlen: number of characters to use from input ID. * * If ZERO_LAST is set, zero out the last byte. * * Return: descriptor length. */ static int create_scsi_string_desc(unsigned char *desc, const char *id, int typ, int maxlen) { int len, plen; int type = typ & STR_MASK; /* code set: UTF-8 */ desc[0] = 3; /* type: SCSI string */ desc[1] = 8; desc[2] = 0; assert_in_range(type, STR_EUI, STR_IQN); assert_true(maxlen % 4 == 0); len = snprintf((char *)(desc + 4), maxlen, "%s%s", str_prefix[type], id); if (len > maxlen) len = maxlen; /* zero-pad */ if (typ & ZERO_LAST) len -= 2; plen = 4 * ((len - 1) / 4) + 4; memset(desc + 4 + len, '\0', plen - len); desc[3] = plen; return plen + 4; } /** * create_vpd83() - create "device identification" VPD page * @buf, @bufsiz, @id: see above. * @type: descriptor type to use (1, 2, 3, 8) * @parm: opaque parameter (e.g. means "naa" for NAA type) * @len: designator length (exact meaning depends on designator type) * * Create a "device identification" VPD page with a single * designation descriptor. * * Return: VPD page length. */ static int create_vpd83(unsigned char *buf, size_t bufsiz, const char *id, uint8_t type, int parm, int len) { unsigned char *desc; int n = 0; /* Fill with large number, which will cause length overflow */ memset(buf, 0xed, bufsiz); buf[0] = 0; buf[1] = 0x83; desc = buf + 4; switch (type) { case 1: n = create_t10_vendor_id_desc(desc, id, len); break; case 2: n = create_eui64_desc(desc, id, len); break; case 3: n = create_naa_desc(desc, id, parm); break; case 8: n = create_scsi_string_desc(desc, id, parm, len); break; default: break; } put_unaligned_be16(n, buf + 2); return n + 4; } /** * assert_correct_wwid() - test that a retrieved WWID matches expectations * @test: test name * @expected: expected WWID length * @returned: WWID length as returned by code under test * @byte0, @byte1: leading chars that our code prepends to the ID * (e.g. "36" for "NAA registered extended" type) * @lowercase: set if lower case WWID is expected * @orig: original ID string, may be longer than wwid * @wwid: WWID as returned by code under test */ static void assert_correct_wwid(const char *test, int expected, int returned, int byte0, int byte1, bool lowercase, const char *orig, const char *wwid) { int ofs = 0, i; condlog(2, "%s: exp/ret: %d/%d, wwid: %s", test, expected, returned, wwid); /* * byte0 and byte1 are the leading chars that our code prepends * to the ID to indicate the designation descriptor type, . */ if (byte0 != 0) { assert_int_equal(byte0, wwid[0]); ++ofs; } if (byte1 != 0) { assert_int_equal(byte1, wwid[1]); ++ofs; } /* check matching length, and length of WWID string */ assert_int_equal(expected, returned); assert_int_equal(returned, strlen(wwid)); /* check expected string value up to expected length */ for (i = 0; i < returned - ofs; i++) assert_int_equal(wwid[ofs + i], lowercase ? tolower(orig[i]) : orig[i]); } /* * For T10 vendor ID - replace sequences of spaces with a single underscore. * Use a different implementation then libmultipath, deliberately. */ static char *subst_spaces(const char *src) { char *dst = calloc(1, strlen(src) + 1); char *p; regmatch_t match; int rc = 0; assert_non_null(dst); for (rc = regexec(&space_re, src, 1, &match, 0), p = dst; rc == 0; src += match.rm_eo, rc = regexec(&space_re, src, 1, &match, 0)) { memcpy(p, src, match.rm_so); p += match.rm_so; *p = '_'; ++p; } assert_int_equal(rc, REG_NOMATCH); strcpy(p, src); return dst; } /** * test_vpd_vnd_LEN_WLEN() - test code for VPD 83, T10 vendor ID * @LEN: ID length in the VPD page (includes 8 byte vendor ID) * @WLEN: WWID buffer size * * The input ID is modified by inserting some spaces, to be able to * test the handling of spaces by the code. This is relevant only for * a minimum input length of 24. * The expected result must be adjusted accordingly. */ #define make_test_vpd_vnd(len, wlen) \ static void test_vpd_vnd_ ## len ## _ ## wlen(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret, rc; \ int exp_len; \ char *exp_wwid, *exp_subst, *input; \ \ input = strdup(test_id); \ /* 8 vendor bytes collapsed to actual vendor ID length + 1 */ \ /* and one '1' prepended */ \ exp_len = len - 8 + sizeof(vendor_id) + 1; \ \ /* insert some spaces to test space collapsing */ \ input[15] = input[17] = input[18] = ' '; \ /* adjust expectation for space treatment */ \ /* drop char for 2nd space on offset 17/18 */ \ if (len >= 18 + 9) \ --exp_len; \ /* drop trailing single '_' if input ends with space */ \ if (len == 15 + 9 || len == 17 + 9 || len == 18 + 9) \ --exp_len; \ if (exp_len >= wlen) \ exp_len = wlen - 1; \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), input, \ 1, 0, len); \ rc = asprintf(&exp_wwid, "%s_%s", vendor_id, input); \ assert_int_not_equal(rc, -1); \ free(input); \ /* Replace spaces, like code under test */ \ exp_subst = subst_spaces(exp_wwid); \ free(exp_wwid); \ 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_vnd_" #len "_" #wlen, \ exp_len, ret, '1', 0, false, \ exp_subst, vt->wwid); \ free(exp_subst); \ } /** * test_vpd_str_TYP_LEN_WLEN() - test code for VPD 83, SCSI name string * @TYP: numeric value of STR_EUI, STR_NAA, STR_IQN above * @LEN: ID length the VPD page * @WLEN: WWID buffer size */ #define make_test_vpd_str(typ, len, wlen) \ static void test_vpd_str_ ## typ ## _ ## len ## _ ## wlen(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ int exp_len; \ int type = typ & STR_MASK; \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, \ 8, typ, len); \ exp_len = len - strlen(str_prefix[type]); \ if (typ & ZERO_LAST) \ exp_len--; \ if (exp_len >= wlen) \ exp_len = wlen - 1; \ 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_str_" #typ "_" #len "_" #wlen, \ exp_len, ret, byte0[type], 0, \ type != STR_IQN, \ 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) * @WLEN: WWID buffer size */ #define make_test_vpd_naa(naa, wlen) \ static void test_vpd_naa_ ## naa ## _ ## wlen(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ int len, exp_len; \ \ switch (naa) { \ case 2: \ case 3: \ case 5: \ len = 17; \ break; \ case 6: \ len = 33; \ break; \ } \ /* returned size is always uneven */ \ exp_len = wlen > len ? len : \ wlen % 2 == 0 ? wlen - 1 : wlen - 2; \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, \ 3, naa, 0); \ 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_naa_" #naa "_" #wlen, \ exp_len, ret, '3', '0' + naa, true, \ test_id, vt->wwid); \ } /** * test_cpd_naa_NAA_badlen_BAD() - test detection of bad length fields * @NAA: Network Name Authority (2, 3, 5, 16) * @BAD: Value for designator length field * @ERR: Expected error code */ #define make_test_vpd_naa_badlen(NAA, BAD, ERR) \ static void test_vpd_naa_##NAA##_badlen_##BAD(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, 3, NAA, 0); \ \ vt->vpdbuf[7] = BAD; \ wrap_will_return(WRAP_IOCTL, n); \ wrap_will_return(WRAP_IOCTL, vt->vpdbuf); \ ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, 40); \ assert_int_equal(-ret, -ERR); \ } /** * test_vpd_eui_LEN_WLEN() - test code for VPD 83, EUI64 * @LEN: EUI64 length (8, 12, or 16) * @WLEN: WWID buffer size * @SML: Use small VPD page size */ #define make_test_vpd_eui(len, wlen, sml) \ static void test_vpd_eui_ ## len ## _ ## wlen ## _ ## sml(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ /* returned size is always uneven */ \ int exp_len = wlen > 2 * len + 1 ? 2 * len + 1 : \ wlen % 2 == 0 ? wlen - 1 : wlen - 2; \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, \ 2, 0, len); \ if (sml) { \ /* overwrite the page size to DEFAULT_SGIO_LEN + 1 */ \ put_unaligned_be16(255, vt->vpdbuf + 2); \ /* this causes get_vpd_sgio to do a second ioctl */ \ wrap_will_return(WRAP_IOCTL, n); \ wrap_will_return(WRAP_IOCTL, vt->vpdbuf); \ } \ 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_eui_" #len "_" #wlen "_" #sml, \ exp_len, ret, '2', 0, true, \ test_id, vt->wwid); \ } /** * test_cpd_eui_LEN_badlen_BAD() - test detection of bad length fields * @NAA: correct length(8, 12, 16) * @BAD: value for designator length field * @ERR: expected error code */ #define make_test_vpd_eui_badlen(LEN, BAD, ERR) \ static void test_vpd_eui_badlen_##LEN##_##BAD(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, 2, 0, LEN); \ \ vt->vpdbuf[7] = BAD; \ wrap_will_return(WRAP_IOCTL, n); \ wrap_will_return(WRAP_IOCTL, vt->vpdbuf); \ ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, 40); \ assert_int_equal(ret, ERR); \ if (ERR >= 0) \ assert_correct_wwid("test_vpd_eui_badlen_"#LEN"_"#BAD, \ 2 * BAD + 1, ret, '2', 0, true, \ test_id, vt->wwid); \ } /** * test_vpd80_SIZE_LEN_WLEN() - test code for VPD 80 * @SIZE, @LEN: see create_vpd80() * @WLEN: WWID buffer size */ #define make_test_vpd80(size, len, wlen) \ static void test_vpd80_ ## size ## _ ## len ## _ ## wlen(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ int exp_len = len > 20 ? 20 : len; \ char *input = strdup(test_id); \ \ /* insert trailing whitespace after pos 20 */ \ memset(input + 20, ' ', sizeof(test_id) - 20); \ if (exp_len >= wlen) \ exp_len = wlen - 1; \ n = create_vpd80(vt->vpdbuf, sizeof(vt->vpdbuf), input, \ size, len); \ wrap_will_return(WRAP_IOCTL, n); \ wrap_will_return(WRAP_IOCTL, vt->vpdbuf); \ ret = get_vpd_sgio(10, 0x80, 0, vt->wwid, wlen); \ assert_correct_wwid("test_vpd80_" #size "_" #len "_" #wlen, \ exp_len, ret, 0, 0, false, \ input, vt->wwid); \ free(input); \ } /* VPD 80 */ /* Tests without trailing whitespace: 21 WWID bytes required */ make_test_vpd80(20, 20, 30); make_test_vpd80(20, 20, 21); make_test_vpd80(20, 20, 20); make_test_vpd80(20, 20, 10); /* Tests with 4 byte trailing whitespace: 21 WWID bytes required */ make_test_vpd80(24, 24, 30); make_test_vpd80(24, 24, 25); make_test_vpd80(24, 24, 24); make_test_vpd80(24, 24, 21); make_test_vpd80(24, 24, 20); /* Tests with 4 byte leading whitespace: 17 WWID bytes required */ make_test_vpd80(20, 16, 30); make_test_vpd80(20, 16, 17); make_test_vpd80(20, 16, 16); /* Tests with 4 byte leading whitespace: 21 WWID bytes required */ make_test_vpd80(24, 20, 21); make_test_vpd80(24, 20, 20); /* Tests with leading and trailing whitespace: 21 WWID bytes required */ make_test_vpd80(30, 24, 30); make_test_vpd80(30, 24, 21); make_test_vpd80(30, 24, 20); /* VPD 83, T10 vendor ID */ make_test_vpd_vnd(40, 40); make_test_vpd_vnd(40, 30); make_test_vpd_vnd(30, 20); make_test_vpd_vnd(29, 30); make_test_vpd_vnd(28, 30); make_test_vpd_vnd(27, 30); /* space at end */ make_test_vpd_vnd(26, 30); /* space at end */ make_test_vpd_vnd(25, 30); make_test_vpd_vnd(24, 30); /* space at end */ make_test_vpd_vnd(23, 30); make_test_vpd_vnd(24, 20); make_test_vpd_vnd(23, 20); make_test_vpd_vnd(22, 20); make_test_vpd_vnd(21, 20); make_test_vpd_vnd(20, 20); make_test_vpd_vnd(19, 20); make_test_vpd_vnd(20, 10); make_test_vpd_vnd(10, 10); /* EUI64 tests */ /* small vpd page test */ make_test_vpd_eui(8, 32, 1); make_test_vpd_eui(12, 32, 1); make_test_vpd_eui(16, 40, 1); /* 64bit, WWID size: 18 */ make_test_vpd_eui(8, 32, 0); make_test_vpd_eui(8, 18, 0); make_test_vpd_eui(8, 17, 0); make_test_vpd_eui(8, 16, 0); make_test_vpd_eui(8, 10, 0); make_test_vpd_eui_badlen(8, 8, 17); /* Invalid entry, length overflow */ make_test_vpd_eui_badlen(8, 12, -EOVERFLOW); make_test_vpd_eui_badlen(8, 9, -EOVERFLOW); /* invalid entry, no length overflow, but no full next entry */ make_test_vpd_eui_badlen(8, 7, -EINVAL); make_test_vpd_eui_badlen(8, 5, -EINVAL); /* invalid entry, length of next one readable but too long */ make_test_vpd_eui_badlen(8, 4, -EOVERFLOW); make_test_vpd_eui_badlen(8, 0, -EOVERFLOW); /* 96 bit, WWID size: 26 */ make_test_vpd_eui(12, 32, 0); make_test_vpd_eui(12, 26, 0); make_test_vpd_eui(12, 25, 0); make_test_vpd_eui(12, 20, 0); make_test_vpd_eui(12, 10, 0); make_test_vpd_eui_badlen(12, 12, 25); make_test_vpd_eui_badlen(12, 16, -EOVERFLOW); make_test_vpd_eui_badlen(12, 13, -EOVERFLOW); /* invalid entry, no length overflow, but no full next entry */ make_test_vpd_eui_badlen(12, 11, -EINVAL); make_test_vpd_eui_badlen(12, 9, -EINVAL); /* non-fatal - valid 8-byte descriptor */ make_test_vpd_eui_badlen(12, 8, 17); /* invalid entry, length of next one readable but too long */ make_test_vpd_eui_badlen(12, 7, -EOVERFLOW); make_test_vpd_eui_badlen(12, 0, -EOVERFLOW); /* 128 bit, WWID size: 34 */ make_test_vpd_eui(16, 40, 0); make_test_vpd_eui(16, 34, 0); make_test_vpd_eui(16, 33, 0); make_test_vpd_eui(16, 20, 0); make_test_vpd_eui_badlen(16, 16, 33); make_test_vpd_eui_badlen(16, 17, -EOVERFLOW); make_test_vpd_eui_badlen(16, 15, -EINVAL); make_test_vpd_eui_badlen(16, 13, -EINVAL); /* non-fatal - valid 12-byte descriptor */ make_test_vpd_eui_badlen(16, 12, 25); /* invalid entry, length of next one readable but too long */ make_test_vpd_eui_badlen(16, 11, -EOVERFLOW); /* non-fatal - valid 8-byte descriptor */ make_test_vpd_eui_badlen(16, 8, 17); /* invalid entry, length of next one readable but too long */ make_test_vpd_eui_badlen(16, 7, -EOVERFLOW); make_test_vpd_eui_badlen(16, 0, -EOVERFLOW); /* NAA IEEE registered extended (36), WWID size: 34 */ make_test_vpd_naa(6, 40); make_test_vpd_naa(6, 34); make_test_vpd_naa(6, 33); make_test_vpd_naa(6, 32); make_test_vpd_naa(6, 20); /* NAA IEEE registered extended with bad designator length */ make_test_vpd_naa_badlen(6, 16, 33); /* offset overflow */ make_test_vpd_naa_badlen(6, 17, -EOVERFLOW); /* invalid entry, no length overflow, but no full next entry */ make_test_vpd_naa_badlen(6, 15, -EINVAL); /* invalid entry, length of next one readable but too long */ make_test_vpd_naa_badlen(6, 8, -EOVERFLOW); make_test_vpd_naa_badlen(6, 0, -EOVERFLOW); /* NAA IEEE registered (35), WWID size: 18 */ make_test_vpd_naa(5, 20); make_test_vpd_naa(5, 18); make_test_vpd_naa(5, 17); make_test_vpd_naa(5, 16); /* NAA IEEE registered with bad designator length */ make_test_vpd_naa_badlen(5, 8, 17); /* offset overflow */ make_test_vpd_naa_badlen(5, 16, -EOVERFLOW); make_test_vpd_naa_badlen(5, 9, -EOVERFLOW); /* invalid entry, no length overflow, but no full next entry */ make_test_vpd_naa_badlen(5, 7, -EINVAL); /* invalid entry, length of next one readable but too long */ make_test_vpd_naa_badlen(5, 4, -EOVERFLOW); make_test_vpd_naa_badlen(5, 0, -EOVERFLOW); /* NAA local (33), WWID size: 18 */ make_test_vpd_naa(3, 20); make_test_vpd_naa(3, 18); make_test_vpd_naa(3, 17); make_test_vpd_naa(3, 16); /* NAA IEEE extended (32), WWID size: 18 */ make_test_vpd_naa(2, 20); make_test_vpd_naa(2, 18); 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) /* 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) /* 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) /* 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) /* 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) /* 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) static int test_vpd(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_vpd80_20_20_30), cmocka_unit_test(test_vpd80_20_20_21), cmocka_unit_test(test_vpd80_20_20_20), cmocka_unit_test(test_vpd80_20_20_10), cmocka_unit_test(test_vpd80_24_24_30), cmocka_unit_test(test_vpd80_24_24_25), cmocka_unit_test(test_vpd80_24_24_24), cmocka_unit_test(test_vpd80_24_24_21), cmocka_unit_test(test_vpd80_24_24_20), cmocka_unit_test(test_vpd80_20_16_30), cmocka_unit_test(test_vpd80_20_16_17), cmocka_unit_test(test_vpd80_20_16_16), cmocka_unit_test(test_vpd80_24_20_21), cmocka_unit_test(test_vpd80_24_20_20), cmocka_unit_test(test_vpd80_30_24_30), cmocka_unit_test(test_vpd80_30_24_21), cmocka_unit_test(test_vpd80_30_24_20), cmocka_unit_test(test_vpd_vnd_40_40), cmocka_unit_test(test_vpd_vnd_40_30), cmocka_unit_test(test_vpd_vnd_30_20), cmocka_unit_test(test_vpd_vnd_29_30), cmocka_unit_test(test_vpd_vnd_28_30), cmocka_unit_test(test_vpd_vnd_27_30), cmocka_unit_test(test_vpd_vnd_26_30), cmocka_unit_test(test_vpd_vnd_25_30), cmocka_unit_test(test_vpd_vnd_24_30), cmocka_unit_test(test_vpd_vnd_23_30), cmocka_unit_test(test_vpd_vnd_24_20), cmocka_unit_test(test_vpd_vnd_23_20), cmocka_unit_test(test_vpd_vnd_22_20), cmocka_unit_test(test_vpd_vnd_21_20), cmocka_unit_test(test_vpd_vnd_20_20), cmocka_unit_test(test_vpd_vnd_19_20), cmocka_unit_test(test_vpd_vnd_20_10), cmocka_unit_test(test_vpd_vnd_10_10), cmocka_unit_test(test_vpd_eui_8_32_1), cmocka_unit_test(test_vpd_eui_12_32_1), cmocka_unit_test(test_vpd_eui_16_40_1), cmocka_unit_test(test_vpd_eui_8_32_0), cmocka_unit_test(test_vpd_eui_8_18_0), cmocka_unit_test(test_vpd_eui_8_17_0), cmocka_unit_test(test_vpd_eui_8_16_0), cmocka_unit_test(test_vpd_eui_8_10_0), cmocka_unit_test(test_vpd_eui_badlen_8_8), cmocka_unit_test(test_vpd_eui_badlen_8_12), cmocka_unit_test(test_vpd_eui_badlen_8_9), cmocka_unit_test(test_vpd_eui_badlen_8_7), cmocka_unit_test(test_vpd_eui_badlen_8_5), cmocka_unit_test(test_vpd_eui_badlen_8_4), cmocka_unit_test(test_vpd_eui_badlen_8_0), cmocka_unit_test(test_vpd_eui_12_32_0), cmocka_unit_test(test_vpd_eui_12_26_0), cmocka_unit_test(test_vpd_eui_12_25_0), cmocka_unit_test(test_vpd_eui_12_20_0), cmocka_unit_test(test_vpd_eui_12_10_0), cmocka_unit_test(test_vpd_eui_badlen_12_12), cmocka_unit_test(test_vpd_eui_badlen_12_16), cmocka_unit_test(test_vpd_eui_badlen_12_13), cmocka_unit_test(test_vpd_eui_badlen_12_11), cmocka_unit_test(test_vpd_eui_badlen_12_9), cmocka_unit_test(test_vpd_eui_badlen_12_8), cmocka_unit_test(test_vpd_eui_badlen_12_7), cmocka_unit_test(test_vpd_eui_badlen_12_0), cmocka_unit_test(test_vpd_eui_16_40_0), cmocka_unit_test(test_vpd_eui_16_34_0), cmocka_unit_test(test_vpd_eui_16_33_0), cmocka_unit_test(test_vpd_eui_16_20_0), cmocka_unit_test(test_vpd_eui_badlen_16_16), cmocka_unit_test(test_vpd_eui_badlen_16_17), cmocka_unit_test(test_vpd_eui_badlen_16_15), cmocka_unit_test(test_vpd_eui_badlen_16_13), cmocka_unit_test(test_vpd_eui_badlen_16_12), cmocka_unit_test(test_vpd_eui_badlen_16_11), cmocka_unit_test(test_vpd_eui_badlen_16_8), cmocka_unit_test(test_vpd_eui_badlen_16_7), cmocka_unit_test(test_vpd_eui_badlen_16_0), cmocka_unit_test(test_vpd_naa_6_40), cmocka_unit_test(test_vpd_naa_6_34), cmocka_unit_test(test_vpd_naa_6_33), cmocka_unit_test(test_vpd_naa_6_32), cmocka_unit_test(test_vpd_naa_6_20), cmocka_unit_test(test_vpd_naa_6_badlen_16), cmocka_unit_test(test_vpd_naa_6_badlen_15), cmocka_unit_test(test_vpd_naa_6_badlen_8), cmocka_unit_test(test_vpd_naa_6_badlen_17), cmocka_unit_test(test_vpd_naa_6_badlen_0), cmocka_unit_test(test_vpd_naa_5_20), cmocka_unit_test(test_vpd_naa_5_18), cmocka_unit_test(test_vpd_naa_5_17), cmocka_unit_test(test_vpd_naa_5_16), cmocka_unit_test(test_vpd_naa_5_badlen_8), cmocka_unit_test(test_vpd_naa_5_badlen_7), cmocka_unit_test(test_vpd_naa_5_badlen_4), cmocka_unit_test(test_vpd_naa_5_badlen_16), cmocka_unit_test(test_vpd_naa_5_badlen_9), cmocka_unit_test(test_vpd_naa_5_badlen_0), cmocka_unit_test(test_vpd_naa_3_20), cmocka_unit_test(test_vpd_naa_3_18), cmocka_unit_test(test_vpd_naa_3_17), cmocka_unit_test(test_vpd_naa_3_16), cmocka_unit_test(test_vpd_naa_2_20), cmocka_unit_test(test_vpd_naa_2_18), cmocka_unit_test(test_vpd_naa_2_17), cmocka_unit_test(test_vpd_naa_2_16), cmocka_unit_test(test_vpd_str_0_20_18), cmocka_unit_test(test_vpd_str_0_20_17), cmocka_unit_test(test_vpd_str_0_20_16), cmocka_unit_test(test_vpd_str_0_20_15), cmocka_unit_test(test_vpd_str_16_20_18), cmocka_unit_test(test_vpd_str_16_20_17), cmocka_unit_test(test_vpd_str_16_20_16), cmocka_unit_test(test_vpd_str_16_20_15), cmocka_unit_test(test_vpd_str_1_20_18), cmocka_unit_test(test_vpd_str_1_20_17), cmocka_unit_test(test_vpd_str_1_20_16), cmocka_unit_test(test_vpd_str_1_20_15), cmocka_unit_test(test_vpd_str_17_20_18), cmocka_unit_test(test_vpd_str_17_20_17), cmocka_unit_test(test_vpd_str_17_20_16), cmocka_unit_test(test_vpd_str_17_20_15), cmocka_unit_test(test_vpd_str_2_20_18), cmocka_unit_test(test_vpd_str_2_20_17), cmocka_unit_test(test_vpd_str_2_20_16), cmocka_unit_test(test_vpd_str_2_20_15), cmocka_unit_test(test_vpd_str_18_20_18), 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), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; init_test_verbosity(-1); ret += test_vpd(); return ret; } multipath-tools-0.11.1/tests/wrap64.h000066400000000000000000000061101475246302400174020ustar00rootroot00000000000000#ifndef WRAP64_H_INCLUDED #define WRAP64_H_INCLUDED #include #include /* The following include is required for LIBAIO_REDIRECT */ #include #include "util.h" /* * Make cmocka work with _FILE_OFFSET_BITS == 64 * * If -D_FILE_OFFSET_BITS=64 is set, glibc headers replace some functions with * their 64bit equivalents. "open()" is replaced by "open64()", etc. cmocka * wrappers for these functions must have correct name __wrap_open() doesn't * work if the function called is not open() but open64(). Consequently, unit * tests using such wrappers will fail. * Use some CPP trickery to insert the correct name. The Makefile rule that * creates the .wrap file must parse the C preprocessor output to generate the * correct -Wl,wrap= option. */ /* Without this indirection, WRAP_FUNC(x) would be expanded to __wrap_WRAP_NAME(x) */ #define CONCAT2_(x, y) x ## y #define CONCAT2(x, y) CONCAT2_(x, y) #if defined(__GLIBC__) && _FILE_OFFSET_BITS == 64 #define WRAP_NAME(x) x ## 64 #else #define WRAP_NAME(x) x #endif #define WRAP_FUNC(x) CONCAT2(__wrap_, WRAP_NAME(x)) #define REAL_FUNC(x) CONCAT2(__real_, WRAP_NAME(x)) /* * With clang, glibc 2.39, and _FILE_OFFSET_BITS==64, * open() resolves to __open64_2(). */ #if defined(__GLIBC__) && __GLIBC_PREREQ(2, 39) && \ defined(__clang__) && __clang__ == 1 && \ defined(__fortify_use_clang) && __fortify_use_clang == 1 #define WRAP_OPEN_NAME __open64_2 #else #define WRAP_OPEN_NAME WRAP_NAME(open) #endif #define WRAP_OPEN CONCAT2(__wrap_, WRAP_OPEN_NAME) #define REAL_OPEN CONCAT2(__real_, WRAP_OPEN_NAME) /* * fcntl() needs special treatment; fcntl64() has been introduced in 2.28. * https://savannah.gnu.org/forum/forum.php?forum_id=9205 */ #if defined(__GLIBC__) && __GLIBC_PREREQ(2, 34) && __BITS_PER_LONG == 32 \ && defined(_TIME_BITS) && _TIME_BITS == 64 #define WRAP_FCNTL_NAME __fcntl_time64 #elif defined(__GLIBC__) && __GLIBC_PREREQ(2, 28) #define WRAP_FCNTL_NAME WRAP_NAME(fcntl) #else #define WRAP_FCNTL_NAME fcntl #endif #define WRAP_FCNTL CONCAT2(__wrap_, WRAP_FCNTL_NAME) #define REAL_FCNTL CONCAT2(__real_, WRAP_FCNTL_NAME) /* * glibc 2.37 uses __ioctl_time64 for ioctl */ #if defined(__GLIBC__) && __GLIBC_PREREQ(2, 34) && __BITS_PER_LONG == 32 \ && defined(_TIME_BITS) && _TIME_BITS == 64 #define WRAP_IOCTL_NAME __ioctl_time64 #else #define WRAP_IOCTL_NAME ioctl #endif #define WRAP_IOCTL CONCAT2(__wrap_, WRAP_IOCTL_NAME) #define REAL_IOCTL CONCAT2(__real_, WRAP_IOCTL_NAME) #if defined(__GLIBC__) && defined(LIBAIO_REDIRECT) && __BITS_PER_LONG == 32 \ && defined(_TIME_BITS) && _TIME_BITS == 64 #define WRAP_IO_GETEVENTS_NAME io_getevents_time64 #else #define WRAP_IO_GETEVENTS_NAME io_getevents #endif #define WRAP_IO_GETEVENTS CONCAT2(__wrap_, WRAP_IO_GETEVENTS_NAME) #define REAL_IO_GETEVENTS CONCAT2(__real_, WRAP_IO_GETEVENTS_NAME) /* * will_return() is itself a macro that uses CPP "stringizing". We need a * macro indirection to make sure the *value* of WRAP_FUNC() is stringized * (see https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html). */ #define wrap_will_return(x, y) will_return(x, y) #endif multipath-tools-0.11.1/third-party/000077500000000000000000000000001475246302400172155ustar00rootroot00000000000000multipath-tools-0.11.1/third-party/valgrind/000077500000000000000000000000001475246302400210235ustar00rootroot00000000000000multipath-tools-0.11.1/third-party/valgrind/drd.h000066400000000000000000000547061475246302400217610ustar00rootroot00000000000000/* ---------------------------------------------------------------- Notice that the following BSD-style license applies to this one file (drd.h) only. The rest of Valgrind is licensed under the terms of the GNU General Public License, version 2, unless otherwise indicated. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- This file is part of DRD, a Valgrind tool for verification of multithreaded programs. Copyright (C) 2006-2017 Bart Van Assche . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------- Notice that the above BSD-style license applies to this one file (drd.h) only. The entire rest of Valgrind is licensed under the terms of the GNU General Public License, version 2. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- */ #ifndef __VALGRIND_DRD_H #define __VALGRIND_DRD_H #include "valgrind.h" /** Obtain the thread ID assigned by Valgrind's core. */ #define DRD_GET_VALGRIND_THREADID \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__DRD_GET_VALGRIND_THREAD_ID, \ 0, 0, 0, 0, 0) /** Obtain the thread ID assigned by DRD. */ #define DRD_GET_DRD_THREADID \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__DRD_GET_DRD_THREAD_ID, \ 0, 0, 0, 0, 0) /** Tell DRD not to complain about data races for the specified variable. */ #define DRD_IGNORE_VAR(x) ANNOTATE_BENIGN_RACE_SIZED(&(x), sizeof(x), "") /** Tell DRD to no longer ignore data races for the specified variable. */ #define DRD_STOP_IGNORING_VAR(x) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_FINISH_SUPPRESSION, \ &(x), sizeof(x), 0, 0, 0) /** * Tell DRD to trace all memory accesses for the specified variable * until the memory that was allocated for the variable is freed. */ #define DRD_TRACE_VAR(x) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_START_TRACE_ADDR, \ &(x), sizeof(x), 0, 0, 0) /** * Tell DRD to stop tracing memory accesses for the specified variable. */ #define DRD_STOP_TRACING_VAR(x) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_STOP_TRACE_ADDR, \ &(x), sizeof(x), 0, 0, 0) /** * @defgroup RaceDetectionAnnotations Data race detection annotations. * * @see See also the source file producer-consumer. */ #define ANNOTATE_PCQ_CREATE(pcq) do { } while(0) /** Tell DRD that a FIFO queue has been destroyed. */ #define ANNOTATE_PCQ_DESTROY(pcq) do { } while(0) /** * Tell DRD that an element has been added to the FIFO queue at address pcq. */ #define ANNOTATE_PCQ_PUT(pcq) do { } while(0) /** * Tell DRD that an element has been removed from the FIFO queue at address pcq, * and that DRD should insert a happens-before relationship between the memory * accesses that occurred before the corresponding ANNOTATE_PCQ_PUT(pcq) * annotation and the memory accesses after this annotation. Correspondence * between PUT and GET annotations happens in FIFO order. Since locking * of the queue is needed anyway to add elements to or to remove elements from * the queue, for DRD all four FIFO annotations are defined as no-ops. */ #define ANNOTATE_PCQ_GET(pcq) do { } while(0) /** * Tell DRD that data races at the specified address are expected and must not * be reported. */ #define ANNOTATE_BENIGN_RACE(addr, descr) \ ANNOTATE_BENIGN_RACE_SIZED(addr, sizeof(*addr), descr) /* Same as ANNOTATE_BENIGN_RACE(addr, descr), but applies to the memory range [addr, addr + size). */ #define ANNOTATE_BENIGN_RACE_SIZED(addr, size, descr) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_START_SUPPRESSION, \ addr, size, 0, 0, 0) /** Tell DRD to ignore all reads performed by the current thread. */ #define ANNOTATE_IGNORE_READS_BEGIN() \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_RECORD_LOADS, \ 0, 0, 0, 0, 0); /** Tell DRD to no longer ignore the reads performed by the current thread. */ #define ANNOTATE_IGNORE_READS_END() \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_RECORD_LOADS, \ 1, 0, 0, 0, 0); /** Tell DRD to ignore all writes performed by the current thread. */ #define ANNOTATE_IGNORE_WRITES_BEGIN() \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_RECORD_STORES, \ 0, 0, 0, 0, 0) /** Tell DRD to no longer ignore the writes performed by the current thread. */ #define ANNOTATE_IGNORE_WRITES_END() \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_RECORD_STORES, \ 1, 0, 0, 0, 0) /** Tell DRD to ignore all memory accesses performed by the current thread. */ #define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \ do { ANNOTATE_IGNORE_READS_BEGIN(); ANNOTATE_IGNORE_WRITES_BEGIN(); } while(0) /** * Tell DRD to no longer ignore the memory accesses performed by the current * thread. */ #define ANNOTATE_IGNORE_READS_AND_WRITES_END() \ do { ANNOTATE_IGNORE_READS_END(); ANNOTATE_IGNORE_WRITES_END(); } while(0) /** * Tell DRD that size bytes starting at addr has been allocated by a custom * memory allocator. */ #define ANNOTATE_NEW_MEMORY(addr, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_CLEAN_MEMORY, \ addr, size, 0, 0, 0) /** Ask DRD to report every access to the specified address. */ #define ANNOTATE_TRACE_MEMORY(addr) DRD_TRACE_VAR(*(char*)(addr)) /** * Tell DRD to assign the specified name to the current thread. This name will * be used in error messages printed by DRD. */ #define ANNOTATE_THREAD_NAME(name) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DRD_SET_THREAD_NAME, \ name, 0, 0, 0, 0) /*@}*/ /* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! This enum comprises an ABI exported by Valgrind to programs which use client requests. DO NOT CHANGE THE ORDER OF THESE ENTRIES, NOR DELETE ANY -- add new ones at the end. */ enum { /* Ask the DRD tool to discard all information about memory accesses */ /* and client objects for the specified range. This client request is */ /* binary compatible with the similarly named Helgrind client request. */ VG_USERREQ__DRD_CLEAN_MEMORY = VG_USERREQ_TOOL_BASE('H','G'), /* args: Addr, SizeT. */ /* Ask the DRD tool the thread ID assigned by Valgrind. */ VG_USERREQ__DRD_GET_VALGRIND_THREAD_ID = VG_USERREQ_TOOL_BASE('D','R'), /* args: none. */ /* Ask the DRD tool the thread ID assigned by DRD. */ VG_USERREQ__DRD_GET_DRD_THREAD_ID, /* args: none. */ /* To tell the DRD tool to suppress data race detection on the */ /* specified address range. */ VG_USERREQ__DRD_START_SUPPRESSION, /* args: start address, size in bytes */ /* To tell the DRD tool no longer to suppress data race detection on */ /* the specified address range. */ VG_USERREQ__DRD_FINISH_SUPPRESSION, /* args: start address, size in bytes */ /* To ask the DRD tool to trace all accesses to the specified range. */ VG_USERREQ__DRD_START_TRACE_ADDR, /* args: Addr, SizeT. */ /* To ask the DRD tool to stop tracing accesses to the specified range. */ VG_USERREQ__DRD_STOP_TRACE_ADDR, /* args: Addr, SizeT. */ /* Tell DRD whether or not to record memory loads in the calling thread. */ VG_USERREQ__DRD_RECORD_LOADS, /* args: Bool. */ /* Tell DRD whether or not to record memory stores in the calling thread. */ VG_USERREQ__DRD_RECORD_STORES, /* args: Bool. */ /* Set the name of the thread that performs this client request. */ VG_USERREQ__DRD_SET_THREAD_NAME, /* args: null-terminated character string. */ /* Tell DRD that a DRD annotation has not yet been implemented. */ VG_USERREQ__DRD_ANNOTATION_UNIMP, /* args: char*. */ /* Tell DRD that a user-defined semaphore synchronization object * is about to be created. */ VG_USERREQ__DRD_ANNOTATE_SEM_INIT_PRE, /* args: Addr, UInt value. */ /* Tell DRD that a user-defined semaphore synchronization object * has been destroyed. */ VG_USERREQ__DRD_ANNOTATE_SEM_DESTROY_POST, /* args: Addr. */ /* Tell DRD that a user-defined semaphore synchronization * object is going to be acquired (semaphore wait). */ VG_USERREQ__DRD_ANNOTATE_SEM_WAIT_PRE, /* args: Addr. */ /* Tell DRD that a user-defined semaphore synchronization * object has been acquired (semaphore wait). */ VG_USERREQ__DRD_ANNOTATE_SEM_WAIT_POST, /* args: Addr. */ /* Tell DRD that a user-defined semaphore synchronization * object is about to be released (semaphore post). */ VG_USERREQ__DRD_ANNOTATE_SEM_POST_PRE, /* args: Addr. */ /* Tell DRD to ignore the inter-thread ordering introduced by a mutex. */ VG_USERREQ__DRD_IGNORE_MUTEX_ORDERING, /* args: Addr. */ /* Tell DRD that a user-defined reader-writer synchronization object * has been created. */ VG_USERREQ__DRD_ANNOTATE_RWLOCK_CREATE = VG_USERREQ_TOOL_BASE('H','G') + 256 + 14, /* args: Addr. */ /* Tell DRD that a user-defined reader-writer synchronization object * is about to be destroyed. */ VG_USERREQ__DRD_ANNOTATE_RWLOCK_DESTROY = VG_USERREQ_TOOL_BASE('H','G') + 256 + 15, /* args: Addr. */ /* Tell DRD that a lock on a user-defined reader-writer synchronization * object has been acquired. */ VG_USERREQ__DRD_ANNOTATE_RWLOCK_ACQUIRED = VG_USERREQ_TOOL_BASE('H','G') + 256 + 17, /* args: Addr, Int is_rw. */ /* Tell DRD that a lock on a user-defined reader-writer synchronization * object is about to be released. */ VG_USERREQ__DRD_ANNOTATE_RWLOCK_RELEASED = VG_USERREQ_TOOL_BASE('H','G') + 256 + 18, /* args: Addr, Int is_rw. */ /* Tell DRD that a Helgrind annotation has not yet been implemented. */ VG_USERREQ__HELGRIND_ANNOTATION_UNIMP = VG_USERREQ_TOOL_BASE('H','G') + 256 + 32, /* args: char*. */ /* Tell DRD to insert a happens-before annotation. */ VG_USERREQ__DRD_ANNOTATE_HAPPENS_BEFORE = VG_USERREQ_TOOL_BASE('H','G') + 256 + 33, /* args: Addr. */ /* Tell DRD to insert a happens-after annotation. */ VG_USERREQ__DRD_ANNOTATE_HAPPENS_AFTER = VG_USERREQ_TOOL_BASE('H','G') + 256 + 34, /* args: Addr. */ }; /** * @addtogroup RaceDetectionAnnotations */ /*@{*/ #ifdef __cplusplus /* ANNOTATE_UNPROTECTED_READ is the preferred way to annotate racy reads. Instead of doing ANNOTATE_IGNORE_READS_BEGIN(); ... = x; ANNOTATE_IGNORE_READS_END(); one can use ... = ANNOTATE_UNPROTECTED_READ(x); */ template inline T ANNOTATE_UNPROTECTED_READ(const volatile T& x) { ANNOTATE_IGNORE_READS_BEGIN(); const T result = x; ANNOTATE_IGNORE_READS_END(); return result; } /* Apply ANNOTATE_BENIGN_RACE_SIZED to a static variable. */ #define ANNOTATE_BENIGN_RACE_STATIC(static_var, description) \ namespace { \ static class static_var##_annotator \ { \ public: \ static_var##_annotator() \ { \ ANNOTATE_BENIGN_RACE_SIZED(&static_var, sizeof(static_var), \ #static_var ": " description); \ } \ } the_##static_var##_annotator; \ } #endif /*@}*/ #endif /* __VALGRIND_DRD_H */ multipath-tools-0.11.1/third-party/valgrind/mpath-tools.supp000066400000000000000000000012341475246302400242030ustar00rootroot00000000000000{ glibc _dlerror_run leak: https://stackoverflow.com/questions/1542457/memory-leak-reported-by-valgrind-in-dlopen Memcheck:Leak match-leak-kinds: reachable fun:calloc fun:_dlerror_run fun:dlopen* } { systemd mempools are never freed: https://bugzilla.redhat.com/show_bug.cgi?id=1215670 Memcheck:Leak match-leak-kinds: reachable fun:malloc fun:mempool_alloc_tile fun:mempool_alloc0_tile fun:hashmap_base_new fun:hashmap_base_ensure_allocated } { libgcrypt library initialization Memcheck:Leak match-leak-kinds: reachable fun:malloc ... fun:_gcry_xmalloc ... fun:global_init.* ... fun:_dl_init } multipath-tools-0.11.1/third-party/valgrind/valgrind.h000066400000000000000000013752211475246302400230150ustar00rootroot00000000000000/* -*- c -*- ---------------------------------------------------------------- Notice that the following BSD-style license applies to this one file (valgrind.h) only. The rest of Valgrind is licensed under the terms of the GNU General Public License, version 2, unless otherwise indicated. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- This file is part of Valgrind, a dynamic binary instrumentation framework. Copyright (C) 2000-2017 Julian Seward. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------- Notice that the above BSD-style license applies to this one file (valgrind.h) only. The entire rest of Valgrind is licensed under the terms of the GNU General Public License, version 2. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- */ /* This file is for inclusion into client (your!) code. You can use these macros to manipulate and query Valgrind's execution inside your own programs. The resulting executables will still run without Valgrind, just a little bit more slowly than they otherwise would, but otherwise unchanged. When not running on valgrind, each client request consumes very few (eg. 7) instructions, so the resulting performance loss is negligible unless you plan to execute client requests millions of times per second. Nevertheless, if that is still a problem, you can compile with the NVALGRIND symbol defined (gcc -DNVALGRIND) so that client requests are not even compiled in. */ #ifndef __VALGRIND_H #define __VALGRIND_H /* ------------------------------------------------------------------ */ /* VERSION NUMBER OF VALGRIND */ /* ------------------------------------------------------------------ */ /* Specify Valgrind's version number, so that user code can conditionally compile based on our version number. Note that these were introduced at version 3.6 and so do not exist in version 3.5 or earlier. The recommended way to use them to check for "version X.Y or later" is (eg) #if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \ && (__VALGRIND_MAJOR__ > 3 \ || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6)) */ #define __VALGRIND_MAJOR__ 3 #define __VALGRIND_MINOR__ 14 #include /* Nb: this file might be included in a file compiled with -ansi. So we can't use C++ style "//" comments nor the "asm" keyword (instead use "__asm__"). */ /* Derive some tags indicating what the target platform is. Note that in this file we're using the compiler's CPP symbols for identifying architectures, which are different to the ones we use within the rest of Valgrind. Note, __powerpc__ is active for both 32 and 64-bit PPC, whereas __powerpc64__ is only active for the latter (on Linux, that is). Misc note: how to find out what's predefined in gcc by default: gcc -Wp,-dM somefile.c */ #undef PLAT_x86_darwin #undef PLAT_amd64_darwin #undef PLAT_x86_win32 #undef PLAT_amd64_win64 #undef PLAT_x86_linux #undef PLAT_amd64_linux #undef PLAT_ppc32_linux #undef PLAT_ppc64be_linux #undef PLAT_ppc64le_linux #undef PLAT_arm_linux #undef PLAT_arm64_linux #undef PLAT_s390x_linux #undef PLAT_mips32_linux #undef PLAT_mips64_linux #undef PLAT_x86_solaris #undef PLAT_amd64_solaris #if defined(__APPLE__) && defined(__i386__) # define PLAT_x86_darwin 1 #elif defined(__APPLE__) && defined(__x86_64__) # define PLAT_amd64_darwin 1 #elif (defined(__MINGW32__) && !defined(__MINGW64__)) \ || defined(__CYGWIN32__) \ || (defined(_WIN32) && defined(_M_IX86)) # define PLAT_x86_win32 1 #elif defined(__MINGW64__) \ || (defined(_WIN64) && defined(_M_X64)) # define PLAT_amd64_win64 1 #elif defined(__linux__) && defined(__i386__) # define PLAT_x86_linux 1 #elif defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) # define PLAT_amd64_linux 1 #elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) # define PLAT_ppc32_linux 1 #elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF != 2 /* Big Endian uses ELF version 1 */ # define PLAT_ppc64be_linux 1 #elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF == 2 /* Little Endian uses ELF version 2 */ # define PLAT_ppc64le_linux 1 #elif defined(__linux__) && defined(__arm__) && !defined(__aarch64__) # define PLAT_arm_linux 1 #elif defined(__linux__) && defined(__aarch64__) && !defined(__arm__) # define PLAT_arm64_linux 1 #elif defined(__linux__) && defined(__s390__) && defined(__s390x__) # define PLAT_s390x_linux 1 #elif defined(__linux__) && defined(__mips__) && (__mips==64) # define PLAT_mips64_linux 1 #elif defined(__linux__) && defined(__mips__) && (__mips!=64) # define PLAT_mips32_linux 1 #elif defined(__sun) && defined(__i386__) # define PLAT_x86_solaris 1 #elif defined(__sun) && defined(__x86_64__) # define PLAT_amd64_solaris 1 #else /* If we're not compiling for our target platform, don't generate any inline asms. */ # if !defined(NVALGRIND) # define NVALGRIND 1 # endif #endif /* ------------------------------------------------------------------ */ /* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */ /* in here of use to end-users -- skip to the next section. */ /* ------------------------------------------------------------------ */ /* * VALGRIND_DO_CLIENT_REQUEST(): a statement that invokes a Valgrind client * request. Accepts both pointers and integers as arguments. * * VALGRIND_DO_CLIENT_REQUEST_STMT(): a statement that invokes a Valgrind * client request that does not return a value. * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind * client request and whose value equals the client request result. Accepts * both pointers and integers as arguments. Note that such calls are not * necessarily pure functions -- they may have side effects. */ #define VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, \ _zzq_request, _zzq_arg1, _zzq_arg2, \ _zzq_arg3, _zzq_arg4, _zzq_arg5) \ do { (_zzq_rlval) = VALGRIND_DO_CLIENT_REQUEST_EXPR((_zzq_default), \ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) #define VALGRIND_DO_CLIENT_REQUEST_STMT(_zzq_request, _zzq_arg1, \ _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) #if defined(NVALGRIND) /* Define NVALGRIND to completely remove the Valgrind magic sequence from the compiled code (analogous to NDEBUG's effects on assert()) */ #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ (_zzq_default) #else /* ! NVALGRIND */ /* The following defines the magic code sequences which the JITter spots and handles magically. Don't look too closely at them as they will rot your brain. The assembly code sequences for all architectures is in this one file. This is because this file must be stand-alone, and we don't want to have multiple files. For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default value gets put in the return slot, so that everything works when this is executed not under Valgrind. Args are passed in a memory block, and so there's no intrinsic limit to the number that could be passed, but it's currently five. The macro args are: _zzq_rlval result lvalue _zzq_default default value (result returned when running on real CPU) _zzq_request request code _zzq_arg1..5 request params The other two macros are used to support function wrapping, and are a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the guest's NRADDR pseudo-register and whatever other information is needed to safely run the call original from the wrapper: on ppc64-linux, the R2 value at the divert point is also needed. This information is abstracted into a user-visible type, OrigFn. VALGRIND_CALL_NOREDIR_* behaves the same as the following on the guest, but guarantees that the branch instruction will not be redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64: branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a complete inline asm, since it needs to be combined with more magic inline asm stuff to be useful. */ /* ----------------- x86-{linux,darwin,solaris} ---------------- */ #if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ || (defined(PLAT_x86_win32) && defined(__GNUC__)) \ || defined(PLAT_x86_solaris) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "roll $3, %%edi ; roll $13, %%edi\n\t" \ "roll $29, %%edi ; roll $19, %%edi\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %EDX = client_request ( %EAX ) */ \ "xchgl %%ebx,%%ebx" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %EAX = guest_NRADDR */ \ "xchgl %%ecx,%%ecx" \ : "=a" (__addr) \ : \ : "cc", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_EAX \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%EAX */ \ "xchgl %%edx,%%edx\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "xchgl %%edi,%%edi\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__) || PLAT_x86_solaris */ /* ------------------------- x86-Win32 ------------------------- */ #if defined(PLAT_x86_win32) && !defined(__GNUC__) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #if defined(_MSC_VER) #define __SPECIAL_INSTRUCTION_PREAMBLE \ __asm rol edi, 3 __asm rol edi, 13 \ __asm rol edi, 29 __asm rol edi, 19 #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ valgrind_do_client_request_expr((uintptr_t)(_zzq_default), \ (uintptr_t)(_zzq_request), (uintptr_t)(_zzq_arg1), \ (uintptr_t)(_zzq_arg2), (uintptr_t)(_zzq_arg3), \ (uintptr_t)(_zzq_arg4), (uintptr_t)(_zzq_arg5)) static __inline uintptr_t valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request, uintptr_t _zzq_arg1, uintptr_t _zzq_arg2, uintptr_t _zzq_arg3, uintptr_t _zzq_arg4, uintptr_t _zzq_arg5) { volatile uintptr_t _zzq_args[6]; volatile unsigned int _zzq_result; _zzq_args[0] = (uintptr_t)(_zzq_request); _zzq_args[1] = (uintptr_t)(_zzq_arg1); _zzq_args[2] = (uintptr_t)(_zzq_arg2); _zzq_args[3] = (uintptr_t)(_zzq_arg3); _zzq_args[4] = (uintptr_t)(_zzq_arg4); _zzq_args[5] = (uintptr_t)(_zzq_arg5); __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default __SPECIAL_INSTRUCTION_PREAMBLE /* %EDX = client_request ( %EAX ) */ __asm xchg ebx,ebx __asm mov _zzq_result, edx } return _zzq_result; } #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ /* %EAX = guest_NRADDR */ \ __asm xchg ecx,ecx \ __asm mov __addr, eax \ } \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_EAX ERROR #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ __asm xchg edi,edi \ } \ } while (0) #else #error Unsupported compiler. #endif #endif /* PLAT_x86_win32 */ /* ----------------- amd64-{linux,darwin,solaris} --------------- */ #if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ || defined(PLAT_amd64_solaris) \ || (defined(PLAT_amd64_win64) && defined(__GNUC__)) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \ "rolq $61, %%rdi ; rolq $51, %%rdi\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %RDX = client_request ( %RAX ) */ \ "xchgq %%rbx,%%rbx" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %RAX = guest_NRADDR */ \ "xchgq %%rcx,%%rcx" \ : "=a" (__addr) \ : \ : "cc", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_RAX \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%RAX */ \ "xchgq %%rdx,%%rdx\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "xchgq %%rdi,%%rdi\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ /* ------------------------- amd64-Win64 ------------------------- */ #if defined(PLAT_amd64_win64) && !defined(__GNUC__) #error Unsupported compiler. #endif /* PLAT_amd64_win64 */ /* ------------------------ ppc32-linux ------------------------ */ #if defined(PLAT_ppc32_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rlwinm 0,0,3,0,31 ; rlwinm 0,0,13,0,31\n\t" \ "rlwinm 0,0,29,0,31 ; rlwinm 0,0,19,0,31\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned int _zzq_args[6]; \ unsigned int _zzq_result; \ unsigned int* _zzq_ptr; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R11 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc32_linux */ /* ------------------------ ppc64-linux ------------------------ */ #if defined(PLAT_ppc64be_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ unsigned long int r2; /* what tocptr do we need? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ "rotldi 0,0,61 ; rotldi 0,0,51\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned long int _zzq_args[6]; \ unsigned long int _zzq_result; \ unsigned long int* _zzq_ptr; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR_GPR2 */ \ "or 4,4,4\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->r2 = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R11 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc64be_linux */ #if defined(PLAT_ppc64le_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ unsigned long int r2; /* what tocptr do we need? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ "rotldi 0,0,61 ; rotldi 0,0,51\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned long int _zzq_args[6]; \ unsigned long int _zzq_result; \ unsigned long int* _zzq_ptr; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR_GPR2 */ \ "or 4,4,4\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->r2 = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R12 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc64le_linux */ /* ------------------------- arm-linux ------------------------- */ #if defined(PLAT_arm_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \ "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile("mov r3, %1\n\t" /*default*/ \ "mov r4, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* R3 = client_request ( R4 ) */ \ "orr r10, r10, r10\n\t" \ "mov %0, r3" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "cc","memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* R3 = guest_NRADDR */ \ "orr r11, r11, r11\n\t" \ "mov %0, r3" \ : "=r" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R4 */ \ "orr r12, r12, r12\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "orr r9, r9, r9\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_arm_linux */ /* ------------------------ arm64-linux ------------------------- */ #if defined(PLAT_arm64_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "ror x12, x12, #3 ; ror x12, x12, #13 \n\t" \ "ror x12, x12, #51 ; ror x12, x12, #61 \n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile("mov x3, %1\n\t" /*default*/ \ "mov x4, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* X3 = client_request ( X4 ) */ \ "orr x10, x10, x10\n\t" \ "mov %0, x3" /*result*/ \ : "=r" (_zzq_result) \ : "r" ((unsigned long int)(_zzq_default)), \ "r" (&_zzq_args[0]) \ : "cc","memory", "x3", "x4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* X3 = guest_NRADDR */ \ "orr x11, x11, x11\n\t" \ "mov %0, x3" \ : "=r" (__addr) \ : \ : "cc", "memory", "x3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir X8 */ \ "orr x12, x12, x12\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "orr x9, x9, x9\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_arm64_linux */ /* ------------------------ s390x-linux ------------------------ */ #if defined(PLAT_s390x_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; /* __SPECIAL_INSTRUCTION_PREAMBLE will be used to identify Valgrind specific * code. This detection is implemented in platform specific toIR.c * (e.g. VEX/priv/guest_s390_decoder.c). */ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "lr 15,15\n\t" \ "lr 1,1\n\t" \ "lr 2,2\n\t" \ "lr 3,3\n\t" #define __CLIENT_REQUEST_CODE "lr 2,2\n\t" #define __GET_NR_CONTEXT_CODE "lr 3,3\n\t" #define __CALL_NO_REDIR_CODE "lr 4,4\n\t" #define __VEX_INJECT_IR_CODE "lr 5,5\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile(/* r2 = args */ \ "lgr 2,%1\n\t" \ /* r3 = default */ \ "lgr 3,%2\n\t" \ __SPECIAL_INSTRUCTION_PREAMBLE \ __CLIENT_REQUEST_CODE \ /* results = r3 */ \ "lgr %0, 3\n\t" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "2", "3", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ __GET_NR_CONTEXT_CODE \ "lgr %0, 3\n\t" \ : "=a" (__addr) \ : \ : "cc", "3", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_R1 \ __SPECIAL_INSTRUCTION_PREAMBLE \ __CALL_NO_REDIR_CODE #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ __VEX_INJECT_IR_CODE); \ } while (0) #endif /* PLAT_s390x_linux */ /* ------------------------- mips32-linux ---------------- */ #if defined(PLAT_mips32_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; /* .word 0x342 * .word 0x742 * .word 0xC2 * .word 0x4C2*/ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "srl $0, $0, 13\n\t" \ "srl $0, $0, 29\n\t" \ "srl $0, $0, 3\n\t" \ "srl $0, $0, 19\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile("move $11, %1\n\t" /*default*/ \ "move $12, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* T3 = client_request ( T4 ) */ \ "or $13, $13, $13\n\t" \ "move %0, $11\n\t" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "$11", "$12", "memory"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %t9 = guest_NRADDR */ \ "or $14, $14, $14\n\t" \ "move %0, $11" /*result*/ \ : "=r" (__addr) \ : \ : "$11" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_T9 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%t9 */ \ "or $15, $15, $15\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or $11, $11, $11\n\t" \ ); \ } while (0) #endif /* PLAT_mips32_linux */ /* ------------------------- mips64-linux ---------------- */ #if defined(PLAT_mips64_linux) typedef struct { unsigned long nraddr; /* where's the code? */ } OrigFn; /* dsll $0,$0, 3 * dsll $0,$0, 13 * dsll $0,$0, 29 * dsll $0,$0, 19*/ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "dsll $0,$0, 3 ; dsll $0,$0,13\n\t" \ "dsll $0,$0,29 ; dsll $0,$0,19\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile("move $11, %1\n\t" /*default*/ \ "move $12, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* $11 = client_request ( $12 ) */ \ "or $13, $13, $13\n\t" \ "move %0, $11\n\t" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "$11", "$12", "memory"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* $11 = guest_NRADDR */ \ "or $14, $14, $14\n\t" \ "move %0, $11" /*result*/ \ : "=r" (__addr) \ : \ : "$11"); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_T9 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir $25 */ \ "or $15, $15, $15\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or $11, $11, $11\n\t" \ ); \ } while (0) #endif /* PLAT_mips64_linux */ /* Insert assembly code for other platforms here... */ #endif /* NVALGRIND */ /* ------------------------------------------------------------------ */ /* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */ /* ugly. It's the least-worst tradeoff I can think of. */ /* ------------------------------------------------------------------ */ /* This section defines magic (a.k.a appalling-hack) macros for doing guaranteed-no-redirection macros, so as to get from function wrappers to the functions they are wrapping. The whole point is to construct standard call sequences, but to do the call itself with a special no-redirect call pseudo-instruction that the JIT understands and handles specially. This section is long and repetitious, and I can't see a way to make it shorter. The naming scheme is as follows: CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc} 'W' stands for "word" and 'v' for "void". Hence there are different macros for calling arity 0, 1, 2, 3, 4, etc, functions, and for each, the possibility of returning a word-typed result, or no result. */ /* Use these to write the name of your wrapper. NOTE: duplicates VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts the default behaviour equivalence class tag "0000" into the name. See pub_tool_redir.h for details -- normally you don't need to think about this, though. */ /* Use an extra level of macroisation so as to ensure the soname/fnname args are fully macro-expanded before pasting them together. */ #define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd #define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \ VG_CONCAT4(_vgw00000ZU_,soname,_,fnname) #define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \ VG_CONCAT4(_vgw00000ZZ_,soname,_,fnname) /* Use this macro from within a wrapper function to collect the context (address and possibly other info) of the original function. Once you have that you can then use it in one of the CALL_FN_ macros. The type of the argument _lval is OrigFn. */ #define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval) /* Also provide end-user facilities for function replacement, rather than wrapping. A replacement function differs from a wrapper in that it has no way to get hold of the original function being called, and hence no way to call onwards to it. In a replacement function, VALGRIND_GET_ORIG_FN always returns zero. */ #define I_REPLACE_SONAME_FNNAME_ZU(soname,fnname) \ VG_CONCAT4(_vgr00000ZU_,soname,_,fnname) #define I_REPLACE_SONAME_FNNAME_ZZ(soname,fnname) \ VG_CONCAT4(_vgr00000ZZ_,soname,_,fnname) /* Derivatives of the main macros below, for calling functions returning void. */ #define CALL_FN_v_v(fnptr) \ do { volatile unsigned long _junk; \ CALL_FN_W_v(_junk,fnptr); } while (0) #define CALL_FN_v_W(fnptr, arg1) \ do { volatile unsigned long _junk; \ CALL_FN_W_W(_junk,fnptr,arg1); } while (0) #define CALL_FN_v_WW(fnptr, arg1,arg2) \ do { volatile unsigned long _junk; \ CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0) #define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \ do { volatile unsigned long _junk; \ CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0) #define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \ do { volatile unsigned long _junk; \ CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0) #define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \ do { volatile unsigned long _junk; \ CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0) #define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \ do { volatile unsigned long _junk; \ CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0) #define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \ do { volatile unsigned long _junk; \ CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0) /* ----------------- x86-{linux,darwin,solaris} ---------------- */ #if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ || defined(PLAT_x86_solaris) /* These regs are trashed by the hidden call. No need to mention eax as gcc can already see that, plus causes gcc to bomb. */ #define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "movl %%esp,%%edi\n\t" \ "andl $0xfffffff0,%%esp\n\t" #define VALGRIND_RESTORE_STACK \ "movl %%edi,%%esp\n\t" /* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 44(%%eax)\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 48(%%eax)\n\t" \ "pushl 44(%%eax)\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_x86_linux || PLAT_x86_darwin || PLAT_x86_solaris */ /* ---------------- amd64-{linux,darwin,solaris} --------------- */ #if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ || defined(PLAT_amd64_solaris) /* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \ "rdi", "r8", "r9", "r10", "r11" /* This is all pretty complex. It's so as to make stack unwinding work reliably. See bug 243270. The basic problem is the sub and add of 128 of %rsp in all of the following macros. If gcc believes the CFA is in %rsp, then unwinding may fail, because what's at the CFA is not what gcc "expected" when it constructs the CFIs for the places where the macros are instantiated. But we can't just add a CFI annotation to increase the CFA offset by 128, to match the sub of 128 from %rsp, because we don't know whether gcc has chosen %rsp as the CFA at that point, or whether it has chosen some other register (eg, %rbp). In the latter case, adding a CFI annotation to change the CFA offset is simply wrong. So the solution is to get hold of the CFA using __builtin_dwarf_cfa(), put it in a known register, and add a CFI annotation to say what the register is. We choose %rbp for this (perhaps perversely), because: (1) %rbp is already subject to unwinding. If a new register was chosen then the unwinder would have to unwind it in all stack traces, which is expensive, and (2) %rbp is already subject to precise exception updates in the JIT. If a new register was chosen, we'd have to have precise exceptions for it too, which reduces performance of the generated code. However .. one extra complication. We can't just whack the result of __builtin_dwarf_cfa() into %rbp and then add %rbp to the list of trashed registers at the end of the inline assembly fragments; gcc won't allow %rbp to appear in that list. Hence instead we need to stash %rbp in %r15 for the duration of the asm, and say that %r15 is trashed instead. gcc seems happy to go with that. Oh .. and this all needs to be conditionalized so that it is unchanged from before this commit, when compiled with older gccs that don't support __builtin_dwarf_cfa. Furthermore, since this header file is freestanding, it has to be independent of config.h, and so the following conditionalization cannot depend on configure time checks. Although it's not clear from 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)', this expression excludes Darwin. .cfi directives in Darwin assembly appear to be completely different and I haven't investigated how they work. For even more entertainment value, note we have to use the completely undocumented __builtin_dwarf_cfa(), which appears to really compute the CFA, whereas __builtin_frame_address(0) claims to but actually doesn't. See https://bugs.kde.org/show_bug.cgi?id=243270#c47 */ #if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) # define __FRAME_POINTER \ ,"r"(__builtin_dwarf_cfa()) # define VALGRIND_CFI_PROLOGUE \ "movq %%rbp, %%r15\n\t" \ "movq %2, %%rbp\n\t" \ ".cfi_remember_state\n\t" \ ".cfi_def_cfa rbp, 0\n\t" # define VALGRIND_CFI_EPILOGUE \ "movq %%r15, %%rbp\n\t" \ ".cfi_restore_state\n\t" #else # define __FRAME_POINTER # define VALGRIND_CFI_PROLOGUE # define VALGRIND_CFI_EPILOGUE #endif /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "movq %%rsp,%%r14\n\t" \ "andq $0xfffffffffffffff0,%%rsp\n\t" #define VALGRIND_RESTORE_STACK \ "movq %%r14,%%rsp\n\t" /* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned long) == 8. */ /* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_ macros. In order not to trash the stack redzone, we need to drop %rsp by 128 before the hidden call, and restore afterwards. The nastiness is that it is only by luck that the stack still appears to be unwindable during the hidden call - since then the behaviour of any routine using this macro does not match what the CFI data says. Sigh. Why is this important? Imagine that a wrapper has a stack allocated local, and passes to the hidden call, a pointer to it. Because gcc does not know about the hidden call, it may allocate that local in the redzone. Unfortunately the hidden call may then trash it before it comes to use it. So we must step clear of the redzone, for the duration of the hidden call, to make it safe. Probably the same problem afflicts the other redzone-style ABIs too (ppc64-linux); but for those, the stack is self describing (none of this CFI nonsense) so at least messing with the stack pointer doesn't give a danger of non-unwindable stack. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 88(%%rax)\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 96(%%rax)\n\t" \ "pushq 88(%%rax)\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ /* ------------------------ ppc32-linux ------------------------ */ #if defined(PLAT_ppc32_linux) /* This is useful for finding out about the on-stack stuff: extern int f9 ( int,int,int,int,int,int,int,int,int ); extern int f10 ( int,int,int,int,int,int,int,int,int,int ); extern int f11 ( int,int,int,int,int,int,int,int,int,int,int ); extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int ); int g9 ( void ) { return f9(11,22,33,44,55,66,77,88,99); } int g10 ( void ) { return f10(11,22,33,44,55,66,77,88,99,110); } int g11 ( void ) { return f11(11,22,33,44,55,66,77,88,99,110,121); } int g12 ( void ) { return f12(11,22,33,44,55,66,77,88,99,110,121,132); } */ /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rlwinm 1,1,0,0,27\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc32-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-16\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-16\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-32\n\t" \ /* arg11 */ \ "lwz 3,44(11)\n\t" \ "stw 3,16(1)\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ _argvec[12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-32\n\t" \ /* arg12 */ \ "lwz 3,48(11)\n\t" \ "stw 3,20(1)\n\t" \ /* arg11 */ \ "lwz 3,44(11)\n\t" \ "stw 3,16(1)\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc32_linux */ /* ------------------------ ppc64-linux ------------------------ */ #if defined(PLAT_ppc64be_linux) /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rldicr 1,1,0,59\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+0]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+1]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+2]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+3]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+4]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+5]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+6]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+7]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+8]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+9]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+10]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+11]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg11 */ \ "ld 3,88(11)\n\t" \ "std 3,128(1)\n\t" \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+12]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ _argvec[2+12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg12 */ \ "ld 3,96(11)\n\t" \ "std 3,136(1)\n\t" \ /* arg11 */ \ "ld 3,88(11)\n\t" \ "std 3,128(1)\n\t" \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc64be_linux */ /* ------------------------- ppc64le-linux ----------------------- */ #if defined(PLAT_ppc64le_linux) /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rldicr 1,1,0,59\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+0]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+1]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+2]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+3]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+4]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+5]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+6]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+7]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+8]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+9]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+10]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+11]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg11 */ \ "ld 3,88(12)\n\t" \ "std 3,112(1)\n\t" \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+12]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ _argvec[2+12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg12 */ \ "ld 3,96(12)\n\t" \ "std 3,120(1)\n\t" \ /* arg11 */ \ "ld 3,88(12)\n\t" \ "std 3,112(1)\n\t" \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc64le_linux */ /* ------------------------- arm-linux ------------------------- */ #if defined(PLAT_arm_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4", "r12", "r14" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ /* This is a bit tricky. We store the original stack pointer in r10 as it is callee-saves. gcc doesn't allow the use of r11 for some reason. Also, we can't directly "bic" the stack pointer in thumb mode since r13 isn't an allowed register number in that context. So use r4 as a temporary, since that is about to get trashed anyway, just after each use of this macro. Side effect is we need to be very careful about any future changes, since VALGRIND_ALIGN_STACK simply assumes r4 is usable. */ #define VALGRIND_ALIGN_STACK \ "mov r10, sp\n\t" \ "mov r4, sp\n\t" \ "bic r4, r4, #7\n\t" \ "mov sp, r4\n\t" #define VALGRIND_RESTORE_STACK \ "mov sp, r10\n\t" /* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "push {r0} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "push {r0, r1} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "push {r0, r1, r2} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "push {r0, r1, r2, r3} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #40] \n\t" \ "push {r0} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #40] \n\t" \ "ldr r1, [%1, #44] \n\t" \ "push {r0, r1} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #40] \n\t" \ "ldr r1, [%1, #44] \n\t" \ "ldr r2, [%1, #48] \n\t" \ "push {r0, r1, r2} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_arm_linux */ /* ------------------------ arm64-linux ------------------------ */ #if defined(PLAT_arm64_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "x0", "x1", "x2", "x3","x4", "x5", "x6", "x7", "x8", "x9", \ "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", \ "x18", "x19", "x20", "x30", \ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", \ "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", \ "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", \ "v26", "v27", "v28", "v29", "v30", "v31" /* x21 is callee-saved, so we can use it to save and restore SP around the hidden call. */ #define VALGRIND_ALIGN_STACK \ "mov x21, sp\n\t" \ "bic sp, x21, #15\n\t" #define VALGRIND_RESTORE_STACK \ "mov sp, x21\n\t" /* These CALL_FN_ macros assume that on arm64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x20 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x20 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x30 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1, #88] \n\t" \ "str x8, [sp, #16] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11, \ arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x30 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1, #88] \n\t" \ "str x8, [sp, #16] \n\t" \ "ldr x8, [%1, #96] \n\t" \ "str x8, [sp, #24] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_arm64_linux */ /* ------------------------- s390x-linux ------------------------- */ #if defined(PLAT_s390x_linux) /* Similar workaround as amd64 (see above), but we use r11 as frame pointer and save the old r11 in r7. r11 might be used for argvec, therefore we copy argvec in r1 since r1 is clobbered after the call anyway. */ #if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) # define __FRAME_POINTER \ ,"d"(__builtin_dwarf_cfa()) # define VALGRIND_CFI_PROLOGUE \ ".cfi_remember_state\n\t" \ "lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \ "lgr 7,11\n\t" \ "lgr 11,%2\n\t" \ ".cfi_def_cfa r11, 0\n\t" # define VALGRIND_CFI_EPILOGUE \ "lgr 11, 7\n\t" \ ".cfi_restore_state\n\t" #else # define __FRAME_POINTER # define VALGRIND_CFI_PROLOGUE \ "lgr 1,%1\n\t" # define VALGRIND_CFI_EPILOGUE #endif /* Nb: On s390 the stack pointer is properly aligned *at all times* according to the s390 GCC maintainer. (The ABI specification is not precise in this regard.) Therefore, VALGRIND_ALIGN_STACK and VALGRIND_RESTORE_STACK are not defined here. */ /* These regs are trashed by the hidden call. Note that we overwrite r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the function a proper return address. All others are ABI defined call clobbers. */ #define __CALLER_SAVED_REGS "0","1","2","3","4","5","14", \ "f0","f1","f2","f3","f4","f5","f6","f7" /* Nb: Although r11 is modified in the asm snippets below (inside VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for two reasons: (1) r11 is restored in VALGRIND_CFI_EPILOGUE, so effectively it is not modified (2) GCC will complain that r11 cannot appear inside a clobber section, when compiled with -O -fno-omit-frame-pointer */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 1, 0(1)\n\t" /* target->r1 */ \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) /* The call abi has the arguments in r2-r6 and stack */ #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1, arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1, arg2, arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1, arg2, arg3, arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1, arg2, arg3, arg4, arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-168\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,168\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-176\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,176\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-184\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,184\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-192\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,192\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-200\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,200\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10, arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-208\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "mvc 200(8,15), 88(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,208\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10, arg11, arg12)\ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ _argvec[12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-216\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "mvc 200(8,15), 88(1)\n\t" \ "mvc 208(8,15), 96(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,216\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_s390x_linux */ /* ------------------------- mips32-linux ----------------------- */ #if defined(PLAT_mips32_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ "$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ "$25", "$31" /* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16\n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" /* arg1*/ \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 24\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 24 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 32\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "nop\n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 32 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 32\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 32 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 40\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 40 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 40\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 40 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 48\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 48 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 48\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 44(%1) \n\t" \ "sw $4, 40($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 48 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 56\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 44(%1) \n\t" \ "sw $4, 40($29) \n\t" \ "lw $4, 48(%1) \n\t" \ "sw $4, 44($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 56 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_mips32_linux */ /* ------------------------- mips64-linux ------------------------- */ #if defined(PLAT_mips64_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ "$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ "$25", "$31" /* These CALL_FN_ macros assume that on mips64-linux, sizeof(long long) == 8. */ #define MIPS64_LONG2REG_CAST(x) ((long long)(long)x) #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[1]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ __asm__ volatile( \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[2]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" /* arg1*/ \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[3]; \ volatile unsigned long long _res; \ _argvec[0] = _orig.nraddr; \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[4]; \ volatile unsigned long long _res; \ _argvec[0] = _orig.nraddr; \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[5]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[6]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[7]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[8]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[9]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[10]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ __asm__ volatile( \ "dsubu $29, $29, 8\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 8\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[11]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ __asm__ volatile( \ "dsubu $29, $29, 16\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 16\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[12]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ __asm__ volatile( \ "dsubu $29, $29, 24\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 88(%1)\n\t" \ "sd $4, 16($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 24\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[13]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ _argvec[12] = MIPS64_LONG2REG_CAST(arg12); \ __asm__ volatile( \ "dsubu $29, $29, 32\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 88(%1)\n\t" \ "sd $4, 16($29)\n\t" \ "ld $4, 96(%1)\n\t" \ "sd $4, 24($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 32\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #endif /* PLAT_mips64_linux */ /* ------------------------------------------------------------------ */ /* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */ /* */ /* ------------------------------------------------------------------ */ /* Some request codes. There are many more of these, but most are not exposed to end-user view. These are the public ones, all of the form 0x1000 + small_number. Core ones are in the range 0x00000000--0x0000ffff. The non-public ones start at 0x2000. */ /* These macros are used by tools -- they must be public, but don't embed them into other programs. */ #define VG_USERREQ_TOOL_BASE(a,b) \ ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16)) #define VG_IS_TOOL_USERREQ(a, b, v) \ (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000)) /* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! This enum comprises an ABI exported by Valgrind to programs which use client requests. DO NOT CHANGE THE NUMERIC VALUES OF THESE ENTRIES, NOR DELETE ANY -- add new ones at the end of the most relevant group. */ typedef enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001, VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002, /* These allow any function to be called from the simulated CPU but run on the real CPU. Nb: the first arg passed to the function is always the ThreadId of the running thread! So CLIENT_CALL0 actually requires a 1 arg function, etc. */ VG_USERREQ__CLIENT_CALL0 = 0x1101, VG_USERREQ__CLIENT_CALL1 = 0x1102, VG_USERREQ__CLIENT_CALL2 = 0x1103, VG_USERREQ__CLIENT_CALL3 = 0x1104, /* Can be useful in regression testing suites -- eg. can send Valgrind's output to /dev/null and still count errors. */ VG_USERREQ__COUNT_ERRORS = 0x1201, /* Allows the client program and/or gdbserver to execute a monitor command. */ VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202, /* These are useful and can be interpreted by any tool that tracks malloc() et al, by using vg_replace_malloc.c. */ VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301, VG_USERREQ__RESIZEINPLACE_BLOCK = 0x130b, VG_USERREQ__FREELIKE_BLOCK = 0x1302, /* Memory pool support. */ VG_USERREQ__CREATE_MEMPOOL = 0x1303, VG_USERREQ__DESTROY_MEMPOOL = 0x1304, VG_USERREQ__MEMPOOL_ALLOC = 0x1305, VG_USERREQ__MEMPOOL_FREE = 0x1306, VG_USERREQ__MEMPOOL_TRIM = 0x1307, VG_USERREQ__MOVE_MEMPOOL = 0x1308, VG_USERREQ__MEMPOOL_CHANGE = 0x1309, VG_USERREQ__MEMPOOL_EXISTS = 0x130a, /* Allow printfs to valgrind log. */ /* The first two pass the va_list argument by value, which assumes it is the same size as or smaller than a UWord, which generally isn't the case. Hence are deprecated. The second two pass the vargs by reference and so are immune to this problem. */ /* both :: char* fmt, va_list vargs (DEPRECATED) */ VG_USERREQ__PRINTF = 0x1401, VG_USERREQ__PRINTF_BACKTRACE = 0x1402, /* both :: char* fmt, va_list* vargs */ VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404, /* Stack support. */ VG_USERREQ__STACK_REGISTER = 0x1501, VG_USERREQ__STACK_DEREGISTER = 0x1502, VG_USERREQ__STACK_CHANGE = 0x1503, /* Wine support */ VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601, /* Querying of debug info. */ VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701, /* Disable/enable error reporting level. Takes a single Word arg which is the delta to this thread's error disablement indicator. Hence 1 disables or further disables errors, and -1 moves back towards enablement. Other values are not allowed. */ VG_USERREQ__CHANGE_ERR_DISABLEMENT = 0x1801, /* Some requests used for Valgrind internal, such as self-test or self-hosting. */ /* Initialise IR injection */ VG_USERREQ__VEX_INIT_FOR_IRI = 0x1901, /* Used by Inner Valgrind to inform Outer Valgrind where to find the list of inner guest threads */ VG_USERREQ__INNER_THREADS = 0x1902 } Vg_ClientRequest; #if !defined(__GNUC__) # define __extension__ /* */ #endif /* Returns the number of Valgrinds this code is running under. That is, 0 if running natively, 1 if running under Valgrind, 2 if running under Valgrind which is running under another Valgrind, etc. */ #define RUNNING_ON_VALGRIND \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \ VG_USERREQ__RUNNING_ON_VALGRIND, \ 0, 0, 0, 0, 0) \ /* Discard translation of code in the range [_qzz_addr .. _qzz_addr + _qzz_len - 1]. Useful if you are debugging a JITter or some such, since it provides a way to make sure valgrind will retranslate the invalidated area. Returns no value. */ #define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DISCARD_TRANSLATIONS, \ _qzz_addr, _qzz_len, 0, 0, 0) #define VALGRIND_INNER_THREADS(_qzz_addr) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__INNER_THREADS, \ _qzz_addr, 0, 0, 0, 0) /* These requests are for getting Valgrind itself to print something. Possibly with a backtrace. This is a really ugly hack. The return value is the number of characters printed, excluding the "**** " part at the start and the backtrace (if present). */ #if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) /* Modern GCC will optimize the static routine out if unused, and unused attribute will shut down warnings about it. */ static int VALGRIND_PRINTF(const char *format, ...) __attribute__((format(__printf__, 1, 2), __unused__)); #endif static int #if defined(_MSC_VER) __inline #endif VALGRIND_PRINTF(const char *format, ...) { #if defined(NVALGRIND) (void)format; return 0; #else /* NVALGRIND */ #if defined(_MSC_VER) || defined(__MINGW64__) uintptr_t _qzz_res; #else unsigned long _qzz_res; #endif va_list vargs; va_start(vargs, format); #if defined(_MSC_VER) || defined(__MINGW64__) _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_VALIST_BY_REF, (uintptr_t)format, (uintptr_t)&vargs, 0, 0, 0); #else _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_VALIST_BY_REF, (unsigned long)format, (unsigned long)&vargs, 0, 0, 0); #endif va_end(vargs); return (int)_qzz_res; #endif /* NVALGRIND */ } #if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...) __attribute__((format(__printf__, 1, 2), __unused__)); #endif static int #if defined(_MSC_VER) __inline #endif VALGRIND_PRINTF_BACKTRACE(const char *format, ...) { #if defined(NVALGRIND) (void)format; return 0; #else /* NVALGRIND */ #if defined(_MSC_VER) || defined(__MINGW64__) uintptr_t _qzz_res; #else unsigned long _qzz_res; #endif va_list vargs; va_start(vargs, format); #if defined(_MSC_VER) || defined(__MINGW64__) _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, (uintptr_t)format, (uintptr_t)&vargs, 0, 0, 0); #else _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, (unsigned long)format, (unsigned long)&vargs, 0, 0, 0); #endif va_end(vargs); return (int)_qzz_res; #endif /* NVALGRIND */ } /* These requests allow control to move from the simulated CPU to the real CPU, calling an arbitrary function. Note that the current ThreadId is inserted as the first argument. So this call: VALGRIND_NON_SIMD_CALL2(f, arg1, arg2) requires f to have this signature: Word f(Word tid, Word arg1, Word arg2) where "Word" is a word-sized type. Note that these client requests are not entirely reliable. For example, if you call a function with them that subsequently calls printf(), there's a high chance Valgrind will crash. Generally, your prospects of these working are made higher if the called function does not refer to any global variables, and does not refer to any libc or other functions (printf et al). Any kind of entanglement with libc or dynamic linking is likely to have a bad outcome, for tricky reasons which we've grappled with a lot in the past. */ #define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL0, \ _qyy_fn, \ 0, 0, 0, 0) #define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL1, \ _qyy_fn, \ _qyy_arg1, 0, 0, 0) #define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL2, \ _qyy_fn, \ _qyy_arg1, _qyy_arg2, 0, 0) #define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL3, \ _qyy_fn, \ _qyy_arg1, _qyy_arg2, \ _qyy_arg3, 0) /* Counts the number of errors that have been recorded by a tool. Nb: the tool must record the errors with VG_(maybe_record_error)() or VG_(unique_error)() for them to be counted. */ #define VALGRIND_COUNT_ERRORS \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR( \ 0 /* default return */, \ VG_USERREQ__COUNT_ERRORS, \ 0, 0, 0, 0, 0) /* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing when heap blocks are allocated in order to give accurate results. This happens automatically for the standard allocator functions such as malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete, delete[], etc. But if your program uses a custom allocator, this doesn't automatically happen, and Valgrind will not do as well. For example, if you allocate superblocks with mmap() and then allocates chunks of the superblocks, all Valgrind's observations will be at the mmap() level and it won't know that the chunks should be considered separate entities. In Memcheck's case, that means you probably won't get heap block overrun detection (because there won't be redzones marked as unaddressable) and you definitely won't get any leak detection. The following client requests allow a custom allocator to be annotated so that it can be handled accurately by Valgrind. VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated by a malloc()-like function. For Memcheck (an illustrative case), this does two things: - It records that the block has been allocated. This means any addresses within the block mentioned in error messages will be identified as belonging to the block. It also means that if the block isn't freed it will be detected by the leak checker. - It marks the block as being addressable and undefined (if 'is_zeroed' is not set), or addressable and defined (if 'is_zeroed' is set). This controls how accesses to the block by the program are handled. 'addr' is the start of the usable block (ie. after any redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator can apply redzones -- these are blocks of padding at the start and end of each block. Adding redzones is recommended as it makes it much more likely Valgrind will spot block overruns. `is_zeroed' indicates if the memory is zeroed (or filled with another predictable value), as is the case for calloc(). VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a heap block -- that will be used by the client program -- is allocated. It's best to put it at the outermost level of the allocator if possible; for example, if you have a function my_alloc() which calls internal_alloc(), and the client request is put inside internal_alloc(), stack traces relating to the heap block will contain entries for both my_alloc() and internal_alloc(), which is probably not what you want. For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out custom blocks from within a heap block, B, that has been allocated with malloc/calloc/new/etc, then block B will be *ignored* during leak-checking -- the custom blocks will take precedence. VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For Memcheck, it does two things: - It records that the block has been deallocated. This assumes that the block was annotated as having been allocated via VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. - It marks the block as being unaddressable. VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a heap block is deallocated. VALGRIND_RESIZEINPLACE_BLOCK informs a tool about reallocation. For Memcheck, it does four things: - It records that the size of a block has been changed. This assumes that the block was annotated as having been allocated via VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. - If the block shrunk, it marks the freed memory as being unaddressable. - If the block grew, it marks the new area as undefined and defines a red zone past the end of the new block. - The V-bits of the overlap between the old and the new block are preserved. VALGRIND_RESIZEINPLACE_BLOCK should be put after allocation of the new block and before deallocation of the old block. In many cases, these three client requests will not be enough to get your allocator working well with Memcheck. More specifically, if your allocator writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call will be necessary to mark the memory as addressable just before the zeroing occurs, otherwise you'll get a lot of invalid write errors. For example, you'll need to do this if your allocator recycles freed blocks, but it zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK). Alternatively, if your allocator reuses freed blocks for allocator-internal data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary. Really, what's happening is a blurring of the lines between the client program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the memory should be considered unaddressable to the client program, but the allocator knows more than the rest of the client program and so may be able to safely access it. Extra client requests are necessary for Valgrind to understand the distinction between the allocator and the rest of the program. Ignored if addr == 0. */ #define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \ addr, sizeB, rzB, is_zeroed, 0) /* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. Ignored if addr == 0. */ #define VALGRIND_RESIZEINPLACE_BLOCK(addr, oldSizeB, newSizeB, rzB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__RESIZEINPLACE_BLOCK, \ addr, oldSizeB, newSizeB, rzB, 0) /* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. Ignored if addr == 0. */ #define VALGRIND_FREELIKE_BLOCK(addr, rzB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__FREELIKE_BLOCK, \ addr, rzB, 0, 0, 0) /* Create a memory pool. */ #define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ pool, rzB, is_zeroed, 0, 0) /* Create a memory pool with some flags specifying extended behaviour. When flags is zero, the behaviour is identical to VALGRIND_CREATE_MEMPOOL. The flag VALGRIND_MEMPOOL_METAPOOL specifies that the pieces of memory associated with the pool using VALGRIND_MEMPOOL_ALLOC will be used by the application as superblocks to dole out MALLOC_LIKE blocks using VALGRIND_MALLOCLIKE_BLOCK. In other words, a meta pool is a "2 levels" pool : first level is the blocks described by VALGRIND_MEMPOOL_ALLOC. The second level blocks are described using VALGRIND_MALLOCLIKE_BLOCK. Note that the association between the pool and the second level blocks is implicit : second level blocks will be located inside first level blocks. It is necessary to use the VALGRIND_MEMPOOL_METAPOOL flag for such 2 levels pools, as otherwise valgrind will detect overlapping memory blocks, and will abort execution (e.g. during leak search). Such a meta pool can also be marked as an 'auto free' pool using the flag VALGRIND_MEMPOOL_AUTO_FREE, which must be OR-ed together with the VALGRIND_MEMPOOL_METAPOOL. For an 'auto free' pool, VALGRIND_MEMPOOL_FREE will automatically free the second level blocks that are contained inside the first level block freed with VALGRIND_MEMPOOL_FREE. In other words, calling VALGRIND_MEMPOOL_FREE will cause implicit calls to VALGRIND_FREELIKE_BLOCK for all the second level blocks included in the first level block. Note: it is an error to use the VALGRIND_MEMPOOL_AUTO_FREE flag without the VALGRIND_MEMPOOL_METAPOOL flag. */ #define VALGRIND_MEMPOOL_AUTO_FREE 1 #define VALGRIND_MEMPOOL_METAPOOL 2 #define VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ pool, rzB, is_zeroed, flags, 0) /* Destroy a memory pool. */ #define VALGRIND_DESTROY_MEMPOOL(pool) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL, \ pool, 0, 0, 0, 0) /* Associate a piece of memory with a memory pool. */ #define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_ALLOC, \ pool, addr, size, 0, 0) /* Disassociate a piece of memory from a memory pool. */ #define VALGRIND_MEMPOOL_FREE(pool, addr) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_FREE, \ pool, addr, 0, 0, 0) /* Disassociate any pieces outside a particular range. */ #define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_TRIM, \ pool, addr, size, 0, 0) /* Resize and/or move a piece associated with a memory pool. */ #define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MOVE_MEMPOOL, \ poolA, poolB, 0, 0, 0) /* Resize and/or move a piece associated with a memory pool. */ #define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_CHANGE, \ pool, addrA, addrB, size, 0) /* Return 1 if a mempool exists, else 0. */ #define VALGRIND_MEMPOOL_EXISTS(pool) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__MEMPOOL_EXISTS, \ pool, 0, 0, 0, 0) /* Mark a piece of memory as being a stack. Returns a stack id. start is the lowest addressable stack byte, end is the highest addressable stack byte. */ #define VALGRIND_STACK_REGISTER(start, end) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__STACK_REGISTER, \ start, end, 0, 0, 0) /* Unmark the piece of memory associated with a stack id as being a stack. */ #define VALGRIND_STACK_DEREGISTER(id) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_DEREGISTER, \ id, 0, 0, 0, 0) /* Change the start and end address of the stack id. start is the new lowest addressable stack byte, end is the new highest addressable stack byte. */ #define VALGRIND_STACK_CHANGE(id, start, end) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_CHANGE, \ id, start, end, 0, 0) /* Load PDB debug info for Wine PE image_map. */ #define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__LOAD_PDB_DEBUGINFO, \ fd, ptr, total_size, delta, 0) /* Map a code address to a source file name and line number. buf64 must point to a 64-byte buffer in the caller's address space. The result will be dumped in there and is guaranteed to be zero terminated. If no info is found, the first byte is set to zero. */ #define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__MAP_IP_TO_SRCLOC, \ addr, buf64, 0, 0, 0) /* Disable error reporting for this thread. Behaves in a stack like way, so you can safely call this multiple times provided that VALGRIND_ENABLE_ERROR_REPORTING is called the same number of times to re-enable reporting. The first call of this macro disables reporting. Subsequent calls have no effect except to increase the number of VALGRIND_ENABLE_ERROR_REPORTING calls needed to re-enable reporting. Child threads do not inherit this setting from their parents -- they are always created with reporting enabled. */ #define VALGRIND_DISABLE_ERROR_REPORTING \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ 1, 0, 0, 0, 0) /* Re-enable error reporting, as per comments on VALGRIND_DISABLE_ERROR_REPORTING. */ #define VALGRIND_ENABLE_ERROR_REPORTING \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ -1, 0, 0, 0, 0) /* Execute a monitor command from the client program. If a connection is opened with GDB, the output will be sent according to the output mode set for vgdb. If no connection is opened, output will go to the log output. Returns 1 if command not recognised, 0 otherwise. */ #define VALGRIND_MONITOR_COMMAND(command) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__GDB_MONITOR_COMMAND, \ command, 0, 0, 0, 0) #undef PLAT_x86_darwin #undef PLAT_amd64_darwin #undef PLAT_x86_win32 #undef PLAT_amd64_win64 #undef PLAT_x86_linux #undef PLAT_amd64_linux #undef PLAT_ppc32_linux #undef PLAT_ppc64be_linux #undef PLAT_ppc64le_linux #undef PLAT_arm_linux #undef PLAT_s390x_linux #undef PLAT_mips32_linux #undef PLAT_mips64_linux #undef PLAT_x86_solaris #undef PLAT_amd64_solaris #endif /* __VALGRIND_H */