Imported Upstream version 0.9.7 upstream upstream/0.9.7
authorTizenOpenSource <tizenopensrc@samsung.com>
Fri, 8 Dec 2023 03:28:18 +0000 (12:28 +0900)
committerTizenOpenSource <tizenopensrc@samsung.com>
Fri, 8 Dec 2023 03:28:18 +0000 (12:28 +0900)
98 files changed:
.github/workflows/build-and-unittest.yaml
.github/workflows/coverity.yaml
.github/workflows/foreign.yaml
.github/workflows/multiarch.yaml
.github/workflows/native.yaml
.gitignore
Makefile.inc
README.md
create-config.mk
kpartx/dm-parts.rules
kpartx/kpartx.8
libdmmp/Makefile
libmpathpersist/mpath_persist_int.c
libmpathpersist/mpath_persistent_reserve_in.3
libmpathpersist/mpath_persistent_reserve_out.3
libmpathpersist/mpath_updatepr.c
libmpathutil/libmpathutil.version
libmpathutil/util.c
libmpathutil/util.h
libmultipath/Makefile
libmultipath/alias.c
libmultipath/alias.h
libmultipath/blacklist.c
libmultipath/checkers/directio.c
libmultipath/checkers/tur.c
libmultipath/config.c
libmultipath/config.h
libmultipath/configure.c
libmultipath/configure.h
libmultipath/defaults.h
libmultipath/devmapper.c
libmultipath/dict.c
libmultipath/dict.h
libmultipath/discovery.c
libmultipath/hwtable.c
libmultipath/io_err_stat.c
libmultipath/libmultipath.version
libmultipath/lock.h
libmultipath/pgpolicies.c
libmultipath/pgpolicies.h
libmultipath/print.c
libmultipath/prio.c
libmultipath/prio.h
libmultipath/prioritizers/alua.c
libmultipath/prioritizers/alua_rtpg.c
libmultipath/prioritizers/alua_rtpg.h
libmultipath/prioritizers/ana.c
libmultipath/prioritizers/const.c
libmultipath/prioritizers/datacore.c
libmultipath/prioritizers/emc.c
libmultipath/prioritizers/hds.c
libmultipath/prioritizers/hp_sw.c
libmultipath/prioritizers/iet.c
libmultipath/prioritizers/ontap.c
libmultipath/prioritizers/path_latency.c
libmultipath/prioritizers/random.c
libmultipath/prioritizers/rdac.c
libmultipath/prioritizers/sysfs.c
libmultipath/prioritizers/weightedpath.c
libmultipath/prkey.c
libmultipath/prkey.h
libmultipath/propsel.c
libmultipath/propsel.h
libmultipath/structs.c
libmultipath/structs.h
libmultipath/structs_vec.c
libmultipath/switchgroup.c
libmultipath/sysfs.c
libmultipath/valid.c
libmultipath/version.h
libmultipath/wwids.c
mpathpersist/Makefile
mpathpersist/mpathpersist.8.in [moved from mpathpersist/mpathpersist.8 with 97% similarity]
multipath/11-dm-mpath.rules
multipath/Makefile
multipath/main.c
multipath/multipath.8.in [moved from multipath/multipath.8 with 95% similarity]
multipath/multipath.conf.5.in [moved from multipath/multipath.conf.5 with 94% similarity]
multipath/multipath.rules.in
multipathd/Makefile
multipathd/cli_handlers.c
multipathd/fpin_handlers.c
multipathd/main.c
multipathd/main.h
multipathd/multipathc.8
multipathd/multipathd.8.in [moved from multipathd/multipathd.8 with 97% similarity]
multipathd/multipathd.service.in [moved from multipathd/multipathd.service with 85% similarity]
multipathd/uxlsnr.c
tests/Makefile
tests/alias.c
tests/blacklist.c
tests/directio.c
tests/pgpolicy.c
tests/test-lib.c
tests/test-log.c
tests/util.c
tests/valid.c
tests/vpd.c

index abf17bf..61077df 100644 (file)
@@ -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
index 321b94e..a1f4146 100644 (file)
@@ -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
index 2937b72..1b04292 100644 (file)
@@ -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
index 513021b..d76a01e 100644 (file)
@@ -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:
index a7ad4c8..5d83a58 100644 (file)
@@ -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:
index 535353e..6890e4a 100644 (file)
@@ -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
index 2e25d2e..6b45430 100644 (file)
@@ -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' $< >$@
index 5e04f5c..d4f35f5 100644 (file)
--- 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 <bmarzins@redhat.com> and 
+Martin Wilck <mwilck@suse.com> 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 <christophe.varoqui@opensvc.com>
-Device-mapper development mailing list <dm-devel@redhat.com>
+Device-mapper development mailing list <dm-devel@lists.linux.dev>
 
 
 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.
index 2a95ec5..4d318b9 100644 (file)
@@ -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)" >>$@
index b48b67c..ef5ff2e 100644 (file)
@@ -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}"
index 2b144a7..ef8051a 100644 (file)
@@ -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
 .
 .
 .\" ----------------------------------------------------------------------------
index 6d28caf..172ba04 100644 (file)
@@ -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
index 6924b37..178c2f5 100644 (file)
@@ -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;
 }
index c168cae..2f9b8f7 100644 (file)
@@ -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
 .
 .
 .\" ----------------------------------------------------------------------------
index f20be31..d3204c5 100644 (file)
@@ -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
 .
 .
 .\" ----------------------------------------------------------------------------
index 4529a82..36bd777 100644 (file)
@@ -14,6 +14,9 @@
 #include <mpath_persist.h>
 #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;
index 1238fc9..15ff467 100644 (file)
@@ -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;
index 9662e1e..9d147fc 100644 (file)
@@ -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;
index 75e20fd..de9fcfd 100644 (file)
@@ -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;
index 3df851e..85767ab 100644 (file)
@@ -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)
index 0520122..74431f3 100644 (file)
@@ -8,6 +8,9 @@
 #include <string.h>
 #include <limits.h>
 #include <stdio.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <sys/inotify.h>
 
 #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
 "# 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(&timestamp_mutex);
+       if (ret == 0) {
+               ts = st.st_mtim;
+               changed = timespeccmp(&ts, &bindings_last_updated) > 0;
+       }
+       pthread_mutex_unlock(&timestamp_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(&timestamp_mutex);
+               bindings_last_updated = ts;
+               pthread_mutex_unlock(&timestamp_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(&timestamp_mutex);
+                       bindings_last_updated = ts;
+                       pthread_mutex_unlock(&timestamp_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;
 }
index dbc950c..629e8d5 100644 (file)
@@ -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 */
index 8d15d2e..75100b2 100644 (file)
@@ -2,6 +2,8 @@
  * Copyright (c) 2004, 2005 Christophe Varoqui
  */
 #include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
 #include <libudev.h>
 
 #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;
 
index 2f3ece0..12b8be4 100644 (file)
@@ -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;
        }
 
index 551dc4f..eb2fca1 100644 (file)
@@ -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 {
index 5c5c072..b7dbc6f 100644 (file)
@@ -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:
index 8794746..384193a 100644 (file)
@@ -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;
index e551047..d809490 100644 (file)
@@ -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, &params, 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 */
index 2bf73e6..9d935db 100644 (file)
@@ -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,
 };
 
 /*
index a5e9ea0..64b633f 100644 (file)
@@ -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"
 
 #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/"
 
 
index a49db3b..9be82f4 100644 (file)
@@ -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;
index 2e9b45f..0a160e9 100644 (file)
@@ -169,27 +169,6 @@ fail:
 }
 
 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)
 {
        char **str_ptr = (char **)ptr;
@@ -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,                \
@@ -1185,6 +1153,30 @@ declare_pc_handler(eh_deadline, set_undef_off_zero)
 declare_pc_snprint(eh_deadline, print_undef_off_zero)
 
 static int
+def_max_retries_handler(struct config *conf, vector strvec, const char *file,
+                       int line_nr)
+{
+       char * buff;
+
+       buff = set_value(strvec);
+       if (!buff)
+               return 1;
+
+       if (strcmp(buff, "off") == 0)
+               conf->max_retries = MAX_RETRIES_OFF;
+       else if (strcmp(buff, "0") == 0)
+               conf->max_retries = MAX_RETRIES_ZERO;
+       else
+               do_set_int(strvec, &conf->max_retries, 1, 5, file, line_nr,
+                          buff);
+
+       free(buff);
+       return 0;
+}
+
+declare_def_snprint(max_retries, print_undef_off_zero)
+
+static int
 set_pgpolicy(vector strvec, void *ptr, const char *file, int line_nr)
 {
        char * buff;
@@ -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)
@@ -1677,6 +1665,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);
index 15d9cba..7e2dfbe 100644 (file)
@@ -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 */
index d9ee2cb..6fd4dab 100644 (file)
@@ -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);
                }
        }
 
index 3c4f866..ae6aac7 100644 (file)
@@ -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
index dc1c252..1c59445 100644 (file)
@@ -19,7 +19,6 @@
 #include <sys/ioctl.h>
 #include <linux/fs.h>
 #include <libaio.h>
-#include <errno.h>
 #include <sys/mman.h>
 #include <sys/select.h>
 
@@ -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);
 }
index faef2a2..8ab7c80 100644 (file)
@@ -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;
index 9814be7..ac80d1d 100644 (file)
@@ -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);
index 10b44d3..edc3c61 100644 (file)
@@ -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;
index 1592761..9e4ddda 100644 (file)
@@ -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
index 3193dbe..360308d 100644 (file)
@@ -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[] = {
index cdd3752..a54487f 100644 (file)
@@ -3,20 +3,25 @@
 #include <stddef.h>
 #include <dlfcn.h>
 #include <sys/stat.h>
+#include <libudev.h>
 
 #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)
index 184bf65..318d260 100644 (file)
@@ -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 */
index 0ab06e2..ec68f37 100644 (file)
@@ -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);
index 2db9153..dd2224c 100644 (file)
@@ -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;
        }
index 675709f..c5f9a8f 100644 (file)
@@ -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__ */
index b5c7873..e9827dc 100644 (file)
@@ -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;
 
index 059d859..2b07f47 100644 (file)
@@ -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;
 }
index d1d473d..f288306 100644 (file)
@@ -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));
 }
index 3b63cca..856c23d 100644 (file)
@@ -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));
 }
index d569f2d..212301e 100644 (file)
@@ -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));
 }
index 5b85ad2..5570271 100644 (file)
@@ -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));
 }
index 167a46b..f3bf64c 100644 (file)
@@ -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);
 }
index 262e69d..117886e 100644 (file)
@@ -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));
 }
index 2f5be9b..f414257 100644 (file)
@@ -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;
index b742ac2..148b957 100644 (file)
@@ -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;
 
index 92a2fb8..f40cd84 100644 (file)
@@ -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));
 }
index a6feb42..5e8adc0 100644 (file)
@@ -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];
index 561ebb4..de51a9e 100644 (file)
@@ -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);
 }
index a215499..c66d293 100644 (file)
@@ -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) {
index a16de10..43afd5e 100644 (file)
@@ -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 */
index a25cc92..44241e2 100644 (file)
@@ -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);
index 152ca44..73615c2 100644 (file)
@@ -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);
index 87e84d5..1b305fd 100644 (file)
@@ -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);
index 9e2c1ab..a1aac1b 100644 (file)
@@ -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;
index 5a61876..0e8a46e 100644 (file)
@@ -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;
index 6fdfcfa..b1e1f39 100644 (file)
@@ -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)
index c45296a..ad3d661 100644 (file)
@@ -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;
        }
 
index a6aa921..f223778 100644 (file)
@@ -17,6 +17,8 @@
 #include <stddef.h>
 #include <errno.h>
 #include <libudev.h>
+#include <dirent.h>
+#include <libmount/libmount.h>
 
 #include "vector.h"
 #include "config.h"
 #include "mpath_cmd.h"
 #include "valid.h"
 
+static int subdir_filter(const struct dirent *ent)
+{
+       unsigned int j;
+       static char const *const skip[] = {
+               ".",
+               "..",
+               "holders",
+               "integrity",
+               "mq",
+               "power",
+               "queue",
+               "slaves",
+               "trace",
+       };
+
+       if (ent->d_type != DT_DIR)
+               return 0;
+
+       for (j = 0; j < ARRAY_SIZE(skip); j++)
+               if (!strcmp(skip[j], ent->d_name))
+                       return 0;
+       return 1;
+}
+
+static int read_partitions(const char *syspath, vector parts)
+{
+       struct scandir_result sr = { .n = 0 };
+       char path[PATH_MAX], *last;
+       char *prop;
+       int i;
+
+       strlcpy(path, syspath, sizeof(path));
+       sr.n = scandir(path, &sr.di, subdir_filter, NULL);
+       if (sr.n == -1)
+               return -errno;
+
+       pthread_cleanup_push_cast(free_scandir_result, &sr);
+
+       /* parts[0] is the whole disk */
+       if ((prop = strdup(strrchr(path, '/') + 1)) != NULL) {
+               if (vector_alloc_slot(parts))
+                       vector_set_slot(parts, prop);
+               else
+                       free(prop);
+       }
+
+       last = path + strlen(path);
+       for (i = 0; i < sr.n; i++) {
+               struct stat st;
+
+               /* only add dirs that have the "partition" attribute */
+               snprintf(last, sizeof(path) - (last - path), "/%s/partition",
+                        sr.di[i]->d_name);
+
+               if (stat(path, &st) == 0 &&
+                   (prop = strdup(sr.di[i]->d_name)) != NULL) {
+                       if (vector_alloc_slot(parts))
+                               vector_set_slot(parts, prop);
+                       else
+                               free(prop);
+               }
+       }
+
+       pthread_cleanup_pop(1);
+       return 0;
+}
+
+static int no_dots(const struct dirent *ent)
+{
+       const char *name = ent->d_name;
+
+       if (name[0] == '.' &&
+           (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+               return 0;
+       return 1;
+}
+
+static int check_holders(const char *syspath)
+{
+       struct scandir_result __attribute__((cleanup(free_scandir_result)))
+               sr = { .n = 0 };
+
+       sr.n = scandir(syspath, &sr.di, no_dots, NULL);
+       if (sr.n > 0)
+               condlog(4, "%s: found holders under %s", __func__, syspath);
+       return sr.n;
+}
+
+static int check_all_holders(const struct _vector *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;
 
index 2da0045..9c549c1 100644 (file)
@@ -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"
 
index 89bb60c..591cd09 100644 (file)
@@ -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;
 
index f57c105..f374946 100644 (file)
@@ -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))
 
similarity index 97%
rename from mpathpersist/mpathpersist.8
rename to mpathpersist/mpathpersist.8.in
index d594422..fecef0d 100644 (file)
@@ -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 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"
+.TH MPATHPERSIST 8 2021-11-12 Linux
 .
 .
 .\" ----------------------------------------------------------------------------
@@ -30,7 +31,7 @@ mpathpersist \- Manages SCSI persistent reservations on dm multipath devices.
 .
 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
+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.
 .
index d191ae8..c339f52 100644 (file)
@@ -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
index 73db991..0efb9b2 100644 (file)
@@ -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),' $< >$@
index b9f360b..9e1c505 100644 (file)
@@ -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);
        }
 
similarity index 95%
rename from multipath/multipath.8
rename to multipath/multipath.8.in
index 88149d5..348eb22 100644 (file)
@@ -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 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"
+.TH MULTIPATH 8 2021-11-12 Linux
 .
 .
 .\" ----------------------------------------------------------------------------
@@ -184,7 +185,7 @@ Display the currently used multipathd configuration.
 .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.
+creating \fI@CONFIGFILE@\fR.
 .
 .\" ----------------------------------------------------------------------------
 .SH OPTIONS
@@ -232,11 +233,11 @@ option from \fBmultipath.conf(5)\fR.
 .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
+\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 multipath.conf (5).
+.BR @CONFIGFILE@ (5).
 This option should only be used in rare circumstances.
 .
 .TP
@@ -245,8 +246,8 @@ 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.
+(\fBdeprecated, do not use\fR) Set \fIuser_friendly_names\fR bindings file location. The default is
+\fI@STATE_DIR@/bindings\fR.
 .
 .TP
 .B \-q
similarity index 94%
rename from multipath/multipath.conf.5
rename to multipath/multipath.conf.5.in
index b4dccd1..683bdb7 100644 (file)
@@ -1,26 +1,26 @@
 .\" ----------------------------------------------------------------------------
 .\" 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
+.\" 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
+.TH MULTIPATH.CONF 5 2023-06-15 Linux
 .
 .
 .\" ----------------------------------------------------------------------------
 .SH NAME
 .\" ----------------------------------------------------------------------------
 .
-multipath.conf \- multipath daemon configuration file.
+@CONFIGFILE@, @CONFIGDIR@/*.conf \- multipath daemon configuration file.
 .
 .
 .\" ----------------------------------------------------------------------------
 .SH DESCRIPTION
 .\" ----------------------------------------------------------------------------
 .
-.B "/etc/multipath.conf"
+.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
@@ -29,6 +29,15 @@ a comment line. Empty lines are ignored.
 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
@@ -85,7 +94,7 @@ not mandatory.
 .
 .LP
 .B Note on regular expressions:
-The \fImultipath.conf\fR syntax allows many attribute values to be specified as POSIX
+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
@@ -233,11 +242,40 @@ per-multipath option in the configuration file.
 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.
 .
@@ -682,7 +720,7 @@ The default is: \fBno\fR
 .B user_friendly_names
 If set to
 .I yes
-, using the bindings file \fI/etc/multipath/bindings\fR to assign a persistent
+, using the bindings file \fI@STATE_DIR@/bindings\fR to assign a persistent
 and unique alias to the multipath, in the form of mpath<n>. If set to
 .I no
 use the WWID as the alias. In either case this be will
@@ -744,7 +782,7 @@ 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
+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.
@@ -755,36 +793,43 @@ The default is: \fB<unset>\fR
 .
 .
 .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<unset>\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.
+(Deprecated) This option is not supported any more, and will be ignored.
 .RS
 .TP
-The default is: \fB/etc/multipath/bindings\fR
+The compiled-in value is: \fB@STATE_DIR@/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.
+(Deprecated) This option is not supported any more, and will be ignored.
 .RS
 .TP
-The default is: \fB/etc/multipath/wwids\fR
+The compiled-in value is: \fB@STATE_DIR@/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.
+(Deprecated) This option is not supported any more, and will be ignored.
 .RS
 .TP
-The default is: \fB/etc/multipath/prkeys\fR
+The compiled-in value is: \fB@STATE_DIR@/prkeys\fR
 .RE
 .
 .
@@ -843,7 +888,7 @@ The default is: \fBno\fR
 .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,
+@CONFIGFILE@. If the SCSI layer has not attached a hardware handler,
 multipath will continue to use its configured hardware handler.
 .RS
 .PP
@@ -951,6 +996,10 @@ The default is: \fB<unset>\fR
 .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
@@ -1282,6 +1331,21 @@ The default is: \fBno\fR
 .
 .
 .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 <name>\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
@@ -1530,7 +1594,7 @@ 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
+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
@@ -1962,6 +2026,7 @@ specified the order of precedence is \fIno_path_retry, queue_if_no_path, dev_los
 .BR udev (8),
 .BR dmsetup (8),
 .BR multipath (8),
+.BR multipathc (8),
 .BR multipathd (8).
 .
 .
index 8d3cf33..6f12376 100644 (file)
@@ -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"
index 9d53132..997b40c 100644 (file)
@@ -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)
index e65fb75..420d75d 100644 (file)
@@ -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, &params, 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
index 8f464f0..be087ca 100644 (file)
@@ -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);
 
index 1e1b254..230c9d1 100644 (file)
@@ -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, &params, 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, &params, 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, &params, 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;
 }
index e8bee8e..8a178c0 100644 (file)
@@ -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 */
index 6c57c6c..cf7ae5b 100644 (file)
@@ -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
similarity index 97%
rename from multipathd/multipathd.8
rename to multipathd/multipathd.8.in
index bdf102e..e98c27f 100644 (file)
@@ -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
+.\" 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
@@ -154,7 +155,7 @@ 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.
+specified within the configuration file \fI@CONFIGFILE@\fR.
 .
 .TP
 .B list|show config local
@@ -164,7 +165,7 @@ 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.
+specified within the configuration file \fI@CONFIGFILE@\fR.
 .
 .TP
 .B list|show devices
@@ -289,13 +290,13 @@ Get the current persistent reservation key associated with $map.
 .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.
+\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/etc/multipath.conf\fR.
+\fIreservation_key\fR is set to \fBfile\fR in \fI@CONFIGFILE@\fR.
 .
 .TP
 .B path $path setmarginal
similarity index 85%
rename from multipathd/multipathd.service
rename to multipathd/multipathd.service.in
index aec62db..6d03ff7 100644 (file)
@@ -2,8 +2,8 @@
 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
+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
@@ -22,4 +22,3 @@ TasksMax=infinity
 
 [Install]
 WantedBy=sysinit.target
-Also=multipathd.socket
index 02e89fb..4d6f258 100644 (file)
@@ -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;
index 860338b..7dac8a8 100644 (file)
@@ -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
index 3ca6c28..f893d17 100644 (file)
@@ -3,14 +3,19 @@
 #include <setjmp.h>
 #include <stdio.h>
 #include <cmocka.h>
+#include "strbuf.h"
 #include "util.h"
 #include "alias.h"
 #include "test-log.h"
 #include <errno.h>
+#include <string.h>
 
 #include "globals.c"
 #include "../libmultipath/alias.c"
 
+/* For verbose printing of all aliases in the ordering tests */
+#define ALIAS_DEBUG 0
+
 #if INT_MAX == 0x7fffffff
 /* user_friendly_name for map #INT_MAX */
 #define MPATH_ID_INT_MAX "fxshrxw"
 #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 == &timestamp_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 == &timestamp_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;
 }
index 882aa3a..ba8dfd0 100644 (file)
@@ -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");
index db9643e..5201d21 100644 (file)
@@ -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);
index 43be831..ccf29bc 100644 (file)
@@ -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),
index 0bc49d5..f75ea31 100644 (file)
@@ -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 (!?) */
index c174587..6351699 100644 (file)
@@ -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));
 }
 
index 9affb0e..d6083dc 100644 (file)
@@ -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);
index 398b771..18a5a7b 100644 (file)
@@ -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),
index a7d2092..1b2d62d 100644 (file)
@@ -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)