From: TizenOpenSource Date: Fri, 8 Dec 2023 03:28:18 +0000 (+0900) Subject: Imported Upstream version 0.9.7 X-Git-Tag: upstream/0.9.7^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c75073a06f65b63e4292bbb309670bc3b3b241e3;p=platform%2Fupstream%2Fmultipath-tools.git Imported Upstream version 0.9.7 --- diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml index abf17bf..61077df 100644 --- a/.github/workflows/build-and-unittest.yaml +++ b/.github/workflows/build-and-unittest.yaml @@ -8,82 +8,68 @@ on: pull_request: jobs: - bionic: - runs-on: ubuntu-18.04 + jammy: + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: rl: ['', 'libreadline', 'libedit'] + cc: [ gcc, clang ] steps: - uses: actions/checkout@v2 - - 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: update run: sudo apt-get update - name: dependencies run: > sudo apt-get install --yes gcc - make perl-base pkg-config valgrind + make pkg-config valgrind libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev - - 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 - run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) valgrind-test - - name: valgrind-results - run: cat tests/*.vgr - - 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-gcc10: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - rl: ['', 'libreadline', 'libedit'] - steps: - - uses: actions/checkout@v2 + libmount-dev linux-modules-extra-$(uname -r) - 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 perl-base pkg-config valgrind - libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev - libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev + - 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=gcc-10 >> $GITHUB_ENV + 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 }} + 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) test + run: > + make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) + OPTFLAGS="$OPT" test - name: valgrind-test - run: make -Orecurse -j$(grep -c ^processor /proc/cpuinfo) 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/ram0 test - focal-clang10: + 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@v2 - name: mpath @@ -94,20 +80,25 @@ jobs: run: sudo apt-get update - name: dependencies run: > - sudo apt-get install --yes clang - make perl-base pkg-config valgrind + 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=clang >> $GITHUB_ENV + 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 diff --git a/.github/workflows/coverity.yaml b/.github/workflows/coverity.yaml index 321b94e..a1f4146 100644 --- a/.github/workflows/coverity.yaml +++ b/.github/workflows/coverity.yaml @@ -6,7 +6,7 @@ on: jobs: upload-coverity-scan: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml index 2937b72..1b04292 100644 --- a/.github/workflows/foreign.yaml +++ b/.github/workflows/foreign.yaml @@ -10,11 +10,11 @@ on: jobs: cross-build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - os: [bullseye, sid] + os: [bullseye, bookworm, sid] arch: [ppc64le, arm64, s390x] container: ghcr.io/mwilck/multipath-cross-debian_cross-${{ matrix.os }}-${{ matrix.arch }} steps: @@ -37,12 +37,12 @@ jobs: path: binaries.tar test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: cross-build strategy: fail-fast: false matrix: - os: [bullseye, sid] + os: [bullseye, bookworm, sid] arch: [ppc64le, arm64, s390x] steps: - name: set container arch diff --git a/.github/workflows/multiarch.yaml b/.github/workflows/multiarch.yaml index 513021b..d76a01e 100644 --- a/.github/workflows/multiarch.yaml +++ b/.github/workflows/multiarch.yaml @@ -10,7 +10,7 @@ on: jobs: build-current: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -54,7 +54,7 @@ jobs: pull-params: "--platform linux/${{ matrix.arch }}" build-old: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index a7ad4c8..5d83a58 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -9,15 +9,16 @@ on: jobs: stable: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: - - debian-buster - debian-jessie + - debian-buster - debian-bullseye - - fedora-36 + - debian-bookworm + - fedora-37 - opensuse-leap container: ghcr.io/mwilck/multipath-build-${{ matrix.os }} steps: diff --git a/.gitignore b/.gitignore index 535353e..6890e4a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,11 +13,16 @@ cscope.files cscope.out kpartx/kpartx multipath/multipath +multipath/multipath.8 +multipath/multipath.conf.5 multipath/multipath.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 diff --git a/Makefile.inc b/Makefile.inc index 2e25d2e..6b45430 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -20,9 +20,9 @@ SCSI_DH_MODULES_PRELOAD := EXTRAVERSION := $(shell rev=$$(git rev-parse --short=7 HEAD 2>/dev/null); echo $${rev:+-g$$rev}) -# PKGCONFIG must be read from the environment to enable compilation +# PKG_CONFIG must be read from the environment to enable compilation # in Debian multiarch setups -PKGCONFIG ?= pkg-config +PKG_CONFIG ?= pkg-config ifeq ($(TOPDIR),) TOPDIR = .. @@ -36,28 +36,43 @@ prefix := # Prefix for binaries exec_prefix := $(prefix) # Prefix for non-essential libraries (libdmmp) -usr_prefix := $(prefix) +usr_prefix := $(if $(prefix),$(prefix),/usr) +# Prefix for configfuration files (multipath.conf) +etc_prefix := $(prefix) # Where to install systemd-related files. systemd is usually installed under /usr -# Note: some systemd installations use separate "prefix" and "rootprefix". -# In this case, override only unitdir to use systemd's "rootprefix" instead of $(systemd_prefix) +# Note: systemd installations with "split-usr=true" use separate "prefixdir" and +# "rootprefixdir". Our systemd_prefix corresponds to "prefixdir". +# In this case, override only unitdir and libudevdir below to use +# systemd's "rootprefixdir" instead of $(systemd_prefix) systemd_prefix := /usr -unitdir := $(systemd_prefix)/lib/systemd/system -tmpfilesdir := $(systemd_prefix)/lib/tmpfiles.d -modulesloaddir := $(systemd_prefix)/lib/modules-load.d -libudevdir := $(systemd_prefix)/lib/udev + +# 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)) + +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 +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 +syslibdir := $(prefix)$(LIB) +usrlibdir := $(usr_prefix)$(LIB) +includedir := $(usr_prefix)include pkgconfdir := $(usrlibdir)/pkgconfig -plugindir := $(prefix)/$(LIB)/multipath -configdir := $(prefix)/etc/multipath/conf.d +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 $(PKGCONFIG) --variable=includedir devmapper),/usr/include) -libudev_incdir := $(or $(shell $(PKGCONFIG) --variable=includedir libudev),/usr/include) +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 ifeq ($(V),) @@ -77,15 +92,18 @@ 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 WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implicit-int \ -Werror=implicit-function-declaration -Werror=format-security \ $(WNOCLOBBERED) -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) $(W_URCU_TYPE_LIMITS) -CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) \ +CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) $(D_URCU_VERSION) \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(plugindir)\" \ - -DRUNTIME_DIR=\"$(runtimedir)\" \ - -DCONFIG_DIR=\"$(configdir)\" -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP + -DRUNTIME_DIR=\"$(runtimedir)\" -DCONFIG_DIR=\"$(configdir)\" \ + -DDEFAULT_CONFIGFILE=\"$(configfile)\" -DSTATE_DIR=\"$(statedir)\" \ + -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe BIN_CFLAGS := -fPIE -DPIE LIB_CFLAGS := -fPIC @@ -128,3 +146,6 @@ NV_VERSION_SCRIPT = $(DEVLIB:%.so=%-nv.version) @grep -P '^[ \t]+[a-zA-Z_][a-zA-Z0-9_]*;' $< >>$@ @printf 'local:\n\t*;\n};\n' >>$@ +%: %.in + @echo creating $@ + $(Q)sed 's:@CONFIGFILE@:'$(configfile)':g;s:@CONFIGDIR@:'$(configdir)':g;s:@STATE_DIR@:'$(statedir)':g;s:@RUNTIME_DIR@:'$(runtimedir)':g;s/@MODPROBE_UNIT@/'$(MODPROBE_UNIT)'/g' $< >$@ diff --git a/README.md b/README.md index 5e04f5c..d4f35f5 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ 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. @@ -41,14 +42,6 @@ Go to: https://github.com/opensvc/multipath-tools/tags Select a release-tag and then click on "zip" or "tar.gz". -Devel code -========== - -To get latest devel code: - - git clone -b queue https://github.com/openSUSE/multipath-tools - - Building multipath-tools ======================== @@ -88,9 +81,17 @@ The following variables can be passed to the `make` command line: * `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. - * `configdir="/some/path"` : directory to search for configuration files. + 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/multipath/conf.d`. + 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. @@ -102,6 +103,13 @@ The following variables can be passed to the `make` command line: 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 @@ -113,26 +121,35 @@ The following variables can be passed to the `make` command line: 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. + 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. - Distributions may want to set this to `/usr`. - **Note**: for multipath-tools, unlike many other packages, `prefix` - defaults to the empty string, which resolves to the root directory (`/`). + "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. You may want to set this to `/usr` if `prefix` is empty. - * `systemd_prefix`: Prefix for systemd-related files. It defaults to `/usr`. - Some systemd installations use separate `prefix` and `rootprefix`. On such - a distribution, set `prefix`, and override `unitdir` to use systemd's - `rootprefix`. + 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. -See also `configdir` and `plugindir` above. See `Makefile.inc` for more -fine-grained control. +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` ### Compiler Options @@ -162,32 +179,58 @@ The following targets are intended for developers only. * `make compile-commands.json` to create input for [clangd](https://clangd.llvm.org/). -Add storage devices -=================== - -Follow the instructions in the `libmultipath/hwtable.c` header. +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). -(subscribers-only) -To subscribe and archives: https://listman.redhat.com/mailman/listinfo/dm-devel -Searchable: https://marc.info/?l=dm-devel +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. +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. + +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 ========= -pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog -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 +* post-0.4.5: https://github.com/opensvc/multipath-tools/commits/master Maintainer ========== Christophe Varoqui -Device-mapper development mailing list +Device-mapper development mailing list Licence @@ -229,15 +272,25 @@ To enable ALUA, the following options should be changed: NVMe ==== -To use Device Mapper/multipath-tools with NVMe devices, -if the Native NVMe Multipath subsystem is enabled -( "Y" in `/sys/module/nvme_core/parameters/multipath` ), -it has to be disabled: - -`echo "options nvme_core multipath=N" > /etc/modprobe.d/01-nvme_core-mp.conf`, -regenerate the initramfs (`dracut -f` or `update-initramfs`) and reboot. - -Check that it is disabled(N) with: -`cat /sys/module/nvme_core/parameters/multipath` -or -`systool -m nvme_core -A multipath` + +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. diff --git a/create-config.mk b/create-config.mk index 2a95ec5..4d318b9 100644 --- a/create-config.mk +++ b/create-config.mk @@ -23,7 +23,7 @@ check_cmd = $(shell \ # Check whether a function with name $1 has been declared in header file $2. check_func = $(shell \ - if grep -Eq "^[^[:blank:]]+[[:blank:]]+$1[[:blank:]]*(.*)*" "$2"; then \ + if grep -Eq "^(extern[[:blank:]]+)?[^[:blank:]]+[[:blank:]]+$1[[:blank:]]*(.*)*" "$2"; then \ found=1; \ status="yes"; \ else \ @@ -73,6 +73,10 @@ TEST_URCU_TYPE_LIMITS = $(shell \ $(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) @@ -104,17 +108,26 @@ ifneq ($(call check_var,ELS_DTAG_LNK_INTEGRITY,$(kernel_incdir)/scsi/fc/fc_els.h 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,$(PKGCONFIG) --exists json-c) +ENABLE_LIBDMMP := $(call check_cmd,$(PKG_CONFIG) --exists json-c) ifeq ($(ENABLE_DMEVENTS_POLL),0) DEFINES += -DNO_DMEVENTS_POLL endif -SYSTEMD := $(strip $(or $(shell $(PKGCONFIG) --modversion libsystemd 2>/dev/null | awk '{print $$1}'), \ +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'))) @@ -159,6 +172,7 @@ $(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)" >>$@ diff --git a/kpartx/dm-parts.rules b/kpartx/dm-parts.rules index b48b67c..ef5ff2e 100644 --- a/kpartx/dm-parts.rules +++ b/kpartx/dm-parts.rules @@ -31,7 +31,7 @@ ENV{DM_UDEV_LOW_PRIORITY_FLAG}!="1", OPTIONS+="link_priority=50" 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}=="?*" \ +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}" diff --git a/kpartx/kpartx.8 b/kpartx/kpartx.8 index 2b144a7..ef8051a 100644 --- a/kpartx/kpartx.8 +++ b/kpartx/kpartx.8 @@ -1,11 +1,12 @@ .\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. .\" 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" +.TH KPARTX 8 2019-04-27 Linux . . .\" ---------------------------------------------------------------------------- diff --git a/libdmmp/Makefile b/libdmmp/Makefile index 6d28caf..172ba04 100644 --- a/libdmmp/Makefile +++ b/libdmmp/Makefile @@ -14,10 +14,10 @@ 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 $(PKGCONFIG) --cflags json-c) +CPPFLAGS += -I$(libdmmpdir) -I$(mpathcmddir) $(shell $(PKG_CONFIG) --cflags json-c) CFLAGS += $(LIB_CFLAGS) -fvisibility=hidden -LIBDEPS += $(shell $(PKGCONFIG) --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread +LIBDEPS += $(shell $(PKG_CONFIG) --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread all: $(LIBS) doc .PHONY: doc clean install uninstall check speed_test dep_clean @@ -44,7 +44,7 @@ install: $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)sed -i 's|__INCLUDEDIR__|$(includedir)|g' \ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) - $(Q)$(INSTALL_PROGRAM) -d 755 $(DESTDIR)$(mandir)/man3 + $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 644 -t $(DESTDIR)$(mandir)/man3 docs/man/*.3 uninstall: @@ -74,8 +74,10 @@ 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 \ - KBUILD_BUILD_TIMESTAMP=`git log -n1 --pretty=%cd --date=iso -- $^` \ perl docs/kernel-doc -man $$TEMPFILE | \ perl docs/split-man.pl docs/man; \ $(RM) -f $$TEMPFILE diff --git a/libmpathpersist/mpath_persist_int.c b/libmpathpersist/mpath_persist_int.c index 6924b37..178c2f5 100644 --- a/libmpathpersist/mpath_persist_int.c +++ b/libmpathpersist/mpath_persist_int.c @@ -733,11 +733,12 @@ int update_map_pr(struct multipath *mpp) int noisy=0; struct prin_resp *resp; unsigned int i; - int ret, isFound; + 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; @@ -749,20 +750,27 @@ int update_map_pr(struct multipath *mpp) 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); - free(resp); - return 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); - free(resp); - return MPATH_PR_SUCCESS; + goto out; } condlog(2, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, @@ -783,10 +791,11 @@ int update_map_pr(struct multipath *mpp) if (isFound) { - mpp->prflag = 1; + mpp->prflag = PRFLAG_SET; condlog(2, "%s: prflag flag set.", mpp->alias ); } +out: free(resp); - return MPATH_PR_SUCCESS; + return ret; } diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index c168cae..2f9b8f7 100644 --- a/libmpathpersist/mpath_persistent_reserve_in.3 +++ b/libmpathpersist/mpath_persistent_reserve_in.3 @@ -1,11 +1,12 @@ .\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. .\" 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 2018-06-15 "Linux" +.TH MPATH_PERSISTENT_RESERVE_IN 3 2018-06-15 Linux . . .\" ---------------------------------------------------------------------------- diff --git a/libmpathpersist/mpath_persistent_reserve_out.3 b/libmpathpersist/mpath_persistent_reserve_out.3 index f20be31..d3204c5 100644 --- a/libmpathpersist/mpath_persistent_reserve_out.3 +++ b/libmpathpersist/mpath_persistent_reserve_out.3 @@ -1,11 +1,12 @@ .\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. .\" 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 2018-06-15 "Linux" +.TH MPATH_PERSISTENT_RESERVE_OUT 3 2018-06-15 Linux . . .\" ---------------------------------------------------------------------------- diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c index 4529a82..36bd777 100644 --- a/libmpathpersist/mpath_updatepr.c +++ b/libmpathpersist/mpath_updatepr.c @@ -14,6 +14,9 @@ #include #include "debug.h" #include "mpath_cmd.h" +#include "vector.h" +#include "globals.h" +#include "config.h" #include "uxsock.h" #include "mpathpr.h" @@ -24,6 +27,12 @@ static int do_update_pr(char *alias, char *cmd, char *key) 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) { @@ -41,7 +50,7 @@ static int do_update_pr(char *alias, char *cmd, char *key) mpath_disconnect(fd); return -1; } - ret = recv_packet(fd, &reply, DEFAULT_REPLY_TIMEOUT); + ret = recv_packet(fd, &reply, timeout); if (ret < 0) { condlog(2, "%s: message=%s recv error=%d", alias, str, errno); ret = -1; diff --git a/libmpathutil/libmpathutil.version b/libmpathutil/libmpathutil.version index 1238fc9..15ff467 100644 --- a/libmpathutil/libmpathutil.version +++ b/libmpathutil/libmpathutil.version @@ -33,7 +33,7 @@ /* * Symbols exported by both libmpathutil and libmultipath - * libmpathutil exports just dummy symbols, intended to be overriden + * 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! @@ -93,12 +93,15 @@ local: }; /* symbols referenced internally by libmultipath */ -LIBMPATHUTIL_1.0 { +LIBMPATHUTIL_2.0 { alloc_bitfield; __append_strbuf_str; append_strbuf_quoted; basenamecpy; + cleanup_fd_ptr; cleanup_free_ptr; + cleanup_vector_free; + cleanup_fclose; filepresent; find_keyword; free_keywords; @@ -113,7 +116,6 @@ LIBMPATHUTIL_1.0 { log_safe; msort; parse_devt; - parse_prkey; process_file; safe_write; set_value; @@ -121,15 +123,9 @@ LIBMPATHUTIL_1.0 { snprint_keyword; steal_strbuf_str; strlcat; - systemd_service_enabled; validate_config_strvec; vector_find_or_add_slot; vector_insert_slot; vector_move_up; vector_sort; }; - -LIBMPATHUTIL_1.1 { -global: - cleanup_fd_ptr; -} LIBMPATHUTIL_1.0; diff --git a/libmpathutil/util.c b/libmpathutil/util.c index 9662e1e..9d147fc 100644 --- a/libmpathutil/util.c +++ b/libmpathutil/util.c @@ -213,64 +213,6 @@ setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached) } } -int systemd_service_enabled_in(const char *dev, const char *prefix) -{ - static const char service[] = "multipathd.service"; - char path[PATH_MAX], file[PATH_MAX]; - DIR *dirfd; - struct dirent *d; - int found = 0; - - if (safe_sprintf(path, "%s/systemd/system", prefix)) - return 0; - - condlog(3, "%s: checking for %s in %s", dev, service, path); - - dirfd = opendir(path); - if (dirfd == NULL) - return 0; - - while ((d = readdir(dirfd)) != NULL) { - char *p; - struct stat stbuf; - - if ((strcmp(d->d_name,".") == 0) || - (strcmp(d->d_name,"..") == 0)) - continue; - - if (strlen(d->d_name) < 6) - continue; - - p = d->d_name + strlen(d->d_name) - 6; - if (strcmp(p, ".wants")) - continue; - if (!safe_sprintf(file, "%s/%s/%s", - path, d->d_name, service) - && stat(file, &stbuf) == 0) { - condlog(3, "%s: found %s", dev, file); - found++; - break; - } - } - closedir(dirfd); - - return found; -} - -int systemd_service_enabled(const char *dev) -{ - int found = 0; - - found = systemd_service_enabled_in(dev, "/etc"); - if (!found) - found = systemd_service_enabled_in(dev, "/usr/lib"); - if (!found) - found = systemd_service_enabled_in(dev, "/lib"); - if (!found) - found = systemd_service_enabled_in(dev, "/run"); - return found; -} - static int _linux_version_code; static pthread_once_t _lvc_initialized = PTHREAD_ONCE_INIT; @@ -386,6 +328,18 @@ 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; diff --git a/libmpathutil/util.h b/libmpathutil/util.h index 75e20fd..de9fcfd 100644 --- a/libmpathutil/util.h +++ b/libmpathutil/util.h @@ -21,7 +21,6 @@ size_t strlcat(char * restrict dst, const char * restrict src, size_t size); 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 systemd_service_enabled(const char *dev); 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); @@ -48,6 +47,8 @@ int should_exit(void); 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; diff --git a/libmultipath/Makefile b/libmultipath/Makefile index 3df851e..85767ab 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -7,7 +7,7 @@ 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 \ - -lurcu -laio $(SYSTEMD_LIBDEPS) + -lmount -lurcu -laio $(SYSTEMD_LIBDEPS) # object files referencing MULTIPATH_DIR or CONFIG_DIR # they need to be recompiled for unit tests @@ -71,7 +71,7 @@ uninstall: 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)) +include $(wildcard $(OBJS:.o=.d) $(OBJS-T:.o=.d)) dep_clean: - $(Q)$(RM) $(OBJS:.o=.d) + $(Q)$(RM) $(OBJS:.o=.d) $(OBJS-T:.o=.d) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 0520122..74431f3 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #include "debug.h" #include "util.h" @@ -20,6 +23,8 @@ #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 @@ -48,7 +53,297 @@ "# alias wwid\n" \ "#\n" -static const char bindings_file_header[] = BINDINGS_FILE_HEADER; +/* 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 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, numercally 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) @@ -109,178 +404,78 @@ scan_devname(const char *alias, const char *prefix) return n; } -static int -id_already_taken(int id, const char *prefix, const char *map_wwid) +static bool alias_already_taken(const char *alias, const char *map_wwid) +{ + + char wwid[WWID_SIZE]; + + /* If the map doesn't exist, it's fine */ + if (dm_get_uuid(alias, wwid, sizeof(wwid)) != 0) + return false; + + /* If both the name and the wwid match, it's fine.*/ + if (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 0; + return false; alias = get_strbuf_str(&buf); - if (dm_map_present(alias)) { - char wwid[WWID_SIZE]; - - /* If both the name and the wwid match, then it's fine.*/ - if (dm_get_uuid(alias, wwid, sizeof(wwid)) == 0 && - strncmp(map_wwid, wwid, sizeof(wwid)) == 0) - return 0; - condlog(3, "%s: alias '%s' already taken, but not in bindings file. reselecting alias", map_wwid, alias); - return 1; - } - return 0; + return alias_already_taken(alias, map_wwid); } - -/* - * Returns: 0 if matching entry in WWIDs file found - * -1 if an error occurs - * >0 a free ID that could be used for the WWID at hand - * *map_alias is set to a freshly allocated string with the matching alias if - * the function returns 0, or to NULL otherwise. - */ -static int -lookup_binding(FILE *f, const char *map_wwid, char **map_alias, - const char *prefix, int check_if_taken) +int get_free_id(const Bindings *bindings, const char *prefix, const char *map_wwid) { - char buf[LINE_MAX]; - unsigned int line_nr = 0; - int id = 1; - int biggest_id = 1; - int smallest_bigger_id = INT_MAX; - - *map_alias = NULL; - - rewind(f); - while (fgets(buf, LINE_MAX, f)) { - const char *alias, *wwid; - char *c, *saveptr; - int curr_id; - - line_nr++; - c = strpbrk(buf, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(buf, " \t", &saveptr); - if (!alias) /* blank line */ - continue; - curr_id = scan_devname(alias, prefix); - if (curr_id == id) { - if (id < INT_MAX) - id++; - else { - id = -1; - break; - } - } - if (curr_id > biggest_id) - biggest_id = curr_id; - if (curr_id > id && curr_id < smallest_bigger_id) - smallest_bigger_id = curr_id; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid){ - condlog(3, - "Ignoring malformed line %u in bindings file", - line_nr); + 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; } - if (strcmp(wwid, map_wwid) == 0){ - condlog(3, "Found matching wwid [%s] in bindings file." - " Setting alias to %s", wwid, alias); - *map_alias = strdup(alias); - if (*map_alias == NULL) { - condlog(0, "Cannot copy alias from bindings " - "file: out of memory"); - return -1; - } - return 0; - } - } - if (!prefix && check_if_taken) - id = -1; - if (id >= smallest_bigger_id) { - if (biggest_id < INT_MAX) - id = biggest_id + 1; - else - id = -1; - } - if (id > 0 && check_if_taken) { - while(id_already_taken(id, prefix, map_wwid)) { - if (id == INT_MAX) { - id = -1; - break; - } + while (id < curr_id && id_already_taken(id, prefix, map_wwid)) id++; - if (id == smallest_bigger_id) { - if (biggest_id == INT_MAX) { - id = -1; - break; - } - if (biggest_id >= smallest_bigger_id) - id = biggest_id + 1; - } - } + if (id < curr_id) + return id; + id++; + if (id <= 0) + break; } - if (id < 0) { - condlog(0, "no more available user_friendly_names"); - return -1; - } else - condlog(3, "No matching wwid [%s] in bindings file.", map_wwid); - return id; -} -static int -rlookup_binding(FILE *f, char *buff, const char *map_alias) -{ - char line[LINE_MAX]; - unsigned int line_nr = 0; - - buff[0] = '\0'; - - while (fgets(line, LINE_MAX, f)) { - char *c, *saveptr; - const char *alias, *wwid; - - line_nr++; - c = strpbrk(line, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(line, " \t", &saveptr); - if (!alias) /* blank line */ - continue; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid){ - condlog(3, - "Ignoring malformed line %u in bindings file", - line_nr); - continue; - } - if (strlen(wwid) > WWID_SIZE - 1) { - condlog(3, - "Ignoring too large wwid at %u in bindings file", line_nr); - continue; - } - if (strcmp(alias, map_alias) == 0){ - condlog(3, "Found matching alias [%s] in bindings file." - " Setting wwid to %s", alias, wwid); - strlcpy(buff, wwid, WWID_SIZE); - return 0; - } + for (; id > 0; id++) { + if (!id_already_taken(id, prefix, map_wwid)) + break; } - condlog(3, "No matching alias [%s] in bindings file.", map_alias); - return -1; + if (id <= 0) { + id = -1; + condlog(0, "no more available user_friendly_names"); + } + return id; } +/* Called with binding_mutex held */ static char * -allocate_binding(int fd, const char *wwid, int id, const char *prefix) +allocate_binding(const char *wwid, int id, const char *prefix) { STRBUF_ON_STACK(buf); - off_t offset; - ssize_t len; - char *alias, *c; + char *alias; if (id <= 0) { condlog(0, "%s: cannot allocate new binding for id %d", @@ -292,314 +487,217 @@ allocate_binding(int fd, const char *wwid, int id, const char *prefix) format_devname(&buf, id) == -1) return NULL; - if (print_strbuf(&buf, " %s\n", wwid) < 0) - return NULL; + alias = steal_strbuf_str(&buf); - offset = lseek(fd, 0, SEEK_END); - if (offset < 0){ - condlog(0, "Cannot seek to end of bindings file : %s", - strerror(errno)); + 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; } - len = get_strbuf_len(&buf); - alias = steal_strbuf_str(&buf); - - if (write(fd, alias, len) != len) { - condlog(0, "Cannot write binding to bindings file : %s", - strerror(errno)); - /* clear partial write */ - if (ftruncate(fd, offset)) - condlog(0, "Cannot truncate the header : %s", - strerror(errno)); + 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; } - c = strchr(alias, ' '); - if (c) - *c = '\0'; condlog(3, "Created new binding [%s] for WWID [%s]", alias, wwid); return alias; } -char * -use_existing_alias (const char *wwid, const char *file, const char *alias_old, - const char *prefix, int bindings_read_only) +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 corrcectly 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; - int fd, can_write; - char buff[WWID_SIZE]; - FILE *f; + bool new_binding = false; + const struct binding *bdg; - fd = open_file(file, &can_write, bindings_file_header); - if (fd < 0) - return NULL; + read_bindings_file(); - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor"); - close(fd); - return NULL; - } - /* lookup the binding. if it exists, the wwid will be in buff - * either way, id contains the id for the alias - */ - rlookup_binding(f, buff, alias_old); + pthread_mutex_lock(&bindings_mutex); + pthread_cleanup_push(cleanup_mutex, &bindings_mutex); - if (strlen(buff) > 0) { - /* if buff is our wwid, it's already - * allocated correctly - */ - if (strcmp(buff, wwid) == 0) + 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); - else { - alias = NULL; + goto out; + } else { condlog(0, "alias %s already bound to wwid %s, cannot reuse", - alias_old, buff); + alias_old, bdg->wwid); + goto new_alias; } - goto out; - } - - id = lookup_binding(f, wwid, &alias, NULL, 0); - if (alias) { - condlog(3, "Use existing binding [%s] for WWID [%s]", - alias, wwid); - goto out; } /* allocate the existing alias in the bindings file */ id = scan_devname(alias_old, prefix); - if (id <= 0) - goto out; - if (fflush(f) != 0) { - condlog(0, "cannot fflush bindings file stream : %s", - strerror(errno)); +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 (can_write && !bindings_read_only) { - alias = allocate_binding(fd, wwid, id, prefix); - condlog(0, "Allocated existing binding [%s] for WWID [%s]", - alias, wwid); - } - -out: - pthread_cleanup_push(free, alias); - fclose(f); - pthread_cleanup_pop(0); - return alias; -} - -char * -get_user_friendly_alias(const char *wwid, const char *file, const char *prefix, - int bindings_read_only) -{ - char *alias; - int fd, id; - FILE *f; - int can_write; - - if (!wwid || *wwid == '\0') { - condlog(3, "Cannot find binding for empty WWID"); - return NULL; - } - - fd = open_file(file, &can_write, bindings_file_header); - if (fd < 0) - return NULL; - - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor : %s", - strerror(errno)); - close(fd); - return NULL; - } - - id = lookup_binding(f, wwid, &alias, prefix, 1); - if (id < 0) { - fclose(f); - return NULL; + 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; } - pthread_cleanup_push(free, alias); + if (!bindings_read_only && id > 0) + alias = allocate_binding(wwid, id, prefix); - if (fflush(f) != 0) { - condlog(0, "cannot fflush bindings file stream : %s", - strerror(errno)); - free(alias); - alias = NULL; - } else if (can_write && !bindings_read_only && !alias) - alias = allocate_binding(fd, wwid, id, prefix); - - fclose(f); + if (alias && !new_binding) + condlog(2, "Allocated existing binding [%s] for WWID [%s]", + alias, wwid); - pthread_cleanup_pop(0); +out: + /* unlock bindings_mutex */ + pthread_cleanup_pop(1); return alias; } -int -get_user_friendly_wwid(const char *alias, char *buff, const char *file) +int get_user_friendly_wwid(const char *alias, char *buff) { - int fd, unused; - FILE *f; + const struct binding *bdg; + int rc = -1; if (!alias || *alias == '\0') { condlog(3, "Cannot find binding for empty alias"); return -1; } - fd = open_file(file, &unused, bindings_file_header); - if (fd < 0) - return -1; - - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor : %s", - strerror(errno)); - close(fd); - return -1; - } + read_bindings_file(); - rlookup_binding(f, buff, alias); - if (!strlen(buff)) { - fclose(f); - return -1; - } - - fclose(f); - return 0; + 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; } -struct binding { - char *alias; - char *wwid; -}; - -static void _free_binding(struct binding *bdg) +void cleanup_bindings(void) { - free(bdg->wwid); - free(bdg->alias); - free(bdg); -} - -/* - * Perhaps one day we'll implement this more efficiently, thus use - * an abstract type. - */ -typedef struct _vector Bindings; - -static void free_bindings(Bindings *bindings) -{ - struct binding *bdg; - int i; - - vector_foreach_slot(bindings, bdg, i) - _free_binding(bdg); - vector_reset(bindings); + pthread_mutex_lock(&bindings_mutex); + free_bindings(&global_bindings); + pthread_mutex_unlock(&bindings_mutex); } enum { - BINDING_EXISTS, - BINDING_CONFLICT, - BINDING_ADDED, - BINDING_DELETED, - BINDING_NOTFOUND, - BINDING_ERROR, + READ_BINDING_OK, + READ_BINDING_SKIP, }; -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 = strcmp(bdg->alias, alias)) <= 0) - break; - } +static int read_binding(char *line, unsigned int linenr, char **alias, + char **wwid) { + char *c, *saveptr; - /* 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 write_bindings_file(const Bindings *bindings, int fd) -{ - struct binding *bnd; - STRBUF_ON_STACK(line); - int i; - - if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) - != sizeof(BINDINGS_FILE_HEADER) - 1) - return -1; - - vector_foreach_slot(bindings, bnd, i) { - int len; - - if ((len = print_strbuf(&line, "%s %s\n", - bnd->alias, bnd->wwid)) < 0) - return -1; - if (write(fd, get_strbuf_str(&line), len) != len) - return -1; - truncate_strbuf(&line, 0); - } - return 0; -} + c = strpbrk(line, "#\n\r"); + if (c) + *c = '\0'; -static int fix_bindings_file(const struct config *conf, - const Bindings *bindings) -{ - int rc; - int fd = -1; - char tempname[PATH_MAX]; - mode_t old_umask; + *alias = strtok_r(line, " \t", &saveptr); + if (!*alias) /* blank line */ + return READ_BINDING_SKIP; - if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) - return -1; - /* coverity: SECURE_TEMP */ - old_umask = umask(0077); - if ((fd = mkstemp(tempname)) == -1) { - condlog(1, "%s: mkstemp: %m", __func__); - return -1; + *wwid = strtok_r(NULL, " \t", &saveptr); + if (!*wwid) { + condlog(1, "invalid line %u in bindings file, missing WWID", + linenr); + return READ_BINDING_SKIP; } - umask(old_umask); - pthread_cleanup_push(cleanup_fd_ptr, &fd); - rc = write_bindings_file(bindings, fd); - pthread_cleanup_pop(1); - if (rc == -1) { - condlog(1, "failed to write new bindings file %s", - tempname); - unlink(tempname); - return rc; + if (strlen(*wwid) > WWID_SIZE - 1) { + condlog(3, + "Ignoring too large wwid at %u in bindings file", + linenr); + return READ_BINDING_SKIP; } - if ((rc = rename(tempname, conf->bindings_file)) == -1) - condlog(0, "%s: rename: %m", __func__); - else - condlog(1, "updated bindings file %s", conf->bindings_file); - return rc; + 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, @@ -610,30 +708,28 @@ static int _check_bindings_file(const struct config *conf, FILE *file, 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 *c, *alias, *wwid, *saveptr; + char *alias, *wwid; const char *mpe_wwid; - linenr++; - c = strpbrk(line, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(line, " \t", &saveptr); - if (!alias) /* blank line */ - continue; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid) { - condlog(1, "invalid line %d in bindings file, missing WWID", - linenr); + if (read_binding(line, ++linenr, &alias, &wwid) + == READ_BINDING_SKIP) continue; - } - 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); mpe_wwid = get_mpe_wwid(conf->mptable, alias); if (mpe_wwid && strcmp(mpe_wwid, wwid)) { @@ -667,27 +763,72 @@ static int _check_bindings_file(const struct config *conf, FILE *file, return rc; } -static void cleanup_fclose(void *p) +static int mp_alias_compar(const void *p1, const void *p2) { - fclose(p); + return alias_compar(&((*(struct mpentry * const *)p1)->alias), + &((*(struct mpentry * const *)p2)->alias)); } -static int alias_compar(const void *p1, const void *p2) +static int _read_bindings_file(const struct config *conf, Bindings *bindings, + bool force) { - const char *alias1 = (*(struct mpentry * const *)p1)->alias; - const char *alias2 = (*(struct mpentry * const *)p2)->alias; + 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; + } + } - if (alias1 && alias2) - return strcmp(alias1, alias2); - else - /* Move NULL alias to the end */ - return alias1 ? -1 : alias2 ? 1 : 0; -} + fd = open_file(bindings_file_path, &can_write, BINDINGS_FILE_HEADER); + if (fd == -1) + return BINDINGS_FILE_ERROR; -static void cleanup_vector_free(void *arg) -{ - if (arg) - vector_free((vector)arg); + 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; } /* @@ -708,8 +849,7 @@ static void cleanup_vector_free(void *arg) */ int check_alias_settings(const struct config *conf) { - int can_write; - int rc = 0, i, fd; + int i, rc; Bindings bindings = {.allocated = 0, }; vector mptable = NULL; struct mpentry *mpe; @@ -721,7 +861,7 @@ int check_alias_settings(const struct config *conf) pthread_cleanup_push_cast(free_bindings, &bindings); pthread_cleanup_push(cleanup_vector_free, mptable); - vector_sort(mptable, alias_compar); + vector_sort(mptable, mp_alias_compar); vector_foreach_slot(mptable, mpe, i) { if (!mpe->alias) /* @@ -742,26 +882,12 @@ int check_alias_settings(const struct config *conf) pthread_cleanup_pop(1); pthread_cleanup_pop(1); - pthread_cleanup_push_cast(free_bindings, &bindings); - fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); - if (fd != -1) { - FILE *file = fdopen(fd, "r"); - - if (file != NULL) { - pthread_cleanup_push(cleanup_fclose, file); - rc = _check_bindings_file(conf, file, &bindings); - pthread_cleanup_pop(1); - if (rc == -1 && can_write && !conf->bindings_read_only) - rc = fix_bindings_file(conf, &bindings); - else if (rc == -1) - condlog(0, "ERROR: bad settings in read-only bindings file %s", - conf->bindings_file); - } else { - condlog(1, "failed to fdopen %s: %m", - conf->bindings_file); - close(fd); - } + rc = _read_bindings_file(conf, &bindings, true); + + if (rc == BINDINGS_FILE_READ) { + set_global_bindings(&bindings); + rc = 0; } - pthread_cleanup_pop(1); + return rc; } diff --git a/libmultipath/alias.h b/libmultipath/alias.h index dbc950c..629e8d5 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -2,15 +2,13 @@ #define _ALIAS_H int valid_alias(const char *alias); -char *get_user_friendly_alias(const char *wwid, const char *file, - const char *prefix, - int bindings_readonly); -int get_user_friendly_wwid(const char *alias, char *buff, const char *file); -char *use_existing_alias (const char *wwid, const char *file, - const char *alias_old, - const char *prefix, int bindings_read_only); +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 */ diff --git a/libmultipath/blacklist.c b/libmultipath/blacklist.c index 8d15d2e..75100b2 100644 --- a/libmultipath/blacklist.c +++ b/libmultipath/blacklist.c @@ -2,6 +2,8 @@ * Copyright (c) 2004, 2005 Christophe Varoqui */ #include +#include +#include #include #include "checkers.h" @@ -191,6 +193,27 @@ find_blacklist_device (const struct _vector *blist, const char *vendor, 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) { @@ -198,9 +221,15 @@ setup_default_blist (struct config * conf) struct hwentry *hwe; int i; - if (store_ble(conf->blist_devnode, "!^(sd[a-z]|dasd[a-z]|nvme[0-9])", ORIGIN_DEFAULT)) - return 1; - + 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; diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 2f3ece0..12b8be4 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -70,6 +70,7 @@ 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) @@ -77,9 +78,9 @@ add_aio_group(void) memset(aio_grp, 0, sizeof(struct aio_group)); INIT_LIST_HEAD(&aio_grp->orphans); - if (io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx) != 0) { + if ((rc = io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx)) != 0) { LOG(1, "io_setup failed"); - if (errno == EAGAIN) + if (rc == -EAGAIN) LOG(1, "global number of io events too small. Increase fs.aio-max-nr with sysctl"); free(aio_grp); return NULL; @@ -232,15 +233,15 @@ void libcheck_free (struct checker * c) } } - if (ct->running && - (ct->req->state != PATH_PENDING || - io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event) == 0)) + if (ct->running && ct->req->state != PATH_PENDING) ct->running = 0; if (!ct->running) { 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); @@ -259,14 +260,13 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) struct timespec *timep = timeout; do { - errno = 0; 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(3, "io finished %lu/%lu", events[i].res, + LOG(4, "io finished %lu/%lu", events[i].res, events[i].res2); /* got an orphaned request */ @@ -283,8 +283,7 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) } while (nr == 128); /* assume there are more events and try again */ if (nr < 0) - LOG(3, "async io getevents returned %i (errno=%s)", - nr, strerror(errno)); + LOG(4, "async io getevents returned %s", strerror(-nr)); return got_events; } @@ -315,13 +314,13 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) } else { struct iocb *ios[1] = { &ct->req->io }; - LOG(3, "starting new request"); + 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 (io_submit(ct->aio_grp->ioctx, 1, ios) != 1) { - LOG(3, "io_submit error %i", errno); + if ((rc = io_submit(ct->aio_grp->ioctx, 1, ios)) != 1) { + LOG(3, "io_submit error %i", -rc); return PATH_UNCHECKED; } } @@ -351,16 +350,10 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) LOG(3, "abort check on timeout"); - r = io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); - /* - * Only reset ct->running if we really - * could abort the pending I/O - */ - if (!r) - ct->running = 0; + io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); rc = PATH_DOWN; } else { - LOG(3, "async io pending"); + LOG(4, "async io pending"); rc = PATH_PENDING; } diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index 551dc4f..eb2fca1 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -32,6 +32,7 @@ enum { MSG_TUR_RUNNING = CHECKER_FIRST_MSGID, MSG_TUR_TIMEOUT, MSG_TUR_FAILED, + MSG_TUR_TRANSITIONING, }; #define _IDX(x) (MSG_ ## x - CHECKER_FIRST_MSGID) @@ -39,6 +40,7 @@ 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, }; @@ -179,13 +181,20 @@ retry: else if (key == 0x2) { /* Not Ready */ /* Note: Other ALUA states are either UP or DOWN */ - if( asc == 0x04 && ascq == 0x0b){ + 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; @@ -350,6 +359,7 @@ int libcheck_check(struct checker * c) 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; @@ -390,13 +400,17 @@ int libcheck_check(struct checker * c) * It fails only in OOM situations. In this case, return * PATH_UNCHECKED to avoid prematurely failing the path. */ - if (libcheck_init(c) != 0) + 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)) + 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 @@ -404,7 +418,7 @@ int libcheck_check(struct checker * c) /* Start new TUR checker */ pthread_mutex_lock(&ct->lock); tur_status = ct->state = PATH_PENDING; - ct->msgid = CHECKER_MSGID_NONE; + c->msgid = ct->msgid = MSG_TUR_RUNNING; pthread_mutex_unlock(&ct->lock); ct->fd = c->fd; ct->timeout = c->timeout; @@ -424,7 +438,7 @@ int libcheck_check(struct checker * c) } tur_timeout(&tsp); pthread_mutex_lock(&ct->lock); - if (ct->state == PATH_PENDING) + if (ct->state == PATH_PENDING && ct->msgid == MSG_TUR_RUNNING) r = pthread_cond_timedwait(&ct->active, &ct->lock, &tsp); if (!r) { @@ -432,7 +446,7 @@ int libcheck_check(struct checker * c) c->msgid = ct->msgid; } pthread_mutex_unlock(&ct->lock); - if (tur_status == PATH_PENDING) { + 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 { diff --git a/libmultipath/config.c b/libmultipath/config.c index 5c5c072..b7dbc6f 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -452,6 +452,8 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) 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); @@ -617,6 +619,8 @@ store_hwe (vector hwtable, struct hwentry * dhwe) 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; @@ -748,15 +752,6 @@ static void _uninit_config(struct config *conf) if (conf->hwhandler) free(conf->hwhandler); - if (conf->bindings_file) - free(conf->bindings_file); - - if (conf->wwids_file) - free(conf->wwids_file); - - if (conf->prkeys_file) - free(conf->prkeys_file); - if (conf->prio_name) free(conf->prio_name); @@ -918,9 +913,6 @@ int _init_config (const char *file, struct config *conf) * internal defaults */ get_sys_max_fds(&conf->max_fds); - conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); - conf->wwids_file = set_default(DEFAULT_WWIDS_FILE); - conf->prkeys_file = set_default(DEFAULT_PRKEYS_FILE); conf->attribute_flags = 0; conf->reassign_maps = DEFAULT_REASSIGN_MAPS; conf->checkint = CHECKINT_UNDEF; @@ -1074,12 +1066,6 @@ int _init_config (const char *file, struct config *conf) merge_blacklist(conf->elist_wwid); merge_blacklist_device(conf->elist_device); - if (conf->bindings_file == NULL) - conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); - - if (!conf->bindings_file || !conf->wwids_file || !conf->prkeys_file) - goto out; - libmp_verbosity = conf->verbosity; return 0; out: diff --git a/libmultipath/config.h b/libmultipath/config.h index 8794746..384193a 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -76,6 +76,8 @@ struct hwentry { 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; @@ -160,6 +162,7 @@ struct config { 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; @@ -171,6 +174,8 @@ struct config { 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; @@ -197,15 +202,13 @@ struct config { int skip_delegate; unsigned int sequence_nr; int recheck_wwid; + int auto_resize; char * selector; struct _vector uid_attrs; char * uid_attribute; char * features; char * hwhandler; - char * bindings_file; - char * wwids_file; - char * prkeys_file; char * prio_name; char * prio_args; char * checker_name; diff --git a/libmultipath/configure.c b/libmultipath/configure.c index e551047..d809490 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -304,6 +304,8 @@ int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) 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); /* @@ -593,11 +595,12 @@ sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload) ssize_t len; int i, j, ret, err = 0; struct udev_device *udd; - int max_sectors_kb; + int max_sectors_kb = mpp->max_sectors_kb; - if (mpp->max_sectors_kb == MAX_SECTORS_KB_UNDEF) + /* by default, do not initialize max_sectors_kb on the device */ + if (max_sectors_kb == MAX_SECTORS_KB_UNDEF && !is_reload) return 0; - max_sectors_kb = mpp->max_sectors_kb; + /* on reload, re-apply the user tuning on all the path devices */ if (is_reload) { if (!has_dm_info(mpp) && dm_get_info(mpp->alias, &mpp->dmi) != 0) { @@ -670,7 +673,8 @@ static bool is_udev_ready(struct multipath *cmpp) static void select_reload_action(struct multipath *mpp, const char *reason) { - mpp->action = ACT_RELOAD; + mpp->action = mpp->action == ACT_RENAME ? ACT_RELOAD_RENAME : + ACT_RELOAD; condlog(3, "%s: set ACT_RELOAD (%s)", mpp->alias, reason); } @@ -681,42 +685,33 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, 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_by_name) { - if (cmpp) { - 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; - if (force_reload) { - mpp->force_udev_reload = 1; - mpp->action = ACT_FORCERENAME; - } - return; + 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)", - mpp->alias); + condlog(3, "%s: set ACT_CREATE (map does not exist%s)", + mpp->alias, cmpp_by_name ? ", name changed" : ""); return; } - if (!cmpp) { - 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, name changed)", + if (!cmpp_by_name) { + condlog(2, "%s: rename %s to %s", mpp->wwid, cmpp->alias, mpp->alias); - return; - } - - if (cmpp != cmpp_by_name) { + 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); @@ -724,29 +719,27 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, free(mpp->alias); mpp->alias = strdup(cmpp->alias); mpp->action = ACT_IMPOSSIBLE; - return; + /* don't return here. Check for other needed actions */ } - if (force_reload) { + if (cmpp->size != mpp->size) { mpp->force_udev_reload = 1; - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (forced by user)", + mpp->action = mpp->action == ACT_RENAME ? ACT_RESIZE_RENAME : + ACT_RESIZE; + condlog(3, "%s: set ACT_RESIZE (size change)", mpp->alias); return; } - if (cmpp->size != mpp->size) { + + if (force_reload) { mpp->force_udev_reload = 1; - mpp->action = ACT_RESIZE; - condlog(3, "%s: set ACT_RESIZE (size change)", - mpp->alias); + select_reload_action(mpp, "forced by user"); return; } if (!is_udev_ready(cmpp) && count_active_paths(mpp) > 0) { mpp->force_udev_reload = 1; - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (udev incomplete)", - mpp->alias); + select_reload_action(mpp, "udev incomplete"); return; } @@ -808,14 +801,14 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, return; } if (cmpp->nextpg != mpp->bestpg) { - mpp->action = ACT_SWITCHPG; + mpp->action = mpp->action == ACT_RENAME ? ACT_SWITCHPG_RENAME : + ACT_SWITCHPG; condlog(3, "%s: set ACT_SWITCHPG (next path group change)", mpp->alias); return; } - mpp->action = ACT_NOTHING; - condlog(3, "%s: set ACT_NOTHING (map unchanged)", - mpp->alias); + if (mpp->action == ACT_NOTHING) + condlog(3, "%s: set ACT_NOTHING (map unchanged)", mpp->alias); return; } @@ -916,6 +909,17 @@ int domap(struct multipath *mpp, char *params, int is_daemon) } } + 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: @@ -923,6 +927,7 @@ int domap(struct multipath *mpp, char *params, int is_daemon) 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 @@ -949,6 +954,7 @@ int domap(struct multipath *mpp, char *params, int is_daemon) break; case ACT_RELOAD: + case ACT_RELOAD_RENAME: sysfs_set_max_sectors_kb(mpp, 1); if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP)) mpp->ghost_delay_tick = 0; @@ -956,6 +962,7 @@ int domap(struct multipath *mpp, char *params, int is_daemon) break; case ACT_RESIZE: + case ACT_RESIZE_RENAME: sysfs_set_max_sectors_kb(mpp, 1); if (mpp->ghost_delay_tick > 0 && pathcount(mpp, PATH_UP)) mpp->ghost_delay_tick = 0; @@ -963,29 +970,10 @@ int domap(struct multipath *mpp, char *params, int is_daemon) break; case ACT_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); - break; - - case ACT_FORCERENAME: - 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) { - sysfs_set_max_sectors_kb(mpp, 1); - if (mpp->ghost_delay_tick > 0 && - pathcount(mpp, PATH_UP)) - mpp->ghost_delay_tick = 0; - r = dm_addmap_reload(mpp, params, 0); - } break; default: + r = DOMAP_FAIL; break; } @@ -1205,13 +1193,13 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, 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 (cmd == CMD_DRY_RUN) - mpp->action = ACT_DRY_RUN; if (mpp->action == ACT_UNDEF) select_action(mpp, curmp, force_reload == FORCE_RELOAD_YES ? 1 : 0); @@ -1273,8 +1261,11 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, ret = CP_OK; out: free(size_mismatch_seen); - if (!mpvec) - free_multipathvec(newmp, KEEP_PATHS); + if (!mpvec) { + vector_foreach_slot (newmp, mpp, i) + remove_map(mpp, vecs->pathvec, NULL); + vector_free(newmp); + } return ret; } @@ -1387,8 +1378,7 @@ static int _get_refwwid(enum mpath_cmds cmd, const char *dev, refwwid = tmpwwid; /* or may be a binding */ - else if (get_user_friendly_wwid(dev, tmpwwid, - conf->bindings_file) == 0) + else if (get_user_friendly_wwid(dev, tmpwwid) == 0) refwwid = tmpwwid; /* or may be an alias */ diff --git a/libmultipath/configure.h b/libmultipath/configure.h index 2bf73e6..9d935db 100644 --- a/libmultipath/configure.h +++ b/libmultipath/configure.h @@ -18,9 +18,11 @@ enum actions { ACT_RENAME, ACT_CREATE, ACT_RESIZE, - ACT_FORCERENAME, + ACT_RELOAD_RENAME, ACT_DRY_RUN, ACT_IMPOSSIBLE, + ACT_RESIZE_RENAME, + ACT_SWITCHPG_RENAME, }; /* diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index a5e9ea0..64b633f 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -29,6 +29,8 @@ #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 @@ -54,6 +56,7 @@ #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" @@ -64,10 +67,9 @@ #define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE RUNTIME_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" -#define DEFAULT_CONFIGFILE "/etc/multipath.conf" -#define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings" -#define DEFAULT_WWIDS_FILE "/etc/multipath/wwids" -#define DEFAULT_PRKEYS_FILE "/etc/multipath/prkeys" +#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/" diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index a49db3b..9be82f4 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -706,12 +706,16 @@ dm_get_prefixed_uuid(const char *name, char *uuid, int uuid_len) { struct dm_task *dmt; const char *uuidtmp; + struct dm_info info; int r = 1; dmt = libmp_dm_task_create(DM_DEVICE_INFO); if (!dmt) return 1; + if (uuid_len > 0) + uuid[0] = '\0'; + if (!dm_task_set_name (dmt, name)) goto uuidout; @@ -720,11 +724,13 @@ dm_get_prefixed_uuid(const char *name, char *uuid, int uuid_len) goto uuidout; } + if (!dm_task_get_info(dmt, &info) || + !info.exists) + goto uuidout; + uuidtmp = dm_task_get_uuid(dmt); if (uuidtmp) strlcpy(uuid, uuidtmp, uuid_len); - else - uuid[0] = '\0'; r = 0; uuidout: @@ -1359,7 +1365,6 @@ dm_get_maps (vector mp) } vector_set_slot(mp, mpp); - mpp = NULL; next: next = names->next; names = (void *) names + next; diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 2e9b45f..0a160e9 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -168,27 +168,6 @@ fail: return 0; } -static int -set_path(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 ((*str_ptr)[0] != '/'){ - condlog(1, "%s line %d, %s is not an absolute path. Ignoring", - file, line_nr, *str_ptr); - free(*str_ptr); - *str_ptr = old_str; - } else - free(old_str); - return 0; -} - static int set_str_noslash(vector strvec, void *ptr, const char *file, int line_nr) { @@ -278,11 +257,6 @@ static int print_str(struct strbuf *buff, const char *ptr) return ret == -EINVAL ? 0 : ret; } -static int print_ignored(struct strbuf *buff) -{ - return append_strbuf_quoted(buff, "ignored"); -} - static int print_yes_no(struct strbuf *buff, long v) { return append_strbuf_quoted(buff, v == YN_NO ? "no" : "yes"); @@ -319,14 +293,16 @@ def_ ## option ## _handler (struct config *conf, vector strvec, \ static int deprecated_handler(struct config *conf, vector strvec, const char *file, int line_nr); -#define declare_deprecated_handler(option) \ +#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 "\"", file, line_nr); \ + 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); \ @@ -548,7 +524,6 @@ declare_def_snprint(verbosity, print_int) declare_def_handler(reassign_maps, set_yes_no) declare_def_snprint(reassign_maps, print_yes_no) -declare_deprecated_handler(multipath_dir) static int def_partition_delim_handler(struct config *conf, vector strvec, const char *file, int line_nr) @@ -835,15 +810,6 @@ 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_warn_handler(bindings_file, set_path) -declare_def_snprint(bindings_file, print_str) - -declare_def_warn_handler(wwids_file, set_path) -declare_def_snprint(wwids_file, print_str) - -declare_def_warn_handler(prkeys_file, set_path) -declare_def_snprint(prkeys_file, print_str) - declare_def_handler(retain_hwhandler, set_yes_no_undef) declare_def_snprint_defint(retain_hwhandler, print_yes_no_undef, DEFAULT_RETAIN_HWHANDLER) @@ -868,6 +834,22 @@ 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) @@ -902,17 +884,6 @@ 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) -static int def_disable_changed_wwids_handler(struct config *conf, vector strvec, - const char *file, int line_nr) -{ - return 0; -} -static int snprint_def_disable_changed_wwids(struct config *conf, - struct strbuf *buff, - const void *data) -{ - return print_ignored(buff); -} declare_def_range_handler(remove_retries, 0, INT_MAX) declare_def_snprint(remove_retries, print_int) @@ -934,9 +905,6 @@ declare_def_handler(enable_foreign, set_str) declare_def_snprint_defstr(enable_foreign, print_str, DEFAULT_ENABLE_FOREIGN) -declare_deprecated_handler(config_dir) -declare_deprecated_handler(pg_timeout) - #define declare_def_attr_handler(option, function) \ static int \ def_ ## option ## _handler (struct config *conf, vector strvec, \ @@ -1184,6 +1152,30 @@ 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) { @@ -1209,14 +1201,10 @@ set_pgpolicy(vector strvec, void *ptr, const char *file, int line_nr) int print_pgpolicy(struct strbuf *buff, long pgpolicy) { - char str[POLICY_NAME_SIZE]; - if (!pgpolicy) return 0; - get_pgpolicy_name(str, POLICY_NAME_SIZE, pgpolicy); - - return append_strbuf_quoted(buff, str); + return append_strbuf_quoted(buff, get_pgpolicy_name(pgpolicy)); } declare_def_handler(pgpolicy, set_pgpolicy) @@ -1676,6 +1664,43 @@ 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) +{ + if (!v) + return 0; + 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) @@ -2064,7 +2089,15 @@ snprint_deprecated (struct config *conf, struct strbuf *buff, const void * data) return 0; } -declare_deprecated_handler(getuid_callout) +// 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 @@ -2107,15 +2140,18 @@ init_keywords(vector keywords) 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("bindings_file", &def_bindings_file_handler, &snprint_def_bindings_file); - install_keyword("wwids_file", &def_wwids_file_handler, &snprint_def_wwids_file); - install_keyword("prkeys_file", &def_prkeys_file_handler, &snprint_def_prkeys_file); + 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); @@ -2137,10 +2173,11 @@ init_keywords(vector keywords) 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", &def_disable_changed_wwids_handler, &snprint_def_disable_changed_wwids); + 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); @@ -2206,6 +2243,8 @@ init_keywords(vector keywords) 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); @@ -2248,6 +2287,8 @@ init_keywords(vector keywords) 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); diff --git a/libmultipath/dict.h b/libmultipath/dict.h index 15d9cba..7e2dfbe 100644 --- a/libmultipath/dict.h +++ b/libmultipath/dict.h @@ -17,4 +17,5 @@ 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); #endif /* _DICT_H */ diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index d9ee2cb..6fd4dab 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -614,6 +614,43 @@ sysfs_set_eh_deadline(struct path *pp) 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) { @@ -857,6 +894,9 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) 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; @@ -875,7 +915,8 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET && - pp->eh_deadline == EH_DEADLINE_UNSET) + pp->eh_deadline == EH_DEADLINE_UNSET && + conf->max_retries == MAX_RETRIES_UNSET) continue; if (pp->bus != SYSFS_BUS_SCSI) { @@ -883,6 +924,7 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) 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) @@ -895,10 +937,11 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) continue; } - if (pp->dev_loss != DEV_LOSS_TMO_UNSET && - pp->dev_loss < min_dev_loss) { - warn_dev_loss = true; + if (pp->dev_loss == DEV_LOSS_TMO_UNSET) + 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 && @@ -1014,18 +1057,13 @@ detect_alua(struct path * pp) { int ret; int tpgs; - unsigned int timeout; - if (pp->bus != SYSFS_BUS_SCSI) { pp->tpgs = TPGS_NONE; return; } - if (sysfs_get_timeout(pp, &timeout) <= 0) - timeout = DEF_TIMEOUT; - - tpgs = get_target_port_group_support(pp, timeout); + tpgs = get_target_port_group_support(pp); if (tpgs == -RTPG_INQUIRY_FAILED) return; else if (tpgs <= 0) { @@ -1036,8 +1074,8 @@ detect_alua(struct path * pp) if (pp->fd == -1 || pp->offline) return; - ret = get_target_port_group(pp, timeout); - if (ret < 0 || get_asymmetric_access_state(pp, ret, timeout) < 0) { + ret = get_target_port_group(pp); + if (ret < 0 || get_asymmetric_access_state(pp, ret) < 0) { int state; if (ret == -RTPG_INQUIRY_FAILED) @@ -1051,6 +1089,7 @@ detect_alua(struct path * pp) return; } pp->tpgs = tpgs; + pp->tpg_id = ret; } int path_get_tpgs(struct path *pp) @@ -1211,6 +1250,17 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, 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__); @@ -1472,6 +1522,7 @@ scsi_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) { struct udev_device *parent; const char *attr_path = NULL; + static const char unknown[] = "UNKNOWN"; parent = pp->udev; while (parent) { @@ -1492,19 +1543,22 @@ scsi_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) 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;; - + 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) - return PATHINFO_FAILED;; - + 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) - return PATHINFO_FAILED;; - + 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); /* @@ -1948,9 +2002,6 @@ get_state (struct path * pp, struct config *conf, int daemon, int oldstate) checker_set_async(c); else checker_set_sync(c); - if (!conf->checker_timeout && - sysfs_get_timeout(pp, &(c->timeout)) <= 0) - c->timeout = DEF_TIMEOUT; state = checker_check(c, oldstate); condlog(3, "%s: %s state = %s", pp->dev, checker_name(c), checker_state_name(state)); @@ -1962,7 +2013,7 @@ get_state (struct path * pp, struct config *conf, int daemon, int oldstate) } static int -get_prio (struct path * pp, int timeout) +get_prio (struct path * pp) { struct prio * p; struct config *conf; @@ -1985,7 +2036,7 @@ get_prio (struct path * pp, int timeout) } } old_prio = pp->priority; - pp->priority = prio_getprio(p, pp, timeout); + pp->priority = prio_getprio(p, pp); if (pp->priority < 0) { /* this changes pp->offline, but why not */ int state = path_offline(pp); @@ -2089,7 +2140,7 @@ get_udev_uid(struct path * pp, const char *uid_attribute, struct udev_device *ud const char *value; value = udev_device_get_property_value(udev, uid_attribute); - if (!value || strlen(value) == 0) + 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); @@ -2463,8 +2514,7 @@ int pathinfo(struct path *pp, struct config *conf, int mask) */ if ((mask & DI_PRIO) && path_state == PATH_UP && strlen(pp->wwid)) { if (pp->state != PATH_DOWN || pp->priority == PRIO_UNDEF) { - get_prio(pp, (pp->state != PATH_DOWN)? - (conf->checker_timeout * 1000) : 10); + get_prio(pp); } } diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index 3c4f866..ae6aac7 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -67,6 +67,8 @@ .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, @@ -485,7 +487,9 @@ static struct hwentry default_hw[] = { /* USP-V, HUS VM, VSP, VSP G1X00 and VSP GX00 families / HPE XP */ .vendor = "(HITACHI|HP|HPE)", .product = "^OPEN-", - .pgpolicy = MULTIBUS, + .pgpolicy = GROUP_BY_PRIO, + .pgfailback = -FAILBACK_IMMEDIATE, + .no_path_retry = 10, }, { /* AMS other than AMS 2000 */ @@ -1067,6 +1071,8 @@ static struct hwentry default_hw[] = { .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 12, .prio_name = PRIO_ALUA, + .checker_name = DIRECTIO, + .detect_checker = DETECT_CHECKER_OFF, }, /* * DataCore diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index dc1c252..1c59445 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -38,6 +37,7 @@ #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 @@ -111,10 +111,14 @@ static int init_each_dio_ctx(struct dio_ctx *ct, int blksize, return 0; } -static void deinit_each_dio_ctx(struct dio_ctx *ct) +static int deinit_each_dio_ctx(struct dio_ctx *ct) { - if (ct->buf) - free(ct->buf); + 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) @@ -164,17 +168,21 @@ fail_close: 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; - cancel_inflight_io(p); - for (i = 0; i < CONCUR_NR_EVENT; i++) - deinit_each_dio_ctx(p->dio_ctx_array + i); - free(p->dio_ctx_array); + 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); @@ -211,6 +219,15 @@ static void free_io_err_pathvec(void) 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); @@ -451,7 +468,7 @@ static void end_io_err_stat(struct io_err_stat_path *pp) static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) { - int rc = -1; + int rc; if (ct->io_starttime.tv_nsec == 0 && ct->io_starttime.tv_sec == 0) { @@ -459,15 +476,15 @@ static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) get_monotonic_time(&ct->io_starttime); io_prep_pread(&ct->io, fd, ct->buf, ct->blksize, 0); - if (io_submit(ioctx, 1, ios) != 1) { - io_err_stat_log(5, "%s: io_submit error %i", - dev, errno); - return rc; + if ((rc = io_submit(ioctx, 1, ios)) != 1) { + io_err_stat_log(2, "%s: io_submit error %s", + dev, strerror(-rc)); + return -1; } - rc = 0; + return 0; } - return rc; + return -1; } static void send_batch_async_ios(struct io_err_stat_path *pp) @@ -503,7 +520,7 @@ static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, int rc = PATH_UNCHECKED; int r; - if (ct->io_starttime.tv_sec == 0) + 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) { @@ -512,10 +529,8 @@ static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, 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 %i", - dev, errno); - ct->io_starttime.tv_sec = 0; - ct->io_starttime.tv_nsec = 0; + io_err_stat_log(5, "%s: io_cancel error %s", + dev, strerror(-r)); rc = PATH_TIMEOUT; } else { rc = PATH_PENDING; @@ -544,7 +559,7 @@ static void poll_async_io_timeout(void) static void cancel_inflight_io(struct io_err_stat_path *pp) { struct io_event event; - int i, r; + int i; for (i = 0; i < CONCUR_NR_EVENT; i++) { struct dio_ctx *ct = pp->dio_ctx_array + i; @@ -555,12 +570,7 @@ static void cancel_inflight_io(struct io_err_stat_path *pp) continue; io_err_stat_log(5, "%s: abort infligh io", pp->devname); - r = io_cancel(ioctx, ios[0], &event); - if (r) - io_err_stat_log(5, "%s: io_cancel error %d, %i", - pp->devname, r, errno); - ct->io_starttime.tv_sec = 0; - ct->io_starttime.tv_nsec = 0; + io_cancel(ioctx, ios[0], &event); } } @@ -596,12 +606,11 @@ static void process_async_ios_event(int timeout_nsecs, char *dev) int i, n; struct timespec timeout = { .tv_nsec = timeout_nsecs }; - errno = 0; pthread_testcancel(); n = io_getevents(ioctx, 1L, CONCUR_NR_EVENT, events, &timeout); if (n < 0) { - io_err_stat_log(3, "%s: async io events returned %d (errno=%s)", - dev, n, strerror(errno)); + 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]); @@ -690,8 +699,9 @@ int start_io_err_stat_thread(void *data) if (uatomic_read(&io_err_thread_running) == 1) return 0; - if (io_setup(CONCUR_NR_EVENT, &ioctx) != 0) { - io_err_stat_log(4, "io_setup failed"); + 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; } @@ -746,5 +756,4 @@ void stop_io_err_stat_thread(void) pthread_join(io_err_stat_thr, NULL); free_io_err_pathvec(); - io_destroy(ioctx); } diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index faef2a2..8ab7c80 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -33,7 +33,7 @@ /* * Symbols exported by both libmpathutil and libmultipath - * libmpathutil exports just dummy symbols, intended to be overriden + * 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! @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMULTIPATH_17.0.0 { +LIBMULTIPATH_22.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; @@ -64,6 +64,7 @@ global: checker_name; checker_state_name; check_foreign; + cleanup_bindings; cleanup_lock; coalesce_paths; count_active_paths; @@ -120,6 +121,7 @@ global: get_used_hwes; get_vpd_sgio; group_by_prio; + handle_bindings_file_inotify; has_dm_info; init_checkers; init_config; @@ -214,7 +216,7 @@ global: /* prioritizers */ get_asymmetric_access_state; - get_prio_timeout; + get_prio_timeout_ms; get_target_port_group; get_target_port_group_support; libmp_nvme_ana_log; diff --git a/libmultipath/lock.h b/libmultipath/lock.h index 9814be7..ac80d1d 100644 --- a/libmultipath/lock.h +++ b/libmultipath/lock.h @@ -13,15 +13,20 @@ struct mutex_lock { int waiters; /* uatomic access only */ }; -#if !defined(__GLIBC__) && defined(__GNUC__) && __GNUC__ == 12 +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 void init_lock(struct mutex_lock *a) +static inline int uatomic_xchg_int(int *ptr, int val) { - pthread_mutex_init(&a->mutex, NULL); - uatomic_set(&a->waiters, 0); + return uatomic_xchg(ptr, val); } static inline void lock(struct mutex_lock *a) @@ -31,6 +36,10 @@ static inline void lock(struct mutex_lock *a) 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); @@ -51,10 +60,6 @@ static inline bool lock_has_waiters(struct mutex_lock *a) return (uatomic_read(&a->waiters) > 0); } -#if !defined(__GLIBC__) && defined(__GNUC__) && __GNUC__ == 12 -#pragma GCC diagnostic pop -#endif - #define lock_cleanup_pop(a) pthread_cleanup_pop(1) void cleanup_lock (void * data); diff --git a/libmultipath/pgpolicies.c b/libmultipath/pgpolicies.c index 10b44d3..edc3c61 100644 --- a/libmultipath/pgpolicies.c +++ b/libmultipath/pgpolicies.c @@ -25,35 +25,29 @@ int get_pgpolicy_id(char * str) 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; } -int get_pgpolicy_name(char * buff, int len, int id) +const char *get_pgpolicy_name(int id) { - char * s; - switch (id) { case FAILOVER: - s = "failover"; - break; + return "failover"; case MULTIBUS: - s = "multibus"; - break; + return "multibus"; case GROUP_BY_SERIAL: - s = "group_by_serial"; - break; + return "group_by_serial"; case GROUP_BY_PRIO: - s = "group_by_prio"; - break; + return "group_by_prio"; case GROUP_BY_NODE_NAME: - s = "group_by_node_name"; - break; - default: - s = "undefined"; - break; + return "group_by_node_name"; + case GROUP_BY_TPG: + return "group_by_tpg"; } - return snprintf(buff, len, "%s", s); + return "undefined"; /* IOPOLICY_UNDEF */ } @@ -191,6 +185,12 @@ 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 *)) { @@ -279,6 +279,14 @@ 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; diff --git a/libmultipath/pgpolicies.h b/libmultipath/pgpolicies.h index 1592761..9e4ddda 100644 --- a/libmultipath/pgpolicies.h +++ b/libmultipath/pgpolicies.h @@ -16,11 +16,12 @@ enum iopolicies { MULTIBUS, GROUP_BY_SERIAL, GROUP_BY_PRIO, - GROUP_BY_NODE_NAME + GROUP_BY_NODE_NAME, + GROUP_BY_TPG, }; int get_pgpolicy_id(char *); -int get_pgpolicy_name (char *, int, int); +const char *get_pgpolicy_name (int); int group_paths(struct multipath *, int); /* * policies @@ -30,5 +31,6 @@ 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 diff --git a/libmultipath/print.c b/libmultipath/print.c index 3193dbe..360308d 100644 --- a/libmultipath/print.c +++ b/libmultipath/print.c @@ -782,6 +782,14 @@ snprint_path_vpd_data(struct strbuf *buff, const struct path * pp) 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 const struct multipath_data mpd[] = { {'n', "name", snprint_name}, {'w', "uuid", snprint_multipath_uuid}, @@ -836,6 +844,7 @@ static const struct path_data pd[] = { {'P', "protocol", snprint_path_protocol}, {'I', "init_st", snprint_initialized}, {'L', "LUN hex", snprint_path_lunhex}, + {'A', "TPG", snprint_alua_tpg}, }; static const struct pathgroup_data pgd[] = { diff --git a/libmultipath/prio.c b/libmultipath/prio.c index cdd3752..a54487f 100644 --- a/libmultipath/prio.c +++ b/libmultipath/prio.c @@ -3,20 +3,25 @@ #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(unsigned int timeout_ms, - unsigned int default_timeout) +unsigned int get_prio_timeout_ms(const struct path *pp) { - if (timeout_ms) - return timeout_ms; - return default_timeout; + 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) @@ -136,7 +141,7 @@ struct prio *add_prio (const char *name) errstr); goto out; } - p->getprio = (int (*)(struct path *, char *, unsigned int)) dlsym(p->handle, "getprio"); + p->getprio = (int (*)(struct path *, char *)) dlsym(p->handle, "getprio"); errstr = dlerror(); if (errstr != NULL) condlog(0, "A dynamic linking error occurred: (%s)", errstr); @@ -149,9 +154,9 @@ out: return NULL; } -int prio_getprio (struct prio * p, struct path * pp, unsigned int timeout) +int prio_getprio (struct prio * p, struct path * pp) { - return p->getprio(pp, p->args, timeout); + return p->getprio(pp, p->args); } int prio_selected (const struct prio * p) diff --git a/libmultipath/prio.h b/libmultipath/prio.h index 184bf65..318d260 100644 --- a/libmultipath/prio.h +++ b/libmultipath/prio.h @@ -49,15 +49,14 @@ struct prio { struct list_head node; char name[PRIO_NAME_LEN]; char args[PRIO_ARGS_LEN]; - int (*getprio)(struct path *, char *, unsigned int); + int (*getprio)(struct path *, char *); }; -unsigned int get_prio_timeout(unsigned int checker_timeout, - unsigned int default_timeout); +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 *, unsigned int); +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 *); @@ -66,6 +65,6 @@ 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 *, unsigned int); +int getprio(struct path *, char *); #endif /* _PRIO_H */ diff --git a/libmultipath/prioritizers/alua.c b/libmultipath/prioritizers/alua.c index 0ab06e2..ec68f37 100644 --- a/libmultipath/prioritizers/alua.c +++ b/libmultipath/prioritizers/alua.c @@ -51,22 +51,26 @@ static const char *aas_print_string(int rc) } int -get_alua_info(struct path * pp, unsigned int timeout) +get_alua_info(struct path * pp) { int rc; int tpg; + bool diff_tpg; - tpg = get_target_port_group(pp, timeout); + tpg = get_target_port_group(pp); if (tpg < 0) { - rc = get_target_port_group_support(pp, timeout); + 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; } - condlog(3, "%s: reported target port group is %i", pp->dev, tpg); - rc = get_asymmetric_access_state(pp, tpg, timeout); + 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); @@ -94,7 +98,7 @@ int get_exclusive_pref_arg(char *args) return 1; } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path * pp, char * args) { int rc; int aas; @@ -105,7 +109,7 @@ int getprio (struct path * pp, char * args, unsigned int timeout) return -ALUA_PRIO_NO_INFORMATION; exclusive_pref = get_exclusive_pref_arg(args); - rc = get_alua_info(pp, timeout); + rc = get_alua_info(pp); if (rc >= 0) { aas = (rc & 0x0f); priopath = (rc & 0x80); diff --git a/libmultipath/prioritizers/alua_rtpg.c b/libmultipath/prioritizers/alua_rtpg.c index 2db9153..dd2224c 100644 --- a/libmultipath/prioritizers/alua_rtpg.c +++ b/libmultipath/prioritizers/alua_rtpg.c @@ -136,7 +136,7 @@ scsi_error(struct sg_io_hdr *hdr, int opcode) */ static int do_inquiry_sg(int fd, int evpd, unsigned int codepage, - void *resp, int resplen, unsigned int timeout) + void *resp, int resplen, unsigned int timeout_ms) { struct inquiry_command cmd; struct sg_io_hdr hdr; @@ -162,7 +162,7 @@ retry: hdr.dxfer_len = resplen; hdr.sbp = sense; hdr.mx_sb_len = sizeof(sense); - hdr.timeout = get_prio_timeout(timeout, SGIO_TIMEOUT); + hdr.timeout = timeout_ms; if (ioctl(fd, SG_IO, &hdr) < 0) { PRINT_DEBUG("do_inquiry: IOCTL failed!"); @@ -185,7 +185,7 @@ retry: } int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, - void *resp, int resplen, unsigned int timeout) + void *resp, int resplen) { struct udev_device *ud = NULL; @@ -206,7 +206,8 @@ int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, return 0; } } - return do_inquiry_sg(pp->fd, evpd, codepage, resp, resplen, timeout); + return do_inquiry_sg(pp->fd, evpd, codepage, resp, resplen, + get_prio_timeout_ms(pp)); } /* @@ -214,13 +215,13 @@ int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, * data returned by the standard inquiry command. */ int -get_target_port_group_support(const struct path *pp, unsigned int timeout) +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), timeout); + rc = do_inquiry(pp, 0, 0x00, &inq, sizeof(inq)); if (!rc) { rc = inquiry_data_get_tpgs(&inq); } @@ -229,7 +230,7 @@ get_target_port_group_support(const struct path *pp, unsigned int timeout) } int -get_target_port_group(const struct path * pp, unsigned int timeout) +get_target_port_group(const struct path * pp) { unsigned char *buf; const struct vpd83_data * vpd83; @@ -246,7 +247,7 @@ get_target_port_group(const struct path * pp, unsigned int timeout) } memset(buf, 0, buflen); - rc = do_inquiry(pp, 1, 0x83, buf, buflen, timeout); + rc = do_inquiry(pp, 1, 0x83, buf, buflen); if (rc < 0) goto out; @@ -263,7 +264,7 @@ get_target_port_group(const struct path * pp, unsigned int timeout) } buflen = scsi_buflen; memset(buf, 0, buflen); - rc = do_inquiry(pp, 1, 0x83, buf, buflen, timeout); + rc = do_inquiry(pp, 1, 0x83, buf, buflen); if (rc < 0) goto out; } @@ -293,7 +294,7 @@ out: } int -do_rtpg(int fd, void* resp, long resplen, unsigned int timeout) +do_rtpg(int fd, void* resp, long resplen, unsigned int timeout_ms) { struct rtpg_command cmd; struct sg_io_hdr hdr; @@ -316,7 +317,7 @@ retry: hdr.dxfer_len = resplen; hdr.mx_sb_len = sizeof(sense); hdr.sbp = sense; - hdr.timeout = get_prio_timeout(timeout, SGIO_TIMEOUT); + hdr.timeout = timeout_ms; if (ioctl(fd, SG_IO, &hdr) < 0) { condlog(2, "%s: sg ioctl failed: %s", @@ -340,8 +341,7 @@ retry: } int -get_asymmetric_access_state(const struct path *pp, unsigned int tpg, - unsigned int timeout) +get_asymmetric_access_state(const struct path *pp, unsigned int tpg) { unsigned char *buf; struct rtpg_data * tpgd; @@ -349,6 +349,7 @@ get_asymmetric_access_state(const struct path *pp, unsigned int tpg, 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; @@ -359,7 +360,7 @@ get_asymmetric_access_state(const struct path *pp, unsigned int tpg, return -RTPG_RTPG_FAILED; } memset(buf, 0, buflen); - rc = do_rtpg(fd, buf, buflen, timeout); + rc = do_rtpg(fd, buf, buflen, timeout_ms); if (rc < 0) { PRINT_DEBUG("%s: do_rtpg returned %d", __func__, rc); goto out; @@ -377,7 +378,7 @@ get_asymmetric_access_state(const struct path *pp, unsigned int tpg, } buflen = scsi_buflen; memset(buf, 0, buflen); - rc = do_rtpg(fd, buf, buflen, timeout); + rc = do_rtpg(fd, buf, buflen, timeout_ms); if (rc < 0) goto out; } diff --git a/libmultipath/prioritizers/alua_rtpg.h b/libmultipath/prioritizers/alua_rtpg.h index 675709f..c5f9a8f 100644 --- a/libmultipath/prioritizers/alua_rtpg.h +++ b/libmultipath/prioritizers/alua_rtpg.h @@ -22,9 +22,8 @@ #define RTPG_RTPG_FAILED 3 #define RTPG_TPG_NOT_FOUND 4 -int get_target_port_group_support(const struct path *pp, unsigned int timeout); -int get_target_port_group(const struct path *pp, unsigned int timeout); -int get_asymmetric_access_state(const struct path *pp, - unsigned int tpg, unsigned int timeout); +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 /* __RTPG_H__ */ diff --git a/libmultipath/prioritizers/ana.c b/libmultipath/prioritizers/ana.c index b5c7873..e9827dc 100644 --- a/libmultipath/prioritizers/ana.c +++ b/libmultipath/prioritizers/ana.c @@ -203,8 +203,7 @@ static int get_ana_info(struct path * pp) * - ALUA's LBA-dependent state has no ANA equivalent. */ -int getprio(struct path *pp, __attribute__((unused)) char *args, - __attribute__((unused)) unsigned int timeout) +int getprio(struct path *pp, __attribute__((unused)) char *args) { int rc; diff --git a/libmultipath/prioritizers/const.c b/libmultipath/prioritizers/const.c index 059d859..2b07f47 100644 --- a/libmultipath/prioritizers/const.c +++ b/libmultipath/prioritizers/const.c @@ -3,8 +3,7 @@ #include "prio.h" int getprio(__attribute__((unused)) struct path * pp, - __attribute__((unused)) char * args, - __attribute__((unused)) unsigned int timeout) + __attribute__((unused)) char * args) { return 1; } diff --git a/libmultipath/prioritizers/datacore.c b/libmultipath/prioritizers/datacore.c index d1d473d..f288306 100644 --- a/libmultipath/prioritizers/datacore.c +++ b/libmultipath/prioritizers/datacore.c @@ -32,7 +32,8 @@ #define dc_log(prio, msg) condlog(prio, "%s: datacore prio: " msg, dev) -int datacore_prio (const char *dev, int sg_fd, char * args) +int datacore_prio (const char *dev, int sg_fd, char * args, + unsigned int timeout_ms) { int k; char sdsname[32]; @@ -42,7 +43,6 @@ int datacore_prio (const char *dev, int sg_fd, char * args) unsigned char sense_buffer[32]; sg_io_hdr_t io_hdr; - int timeout = 2000; char preferredsds_buff[255] = ""; char * preferredsds = &preferredsds_buff[0]; @@ -52,9 +52,9 @@ int datacore_prio (const char *dev, int sg_fd, char * args) } if (sscanf(args, "timeout=%i preferredsds=%s", - &timeout, preferredsds) == 2) {} + (int *)&timeout_ms, preferredsds) == 2) {} else if (sscanf(args, "preferredsds=%s timeout=%i", - preferredsds, &timeout) == 2) {} + preferredsds, (int *)&timeout_ms) == 2) {} else if (sscanf(args, "preferredsds=%s", preferredsds) == 1) {} else { @@ -67,10 +67,6 @@ int datacore_prio (const char *dev, int sg_fd, char * args) dc_log(0, "prio args: preferredsds too short (1 character min)"); return 0; } - if ((timeout < 500) || (timeout > 20000)) { - dc_log(0, "prio args: timeout out of bounds [500:20000]"); - return 0; - } if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) return 0; @@ -83,7 +79,7 @@ int datacore_prio (const char *dev, int sg_fd, char * args) io_hdr.dxferp = inqBuff; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_buffer; - io_hdr.timeout = timeout; + io_hdr.timeout = timeout_ms; // on error just return prio 0 if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) @@ -98,8 +94,7 @@ int datacore_prio (const char *dev, int sg_fd, char * args) return 0; } -int getprio(struct path * pp, char * args, - __attribute__((unused)) unsigned int timeout) +int getprio(struct path * pp, char * args) { - return datacore_prio(pp->dev, pp->fd, args); + return datacore_prio(pp->dev, pp->fd, args, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/emc.c b/libmultipath/prioritizers/emc.c index 3b63cca..856c23d 100644 --- a/libmultipath/prioritizers/emc.c +++ b/libmultipath/prioritizers/emc.c @@ -12,7 +12,7 @@ #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) +int emc_clariion_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char sense_buffer[128]; unsigned char sb[128]; @@ -31,7 +31,7 @@ int emc_clariion_prio(const char *dev, int fd, unsigned int timeout) io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; - io_hdr.timeout = get_prio_timeout(timeout, 60000); + 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"); @@ -81,8 +81,7 @@ out: return(ret); } -int getprio (struct path *pp, __attribute__((unused)) char *args, - unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args) { - return emc_clariion_prio(pp->dev, pp->fd, timeout); + return emc_clariion_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/hds.c b/libmultipath/prioritizers/hds.c index d569f2d..212301e 100644 --- a/libmultipath/prioritizers/hds.c +++ b/libmultipath/prioritizers/hds.c @@ -84,7 +84,7 @@ #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) +int hds_modular_prio (const char *dev, int fd, unsigned int timeout_ms) { int k; char vendor[9]; @@ -114,7 +114,7 @@ int hds_modular_prio (const char *dev, int fd, unsigned int timeout) io_hdr.dxferp = inqBuff; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_buffer; - io_hdr.timeout = get_prio_timeout(timeout, 2000); /* TimeOut = 2 seconds */ + io_hdr.timeout = timeout_ms; if (ioctl (fd, SG_IO, &io_hdr) < 0) { pp_hds_log(0, "SG_IO error"); @@ -168,8 +168,7 @@ int hds_modular_prio (const char *dev, int fd, unsigned int timeout) return -1; } -int getprio (struct path * pp, __attribute__((unused)) char *args, - unsigned int timeout) +int getprio (struct path * pp, __attribute__((unused)) char *args) { - return hds_modular_prio(pp->dev, pp->fd, timeout); + return hds_modular_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/hp_sw.c b/libmultipath/prioritizers/hp_sw.c index 5b85ad2..5570271 100644 --- a/libmultipath/prioritizers/hp_sw.c +++ b/libmultipath/prioritizers/hp_sw.c @@ -32,7 +32,7 @@ #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) +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]; @@ -46,7 +46,7 @@ int hp_sw_prio(const char *dev, int fd, unsigned int timeout) io_hdr.dxfer_direction = SG_DXFER_NONE; io_hdr.cmdp = turCmdBlk; io_hdr.sbp = sb; - io_hdr.timeout = get_prio_timeout(timeout, 60000); + io_hdr.timeout = timeout_ms; io_hdr.pack_id = 0; retry: if (ioctl(fd, SG_IO, &io_hdr) < 0) { @@ -95,8 +95,7 @@ out: return(ret); } -int getprio (struct path *pp, __attribute__((unused)) char *args, - unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args) { - return hp_sw_prio(pp->dev, pp->fd, timeout); + return hp_sw_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index 167a46b..f3bf64c 100644 --- a/libmultipath/prioritizers/iet.c +++ b/libmultipath/prioritizers/iet.c @@ -138,8 +138,7 @@ int iet_prio(const char *dev, char * args) return 10; } -int getprio(struct path * pp, char * args, - __attribute__((unused)) unsigned int timeout) +int getprio(struct path * pp, char * args) { return iet_prio(pp->dev, args); } diff --git a/libmultipath/prioritizers/ontap.c b/libmultipath/prioritizers/ontap.c index 262e69d..117886e 100644 --- a/libmultipath/prioritizers/ontap.c +++ b/libmultipath/prioritizers/ontap.c @@ -28,7 +28,6 @@ #define INQUIRY_CMDLEN 6 #define DEFAULT_PRIOVAL 10 #define RESULTS_MAX 256 -#define SG_TIMEOUT 60000 #define pp_ontap_log(prio, fmt, args...) \ condlog(prio, "%s: ontap prio: " fmt, dev, ##args) @@ -72,7 +71,7 @@ static void process_sg_error(struct sg_io_hdr *io_hdr) */ static int send_gva(const char *dev, int fd, unsigned char pg, unsigned char *results, int *results_size, - unsigned int timeout) + unsigned int timeout_ms) { unsigned char sb[128]; unsigned char cdb[10] = {0xc0, 0, 0x1, 0xa, 0x98, 0xa, @@ -90,7 +89,7 @@ static int send_gva(const char *dev, int fd, unsigned char pg, io_hdr.dxferp = results; io_hdr.cmdp = cdb; io_hdr.sbp = sb; - io_hdr.timeout = get_prio_timeout(timeout, SG_TIMEOUT); + 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); @@ -123,7 +122,7 @@ out: * 0: Device _not_ proxy path * 1: Device _is_ proxy path */ -static int get_proxy(const char *dev, int fd, unsigned int timeout) +static int get_proxy(const char *dev, int fd, unsigned int timeout_ms) { unsigned char results[256]; unsigned char sb[128]; @@ -142,7 +141,7 @@ static int get_proxy(const char *dev, int fd, unsigned int timeout) io_hdr.dxferp = results; io_hdr.cmdp = cdb; io_hdr.sbp = sb; - io_hdr.timeout = get_prio_timeout(timeout, SG_TIMEOUT); + 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, " @@ -183,7 +182,7 @@ out: * 2: iSCSI software * 1: FCP proxy */ -static int ontap_prio(const char *dev, int fd, unsigned int timeout) +static int ontap_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char results[RESULTS_MAX]; int results_size=RESULTS_MAX; @@ -196,7 +195,7 @@ static int ontap_prio(const char *dev, int fd, unsigned int timeout) is_iscsi_software = is_iscsi_hardware = is_proxy = 0; memset(&results, 0, sizeof (results)); - rc = send_gva(dev, fd, 0x41, results, &results_size, timeout); + 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) { @@ -221,7 +220,7 @@ static int ontap_prio(const char *dev, int fd, unsigned int timeout) } try_fcp_proxy: - rc = get_proxy(dev, fd, timeout); + rc = get_proxy(dev, fd, timeout_ms); if (rc >= 0) { is_proxy = rc; } @@ -241,8 +240,7 @@ prio_select: } } -int getprio (struct path *pp, __attribute__((unused)) char *args, - unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args) { - return ontap_prio(pp->dev, pp->fd, timeout); + return ontap_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/path_latency.c b/libmultipath/prioritizers/path_latency.c index 2f5be9b..f414257 100644 --- a/libmultipath/prioritizers/path_latency.c +++ b/libmultipath/prioritizers/path_latency.c @@ -104,10 +104,10 @@ static void cleanup_directio_read(int fd, char *buf, int restore_flags) } } -static int do_directio_read(int fd, unsigned int timeout, char *buf, int sz) +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 }; + struct timeval tm = { .tv_sec = timeout_ms / 1000}; int ret; int num_read; @@ -208,7 +208,7 @@ int calcPrio(double lg_avglatency, double lg_maxavglatency, return lg_maxavglatency - lg_avglatency; } -int getprio(struct path *pp, char *args, unsigned int timeout) +int getprio(struct path *pp, char *args) { int rc, temp; int io_num = 0; @@ -247,7 +247,8 @@ int getprio(struct path *pp, char *args, unsigned int timeout) (void)clock_gettime(CLOCK_MONOTONIC, &tv_before); - if (do_directio_read(pp->fd, timeout, buf, blksize)) { + 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; diff --git a/libmultipath/prioritizers/random.c b/libmultipath/prioritizers/random.c index b742ac2..148b957 100644 --- a/libmultipath/prioritizers/random.c +++ b/libmultipath/prioritizers/random.c @@ -6,8 +6,7 @@ #include "prio.h" int getprio(__attribute__((unused)) struct path *pp, - __attribute__((unused)) char *args, - __attribute__((unused)) unsigned int timeout) + __attribute__((unused)) char *args) { struct timeval tv; diff --git a/libmultipath/prioritizers/rdac.c b/libmultipath/prioritizers/rdac.c index 92a2fb8..f40cd84 100644 --- a/libmultipath/prioritizers/rdac.c +++ b/libmultipath/prioritizers/rdac.c @@ -12,7 +12,7 @@ #define pp_rdac_log(prio, msg) condlog(prio, "%s: rdac prio: " msg, dev) -int rdac_prio(const char *dev, int fd, unsigned int timeout) +int rdac_prio(const char *dev, int fd, unsigned int timeout_ms) { unsigned char sense_buffer[128]; unsigned char sb[128]; @@ -31,7 +31,7 @@ int rdac_prio(const char *dev, int fd, unsigned int timeout) io_hdr.dxferp = sense_buffer; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sb; - io_hdr.timeout = get_prio_timeout(timeout, 60000); + 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"); @@ -91,8 +91,7 @@ out: return(ret); } -int getprio (struct path *pp, __attribute__((unused)) char *args, - unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args) { - return rdac_prio(pp->dev, pp->fd, timeout); + return rdac_prio(pp->dev, pp->fd, get_prio_timeout_ms(pp)); } diff --git a/libmultipath/prioritizers/sysfs.c b/libmultipath/prioritizers/sysfs.c index a6feb42..5e8adc0 100644 --- a/libmultipath/prioritizers/sysfs.c +++ b/libmultipath/prioritizers/sysfs.c @@ -36,8 +36,7 @@ int get_exclusive_pref_arg(char *args) return 1; } -int getprio (struct path * pp, char *args, - __attribute__((unused)) unsigned int timeout) +int getprio (struct path * pp, char *args) { int prio = 0, rc, i; char buff[512]; diff --git a/libmultipath/prioritizers/weightedpath.c b/libmultipath/prioritizers/weightedpath.c index 561ebb4..de51a9e 100644 --- a/libmultipath/prioritizers/weightedpath.c +++ b/libmultipath/prioritizers/weightedpath.c @@ -125,8 +125,7 @@ int prio_path_weight(struct path *pp, char *prio_args) return priority; } -int getprio(struct path *pp, char *args, - __attribute__((unused)) unsigned int timeout) +int getprio(struct path *pp, char *args) { return prio_path_weight(pp, args); } diff --git a/libmultipath/prkey.c b/libmultipath/prkey.c index a215499..c66d293 100644 --- a/libmultipath/prkey.c +++ b/libmultipath/prkey.c @@ -157,8 +157,7 @@ static int do_prkey(int fd, char *wwid, char *keystr, int cmd) return 0; } -int get_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) { int fd; int unused; @@ -168,7 +167,7 @@ int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, if (!strlen(mpp->wwid)) goto out; - fd = open_file(conf->prkeys_file, &unused, PRKEYS_FILE_HEADER); + fd = open_file(DEFAULT_PRKEYS_FILE, &unused, PRKEYS_FILE_HEADER); if (fd < 0) goto out; ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_READ); @@ -201,7 +200,7 @@ int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, sa_flags &= MPATH_F_APTPL_MASK; } - fd = open_file(conf->prkeys_file, &can_write, PRKEYS_FILE_HEADER); + fd = open_file(DEFAULT_PRKEYS_FILE, &can_write, PRKEYS_FILE_HEADER); if (fd < 0) goto out; if (!can_write) { diff --git a/libmultipath/prkey.h b/libmultipath/prkey.h index a16de10..43afd5e 100644 --- a/libmultipath/prkey.h +++ b/libmultipath/prkey.h @@ -16,9 +16,8 @@ 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 config *conf, struct multipath *mpp, uint64_t *prkey, - uint8_t *sa_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 */ diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index a25cc92..44241e2 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -35,7 +35,8 @@ pgpolicyfn *pgpolicies[] = { one_group, group_by_serial, group_by_prio, - group_by_node_name + group_by_node_name, + group_by_tpg, }; #define do_set(var, src, dest, msg) \ @@ -249,25 +250,85 @@ out: return 0; } +static bool +verify_alua_prio(struct multipath *mp) +{ + int i; + struct path *pp; + + vector_foreach_slot(mp->paths, pp, i) { + const char *name = prio_name(&pp->prio); + if (strncmp(name, PRIO_ALUA, PRIO_NAME_LEN) && + strncmp(name, PRIO_SYSFS, PRIO_NAME_LEN)) + return false; + } + return true; +} + +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; - char buff[POLICY_NAME_SIZE]; + 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]; - get_pgpolicy_name(buff, POLICY_NAME_SIZE, mp->pgpolicy); - condlog(3, "%s: path_grouping_policy = %s %s", mp->alias, buff, origin); + condlog(log_prio, "%s: path_grouping_policy = %s %s", mp->alias, + get_pgpolicy_name(mp->pgpolicy), origin); return 0; } @@ -340,19 +401,15 @@ int select_alias(struct config *conf, struct multipath * mp) select_alias_prefix(conf, mp); - if (strlen(mp->alias_old) > 0) { - mp->alias = use_existing_alias(mp->wwid, conf->bindings_file, - mp->alias_old, mp->alias_prefix, - conf->bindings_read_only); - memset (mp->alias_old, 0, WWID_SIZE); - origin = "(setting: using existing alias)"; - } + mp->alias = get_user_friendly_alias(mp->wwid, mp->alias_old, mp->alias_prefix, + conf->bindings_read_only); - if (mp->alias == NULL) { - mp->alias = get_user_friendly_alias(mp->wwid, - conf->bindings_file, 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); @@ -567,6 +624,23 @@ out: 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 @@ -603,6 +677,8 @@ int select_checker(struct config *conf, struct path *pp) 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)) { @@ -623,19 +699,7 @@ out: checker_get(c, ckr_name); condlog(3, "%s: path_checker = %s %s", pp->dev, checker_name(c), origin); - if (conf->checker_timeout) { - c->timeout = conf->checker_timeout; - condlog(3, "%s: checker timeout = %u s %s", - pp->dev, c->timeout, conf_origin); - } - else if (sysfs_get_timeout(pp, &c->timeout) > 0) - condlog(3, "%s: checker timeout = %u s (setting: kernel sysfs)", - pp->dev, c->timeout); - else { - c->timeout = DEF_TIMEOUT; - condlog(3, "%s: checker timeout = %u s %s", - pp->dev, c->timeout, default_origin); - } + c->timeout = pp->checker_timeout; return 0; } @@ -745,6 +809,8 @@ int select_prio(struct config *conf, struct path *pp) 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)) { @@ -925,7 +991,7 @@ int select_reservation_key(struct config *conf, struct multipath *mp) out: if (mp->prkey_source == PRKEY_SOURCE_FILE) { from_file = " (from prkeys file)"; - if (get_prkey(conf, mp, &prkey, &mp->sa_flags) != 0) + if (get_prkey(mp, &prkey, &mp->sa_flags) != 0) put_be64(mp->reservation_key, 0); else put_be64(mp->reservation_key, prkey); diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h index 152ca44..73615c2 100644 --- a/libmultipath/propsel.h +++ b/libmultipath/propsel.h @@ -1,5 +1,7 @@ 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); diff --git a/libmultipath/structs.c b/libmultipath/structs.c index 87e84d5..1b305fd 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -125,6 +125,7 @@ alloc_path (void) 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); @@ -146,6 +147,7 @@ uninitialize_path(struct path *pp) pp->dmstate = PSTATE_UNDEF; pp->uid_attribute = NULL; + pp->checker_timeout = 0; if (checker_selected(&pp->checker)) checker_put(&pp->checker); diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 9e2c1ab..a1aac1b 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -143,6 +143,18 @@ enum detect_checker_states { 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, @@ -167,6 +179,13 @@ enum queue_mode_states { QUEUE_MODE_RQ, }; +enum auto_resize_state { + AUTO_RESIZE_UNDEF = 0, + AUTO_RESIZE_NEVER, + AUTO_RESIZE_GROW_ONLY, + AUTO_RESIZE_GROW_SHRINK, +}; + #define PROTOCOL_UNSET -1 enum scsi_protocol { @@ -283,6 +302,12 @@ enum eh_deadline_states { 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, @@ -317,6 +342,8 @@ struct hd_geometry { }; #endif +#define GROUP_ID_UNDEF -1 + struct path { char dev[FILE_NAME_SIZE]; char dev_t[BLK_DEV_SIZE]; @@ -368,16 +395,28 @@ struct path { unsigned int dev_loss; int eh_deadline; bool 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, +}; + 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; @@ -449,7 +488,7 @@ struct multipath { int prkey_source; struct be64 reservation_key; uint8_t sa_flags; - unsigned char prflag; + int prflag; int all_tg_pt; struct gen_multipath generic_mp; bool fpin_must_reload; diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c index 5a61876..0e8a46e 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -267,7 +267,7 @@ int adopt_paths(vector pathvec, struct multipath *mpp) if (mpp->queue_mode == QUEUE_MODE_RQ && pp->bus == SYSFS_BUS_NVME && pp->sg_id.proto_id == NVME_PROTOCOL_TCP) { - condlog(2, "%s: mulitpath device %s created with request queue_mode. Unable to add nvme:tcp paths", + condlog(2, "%s: multipath device %s created with request queue_mode. Unable to add nvme:tcp paths", pp->dev, mpp->alias); continue; } @@ -392,10 +392,8 @@ remove_maps(struct vectors * vecs) if (!vecs) return; - vector_foreach_slot (vecs->mpvec, mpp, i) { - remove_map(mpp, vecs->pathvec, vecs->mpvec); - i--; - } + vector_foreach_slot (vecs->mpvec, mpp, i) + remove_map(mpp, vecs->pathvec, NULL); vector_free(vecs->mpvec); vecs->mpvec = NULL; diff --git a/libmultipath/switchgroup.c b/libmultipath/switchgroup.c index 6fdfcfa..b1e1f39 100644 --- a/libmultipath/switchgroup.c +++ b/libmultipath/switchgroup.c @@ -12,6 +12,7 @@ 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; @@ -24,12 +25,17 @@ void path_group_prio_update(struct pathgroup *pgp) marginal++; if (pp->state == PATH_UP || pp->state == PATH_GHOST) { - priority += pp->priority; + if (pp->priority != PRIO_UNDEF) { + defined_prios++; + priority += pp->priority; + } pgp->enabled_paths++; } } - if (pgp->enabled_paths) - pgp->priority = 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) diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c index c45296a..ad3d661 100644 --- a/libmultipath/sysfs.c +++ b/libmultipath/sysfs.c @@ -175,7 +175,6 @@ sysfs_get_size (struct path *pp, unsigned long long * size) if (r != 1) { condlog(3, "%s: Cannot parse size attribute", pp->dev); - *size = 0; return 1; } diff --git a/libmultipath/valid.c b/libmultipath/valid.c index a6aa921..f223778 100644 --- a/libmultipath/valid.c +++ b/libmultipath/valid.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "vector.h" #include "config.h" @@ -30,12 +32,271 @@ #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 *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 *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 *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 *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 *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; @@ -53,23 +314,11 @@ is_path_valid(const char *name, struct config *conf, struct path *pp, return PATH_IS_VALID_NO_CHECK; } - /* - * "multipath -u" may be run before the daemon is started. In this - * case, systemd might own the socket but might delay multipathd - * startup until some other unit (udev settle!) has finished - * starting. With many LUNs, the listen backlog may be exceeded, which - * would cause connect() to block. This causes udev workers calling - * "multipath -u" to hang, and thus creates a deadlock, until "udev - * settle" times out. To avoid this, call connect() in non-blocking - * mode here, and take EAGAIN as indication for a filled-up systemd - * backlog. - */ - if (check_multipathd) { fd = __mpath_connect(1); if (fd < 0) { - if (errno != EAGAIN && !systemd_service_enabled(name)) { - condlog(3, "multipathd not running or enabled"); + if (errno != EAGAIN) { + condlog(3, "multipathd not running"); return PATH_IS_NOT_VALID; } } else @@ -80,6 +329,10 @@ is_path_valid(const char *name, struct config *conf, struct path *pp, 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; @@ -96,6 +349,11 @@ is_path_valid(const char *name, struct config *conf, struct path *pp, 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; diff --git a/libmultipath/version.h b/libmultipath/version.h index 2da0045..9c549c1 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,9 +20,9 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION_CODE 0x000904 +#define VERSION_CODE 0x000907 /* MMDDYY, in hex */ -#define DATE_CODE 0x0C1316 +#define DATE_CODE 0x0B1017 #define PROG "multipath-tools" diff --git a/libmultipath/wwids.c b/libmultipath/wwids.c index 89bb60c..591cd09 100644 --- a/libmultipath/wwids.c +++ b/libmultipath/wwids.c @@ -94,12 +94,8 @@ replace_wwids(vector mp) struct multipath * mpp; size_t len; int ret = -1; - struct config *conf; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) goto out; @@ -200,7 +196,6 @@ remove_wwid(char *wwid) { int len, can_write; char *str; int ret = -1; - struct config *conf; len = strlen(wwid) + 4; /* two slashes the newline and a zero byte */ str = malloc(len); @@ -216,10 +211,7 @@ remove_wwid(char *wwid) { goto out; } condlog(3, "removing line '%s' from wwids file", str); - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) { ret = -1; @@ -244,12 +236,8 @@ check_wwids_file(char *wwid, int write_wwid) { int fd, can_write, found, ret; FILE *f; - struct config *conf; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) return -1; diff --git a/mpathpersist/Makefile b/mpathpersist/Makefile index f57c105..f374946 100644 --- a/mpathpersist/Makefile +++ b/mpathpersist/Makefile @@ -8,10 +8,11 @@ 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) +all: $(EXEC) $(MANPAGES) $(EXEC): $(OBJS) $(Q)$(CC) $(OBJS) -o $(EXEC) $(LDFLAGS) $(CFLAGS) $(LIBDEPS) @@ -23,7 +24,7 @@ install: $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) + $(Q)$(RM) core *.o $(EXEC) $(MANPAGES) include $(wildcard $(OBJS:.o=.d)) diff --git a/mpathpersist/mpathpersist.8 b/mpathpersist/mpathpersist.8 deleted file mode 100644 index d594422..0000000 --- a/mpathpersist/mpathpersist.8 +++ /dev/null @@ -1,300 +0,0 @@ -.\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t mpathpersist/mpathpersist.8 -.\" -.\" ---------------------------------------------------------------------------- -. -.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/etc/multipath.conf\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 diff --git a/mpathpersist/mpathpersist.8.in b/mpathpersist/mpathpersist.8.in new file mode 100644 index 0000000..fecef0d --- /dev/null +++ b/mpathpersist/mpathpersist.8.in @@ -0,0 +1,301 @@ +.\" ---------------------------------------------------------------------------- +.\" 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 diff --git a/multipath/11-dm-mpath.rules b/multipath/11-dm-mpath.rules index d191ae8..c339f52 100644 --- a/multipath/11-dm-mpath.rules +++ b/multipath/11-dm-mpath.rules @@ -14,7 +14,7 @@ ENV{.MPATH_DEVICE_READY_OLD}="$env{MPATH_DEVICE_READY}" # 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",\ +ENV{DM_SUBSYSTEM_UDEV_FLAG2}=="1", ENV{MPATH_DEVICE_READY}="0", \ GOTO="mpath_action" # If the last path has failed mark the device not ready @@ -68,13 +68,13 @@ ENV{MPATH_DEVICE_READY}=="0", ENV{DM_NOSCAN}="1" # Also skip all foreign rules if no path is available. # Remember the original value of DM_DISABLE_OTHER_RULES_FLAG # and restore it back once we have at least one path available. -ENV{MPATH_DEVICE_READY}=="0", ENV{.MPATH_DEVICE_READY_OLD}=="1",\ - ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}=="",\ +ENV{MPATH_DEVICE_READY}=="0", ENV{.MPATH_DEVICE_READY_OLD}=="1", \ + ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}=="", \ ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="$env{DM_UDEV_DISABLE_OTHER_RULES_FLAG}" ENV{MPATH_DEVICE_READY}=="0", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" -ENV{MPATH_DEVICE_READY}!="0", ENV{.MPATH_DEVICE_READY_OLD}=="0",\ - ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{DM_DISABLE_OTHER_RULES_FLAG_OLD}",\ - ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="",\ +ENV{MPATH_DEVICE_READY}!="0", ENV{.MPATH_DEVICE_READY_OLD}=="0", \ + ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{DM_DISABLE_OTHER_RULES_FLAG_OLD}", \ + ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="", \ ENV{DM_ACTIVATION}="1", ENV{MPATH_UNCHANGED}="0" # The code to check multipath state ends here. We need to set diff --git a/multipath/Makefile b/multipath/Makefile index 73db991..0efb9b2 100644 --- a/multipath/Makefile +++ b/multipath/Makefile @@ -3,7 +3,9 @@ # include ../Makefile.inc -EXEC := multipath +EXEC := multipath +MANPAGES := multipath.8 multipath.conf.5 +GENERATED := $(MANPAGES) multipath.rules tmpfiles.conf CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) CFLAGS += $(BIN_CFLAGS) @@ -13,7 +15,7 @@ LIBDEPS += -L$(multipathdir) -lmultipath -L$(mpathutildir) -lmpathutil \ OBJS := main.o -all: $(EXEC) multipath.rules tmpfiles.conf +all: $(EXEC) $(GENERATED) $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? @@ -25,14 +27,16 @@ install: $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 11-dm-mpath.rules $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 multipath.rules $(DESTDIR)$(udevrulesdir)/56-multipath.rules - $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(modulesloaddir) - $(Q)$(INSTALL_PROGRAM) -m 644 modules-load.conf $(DESTDIR)$(modulesloaddir)/multipath.conf $(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 \ @@ -47,15 +51,12 @@ uninstall: $(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) multipath.rules tmpfiles.conf + $(Q)$(RM) core *.o $(EXEC) $(GENERATED) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) - -%: %.in - @echo creating $@ - $(Q)sed 's,@RUNTIME_DIR@,$(runtimedir),' $< >$@ diff --git a/multipath/main.c b/multipath/main.c index b9f360b..9e1c505 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -157,6 +157,7 @@ usage (char * progname) " . 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" @@ -607,6 +608,8 @@ check_path_valid(const char *name, struct config *conf, bool is_uevent) 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) @@ -840,6 +843,8 @@ main (int argc, char *argv[]) conf->force_sync = 1; 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': @@ -851,7 +856,7 @@ main (int argc, char *argv[]) libmp_verbosity = atoi(optarg); break; case 'b': - conf->bindings_file = strdup(optarg); + condlog(1, "option -b ignored"); break; case 'B': conf->bindings_read_only = 1; @@ -1020,7 +1025,7 @@ main (int argc, char *argv[]) } if (check_alias_settings(conf)) { - fprintf(stderr, "fatal configuration error, aborting"); + fprintf(stderr, "fatal configuration error, aborting\n"); exit(RTVL_FAIL); } diff --git a/multipath/multipath.8 b/multipath/multipath.8 deleted file mode 100644 index 88149d5..0000000 --- a/multipath/multipath.8 +++ /dev/null @@ -1,294 +0,0 @@ -.\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipath/multipath.8 -.\" -.\" ---------------------------------------------------------------------------- -. -.TH MULTIPATH 8 2021-11-12 "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 \fImultipath.conf\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 -\fImultipath.conf\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 multipath.conf (5). -This option should only be used in rare circumstances. -. -.TP -.B \-B -Treat the bindings file as read only. -. -.TP -.BI \-b " file" -Set \fIuser_friendly_names\fR bindings file location. The default is -\fI/etc/multipath/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 diff --git a/multipath/multipath.8.in b/multipath/multipath.8.in new file mode 100644 index 0000000..348eb22 --- /dev/null +++ b/multipath/multipath.8.in @@ -0,0 +1,295 @@ +.\" ---------------------------------------------------------------------------- +.\" 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 2021-11-12 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 diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 deleted file mode 100644 index b4dccd1..0000000 --- a/multipath/multipath.conf.5 +++ /dev/null @@ -1,1974 +0,0 @@ -.\" ---------------------------------------------------------------------------- -.\" 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 2022-10-01 Linux -. -. -.\" ---------------------------------------------------------------------------- -.SH NAME -.\" ---------------------------------------------------------------------------- -. -multipath.conf \- multipath daemon configuration file. -. -. -.\" ---------------------------------------------------------------------------- -.SH DESCRIPTION -.\" ---------------------------------------------------------------------------- -. -.B "/etc/multipath.conf" -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. -. -. -.\" ---------------------------------------------------------------------------- -.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 \fImultipath.conf\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 any more, and the value is 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 -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 -The default is: \fBfailover\fR -.RE -. -. -.TP -.B pg_timeout -(Deprecated) This option is not supported any more, and the value is 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 then 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 any more, and the value is 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, [2, 200]. -.TP -.I base_num -The base number value of logarithmic scale, used to partition different priority ranks. Valid Values: Integer, -[2, 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 -Issue a \fITEST UNIT READY\fR command to the 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 fds 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 yes -, multipathd will disable queueing when the last path to a device has been -deleted. -.RS -.TP -The default is: \fBno\fR -.RE -. -. -.TP -.B user_friendly_names -If set to -.I yes -, using the bindings file \fI/etc/multipath/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 do 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 bindings_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the binding file to be used when the user_friendly_names -option is set. -.RS -.TP -The default is: \fB/etc/multipath/bindings\fR -.RE -. -. -.TP -.B wwids_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the WWIDs file, which is used by multipath to keep track -of the WWIDs for LUNs it has created multipath devices on in the past. -.RS -.TP -The default is: \fB/etc/multipath/wwids\fR -.RE -. -. -.TP -.B prkeys_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the prkeys file, which is used by multipathd to keep -track of the persistent reservation key used for a specific WWID, when -\fIreservation_key\fR is set to \fBfile\fR. -.RS -.TP -The default is: \fB/etc/multipath/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 -multipath.conf. 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 sysf -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 any more, and the value is ignored. -. -. -.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 reinstantate. 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 no -Multipath behaves like \fBstrict\fR. Multipathd behaves like \fBgreedy\fR. -.TP -.I yes -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. -.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 any more, and the value is 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 max_sectors_kb device parameter on all path devices and the multipath -device to the specified value. -.RS -.TP -The default is: in \fB/sys/block//queue/max_sectors_kb\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 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 any more. 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 \fImultipath.conf\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 multipathd (8). -. -. -.\" ---------------------------------------------------------------------------- -.SH AUTHORS -.\" ---------------------------------------------------------------------------- -. -\fImultipath-tools\fR was developed by Christophe Varoqui, -and others. -.\" EOF diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in new file mode 100644 index 0000000..683bdb7 --- /dev/null +++ b/multipath/multipath.conf.5.in @@ -0,0 +1,2039 @@ +.\" ---------------------------------------------------------------------------- +.\" 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 2023-06-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 any more, and the value is 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 +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 any more, and the value is 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 then 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 any more, and the value is 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, [2, 200]. +.TP +.I base_num +The base number value of logarithmic scale, used to partition different priority ranks. Valid Values: Integer, +[2, 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 +Issue a \fITEST UNIT READY\fR command to the 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 fds 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 yes +, multipathd will disable queueing when the last path to a device has been +deleted. +.RS +.TP +The default is: \fBno\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 any more, 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 any more, 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 any more, 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 sysf +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 any more, and the value is 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 reinstantate. 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 no +Multipath behaves like \fBstrict\fR. Multipathd behaves like \fBgreedy\fR. +.TP +.I yes +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. +.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 any more, and the value is 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 max_sectors_kb device parameter on all path devices and the multipath +device to the specified value. +.RS +.TP +The default is: in \fB/sys/block//queue/max_sectors_kb\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 any more. 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 diff --git a/multipath/multipath.rules.in b/multipath/multipath.rules.in index 8d3cf33..6f12376 100644 --- a/multipath/multipath.rules.in +++ b/multipath/multipath.rules.in @@ -31,7 +31,8 @@ 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}="$env{MPATH_SBIN_PATH}/multipath -u %k" +IMPORT{program}=="$env{MPATH_SBIN_PATH}/multipath -u %k", \ + ENV{.MPATH_CHECK_PASSED}="1" # case 1: this is definitely multipath ENV{DM_MULTIPATH_DEVICE_PATH}=="1", \ @@ -82,10 +83,19 @@ LABEL="stop_wait" # If timeout hasn't expired but we're not in "maybe" state any more, 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}!="?*", \ - ENV{FIND_MULTIPATHS_WAIT_UNTIL}=="?*", \ - ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="0", \ - ENV{FIND_MULTIPATHS_WAIT_CANCELLED}="1", \ - RUN+="/usr/bin/systemctl stop cancel-multipath-wait-$kernel.timer" +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+="/usr/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" diff --git a/multipathd/Makefile b/multipathd/Makefile index 9d53132..997b40c 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -1,11 +1,10 @@ include ../Makefile.inc -EXEC := multipathd -CLI := multipathc +EXEC := multipathd +CLI := multipathc +MANPAGES := multipathd.8 CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) -I$(mpathcmddir) -I$(thirdpartydir) \ - $(shell $(PKGCONFIG) --modversion liburcu 2>/dev/null | \ - awk -F. '{ printf("-DURCU_VERSION=0x%06x", 256 * ( 256 * $$1 + $$2) + $$3); }') \ -DBINDIR='"$(bindir)"' $(SYSTEMD_CPPFLAGS) # @@ -42,7 +41,7 @@ ifeq ($(FPIN_SUPPORT),1) OBJS += fpin_handlers.o endif -all : $(EXEC) $(CLI) +all : $(EXEC) $(CLI) $(MANPAGES) $(EXEC).service $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? @@ -79,9 +78,9 @@ uninstall: $(Q)$(RM) $(DESTDIR)$(unitdir)/$(EXEC).socket clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) $(CLI) + $(Q)$(RM) core *.o $(EXEC) $(CLI) $(MANPAGES) $(EXEC).service -include $(wildcard $(OBJS:.o=.d)) +include $(wildcard $(OBJS:.o=.d) $(CLI_OBJS:.o=.d)) dep_clean: - $(Q)$(RM) $(OBJS:.o=.d) + $(Q)$(RM) $(OBJS:.o=.d) $(CLI_OBJS:.o=.d) diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index e65fb75..420d75d 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -760,7 +760,7 @@ cli_del_map (void * v, struct strbuf *reply, void * data) } rc = ev_remove_map(param, alias, minor, vecs); if (rc == 2) - append_strbuf_str(reply, "delayed"); + append_strbuf_str(reply, "delayed\n"); free(alias); return rc; @@ -810,32 +810,7 @@ cli_reload(void *v, struct strbuf *reply, void *data) return 1; } - return reload_and_sync_map(mpp, vecs, 0); -} - -static int resize_map(struct multipath *mpp, unsigned long long size, - struct vectors * vecs) -{ - 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; - return 1; - } - 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; - return 1; - } - return 0; + return reload_and_sync_map(mpp, vecs); } static int @@ -845,9 +820,11 @@ cli_resize(void *v, struct strbuf *reply, void *data) char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; - unsigned long long size; + 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); @@ -867,21 +844,24 @@ cli_resize(void *v, struct strbuf *reply, void *data) return 1; } - pgp = VECTOR_SLOT(mpp->pg, 0); - - if (!pgp){ - condlog(0, "%s: couldn't get path group. cannot resize", - mapname); - 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; + } } - pp = VECTOR_SLOT(pgp->paths, 0); - - if (!pp){ - condlog(0, "%s: couldn't get path. cannot resize", mapname); + if (!size) { + condlog(0, "%s: couldn't get size from sysfs. cannot resize", + mapname); return 1; } - if (!pp->udev || sysfs_get_size(pp, &size)) { - condlog(0, "%s: couldn't get size for sysfs. cannot resize", + if (mismatch) { + condlog(0, "%s: path size not consistent. cannot resize", mapname); return 1; } @@ -893,14 +873,12 @@ cli_resize(void *v, struct strbuf *reply, void *data) condlog(3, "%s old size is %llu, new size is %llu", mapname, mpp->size, size); - if (resize_map(mpp, size, vecs) != 0) - return 1; + ret = resize_map(mpp, size, vecs); - if (setup_multipath(vecs, mpp) != 0) - return 1; - sync_map_state(mpp); + if (ret == 2) + condlog(0, "%s: map removed while trying to resize", mapname); - return 0; + return (ret != 0); } static int @@ -1277,6 +1255,11 @@ cli_shutdown (void * v, struct strbuf *reply, void * data) 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); @@ -1287,10 +1270,7 @@ cli_getprstatus (void * v, struct strbuf *reply, void * data) if (!mpp) return 1; - condlog(3, "%s: prflag = %u", param, (unsigned int)mpp->prflag); - - if (print_strbuf(reply, "%d", mpp->prflag) < 0) - return 1; + append_strbuf_str(reply, prflag_str[mpp->prflag]); condlog(3, "%s: reply = %s", param, get_strbuf_str(reply)); @@ -1310,8 +1290,8 @@ cli_setprstatus(void * v, struct strbuf *reply, void * data) if (!mpp) return 1; - if (!mpp->prflag) { - mpp->prflag = 1; + if (mpp->prflag != PRFLAG_SET) { + mpp->prflag = PRFLAG_SET; condlog(2, "%s: prflag set", param); } @@ -1332,8 +1312,8 @@ cli_unsetprstatus(void * v, struct strbuf *reply, void * data) if (!mpp) return 1; - if (mpp->prflag) { - mpp->prflag = 0; + if (mpp->prflag != PRFLAG_UNSET) { + mpp->prflag = PRFLAG_UNSET; condlog(2, "%s: prflag unset", param); } @@ -1447,7 +1427,7 @@ static int cli_set_marginal(void * v, struct strbuf *reply, void * data) } pp->marginal = 1; - return reload_and_sync_map(pp->mpp, vecs, 0); + return reload_and_sync_map(pp->mpp, vecs); } static int cli_unset_marginal(void * v, struct strbuf *reply, void * data) @@ -1474,7 +1454,7 @@ static int cli_unset_marginal(void * v, struct strbuf *reply, void * data) } pp->marginal = 0; - return reload_and_sync_map(pp->mpp, vecs, 0); + return reload_and_sync_map(pp->mpp, vecs); } static int cli_unset_all_marginal(void * v, struct strbuf *reply, void * data) @@ -1511,7 +1491,7 @@ static int cli_unset_all_marginal(void * v, struct strbuf *reply, void * data) vector_foreach_slot (pgp->paths, pp, j) pp->marginal = 0; - return reload_and_sync_map(mpp, vecs, 0); + return reload_and_sync_map(mpp, vecs); } #define HANDLER(x) x diff --git a/multipathd/fpin_handlers.c b/multipathd/fpin_handlers.c index 8f464f0..be087ca 100644 --- a/multipathd/fpin_handlers.c +++ b/multipathd/fpin_handlers.c @@ -60,18 +60,15 @@ static void _udev_device_unref(void *p) /*set/unset the path state to marginal*/ -static int fpin_set_pathstate(struct path *pp, bool set) +static void fpin_set_pathstate(struct path *pp, bool set) { const char *action = set ? "set" : "unset"; - if (!pp || !pp->mpp || !pp->mpp->alias) - return -1; - - condlog(3, "\n%s: %s marginal path %s (fpin)", - action, pp->mpp->alias, pp->dev_t); + condlog(3, "%s: %s marginal path %s (fpin)", + pp->mpp ? pp->mpp->alias : "orphan", action, pp->dev_t); pp->marginal = set; - pp->mpp->fpin_must_reload = true; - return 0; + if (pp->mpp) + pp->mpp->fpin_must_reload = true; } /* This will unset marginal state of a device*/ @@ -82,14 +79,14 @@ static void fpin_path_unsetmarginal(char *devname, struct vectors *vecs) pp = find_path_by_dev(vecs->pathvec, devname); if (!pp) pp = find_path_by_devt(vecs->pathvec, devname); - - fpin_set_pathstate(pp, false); + if (pp) + fpin_set_pathstate(pp, false); } /*This will set the marginal state of a device*/ -static int fpin_path_setmarginal(struct path *pp) +static void fpin_path_setmarginal(struct path *pp) { - return fpin_set_pathstate(pp, true); + fpin_set_pathstate(pp, true); } /* Unsets all the devices in the list from marginal state */ @@ -127,7 +124,7 @@ empty: /* 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, 0); + ret = reload_and_sync_map(mpp, vecs); if (ret == 2) condlog(2, "map removed during reload"); else @@ -183,8 +180,8 @@ static void fpin_set_rport_marginal(struct udev_device *rport_dev) udev_device_get_syspath(rport_dev)); } -/*Add the marginal devices info into the list*/ -static void +/*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; @@ -199,70 +196,165 @@ fpin_add_marginal_dev_info(uint32_t host_num, char *devname) 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 goes through the vecs->pathvec, and for - * each path, check that the host number, - * the target WWPN associated with the path matches - * with the els wwpn and sets the path and port state to + * 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 int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *vecs, +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) { - struct path *pp; - struct multipath *mpp; - int i, k; 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) { - /* Checks the host number and also for the SCSI FCP */ - if (pp->bus != SYSFS_BUS_SCSI || pp->sg_id.proto_id != SCSI_PROTOCOL_FCP || host_num != pp->sg_id.host_no) + if (!pp->mpp) continue; - 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); - continue; - } - pthread_cleanup_push(_udev_device_unref, rport_dev); - value = udev_device_get_sysattr_value(rport_dev, "port_name"); - if (!value) - goto unref; - - if (value) - wwpn = strtol(value, NULL, 16); - /* - * If the port wwpn matches sets the path and port state - * to marginal - */ - if (wwpn == els_wwpn) { - ret = fpin_path_setmarginal(pp); - if (ret < 0) - goto unref; - fpin_set_rport_marginal(rport_dev); - fpin_add_marginal_dev_info(host_num, pp->dev); + /*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); } -unref: - 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, 0); + ret = reload_and_sync_map(mpp, vecs); if (ret == 2) condlog(2, "map removed during reload"); else @@ -286,14 +378,18 @@ fpin_parse_li_els_setpath_marginal(uint16_t host_num, struct fc_tlv_desc *tlv, 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); - condlog(4, "Got wwn count as %d\n", wwn_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); + 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); diff --git a/multipathd/main.c b/multipathd/main.c index 1e1b254..230c9d1 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -395,34 +395,51 @@ 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, int refresh) +need_switch_pathgroup (struct multipath * mpp, bool *need_reload) { - struct pathgroup * pgp; - struct path * pp; - unsigned int i, j; - struct config *conf; int bestpg; + *need_reload = false; if (!mpp) return 0; - /* - * Refresh path priority values - */ - if (refresh) { - vector_foreach_slot (mpp->pg, pgp, i) { - vector_foreach_slot (pgp->paths, pp, j) { - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, - conf); - pathinfo(pp, conf, DI_PRIO); - pthread_cleanup_pop(1); - } - } - } - - if (!mpp->pg || VECTOR_SIZE(mpp->paths) == 0) + if (VECTOR_SIZE(mpp->pg) < 2) return 0; bestpg = select_path_group(mpp); @@ -430,10 +447,9 @@ need_switch_pathgroup (struct multipath * mpp, int refresh) return 0; mpp->bestpg = bestpg; - if (mpp->bestpg != mpp->nextpg) - return 1; + *need_reload = !path_groups_in_order(mpp); - return 0; + return (*need_reload || mpp->bestpg != mpp->nextpg); } static void @@ -586,13 +602,26 @@ flush_map_nopaths(struct multipath *mpp, struct vectors *vecs) { return false; } +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; - struct path *pp; - int i; retry: condlog(4, "%s: updating new map", mpp->alias); @@ -609,15 +638,6 @@ retry: mpp->action = ACT_RELOAD; - if (mpp->prflag) { - vector_foreach_slot(mpp->paths, pp, i) { - if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) { - /* persistent reservation check*/ - mpath_pr_event_handle(pp); - } - } - } - if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup new map in update", mpp->alias); retries = -1; @@ -643,6 +663,11 @@ fail: 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; @@ -1191,6 +1216,7 @@ ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) int start_waiter = 0; int ret; int ro; + unsigned char prflag = PRFLAG_UNSET; /* * need path UID to go any further @@ -1234,6 +1260,8 @@ rescan: 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"); @@ -1252,9 +1280,6 @@ rescan: goto fail; /* leave path added to pathvec */ } - /* persistent reservation check*/ - mpath_pr_event_handle(pp); - /* ro check - if new path is ro, force map to be ro as well */ ro = sysfs_get_ro(pp); if (ro == 1) @@ -1319,6 +1344,10 @@ rescan: 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; @@ -1501,6 +1530,35 @@ needs_ro_update(struct multipath *mpp, int ro) return true; } +int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors * vecs) +{ + 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; + return 1; + } + 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; + return 1; + } + if (setup_multipath(vecs, mpp) != 0) + return 2; + sync_map_state(mpp); + + return 0; +} + static int uev_update_path (struct uevent *uev, struct vectors * vecs) { @@ -1532,6 +1590,11 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) 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; @@ -1581,7 +1644,7 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) else { if (ro == 1) pp->mpp->force_readonly = 1; - retval = reload_and_sync_map(mpp, vecs, 0); + retval = reload_and_sync_map(mpp, vecs); if (retval == 2) condlog(2, "%s: map removed during reload", pp->dev); else { @@ -1590,6 +1653,29 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) } } } + if (auto_resize != AUTO_RESIZE_NEVER && + !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); @@ -1969,20 +2055,26 @@ ghost_delay_tick(struct vectors *vecs) } static void -deferred_failback_tick (vector mpvec) +deferred_failback_tick (struct vectors *vecs) { struct multipath * mpp; unsigned int i; + bool need_reload; - vector_foreach_slot (mpvec, mpp, i) { + 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, 1)) - switch_pathgroup(mpp); + if (!mpp->failback_tick && + need_switch_pathgroup(mpp, &need_reload)) { + if (need_reload) + reload_and_sync_map(mpp, vecs); + else + switch_pathgroup(mpp); + } } } } @@ -2038,54 +2130,40 @@ int update_prio(struct path *pp, int refresh_all) int i, j, changed = 0; struct config *conf; - if (refresh_all) { - vector_foreach_slot (pp->mpp->pg, pgp, i) { - vector_foreach_slot (pgp->paths, pp1, j) { - oldpriority = pp1->priority; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, - conf); - pathinfo(pp1, conf, DI_PRIO); - pthread_cleanup_pop(1); - if (pp1->priority != oldpriority) - changed = 1; - } - } - return changed; - } oldpriority = pp->priority; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - if (pp->state != PATH_DOWN) + if (pp->state != PATH_DOWN) { + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); pathinfo(pp, conf, DI_PRIO); - pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + } - if (pp->priority == oldpriority) + if (pp->priority == oldpriority && !refresh_all) return 0; - return 1; + + vector_foreach_slot (pp->mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp1, j) { + if (pp1 == pp || pp1->state == PATH_DOWN) + continue; + oldpriority = pp1->priority; + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); + pathinfo(pp1, conf, DI_PRIO); + pthread_cleanup_pop(1); + if (pp1->priority != oldpriority) + changed = 1; + } + } + return changed; } -static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, +static int reload_map(struct vectors *vecs, struct multipath *mpp, int is_daemon) { char *params __attribute__((cleanup(cleanup_charp))) = NULL; - struct path *pp; - int i, r; + int r; update_mpp_paths(mpp, vecs->pathvec); - if (refresh) { - vector_foreach_slot (mpp->paths, pp, i) { - struct config *conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - r = pathinfo(pp, conf, DI_PRIO); - pthread_cleanup_pop(1); - if (r) { - condlog(2, "%s: failed to refresh pathinfo", - mpp->alias); - return 1; - } - } - } if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup map", mpp->alias); return 1; @@ -2102,10 +2180,9 @@ static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, return 0; } -int reload_and_sync_map(struct multipath *mpp, - struct vectors *vecs, int refresh) +int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs) { - if (reload_map(vecs, mpp, refresh, 1)) + if (reload_map(vecs, mpp, 1)) return 1; if (setup_multipath(vecs, mpp) != 0) return 2; @@ -2240,6 +2317,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) struct config *conf; int marginal_pathgroups, marginal_changed = 0; int ret; + bool need_reload; if (((pp->initialized == INIT_OK || pp->initialized == INIT_PARTIAL || pp->initialized == INIT_REQUESTED_UDEV) && !pp->mpp) || @@ -2479,13 +2557,17 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) } if (newstate == PATH_UP || newstate == PATH_GHOST) { - if (pp->mpp->prflag) { + 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); } } @@ -2556,25 +2638,30 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) */ condlog(4, "path prio refresh"); - if (marginal_changed) - reload_and_sync_map(pp->mpp, vecs, 1); - else if (update_prio(pp, new_path_up) && - (pp->mpp->pgpolicyfn == (pgpolicyfn *)group_by_prio) && - pp->mpp->pgfailback == -FAILBACK_IMMEDIATE) { + if (marginal_changed) { + update_prio(pp, 1); + reload_and_sync_map(pp->mpp, vecs); + } else if (update_prio(pp, new_path_up) && + pp->mpp->pgpolicyfn == (pgpolicyfn *)group_by_prio && + pp->mpp->pgfailback == -FAILBACK_IMMEDIATE) { condlog(2, "%s: path priorities changed. reloading", pp->mpp->alias); - reload_and_sync_map(pp->mpp, vecs, !new_path_up); - } else if (need_switch_pathgroup(pp->mpp, 0)) { + reload_and_sync_map(pp->mpp, vecs); + } else if (need_switch_pathgroup(pp->mpp, &need_reload)) { if (pp->mpp->pgfailback > 0 && (new_path_up || pp->mpp->failback_tick <= 0)) - pp->mpp->failback_tick = - pp->mpp->pgfailback + 1; + pp->mpp->failback_tick = pp->mpp->pgfailback + 1; else if (pp->mpp->pgfailback == -FAILBACK_IMMEDIATE || - (chkr_new_path_up && followover_should_failback(pp))) - switch_pathgroup(pp->mpp); + (chkr_new_path_up && followover_should_failback(pp))) { + if (need_reload) + reload_and_sync_map(pp->mpp, vecs); + else + switch_pathgroup(pp->mpp); + } } return 1; } + enum checker_state { CHECKER_STARTING, CHECKER_RUNNING, @@ -2696,7 +2783,7 @@ unlock: pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); - deferred_failback_tick(vecs->mpvec); + deferred_failback_tick(vecs); retry_count_tick(vecs->mpvec); missing_uev_wait_tick(vecs); ghost_delay_tick(vecs); @@ -2852,6 +2939,8 @@ configure (struct vectors * vecs, enum force_reload_types reload_type) 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); } /* @@ -3293,6 +3382,7 @@ static void cleanup_child(void) { cleanup_threads(); cleanup_vecs(); + cleanup_bindings(); if (poll_dmevents) cleanup_dmevent_waiter(); @@ -3773,6 +3863,7 @@ void * mpath_pr_event_handler_fn (void * pathp ) goto out; } + mpp->prflag = PRFLAG_UNSET; ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, 0); if (ret != MPATH_PR_SUCCESS ) { @@ -3825,7 +3916,7 @@ void * mpath_pr_event_handler_fn (void * pathp ) { condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret); } - mpp->prflag = 1; + mpp->prflag = PRFLAG_SET; free(param); out: @@ -3843,12 +3934,12 @@ int mpath_pr_event_handle(struct path *pp) struct multipath * mpp; if (pp->bus != SYSFS_BUS_SCSI) - return 0; + goto no_pr; mpp = pp->mpp; if (!get_be64(mpp->reservation_key)) - return -1; + goto no_pr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); @@ -3861,4 +3952,8 @@ int mpath_pr_event_handle(struct path *pp) pthread_attr_destroy(&attr); rc = pthread_join(thread, NULL); return 0; + +no_pr: + pp->mpp->prflag = PRFLAG_UNSET; + return 0; } diff --git a/multipathd/main.h b/multipathd/main.h index e8bee8e..8a178c0 100644 --- a/multipathd/main.h +++ b/multipathd/main.h @@ -47,10 +47,11 @@ int __setup_multipath (struct vectors * vecs, struct multipath * mpp, int reset); #define setup_multipath(vecs, mpp) __setup_multipath(vecs, mpp, 1) int update_multipath (struct vectors *vecs, char *mapname, int reset); -int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs, - int refresh); +int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs); void 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 */ diff --git a/multipathd/multipathc.8 b/multipathd/multipathc.8 index 6c57c6c..cf7ae5b 100644 --- a/multipathd/multipathc.8 +++ b/multipathd/multipathc.8 @@ -1,8 +1,9 @@ .\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. .\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipathd/multipathd.8 +.\" 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 diff --git a/multipathd/multipathd.8 b/multipathd/multipathd.8 deleted file mode 100644 index bdf102e..0000000 --- a/multipathd/multipathd.8 +++ /dev/null @@ -1,378 +0,0 @@ -.\" ---------------------------------------------------------------------------- -.\" Update the date below if you make any significant change. -.\" Make sure there are no errors with: -.\" groff -z -wall -b -e -t multipathd/multipathd.8 -.\" -.\" ---------------------------------------------------------------------------- -. -.TH MULTIPATHD 8 2022-09-03 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 format $format -Show the paths that multipathd is monitoring, using a format string with path -format wildcards. -. -.TP -.B list|show maps|multipaths -Show the multipath devices that the multipathd is monitoring. -. -.TP -.B list|show maps|multipaths format $format -Show the status of all multipath devices that the multipathd is monitoring, -using a format string with multipath format wildcards. -. -.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 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 wildcards -Show the format wildcards used in interactive commands taking $format. -. -.TP -.B list|show config -Show the currently used configuration, derived from default values and values -specified within the configuration file \fI/etc/multipath.conf\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/etc/multipath.conf\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 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 map|multipath $map -Stop monitoring a multipath device. -. -.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 on all multipath devices. -. -.TP -.B disablequeueing map|multipath $map -Disable queuing on multipathed map $map. -. -.TP -.B restorequeueing map|multipath $map -Restore queuing on multipahted map $map. -. -.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/etc/multipath.conf\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/etc/multipath.conf\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 "SYSTEMD INTEGRATION" -.\" ---------------------------------------------------------------------------- -. -When compiled with systemd support two systemd service files are installed, -\fImultipathd.service\fR and \fImultipathd.socket\fR 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 diff --git a/multipathd/multipathd.8.in b/multipathd/multipathd.8.in new file mode 100644 index 0000000..e98c27f --- /dev/null +++ b/multipathd/multipathd.8.in @@ -0,0 +1,379 @@ +.\" ---------------------------------------------------------------------------- +.\" 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 2022-09-03 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 format $format +Show the paths that multipathd is monitoring, using a format string with path +format wildcards. +. +.TP +.B list|show maps|multipaths +Show the multipath devices that the multipathd is monitoring. +. +.TP +.B list|show maps|multipaths format $format +Show the status of all multipath devices that the multipathd is monitoring, +using a format string with multipath format wildcards. +. +.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 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 wildcards +Show the format wildcards used in interactive commands taking $format. +. +.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 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 map|multipath $map +Stop monitoring a multipath device. +. +.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 on all multipath devices. +. +.TP +.B disablequeueing map|multipath $map +Disable queuing on multipathed map $map. +. +.TP +.B restorequeueing map|multipath $map +Restore queuing on multipahted map $map. +. +.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 "SYSTEMD INTEGRATION" +.\" ---------------------------------------------------------------------------- +. +When compiled with systemd support two systemd service files are installed, +\fImultipathd.service\fR and \fImultipathd.socket\fR 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 diff --git a/multipathd/multipathd.service b/multipathd/multipathd.service deleted file mode 100644 index aec62db..0000000 --- a/multipathd/multipathd.service +++ /dev/null @@ -1,25 +0,0 @@ -[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 -After=systemd-udevd-kernel.socket -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 - -[Service] -Type=notify -NotifyAccess=main -ExecStart=/sbin/multipathd -d -s -ExecReload=/sbin/multipathd reconfigure -TasksMax=infinity - -[Install] -WantedBy=sysinit.target -Also=multipathd.socket diff --git a/multipathd/multipathd.service.in b/multipathd/multipathd.service.in new file mode 100644 index 0000000..6d03ff7 --- /dev/null +++ b/multipathd/multipathd.service.in @@ -0,0 +1,24 @@ +[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 + +[Service] +Type=notify +NotifyAccess=main +ExecStart=/sbin/multipathd -d -s +ExecReload=/sbin/multipathd reconfigure +TasksMax=infinity + +[Install] +WantedBy=sysinit.target diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index 02e89fb..4d6f258 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -41,6 +41,7 @@ #include "cli.h" #include "uxlsnr.h" #include "strbuf.h" +#include "alias.h" /* state of client connection */ enum { @@ -190,6 +191,7 @@ void wakeup_cleanup(void *arg) 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 @@ -200,6 +202,7 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, struct config *conf; int dir_reset = 0; int conf_reset = 0; + int mp_reset = 0; if (notify_fd == -1) return; @@ -214,6 +217,8 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, conf_reset = 1; if (wds->dir_wd == -1) dir_reset = 1; + if (wds->mp_wd == -1) + mp_reset = 1; } put_multipath_config(conf); @@ -235,7 +240,13 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, if (wds->conf_wd == -1) condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); } - return; + 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) @@ -256,12 +267,13 @@ static void handle_inotify(int fd, struct watch_descriptors *wds) inotify_rm_watch(fd, wds->conf_wd); if (wds->dir_wd != -1) inotify_rm_watch(fd, wds->dir_wd); - wds->conf_wd = wds->dir_wd = -1; + if (wds->mp_wd != -1) + inotify_rm_watch(fd, wds->mp_wd); + wds->conf_wd = wds->dir_wd = wds->mp_wd = -1; } break; } - got_notify = 1; for (ptr = buff; ptr < buff + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; @@ -273,7 +285,13 @@ static void handle_inotify(int fd, struct watch_descriptors *wds) 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) @@ -599,7 +617,7 @@ void *uxsock_listen(long ux_sock, void *trigger_data) 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 }; + struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1, .mp_wd = -1, }; struct vectors *vecs = trigger_data; condlog(3, "uxsock: startup listener"); @@ -666,7 +684,8 @@ void *uxsock_listen(long ux_sock, void *trigger_data) 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)) + 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; diff --git a/tests/Makefile b/tests/Makefile index 860338b..7dac8a8 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -48,14 +48,14 @@ hwtable-test_OBJDEPS := $(multipathdir)/discovery.o $(multipathdir)/blacklist.o $(multipathdir)/structs.o $(multipathdir)/propsel.o hwtable-test_LIBDEPS := -ludev -lpthread -ldl blacklist-test_TESTDEPS := test-log.o -blacklist-test_OBJDEPS := $(multipathdir)/blacklist.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 := -lpthread -ldl valid-test_OBJDEPS := $(multipathdir)/valid.o $(multipathdir)/discovery.o -valid-test_LIBDEPS := -ludev -lpthread -ldl +valid-test_LIBDEPS := -lmount -ludev -lpthread -ldl devt-test_LIBDEPS := -ludev mpathvalid-test_LIBDEPS := -ludev -lpthread -ldl mpathvalid-test_OBJDEPS := $(mpathvaliddir)/mpath_valid.o @@ -112,7 +112,7 @@ dep_clean: # 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) \ + @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 diff --git a/tests/alias.c b/tests/alias.c index 3ca6c28..f893d17 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -3,14 +3,19 @@ #include #include #include +#include "strbuf.h" #include "util.h" #include "alias.h" #include "test-log.h" #include +#include #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" @@ -20,18 +25,6 @@ #define MPATH_ID_INT_MAX_p1 "fxshrxx" #endif -void __wrap_rewind(FILE *stream) -{} - -char *__wrap_fgets(char *buf, int n, FILE *stream) -{ - char *val = mock_ptr_type(char *); - if (!val) - return NULL; - strlcpy(buf, val, n); - return buf; -} - static int __set_errno(int err) { if (err >= 0) { @@ -43,29 +36,44 @@ static int __set_errno(int err) } } -off_t __wrap_lseek(int fd, off_t offset, int whence) -{ - return __set_errno(mock_type(int)); - -} - +/* + * allocate_binding -> write_bindings_file() writes the entire file, i.e. the + * header, any pre-existing 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); - check_expected(buf); + assert_ptr_not_equal(start, NULL); return __set_errno(mock_type(int)); } -int __wrap_ftruncate(int fd, off_t length) +int __wrap_rename(const char *old, const char *new) { - check_expected(length); return __set_errno(mock_type(int)); } -int __wrap_dm_map_present(const char * str) +int __wrap_mkstemp(char *template) { - check_expected(str); - return mock_type(int); + return 10; } int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) @@ -81,6 +89,50 @@ int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) 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) { @@ -384,41 +436,109 @@ static int test_scan_devname(void) static void mock_unused_alias(const char *alias) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 0); + expect_string(__wrap_dm_get_uuid, name, alias); + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); + will_return(__wrap_dm_get_uuid, 1); } static void mock_self_alias(const char *alias, const char *wwid) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); expect_string(__wrap_dm_get_uuid, name, alias); expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); will_return(__wrap_dm_get_uuid, 0); will_return(__wrap_dm_get_uuid, wwid); } -#define USED_STR(alias_str, wwid_str) wwid_str ": alias '" alias_str "' already taken, but not in bindings file. reselecting alias\n" +#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_uuid, name, alias); \ + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ + will_return(__wrap_dm_get_uuid, 1); \ + } while (0) + +#define mock_used_alias(alias, wwid) \ + do { \ + expect_string(__wrap_dm_get_uuid, name, alias); \ + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ + will_return(__wrap_dm_get_uuid, 0); \ + will_return(__wrap_dm_get_uuid, "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_failed_alias(const char *alias, char *msg) +static void mock_bindings_file(const char *content) { + return __mock_bindings_file(content, false); +} + +static int teardown_bindings(void **state) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); - expect_string(__wrap_dm_get_uuid, name, alias); - expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); - will_return(__wrap_dm_get_uuid, 1); - expect_condlog(3, msg); + cleanup_bindings(); + return 0; } -static void mock_used_alias(const char *alias, char *msg) +static int lookup_binding(FILE *dummy, const char *wwid, char **alias, + const char *prefix, int check_if_taken) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); - expect_string(__wrap_dm_get_uuid, name, alias); - expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); - will_return(__wrap_dm_get_uuid, 0); - will_return(__wrap_dm_get_uuid, "WWID_USED"); - expect_condlog(3, msg); + 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) @@ -426,8 +546,8 @@ static void lb_empty(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + 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); @@ -438,9 +558,9 @@ static void lb_empty_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file(""); mock_unused_alias("MPATHa"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + 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); @@ -452,12 +572,11 @@ static void lb_empty_failed(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); - mock_failed_alias("MPATHa", USED_STR("MPATHa", "WWID0")); - mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + 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, 2); + assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); free(alias); } @@ -467,10 +586,10 @@ static void lb_empty_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -482,10 +601,10 @@ static void lb_empty_1_used_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_used_alias("MPATHa", "WWID0"); mock_self_alias("MPATHb", "WWID0"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -497,10 +616,9 @@ static void lb_match_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - expect_condlog(3, "Found matching wwid [WWID0] in bindings file." - " Setting alias to MPATHa\n"); - rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 0); + 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"); @@ -512,10 +630,10 @@ static void lb_nomatch_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 0); + 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); } @@ -525,9 +643,8 @@ static void lb_nomatch_a_bad_check(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(0, "no more available user_friendly_names\n"); + 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); @@ -538,10 +655,9 @@ static void lb_nomatch_a_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n"); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + 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); @@ -552,29 +668,26 @@ static void lb_nomatch_a_3_used_failed_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID1")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID1")); - mock_used_alias("MPATHd", USED_STR("MPATHd", "WWID1")); - mock_failed_alias("MPATHe", USED_STR("MPATHe", "WWID1")); - mock_self_alias("MPATHf", "WWID1"); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + 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, 6); + assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); } -static void do_lb_match_c(void **state, int check_if_taken) +static void do_lb_match_c(void **state) { int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHc WWID1\n"); - expect_condlog(3, "Found matching wwid [WWID1] in bindings file." - " Setting alias to MPATHc\n"); - rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", check_if_taken); + 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"); @@ -583,12 +696,12 @@ static void do_lb_match_c(void **state, int check_if_taken) static void lb_match_c(void **state) { - do_lb_match_c(state, 0); + do_lb_match_c(state); } static void lb_match_c_check(void **state) { - do_lb_match_c(state, 1); + do_lb_match_c(state); } static void lb_nomatch_a_c(void **state) @@ -596,11 +709,11 @@ static void lb_nomatch_a_c(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + 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); } @@ -610,11 +723,10 @@ static void lb_nomatch_a_d_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1"); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + 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); @@ -625,12 +737,11 @@ static void lb_nomatch_a_d_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); + mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHc"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -641,13 +752,12 @@ static void lb_nomatch_a_d_2_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); + 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"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -658,14 +768,13 @@ static void lb_nomatch_a_d_3_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); - mock_used_alias("MPATHe", USED_STR("MPATHe", "WWID2")); + 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"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); @@ -676,11 +785,11 @@ static void lb_nomatch_c_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + 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); } @@ -690,12 +799,11 @@ static void lb_nomatch_d_a_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHc WWID1\n" + "MPATHa WWID0\n" + "MPATHd WWID0\n"); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + 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); @@ -706,13 +814,12 @@ static void lb_nomatch_d_a_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID0\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); + 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"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -723,12 +830,12 @@ static void lb_nomatch_a_b(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + 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); } @@ -738,14 +845,19 @@ static void lb_nomatch_a_b_bad(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); - assert_int_equal(rc, 3); + 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); } @@ -754,90 +866,156 @@ static void lb_nomatch_a_b_bad_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); - mock_self_alias("MPATHc", "WWID2"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + 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, 3); + assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } -static void lb_nomatch_b_a(void **state) +static void lb_nomatch_b_z_a(void **state) { int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); - assert_int_equal(rc, 27); + 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_a_3_used(void **state) +static void lb_nomatch_b_aa_a(void **state) { int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHaa", USED_STR("MPATHaa", "WWID2")); - mock_used_alias("MPATHab", USED_STR("MPATHab", "WWID2")); - mock_used_alias("MPATHac", USED_STR("MPATHac", "WWID2")); - mock_unused_alias("MPATHad"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + 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, 30); + assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } -#ifdef MPATH_ID_INT_MAX -static void do_lb_nomatch_int_max(void **state, int check_if_taken) +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); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(0, "no more available user_friendly_names\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", check_if_taken); - assert_int_equal(rc, -1); + 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_int_max(void **state) +static void lb_nomatch_b_a_aa_zz(void **state) { - do_lb_nomatch_int_max(state, 0); + 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); } -static void lb_nomatch_int_max_check(void **state) +#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) { - do_lb_nomatch_int_max(state, 1); + 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); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + 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); } @@ -845,14 +1023,15 @@ static void lb_nomatch_int_max_m1(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); - assert_int_equal(rc, INT_MAX); + 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); } @@ -860,15 +1039,16 @@ static void lb_nomatch_int_max_m1_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + 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); } @@ -876,15 +1056,16 @@ static void lb_nomatch_int_max_m1_1_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); - mock_unused_alias("MPATH" MPATH_ID_INT_MAX); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, INT_MAX); + 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); } @@ -892,15 +1073,18 @@ static void lb_nomatch_int_max_m1_2_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + 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 @@ -908,53 +1092,68 @@ static void lb_nomatch_int_max_m1_2_used(void **state) static int test_lookup_binding(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(lb_empty), - cmocka_unit_test(lb_empty_unused), - cmocka_unit_test(lb_empty_failed), - cmocka_unit_test(lb_empty_1_used), - cmocka_unit_test(lb_empty_1_used_self), - cmocka_unit_test(lb_match_a), - cmocka_unit_test(lb_nomatch_a), - cmocka_unit_test(lb_nomatch_a_bad_check), - cmocka_unit_test(lb_nomatch_a_unused), - cmocka_unit_test(lb_nomatch_a_3_used_failed_self), - cmocka_unit_test(lb_match_c), - cmocka_unit_test(lb_match_c_check), - cmocka_unit_test(lb_nomatch_a_c), - cmocka_unit_test(lb_nomatch_a_d_unused), - cmocka_unit_test(lb_nomatch_a_d_1_used), - cmocka_unit_test(lb_nomatch_a_d_2_used), - cmocka_unit_test(lb_nomatch_a_d_3_used), - cmocka_unit_test(lb_nomatch_c_a), - cmocka_unit_test(lb_nomatch_d_a_unused), - cmocka_unit_test(lb_nomatch_d_a_1_used), - cmocka_unit_test(lb_nomatch_a_b), - cmocka_unit_test(lb_nomatch_a_b_bad), - cmocka_unit_test(lb_nomatch_a_b_bad_self), - cmocka_unit_test(lb_nomatch_b_a), - cmocka_unit_test(lb_nomatch_b_a_3_used), + 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(lb_nomatch_int_max), - cmocka_unit_test(lb_nomatch_int_max_check), - cmocka_unit_test(lb_nomatch_int_max_used), - cmocka_unit_test(lb_nomatch_int_max_m1), - cmocka_unit_test(lb_nomatch_int_max_m1_used), - cmocka_unit_test(lb_nomatch_int_max_m1_1_used), - cmocka_unit_test(lb_nomatch_int_max_m1_2_used), + 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'; - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); @@ -966,9 +1165,8 @@ static void rl_match_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - expect_condlog(3, "Found matching alias [MPATHa] in bindings file. " - "Setting wwid to WWID0\n"); + 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"); @@ -980,9 +1178,8 @@ static void rl_nomatch_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching alias [MPATHb] in bindings file.\n"); + 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, ""); @@ -994,10 +1191,9 @@ static void rl_malformed_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa \n"); - will_return(__wrap_fgets, NULL); - expect_condlog(3, "Ignoring malformed line 1 in bindings file\n"); - expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + 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, ""); @@ -1014,10 +1210,9 @@ static void rl_overlong_a(void **state) snprintf(line + sizeof(line) - 2, 2, "\n"); buf[0] = '\0'; - will_return(__wrap_fgets, line); - will_return(__wrap_fgets, NULL); expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); - expect_condlog(3, "No matching alias [MPATHa] 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, ""); @@ -1029,11 +1224,10 @@ static void rl_match_b(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb WWID2\n"); - expect_condlog(3, "Found matching alias [MPATHb] in bindings file. " - "Setting wwid to WWID2\n"); + 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"); @@ -1042,31 +1236,41 @@ static void rl_match_b(void **state) static int test_rlookup_binding(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(rl_empty), - cmocka_unit_test(rl_match_a), - cmocka_unit_test(rl_nomatch_a), - cmocka_unit_test(rl_malformed_a), - cmocka_unit_test(rl_overlong_a), - cmocka_unit_test(rl_match_b), + 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; - will_return(__wrap_lseek, 0); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln)); - expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); + 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(0, "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); + check_bindings_size(1); free(alias); } @@ -1075,15 +1279,17 @@ static void al_zz(void **state) static const char ln[] = "MPATHzz WWIDzz\n"; char *alias; - will_return(__wrap_lseek, 0); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln)); - expect_condlog(3, "Created new binding [MPATHzz] for WWID [WWIDzz]\n"); + 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(0, "WWIDzz", 26*26 + 26, "MPATH"); + 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); } @@ -1092,8 +1298,9 @@ static void al_0(void **state) char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id 0\n"); - alias = allocate_binding(0, "WWIDa", 0, "MPATH"); + alias = allocate_binding("WWIDa", 0, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); } static void al_m2(void **state) @@ -1101,47 +1308,688 @@ static void al_m2(void **state) char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id -2\n"); - alias = allocate_binding(0, "WWIDa", -2, "MPATH"); + alias = allocate_binding("WWIDa", -2, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); } -static void al_lseek_err(void **state) +static void al_write_partial(void **state) { + static const char ln[] = "MPATHa WWIDa\n"; char *alias; - will_return(__wrap_lseek, -ENODEV); - expect_condlog(0, "Cannot seek to end of bindings file : No such device\n"); - alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + 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"; - const int offset = 20; char *alias; - will_return(__wrap_lseek, offset); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln) - 1); - expect_value(__wrap_ftruncate, length, offset); - will_return(__wrap_ftruncate, 0); - expect_condlog(0, "Cannot write binding to bindings file :"); + 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); - alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + 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(al_a), - cmocka_unit_test(al_zz), - cmocka_unit_test(al_0), - cmocka_unit_test(al_m2), - cmocka_unit_test(al_lseek_err), - cmocka_unit_test(al_write_err), + 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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() +{ + 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() +{ + 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); @@ -1152,11 +2000,16 @@ 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; } diff --git a/tests/blacklist.c b/tests/blacklist.c index 882aa3a..ba8dfd0 100644 --- a/tests/blacklist.c +++ b/tests/blacklist.c @@ -24,6 +24,8 @@ #include "test-log.h" #include "debug.h" +#include "../libmultipath/blacklist.c" + struct udev_device { const char *sysname; char *property_list[]; @@ -224,8 +226,15 @@ static void test_devnode_default(void **state) { assert_int_equal(filter_devnode(blist_devnode_default, NULL, "sdaa"), MATCH_NOTHING); - assert_int_equal(filter_devnode(blist_devnode_default, NULL, "nvme0n1"), - 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"); diff --git a/tests/directio.c b/tests/directio.c index db9643e..5201d21 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -141,10 +141,9 @@ 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) { #ifdef DIO_TEST_DEV - mock_type(int); return __real_io_cancel(ctx, iocb, evt); #else - return mock_type(int); + return 0; #endif } @@ -439,14 +438,8 @@ static void test_check_state_timeout(void **state) do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); return_io_getevents_none(); - will_return(__wrap_io_cancel, 0); do_check_state(&c, 1, 30, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); -#ifdef DIO_TEST_DEV - /* io_cancel will return negative value on timeout, so it happens again - * when freeing the checker */ - will_return(__wrap_io_cancel, 0); -#endif libcheck_free(&c); do_libcheck_reset(1); } @@ -468,12 +461,8 @@ static void test_check_state_async_timeout(void **state) return_io_getevents_none(); do_check_state(&c, 0, 3, PATH_PENDING); return_io_getevents_none(); - will_return(__wrap_io_cancel, 0); do_check_state(&c, 0, 3, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); -#ifdef DIO_TEST_DEV - will_return(__wrap_io_cancel, 0); -#endif libcheck_free(&c); do_libcheck_reset(1); } @@ -501,13 +490,8 @@ static void test_free_with_pending(void **state) check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 1, 0); - will_return(__wrap_io_cancel, 0); libcheck_free(&c[1]); -#ifdef DIO_TEST_DEV - check_aio_grp(aio_grp, 1, 1); /* real cancel doesn't remove request */ -#else - check_aio_grp(aio_grp, 0, 0); -#endif + check_aio_grp(aio_grp, 1, 1); /* cancel doesn't remove request */ do_libcheck_reset(1); } @@ -533,7 +517,6 @@ static void test_orphaned_aio_group(void **state) assert_int_equal(i, 1); for (i = 0; i < AIO_GROUP_SIZE; i++) { assert_true(is_checker_running(&c[i])); - will_return(__wrap_io_cancel, -1); if (i == AIO_GROUP_SIZE - 1) { /* remove the orphaned group and create a new one */ will_return(__wrap_io_destroy, 0); @@ -559,12 +542,10 @@ static void test_timeout_cancel_failed(void **state) do_libcheck_init(&c[i], 4096, &reqs[i]); aio_grp = get_aio_grp(c); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); check_aio_grp(aio_grp, 2, 0); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 1, &reqs[0], &res[0]); @@ -600,7 +581,6 @@ static void test_async_timeout_cancel_failed(void **state) return_io_getevents_none(); do_check_state(&c[1], 0, 2, PATH_PENDING); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); #ifndef DIO_TEST_DEV /* can't pick which even gets returned on real devices */ @@ -608,7 +588,6 @@ static void test_async_timeout_cancel_failed(void **state) do_check_state(&c[1], 0, 2, PATH_UP); #endif return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 2, reqs, res); @@ -637,7 +616,6 @@ static void test_orphan_checker_cleanup(void **state) aio_grp = get_aio_grp(c); return_io_getevents_none(); do_check_state(&c[0], 0, 30, PATH_PENDING); - will_return(__wrap_io_cancel, -1); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 2, 1); @@ -662,7 +640,6 @@ static void test_orphan_reset_cleanup(void **state) orphan_aio_grp = get_aio_grp(&c); return_io_getevents_none(); do_check_state(&c, 0, 30, PATH_PENDING); - will_return(__wrap_io_cancel, -1); check_aio_grp(orphan_aio_grp, 1, 0); libcheck_free(&c); check_aio_grp(orphan_aio_grp, 1, 1); diff --git a/tests/pgpolicy.c b/tests/pgpolicy.c index 43be831..ccf29bc 100644 --- a/tests/pgpolicy.c +++ b/tests/pgpolicy.c @@ -32,6 +32,15 @@ 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; @@ -639,6 +648,227 @@ static void test_group_by_prio_mixed_one_marginal8(void **state) 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"}; @@ -1002,6 +1232,19 @@ int test_pgpolicies(void) 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), diff --git a/tests/test-lib.c b/tests/test-lib.c index 0bc49d5..f75ea31 100644 --- a/tests/test-lib.c +++ b/tests/test-lib.c @@ -342,12 +342,6 @@ void mock_pathinfo(int mask, const struct mocked_path *mp) mp->wwid); } - if (mask & DI_CHECKER) { - /* get_state -> sysfs_get_timeout */ - will_return(__wrap_udev_device_get_subsystem, "scsi"); - will_return(__wrap_udev_device_get_sysattr_value, "180"); - } - if (mask & DI_PRIO && mp->flags & NEED_SELECT_PRIO) { /* sysfs_get_timeout, again (!?) */ diff --git a/tests/test-log.c b/tests/test-log.c index c174587..6351699 100644 --- a/tests/test-log.c +++ b/tests/test-log.c @@ -16,12 +16,14 @@ void __wrap_dlog (int prio, const char * fmt, ...) va_list ap; char *expected; - check_expected(prio); 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)); } diff --git a/tests/util.c b/tests/util.c index 9affb0e..d6083dc 100644 --- a/tests/util.c +++ b/tests/util.c @@ -193,7 +193,7 @@ static void test_bitmask_1(void **state) for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(!is_bit_set_in_bitfield(b, bf)); + assert_false(is_bit_set_in_bitfield(b, bf)); set_bit_in_bitfield(b, bf); for (k = 0; k < BITARR_SZ; k++) { #if 0 @@ -207,13 +207,13 @@ static void test_bitmask_1(void **state) } for (m = 0; m < 64; m++) if (i == m) - assert(is_bit_set_in_bitfield(64 * j + m, - bf)); + assert_true(is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(!is_bit_set_in_bitfield(64 * j + m, - bf)); + assert_false(is_bit_set_in_bitfield(64 * j + m, + bf)); clear_bit_in_bitfield(b, bf); - assert(!is_bit_set_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); } @@ -235,16 +235,16 @@ static void test_bitmask_2(void **state) for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(!is_bit_set_in_bitfield(b, bf)); + 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(is_bit_set_in_bitfield(64 * j + m, - bf)); + assert_true(is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(!is_bit_set_in_bitfield(64 * j + m, - bf)); - assert(is_bit_set_in_bitfield(b, bf)); + 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); @@ -260,16 +260,16 @@ static void test_bitmask_2(void **state) for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(is_bit_set_in_bitfield(b, bf)); + 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(!is_bit_set_in_bitfield(64 * j + m, - bf)); + assert_false(is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(is_bit_set_in_bitfield(64 * j + m, - bf)); - assert(!is_bit_set_in_bitfield(b, bf)); + 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); @@ -316,9 +316,8 @@ static void _test_bitmask_small(unsigned int n) uint32_t *arr; unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; - assert(sizeof(bitfield_t) == 4 || sizeof(bitfield_t) == 8); - assert(n <= 64); - assert(n >= 1); + assert_true(sizeof(bitfield_t) == 4 || sizeof(bitfield_t) == 8); + assert_in_range(n, 1, 64); bf = alloc_bitfield(n); assert_non_null(bf); @@ -366,8 +365,7 @@ static void _test_bitmask_small_2(unsigned int n) uint32_t *arr; unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; - assert(n <= 128); - assert(n >= 65); + assert_in_range(n, 65, 128); bf = alloc_bitfield(n); assert_non_null(bf); diff --git a/tests/valid.c b/tests/valid.c index 398b771..18a5a7b 100644 --- a/tests/valid.c +++ b/tests/valid.c @@ -62,11 +62,6 @@ int __wrap___mpath_connect(int nonblocking) return -1; } -int __wrap_systemd_service_enabled(const char *dev) -{ - return (int)mock_type(bool); -} - /* There's no point in checking the return value here */ int __wrap_mpath_disconnect(int fd) { @@ -83,6 +78,13 @@ struct udev_device *__wrap_udev_device_new_from_subsystem_sysname(struct udev *u 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) @@ -97,6 +99,12 @@ 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 *elist, const char *vendor, const char * product, const char *dev) @@ -165,6 +173,11 @@ int __wrap_is_failed_wwid(const char *wwid) 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); @@ -198,7 +211,6 @@ enum { enum { CHECK_MPATHD_RUNNING, CHECK_MPATHD_EAGAIN, - CHECK_MPATHD_ENABLED, CHECK_MPATHD_SKIP, }; @@ -214,17 +226,16 @@ static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, else if (check_multipathd == CHECK_MPATHD_EAGAIN) { will_return(__wrap___mpath_connect, false); will_return(__wrap___mpath_connect, EAGAIN); - } else if (check_multipathd == CHECK_MPATHD_ENABLED) { - will_return(__wrap___mpath_connect, false); - will_return(__wrap___mpath_connect, ECONNREFUSED); - will_return(__wrap_systemd_service_enabled, true); } + /* 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) { @@ -250,6 +261,10 @@ static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, 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); @@ -318,19 +333,10 @@ static void test_check_multipathd(void **state) will_return(__wrap_sysfs_is_multipathed, false); will_return(__wrap___mpath_connect, false); will_return(__wrap___mpath_connect, ECONNREFUSED); - will_return(__wrap_systemd_service_enabled, false); + assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); - /* test pass because service is enabled. fail getting udev */ - memset(&pp, 0, sizeof(pp)); - setup_passing(name, NULL, CHECK_MPATHD_ENABLED, 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 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); @@ -347,6 +353,30 @@ static void test_check_multipathd(void **state) 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) @@ -485,7 +515,7 @@ static void test_check_uuid_present(void **state) memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; - setup_passing(name, wwid, CHECK_MPATHD_ENABLED, STAGE_CHECK_WWIDS); + setup_passing(name, wwid, CHECK_MPATHD_RUNNING, STAGE_CHECK_WWIDS); will_return(__wrap_dm_map_present_by_uuid, 1); will_return(__wrap_dm_map_present_by_uuid, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, true), diff --git a/tests/vpd.c b/tests/vpd.c index a7d2092..1b2d62d 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -119,7 +119,7 @@ static void hex2bin(unsigned char *dst, const char *src, const char *sc; unsigned char *ds; - assert(srclen % 2 == 0); + assert_true(srclen % 2 == 0); for (sc = src, ds = dst; sc < src + srclen && ds < dst + dstlen; sc += 2, ++ds)