--- /dev/null
+name: basic-build-and-ci
+on:
+ push:
+ branches:
+ - master
+ - queue
+ - tip
+ pull_request:
+
+jobs:
+ bionic:
+ runs-on: ubuntu-18.04
+ 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
+ libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev
+ libudev-dev libjson-c-dev liburcu-dev libcmocka-dev
+ - name: build
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo)
+ - name: test
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo) test
+ - name: valgrind-test
+ run: make -O -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
+ steps:
+ - uses: actions/checkout@v2
+ - 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
+ - name: set CC
+ run: echo CC=gcc-10 >> $GITHUB_ENV
+ - name: build
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo)
+ - name: test
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo) test
+ - name: valgrind-test
+ run: make -O -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/ram0 test
+ focal-clang10:
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - 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 clang
+ make perl-base pkg-config valgrind
+ libdevmapper-dev libreadline-dev libaio-dev libsystemd-dev
+ libudev-dev libjson-c-dev liburcu-dev libcmocka-dev
+ - name: set CC
+ run: echo CC=clang >> $GITHUB_ENV
+ - name: build
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo)
+ - name: test
+ run: make -O -j$(grep -c ^processor /proc/cpuinfo) test
+ - name: valgrind-test
+ run: make -O -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/ram0 test
--- /dev/null
+name: compile and unit test on foreign arch
+on:
+ push:
+ branches:
+ - master
+ - queue
+ - tip
+ pull_request:
+
+jobs:
+
+ build:
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ os: [buster]
+ arch: ['ppc64le', 'aarch64', 's390x']
+ container: mwilck/multipath-build-${{ matrix.os }}-${{ matrix.arch }}
+ steps:
+ - name: checkout
+ uses: actions/checkout@v1
+ - name: build and test
+ if: ${{ matrix.arch == '' || matrix.arch == '-i386' }}
+ run: make test
+ - name: build
+ if: ${{ matrix.arch != '' && matrix.arch != '-i386' }}
+ run: make test-progs
+ - name: archive
+ if: ${{ matrix.arch != '' && matrix.arch != '-i386' }}
+ run: >
+ tar cfv binaries.tar
+ Makefile*
+ libmpathcmd/*.so* libmultipath/*.so*
+ tests/lib tests/*-test tests/Makefile tests/*.so*
+ - uses: actions/upload-artifact@v1
+ if: ${{ matrix.arch != '' && matrix.arch != '-i386' }}
+ with:
+ name: multipath-${{ matrix.os }}-${{ matrix.arch }}
+ path: binaries.tar
+
+ test:
+ runs-on: ubuntu-20.04
+ needs: build
+ strategy:
+ matrix:
+ os: [buster]
+ arch: ['ppc64le', 'aarch64', 's390x']
+ steps:
+ - name: get binaries
+ uses: actions/download-artifact@v1
+ with:
+ name: multipath-${{ matrix.os }}-${{ matrix.arch }}
+ - name: unpack
+ run: tar xfv multipath-${{ matrix.os }}-${{ matrix.arch }}/binaries.tar
+ - name: enable foreign arch
+ run: sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
+ - name: run tests
+ # Github actions doesn't support referencing docker images with
+ # context variables. Workaround: use mosteo-actions/docker-run action
+ # See https://github.community/t/expressions-in-docker-uri/16271
+ uses: mosteo-actions/docker-run@v1
+ with:
+ image: mwilck/multipath-run-${{ matrix.os }}-${{ matrix.arch }}
+ # The runner is an image that has "make" as entrypoint
+ # So run "make -C tests" here
+ command: -C tests
--- /dev/null
+name: compile and unit test on native arch
+on:
+ push:
+ branches:
+ - master
+ - queue
+ - tip
+ pull_request:
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ os: [buster, jessie, sid, alpine, fedora-34]
+ arch: ['', '-i386']
+ exclude:
+ - os: fedora-34
+ arch: '-i386'
+ container: mwilck/multipath-build-${{ matrix.os }}${{ matrix.arch }}
+ steps:
+ - name: checkout
+ uses: actions/checkout@v1
+ - name: build and test
+ run: make test
+ - name: clean
+ run: make clean
+ - name: clang
+ env:
+ CC: clang
+ run: make test
+
libmultipath/checkers \
libmultipath/foreign \
libmpathpersist \
+ libmpathvalid \
multipath \
multipathd \
mpathpersist \
$(MAKE) -C $@
libmultipath libdmmp: libmpathcmd
-libmpathpersist multipath multipathd: libmultipath
+libmpathpersist libmpathvalid multipath multipathd: libmultipath
+libmultipath/prioritizers libmultipath/checkers libmultipath/foreign: libmultipath
mpathpersist multipathd: libmpathpersist
libmultipath/checkers.install \
$(MAKE) -C ${@:.uninstall=} uninstall
clean: $(BUILDDIRS.clean)
-install: $(BUILDDIRS:=.install)
+install: all $(BUILDDIRS:=.install)
uninstall: $(BUILDDIRS:=.uninstall)
-test: all
- $(MAKE) -C tests
+test-progs: all
+ $(MAKE) -C tests progs
+
+test: test-progs
+ $(MAKE) -C tests all
valgrind-test: all
$(MAKE) -C tests valgrind
# Uncomment to disable dmevents polling support
# ENABLE_DMEVENTS_POLL = 0
+PKGCONFIG ?= pkg-config
+
ifeq ($(TOPDIR),)
TOPDIR = ..
endif
endif
ifndef SYSTEMD
- ifeq ($(shell pkg-config --modversion libsystemd >/dev/null 2>&1 && echo 1), 1)
- SYSTEMD = $(shell pkg-config --modversion libsystemd | awk '{print $$1}')
+ ifeq ($(shell $(PKGCONFIG) --modversion libsystemd >/dev/null 2>&1 && echo 1), 1)
+ SYSTEMD = $(shell $(PKGCONFIG) --modversion libsystemd | awk '{print $$1}')
else
ifeq ($(shell systemctl --version >/dev/null 2>&1 && echo 1), 1)
SYSTEMD = $(shell systemctl --version 2> /dev/null | \
unitdir = $(prefix)/$(SYSTEMDPATH)/systemd/system
mpathpersistdir = $(TOPDIR)/libmpathpersist
mpathcmddir = $(TOPDIR)/libmpathcmd
+mpathvaliddir = $(TOPDIR)/libmpathvalid
thirdpartydir = $(TOPDIR)/third-party
libdmmpdir = $(TOPDIR)/libdmmp
nvmedir = $(TOPDIR)/libmultipath/nvme
BIN_CFLAGS = -fPIE -DPIE
LIB_CFLAGS = -fPIC
SHARED_FLAGS = -shared
-LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,now
+LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,now -Wl,-z,defs
BIN_LDFLAGS = -pie
# Check whether a function with name $1 has been declared in header file $2.
To enable ALUA, the following options should be changed:
- EMC CLARiiON/VNX:
- "Failover Mode" should be changed to "4".
+ "Failover Mode" should be changed to "4" or "Active-Active mode(ALUA)-failover mode 4"
- HPE 3PAR:
"Host:" should be changed to "Generic-ALUA Persona 2 (UARepLun, SESLun, ALUA)".
- NetApp ONTAP:
To check ALUA state: "igroup show -v <igroup_name>", and to enable ALUA:
"igroup set <igroup_name> alua yes".
+
+- Huawei OceanStor:
+ "Host Access Mode" should be changed to "Asymmetric".
+[![basic-build-and-ci](https://github.com/openSUSE/multipath-tools/actions/workflows/build-and-unittest.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/build-and-unittest.yaml) [![compile and unit test on native arch](https://github.com/openSUSE/multipath-tools/actions/workflows/native.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/native.yaml) [![compile and unit test on foreign arch](https://github.com/openSUSE/multipath-tools/actions/workflows/foreign.yaml/badge.svg)](https://github.com/openSUSE/multipath-tools/actions/workflows/foreign.yaml)
+
multipath-tools for Linux
-*************************
+=========================
+
https://github.com/opensvc/multipath-tools
fprintf(stderr, "can't del loop : %s\n",
loopdev);
r = 1;
- } else
+ } else if (verbose)
fprintf(stderr, "loop deleted : %s\n", loopdev);
}
goto end;
if (n > 0)
break;
}
- if (what == LIST && loopcreated && S_ISREG (buf.st_mode)) {
+ if (what == LIST && loopcreated) {
if (fd != -1)
close(fd);
if (del_loop(device)) {
if (verbose)
- printf("can't del loop : %s\n",
+ fprintf(stderr, "can't del loop : %s\n",
device);
exit(1);
}
- printf("loop deleted : %s\n", device);
+ if (verbose)
+ fprintf(stderr, "loop deleted : %s\n", device);
}
end:
- dm_lib_release();
dm_lib_exit();
return r;
OBJS = libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o
CFLAGS += $(LIB_CFLAGS) -fvisibility=hidden -I$(libdmmpdir) -I$(mpathcmddir) \
- $(shell pkg-config --cflags json-c)
+ $(shell $(PKGCONFIG) --cflags json-c)
-LIBDEPS += $(shell pkg-config --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread
+LIBDEPS += $(shell $(PKGCONFIG) --libs json-c) -L$(mpathcmddir) -lmpathcmd -lpthread
all: $(LIBS) doc
+.PHONY: doc doc.gz clean install uninstall check speed_test dep_clean
$(LIBS): $(OBJS)
$(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
$(LN) $@ $(DEVLIB)
-install:
+install: doc.gz
mkdir -p $(DESTDIR)$(usrlibdir)
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(usrlibdir)/$(LIBS)
$(INSTALL_PROGRAM) -m 644 -D \
$(DESTDIR)$(pkgconfdir)/$(PKGFILE)
perl -i -pe 's|__INCLUDEDIR__|$(includedir)|g' \
$(DESTDIR)$(pkgconfdir)/$(PKGFILE)
- @for file in docs/man/*.3.gz; do \
- $(INSTALL_PROGRAM) -m 644 -D \
- $$file \
- $(DESTDIR)$(man3dir)/ || exit $?; \
- done
+ $(INSTALL_PROGRAM) -m 644 -t $(DESTDIR)$(man3dir) docs/man/*.3.gz
uninstall:
$(RM) $(DESTDIR)$(usrlibdir)/$(LIBS)
clean: dep_clean
$(RM) core *.a *.o *.gz *.so *.so.*
- $(RM) -r docs/man
+ $(RM) docs/man/*.gz
$(MAKE) -C test clean
include $(wildcard $(OBJS:.o=.d))
speed_test: all
$(MAKE) -C test speed_test
-doc: docs/man/$(EXTRA_MAN_FILES).gz
+doc.gz: doc $(patsubst %,%.gz,$(wildcard docs/man/*.3))
-TEMPFILE := $(shell mktemp)
+doc: docs/man/dmmp_strerror.3
-docs/man/$(EXTRA_MAN_FILES).gz: $(HEADERS)
- @for file in $(EXTRA_MAN_FILES); do \
- $(INSTALL_PROGRAM) -v -m 644 -D docs/$$file docs/man/$$file; \
- done
- cat $(HEADERS) | \
- perl docs/doc-preclean.pl > "$(TEMPFILE)"
- perl docs/kernel-doc -man "$(TEMPFILE)" | \
- perl docs/split-man.pl docs/man
- -rm -f "$(TEMPFILE)"
- @for file in docs/man/*.3; do \
- gzip -f $$file; \
- done
- find docs/man -type f -name \*[0-9].gz
+docs/man/%.3.gz: docs/man/%.3
+ gzip -c $< >$@
+
+docs/man/dmmp_strerror.3: $(HEADERS)
+ TEMPFILE=$(shell mktemp); \
+ cat $^ | perl docs/doc-preclean.pl >$$TEMPFILE; \
+ perl docs/kernel-doc -man $$TEMPFILE | \
+ perl docs/split-man.pl docs/man; \
+ rm -f $$TEMPFILE
dep_clean:
$(RM) $(OBJS:.o=.d)
--- /dev/null
+.TH "dmmp_context_free" 3 "dmmp_context_free" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_free \- Release the memory of struct dmmp_context.
+.SH SYNOPSIS
+.B "void" dmmp_context_free
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+.SH "DESCRIPTION"
+
+Release the memory of struct dmmp_context, but the userdata memory defined
+via \fBdmmp_context_userdata_set\fP will not be touched.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_context_log_func_set" 3 "dmmp_context_log_func_set" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_log_func_set \- Set log handler function.
+.SH SYNOPSIS
+.B "void" dmmp_context_log_func_set
+.BI "(struct dmmp_context *" ctx ","
+.BI "void (*" log_func ") (struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, va_list args));"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "log_func" 12
+Pointer of log handler function. If set to NULL, all log will be
+ignored.
+.SH "DESCRIPTION"
+
+Set custom log handler. The log handler will be invoked when log message
+is equal or more important(less value) than log priority setting.
+Please check manpage libdmmp.h(3) for detail usage.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_context_log_priority_get" 3 "dmmp_context_log_priority_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_log_priority_get \- Get log priority.
+.SH SYNOPSIS
+.B "int" dmmp_context_log_priority_get
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve current log priority. Valid log priority values are:
+
+* DMMP_LOG_PRIORITY_ERROR -- 3
+
+* DMMP_LOG_PRIORITY_WARNING -- 4
+
+* DMMP_LOG_PRIORITY_INFO -- 5
+
+* DMMP_LOG_PRIORITY_DEBUG -- 7
+.SH "RETURN"
+int, log priority.
--- /dev/null
+.TH "dmmp_context_log_priority_set" 3 "dmmp_context_log_priority_set" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_log_priority_set \- Set log priority.
+.SH SYNOPSIS
+.B "void" dmmp_context_log_priority_set
+.BI "(struct dmmp_context *" ctx ","
+.BI "int " priority ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "priority" 12
+int, log priority.
+.SH "DESCRIPTION"
+
+
+When library generates log message, only equal or more important(less value)
+message will be forwarded to log handler function. Valid log priority values
+are:
+
+* DMMP_LOG_PRIORITY_ERROR -- 3
+
+* DMMP_LOG_PRIORITY_WARNING -- 4
+
+* DMMP_LOG_PRIORITY_INFO -- 5
+
+* DMMP_LOG_PRIORITY_DEBUG -- 7
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_context_new" 3 "dmmp_context_new" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_new \- Create struct dmmp_context.
+.SH SYNOPSIS
+.B "struct dmmp_context *" dmmp_context_new
+.BI "(" void ");"
+.SH ARGUMENTS
+.IP "void" 12
+no arguments
+.SH "DESCRIPTION"
+
+The default logging level (DMMP_LOG_PRIORITY_DEFAULT) is
+DMMP_LOG_PRIORITY_WARNING which means only warning and error message will be
+forward to log handler function. The default log handler function will print
+log message to STDERR, to change so, please use \fBdmmp_context_log_func_set\fP
+to set your own log handler, check manpage libdmmp.h(3) for detail.
+.SH "RETURN"
+Pointer of 'struct dmmp_context'. Should be freed by
+\fBdmmp_context_free\fP.
--- /dev/null
+.TH "dmmp_context_timeout_get" 3 "dmmp_context_timeout_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_timeout_get \- Get IPC timeout.
+.SH SYNOPSIS
+.B "unsigned int" dmmp_context_timeout_get
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve timeout value of IPC connection to multipathd daemon.
+.SH "RETURN"
+unsigned int. Timeout in milliseconds.
--- /dev/null
+.TH "dmmp_context_timeout_set" 3 "dmmp_context_timeout_set" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_timeout_set \- Set IPC timeout.
+.SH SYNOPSIS
+.B "void" dmmp_context_timeout_set
+.BI "(struct dmmp_context *" ctx ","
+.BI "unsigned int " tmo ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "tmo" 12
+Timeout in milliseconds(1 seconds equal 1000 milliseconds).
+0 means infinite, function only return when error or pass.
+.SH "DESCRIPTION"
+
+By default, the IPC to multipathd daemon will timeout after 60 seconds.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_context_userdata_get" 3 "dmmp_context_userdata_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_userdata_get \- Get user data pointer.
+.SH SYNOPSIS
+.B "void *" dmmp_context_userdata_get
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve user data pointer from 'struct dmmp_context'.
+.SH "RETURN"
+void *. Pointer of user defined data.
--- /dev/null
+.TH "dmmp_context_userdata_set" 3 "dmmp_context_userdata_set" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_context_userdata_set \- Set user data pointer.
+.SH SYNOPSIS
+.B "void" dmmp_context_userdata_set
+.BI "(struct dmmp_context *" ctx ","
+.BI "void *" userdata ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "userdata" 12
+Pointer of user defined data.
+.SH "DESCRIPTION"
+
+Store user data pointer into 'struct dmmp_context'.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_flush_mpath" 3 "dmmp_flush_mpath" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_flush_mpath \- Flush specified multipath device map if unused.
+.SH SYNOPSIS
+.B "int" dmmp_flush_mpath
+.BI "(struct dmmp_context *" ctx ","
+.BI "const char *" mpath_name ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "mpath_name" 12
+const char *. The name of multipath device map.
+.SH "DESCRIPTION"
+
+Flush a multipath device map specified as parameter, if unused.
+.SH "RETURN"
+int. Valid error codes are:
+
+* DMMP_OK
+
+* DMMP_ERR_BUG
+
+* DMMP_ERR_NO_MEMORY
+
+* DMMP_ERR_NO_DAEMON
+
+* DMMP_ERR_MPATH_BUSY
+
+* DMMP_ERR_MPATH_NOT_FOUND
+
+* DMMP_ERR_INVALID_ARGUMENT
+
+* DMMP_ERR_PERMISSION_DENY
+
+Error number could be converted to string by \fBdmmp_strerror\fP.
--- /dev/null
+.TH "dmmp_last_error_msg" 3 "dmmp_last_error_msg" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_last_error_msg \- Retrieves the last error message.
+.SH SYNOPSIS
+.B "const char *" dmmp_last_error_msg
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieves the last error message.
+.SH "RETURN"
+const char *. No need to free this memory, the resources will get
+freed when \fBdmmp_context_free\fP.
--- /dev/null
+.TH "dmmp_log_priority_str" 3 "dmmp_log_priority_str" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_log_priority_str \- Convert log priority to string.
+.SH SYNOPSIS
+.B "const char *" dmmp_log_priority_str
+.BI "(int " priority ");"
+.SH ARGUMENTS
+.IP "priority" 12
+int. Log priority.
+.SH "DESCRIPTION"
+
+Convert log priority to string (const char *).
+.SH "RETURN"
+const char *. Valid string are:
+
+* "ERROR" for DMMP_LOG_PRIORITY_ERROR
+
+* "WARN " for DMMP_LOG_PRIORITY_WARNING
+
+* "INFO " for DMMP_LOG_PRIORITY_INFO
+
+* "DEBUG" for DMMP_LOG_PRIORITY_DEBUG
+
+* "Invalid argument" for invalid log priority.
--- /dev/null
+.TH "dmmp_mpath_array_free" 3 "dmmp_mpath_array_free" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_mpath_array_free \- Free 'struct dmmp_mpath' pointer array.
+.SH SYNOPSIS
+.B "void" dmmp_mpath_array_free
+.BI "(struct dmmp_mpath **" dmmp_mps ","
+.BI "uint32_t " dmmp_mp_count ");"
+.SH ARGUMENTS
+.IP "dmmp_mps" 12
+Pointer of 'struct dmmp_mpath' array.
+.IP "dmmp_mp_count" 12
+uint32_t, the size of 'dmmp_mps' pointer array.
+.SH "DESCRIPTION"
+
+Free the 'dmmp_mps' pointer array generated by \fBdmmp_mpath_array_get\fP.
+If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_mpath_array_get" 3 "dmmp_mpath_array_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_mpath_array_get \- Query all existing multipath devices.
+.SH SYNOPSIS
+.B "int" dmmp_mpath_array_get
+.BI "(struct dmmp_context *" ctx ","
+.BI "struct dmmp_mpath ***" dmmp_mps ","
+.BI "uint32_t *" dmmp_mp_count ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_mps" 12
+Output pointer array of 'struct dmmp_mpath'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_mp_count" 12
+Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Query all existing multipath devices and store them into a pointer array.
+The memory of 'dmmp_mps' should be freed via \fBdmmp_mpath_array_free\fP.
+.SH "RETURN"
+int. Valid error codes are:
+
+* DMMP_OK
+
+* DMMP_ERR_BUG
+
+* DMMP_ERR_NO_MEMORY
+
+* DMMP_ERR_NO_DAEMON
+
+* DMMP_ERR_INCONSISTENT_DATA
+
+Error number could be converted to string by \fBdmmp_strerror\fP.
--- /dev/null
+.TH "dmmp_mpath_kdev_name_get" 3 "dmmp_mpath_kdev_name_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_mpath_kdev_name_get \- Retrieve kernel DEVNAME of certain mpath.
+.SH SYNOPSIS
+.B "const char *" dmmp_mpath_kdev_name_get
+.BI "(struct dmmp_mpath *" dmmp_mp ");"
+.SH ARGUMENTS
+.IP "dmmp_mp" 12
+Pointer of 'struct dmmp_mpath'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve DEVNAME name used by kernel uevent of specified mpath.
+For example: 'dm-1'.
+.SH "RETURN"
+const char *. No need to free this memory, the resources will get
+freed when \fBdmmp_mpath_array_free\fP.
--- /dev/null
+.TH "dmmp_mpath_name_get" 3 "dmmp_mpath_name_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_mpath_name_get \- Retrieve name(alias) of certain mpath.
+.SH SYNOPSIS
+.B "const char *" dmmp_mpath_name_get
+.BI "(struct dmmp_mpath *" dmmp_mp ");"
+.SH ARGUMENTS
+.IP "dmmp_mp" 12
+Pointer of 'struct dmmp_mpath'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve the name (also known as alias) of certain mpath.
+When the config 'user_friendly_names' been set 'no', the name will be
+identical to WWID retrieved by \fBdmmp_mpath_wwid_get\fP.
+.SH "RETURN"
+const char *. No need to free this memory, the resources will get
+freed when \fBdmmp_mpath_array_free\fP.
--- /dev/null
+.TH "dmmp_mpath_wwid_get" 3 "dmmp_mpath_wwid_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_mpath_wwid_get \- Retrieve WWID of certain mpath.
+.SH SYNOPSIS
+.B "const char *" dmmp_mpath_wwid_get
+.BI "(struct dmmp_mpath *" dmmp_mp ");"
+.SH ARGUMENTS
+.IP "dmmp_mp" 12
+Pointer of 'struct dmmp_mpath'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "RETURN"
+const char *. No need to free this memory, the resources will get
+freed when \fBdmmp_mpath_array_free\fP.
--- /dev/null
+.TH "dmmp_path_array_get" 3 "dmmp_path_array_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_array_get \- Retrieve path pointer array.
+.SH SYNOPSIS
+.B "void" dmmp_path_array_get
+.BI "(struct dmmp_path_group *" dmmp_pg ","
+.BI "struct dmmp_path ***" dmmp_ps ","
+.BI "uint32_t *" dmmp_p_count ");"
+.SH ARGUMENTS
+.IP "dmmp_pg" 12
+Pointer of 'struct dmmp_path_group'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_ps" 12
+Output pointer of 'struct dmmp_path' pointer array.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_p_count" 12
+Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+The memory of output pointer array is hold by 'struct dmmp_mpath', no
+need to free this memory, the resources will got freed when
+\fBdmmp_mpath_array_free\fP.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_path_blk_name_get" 3 "dmmp_path_blk_name_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_blk_name_get \- Retrieve block name.
+.SH SYNOPSIS
+.B "const char *" dmmp_path_blk_name_get
+.BI "(struct dmmp_path *" dmmp_p ");"
+.SH ARGUMENTS
+.IP "dmmp_p" 12
+Pointer of 'struct dmmp_path'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve block name of certain path. The example of block names are "sda",
+"nvme0n1".
+.SH "RETURN"
+const char *. No need to free this memory, the resources will get
+freed when \fBdmmp_mpath_array_free\fP.
--- /dev/null
+.TH "dmmp_path_group_array_get" 3 "dmmp_path_group_array_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_array_get \- Retrieve path groups pointer array.
+.SH SYNOPSIS
+.B "void" dmmp_path_group_array_get
+.BI "(struct dmmp_mpath *" dmmp_mp ","
+.BI "struct dmmp_path_group ***" dmmp_pgs ","
+.BI "uint32_t *" dmmp_pg_count ");"
+.SH ARGUMENTS
+.IP "dmmp_mp" 12
+Pointer of 'struct dmmp_mpath'.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_pgs" 12
+Output pointer of 'struct dmmp_path_group' pointer array.
+If this pointer is NULL, your program will be terminated by assert.
+.IP "dmmp_pg_count" 12
+Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve the path groups of certain mpath.
+
+The memory of output pointer array is hold by 'struct dmmp_mpath', no
+need to free this memory, the resources will got freed when
+\fBdmmp_mpath_array_free\fP.
+.SH "RETURN"
+void
--- /dev/null
+.TH "dmmp_path_group_id_get" 3 "dmmp_path_group_id_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_id_get \- Retrieve path group ID.
+.SH SYNOPSIS
+.B "uint32_t" dmmp_path_group_id_get
+.BI "(struct dmmp_path_group *" dmmp_pg ");"
+.SH ARGUMENTS
+.IP "dmmp_pg" 12
+Pointer of 'struct dmmp_path_group'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Retrieve the path group ID which could be used to switch active path group
+via command:
+
+multipathd -k'switch multipath mpathb group $id'
+.SH "RETURN"
+uint32_t.
--- /dev/null
+.TH "dmmp_path_group_priority_get" 3 "dmmp_path_group_priority_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_priority_get \- Retrieve path group priority.
+.SH SYNOPSIS
+.B "uint32_t" dmmp_path_group_priority_get
+.BI "(struct dmmp_path_group *" dmmp_pg ");"
+.SH ARGUMENTS
+.IP "dmmp_pg" 12
+Pointer of 'struct dmmp_path_group'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+The enabled path group with highest priority will be next active path group
+if active path group down.
+.SH "RETURN"
+uint32_t.
--- /dev/null
+.TH "dmmp_path_group_selector_get" 3 "dmmp_path_group_selector_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_selector_get \- Retrieve path group selector.
+.SH SYNOPSIS
+.B "const char *" dmmp_path_group_selector_get
+.BI "(struct dmmp_path_group *" dmmp_pg ");"
+.SH ARGUMENTS
+.IP "dmmp_pg" 12
+Pointer of 'struct dmmp_path_group'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Path group selector determine which path in active path group will be
+use to next I/O.
+.SH "RETURN"
+const char *.
--- /dev/null
+.TH "dmmp_path_group_status_get" 3 "dmmp_path_group_status_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_status_get \- Retrieve path group status.
+.SH SYNOPSIS
+.B "uint32_t" dmmp_path_group_status_get
+.BI "(struct dmmp_path_group *" dmmp_pg ");"
+.SH ARGUMENTS
+.IP "dmmp_pg" 12
+Pointer of 'struct dmmp_path_group'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+The valid path group statuses are:
+
+* DMMP_PATH_GROUP_STATUS_UNKNOWN
+
+* DMMP_PATH_GROUP_STATUS_ENABLED -- standby to be active
+
+* DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down
+
+* DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O
+.SH "RETURN"
+uint32_t.
--- /dev/null
+.TH "dmmp_path_group_status_str" 3 "dmmp_path_group_status_str" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_group_status_str \- Convert path group status to string.
+.SH SYNOPSIS
+.B "const char *" dmmp_path_group_status_str
+.BI "(uint32_t " pg_status ");"
+.SH ARGUMENTS
+.IP "pg_status" 12
+uint32_t. Path group status.
+When provided value is not a valid path group status, return "Invalid
+argument".
+.SH "DESCRIPTION"
+
+Convert path group status uint32_t to string (const char *).
+.SH "RETURN"
+const char *. Valid string are:
+
+* "Invalid argument"
+
+* "undef"
+
+* "enabled"
+
+* "disabled"
+
+* "active"
--- /dev/null
+.TH "dmmp_path_status_get" 3 "dmmp_path_status_get" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_status_get \- Retrieve the path status.
+.SH SYNOPSIS
+.B "uint32_t" dmmp_path_status_get
+.BI "(struct dmmp_path *" dmmp_p ");"
+.SH ARGUMENTS
+.IP "dmmp_p" 12
+Pointer of 'struct dmmp_path'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+The valid path statuses are:
+
+* DMMP_PATH_STATUS_UNKNOWN
+
+* DMMP_PATH_STATUS_DOWN
+
+Path is down and you shouldn't try to send commands to it.
+
+* DMMP_PATH_STATUS_UP
+
+Path is up and I/O can be sent to it.
+
+* DMMP_PATH_STATUS_SHAKY
+
+Only emc_clariion checker when path not available for "normal"
+operations.
+
+* DMMP_PATH_STATUS_GHOST
+
+Only hp_sw and rdac checkers. Indicates a "passive/standby"
+path on active/passive HP arrays. These paths will return valid
+answers to certain SCSI commands (tur, read_capacity, inquiry,
+start_stop), but will fail I/O commands. The path needs an
+initialization command to be sent to it in order for I/Os to
+succeed.
+
+* DMMP_PATH_STATUS_PENDING
+
+Available for all async checkers when a check IO is in flight.
+
+* DMMP_PATH_STATUS_TIMEOUT
+
+Only tur checker when command timed out.
+
+* DMMP_PATH_STATUS_DELAYED
+
+If a path fails after being up for less than delay_watch_checks checks,
+when it comes back up again, it will not be marked as up until it has
+been up for delay_wait_checks checks. During this time, it is marked as
+"delayed".
+.SH "RETURN"
+uint32_t.
--- /dev/null
+.TH "dmmp_path_status_str" 3 "dmmp_path_status_str" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_path_status_str \- Convert path status to string.
+.SH SYNOPSIS
+.B "const char *" dmmp_path_status_str
+.BI "(uint32_t " path_status ");"
+.SH ARGUMENTS
+.IP "path_status" 12
+uint32_t. Path status.
+When provided value is not a valid path status, return
+"Invalid argument".
+.SH "DESCRIPTION"
+
+Convert path status uint32_t to string (const char *):
+
+* DMMP_PATH_STATUS_UNKNOWN -- "undef"
+
+* DMMP_PATH_STATUS_DOWN -- "faulty"
+
+* DMMP_PATH_STATUS_UP -- "ready"
+
+* DMMP_PATH_STATUS_SHAKY -- "shaky"
+
+* DMMP_PATH_STATUS_GHOST -- "ghost"
+
+* DMMP_PATH_STATUS_PENDING -- "pending"
+
+* DMMP_PATH_STATUS_TIMEOUT -- "timeout"
+
+* DMMP_PATH_STATUS_REMOVED -- "removed"
+
+* DMMP_PATH_STATUS_DELAYED -- "delayed"
+.SH "RETURN"
+const char *. The meaning of status value.
--- /dev/null
+.TH "dmmp_reconfig" 3 "dmmp_reconfig" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_reconfig \- Instruct multipathd daemon to do reconfiguration.
+.SH SYNOPSIS
+.B "int" dmmp_reconfig
+.BI "(struct dmmp_context *" ctx ");"
+.SH ARGUMENTS
+.IP "ctx" 12
+Pointer of 'struct dmmp_context'.
+If this pointer is NULL, your program will be terminated by assert.
+.SH "DESCRIPTION"
+
+Instruct multipathd daemon to do reconfiguration.
+.SH "RETURN"
+int. Valid error codes are:
+
+* DMMP_OK
+
+* DMMP_ERR_BUG
+
+* DMMP_ERR_NO_MEMORY
+
+* DMMP_ERR_NO_DAEMON
+
+* DMMP_ERR_PERMISSION_DENY
+
+Error number could be converted to string by \fBdmmp_strerror\fP.
--- /dev/null
+.TH "dmmp_strerror" 3 "dmmp_strerror" "March 2021" "Device Mapper Multipath API - libdmmp Manual"
+.SH NAME
+dmmp_strerror \- Convert error code to string.
+.SH SYNOPSIS
+.B "const char *" dmmp_strerror
+.BI "(int " rc ");"
+.SH ARGUMENTS
+.IP "rc" 12
+int. Return code by libdmmp functions. When provided error code is not a
+valid error code, return "Invalid argument".
+.SH "DESCRIPTION"
+
+Convert error code (int) to string (const char *):
+
+* DMMP_OK -- "OK"
+
+* DMMP_ERR_BUG -- "BUG of libdmmp library"
+
+* DMMP_ERR_NO_MEMORY -- "Out of memory"
+
+* DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd,
+try to set bigger timeout value via dmmp_context_timeout_set ()"
+
+* DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon"
+
+* DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started"
+
+* DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not
+compatible with current library"
+
+* Other invalid error number -- "Invalid argument"
+.SH "RETURN"
+const char *. The meaning of provided error code.
#include <libdmmp/libdmmp.h>
-int main(int argc, char *argv[])
+int main(void)
{
struct dmmp_context *ctx = NULL;
struct dmmp_mpath **dmmp_mps = NULL;
return rc;
}
-int main(int argc, char *argv[])
+int main(void)
{
struct dmmp_context *ctx = NULL;
struct dmmp_mpath **dmmp_mps = NULL;
SONAME = 0
DEVLIB = libmpathcmd.so
LIBS = $(DEVLIB).$(SONAME)
+VERSION_SCRIPT := libmpathcmd.version
CFLAGS += $(LIB_CFLAGS)
OBJS = mpath_cmd.o
-all: $(LIBS)
+all: $(DEVLIB)
-$(LIBS): $(OBJS)
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
- $(LN) $@ $(DEVLIB)
+$(LIBS): $(OBJS) $(VERSION_SCRIPT)
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ \
+ -Wl,--version-script=$(VERSION_SCRIPT) -o $@ $(OBJS) $(LIBDEPS)
-install: $(LIBS)
+$(DEVLIB): $(LIBS)
+ $(LN) $(LIBS) $@
+
+install: all
$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir)
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
$(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB)
--- /dev/null
+/*
+ * Copyright (c) 2020 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * libmpathcmd ABI
+ *
+ * The ABI of libmpathcmd is supposed to remain stable. Removing symbols
+ * or altering existing symbols' semantics is not allowed. When changing a
+ * a symbol, either use a new name, or explicit symver directives.
+ *
+ * See libmultipath.version for general policy about version numbers.
+ */
+LIBMPATHCMD_1.0.0 {
+global:
+ __mpath_connect;
+ mpath_connect;
+ mpath_disconnect;
+ mpath_process_cmd;
+ mpath_recv_reply;
+ mpath_recv_reply_len;
+ mpath_recv_reply_data;
+ mpath_send_cmd;
+local:
+ *;
+};
SONAME = 0
DEVLIB = libmpathpersist.so
LIBS = $(DEVLIB).$(SONAME)
+VERSION_SCRIPT := libmpathpersist.version
CFLAGS += $(LIB_CFLAGS) -I$(multipathdir) -I$(mpathpersistdir) -I$(mpathcmddir)
+LDFLAGS += -L$(multipathdir) -L$(mpathcmddir)
-LIBDEPS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath \
- -L$(mpathcmddir) -lmpathcmd
+LIBDEPS += -lmultipath -lmpathcmd -ldevmapper -lpthread -ldl
OBJS = mpath_persist.o mpath_updatepr.o mpath_pr_ioctl.o
-all: $(LIBS)
+all: $(DEVLIB) man
-$(LIBS): $(OBJS)
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) $(LIBDEPS) -Wl,-soname=$@ -o $@ $(OBJS)
- $(LN) $(LIBS) $(DEVLIB)
+$(LIBS): $(OBJS) $(VERSION_SCRIPT)
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ \
+ -Wl,--version-script=$(VERSION_SCRIPT) -o $@ $(OBJS) $(LIBDEPS)
+
+$(DEVLIB): $(LIBS)
+ $(LN) $(LIBS) $@
+
+man:
$(GZIP) mpath_persistent_reserve_in.3 > mpath_persistent_reserve_in.3.gz
$(GZIP) mpath_persistent_reserve_out.3 > mpath_persistent_reserve_out.3.gz
-install: $(LIBS)
+install: all
$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir)
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(syslibdir)
--- /dev/null
+/*
+ * Copyright (c) 2020 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * libmpathpersist ABI
+ *
+ * The ABI of libmpathpersist is supposed to remain stable. Removing symbols
+ * or altering existing symbols' semantics is not allowed. When changing a
+ * a symbol, either use a new name, or explicit symver directives.
+ *
+ * See libmultipath.version for general policy about version numbers.
+ */
+LIBMPATHPERSIST_1.0.0 {
+global:
+
+ __mpath_persistent_reserve_in;
+ __mpath_persistent_reserve_out;
+ dumpHex;
+ mpath_alloc_prin_response;
+ mpath_lib_exit;
+ mpath_lib_init;
+ mpath_mx_alloc_len;
+ mpath_persistent_reserve_in;
+ mpath_persistent_reserve_init_vecs;
+ mpath_persistent_reserve_out;
+ mpath_persistent_reserve_free_vecs;
+ prin_do_scsi_ioctl;
+ prout_do_scsi_ioctl;
+ update_map_pr;
+
+local: *;
+};
+
+LIBMPATHPERSIST_1.1.0 {
+global:
+ libmpathpersist_init;
+ libmpathpersist_exit;
+} LIBMPATHPERSIST_1.0.0;
extern struct udev *udev;
+static void adapt_config(struct config *conf)
+{
+ conf->force_sync = 1;
+ set_max_fds(conf->max_fds);
+}
+
+int libmpathpersist_init(void)
+{
+ struct config *conf;
+ int rc = 0;
+
+ if (libmultipath_init()) {
+ condlog(0, "Failed to initialize libmultipath.");
+ return 1;
+ }
+ if (init_config(DEFAULT_CONFIGFILE)) {
+ condlog(0, "Failed to initialize multipath config.");
+ return 1;
+ }
+ conf = libmp_get_multipath_config();
+ adapt_config(conf);
+ libmp_put_multipath_config(conf);
+ return rc;
+}
+
struct config *
mpath_lib_init (void)
{
condlog(0, "Failed to initialize multipath config.");
return NULL;
}
- conf->force_sync = 1;
- set_max_fds(conf->max_fds);
-
+ adapt_config(conf);
return conf;
}
+static void libmpathpersist_cleanup(void)
+{
+ libmultipath_exit();
+ dm_lib_exit();
+}
+
int
mpath_lib_exit (struct config *conf)
{
- dm_lib_release();
- dm_lib_exit();
- cleanup_prio();
- cleanup_checkers();
free_config(conf);
- conf = NULL;
+ libmpathpersist_cleanup();
+ return 0;
+}
+
+int libmpathpersist_exit(void)
+{
+ uninit_config();
+ libmpathpersist_cleanup();
return 0;
}
return ret;
}
-int mpath_persistent_reserve_in (int fd, int rq_servact,
- struct prin_resp *resp, int noisy, int verbose)
-{
- int ret = mpath_persistent_reserve_init_vecs(verbose);
-
- if (ret != MPATH_PR_SUCCESS)
- return ret;
- ret = __mpath_persistent_reserve_in(fd, rq_servact, resp, noisy);
- mpath_persistent_reserve_free_vecs();
- return ret;
-}
-
-int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
- unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, int verbose)
-{
- int ret = mpath_persistent_reserve_init_vecs(verbose);
-
- if (ret != MPATH_PR_SUCCESS)
- return ret;
- ret = __mpath_persistent_reserve_out(fd, rq_servact, rq_scope, rq_type,
- paramp, noisy);
- mpath_persistent_reserve_free_vecs();
- return ret;
-}
-
static vector curmp;
static vector pathvec;
-void mpath_persistent_reserve_free_vecs(void)
+static void __mpath_persistent_reserve_free_vecs(vector curmp, vector pathvec)
{
free_multipathvec(curmp, KEEP_PATHS);
free_pathvec(pathvec, FREE_PATHS);
- curmp = pathvec = NULL;
}
-int mpath_persistent_reserve_init_vecs(int verbose)
+void mpath_persistent_reserve_free_vecs(void)
{
- struct config *conf = get_multipath_config();
+ __mpath_persistent_reserve_free_vecs(curmp, pathvec);
+ curmp = pathvec = NULL;
+}
- conf->verbosity = verbose;
- put_multipath_config(conf);
+static int __mpath_persistent_reserve_init_vecs(vector *curmp_p,
+ vector *pathvec_p, int verbose)
+{
+ libmp_verbosity = verbose;
- if (curmp)
+ if (*curmp_p)
return MPATH_PR_SUCCESS;
/*
* allocate core vectors to store paths and multipaths
*/
- curmp = vector_alloc ();
- pathvec = vector_alloc ();
+ *curmp_p = vector_alloc ();
+ *pathvec_p = vector_alloc ();
- if (!curmp || !pathvec){
+ if (!*curmp_p || !*pathvec_p){
condlog (0, "vector allocation failed.");
goto err;
}
- if (dm_get_maps(curmp))
+ if (dm_get_maps(*curmp_p))
goto err;
return MPATH_PR_SUCCESS;
err:
- mpath_persistent_reserve_free_vecs();
+ __mpath_persistent_reserve_free_vecs(*curmp_p, *pathvec_p);
+ *curmp_p = *pathvec_p = NULL;
return MPATH_PR_DMMP_ERROR;
}
-static int mpath_get_map(int fd, char **palias, struct multipath **pmpp)
+int mpath_persistent_reserve_init_vecs(int verbose)
+{
+ return __mpath_persistent_reserve_init_vecs(&curmp, &pathvec, verbose);
+}
+
+static int mpath_get_map(vector curmp, vector pathvec, int fd, char **palias,
+ struct multipath **pmpp)
{
int ret = MPATH_PR_DMMP_ERROR;
struct stat info;
return ret;
}
-int __mpath_persistent_reserve_in (int fd, int rq_servact,
- struct prin_resp *resp, int noisy)
+static int do_mpath_persistent_reserve_in (vector curmp, vector pathvec,
+ int fd, int rq_servact, struct prin_resp *resp, int noisy)
{
struct multipath *mpp;
int ret;
- ret = mpath_get_map(fd, NULL, &mpp);
+ ret = mpath_get_map(curmp, pathvec, fd, NULL, &mpp);
if (ret != MPATH_PR_SUCCESS)
return ret;
return ret;
}
-int __mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
- unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy)
+
+int __mpath_persistent_reserve_in (int fd, int rq_servact,
+ struct prin_resp *resp, int noisy)
+{
+ return do_mpath_persistent_reserve_in(curmp, pathvec, fd, rq_servact,
+ resp, noisy);
+}
+
+static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd,
+ int rq_servact, int rq_scope, unsigned int rq_type,
+ struct prout_param_descriptor *paramp, int noisy)
{
struct multipath *mpp;
char *alias;
uint64_t prkey;
struct config *conf;
- ret = mpath_get_map(fd, &alias, &mpp);
+ ret = mpath_get_map(curmp, pathvec, fd, &alias, &mpp);
if (ret != MPATH_PR_SUCCESS)
return ret;
memcpy(&prkey, paramp->sa_key, 8);
if (mpp->prkey_source == PRKEY_SOURCE_FILE && prkey &&
- ((!get_be64(mpp->reservation_key) &&
- rq_servact == MPATH_PROUT_REG_SA) ||
- rq_servact == MPATH_PROUT_REG_IGN_SA)) {
+ (rq_servact == MPATH_PROUT_REG_IGN_SA ||
+ (rq_servact == MPATH_PROUT_REG_SA &&
+ (!get_be64(mpp->reservation_key) ||
+ memcmp(paramp->key, &mpp->reservation_key, 8) == 0)))) {
memcpy(&mpp->reservation_key, paramp->sa_key, 8);
if (update_prkey_flags(alias, get_be64(mpp->reservation_key),
paramp->sa_flags)) {
}
if (memcmp(paramp->key, &mpp->reservation_key, 8) &&
- memcmp(paramp->sa_key, &mpp->reservation_key, 8)) {
+ memcmp(paramp->sa_key, &mpp->reservation_key, 8) &&
+ (prkey || rq_servact != MPATH_PROUT_REG_IGN_SA)) {
condlog(0, "%s: configured reservation key doesn't match: 0x%" PRIx64, alias, get_be64(mpp->reservation_key));
ret = MPATH_PR_SYNTAX_ERROR;
goto out1;
return ret;
}
+
+int __mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
+ unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy)
+{
+ return do_mpath_persistent_reserve_out(curmp, pathvec, fd, rq_servact,
+ rq_scope, rq_type, paramp,
+ noisy);
+}
+
+int mpath_persistent_reserve_in (int fd, int rq_servact,
+ struct prin_resp *resp, int noisy, int verbose)
+{
+ vector curmp = NULL, pathvec;
+ int ret = __mpath_persistent_reserve_init_vecs(&curmp, &pathvec,
+ verbose);
+
+ if (ret != MPATH_PR_SUCCESS)
+ return ret;
+ ret = do_mpath_persistent_reserve_in(curmp, pathvec, fd, rq_servact,
+ resp, noisy);
+ __mpath_persistent_reserve_free_vecs(curmp, pathvec);
+ return ret;
+}
+
+int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope,
+ unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, int verbose)
+{
+ vector curmp = NULL, pathvec;
+ int ret = __mpath_persistent_reserve_init_vecs(&curmp, &pathvec,
+ verbose);
+
+ if (ret != MPATH_PR_SUCCESS)
+ return ret;
+ ret = do_mpath_persistent_reserve_out(curmp, pathvec, fd, rq_servact,
+ rq_scope, rq_type, paramp, noisy);
+ __mpath_persistent_reserve_free_vecs(curmp, pathvec);
+ return ret;
+}
+
int
get_mpvec (vector curmp, vector pathvec, char * refwwid)
{
continue;
if (update_multipath_table(mpp, pathvec, DI_CHECKER) != DMP_OK ||
- update_multipath_status(mpp) != DMP_OK) {
+ update_mpp_paths(mpp, pathvec)) {
condlog(1, "error parsing map %s", mpp->wwid);
remove_map(mpp, pathvec, curmp, PURGE_VEC);
i--;
- }
+ } else
+ extract_hwe_from_path(mpp);
}
return MPATH_PR_SUCCESS ;
}
* DESCRIPTION :
* Initialize device mapper multipath configuration. This function must be invoked first
* before performing reservation management functions.
+ * Either this function or mpath_lib_init() may be used.
+ * Use this function to work with libmultipath's internal "struct config"
+ * and "struct udev". The latter will be initialized automatically.
+ * Call libmpathpersist_exit() for cleanup.
+ * RESTRICTIONS:
+ *
+ * RETURNS: 0->Success, 1->Failed.
+ */
+extern int libmpathpersist_init (void);
+
+/*
+ * DESCRIPTION :
+ * Initialize device mapper multipath configuration. This function must be invoked first
+ * before performing reservation management functions.
+ * Either this function or libmpathpersist_init() may be used.
+ * Use this function to work with an application-specific "struct config"
+ * and "struct udev". The latter must be initialized by the application.
+ * Call mpath_lib_exit() for cleanup.
* RESTRICTIONS:
*
* RETURNS: struct config ->Success, NULL->Failed.
* DESCRIPTION :
* Release device mapper multipath configuration. This function must be invoked after
* performing reservation management functions.
+ * Use this after initialization with mpath_lib_init().
* RESTRICTIONS:
*
* RETURNS: 0->Success, 1->Failed.
*/
extern int mpath_lib_exit (struct config *conf);
+/*
+ * DESCRIPTION :
+ * Release device mapper multipath configuration a. This function must be invoked after
+ * performing reservation management functions.
+ * Use this after initialization with libmpathpersist_init().
+ * Calling libmpathpersist_init() after libmpathpersist_exit() will fail.
+ * RESTRICTIONS:
+ *
+ * RETURNS: 0->Success, 1->Failed.
+ */
+extern int libmpathpersist_exit (void);
+
/*
* DESCRIPTION :
/*
* DESCRIPTION :
- * This function is like mpath_persistent_reserve_in(), except that it doesn't call
- * mpath_persistent_reserve_init_vecs() and mpath_persistent_reserve_free_vecs()
- * before and after the actual PR call.
+ * This function is like mpath_persistent_reserve_in(), except that it
+ * requires mpath_persistent_reserve_init_vecs() to be called before the
+ * PR call to set up internal variables. These must later be cleanup up
+ * by calling mpath_persistent_reserve_free_vecs().
+ *
+ * RESTRICTIONS:
+ * This function uses static internal variables, and is not thread-safe.
*/
extern int __mpath_persistent_reserve_in(int fd, int rq_servact,
struct prin_resp *resp, int noisy);
int verbose);
/*
* DESCRIPTION :
- * This function is like mpath_persistent_reserve_out(), except that it doesn't call
- * mpath_persistent_reserve_init_vecs() and mpath_persistent_reserve_free_vecs()
- * before and after the actual PR call.
+ * This function is like mpath_persistent_reserve_out(), except that it
+ * requires mpath_persistent_reserve_init_vecs() to be called before the
+ * PR call to set up internal variables. These must later be cleanup up
+ * by calling mpath_persistent_reserve_free_vecs().
+ *
+ * RESTRICTIONS:
+ * This function uses static internal variables, and is not thread-safe.
*/
extern int __mpath_persistent_reserve_out( int fd, int rq_servact, int rq_scope,
unsigned int rq_type, struct prout_param_descriptor *paramp,
* @verbose: Set verbosity level. Input argument. value:0 to 3. 0->disabled, 3->Max verbose
*
* RESTRICTIONS:
+ * This function uses static internal variables, and is not thread-safe.
*
* RETURNS: MPATH_PR_SUCCESS if successful else returns any of the status specified
* above in RETURN_STATUS.
* DESCRIPTION :
* This function frees data structures allocated by
* mpath_persistent_reserve_init_vecs().
+ *
+ * RESTRICTIONS:
+ * This function uses static internal variables, and is not thread-safe.
*/
void mpath_persistent_reserve_free_vecs(void);
--- /dev/null
+include ../Makefile.inc
+
+SONAME = 0
+DEVLIB = libmpathvalid.so
+LIBS = $(DEVLIB).$(SONAME)
+VERSION_SCRIPT := libmpathvalid.version
+
+CFLAGS += $(LIB_CFLAGS) -I$(multipathdir) -I$(mpathcmddir)
+
+LIBDEPS += -lpthread -ldevmapper -ldl -L$(multipathdir) \
+ -lmultipath -L$(mpathcmddir) -lmpathcmd -ludev
+
+OBJS = mpath_valid.o
+
+all: $(LIBS)
+
+$(LIBS): $(OBJS) $(VERSION_SCRIPT)
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS) -Wl,--version-script=libmpathvalid.version
+ $(LN) $(LIBS) $(DEVLIB)
+
+install: $(LIBS)
+ $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(syslibdir)
+ $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
+ $(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB)
+ $(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(includedir)
+ $(INSTALL_PROGRAM) -m 644 mpath_valid.h $(DESTDIR)$(includedir)
+
+uninstall:
+ $(RM) $(DESTDIR)$(syslibdir)/$(LIBS)
+ $(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB)
+ $(RM) $(DESTDIR)$(includedir)/mpath_valid.h
+
+clean: dep_clean
+ $(RM) core *.a *.o *.so *.so.* *.gz
+
+include $(wildcard $(OBJS:.o=.d))
+
+dep_clean:
+ $(RM) $(OBJS:.o=.d)
--- /dev/null
+MPATH_1.0 {
+ global:
+ mpathvalid_init;
+ mpathvalid_reload_config;
+ mpathvalid_exit;
+ mpathvalid_is_path;
+ mpathvalid_get_mode;
+ local:
+ *;
+};
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <libdevmapper.h>
+#include <libudev.h>
+#include <errno.h>
+
+#include "devmapper.h"
+#include "structs.h"
+#include "util.h"
+#include "config.h"
+#include "discovery.h"
+#include "wwids.h"
+#include "sysfs.h"
+#include "mpath_cmd.h"
+#include "valid.h"
+#include "mpath_valid.h"
+#include "debug.h"
+
+static unsigned int
+get_conf_mode(struct config *conf)
+{
+ if (conf->find_multipaths == FIND_MULTIPATHS_SMART)
+ return MPATH_SMART;
+ if (conf->find_multipaths == FIND_MULTIPATHS_GREEDY)
+ return MPATH_GREEDY;
+ return MPATH_STRICT;
+}
+
+static void
+set_conf_mode(struct config *conf, unsigned int mode)
+{
+ if (mode == MPATH_SMART)
+ conf->find_multipaths = FIND_MULTIPATHS_SMART;
+ else if (mode == MPATH_GREEDY)
+ conf->find_multipaths = FIND_MULTIPATHS_GREEDY;
+ else
+ conf->find_multipaths = FIND_MULTIPATHS_STRICT;
+}
+
+unsigned int
+mpathvalid_get_mode(void)
+{
+ int mode;
+ struct config *conf;
+
+ conf = get_multipath_config();
+ if (!conf)
+ mode = MPATH_MODE_ERROR;
+ else
+ mode = get_conf_mode(conf);
+ put_multipath_config(conf);
+ return mode;
+}
+
+static int
+convert_result(int result) {
+ switch (result) {
+ case PATH_IS_ERROR:
+ return MPATH_IS_ERROR;
+ case PATH_IS_NOT_VALID:
+ return MPATH_IS_NOT_VALID;
+ case PATH_IS_VALID:
+ return MPATH_IS_VALID;
+ case PATH_IS_VALID_NO_CHECK:
+ return MPATH_IS_VALID_NO_CHECK;
+ case PATH_IS_MAYBE_VALID:
+ return MPATH_IS_MAYBE_VALID;
+ }
+ return MPATH_IS_ERROR;
+}
+
+static void
+set_log_style(int log_style)
+{
+ /*
+ * convert MPATH_LOG_* to LOGSINK_*
+ * currently there is no work to do here.
+ */
+ logsink = log_style;
+}
+
+static int
+load_default_config(int verbosity)
+{
+ /* need to set verbosity here to control logging during init_config() */
+ libmp_verbosity = verbosity;
+ if (init_config(DEFAULT_CONFIGFILE))
+ return -1;
+ /* Need to override verbosity from init_config() */
+ libmp_verbosity = verbosity;
+
+ return 0;
+}
+
+int
+mpathvalid_init(int verbosity, int log_style)
+{
+ unsigned int version[3];
+
+ set_log_style(log_style);
+ if (libmultipath_init())
+ return -1;
+
+ skip_libmp_dm_init();
+ if (load_default_config(verbosity))
+ goto fail;
+
+ if (dm_prereq(version))
+ goto fail_config;
+
+ return 0;
+
+fail_config:
+ uninit_config();
+fail:
+ libmultipath_exit();
+ return -1;
+}
+
+int
+mpathvalid_reload_config(void)
+{
+ uninit_config();
+ return load_default_config(libmp_verbosity);
+}
+
+int
+mpathvalid_exit(void)
+{
+ uninit_config();
+ libmultipath_exit();
+ return 0;
+}
+
+/*
+ * name: name of path to check
+ * mode: mode to use for determination. MPATH_DEFAULT uses configured mode
+ * info: on success, contains the path wwid
+ * paths: array of the returned mpath_info from other claimed paths
+ * nr_paths: the size of the paths array
+ */
+int
+mpathvalid_is_path(const char *name, unsigned int mode, char **wwid,
+ const char **path_wwids, unsigned int nr_paths)
+{
+ struct config *conf;
+ int find_multipaths_saved, r = MPATH_IS_ERROR;
+ unsigned int i;
+ struct path *pp;
+
+ if (!name || mode >= MPATH_MODE_ERROR)
+ return r;
+ if (nr_paths > 0 && !path_wwids)
+ return r;
+ if (!udev)
+ return r;
+
+ pp = alloc_path();
+ if (!pp)
+ return r;
+
+ if (wwid) {
+ *wwid = (char *)malloc(WWID_SIZE);
+ if (!*wwid)
+ goto out;
+ }
+
+ conf = get_multipath_config();
+ if (!conf)
+ goto out_wwid;
+ find_multipaths_saved = conf->find_multipaths;
+ if (mode != MPATH_DEFAULT)
+ set_conf_mode(conf, mode);
+ r = convert_result(is_path_valid(name, conf, pp, true));
+ conf->find_multipaths = find_multipaths_saved;
+ put_multipath_config(conf);
+
+ if (r == MPATH_IS_MAYBE_VALID) {
+ for (i = 0; i < nr_paths; i++) {
+ if (path_wwids[i] &&
+ strncmp(path_wwids[i], pp->wwid, WWID_SIZE) == 0) {
+ r = MPATH_IS_VALID;
+ break;
+ }
+ }
+ }
+
+out_wwid:
+ if (wwid) {
+ if (r == MPATH_IS_VALID || r == MPATH_IS_VALID_NO_CHECK ||
+ r == MPATH_IS_MAYBE_VALID)
+ strlcpy(*wwid, pp->wwid, WWID_SIZE);
+ else {
+ free(*wwid);
+ *wwid = NULL;
+ }
+ }
+out:
+ free_path(pp);
+ return r;
+}
--- /dev/null
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This file is part of the device-mapper multipath userspace tools.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIB_MPATH_VALID_H
+#define LIB_MPATH_VALID_H
+
+#ifdef __cpluscplus
+extern "C" {
+#endif
+
+enum mpath_valid_mode {
+ MPATH_DEFAULT,
+ MPATH_STRICT,
+ MPATH_SMART,
+ MPATH_GREEDY,
+ MPATH_MODE_ERROR,
+};
+
+/*
+ * MPATH_IS_VALID_NO_CHECK is used to indicate that it is safe to skip
+ * checks to see if the device has already been released to the system
+ * for use by things other that multipath.
+ * MPATH_IS_MAYBE_VALID is used to indicate that this device would
+ * be a valid multipath path device if another device with the same
+ * wwid existed */
+enum mpath_valid_result {
+ MPATH_IS_ERROR = -1,
+ MPATH_IS_NOT_VALID,
+ MPATH_IS_VALID,
+ MPATH_IS_VALID_NO_CHECK,
+ MPATH_IS_MAYBE_VALID,
+};
+
+enum mpath_valid_log_style {
+ MPATH_LOG_STDERR = -1, /* log to STDERR */
+ MPATH_LOG_STDERR_TIMESTAMP, /* log to STDERR, with timestamps */
+ MPATH_LOG_SYSLOG, /* log to system log */
+};
+
+enum mpath_valid_verbosity {
+ MPATH_LOG_PRIO_NOLOG = -1, /* log nothing */
+ MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_PRIO_WARN,
+ MPATH_LOG_PRIO_NOTICE,
+ MPATH_LOG_PRIO_INFO,
+ MPATH_LOG_PRIO_DEBUG,
+};
+
+/* Function declarations */
+
+/*
+ * DESCRIPTION:
+ * Initialize the device mapper multipath configuration. This
+ * function must be invoked before calling any other
+ * libmpathvalid functions. Call mpathvalid_exit() to cleanup.
+ * @verbosity: the logging level (mpath_valid_verbosity)
+ * @log_style: the logging style (mpath_valid_log_style)
+ *
+ * RESTRICTIONS:
+ * Calling mpathvalid_init() after calling mpathvalid_exit() has no
+ * effect.
+ *
+ * RETURNS: 0 = Success, -1 = Failure
+ */
+int mpathvalid_init(int verbosity, int log_style);
+
+
+/*
+ * DESCRIPTION:
+ * Reread the multipath configuration files and reinitalize
+ * the device mapper multipath configuration. This function can
+ * be called as many times as necessary.
+ *
+ * RETURNS: 0 = Success, -1 = Failure
+ */
+int mpathvalid_reload_config(void);
+
+
+/*
+ * DESCRIPTION:
+ * Release the device mapper multipath configuration. This
+ * function must be called to cleanup resoures allocated by
+ * mpathvalid_init(). After calling this function, no futher
+ * libmpathvalid functions may be called.
+ *
+ * RETURNS: 0 = Success, -1 = Failure
+ */
+int mpathvalid_exit(void);
+
+/*
+ * DESCRIPTION:
+ * Return the configured find_multipaths claim mode, using the
+ * configuration from either mpathvalid_init() or
+ * mpathvalid_reload_config()
+ *
+ * RETURNS:
+ * MPATH_STRICT, MPATH_SMART, MPATH_GREEDY, or MPATH_MODE_ERROR
+ *
+ * MPATH_STRICT = find_multiapths (yes|on|no|off)
+ * MPATH_SMART = find_multipaths smart
+ * MPATH_GREEDY = find_multipaths greedy
+ * MPATH_MODE_ERROR = multipath configuration not initialized
+ */
+unsigned int mpathvalid_get_mode(void);
+/*
+ * DESCRIPTION:
+ * Return whether device-mapper multipath claims a path device,
+ * using the configuration read from either mpathvalid_init() or
+ * mpathvalid_reload_config(). If the device is either claimed or
+ * potentially claimed (MPATH_IS_VALID, MPATH_IS_VALID_NO_CHECK,
+ * or MPATH_IS_MAYBE_VALID) and wwid is not NULL, then *wiid will
+ * be set to point to the wwid of device. If set, *wwid must be
+ * freed by the caller. path_wwids is an obptional parameter that
+ * points to an array of wwids, that were returned from previous
+ * calls to mpathvalid_is_path(). These are wwids of existing
+ * devices that are or potentially are claimed by device-mapper
+ * multipath. path_wwids is used with the MPATH_SMART claim mode,
+ * to claim devices when another device with the same wwid exists.
+ * nr_paths must either be set to the number of elements of
+ * path_wwids, or 0, if path_wwids is NULL.
+ * @name: The kernel name of the device. input argument
+ * @mode: the find_multipaths claim mode (mpath_valid_mode). input argument
+ * @wwid: address of a pointer to the path wwid, or NULL. Output argument.
+ * Set if path is/may be claimed. If set, must be freed by caller
+ * @path_wwids: Array of pointers to path wwids, or NULL. input argument
+ * @nr_paths: number of elements in path_wwids array. input argument.
+ *
+ * RETURNS: device claim result (mpath_valid_result)
+ * Also sets *wwid if wwid is not NULL, and the claim result is
+ * MPATH_IS_VALID, MPATH_IS_VALID_NO_CHECK, or
+ * MPATH_IS_MAYBE_VALID
+ */
+int mpathvalid_is_path(const char *name, unsigned int mode, char **wwid,
+ const char **path_wwids, unsigned int nr_paths);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LIB_PATH_VALID_H */
SONAME = 0
DEVLIB = libmultipath.so
LIBS = $(DEVLIB).$(SONAME)
+VERSION_SCRIPT := libmultipath.version
CFLAGS += $(LIB_CFLAGS) -I$(mpathcmddir) -I$(mpathpersistdir) -I$(nvmedir)
io_err_stat.o dm-generic.o generic.o foreign.o nvme-lib.o \
libsg.o valid.o
-all: $(LIBS)
+all: $(DEVLIB)
nvme-lib.o: nvme-lib.c nvme-ioctl.c nvme-ioctl.h
$(CC) $(CFLAGS) -Wno-unused-function -c -o $@ $<
nvme-ioctl.h: nvme/nvme-ioctl.h
$(call make_static,$<,$@)
-$(LIBS): $(OBJS)
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ -o $@ $(OBJS) $(LIBDEPS)
- $(LN) $@ $(DEVLIB)
-install:
+$(LIBS): $(OBJS) $(VERSION_SCRIPT)
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ \
+ -Wl,--version-script=$(VERSION_SCRIPT) -o $@ $(OBJS) $(LIBDEPS)
+
+$(DEVLIB): $(LIBS)
+ $(LN) $(LIBS) $@
+
+../tests/$(LIBS): $(OBJS) $(VERSION_SCRIPT)
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=`basename $@` \
+ -o $@ $(OBJS) $(LIBDEPS)
+ $(LN) $@ ${@:.so.0=.so}
+
+test-lib: ../tests/$(LIBS)
+
+install: all
$(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir)
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(libdir)
#include "config.h"
#include "util.h"
#include "errno.h"
+#include "devmapper.h"
/*
return n;
}
+static int
+id_already_taken(int id, const char *prefix, const char *map_wwid)
+{
+ char alias[LINE_MAX];
+
+ if (format_devname(alias, id, LINE_MAX, prefix) < 0)
+ return 0;
+
+ 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;
+}
+
+
/*
* Returns: 0 if matching entry in WWIDs file found
* -1 if an error occurs
*/
static int
lookup_binding(FILE *f, const char *map_wwid, char **map_alias,
- const char *prefix)
+ const char *prefix, int check_if_taken)
{
char buf[LINE_MAX];
unsigned int line_nr = 0;
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;
+ }
+ 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 < 0) {
condlog(0, "no more available user_friendly_names");
return -1;
goto out;
}
- id = lookup_binding(f, wwid, &alias, NULL);
+ id = lookup_binding(f, wwid, &alias, NULL, 0);
if (alias) {
condlog(3, "Use existing binding [%s] for WWID [%s]",
alias, wwid);
return NULL;
}
- id = lookup_binding(f, wwid, &alias, prefix);
+ id = lookup_binding(f, wwid, &alias, prefix, 1);
if (id < 0) {
fclose(f);
return NULL;
#include <stddef.h>
#include <dlfcn.h>
#include <sys/stat.h>
+#include <urcu.h>
+#include <urcu/uatomic.h>
#include "debug.h"
#include "checkers.h"
#include "vector.h"
+#include "util.h"
struct checker_class {
struct list_head node;
int (*mp_init)(struct checker *); /* to allocate the mpcontext */
void (*free)(struct checker *); /* to free the context */
void (*reset)(void); /* to reset the global variables */
+ void *(*thread)(void *); /* async thread entry point */
const char **msgtable;
short msgtable_size;
};
c = MALLOC(sizeof(struct checker_class));
if (c) {
INIT_LIST_HEAD(&c->node);
- c->refcount = 1;
+ uatomic_set(&c->refcount, 1);
}
return c;
}
+/* Use uatomic_{sub,add}_return() to ensure proper memory barriers */
+static int checker_class_ref(struct checker_class *cls)
+{
+ return uatomic_add_return(&cls->refcount, 1);
+}
+
+static int checker_class_unref(struct checker_class *cls)
+{
+ return uatomic_sub_return(&cls->refcount, 1);
+}
+
void free_checker_class(struct checker_class *c)
{
+ int cnt;
+
if (!c)
return;
- c->refcount--;
- if (c->refcount) {
- condlog(4, "%s checker refcount %d",
- c->name, c->refcount);
+ cnt = checker_class_unref(c);
+ if (cnt != 0) {
+ condlog(cnt < 0 ? 1 : 4, "%s checker refcount %d",
+ c->name, cnt);
return;
}
condlog(3, "unloading %s checker", c->name);
c->mp_init = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_mp_init");
c->reset = (void (*)(void)) dlsym(c->handle, "libcheck_reset");
- /* These 2 functions can be NULL. call dlerror() to clear out any
+ c->thread = (void *(*)(void*)) dlsym(c->handle, "libcheck_thread");
+ /* These 3 functions can be NULL. call dlerror() to clear out any
* error string */
dlerror();
return generic_msg[CHECKER_MSGID_NONE];
}
+static void checker_cleanup_thread(void *arg)
+{
+ struct checker_class *cls = arg;
+
+ (void)checker_class_unref(cls);
+ rcu_unregister_thread();
+}
+
+static void *checker_thread_entry(void *arg)
+{
+ struct checker_context *ctx = arg;
+ void *rv;
+
+ rcu_register_thread();
+ pthread_cleanup_push(checker_cleanup_thread, ctx->cls);
+ rv = ctx->cls->thread(ctx);
+ pthread_cleanup_pop(1);
+ return rv;
+}
+
+int start_checker_thread(pthread_t *thread, const pthread_attr_t *attr,
+ struct checker_context *ctx)
+{
+ int rv;
+
+ assert(ctx && ctx->cls && ctx->cls->thread);
+ /* Take a ref here, lest the class be freed before the thread starts */
+ (void)checker_class_ref(ctx->cls);
+ rv = pthread_create(thread, attr, checker_thread_entry, ctx);
+ if (rv != 0) {
+ condlog(1, "failed to start checker thread for %s: %m",
+ ctx->cls->name);
+ checker_class_unref(ctx->cls);
+ }
+ return rv;
+}
+
void checker_clear_message (struct checker *c)
{
if (!c)
if (!src)
return;
- src->refcount++;
+ (void)checker_class_ref(dst->cls);
}
int init_checkers(const char *multipath_dir)
{
+#ifdef LOAD_ALL_SHARED_LIBS
+ static const char *const all_checkers[] = {
+ DIRECTIO,
+ TUR,
+ HP_SW,
+ RDAC,
+ EMC_CLARIION,
+ READSECTOR0,
+ CCISS_TUR,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(all_checkers); i++)
+ add_checker_class(multipath_dir, all_checkers[i]);
+#else
if (!add_checker_class(multipath_dir, DEFAULT_CHECKER))
return 1;
+#endif
return 0;
}
#ifndef _CHECKERS_H
#define _CHECKERS_H
+#include <pthread.h>
#include "list.h"
#include "memory.h"
#include "defaults.h"
void checker_set_fd (struct checker *, int);
void checker_enable (struct checker *);
void checker_disable (struct checker *);
+/*
+ * start_checker_thread(): start async path checker thread
+ *
+ * This function provides a wrapper around pthread_create().
+ * The created thread will call the DSO's "libcheck_thread" function with the
+ * checker context as argument.
+ *
+ * Rationale:
+ * Path checkers that do I/O may hang forever. To avoid blocking, some
+ * checkers therefore use asyncronous, detached threads for checking
+ * the paths. These threads may continue hanging if multipathd is stopped.
+ * In this case, we can't unload the checker DSO at exit. In order to
+ * avoid race conditions and crashes, the entry point of the thread
+ * needs to be in libmultipath, not in the DSO itself.
+ *
+ * @param arg: pointer to struct checker_context.
+ */
+struct checker_context {
+ struct checker_class *cls;
+};
+int start_checker_thread (pthread_t *thread, const pthread_attr_t *attr,
+ struct checker_context *ctx);
int checker_check (struct checker *, int);
int checker_is_sync(const struct checker *);
const char *checker_name (const struct checker *);
int libcheck_check(struct checker *);
int libcheck_init(struct checker *);
void libcheck_free(struct checker *);
+void *libcheck_thread(struct checker_context *ctx);
+
/*
* msgid => message map.
*
include ../../Makefile.inc
CFLAGS += $(LIB_CFLAGS) -I..
+LDFLAGS += -L.. -lmultipath
+LIBDEPS = -lmultipath -laio -lpthread -lrt
# If you add or remove a checker also update multipath/multipath.conf.5
LIBS= \
all: $(LIBS)
-libcheckdirectio.so: directio.o
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -laio
-
libcheck%.so: %.o
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS)
install:
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(libdir)
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
-#include <urcu.h>
#include <urcu/uatomic.h>
#include "checkers.h"
pthread_cond_t active;
int holders; /* uatomic access only */
int msgid;
+ struct checker_context ctx;
};
int libcheck_init (struct checker * c)
pthread_mutex_init(&ct->lock, NULL);
if (fstat(c->fd, &sb) == 0)
ct->devt = sb.st_rdev;
+ ct->ctx.cls = c->cls;
c->context = ct;
return 0;
holders = uatomic_sub_return(&ct->holders, 1);
if (!holders)
cleanup_context(ct);
- rcu_unregister_thread();
}
/*
#define tur_deep_sleep(x) do {} while (0)
#endif /* TUR_TEST_MAJOR */
-static void *tur_thread(void *ctx)
+void *libcheck_thread(struct checker_context *ctx)
{
- struct tur_checker_context *ct = ctx;
+ struct tur_checker_context *ct =
+ container_of(ctx, struct tur_checker_context, ctx);
int state, running;
short msgid;
/* This thread can be canceled, so setup clean up */
tur_thread_cleanup_push(ct);
- rcu_register_thread();
condlog(4, "%d:%d : tur checker starting up", major(ct->devt),
minor(ct->devt));
uatomic_set(&ct->running, 1);
tur_set_async_timeout(c);
setup_thread_attr(&attr, 32 * 1024, 1);
- r = pthread_create(&ct->thread, &attr, tur_thread, ct);
+ r = start_checker_thread(&ct->thread, &attr, &ct->ctx);
pthread_attr_destroy(&attr);
if (r) {
uatomic_sub(&ct->holders, 1);
#include "devmapper.h"
#include "mpath_cmd.h"
#include "propsel.h"
+#include "foreign.h"
+
+/*
+ * We don't support re-initialization after
+ * libmultipath_exit().
+ */
+static bool libmultipath_exit_called;
+static pthread_once_t _init_once = PTHREAD_ONCE_INIT;
+static pthread_once_t _exit_once = PTHREAD_ONCE_INIT;
+struct udev *udev;
+
+static void _udev_init(void)
+{
+ if (udev)
+ udev_ref(udev);
+ else
+ udev = udev_new();
+ if (!udev)
+ condlog(0, "%s: failed to initialize udev", __func__);
+}
+
+static bool _is_libmultipath_initialized(void)
+{
+ return !libmultipath_exit_called && !!udev;
+}
+
+int libmultipath_init(void)
+{
+ pthread_once(&_init_once, _udev_init);
+ return !_is_libmultipath_initialized();
+}
+
+static void _libmultipath_exit(void)
+{
+ libmultipath_exit_called = true;
+ cleanup_foreign();
+ cleanup_checkers();
+ cleanup_prio();
+ libmp_dm_exit();
+ udev_unref(udev);
+}
+
+void libmultipath_exit(void)
+{
+ pthread_once(&_exit_once, _libmultipath_exit);
+}
+
+static struct config __internal_config;
+struct config *libmp_get_multipath_config(void)
+{
+ if (!__internal_config.hwtable)
+ /* not initialized */
+ return NULL;
+ return &__internal_config;
+}
+
+struct config *get_multipath_config(void)
+ __attribute__((weak, alias("libmp_get_multipath_config")));
+
+void libmp_put_multipath_config(void *conf __attribute__((unused)))
+{
+ /* empty */
+}
+
+void put_multipath_config(void *conf)
+ __attribute__((weak, alias("libmp_put_multipath_config")));
static int
hwe_strmatch (const struct hwentry *hwe1, const struct hwentry *hwe2)
int i;
struct mpentry * mpe;
- if (!wwid)
+ if (!wwid || !*wwid)
return NULL;
vector_foreach_slot (mptable, mpe, i)
merge_num(flush_on_last_del);
merge_num(fast_io_fail);
merge_num(dev_loss);
+ merge_num(eh_deadline);
merge_num(user_friendly_names);
merge_num(retain_hwhandler);
merge_num(detect_prio);
merge_num(max_sectors_kb);
merge_num(ghost_delay);
merge_num(all_tg_pt);
+ merge_num(recheck_wwid);
merge_num(vpd_vendor_id);
merge_num(san_path_err_threshold);
merge_num(san_path_err_forget_rate);
int i, j;
vector_foreach_slot(mptable, mp1, i) {
+ /* drop invalid multipath configs */
+ if (!mp1->wwid) {
+ condlog(0, "multipaths config section missing wwid");
+ vector_del_slot(mptable, i--);
+ free_mpe(mp1);
+ continue;
+ }
j = i + 1;
vector_foreach_slot_after(mptable, mp2, j) {
- if (strcmp(mp1->wwid, mp2->wwid))
+ if (!mp2->wwid || strcmp(mp1->wwid, mp2->wwid))
continue;
condlog(1, "%s: duplicate multipath config section for %s",
__func__, mp1->wwid);
hwe->flush_on_last_del = dhwe->flush_on_last_del;
hwe->fast_io_fail = dhwe->fast_io_fail;
hwe->dev_loss = dhwe->dev_loss;
+ hwe->eh_deadline = dhwe->eh_deadline;
hwe->user_friendly_names = dhwe->user_friendly_names;
hwe->retain_hwhandler = dhwe->retain_hwhandler;
hwe->detect_prio = dhwe->detect_prio;
return;
}
-struct config *
-alloc_config (void)
+static struct config *alloc_config (void)
{
return (struct config *)MALLOC(sizeof(struct config));
}
-void
-free_config (struct config * conf)
+static void _uninit_config(struct config *conf)
{
if (!conf)
- return;
+ conf = &__internal_config;
if (conf->multipath_dir)
FREE(conf->multipath_dir);
free_hwtable(conf->hwtable);
free_hwe(conf->overrides);
free_keywords(conf->keywords);
- FREE(conf);
+
+ memset(conf, 0, sizeof(*conf));
+}
+
+void uninit_config(void)
+{
+ _uninit_config(&__internal_config);
+}
+
+void free_config(struct config *conf)
+{
+ if (!conf)
+ return;
+ else if (conf == &__internal_config) {
+ condlog(0, "ERROR: %s called for internal config. Use uninit_config() instead",
+ __func__);
+ return;
+ }
+
+ _uninit_config(conf);
+ free(conf);
}
/* if multipath fails to process the config directory, it should continue,
}
#endif
-struct config *
-load_config (char * file)
+static int _init_config (const char *file, struct config *conf);
+
+int init_config(const char *file)
+{
+ return _init_config(file, &__internal_config);
+}
+
+struct config *load_config(const char *file)
{
struct config *conf = alloc_config();
+ if (conf && !_init_config(file, conf))
+ return conf;
+
+ free(conf);
+ return NULL;
+}
+
+int _init_config (const char *file, struct config *conf)
+{
+
if (!conf)
- return NULL;
+ conf = &__internal_config;
/*
- * internal defaults
+ * Processing the config file will overwrite conf->verbosity if set
+ * When we return, we'll copy the config value back
*/
- conf->verbosity = DEFAULT_VERBOSITY;
+ conf->verbosity = libmp_verbosity;
+ /*
+ * 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->remove_retries = 0;
conf->ghost_delay = DEFAULT_GHOST_DELAY;
conf->all_tg_pt = DEFAULT_ALL_TG_PT;
+ conf->recheck_wwid = DEFAULT_RECHECK_WWID;
/*
* preload default hwtable
*/
!conf->wwids_file || !conf->prkeys_file)
goto out;
- return conf;
+ libmp_verbosity = conf->verbosity;
+ return 0;
out:
- free_config(conf);
- return NULL;
+ _uninit_config(conf);
+ return 1;
}
char *get_uid_attribute_by_attrs(struct config *conf,
#define ORIGIN_DEFAULT 0
#define ORIGIN_CONFIG 1
-/*
- * In kernel, fast_io_fail == 0 means immediate failure on rport delete.
- * OTOH '0' means not-configured in various places in multipath-tools.
- */
-#define MP_FAST_IO_FAIL_UNSET (0)
-#define MP_FAST_IO_FAIL_OFF (-1)
-#define MP_FAST_IO_FAIL_ZERO (-2)
-
enum devtypes {
DEV_NONE,
DEV_DEVT,
int flush_on_last_del;
int fast_io_fail;
unsigned int dev_loss;
+ int eh_deadline;
int user_friendly_names;
int retain_hwhandler;
int detect_prio;
int ghost_delay;
int all_tg_pt;
int vpd_vendor_id;
+ int recheck_wwid;
char * bl_product;
};
int attribute_flags;
int fast_io_fail;
unsigned int dev_loss;
+ int eh_deadline;
int log_checker_err;
int allow_queueing;
int allow_usb_devices;
int find_multipaths_timeout;
int marginal_pathgroups;
int skip_delegate;
- unsigned int version[3];
unsigned int sequence_nr;
+ int recheck_wwid;
char * multipath_dir;
char * selector;
char *enable_foreign;
};
-extern struct udev * udev;
+/**
+ * extern variable: udev
+ *
+ * A &struct udev instance used by libmultipath. libmultipath expects
+ * a valid, initialized &struct udev in this variable.
+ * An application can define this variable itself, in which case
+ * the applications's instance will take precedence.
+ * The application can initialize and destroy this variable by
+ * calling libmultipath_init() and libmultipath_exit(), respectively,
+ * whether or not it defines the variable itself.
+ * An application can initialize udev with udev_new() before calling
+ * libmultipath_init(), e.g. if it has to make libudev calls before
+ * libmultipath calls. If an application wants to keep using the
+ * udev variable after calling libmultipath_exit(), it should have taken
+ * an additional reference on it beforehand. This is the case e.g.
+ * after initiazing udev with udev_new().
+ */
+extern struct udev *udev;
+
+/**
+ * libmultipath_init() - library initialization
+ *
+ * This function initializes libmultipath data structures.
+ * It is light-weight; some other initializations, like device-mapper
+ * initialization, are done lazily when the respective functionality
+ * is required.
+ *
+ * Clean up by libmultipath_exit() when the program terminates.
+ * It is an error to call libmultipath_init() after libmultipath_exit().
+ * Return: 0 on success, 1 on failure.
+ */
+int libmultipath_init(void);
+
+/**
+ * libmultipath_exit() - library un-initialization
+ *
+ * This function un-initializes libmultipath data structures.
+ * It is recommended to call this function at program exit.
+ * If the application also calls dm_lib_exit(), it should do so
+ * after libmultipath_exit().
+ *
+ * Calls to libmultipath_init() after libmultipath_exit() will fail
+ * (in other words, libmultipath can't be re-initialized).
+ * Any other libmultipath calls after libmultipath_exit() may cause
+ * undefined behavior.
+ */
+void libmultipath_exit(void);
int find_hwe (const struct _vector *hwtable,
const char * vendor, const char * product, const char *revision,
int store_hwe (vector hwtable, struct hwentry *);
-struct config *load_config (char * file);
-struct config * alloc_config (void);
+struct config *load_config (const char *file);
void free_config (struct config * conf);
-extern struct config *get_multipath_config(void);
-extern void put_multipath_config(void *);
+int init_config(const char *file);
+void uninit_config(void);
+
+/*
+ * libmultipath provides default implementations of
+ * get_multipath_config() and put_multipath_config().
+ * Applications using these should use init_config(file, NULL)
+ * to load the configuration, rather than load_config(file).
+ * Likewise, uninit_config() should be used for teardown, but
+ * using free_config() for that is supported, too.
+ * Applications can define their own {get,put}_multipath_config()
+ * functions, which override the library-internal ones, but
+ * could still call libmp_{get,put}_multipath_config().
+ */
+struct config *libmp_get_multipath_config(void);
+struct config *get_multipath_config(void);
+void libmp_put_multipath_config(void *);
+void put_multipath_config(void *);
int parse_uid_attrs(char *uid_attrs, struct config *conf);
char *get_uid_attribute_by_attrs(struct config *conf,
mpp->disable_queueing = 0;
/*
+ * If this map was created with add_map_without_path(),
+ * mpp->hwe might not be set yet.
+ */
+ if (!mpp->hwe)
+ extract_hwe_from_path(mpp);
+
+ /*
* properties selectors
*
* Ordering matters for some properties:
select_gid(conf, mpp);
select_fast_io_fail(conf, mpp);
select_dev_loss(conf, mpp);
+ select_eh_deadline(conf, mpp);
select_reservation_key(conf, mpp);
select_deferred_remove(conf, mpp);
select_marginal_path_err_sample_time(conf, mpp);
return udd;
}
-static void
-trigger_udev_change(const struct multipath *mpp)
-{
- static const char change[] = "change";
- struct udev_device *udd = get_udev_for_mpp(mpp);
- if (!udd)
- return;
- condlog(3, "triggering %s uevent for %s", change, mpp->alias);
- sysfs_attr_set_value(udd, "uevent", change, sizeof(change)-1);
- udev_device_unref(udd);
-}
-
-static void trigger_partitions_udev_change(struct udev_device *dev,
- const char *action, int len)
+void trigger_partitions_udev_change(struct udev_device *dev,
+ const char *action, int len)
{
struct udev_enumerate *part_enum;
struct udev_list_entry *item;
}
static int
-is_mpp_known_to_udev(const struct multipath *mpp)
-{
- struct udev_device *udd = get_udev_for_mpp(mpp);
- int ret = (udd != NULL);
- udev_device_unref(udd);
- return ret;
-}
-
-static int
sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload)
{
struct pathgroup * pgp;
return err;
}
-static void
-select_reload_action(struct multipath *mpp, const struct multipath *cmpp,
- const char *reason)
+static bool is_udev_ready(struct multipath *cmpp)
{
struct udev_device *mpp_ud;
const char *env;
+ bool rc;
/*
* MPATH_DEVICE_READY != 1 can mean two things:
*/
mpp_ud = get_udev_for_mpp(cmpp);
+ if (!mpp_ud)
+ return true;
env = udev_device_get_property_value(mpp_ud, "MPATH_DEVICE_READY");
- if ((!env || strcmp(env, "1")) && count_active_paths(mpp) > 0)
- mpp->force_udev_reload = 1;
+ rc = (env != NULL && !strcmp(env, "1"));
udev_device_unref(mpp_ud);
+ condlog(4, "%s: %s: \"%s\" -> %d\n", __func__, cmpp->alias, env, rc);
+ return rc;
+}
+
+static void
+select_reload_action(struct multipath *mpp, const char *reason)
+{
mpp->action = ACT_RELOAD;
- condlog(3, "%s: set ACT_RELOAD (%s%s)", mpp->alias,
- mpp->force_udev_reload ? "forced, " : "",
- reason);
+ condlog(3, "%s: set ACT_RELOAD (%s)", mpp->alias, reason);
}
void select_action (struct multipath *mpp, const struct _vector *curmp,
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);
+ return;
+ }
+
if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF &&
!!strstr(mpp->features, "queue_if_no_path") !=
!!strstr(cmpp->features, "queue_if_no_path")) {
- select_reload_action(mpp, cmpp, "no_path_retry change");
+ select_reload_action(mpp, "no_path_retry change");
return;
}
if ((mpp->retain_hwhandler != RETAIN_HWHANDLER_ON ||
(strlen(cmpp->hwhandler) != strlen(mpp->hwhandler) ||
strncmp(cmpp->hwhandler, mpp->hwhandler,
strlen(mpp->hwhandler)))) {
- select_reload_action(mpp, cmpp, "hwhandler change");
+ select_reload_action(mpp, "hwhandler change");
return;
}
!!strstr(mpp->features, "retain_attached_hw_handler") !=
!!strstr(cmpp->features, "retain_attached_hw_handler") &&
get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) {
- select_reload_action(mpp, cmpp, "retain_hwhandler change");
+ select_reload_action(mpp, "retain_hwhandler change");
return;
}
remove_feature(&cmpp_feat, "queue_if_no_path");
remove_feature(&cmpp_feat, "retain_attached_hw_handler");
if (strncmp(mpp_feat, cmpp_feat, PARAMS_SIZE)) {
- select_reload_action(mpp, cmpp, "features change");
+ select_reload_action(mpp, "features change");
FREE(cmpp_feat);
FREE(mpp_feat);
return;
if (!cmpp->selector || strncmp(cmpp->selector, mpp->selector,
strlen(mpp->selector))) {
- select_reload_action(mpp, cmpp, "selector change");
+ select_reload_action(mpp, "selector change");
return;
}
if (cmpp->minio != mpp->minio) {
- select_reload_action(mpp, cmpp, "minio change");
+ select_reload_action(mpp, "minio change");
return;
}
if (!cmpp->pg || VECTOR_SIZE(cmpp->pg) != VECTOR_SIZE(mpp->pg)) {
- select_reload_action(mpp, cmpp, "path group number change");
+ select_reload_action(mpp, "path group number change");
return;
}
if (pgcmp(mpp, cmpp)) {
- select_reload_action(mpp, cmpp, "path group topology change");
+ select_reload_action(mpp, "path group topology change");
return;
}
if (cmpp->nextpg != mpp->bestpg) {
mpp->alias);
return;
}
- if (!is_mpp_known_to_udev(cmpp)) {
- mpp->action = ACT_RELOAD;
- condlog(3, "%s: set ACT_RELOAD (udev device not initialized)",
- mpp->alias);
- return;
- }
mpp->action = ACT_NOTHING;
condlog(3, "%s: set ACT_NOTHING (map unchanged)",
mpp->alias);
{
int r = DOMAP_FAIL;
struct config *conf;
- int verbosity;
/*
* last chance to quit before touching the devmaps
*/
if (mpp->action == ACT_DRY_RUN) {
- conf = get_multipath_config();
- verbosity = conf->verbosity;
- put_multipath_config(conf);
- print_multipath_topology(mpp, verbosity);
+ print_multipath_topology(mpp, libmp_verbosity);
return DOMAP_DRY;
}
* FORCE_RELOAD_WEAK: existing maps are compared to the current conf and only
* reloaded in DM if there's a difference. This is useful during startup.
*/
-int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid,
+int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
int force_reload, enum mpath_cmds cmd)
{
int ret = CP_FAIL;
struct path * pp2;
vector curmp = vecs->mpvec;
vector pathvec = vecs->pathvec;
+ vector newmp;
struct config *conf;
int allow_queueing;
struct bitfield *size_mismatch_seen;
if (size_mismatch_seen == NULL)
return CP_FAIL;
+ if (mpvec)
+ newmp = mpvec;
+ else
+ newmp = vector_alloc();
+ if (!newmp) {
+ condlog(0, "can not allocate newmp");
+ goto out;
+ }
+
vector_foreach_slot (pathvec, pp1, k) {
int invalid;
+
+ if (should_exit()) {
+ ret = CP_FAIL;
+ goto out;
+ }
+
/* skip this path for some reason */
/* 1. if path has no unique id or wwid blacklisted */
goto out;
}
}
- if (r == DOMAP_DRY)
+ if (r == DOMAP_DRY) {
+ if (!vector_alloc_slot(newmp)) {
+ remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC);
+ goto out;
+ }
+ vector_set_slot(newmp, mpp);
continue;
-
- if (r == DOMAP_EXIST && mpp->action == ACT_NOTHING &&
- force_reload == FORCE_RELOAD_WEAK)
- /*
- * First time we're called, and no changes applied.
- * domap() was a noop. But we can't be sure that
- * udev has already finished setting up this device
- * (udev in initrd may have been shut down while
- * processing this device or its children).
- * Trigger a change event, just in case.
- */
- trigger_udev_change(find_mp_by_wwid(curmp, mpp->wwid));
+ }
conf = get_multipath_config();
allow_queueing = conf->allow_queueing;
"queue_if_no_path");
}
- if (!is_daemon && mpp->action != ACT_NOTHING) {
- int verbosity;
-
- conf = get_multipath_config();
- verbosity = conf->verbosity;
- put_multipath_config(conf);
- print_multipath_topology(mpp, verbosity);
- }
+ if (!is_daemon && mpp->action != ACT_NOTHING)
+ print_multipath_topology(mpp, libmp_verbosity);
- if (newmp) {
- if (mpp->action != ACT_REJECT) {
- if (!vector_alloc_slot(newmp))
- goto out;
- vector_set_slot(newmp, mpp);
+ if (mpp->action != ACT_REJECT) {
+ if (!vector_alloc_slot(newmp)) {
+ remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC);
+ goto out;
}
- else
- remove_map(mpp, vecs->pathvec, vecs->mpvec,
- KEEP_VEC);
+ vector_set_slot(newmp, mpp);
}
+ else
+ remove_map(mpp, vecs->pathvec, vecs->mpvec,
+ KEEP_VEC);
}
/*
* Flush maps with only dead paths (ie not in sysfs)
* Keep maps with only failed paths
*/
- if (newmp) {
+ if (mpvec) {
vector_foreach_slot (newmp, mpp, i) {
char alias[WWID_SIZE];
ret = CP_OK;
out:
free(size_mismatch_seen);
+ if (!mpvec)
+ free_multipathvec(newmp, KEEP_PATHS);
return ret;
}
return ret;
}
}
- if (pp->udev && pp->uid_attribute &&
+ if (flags & DI_BLACKLIST &&
filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0)
return PATHINFO_SKIPPED;
refwwid = pp->wwid;
refwwid = dev;
}
- if (refwwid && strlen(refwwid) &&
+ if (flags & DI_BLACKLIST && refwwid && strlen(refwwid) &&
filter_wwid(conf->blist_wwid, conf->elist_wwid, refwwid,
NULL) > 0)
return PATHINFO_SKIPPED;
vector pathvec, char **wwid);
struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type);
void trigger_paths_udev_change(struct multipath *mpp, bool is_mpath);
+void trigger_partitions_udev_change(struct udev_device *dev, const char *action,
+ int len);
#include "config.h"
#include "defaults.h"
#include "debug.h"
+#include "time-util.h"
+#include "util.h"
-void dlog (int sink, int prio, const char * fmt, ...)
+int logsink;
+int libmp_verbosity = DEFAULT_VERBOSITY;
+
+void dlog(int prio, const char * fmt, ...)
{
va_list ap;
- int thres;
- struct config *conf;
va_start(ap, fmt);
- conf = get_multipath_config();
- ANNOTATE_IGNORE_READS_BEGIN();
- thres = (conf) ? conf->verbosity : DEFAULT_VERBOSITY;
- ANNOTATE_IGNORE_READS_END();
- put_multipath_config(conf);
-
- if (prio <= thres) {
- if (sink < 1) {
- if (sink == 0) {
- time_t t = time(NULL);
- struct tm *tb = localtime(&t);
- char buff[16];
-
- strftime(buff, sizeof(buff),
- "%b %d %H:%M:%S", tb);
- buff[sizeof(buff)-1] = '\0';
+ if (logsink != LOGSINK_SYSLOG) {
+ if (logsink == LOGSINK_STDERR_WITH_TIME) {
+ struct timespec ts;
+ char buff[32];
- fprintf(stderr, "%s | ", buff);
- }
- vfprintf(stderr, fmt, ap);
+ get_monotonic_time(&ts);
+ safe_sprintf(buff, "%ld.%06ld",
+ (long)ts.tv_sec,
+ ts.tv_nsec/1000);
+ fprintf(stderr, "%s | ", buff);
}
- else
- log_safe(prio + 3, fmt, ap);
+ vfprintf(stderr, fmt, ap);
}
+ else
+ log_safe(prio + 3, fmt, ap);
va_end(ap);
}
-void dlog (int sink, int prio, const char * fmt, ...)
- __attribute__((format(printf, 3, 4)));
+#ifndef _DEBUG_H
+#define _DEBUG_H
+void dlog (int prio, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
#include <pthread.h>
#include "log_pthread.h"
extern int logsink;
+extern int libmp_verbosity;
-#define condlog(prio, fmt, args...) \
- dlog(logsink, prio, fmt "\n", ##args)
+#ifndef MAX_VERBOSITY
+#define MAX_VERBOSITY 4
+#endif
+
+enum {
+ LOGSINK_STDERR_WITH_TIME = 0,
+ LOGSINK_STDERR_WITHOUT_TIME = -1,
+ LOGSINK_SYSLOG = 1,
+};
+
+#define condlog(prio, fmt, args...) \
+ do { \
+ int __p = (prio); \
+ \
+ if (__p <= MAX_VERBOSITY && __p <= libmp_verbosity) \
+ dlog(__p, fmt "\n", ##args); \
+ } while (0)
+#endif /* _DEBUG_H */
*/
#define DEFAULT_UID_ATTRIBUTE "ID_SERIAL"
#define DEFAULT_NVME_UID_ATTRIBUTE "ID_WWN"
+#define DEFAULT_DASD_UID_ATTRIBUTE "ID_UID"
#define DEFAULT_UDEVDIR "/dev"
#define DEFAULT_MULTIPATHDIR "/" LIB_STRING "/multipath"
#define DEFAULT_SELECTOR "service-time 0"
#define DEFAULT_FIND_MULTIPATHS_TIMEOUT -10
#define DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT 1
#define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF
+#define DEFAULT_RECHECK_WWID RECHECK_WWID_OFF
/* Enable no foreign libraries by default */
#define DEFAULT_ENABLE_FOREIGN "NONE"
#include "sysfs.h"
#include "config.h"
#include "wwids.h"
+#include "version.h"
+#include "time-util.h"
#include "log_pthread.h"
#include <sys/types.h>
#define MAX_WAIT 5
#define LOOPS_PER_SEC 5
+#define INVALID_VERSION ~0U
+static unsigned int dm_library_version[3] = { INVALID_VERSION, };
+static unsigned int dm_kernel_version[3] = { INVALID_VERSION, };
+static unsigned int dm_mpath_target_version[3] = { INVALID_VERSION, };
+
static pthread_once_t dm_initialized = PTHREAD_ONCE_INIT;
+static pthread_once_t versions_initialized = PTHREAD_ONCE_INIT;
+static pthread_mutex_t libmp_dm_lock = PTHREAD_MUTEX_INITIALIZER;
static int dm_conf_verbosity;
return 1;
}
-void dm_udev_wait(unsigned int c)
+static void libmp_udev_wait(unsigned int c)
{
}
-void dm_udev_set_sync_support(int c)
+static void dm_udev_set_sync_support(int c)
{
}
-
+#else
+static void libmp_udev_wait(unsigned int c)
+{
+ pthread_mutex_lock(&libmp_dm_lock);
+ pthread_cleanup_push(cleanup_mutex, &libmp_dm_lock);
+ dm_udev_wait(c);
+ pthread_cleanup_pop(1);
+}
#endif
+int libmp_dm_task_run(struct dm_task *dmt)
+{
+ int r;
+
+ pthread_mutex_lock(&libmp_dm_lock);
+ pthread_cleanup_push(cleanup_mutex, &libmp_dm_lock);
+ r = dm_task_run(dmt);
+ pthread_cleanup_pop(1);
+ return r;
+}
+
__attribute__((format(printf, 4, 5))) static void
dm_write_log (int level, const char *file, int line, const char *f, ...)
{
return;
va_start(ap, f);
- if (logsink < 1) {
- if (logsink == 0) {
- time_t t = time(NULL);
- struct tm *tb = localtime(&t);
- char buff[16];
-
- strftime(buff, sizeof(buff), "%b %d %H:%M:%S", tb);
- buff[sizeof(buff)-1] = '\0';
-
+ if (logsink != LOGSINK_SYSLOG) {
+ if (logsink == LOGSINK_STDERR_WITH_TIME) {
+ struct timespec ts;
+ char buff[32];
+
+ get_monotonic_time(&ts);
+ safe_sprintf(buff, "%ld.%06ld",
+ (long)ts.tv_sec, ts.tv_nsec/1000);
fprintf(stderr, "%s | ", buff);
}
fprintf(stderr, "libdevmapper: %s(%i): ", file, line);
return;
}
-void dm_init(int v)
+static void dm_init(int v)
{
/*
* This maps libdm's standard loglevel _LOG_WARN (= 4), which is rather
dm_log_init(&dm_write_log);
}
+static void init_dm_library_version(void)
+{
+ char version[64];
+ unsigned int v[3];
+
+ dm_get_library_version(version, sizeof(version));
+ if (sscanf(version, "%u.%u.%u ", &v[0], &v[1], &v[2]) != 3) {
+ condlog(0, "invalid libdevmapper version %s", version);
+ return;
+ }
+ memcpy(dm_library_version, v, sizeof(dm_library_version));
+ condlog(3, "libdevmapper version %u.%.2u.%.2u",
+ dm_library_version[0], dm_library_version[1],
+ dm_library_version[2]);
+}
+
static int
dm_lib_prereq (void)
{
- char version[64];
- int v[3];
+
#if defined(LIBDM_API_HOLD_CONTROL)
- int minv[3] = {1, 2, 111};
+ unsigned int minv[3] = {1, 2, 111};
#elif defined(LIBDM_API_GET_ERRNO)
- int minv[3] = {1, 2, 99};
+ unsigned int minv[3] = {1, 2, 99};
#elif defined(LIBDM_API_DEFERRED)
- int minv[3] = {1, 2, 89};
+ unsigned int minv[3] = {1, 2, 89};
#elif defined(DM_SUBSYSTEM_UDEV_FLAG0)
- int minv[3] = {1, 2, 82};
+ unsigned int minv[3] = {1, 2, 82};
#elif defined(LIBDM_API_COOKIE)
- int minv[3] = {1, 2, 38};
+ unsigned int minv[3] = {1, 2, 38};
#else
- int minv[3] = {1, 2, 8};
+ unsigned int minv[3] = {1, 2, 8};
#endif
- dm_get_library_version(version, sizeof(version));
- condlog(3, "libdevmapper version %s", version);
- if (sscanf(version, "%d.%d.%d ", &v[0], &v[1], &v[2]) != 3) {
- condlog(0, "invalid libdevmapper version %s", version);
- return 1;
- }
-
- if VERSION_GE(v, minv)
+ if (VERSION_GE(dm_library_version, minv))
return 0;
- condlog(0, "libdevmapper version must be >= %d.%.2d.%.2d",
+ condlog(0, "libdevmapper version must be >= %u.%.2u.%.2u",
minv[0], minv[1], minv[2]);
return 1;
}
-int
-dm_drv_version(unsigned int *v)
+static void init_dm_drv_version(void)
{
char buff[64];
-
- v[0] = 0;
- v[1] = 0;
- v[2] = 0;
+ unsigned int v[3];
if (!dm_driver_version(buff, sizeof(buff))) {
condlog(0, "cannot get kernel dm version");
- return 1;
+ return;
}
if (sscanf(buff, "%u.%u.%u ", &v[0], &v[1], &v[2]) != 3) {
condlog(0, "invalid kernel dm version '%s'", buff);
- return 1;
+ return;
}
- return 0;
+ memcpy(dm_kernel_version, v, sizeof(dm_library_version));
+ condlog(3, "kernel device mapper v%u.%u.%u",
+ dm_kernel_version[0],
+ dm_kernel_version[1],
+ dm_kernel_version[2]);
}
-int
-dm_tgt_version (unsigned int * version, char * str)
+static int dm_tgt_version (unsigned int *version, char *str)
{
int r = 2;
struct dm_task *dmt;
struct dm_versions *last_target;
unsigned int *v;
- version[0] = 0;
- version[1] = 0;
- version[2] = 0;
-
+ /*
+ * We have to call dm_task_create() and not libmp_dm_task_create()
+ * here to avoid a recursive invocation of
+ * pthread_once(&dm_initialized), which would cause a deadlock.
+ */
if (!(dmt = dm_task_create(DM_DEVICE_LIST_VERSIONS)))
return 1;
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(2, DM_DEVICE_LIST_VERSIONS, dmt);
condlog(0, "Can not communicate with kernel DM");
goto out;
return r;
}
-static int
-dm_tgt_prereq (unsigned int *ver)
+static void init_dm_mpath_version(void)
{
- unsigned int minv[3] = {1, 0, 3};
- unsigned int version[3] = {0, 0, 0};
- unsigned int * v = version;
-
- if (dm_tgt_version(v, TGT_MPATH)) {
- /* in doubt return not capable */
- return 1;
- }
+ if (!dm_tgt_version(dm_mpath_target_version, TGT_MPATH))
+ condlog(3, "DM multipath kernel driver v%u.%u.%u",
+ dm_mpath_target_version[0],
+ dm_mpath_target_version[1],
+ dm_mpath_target_version[2]);
+}
- /* test request based multipath capability */
- condlog(3, "DM multipath kernel driver v%u.%u.%u",
- v[0], v[1], v[2]);
+static int dm_tgt_prereq (unsigned int *ver)
+{
+ unsigned int minv[3] = {1, 0, 3};
- if (VERSION_GE(v, minv)) {
- ver[0] = v[0];
- ver[1] = v[1];
- ver[2] = v[2];
+ if (VERSION_GE(dm_mpath_target_version, minv)) {
+ if (ver) {
+ ver[0] = dm_mpath_target_version[0];
+ ver[1] = dm_mpath_target_version[1];
+ ver[2] = dm_mpath_target_version[2];
+ }
return 0;
}
return 1;
}
+static void _init_versions(void)
+{
+ /* Can't use condlog here because of how VERSION_STRING is defined */
+ if (3 <= libmp_verbosity)
+ dlog(3, VERSION_STRING);
+ init_dm_library_version();
+ init_dm_drv_version();
+ init_dm_mpath_version();
+}
+
+static int init_versions(void) {
+ pthread_once(&versions_initialized, _init_versions);
+ return (dm_library_version[0] == INVALID_VERSION ||
+ dm_kernel_version[0] == INVALID_VERSION ||
+ dm_mpath_target_version[0] == INVALID_VERSION);
+}
+
int dm_prereq(unsigned int *v)
{
+ if (init_versions())
+ return 1;
if (dm_lib_prereq())
return 1;
return dm_tgt_prereq(v);
}
+int libmp_get_version(int which, unsigned int version[3])
+{
+ unsigned int *src_version;
+
+ init_versions();
+ switch (which) {
+ case DM_LIBRARY_VERSION:
+ src_version = dm_library_version;
+ break;
+ case DM_KERNEL_VERSION:
+ src_version = dm_kernel_version;
+ break;
+ case DM_MPATH_TARGET_VERSION:
+ src_version = dm_mpath_target_version;
+ break;
+ case MULTIPATH_VERSION:
+ version[0] = (VERSION_CODE >> 16) & 0xff;
+ version[1] = (VERSION_CODE >> 8) & 0xff;
+ version[2] = VERSION_CODE & 0xff;
+ return 0;
+ default:
+ condlog(0, "%s: invalid value for 'which'", __func__);
+ return 1;
+ }
+ if (src_version[0] == INVALID_VERSION)
+ return 1;
+ memcpy(version, src_version, 3 * sizeof(*version));
+ return 0;
+}
+
static int libmp_dm_udev_sync = 0;
void libmp_udev_set_sync_support(int on)
libmp_dm_udev_sync = !!on;
}
+static bool libmp_dm_init_called;
+void libmp_dm_exit(void)
+{
+ if (!libmp_dm_init_called)
+ return;
+
+ /* switch back to default libdm logging */
+ dm_log_init(NULL);
+#ifdef LIBDM_API_HOLD_CONTROL
+ /* make sure control fd is closed in dm_lib_release() */
+ dm_hold_control_dev(0);
+#endif
+}
+
static void libmp_dm_init(void)
{
- struct config *conf;
- int verbosity;
unsigned int version[3];
if (dm_prereq(version))
exit(1);
- conf = get_multipath_config();
- verbosity = conf->verbosity;
- memcpy(conf->version, version, sizeof(version));
- put_multipath_config(conf);
- dm_init(verbosity);
+ dm_init(libmp_verbosity);
#ifdef LIBDM_API_HOLD_CONTROL
dm_hold_control_dev(1);
#endif
dm_udev_set_sync_support(libmp_dm_udev_sync);
+ libmp_dm_init_called = true;
}
static void _do_skip_libmp_dm_init(void)
DM_UDEV_DISABLE_LIBRARY_FALLBACK | udev_flags))
goto out;
- r = dm_task_run (dmt);
+ r = libmp_dm_task_run (dmt);
if (!r)
dm_log_error(2, task, dmt);
if (udev_wait_flag)
- dm_udev_wait(cookie);
+ libmp_udev_wait(cookie);
out:
dm_task_destroy (dmt);
return r;
!dm_task_set_cookie(dmt, &cookie, udev_flags))
goto freeout;
- r = dm_task_run (dmt);
+ r = libmp_dm_task_run (dmt);
if (!r)
dm_log_error(2, task, dmt);
if (task == DM_DEVICE_CREATE)
- dm_udev_wait(cookie);
+ libmp_udev_wait(cookie);
freeout:
if (prefixed_uuid)
FREE(prefixed_uuid);
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_INFO, dmt);
goto out;
}
dm_task_no_open_count(dmt);
errno = 0;
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_TABLE, dmt);
if (dm_task_get_errno(dmt) == ENXIO)
r = DMP_NOT_FOUND;
if (!dm_task_set_name (dmt, name))
goto uuidout;
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_INFO, dmt);
goto uuidout;
}
dm_task_no_open_count(dmt);
errno = 0;
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_STATUS, dmt);
if (dm_task_get_errno(dmt) == ENXIO)
r = DMP_NOT_FOUND;
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_TABLE, dmt);
goto out;
}
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_TABLE, dmt);
goto out_task;
}
if (safe_sprintf(prefixed_uuid, UUID_PREFIX "%s", uuid))
goto out;
- if (!(dmt = dm_task_create(DM_DEVICE_INFO)))
+ if (!(dmt = libmp_dm_task_create(DM_DEVICE_INFO)))
goto out;
dm_task_no_open_count(dmt);
if (!dm_task_set_uuid(dmt, prefixed_uuid))
goto out_task;
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_INFO, dmt);
goto out_task;
}
if (!dm_task_set_name(dmt, mapname))
goto out;
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_INFO, dmt);
goto out;
}
dm_task_no_open_count(dmt);
- if (!dm_task_run (dmt)) {
+ if (!libmp_dm_task_run (dmt)) {
dm_log_error(3, DM_DEVICE_LIST, dmt);
goto out;
}
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(2, DM_DEVICE_TARGET_MSG, dmt);
goto out;
}
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_LIST, dmt);
goto out;
}
* daemon uev_trigger -> uev_add_map
*/
while (--loop) {
- r = dm_task_run(dmt);
+ r = libmp_dm_task_run(dmt);
if (r)
break;
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_LIST, dmt);
goto out;
}
if (!dm_task_set_cookie(dmt, &cookie, udev_flags))
goto out;
- r = dm_task_run(dmt);
+ r = libmp_dm_task_run(dmt);
if (!r)
dm_log_error(2, DM_DEVICE_RENAME, dmt);
- dm_udev_wait(cookie);
+ libmp_udev_wait(cookie);
out:
dm_task_destroy(dmt);
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_TABLE, dmt);
goto out;
}
if (modified) {
dm_task_no_open_count(reload_dmt);
- if (!dm_task_run(reload_dmt)) {
+ if (!libmp_dm_task_run(reload_dmt)) {
dm_log_error(3, DM_DEVICE_RELOAD, reload_dmt);
condlog(3, "%s: failed to reassign targets", name);
goto out_reload;
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_DEPS, dmt);
goto out;
}
goto out;
}
- r = dm_task_run(dmt);
+ r = libmp_dm_task_run(dmt);
if (!r)
dm_log_error(3, DM_DEVICE_SET_GEOMETRY, dmt);
out:
DMP_NOT_FOUND,
};
-void dm_init(int verbosity);
int dm_prereq(unsigned int *v);
void skip_libmp_dm_init(void);
+void libmp_dm_exit(void);
void libmp_udev_set_sync_support(int on);
struct dm_task *libmp_dm_task_create(int task);
-int dm_drv_version (unsigned int * version);
-int dm_tgt_version (unsigned int * version, char * str);
int dm_simplecmd_flush (int, const char *, uint16_t);
int dm_simplecmd_noflush (int, const char *, uint16_t);
int dm_addmap_create (struct multipath *mpp, char *params);
#include <errno.h>
#define dm_task_get_errno(x) errno
#endif
+enum {
+ DM_LIBRARY_VERSION,
+ DM_KERNEL_VERSION,
+ DM_MPATH_TARGET_VERSION,
+ MULTIPATH_VERSION
+};
+int libmp_get_version(int which, unsigned int version[3]);
+struct dm_task;
+int libmp_dm_task_run(struct dm_task *dmt);
#define dm_log_error(lvl, cmd, dmt) \
condlog(lvl, "%s: libdm task=%d error: %s", __func__, \
declare_mp_attr_snprint(gid, print_gid)
static int
-set_fast_io_fail(vector strvec, void *ptr)
+set_undef_off_zero(vector strvec, void *ptr)
{
char * buff;
int *int_ptr = (int *)ptr;
return 1;
if (strcmp(buff, "off") == 0)
- *int_ptr = MP_FAST_IO_FAIL_OFF;
+ *int_ptr = UOZ_OFF;
else if (sscanf(buff, "%d", int_ptr) != 1 ||
- *int_ptr < MP_FAST_IO_FAIL_ZERO)
- *int_ptr = MP_FAST_IO_FAIL_UNSET;
+ *int_ptr < UOZ_ZERO)
+ *int_ptr = UOZ_UNDEF;
else if (*int_ptr == 0)
- *int_ptr = MP_FAST_IO_FAIL_ZERO;
+ *int_ptr = UOZ_ZERO;
FREE(buff);
return 0;
}
int
-print_fast_io_fail(char * buff, int len, long v)
+print_undef_off_zero(char * buff, int len, long v)
{
- if (v == MP_FAST_IO_FAIL_UNSET)
+ if (v == UOZ_UNDEF)
return 0;
- if (v == MP_FAST_IO_FAIL_OFF)
+ if (v == UOZ_OFF)
return snprintf(buff, len, "\"off\"");
- if (v == MP_FAST_IO_FAIL_ZERO)
+ if (v == UOZ_ZERO)
return snprintf(buff, len, "0");
return snprintf(buff, len, "%ld", v);
}
-declare_def_handler(fast_io_fail, set_fast_io_fail)
-declare_def_snprint_defint(fast_io_fail, print_fast_io_fail,
+declare_def_handler(fast_io_fail, set_undef_off_zero)
+declare_def_snprint_defint(fast_io_fail, print_undef_off_zero,
DEFAULT_FAST_IO_FAIL)
-declare_ovr_handler(fast_io_fail, set_fast_io_fail)
-declare_ovr_snprint(fast_io_fail, print_fast_io_fail)
-declare_hw_handler(fast_io_fail, set_fast_io_fail)
-declare_hw_snprint(fast_io_fail, print_fast_io_fail)
+declare_ovr_handler(fast_io_fail, set_undef_off_zero)
+declare_ovr_snprint(fast_io_fail, print_undef_off_zero)
+declare_hw_handler(fast_io_fail, set_undef_off_zero)
+declare_hw_snprint(fast_io_fail, print_undef_off_zero)
static int
set_dev_loss(vector strvec, void *ptr)
declare_hw_handler(dev_loss, set_dev_loss)
declare_hw_snprint(dev_loss, print_dev_loss)
+declare_def_handler(eh_deadline, set_undef_off_zero)
+declare_def_snprint(eh_deadline, print_undef_off_zero)
+declare_ovr_handler(eh_deadline, set_undef_off_zero)
+declare_ovr_snprint(eh_deadline, print_undef_off_zero)
+declare_hw_handler(eh_deadline, set_undef_off_zero)
+declare_hw_snprint(eh_deadline, print_undef_off_zero)
+
static int
set_pgpolicy(vector strvec, void *ptr)
{
declare_def_handler(marginal_pathgroups, set_yes_no)
declare_def_snprint(marginal_pathgroups, print_yes_no)
+declare_def_handler(recheck_wwid, set_yes_no_undef)
+declare_def_snprint_defint(recheck_wwid, print_yes_no_undef, DEFAULT_RECHECK_WWID)
+declare_ovr_handler(recheck_wwid, set_yes_no_undef)
+declare_ovr_snprint(recheck_wwid, print_yes_no_undef)
+declare_hw_handler(recheck_wwid, set_yes_no_undef)
+declare_hw_snprint(recheck_wwid, print_yes_no_undef)
+
+
static int
def_uxsock_timeout_handler(struct config *conf, vector strvec)
{
install_keyword("gid", &def_gid_handler, &snprint_def_gid);
install_keyword("fast_io_fail_tmo", &def_fast_io_fail_handler, &snprint_def_fast_io_fail);
install_keyword("dev_loss_tmo", &def_dev_loss_handler, &snprint_def_dev_loss);
+ install_keyword("eh_deadline", &def_eh_deadline_handler, &snprint_def_eh_deadline);
install_keyword("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("enable_foreign", &def_enable_foreign_handler,
&snprint_def_enable_foreign);
install_keyword("marginal_pathgroups", &def_marginal_pathgroups_handler, &snprint_def_marginal_pathgroups);
+ install_keyword("recheck_wwid", &def_recheck_wwid_handler, &snprint_def_recheck_wwid);
__deprecated install_keyword("default_selector", &def_selector_handler, NULL);
__deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL);
__deprecated install_keyword("default_uid_attribute", &def_uid_attribute_handler, NULL);
install_keyword("flush_on_last_del", &hw_flush_on_last_del_handler, &snprint_hw_flush_on_last_del);
install_keyword("fast_io_fail_tmo", &hw_fast_io_fail_handler, &snprint_hw_fast_io_fail);
install_keyword("dev_loss_tmo", &hw_dev_loss_handler, &snprint_hw_dev_loss);
+ install_keyword("eh_deadline", &hw_eh_deadline_handler, &snprint_hw_eh_deadline);
install_keyword("user_friendly_names", &hw_user_friendly_names_handler, &snprint_hw_user_friendly_names);
install_keyword("retain_attached_hw_handler", &hw_retain_hwhandler_handler, &snprint_hw_retain_hwhandler);
install_keyword("detect_prio", &hw_detect_prio_handler, &snprint_hw_detect_prio);
install_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay);
install_keyword("all_tg_pt", &hw_all_tg_pt_handler, &snprint_hw_all_tg_pt);
install_keyword("vpd_vendor", &hw_vpd_vendor_handler, &snprint_hw_vpd_vendor);
+ install_keyword("recheck_wwid", &hw_recheck_wwid_handler, &snprint_hw_recheck_wwid);
install_sublevel_end();
install_keyword_root("overrides", &overrides_handler);
install_keyword("flush_on_last_del", &ovr_flush_on_last_del_handler, &snprint_ovr_flush_on_last_del);
install_keyword("fast_io_fail_tmo", &ovr_fast_io_fail_handler, &snprint_ovr_fast_io_fail);
install_keyword("dev_loss_tmo", &ovr_dev_loss_handler, &snprint_ovr_dev_loss);
+ install_keyword("eh_deadline", &ovr_eh_deadline_handler, &snprint_ovr_eh_deadline);
install_keyword("user_friendly_names", &ovr_user_friendly_names_handler, &snprint_ovr_user_friendly_names);
install_keyword("retain_attached_hw_handler", &ovr_retain_hwhandler_handler, &snprint_ovr_retain_hwhandler);
install_keyword("detect_prio", &ovr_detect_prio_handler, &snprint_ovr_detect_prio);
install_keyword("max_sectors_kb", &ovr_max_sectors_kb_handler, &snprint_ovr_max_sectors_kb);
install_keyword("ghost_delay", &ovr_ghost_delay_handler, &snprint_ovr_ghost_delay);
install_keyword("all_tg_pt", &ovr_all_tg_pt_handler, &snprint_ovr_all_tg_pt);
+ install_keyword("recheck_wwid", &ovr_recheck_wwid_handler, &snprint_ovr_recheck_wwid);
install_keyword_root("multipaths", &multipaths_handler);
install_keyword_multi("multipath", &multipath_handler, NULL);
int print_pgfailback(char *buff, int len, long v);
int print_pgpolicy(char *buff, int len, long v);
int print_no_path_retry(char *buff, int len, long v);
-int print_fast_io_fail(char *buff, int len, long v);
+int print_undef_off_zero(char *buff, int len, long v);
int print_dev_loss(char *buff, int len, unsigned long v);
int print_reservation_key(char * buff, int len, struct be64 key, uint8_t
flags, int source);
const char *devtype;
const char *devpath;
+ if (should_exit())
+ break;
+
devpath = udev_list_entry_get_name(entry);
condlog(4, "Discover device %s", devpath);
udevice = udev_device_new_from_syspath(udev, devpath);
if (value) {
tgtdev = udev_device_get_parent(parent);
while (tgtdev) {
+ char c;
+
tgtname = udev_device_get_sysname(tgtdev);
- if (tgtname && sscanf(tgtname, "end_device-%d:%d",
- &host, &tgtid) == 2)
- break;
+ if (tgtname) {
+ if (sscanf(tgtname, "end_device-%d:%d:%d%c",
+ &host, &channel, &tgtid, &c) == 3)
+ break;
+ if (sscanf(tgtname, "end_device-%d:%d%c",
+ &host, &tgtid, &c) == 2)
+ break;
+ }
tgtdev = udev_device_get_parent(tgtdev);
tgtid = -1;
}
return !!preferred;
}
+static int
+sysfs_set_eh_deadline(struct multipath *mpp, struct path *pp)
+{
+ struct udev_device *hostdev;
+ char host_name[HOST_NAME_LEN], value[16];
+ int ret, len;
+
+ if (mpp->eh_deadline == EH_DEADLINE_UNSET)
+ return 0;
+
+ sprintf(host_name, "host%d", pp->sg_id.host_no);
+ hostdev = udev_device_new_from_subsystem_sysname(udev,
+ "scsi_host", host_name);
+ if (!hostdev)
+ return 1;
+
+ if (mpp->eh_deadline == EH_DEADLINE_OFF)
+ len = sprintf(value, "off");
+ else if (mpp->eh_deadline == EH_DEADLINE_ZERO)
+ len = sprintf(value, "0");
+ else
+ len = sprintf(value, "%d", mpp->eh_deadline);
+
+ ret = sysfs_attr_set_value(hostdev, "eh_deadline",
+ value, len + 1);
+ /*
+ * not all scsi drivers support setting eh_deadline, so failing
+ * is totally reasonable
+ */
+ if (ret <= 0)
+ condlog(3, "%s: failed to set eh_deadline to %s, error %d", udev_device_get_sysname(hostdev), value, -ret);
+
+ udev_device_unref(hostdev);
+ return (ret <= 0);
+}
+
static void
sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
{
unsigned int tmo;
int ret;
+ if (mpp->dev_loss == DEV_LOSS_TMO_UNSET &&
+ mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET)
+ return;
+
sprintf(rport_id, "rport-%d:%d-%d",
pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id);
rport_dev = udev_device_new_from_subsystem_sysname(udev,
char session_id[64];
char value[11];
+ if (mpp->dev_loss != DEV_LOSS_TMO_UNSET)
+ condlog(3, "%s: ignoring dev_loss_tmo on iSCSI", pp->dev);
+ if (mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET)
+ return;
+
sprintf(session_id, "session%d", pp->sg_id.transport_id);
session_dev = udev_device_new_from_subsystem_sysname(udev,
"iscsi_session", session_id);
condlog(4, "target%d:%d:%d -> %s", pp->sg_id.host_no,
pp->sg_id.channel, pp->sg_id.scsi_id, session_id);
- if (mpp->dev_loss != DEV_LOSS_TMO_UNSET) {
- condlog(3, "%s: ignoring dev_loss_tmo on iSCSI", pp->dev);
- }
if (mpp->fast_io_fail != MP_FAST_IO_FAIL_UNSET) {
if (mpp->fast_io_fail == MP_FAST_IO_FAIL_OFF) {
condlog(3, "%s: can't switch off fast_io_fail_tmo "
static void
sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp)
{
- struct udev_device *sas_dev = NULL;
- char end_dev_id[64];
+ struct udev_device *parent, *sas_dev = NULL;
+ const char *end_dev_id = NULL;
char value[11];
+ static const char ed_str[] = "end_device-";
- sprintf(end_dev_id, "end_device-%d:%d",
- pp->sg_id.host_no, pp->sg_id.transport_id);
+ if (!pp->udev || mpp->dev_loss == DEV_LOSS_TMO_UNSET)
+ return;
+
+ for (parent = udev_device_get_parent(pp->udev);
+ parent;
+ parent = udev_device_get_parent(parent)) {
+ const char *ed = udev_device_get_sysname(parent);
+
+ if (!strncmp(ed, ed_str, sizeof(ed_str) - 1)) {
+ end_dev_id = ed;
+ break;
+ }
+ }
+ if (!end_dev_id) {
+ condlog(1, "%s: No SAS end device", pp->dev);
+ return;
+ }
sas_dev = udev_device_new_from_subsystem_sysname(udev,
"sas_end_device", end_dev_id);
if (!sas_dev) {
mpp->fast_io_fail = MP_FAST_IO_FAIL_OFF;
}
if (mpp->dev_loss == DEV_LOSS_TMO_UNSET &&
- mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET)
+ mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET &&
+ mpp->eh_deadline == EH_DEADLINE_UNSET)
return 0;
vector_foreach_slot(mpp->paths, pp, i) {
switch (pp->sg_id.proto_id) {
case SCSI_PROTOCOL_FCP:
sysfs_set_rport_tmo(mpp, pp);
- continue;
+ break;
case SCSI_PROTOCOL_ISCSI:
sysfs_set_session_tmo(mpp, pp);
- continue;
+ break;
case SCSI_PROTOCOL_SAS:
sysfs_set_nexus_loss_tmo(mpp, pp);
- continue;
+ break;
default:
if (!err_path)
err_path = pp;
}
+ sysfs_set_eh_deadline(mpp, pp);
}
if (err_path) {
vpd = d;
}
break;
- case 0x8:
- /* SCSI Name: Prio 4 */
- if (memcmp(d + 4, "eui.", 4) &&
- memcmp(d + 4, "naa.", 4) &&
- memcmp(d + 4, "iqn.", 4))
- continue;
+ case 0x2:
+ /* EUI-64: Prio 4 */
if (prio < 4) {
prio = 4;
vpd = d;
}
break;
- case 0x2:
- /* EUI-64: Prio 3 */
+ case 0x8:
+ /* SCSI Name: Prio 3 */
+ if (memcmp(d + 4, "eui.", 4) &&
+ memcmp(d + 4, "naa.", 4) &&
+ memcmp(d + 4, "iqn.", 4))
+ break;
if (prio < 3) {
prio = 3;
vpd = d;
return len;
}
-int
-get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen)
+static int
+fetch_vpd_page(int fd, int pg, unsigned char *buff, int maxlen)
{
- int len, buff_len;
- unsigned char buff[4096];
+ int buff_len;
- memset(buff, 0x0, 4096);
- if (sgio_get_vpd(buff, 4096, fd, pg) < 0) {
+ memset(buff, 0x0, maxlen);
+ if (sgio_get_vpd(buff, maxlen, fd, pg) < 0) {
int lvl = pg == 0x80 || pg == 0x83 ? 3 : 4;
condlog(lvl, "failed to issue vpd inquiry for pg%02x",
return -ENODATA;
}
buff_len = get_unaligned_be16(&buff[2]) + 4;
- if (buff_len > 4096) {
+ if (buff_len > maxlen) {
condlog(3, "vpd pg%02x page truncated", pg);
- buff_len = 4096;
+ buff_len = maxlen;
}
+ return buff_len;
+}
+
+/* based on sg_inq.c from sg3_utils */
+bool
+is_vpd_page_supported(int fd, int pg)
+{
+ int i, len;
+ unsigned char buff[4096];
+
+ len = fetch_vpd_page(fd, 0x00, buff, sizeof(buff));
+ if (len < 0)
+ return false;
+
+ for (i = 4; i < len; ++i)
+ if (buff[i] == pg)
+ return true;
+ return false;
+}
+
+int
+get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen)
+{
+ int len, buff_len;
+ unsigned char buff[4096];
+
+ buff_len = fetch_vpd_page(fd, pg, buff, sizeof(buff));
+ if (buff_len < 0)
+ return buff_len;
if (pg == 0x80)
len = parse_vpd_pg80(buff, str, maxlen);
else if (pg == 0x83)
return PATHINFO_FAILED;
}
devt = udev_device_get_devnum(pp->udev);
+ if (major(devt) == 0 && minor(devt) == 0)
+ return PATHINFO_FAILED;
+
snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), minor(devt));
condlog(4, "%s: dev_t = %s", pp->dev, pp->dev_t);
return get_vpd_sysfs(parent, 0x83, pp->wwid, WWID_SIZE);
}
+/* based on code from s390-tools/dasdinfo/dasdinfo.c */
+static ssize_t dasd_get_uid(struct path *pp)
+{
+ struct udev_device *parent;
+ char value[80];
+ char *p;
+ int i;
+
+ parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "ccw",
+ NULL);
+ if (!parent)
+ return -1;
+
+ if (sysfs_attr_get_value(parent, "uid", value, 80) < 0)
+ return -1;
+
+ p = value - 1;
+ /* look for the 4th '.' and cut there */
+ for (i = 0; i < 4; i++) {
+ p = index(p + 1, '.');
+ if (!p)
+ break;
+ }
+ if (p)
+ *p = '\0';
+
+ return strlcpy(pp->wwid, value, WWID_SIZE);
+}
+
static ssize_t uid_fallback(struct path *pp, int path_state,
const char **origin)
{
ssize_t len = -1;
- if (pp->bus == SYSFS_BUS_SCSI) {
+ if (pp->bus == SYSFS_BUS_CCW) {
+ len = dasd_get_uid(pp);
+ *origin = "sysfs";
+ } else if (pp->bus == SYSFS_BUS_SCSI) {
len = get_vpd_uid(pp);
*origin = "sysfs";
if (len < 0 && path_state == PATH_UP) {
return len;
}
-static bool has_uid_fallback(struct path *pp)
+bool has_uid_fallback(struct path *pp)
{
/*
* Falling back to direct WWID determination is dangerous
!strcmp(pp->uid_attribute, ""))) ||
(pp->bus == SYSFS_BUS_NVME &&
(!strcmp(pp->uid_attribute, DEFAULT_NVME_UID_ATTRIBUTE) ||
+ !strcmp(pp->uid_attribute, ""))) ||
+ (pp->bus == SYSFS_BUS_CCW &&
+ (!strcmp(pp->uid_attribute, DEFAULT_DASD_UID_ATTRIBUTE) ||
!strcmp(pp->uid_attribute, ""))));
}
get_uid (struct path * pp, int path_state, struct udev_device *udev,
int allow_fallback)
{
- char *c;
const char *origin = "unknown";
ssize_t len = 0;
struct config *conf;
int used_fallback = 0;
+ size_t i;
if (!pp->uid_attribute && !pp->getuid) {
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
select_getuid(conf, pp);
+ select_recheck_wwid(conf, pp);
pthread_cleanup_pop(1);
}
} else
len = strlen(pp->wwid);
origin = "callout";
- } else {
- bool udev_available = udev && pp->uid_attribute
- && *pp->uid_attribute;
+ } else if (pp->uid_attribute) {
+ /* if the uid_attribute is an empty string skip udev checking */
+ bool check_uid_attr = udev && *pp->uid_attribute;
- if (udev_available) {
+ if (check_uid_attr) {
len = get_udev_uid(pp, pp->uid_attribute, udev);
origin = "udev";
if (len == 0)
condlog(1, "%s: empty udev uid", pp->dev);
}
- if ((!udev_available || (len <= 0 && allow_fallback))
+ if ((!check_uid_attr || (len <= 0 && allow_fallback))
&& has_uid_fallback(pp)) {
- used_fallback = 1;
+ /* if udev wasn't set or we failed in get_udev_uid()
+ * log at a higher priority */
+ if (!udev || check_uid_attr)
+ used_fallback = 1;
len = uid_fallback(pp, path_state, &origin);
}
}
return 1;
} else {
/* Strip any trailing blanks */
- c = strchr(pp->wwid, '\0');
- c--;
- while (c && c >= pp->wwid && *c == ' ') {
- *c = '\0';
- c--;
- }
+ for (i = strlen(pp->wwid); i > 0 && pp->wwid[i-1] == ' '; i--);
+ /* no-op */
+ pp->wwid[i] = '\0';
}
condlog((used_fallback)? 1 : 3, "%s: uid = %s (%s)", pp->dev,
*pp->wwid == '\0' ? "<empty>" : pp->wwid, origin);
condlog(4, "%s: hidden", pp->dev);
return PATHINFO_SKIPPED;
}
- if (is_claimed_by_foreign(pp->udev) ||
- filter_property(conf, pp->udev, 4, pp->uid_attribute) > 0)
+
+ if (is_claimed_by_foreign(pp->udev))
return PATHINFO_SKIPPED;
+
+ /*
+ * uid_attribute is required for filter_property below,
+ * and needs access to pp->hwe.
+ */
+ if (!(mask & DI_SYSFS) && (mask & DI_BLACKLIST) &&
+ !pp->uid_attribute && VECTOR_SIZE(pp->hwe) == 0)
+ mask |= DI_SYSFS;
}
if (strlen(pp->dev) != 0 && filter_devnode(conf->blist_devnode,
}
if (mask & DI_BLACKLIST && mask & DI_SYSFS) {
- if (filter_device(conf->blist_device, conf->elist_device,
+ /* uid_attribute is required for filter_property() */
+ if (pp->udev && !pp->uid_attribute) {
+ select_getuid(conf, pp);
+ select_recheck_wwid(conf, pp);
+ }
+
+ if (filter_property(conf, pp->udev, 4, pp->uid_attribute) > 0 ||
+ filter_device(conf->blist_device, conf->elist_device,
pp->vendor_id, pp->product_id, pp->dev) > 0 ||
filter_protocol(conf->blist_protocol, conf->elist_protocol,
pp) > 0)
if (pp->initialized != INIT_FAILED) {
pp->initialized = INIT_MISSING_UDEV;
pp->tick = conf->retrigger_delay;
+ } else if (pp->retriggers >= conf->retrigger_tries &&
+ (pp->state == PATH_UP || pp->state == PATH_GHOST)) {
+ /*
+ * We have failed to read udev info for this path
+ * repeatedly. We used the fallback in get_uid()
+ * if there was any, and still got no WWID,
+ * although the path is allegedly up.
+ * It's likely that this path is not fit for
+ * multipath use.
+ */
+ char buf[16];
+
+ snprint_path(buf, sizeof(buf), "%T", pp, 0);
+ condlog(1, "%s: no WWID in state \"%s\", giving up",
+ pp->dev, buf);
+ return PATHINFO_SKIPPED;
}
return PATHINFO_OK;
}
unsigned char *buff, size_t len);
int sysfs_get_asymmetric_access_state(struct path *pp,
char *buff, int buflen);
+bool has_uid_fallback(struct path *pp);
int get_uid(struct path * pp, int path_state, struct udev_device *udev,
int allow_fallback);
+bool is_vpd_page_supported(int fd, int pg);
/*
* discovery bitmask
include ../../Makefile.inc
CFLAGS += $(LIB_CFLAGS) -I.. -I$(nvmedir)
+LDFLAGS += -L..
+LIBDEPS = -lmultipath -ludev -lpthread -lrt
LIBS = libforeign-nvme.so
all: $(LIBS)
libforeign-%.so: %.o
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS)
install:
$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(libdir)
.product = "^EMC PowerMax_",
.pgpolicy = MULTIBUS,
},
+ {
+ /* PowerStore */
+ .vendor = "DellEMC",
+ .product = "PowerStore",
+ .pgpolicy = GROUP_BY_PRIO,
+ .prio_name = PRIO_ALUA,
+ .hwhandler = "1 alua",
+ .pgfailback = -FAILBACK_IMMEDIATE,
+ .no_path_retry = 3,
+ .fast_io_fail = 15,
+ },
/*
* Fujitsu
*/
*
* The hwtable is searched backwards, so place this after "Generic NVMe"
*/
- .vendor = "NVME",
+ .vendor = "NVME",
.product = "^NetApp ONTAP Controller",
.pgpolicy = MULTIBUS,
.no_path_retry = NO_PATH_RETRY_QUEUE,
.pgpolicy = MULTIBUS,
},
/*
- * Imation/Nexsan
+ * StorCentric
*/
+ /* Nexsan */
{
/* E-Series */
.vendor = "NEXSAN",
.prio_name = PRIO_ALUA,
.no_path_retry = 30,
},
- /*
- * Violin Systems
- */
+ /* Violin Systems */
{
/* 3000 / 6000 Series */
.vendor = "VIOLIN",
.pgpolicy = MULTIBUS,
.no_path_retry = 30,
},
+ /* Vexata */
+ {
+ /* VX */
+ .vendor = "Vexata",
+ .product = "VX",
+ .pgpolicy = MULTIBUS,
+ .no_path_retry = 30,
+ },
/*
* Promise Technology
*/
#include "lock.h"
#include "time-util.h"
#include "io_err_stat.h"
+#include "util.h"
#define TIMEOUT_NO_IO_NSEC 10000000 /*10ms = 10000000ns*/
#define FLAKY_PATHFAIL_THRESHOLD 2
#define io_err_stat_log(prio, fmt, args...) \
condlog(prio, "io error statistic: " fmt, ##args)
-
-struct io_err_stat_pathvec {
- pthread_mutex_t mutex;
- vector pathvec;
-};
-
struct dio_ctx {
struct timespec io_starttime;
unsigned int blksize;
int err_rate_threshold;
};
-pthread_t io_err_stat_thr;
-pthread_attr_t io_err_stat_attr;
+static pthread_t io_err_stat_thr;
static pthread_mutex_t io_err_thread_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t io_err_thread_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t io_err_pathvec_lock = PTHREAD_MUTEX_INITIALIZER;
static int io_err_thread_running = 0;
-static struct io_err_stat_pathvec *paths;
+static vector io_err_pathvec;
struct vectors *vecs;
io_context_t ioctx;
rcu_unregister_thread();
}
-struct io_err_stat_path *find_err_path_by_dev(vector pathvec, char *dev)
+static struct io_err_stat_path *find_err_path_by_dev(vector pathvec, char *dev)
{
int i;
struct io_err_stat_path *pp;
return 1;
}
-static void destroy_directio_ctx(struct io_err_stat_path *p)
+static void free_io_err_stat_path(struct io_err_stat_path *p)
{
int i;
- if (!p || !p->dio_ctx_array)
+ if (!p)
return;
+ if (!p->dio_ctx_array)
+ goto free_path;
+
cancel_inflight_io(p);
for (i = 0; i < CONCUR_NR_EVENT; i++)
if (p->fd > 0)
close(p->fd);
+free_path:
+ FREE(p);
}
static struct io_err_stat_path *alloc_io_err_stat_path(void)
return p;
}
-static void free_io_err_stat_path(struct io_err_stat_path *p)
-{
- FREE(p);
-}
-
-static struct io_err_stat_pathvec *alloc_pathvec(void)
-{
- struct io_err_stat_pathvec *p;
- int r;
-
- p = (struct io_err_stat_pathvec *)MALLOC(sizeof(*p));
- if (!p)
- return NULL;
- p->pathvec = vector_alloc();
- if (!p->pathvec)
- goto out_free_struct_pathvec;
- r = pthread_mutex_init(&p->mutex, NULL);
- if (r)
- goto out_free_member_pathvec;
-
- return p;
-
-out_free_member_pathvec:
- vector_free(p->pathvec);
-out_free_struct_pathvec:
- FREE(p);
- return NULL;
-}
-
-static void free_io_err_pathvec(struct io_err_stat_pathvec *p)
+static void free_io_err_pathvec(void)
{
struct io_err_stat_path *path;
int i;
- if (!p)
- return;
- pthread_mutex_destroy(&p->mutex);
- if (!p->pathvec) {
- vector_foreach_slot(p->pathvec, path, i) {
- destroy_directio_ctx(path);
- free_io_err_stat_path(path);
- }
- vector_free(p->pathvec);
- }
- FREE(p);
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ pthread_cleanup_push(cleanup_mutex, &io_err_pathvec_lock);
+ if (!io_err_pathvec)
+ goto out;
+ vector_foreach_slot(io_err_pathvec, path, i)
+ free_io_err_stat_path(path);
+ vector_free(io_err_pathvec);
+ io_err_pathvec = NULL;
+out:
+ pthread_cleanup_pop(1);
}
/*
{
struct io_err_stat_path *p;
- pthread_mutex_lock(&paths->mutex);
- p = find_err_path_by_dev(paths->pathvec, path->dev);
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ p = find_err_path_by_dev(io_err_pathvec, path->dev);
if (p) {
- pthread_mutex_unlock(&paths->mutex);
+ pthread_mutex_unlock(&io_err_pathvec_lock);
return 0;
}
- pthread_mutex_unlock(&paths->mutex);
+ pthread_mutex_unlock(&io_err_pathvec_lock);
p = alloc_io_err_stat_path();
if (!p)
if (setup_directio_ctx(p))
goto free_ioerr_path;
- pthread_mutex_lock(&paths->mutex);
- if (!vector_alloc_slot(paths->pathvec))
- goto unlock_destroy;
- vector_set_slot(paths->pathvec, p);
- pthread_mutex_unlock(&paths->mutex);
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ if (!vector_alloc_slot(io_err_pathvec))
+ goto unlock_pathvec;
+ vector_set_slot(io_err_pathvec, p);
+ pthread_mutex_unlock(&io_err_pathvec_lock);
- io_err_stat_log(2, "%s: enqueue path %s to check",
+ io_err_stat_log(3, "%s: enqueue path %s to check",
path->mpp->alias, path->dev);
return 0;
-unlock_destroy:
- pthread_mutex_unlock(&paths->mutex);
- destroy_directio_ctx(p);
+unlock_pathvec:
+ pthread_mutex_unlock(&io_err_pathvec_lock);
free_ioerr_path:
free_io_err_stat_path(p);
* the repeated count threshold and time frame, we assume a path
* which fails at least twice within 60 seconds is flaky.
*/
- if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0)
- return 1;
+ get_monotonic_time(&curr_time);
if (path->io_err_pathfail_cnt == 0) {
path->io_err_pathfail_cnt++;
path->io_err_pathfail_starttime = curr_time.tv_sec;
if (uatomic_read(&io_err_thread_running) == 0)
return 0;
if (count_active_paths(pp->mpp) <= 0) {
- io_err_stat_log(2, "%s: recover path early", pp->dev);
+ io_err_stat_log(2, "%s: no paths. recovering early", pp->dev);
goto recover;
}
if (pp->io_err_pathfail_cnt != PATH_IO_ERR_WAITING_TO_CHECK)
return 1;
- if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0 ||
- (curr_time.tv_sec - pp->io_err_dis_reinstate_time) >
- pp->mpp->marginal_path_err_recheck_gap_time) {
+ get_monotonic_time(&curr_time);
+ if ((curr_time.tv_sec - pp->io_err_dis_reinstate_time) >
+ pp->mpp->marginal_path_err_recheck_gap_time) {
io_err_stat_log(4, "%s: reschedule checking after %d seconds",
pp->dev,
pp->mpp->marginal_path_err_recheck_gap_time);
* Or else, return 1 to set path state to PATH_SHAKY
*/
if (r == 1) {
- io_err_stat_log(3, "%s: enqueue fails, to recover",
- pp->dev);
+ io_err_stat_log(2, "%s: enqueue failed. recovering early", pp->dev);
goto recover;
} else
pp->io_err_pathfail_cnt = PATH_IO_ERR_IN_CHECKING;
return 0;
}
-static int delete_io_err_stat_by_addr(struct io_err_stat_path *p)
-{
- int i;
-
- i = find_slot(paths->pathvec, p);
- if (i != -1)
- vector_del_slot(paths->pathvec, i);
-
- destroy_directio_ctx(p);
- free_io_err_stat_path(p);
-
- return 0;
-}
-
static void account_async_io_state(struct io_err_stat_path *pp, int rc)
{
switch (rc) {
}
}
-static int poll_io_err_stat(struct vectors *vecs, struct io_err_stat_path *pp)
+static int io_err_stat_time_up(struct io_err_stat_path *pp)
{
struct timespec currtime, difftime;
- struct path *path;
- double err_rate;
- if (clock_gettime(CLOCK_MONOTONIC, &currtime) != 0)
- return 1;
+ get_monotonic_time(&currtime);
timespecsub(&currtime, &pp->start_time, &difftime);
if (difftime.tv_sec < pp->total_time)
return 0;
+ return 1;
+}
+
+static void end_io_err_stat(struct io_err_stat_path *pp)
+{
+ struct timespec currtime;
+ struct path *path;
+ double err_rate;
+
+ get_monotonic_time(&currtime);
io_err_stat_log(4, "%s: check end", pp->devname);
pp->devname);
}
lock_cleanup_pop(vecs->lock);
-
- delete_io_err_stat_by_addr(pp);
-
- return 0;
}
static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev)
ct->io_starttime.tv_sec == 0) {
struct iocb *ios[1] = { &ct->io };
- if (clock_gettime(CLOCK_MONOTONIC, &ct->io_starttime) != 0) {
- ct->io_starttime.tv_sec = 0;
- ct->io_starttime.tv_nsec = 0;
- return rc;
- }
+ 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",
struct dio_ctx *ct;
struct timespec currtime, difftime;
- if (clock_gettime(CLOCK_MONOTONIC, &currtime) != 0)
- return;
+ get_monotonic_time(&currtime);
/*
* Give a free time for all IO to complete or timeout
*/
if (!send_each_async_io(ct, pp->fd, pp->devname))
pp->io_nr++;
}
- if (pp->start_time.tv_sec == 0 && pp->start_time.tv_nsec == 0 &&
- clock_gettime(CLOCK_MONOTONIC, &pp->start_time)) {
- pp->start_time.tv_sec = 0;
- pp->start_time.tv_nsec = 0;
- }
+ if (pp->start_time.tv_sec == 0 && pp->start_time.tv_nsec == 0)
+ get_monotonic_time(&pp->start_time);
}
static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t,
int rc = PATH_UNCHECKED;
int i, j;
- if (clock_gettime(CLOCK_MONOTONIC, &curr_time) != 0)
- return;
- vector_foreach_slot(paths->pathvec, pp, i) {
+ get_monotonic_time(&curr_time);
+ vector_foreach_slot(io_err_pathvec, pp, i) {
for (j = 0; j < CONCUR_NR_EVENT; j++) {
rc = try_to_cancel_timeout_io(pp->dio_ctx_array + j,
&curr_time, pp->devname);
int rc = PATH_UNCHECKED;
int i, j;
- vector_foreach_slot(paths->pathvec, pp, i) {
+ vector_foreach_slot(io_err_pathvec, pp, i) {
for (j = 0; j < CONCUR_NR_EVENT; j++) {
ct = pp->dio_ctx_array + j;
if (&ct->io == io_evt->obj) {
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)",
static void service_paths(void)
{
+ struct _vector _pathvec = { .allocated = 0 };
+ /* avoid gcc warnings that &_pathvec will never be NULL in vector ops */
+ struct _vector * const tmp_pathvec = &_pathvec;
struct io_err_stat_path *pp;
int i;
- pthread_mutex_lock(&paths->mutex);
- vector_foreach_slot(paths->pathvec, pp, i) {
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ pthread_cleanup_push(cleanup_mutex, &io_err_pathvec_lock);
+ vector_foreach_slot(io_err_pathvec, pp, i) {
send_batch_async_ios(pp);
process_async_ios_event(TIMEOUT_NO_IO_NSEC, pp->devname);
poll_async_io_timeout();
- poll_io_err_stat(vecs, pp);
+ if (io_err_stat_time_up(pp)) {
+ if (!vector_alloc_slot(tmp_pathvec))
+ continue;
+ vector_del_slot(io_err_pathvec, i--);
+ vector_set_slot(tmp_pathvec, pp);
+ }
}
- pthread_mutex_unlock(&paths->mutex);
-}
-
-static void cleanup_unlock(void *arg)
-{
- pthread_mutex_unlock((pthread_mutex_t*) arg);
+ pthread_cleanup_pop(1);
+ vector_foreach_slot_backwards(tmp_pathvec, pp, i) {
+ end_io_err_stat(pp);
+ vector_del_slot(tmp_pathvec, i);
+ free_io_err_stat_path(pp);
+ }
+ vector_reset(tmp_pathvec);
}
static void cleanup_exited(__attribute__((unused)) void *arg)
int start_io_err_stat_thread(void *data)
{
int ret;
+ pthread_attr_t io_err_stat_attr;
if (uatomic_read(&io_err_thread_running) == 1)
return 0;
io_err_stat_log(4, "io_setup failed");
return 1;
}
- paths = alloc_pathvec();
- if (!paths)
+
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ io_err_pathvec = vector_alloc();
+ if (!io_err_pathvec) {
+ pthread_mutex_unlock(&io_err_pathvec_lock);
goto destroy_ctx;
+ }
+ pthread_mutex_unlock(&io_err_pathvec_lock);
+ setup_thread_attr(&io_err_stat_attr, 32 * 1024, 0);
pthread_mutex_lock(&io_err_thread_lock);
- pthread_cleanup_push(cleanup_unlock, &io_err_thread_lock);
+ pthread_cleanup_push(cleanup_mutex, &io_err_thread_lock);
ret = pthread_create(&io_err_stat_thr, &io_err_stat_attr,
io_err_stat_loop, data);
&io_err_thread_lock) == 0);
pthread_cleanup_pop(1);
+ pthread_attr_destroy(&io_err_stat_attr);
if (ret) {
io_err_stat_log(0, "cannot create io_error statistic thread");
return 0;
out_free:
- free_io_err_pathvec(paths);
+ pthread_mutex_lock(&io_err_pathvec_lock);
+ vector_free(io_err_pathvec);
+ io_err_pathvec = NULL;
+ pthread_mutex_unlock(&io_err_pathvec_lock);
destroy_ctx:
io_destroy(ioctx);
io_err_stat_log(0, "failed to start io_error statistic thread");
pthread_cancel(io_err_stat_thr);
pthread_join(io_err_stat_thr, NULL);
- free_io_err_pathvec(paths);
+ free_io_err_pathvec();
io_destroy(ioctx);
}
--- /dev/null
+/*
+ * Copyright (c) 2020 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * libmultipath ABI
+ *
+ * libmultipath doesn't have a stable ABI in the usual sense. In particular,
+ * the library does not attempt to ship different versions of the same symbol
+ * for backward compatibility.
+ *
+ * The ABI versioning only serves to avoid linking with a non-matching ABI, to
+ * cut down the set of exported symbols, and to describe it.
+ * The version string is LIBMULTIPATH_$MAJOR.$MINOR.$REL.
+ *
+ * Policy:
+ *
+ * * Bump $MAJOR for incompatible changes, like:
+ * - symbols removed
+ * - parameter list or return values changed for existing functions
+ * - externally visible data structures changed in incompatible ways
+ * (like offsets of previously existing struct members)
+ * In this case, the new version doesn't inherit the previous versions,
+ * because the new library doesn't provide the full previous ABI any more.
+ * All predecessors are merged into the new version.
+ *
+ * * Bump $MINOR for compatible changes, like adding symbols.
+ * The new version inherits the previous ones.
+ *
+ * * Bump $REL to describe deviations from upstream, e.g. in
+ * multipath-tools packages shipped by distributions.
+ * The new version inherits the previous ones.
+ */
+
+LIBMULTIPATH_5.0.0 {
+global:
+ /* symbols referenced by multipath and multipathd */
+ add_foreign;
+ add_map_with_path;
+ adopt_paths;
+ alloc_multipath;
+ alloc_path;
+ alloc_path_with_pathinfo;
+ alloc_strvec;
+ change_foreign;
+ check_alias_settings;
+ checker_clear_message;
+ checker_disable;
+ checker_enable;
+ checker_is_sync;
+ checker_message;
+ checker_name;
+ checker_state_name;
+ check_foreign;
+ cleanup_lock;
+ close_fd;
+ coalesce_paths;
+ convert_dev;
+ count_active_paths;
+ delete_all_foreign;
+ delete_foreign;
+ disassemble_map;
+ disassemble_status;
+ dlog;
+ dm_cancel_deferred_remove;
+ dm_enablegroup;
+ dm_fail_path;
+ _dm_flush_map;
+ dm_flush_map_nopaths;
+ dm_flush_maps;
+ dm_geteventnr;
+ dm_get_info;
+ dm_get_major_minor;
+ dm_get_map;
+ dm_get_maps;
+ dm_get_multipath;
+ dm_get_status;
+ dm_get_uuid;
+ dm_is_mpath;
+ dm_mapname;
+ dm_map_present;
+ dm_queue_if_no_path;
+ dm_reassign;
+ dm_reinstate_path;
+ dm_simplecmd_noflush;
+ dm_switchgroup;
+ domap;
+ ensure_directories_exist;
+ extract_hwe_from_path;
+ filter_devnode;
+ filter_path;
+ filter_wwid;
+ find_mp_by_alias;
+ find_mp_by_minor;
+ find_mp_by_str;
+ find_mp_by_wwid;
+ find_mpe;
+ find_path_by_dev;
+ find_path_by_devt;
+ find_slot;
+ foreign_multipath_layout;
+ foreign_path_layout;
+ free_config;
+ free_multipath;
+ free_multipathvec;
+ free_path;
+ free_pathvec;
+ free_strvec;
+ get_monotonic_time;
+ get_multipath_layout;
+ get_path_layout;
+ get_pgpolicy_id;
+ get_refwwid;
+ get_state;
+ get_udev_device;
+ get_uid;
+ get_used_hwes;
+ group_by_prio;
+ init_checkers;
+ init_foreign;
+ init_prio;
+ io_err_stat_handle_pathfail;
+ is_path_valid;
+ is_quote;
+ libmp_dm_task_create;
+ libmp_get_version;
+ libmp_udev_set_sync_support;
+ load_config;
+ log_thread_reset;
+ log_thread_start;
+ log_thread_stop;
+ need_io_err_check;
+ normalize_timespec;
+ orphan_path;
+ orphan_paths;
+ parse_prkey_flags;
+ pathcount;
+ path_discovery;
+ path_get_tpgs;
+ pathinfo;
+ path_offline;
+ print_all_paths;
+ print_foreign_topology;
+ _print_multipath_topology;
+ pthread_cond_init_mono;
+ recv_packet;
+ recv_packet_from_client;
+ reinstate_paths;
+ remember_wwid;
+ remove_map;
+ remove_map_by_alias;
+ remove_maps;
+ remove_wwid;
+ replace_wwids;
+ reset_checker_classes;
+ select_all_tg_pt;
+ select_action;
+ select_find_multipaths_timeout;
+ select_no_path_retry;
+ select_path_group;
+ select_reservation_key;
+ send_packet;
+ set_max_fds;
+ __set_no_path_retry;
+ set_path_removed;
+ set_prkey;
+ setup_map;
+ setup_thread_attr;
+ should_multipath;
+ snprint_blacklist_report;
+ snprint_config;
+ snprint_devices;
+ snprint_foreign_multipaths;
+ snprint_foreign_paths;
+ snprint_foreign_topology;
+ _snprint_multipath;
+ snprint_multipath_header;
+ snprint_multipath_map_json;
+ _snprint_multipath_topology;
+ snprint_multipath_topology_json;
+ _snprint_path;
+ snprint_path_header;
+ snprint_status;
+ snprint_wildcards;
+ stop_io_err_stat_thread;
+ store_path;
+ store_pathinfo;
+ strchop;
+ strlcpy;
+ sync_map_state;
+ sysfs_attr_set_value;
+ sysfs_get_size;
+ sysfs_is_multipathed;
+ timespecsub;
+ trigger_paths_udev_change;
+ uevent_dispatch;
+ uevent_get_dm_str;
+ uevent_get_env_positive_int;
+ uevent_is_mpath;
+ uevent_listen;
+ update_mpp_paths;
+ update_multipath_strings;
+ update_multipath_table;
+ update_pathvec_from_dm;
+ update_queue_mode_add_path;
+ update_queue_mode_del_path;
+ ux_socket_listen;
+ valid_alias;
+ vector_alloc;
+ vector_alloc_slot;
+ vector_del_slot;
+ vector_free;
+ vector_reset;
+ vector_set_slot;
+ verify_paths;
+
+ /* checkers */
+ sg_read;
+
+ /* prioritizers */
+ get_asymmetric_access_state;
+ get_prio_timeout;
+ get_target_port_group;
+ get_target_port_group_support;
+ libmp_nvme_ana_log;
+ libmp_nvme_get_nsid;
+ libmp_nvme_identify_ns;
+ log_nvme_errcode;
+ nvme_id_ctrl_ana;
+ snprint_host_wwnn;
+ snprint_host_wwpn;
+ snprint_path_serial;
+ snprint_tgt_wwnn;
+ snprint_tgt_wwpn;
+ sysfs_get_asymmetric_access_state;
+
+ /* foreign */
+ free_scandir_result;
+ sysfs_attr_get_value;
+
+ /* added in 2.1.0 */
+ libmp_dm_task_run;
+ cleanup_mutex;
+
+ /* added in 2.2.0 */
+ libmp_get_multipath_config;
+ get_multipath_config;
+ libmp_put_multipath_config;
+ put_multipath_config;
+ init_config;
+ uninit_config;
+
+ /* added in 2.3.0 */
+ udev;
+ logsink;
+ libmultipath_init;
+ libmultipath_exit;
+
+ /* added in 4.1.0 */
+ libmp_verbosity;
+
+ /* added in 4.2.0 */
+ dm_prereq;
+ skip_libmp_dm_init;
+
+ /* added in 4.3.0 */
+ start_checker_thread;
+
+ /* added in 4.4.0 */
+ get_next_string;
+
+ /* added in 4.5.0 */
+ get_vpd_sgio;
+ trigger_partitions_udev_change;
+local:
+ *;
+};
#include <string.h>
#include <syslog.h>
#include <time.h>
+#include <pthread.h>
#include "memory.h"
#include "log.h"
+#include "util.h"
#define ALIGN(len, s) (((len)+(s)-1)/(s)*(s))
struct logarea* la;
+static pthread_mutex_t logq_lock = PTHREAD_MUTEX_INITIALIZER;
#if LOGDBG
static void dump_logarea (void)
int log_init(char *program_name, int size)
{
+ int ret = 1;
+
logdbg(stderr,"enter log_init\n");
+
+ pthread_mutex_lock(&logq_lock);
+ pthread_cleanup_push(cleanup_mutex, &logq_lock);
+
openlog(program_name, 0, LOG_DAEMON);
+ if (!la)
+ ret = logarea_init(size);
- if (logarea_init(size))
- return 1;
+ pthread_cleanup_pop(1);
- return 0;
+ return ret;
}
-void free_logarea (void)
+static void free_logarea (void)
{
FREE(la->start);
FREE(la->buff);
void log_close (void)
{
- free_logarea();
+ pthread_mutex_lock(&logq_lock);
+ pthread_cleanup_push(cleanup_mutex, &logq_lock);
+
+ if (la)
+ free_logarea();
closelog();
+ pthread_cleanup_pop(1);
return;
}
void log_reset (char *program_name)
{
+ pthread_mutex_lock(&logq_lock);
+ pthread_cleanup_push(cleanup_mutex, &logq_lock);
+
closelog();
- tzset();
openlog(program_name, 0, LOG_DAEMON);
+
+ pthread_cleanup_pop(1);
}
-int log_enqueue (int prio, const char * fmt, va_list ap)
+__attribute__((format(printf, 2, 0)))
+static int _log_enqueue(int prio, const char * fmt, va_list ap)
{
int len, fwd;
char buff[MAX_MSG_SIZE];
return 0;
}
-int log_dequeue (void * buff)
+int log_enqueue(int prio, const char *fmt, va_list ap)
+{
+ int ret = 1;
+
+ pthread_mutex_lock(&logq_lock);
+ pthread_cleanup_push(cleanup_mutex, &logq_lock);
+ if (la)
+ ret = _log_enqueue(prio, fmt, ap);
+ pthread_cleanup_pop(1);
+ return ret;
+}
+
+static int _log_dequeue(void *buff)
{
struct logmsg * src = (struct logmsg *)la->head;
struct logmsg * dst = (struct logmsg *)buff;
return 0;
}
+int log_dequeue(void *buff)
+{
+ int ret = 1;
+
+ pthread_mutex_lock(&logq_lock);
+ pthread_cleanup_push(cleanup_mutex, &logq_lock);
+ if (la)
+ ret = _log_dequeue(buff);
+ pthread_cleanup_pop(1);
+ return ret;
+}
+
/*
* this one can block under memory pressure
*/
int log_dequeue (void *);
void log_syslog (void *);
void dump_logmsg (void *);
-void free_logarea (void);
#endif /* LOG_H */
#include "log_pthread.h"
#include "log.h"
#include "lock.h"
+#include "util.h"
static pthread_t log_thr;
-static pthread_mutex_t logq_lock;
-static pthread_mutex_t logev_lock;
-static pthread_cond_t logev_cond;
+/* logev_lock must not be taken with logq_lock held */
+static pthread_mutex_t logev_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t logev_cond = PTHREAD_COND_INITIALIZER;
static int logq_running;
static int log_messages_pending;
void log_safe (int prio, const char * fmt, va_list ap)
{
+ bool running;
+
if (prio > LOG_DEBUG)
prio = LOG_DEBUG;
- if (log_thr == (pthread_t)0) {
- vsyslog(prio, fmt, ap);
- return;
- }
+ /*
+ * logev_lock protects logq_running. By holding it, we avoid a race
+ * with log_thread_stop() -> log_close(), which would free the logarea.
+ */
+ pthread_mutex_lock(&logev_lock);
+ pthread_cleanup_push(cleanup_mutex, &logev_lock);
+ running = logq_running;
- pthread_mutex_lock(&logq_lock);
- log_enqueue(prio, fmt, ap);
- pthread_mutex_unlock(&logq_lock);
+ if (running) {
+ log_enqueue(prio, fmt, ap);
- pthread_mutex_lock(&logev_lock);
- log_messages_pending = 1;
- pthread_cond_signal(&logev_cond);
- pthread_mutex_unlock(&logev_lock);
+ log_messages_pending = 1;
+ pthread_cond_signal(&logev_cond);
+ }
+ pthread_cleanup_pop(1);
+
+ if (!running)
+ vsyslog(prio, fmt, ap);
}
static void flush_logqueue (void)
int empty;
do {
- pthread_mutex_lock(&logq_lock);
empty = log_dequeue(la->buff);
- pthread_mutex_unlock(&logq_lock);
if (!empty)
log_syslog(la->buff);
} while (empty == 0);
}
+static void cleanup_log_thread(__attribute((unused)) void *arg)
+{
+ logdbg(stderr, "log thread exiting");
+ pthread_mutex_lock(&logev_lock);
+ logq_running = 0;
+ pthread_mutex_unlock(&logev_lock);
+}
+
static void * log_thread (__attribute__((unused)) void * et)
{
int running;
pthread_mutex_lock(&logev_lock);
- logq_running = 1;
+ running = logq_running;
+ if (!running)
+ logq_running = 1;
+ pthread_cond_signal(&logev_cond);
pthread_mutex_unlock(&logev_lock);
+ if (running)
+ /* already started */
+ return NULL;
+ pthread_cleanup_push(cleanup_log_thread, NULL);
mlockall(MCL_CURRENT | MCL_FUTURE);
logdbg(stderr,"enter log_thread\n");
while (1) {
pthread_mutex_lock(&logev_lock);
- if (logq_running && !log_messages_pending)
+ pthread_cleanup_push(cleanup_mutex, &logev_lock);
+ while (!log_messages_pending)
+ /* this is a cancellation point */
pthread_cond_wait(&logev_cond, &logev_lock);
log_messages_pending = 0;
- running = logq_running;
- pthread_mutex_unlock(&logev_lock);
- if (!running)
- break;
+ pthread_cleanup_pop(1);
+
flush_logqueue();
}
+ pthread_cleanup_pop(1);
return NULL;
}
void log_thread_start (pthread_attr_t *attr)
{
- logdbg(stderr,"enter log_thread_start\n");
+ int running = 0;
- pthread_mutex_init(&logq_lock, NULL);
- pthread_mutex_init(&logev_lock, NULL);
- pthread_cond_init(&logev_cond, NULL);
+ logdbg(stderr,"enter log_thread_start\n");
if (log_init("multipathd", 0)) {
fprintf(stderr,"can't initialize log buffer\n");
exit(1);
}
- if (pthread_create(&log_thr, attr, log_thread, NULL)) {
+
+ pthread_mutex_lock(&logev_lock);
+ pthread_cleanup_push(cleanup_mutex, &logev_lock);
+ if (!pthread_create(&log_thr, attr, log_thread, NULL))
+ while (!(running = logq_running))
+ pthread_cond_wait(&logev_cond, &logev_lock);
+ pthread_cleanup_pop(1);
+
+ if (!running) {
fprintf(stderr,"can't start log thread\n");
exit(1);
}
void log_thread_reset (void)
{
logdbg(stderr,"resetting log\n");
-
- pthread_mutex_lock(&logq_lock);
log_reset("multipathd");
- pthread_mutex_unlock(&logq_lock);
}
void log_thread_stop (void)
{
+ int running;
+
+ if (!la)
+ return;
+
logdbg(stderr,"enter log_thread_stop\n");
pthread_mutex_lock(&logev_lock);
- logq_running = 0;
- pthread_cond_signal(&logev_cond);
- pthread_mutex_unlock(&logev_lock);
+ pthread_cleanup_push(cleanup_mutex, &logev_lock);
+ running = logq_running;
+ if (running) {
+ pthread_cancel(log_thr);
+ pthread_cond_signal(&logev_cond);
+ }
+ pthread_cleanup_pop(1);
- pthread_mutex_lock(&logq_lock);
- pthread_cancel(log_thr);
- pthread_mutex_unlock(&logq_lock);
- pthread_join(log_thr, NULL);
- log_thr = (pthread_t)0;
+ if (running)
+ pthread_join(log_thr, NULL);
flush_logqueue();
-
- pthread_mutex_destroy(&logq_lock);
- pthread_mutex_destroy(&logev_lock);
- pthread_cond_destroy(&logev_cond);
-
log_close();
}
/* non-recursive configuration stream handler */
static int kw_level = 0;
-int warn_on_duplicates(vector uniques, char *str, char *file)
+int warn_on_duplicates(vector uniques, char *str, const char *file)
{
char *tmp;
int i;
}
int
-validate_config_strvec(vector strvec, char *file)
+validate_config_strvec(vector strvec, const char *file)
{
char *str = NULL;
int i;
}
static int
-process_stream(struct config *conf, FILE *stream, vector keywords, char *file)
+process_stream(struct config *conf, FILE *stream, vector keywords,
+ const char *file)
{
int i;
int r = 0, t;
if (!strcmp(str, EOB)) {
if (kw_level > 0) {
free_strvec(strvec);
- break;
+ goto out;
}
condlog(0, "unmatched '%s' at line %d of %s",
EOB, line_nr, file);
free_strvec(strvec);
}
-
+ if (kw_level == 1)
+ condlog(1, "missing '%s' at end of %s", EOB, file);
out:
FREE(buf);
free_uniques(uniques);
/* Data initialization */
int
-process_file(struct config *conf, char *file)
+process_file(struct config *conf, const char *file)
{
int r;
FILE *stream;
extern void free_keywords(vector keywords);
extern vector alloc_strvec(char *string);
extern void *set_value(vector strvec);
-extern int process_file(struct config *conf, char *conf_file);
+extern int process_file(struct config *conf, const char *conf_file);
extern struct keyword * find_keyword(vector keywords, vector v, char * name);
int snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
const void *data);
struct udev_device *u_dev;
path = udev_list_entry_get_name(item);
+ if (!path)
+ continue;
u_dev = udev_device_new_from_syspath(udev, path);
+ if (!u_dev)
+ continue;
devname = udev_device_get_sysname(u_dev);
+ if (!devname) {
+ udev_device_unref(u_dev);
+ continue;
+ }
fwd += snprintf(buff + fwd, len - fwd, " %s", devname);
if (fwd >= len)
return default_timeout;
}
-int init_prio (char *multipath_dir)
+int init_prio (const char *multipath_dir)
{
+#ifdef LOAD_ALL_SHARED_LIBS
+ static const char *const all_prios[] = {
+ PRIO_ALUA,
+ PRIO_CONST,
+ PRIO_DATACORE,
+ PRIO_EMC,
+ PRIO_HDS,
+ PRIO_HP_SW,
+ PRIO_ONTAP,
+ PRIO_RANDOM,
+ PRIO_RDAC,
+ PRIO_WEIGHTED_PATH,
+ PRIO_SYSFS,
+ PRIO_PATH_LATENCY,
+ PRIO_ANA,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(all_prios); i++)
+ add_prio(multipath_dir, all_prios[i]);
+#else
if (!add_prio(multipath_dir, DEFAULT_PRIO))
return 1;
+#endif
return 0;
}
return snprintf(p->args, PRIO_ARGS_LEN, "%s", args);
}
-struct prio * add_prio (char *multipath_dir, char * name)
+struct prio * add_prio (const char *multipath_dir, const char * name)
{
char libname[LIB_PRIO_NAMELEN];
struct stat stbuf;
unsigned int get_prio_timeout(unsigned int checker_timeout,
unsigned int default_timeout);
-int init_prio (char *);
+int init_prio (const char *);
void cleanup_prio (void);
-struct prio * add_prio (char *, char *);
+struct prio * add_prio (const char *, const char *);
int prio_getprio (struct prio *, struct path *, unsigned int);
void prio_get (char *, struct prio *, char *, char *);
void prio_put (struct prio *);
include ../../Makefile.inc
CFLAGS += $(LIB_CFLAGS) -I..
+LDFLAGS += -L..
+LIBDEPS = -lmultipath -lm -lpthread -lrt
# If you add or remove a prioritizer also update multipath/multipath.conf.5
LIBS = \
all: $(LIBS)
-libpriopath_latency.so: path_latency.o
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -lm
-
libprio%.so: %.o
- $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^
+ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ $(LIBDEPS)
install: $(LIBS)
$(INSTALL_PROGRAM) -m 755 libprio*.so $(DESTDIR)$(libdir)
__do_set_from_vec(struct hwentry, var, (src)->hwe, dest)
#define do_set_from_hwe(var, src, dest, msg) \
- if (!src->hwe) { \
- condlog(0, "BUG: do_set_from_hwe called with hwe == NULL"); \
- } else if (__do_set_from_hwe(var, src, dest)) { \
+ if (src->hwe && __do_set_from_hwe(var, src, dest)) { \
origin = msg; \
goto out; \
}
{
int len;
char buff[44];
- const char *checker_name;
+ const char *checker_name = NULL;
if (pp->bus != SYSFS_BUS_SCSI)
return 0;
- /* Avoid ioctl if this is likely not an RDAC array */
- if (__do_set_from_hwe(checker_name, pp, checker_name) &&
- strcmp(checker_name, RDAC))
+ /* Avoid checking 0xc9 if this is likely not an RDAC array */
+ if (!__do_set_from_hwe(checker_name, pp, checker_name) &&
+ !is_vpd_page_supported(pp->fd, 0xC9))
+ return 0;
+ if (checker_name && strcmp(checker_name, RDAC))
return 0;
len = get_vpd_sgio(pp->fd, 0xC9, 0, buff, 44);
if (len <= 0)
return 0;
}
+/* must be called after select_getuid */
+int select_recheck_wwid(struct config *conf, struct path * pp)
+{
+ const char *origin;
+
+ pp_set_ovr(recheck_wwid);
+ pp_set_hwe(recheck_wwid);
+ pp_set_conf(recheck_wwid);
+ pp_set_default(recheck_wwid, DEFAULT_RECHECK_WWID);
+out:
+ if (pp->recheck_wwid == RECHECK_WWID_ON &&
+ (pp->bus != SYSFS_BUS_SCSI || pp->getuid != NULL ||
+ !has_uid_fallback(pp))) {
+ pp->recheck_wwid = RECHECK_WWID_OFF;
+ origin = "(setting: unsupported by device type/config)";
+ }
+ condlog(3, "%s: recheck_wwid = %i %s", pp->dev, pp->recheck_wwid,
+ origin);
+ return 0;
+}
+
void
detect_prio(struct config *conf, struct path * pp)
{
int select_minio(struct config *conf, struct multipath *mp)
{
- unsigned int minv_dmrq[3] = {1, 1, 0};
+ unsigned int minv_dmrq[3] = {1, 1, 0}, version[3];
- if (VERSION_GE(conf->version, minv_dmrq))
+ if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version)
+ && VERSION_GE(version, minv_dmrq))
return select_minio_rq(conf, mp);
else
return select_minio_bio(conf, mp);
mp_set_conf(fast_io_fail);
mp_set_default(fast_io_fail, DEFAULT_FAST_IO_FAIL);
out:
- print_fast_io_fail(buff, 12, mp->fast_io_fail);
+ print_undef_off_zero(buff, 12, mp->fast_io_fail);
condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias, buff, origin);
return 0;
}
return 0;
}
+int select_eh_deadline(struct config *conf, struct multipath *mp)
+{
+ const char *origin;
+ char buff[12];
+
+ mp_set_ovr(eh_deadline);
+ mp_set_hwe(eh_deadline);
+ mp_set_conf(eh_deadline);
+ mp->eh_deadline = EH_DEADLINE_UNSET;
+ /* not changing sysfs in default cause, so don't print anything */
+ return 0;
+out:
+ print_undef_off_zero(buff, 12, mp->eh_deadline);
+ condlog(3, "%s: eh_deadline = %s %s", mp->alias, buff, origin);
+ return 0;
+}
+
int select_flush_on_last_del(struct config *conf, struct multipath *mp)
{
const char *origin;
int select_retain_hwhandler(struct config *conf, struct multipath *mp)
{
const char *origin;
- unsigned int minv_dm_retain[3] = {1, 5, 0};
+ unsigned int minv_dm_retain[3] = {1, 5, 0}, version[3];
- if (!VERSION_GE(conf->version, minv_dm_retain)) {
+ if (!libmp_get_version(DM_MPATH_TARGET_VERSION, version) &&
+ !VERSION_GE(version, minv_dm_retain)) {
mp->retain_hwhandler = RETAIN_HWHANDLER_OFF;
origin = "(setting: WARNING, requires kernel dm-mpath version >= 1.5.0)";
goto out;
int select_hwhandler (struct config *conf, struct multipath * mp);
int select_checker(struct config *conf, struct path *pp);
int select_getuid (struct config *conf, struct path * pp);
+int select_recheck_wwid(struct config *conf, struct path * pp);
int select_prio (struct config *conf, struct path * pp);
int select_find_multipaths_timeout(struct config *conf, struct path *pp);
int select_no_path_retry(struct config *conf, struct multipath *mp);
int select_gid(struct config *conf, struct multipath *mp);
int select_fast_io_fail(struct config *conf, struct multipath *mp);
int select_dev_loss(struct config *conf, struct multipath *mp);
+int select_eh_deadline(struct config *conf, struct multipath *mp);
int select_reservation_key(struct config *conf, struct multipath *mp);
int select_retain_hwhandler (struct config *conf, struct multipath * mp);
int select_detect_prio(struct config *conf, struct path * pp);
return mpp;
}
+void *set_mpp_hwe(struct multipath *mpp, const struct path *pp)
+{
+ if (!mpp || !pp || !pp->hwe)
+ return NULL;
+ if (mpp->hwe)
+ return mpp->hwe;
+ mpp->hwe = vector_convert(NULL, pp->hwe,
+ struct hwentry, identity);
+ return mpp->hwe;
+}
+
void free_multipath_attributes(struct multipath *mpp)
{
if (!mpp)
free_pathvec(mpp->paths, free_paths);
free_pgvec(mpp->pg, free_paths);
+ if (mpp->hwe) {
+ vector_free(mpp->hwe);
+ mpp->hwe = NULL;
+ }
FREE_PTR(mpp->mpcontext);
FREE(mpp);
}
}
struct path *
-find_path_by_dev (const struct _vector *pathvec, const char * dev)
+find_path_by_dev (const struct _vector *pathvec, const char *dev)
{
int i;
struct path * pp;
- if (!pathvec)
+ if (!pathvec || !dev)
return NULL;
vector_foreach_slot (pathvec, pp, i)
VPD_VP_ARRAY_SIZE, /* This must remain the last entry */
};
+/*
+ * Multipath treats 0 as undefined for optional config parameters.
+ * Use this for cases where 0 is a valid option for systems multipath
+ * is communicating with
+ */
+enum undefined_off_zero {
+ UOZ_UNDEF = 0,
+ UOZ_OFF = -1,
+ UOZ_ZERO = -2,
+};
+
+enum fast_io_fail_states {
+ MP_FAST_IO_FAIL_UNSET = UOZ_UNDEF,
+ MP_FAST_IO_FAIL_OFF = UOZ_OFF,
+ MP_FAST_IO_FAIL_ZERO = UOZ_ZERO,
+};
+
+enum eh_deadline_states {
+ EH_DEADLINE_UNSET = UOZ_UNDEF,
+ EH_DEADLINE_OFF = UOZ_OFF,
+ EH_DEADLINE_ZERO = UOZ_ZERO,
+};
+
+enum recheck_wwid_states {
+ RECHECK_WWID_UNDEF = YNU_UNDEF,
+ RECHECK_WWID_OFF = YNU_NO,
+ RECHECK_WWID_ON = YNU_YES,
+};
+
struct vpd_vendor_page {
int pg;
const char *name;
int find_multipaths_timeout;
int marginal;
int vpd_vendor_id;
+ int recheck_wwid;
/* configlet pointers */
vector hwe;
struct gen_path generic_path;
int ghost_delay;
int ghost_delay_tick;
unsigned int dev_loss;
+ int eh_deadline;
uid_t uid;
gid_t gid;
mode_t mode;
struct path * alloc_path (void);
struct pathgroup * alloc_pathgroup (void);
struct multipath * alloc_multipath (void);
+void *set_mpp_hwe(struct multipath *mpp, const struct path *pp);
void uninitialize_path(struct path *pp);
void free_path (struct path *);
void free_pathvec (vector vec, enum free_path_mode free_paths);
void orphan_path(struct path *pp, const char *reason)
{
condlog(3, "%s: orphan path, %s", pp->dev, reason);
- if (pp->mpp && pp->hwe && pp->mpp->hwe == pp->hwe) {
- condlog(0, "BUG: orphaning path %s that holds hwe of %s",
- pp->dev, pp->mpp->alias);
- pp->mpp->hwe = NULL;
- }
pp->mpp = NULL;
uninitialize_path(pp);
}
int i;
struct path * pp;
- /* Avoid BUG message from orphan_path() */
- mpp->hwe = NULL;
vector_foreach_slot (pathvec, pp, i) {
if (pp->mpp == mpp) {
if (pp->initialized == INIT_REMOVED) {
if (mpp->hwe || !mpp->paths)
return;
- condlog(3, "%s: searching paths for valid hwe", mpp->alias);
+ condlog(4, "%s: searching paths for valid hwe", mpp->alias);
/* doing this in two passes seems like paranoia to me */
vector_foreach_slot(mpp->paths, pp, i) {
- if (pp->state != PATH_UP)
- continue;
- if (pp->hwe) {
- mpp->hwe = pp->hwe;
- return;
- }
+ if (pp->state == PATH_UP &&
+ pp->initialized != INIT_REMOVED && pp->hwe)
+ goto done;
}
vector_foreach_slot(mpp->paths, pp, i) {
- if (pp->state == PATH_UP)
- continue;
- if (pp->hwe) {
- mpp->hwe = pp->hwe;
- return;
- }
+ if (pp->state != PATH_UP &&
+ pp->initialized != INIT_REMOVED && pp->hwe)
+ goto done;
}
+done:
+ if (i < VECTOR_SIZE(mpp->paths))
+ (void)set_mpp_hwe(mpp, pp);
+
+ if (mpp->hwe)
+ condlog(3, "%s: got hwe from path %s", mpp->alias, pp->dev);
+ else
+ condlog(2, "%s: no hwe found", mpp->alias);
}
int
r = dm_get_map(mpp->alias, &mpp->size, params);
if (r != DMP_OK) {
- condlog(3, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting table" : "map not present");
+ condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting table" : "map not present");
return r;
}
if (disassemble_map(pathvec, params, mpp)) {
- condlog(3, "%s: cannot disassemble map", mpp->alias);
+ condlog(2, "%s: cannot disassemble map", mpp->alias);
return DMP_ERR;
}
+ *params = '\0';
+ if (dm_get_status(mpp->alias, params) != DMP_OK)
+ condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting status" : "map not present");
+ else if (disassemble_status(params, mpp))
+ condlog(2, "%s: cannot disassemble status", mpp->alias);
+
/* FIXME: we should deal with the return value here */
update_pathvec_from_dm(pathvec, mpp, flags);
return DMP_OK;
}
-int
-update_multipath_status (struct multipath *mpp)
-{
- int r = DMP_ERR;
- char status[PARAMS_SIZE] = {0};
-
- if (!mpp)
- return r;
-
- r = dm_get_status(mpp->alias, status);
- if (r != DMP_OK) {
- condlog(3, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting status" : "map not present");
- return r;
- }
-
- if (disassemble_status(status, mpp)) {
- condlog(3, "%s: cannot disassemble status", mpp->alias);
- return DMP_ERR;
- }
-
- return DMP_OK;
-}
-
static struct path *find_devt_in_pathgroups(const struct multipath *mpp,
const char *dev_t)
{
}
if (!found) {
condlog(3, "%s dropped path %s", mpp->alias, pp->dev);
- if (mpp->hwe == pp->hwe)
- mpp->hwe = NULL;
vector_del_slot(mpp->paths, i--);
orphan_path(pp, "path removed externally");
}
update_mpp_paths(mpp, pathvec);
vector_foreach_slot (mpp->paths, pp, i)
pp->mpp = mpp;
- if (mpp->hwe == NULL)
- extract_hwe_from_path(mpp);
}
int
r = update_multipath_table(mpp, pathvec, 0);
if (r != DMP_OK)
return r;
- sync_paths(mpp, pathvec);
- r = update_multipath_status(mpp);
- if (r != DMP_OK)
- return r;
+ sync_paths(mpp, pathvec);
vector_foreach_slot(mpp->pg, pgp, i)
if (pgp->paths)
conf = get_multipath_config();
mpp->mpe = find_mpe(conf->mptable, pp->wwid);
- mpp->hwe = pp->hwe;
put_multipath_config(conf);
+ /*
+ * We need to call this before select_alias(),
+ * because that accesses hwe properties.
+ */
+ if (pp->hwe && !set_mpp_hwe(mpp, pp))
+ goto out;
+
strcpy(mpp->wwid, pp->wwid);
find_existing_alias(mpp, vecs);
if (select_alias(conf, mpp))
goto out;
mpp->size = pp->size;
- if (adopt_paths(vecs->pathvec, mpp) ||
- find_slot(vecs->pathvec, pp) == -1)
+ if (adopt_paths(vecs->pathvec, mpp) || pp->mpp != mpp ||
+ find_slot(mpp->paths, pp) == -1)
goto out;
if (add_vec) {
vector_del_slot(mpp->paths, i);
i--;
- /* Make sure mpp->hwe doesn't point to freed memory.
- * We call extract_hwe_from_path() below to restore
- * mpp->hwe
- */
- if (mpp->hwe == pp->hwe)
- mpp->hwe = NULL;
/*
* Don't delete path from pathvec yet. We'll do this
* after the path has been removed from the map, in
mpp->alias, pp->dev, pp->dev_t);
}
}
- extract_hwe_from_path(mpp);
return count;
}
pthread_cleanup_push(close_fd, (void *)fd);
nr = read(fd, uuid, sizeof(uuid));
if (nr > (int)UUID_PREFIX_LEN &&
- !memcmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN))
+ !memcmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN)) {
found = true;
- else if (nr < 0) {
+ if (set_wwid) {
+ nr -= UUID_PREFIX_LEN;
+ memcpy(pp->wwid, uuid + UUID_PREFIX_LEN, nr);
+ if (nr == WWID_SIZE) {
+ condlog(4, "%s: overflow while reading from %s",
+ __func__, pathbuf);
+ pp->wwid[0] = '\0';
+ } else {
+ pp->wwid[nr] = '\0';
+ strchop(pp->wwid);
+ }
+ }
+ } else if (nr < 0)
condlog(1, "%s: error reading from %s: %m",
__func__, pathbuf);
- }
- if (found && set_wwid) {
- nr -= UUID_PREFIX_LEN;
- memcpy(pp->wwid, uuid + UUID_PREFIX_LEN, nr);
- if (nr == WWID_SIZE) {
- condlog(4, "%s: overflow while reading from %s",
- __func__, pathbuf);
- pp->wwid[0] = '\0';
- } else {
- pp->wwid[nr] = '\0';
- strchop(pp->wwid);
- }
- }
pthread_cleanup_pop(1);
}
free(*p);
}
+void cleanup_mutex(void *arg)
+{
+ pthread_mutex_unlock(arg);
+}
+
struct bitfield *alloc_bitfield(unsigned int maxbit)
{
unsigned int n;
{
condlog(0, "%s: bitfield overflow: %u >= %u", f, bit, len);
}
+
+int should_exit(void)
+{
+ return 0;
+}
int parse_prkey_flags(const char *ptr, uint64_t *prkey, uint8_t *flags);
int safe_write(int fd, const void *buf, size_t count);
void set_max_fds(rlim_t max_fds);
+int should_exit(void);
#define KERNEL_VERSION(maj, min, ptc) ((((maj) * 256) + (min)) * 256 + (ptc))
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
void close_fd(void *arg);
void cleanup_free_ptr(void *arg);
+void cleanup_mutex(void *arg);
struct scandir_result {
struct dirent **di;
if (pp->wwid[0] == '\0')
return PATH_IS_NOT_VALID;
- if (pp->udev && pp->uid_attribute &&
- filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0)
- return PATH_IS_NOT_VALID;
-
r = is_failed_wwid(pp->wwid);
if (r != WWID_IS_NOT_FAILED) {
if (r == WWID_IS_FAILED)
#ifndef _VERSION_H
#define _VERSION_H
-#define VERSION_CODE 0x000805
-#define DATE_CODE 0x0b0914
+#define VERSION_CODE 0x000806
+#define DATE_CODE 0x040115
#define PROG "multipath-tools"
void mpath_print_transport_id(struct prin_fulldescr *fdesc);
int construct_transportid(const char * inp, struct transportid transid[], int num_transportids);
-int logsink;
-struct config *multipath_conf;
-
-struct config *get_multipath_config(void)
-{
- return multipath_conf;
-}
-
-void put_multipath_config(__attribute__((unused)) void * arg)
-{
- /* Noop for now */
-}
-
void rcu_register_thread_memb(void) {}
void rcu_unregister_thread_memb(void) {}
-struct udev *udev;
static int verbose, loglevel, noisy;
exit (1);
}
- udev = udev_new();
- multipath_conf = mpath_lib_init();
- if(!multipath_conf) {
- udev_unref(udev);
+ if (libmpathpersist_init()) {
exit(1);
}
+ if (atexit((void(*)(void))libmpathpersist_exit))
+ fprintf(stderr, "failed to register cleanup handler for libmpathpersist: %m");
ret = handle_args(argc, argv, 0);
-
- mpath_lib_exit(multipath_conf);
- udev_unref(udev);
-
return (ret >= 0) ? ret : MPATH_PR_OTHER;
}
# Check the map state directly with multipath -U.
# This doesn't attempt I/O on the device.
-PROGRAM=="$env{MPATH_SBIN_PATH}/multipath -U %k", GOTO="paths_ok"
+PROGRAM=="$env{MPATH_SBIN_PATH}/multipath -U -v1 %k", GOTO="paths_ok"
ENV{MPATH_DEVICE_READY}="0", GOTO="mpath_action"
LABEL="paths_ok"
#include "valid.h"
#include "alias.h"
-int logsink;
-struct udev *udev;
-struct config *multipath_conf;
-
/*
* Return values of configure(), check_path_valid(), and main().
*/
RTVL_RETRY, /* returned by configure(), not by main() */
};
-struct config *get_multipath_config(void)
-{
- return multipath_conf;
-}
-
-void put_multipath_config(__attribute__((unused)) void *arg)
-{
- /* Noop for now */
-}
-
static int
dump_config (struct config *conf, vector hwes, vector mpvec)
{
void rcu_unregister_thread_memb(void) {}
static int
-filter_pathvec (vector pathvec, char * refwwid)
+filter_pathvec (vector pathvec, const char *refwwid)
{
int i;
struct path * pp;
continue;
}
- if (update_multipath_table(mpp, pathvec, flags) != DMP_OK ||
- update_multipath_status(mpp) != DMP_OK) {
+ if (update_multipath_table(mpp, pathvec, flags) != DMP_OK) {
condlog(1, "error parsing map %s", mpp->wwid);
remove_map(mpp, pathvec, curmp, PURGE_VEC);
i--;
mpp->bestpg = select_path_group(mpp);
if (cmd == CMD_LIST_SHORT ||
- cmd == CMD_LIST_LONG) {
- struct config *conf = get_multipath_config();
- print_multipath_topology(mpp, conf->verbosity);
- put_multipath_config(conf);
- }
+ cmd == CMD_LIST_LONG)
+ print_multipath_topology(mpp, libmp_verbosity);
if (cmd == CMD_CREATE)
reinstate_paths(mpp);
}
- if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) {
- struct config *conf = get_multipath_config();
-
- print_foreign_topology(conf->verbosity);
- put_multipath_config(conf);
- }
+ if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG)
+ print_foreign_topology(libmp_verbosity);
return 0;
}
if (mpp == NULL)
goto free;
- if (update_multipath_table(mpp, pathvec, 0) != DMP_OK ||
- update_multipath_status(mpp) != DMP_OK)
+ if (update_multipath_table(mpp, pathvec, 0) != DMP_OK)
goto free;
vector_foreach_slot (mpp->pg, pg, i) {
wait = find_multipaths_check_timeout(pp, 0, &until);
if (wait == FIND_MULTIPATHS_WAITING)
printf("FIND_MULTIPATHS_WAIT_UNTIL=\"%ld.%06ld\"\n",
- until.tv_sec, until.tv_nsec/1000);
+ (long)until.tv_sec, until.tv_nsec/1000);
else if (wait == FIND_MULTIPATHS_WAIT_DONE)
printf("FIND_MULTIPATHS_WAIT_UNTIL=\"0\"\n");
printf("DM_MULTIPATH_DEVICE_PATH=\"%d\"\n",
return ret;
}
+static struct vectors vecs;
+static void cleanup_vecs(void)
+{
+ free_multipathvec(vecs.mpvec, KEEP_PATHS);
+ free_pathvec(vecs.pathvec, FREE_PATHS);
+}
+
static int
configure (struct config *conf, enum mpath_cmds cmd,
enum devtypes dev_type, char *devpath)
{
vector curmp = NULL;
vector pathvec = NULL;
- struct vectors vecs;
int r = RTVL_FAIL, rc;
int di_flag = 0;
char * refwwid = NULL;
*/
curmp = vector_alloc();
pathvec = vector_alloc();
+ atexit(cleanup_vecs);
if (!curmp || !pathvec) {
condlog(0, "can not allocate memory");
if (path_discovery(pathvec, di_flag) < 0)
goto out;
- if (conf->verbosity > 2)
+ if (libmp_verbosity > 2)
print_all_paths(pathvec, 1);
get_path_layout(pathvec, 0);
if (refwwid)
FREE(refwwid);
- free_multipathvec(curmp, KEEP_PATHS);
- free_pathvec(pathvec, FREE_PATHS);
-
return r;
}
check_path_valid(const char *name, struct config *conf, bool is_uevent)
{
int fd, r = PATH_IS_ERROR;
- struct path *pp = NULL;
+ struct path *pp;
vector pathvec = NULL;
+ const char *wwid;
pp = alloc_path();
if (!pp)
if (store_path(pathvec, pp) != 0) {
free_path(pp);
+ pp = NULL;
goto fail;
+ } else {
+ /* make sure path isn't freed twice */
+ wwid = pp->wwid;
+ pp = NULL;
}
/* For find_multipaths = SMART, if there is more than one path
* matching the refwwid, then the path is valid */
if (path_discovery(pathvec, DI_SYSFS | DI_WWID) < 0)
goto fail;
- filter_pathvec(pathvec, pp->wwid);
+ filter_pathvec(pathvec, wwid);
if (VECTOR_SIZE(pathvec) > 1)
r = PATH_IS_VALID;
else
out:
r = print_cmd_valid(r, pathvec, conf);
- free_pathvec(pathvec, FREE_PATHS);
/*
* multipath -u must exit with status 0, otherwise udev won't
* import its output.
*/
if (!is_uevent && r == PATH_IS_NOT_VALID)
- return RTVL_FAIL;
- return RTVL_OK;
+ r = RTVL_FAIL;
+ else
+ r = RTVL_OK;
+ goto cleanup;
fail:
- if (pathvec)
- free_pathvec(pathvec, FREE_PATHS);
- else
+ r = RTVL_FAIL;
+
+cleanup:
+ if (pp != NULL)
free_path(pp);
- return RTVL_FAIL;
+ if (pathvec != NULL)
+ free_pathvec(pathvec, FREE_PATHS);
+ return r;
}
static int
int retries = -1;
bool enable_foreign = false;
- udev = udev_new();
- logsink = 0;
- conf = load_config(DEFAULT_CONFIGFILE);
- if (!conf)
+ libmultipath_init();
+ if (atexit(dm_lib_exit) || atexit(libmultipath_exit))
+ condlog(1, "failed to register cleanup handler for libmultipath: %m");
+ logsink = LOGSINK_STDERR_WITH_TIME;
+ if (init_config(DEFAULT_CONFIGFILE))
exit(RTVL_FAIL);
- multipath_conf = conf;
+ if (atexit(uninit_config))
+ condlog(1, "failed to register cleanup handler for config: %m");
+ conf = get_multipath_config();
conf->retrigger_tries = 0;
conf->force_sync = 1;
while ((arg = getopt(argc, argv, ":adDcChl::eFfM:v:p:b:BrR:itTquUwW")) != EOF ) {
exit(RTVL_FAIL);
}
- conf->verbosity = atoi(optarg);
+ libmp_verbosity = atoi(optarg);
break;
case 'b':
conf->bindings_file = strdup(optarg);
break;
case 't':
r = dump_config(conf, NULL, NULL) ? RTVL_FAIL : RTVL_OK;
- goto out_free_config;
+ goto out;
case 'T':
cmd = CMD_DUMP_CONFIG;
break;
}
if (dev_type == DEV_UEVENT) {
openlog("multipath", 0, LOG_DAEMON);
- setlogmask(LOG_UPTO(conf->verbosity + 3));
- logsink = 1;
+ setlogmask(LOG_UPTO(libmp_verbosity + 3));
+ logsink = LOGSINK_SYSLOG;
}
set_max_fds(conf->max_fds);
condlog(3, "restart multipath configuration process");
out:
- dm_lib_release();
- dm_lib_exit();
-
- cleanup_foreign();
- cleanup_prio();
- cleanup_checkers();
+ put_multipath_config(conf);
+ if (dev)
+ FREE(dev);
if (dev_type == DEV_UEVENT)
closelog();
-out_free_config:
- /*
- * Freeing config must be done after dm_lib_exit(), because
- * the logging function (dm_write_log()), which is called there,
- * references the config.
- */
- free_config(conf);
- conf = NULL;
- udev_unref(udev);
- if (dev)
- FREE(dev);
#ifdef _DEBUG_
dbg_free_final(NULL);
#endif
.
.TP
.B path_checker
-The default method used to determine the paths state. Possible values
-are:
+The default method used to determine the path's state. The synchronous
+checkers (all except \fItur\fR and \fIdirectio\fR) will cause multipathd to
+pause most activity, waiting up to \fIchecker_timeout\fR seconds for the path
+to respond. The asynchronous checkers (\fItur\fR and \fIdirectio\fR) will not
+pause multipathd. Instead, multipathd will check for a response once per
+second, until \fIchecker_timeout\fR seconds have elapsed. Possible values are:
.RS
.TP 12
.I readsector0
Series, and OEM arrays from IBM DELL SGI STK and SUN.
.TP
.I directio
-(Deprecated) Read the first sector with direct I/O. If you have a large number
-of paths, or many AIO users on a system, you may need to use sysctl to
-increase fs.aio-max-nr. This checker is being deprecated, it could cause
-spurious path failures under high load. Please use \fItur\fR instead.
+Read the first sector with direct I/O. This checker could cause spurious path
+failures under high load. Increasing \fIchecker_timeout\fR can help with this.
.TP
.I cciss_tur
(Hardware-dependent)
.
.TP
.B checker_timeout
-Specify the timeout to use for path checkers and prioritizers that issue SCSI
-commands with an explicit timeout, in seconds.
+Specify the timeout to use for path checkers and prioritizers, in seconds.
+Only prioritizers that issue scsi commands use checker_timeout. If a path
+does not respond to the checker command after \fIchecker_timeout\fR
+seconds have elapsed, it is considered down.
.RS
.TP
-The default is: in \fB/sys/block/sd<x>/device/timeout\fR
+The default is: in \fB/sys/block/<dev>/device/timeout\fR
.RE
.
.
.
.
.TP
+.B eh_deadline
+Specify the maximum number of seconds the SCSI layer will spend doing error
+handling when scsi devices fail. After this timeout the scsi layer will perform
+a full HBA reset. Setting this may be necessary in cases where the rport is
+never lost, so \fIfast_io_fail_tmo\fR and \fIdev_loss_tmo\fR will never
+trigger, but (frequently do to load) scsi commands still hang. \fBNote:\fR when
+the scsi error handler performs the HBA reset, all target paths on that HBA
+will be affected. eh_deadline should only be set in cases where all targets on
+the affected HBAs are multipathed.
+.RS
+.TP
+The default is: \fB<unset>\fR
+.RE
+.
+.
+.TP
.B bindings_file
The full pathname of the binding file to be used when the user_friendly_names
option is set.
normal pathgroup. See "Shaky paths detection" below for more information.
.RS
.TP
-The default is: \fBno\fR
+The default is: \fBno\fR
.RE
.
.
those issues.
.RS
.TP
-The default is: \fB1000\fR
+The default is: \fB4000\fR
.RE
.
.
device to the specified value.
.RS
.TP
-The default is: \fB<device dependent>\fR
+The default is: in \fB/sys/block/<dev>/queue/max_sectors_kb\fR
.RE
.
.
.RE
.
.
+.TP
+.B recheck_wwid
+If set to \fIyes\fR, when a failed path is restored, its wwid is rechecked. If
+the wwid has changed, the path is removed from the current multipath device,
+and re-added as a new path. Multipathd will also recheck a path's wwid if it is
+manually re-added. This option only works for SCSI devices that are configured
+to use the default uid_attribute, \fIID_SERIAL\fR, or sysfs for getting their
+wwid.
+.RS
+.TP
+The default is: \fBno\fR
+.RE
+.
+.
.
.\" ----------------------------------------------------------------------------
endif
OBJS = main.o pidfile.o uxlsnr.o uxclnt.o cli.o cli_handlers.o waiter.o \
- dmevents.o
+ dmevents.o init_unwinder.o
EXEC = multipathd
pp = find_path_by_dev(vecs->pathvec, param);
if (pp && pp->initialized != INIT_REMOVED) {
condlog(2, "%s: path already in pathvec", param);
+
+ if (pp->recheck_wwid == RECHECK_WWID_ON &&
+ check_path_wwid_change(pp)) {
+ condlog(0, "%s: wwid changed. Removing device",
+ pp->dev);
+ handle_path_wwid_change(pp, vecs);
+ return 1;
+ }
+
if (pp->mpp)
return 0;
} else if (pp) {
}
do {
if (dm_get_major_minor(param, &major, &minor) < 0)
- condlog(2, "%s: not a device mapper table", param);
+ condlog(count ? 2 : 3,
+ "%s: not a device mapper table", param);
else {
sprintf(dev_path, "dm-%d", minor);
alias = dm_mapname(major, minor);
}
/*if there is no mapname found, we first create the device*/
if (!alias && !count) {
- condlog(2, "%s: mapname not found for %d:%d",
+ condlog(3, "%s: mapname not found for %d:%d",
param, major, minor);
get_refwwid(CMD_NONE, param, DEV_DEVMAP,
vecs->pathvec, &refwwid);
!= CP_OK)
condlog(2, "%s: coalesce_paths failed",
param);
- dm_lib_release();
FREE(refwwid);
}
} /*we attempt to create device only once*/
if (resize_map(mpp, size, vecs) != 0)
return 1;
- dm_lib_release();
if (setup_multipath(vecs, mpp) != 0)
return 1;
sync_map_state(mpp);
{
unsigned int v[3];
- if (dm_drv_version(v))
+ if (libmp_get_version(DM_KERNEL_VERSION, v))
return 0;
if (VERSION_GE(v, DM_VERSION_FOR_ARM_POLL))
dm_task_no_open_count(dmt);
- if (!dm_task_run(dmt)) {
+ if (!libmp_dm_task_run(dmt)) {
dm_log_error(3, DM_DEVICE_LIST, dmt);
goto fail;
}
struct dev_event *dev_evt;
int i;
+ if (!waiter)
+ return;
pthread_mutex_lock(&waiter->events_lock);
vector_foreach_slot(waiter->events, dev_evt, i)
free(dev_evt);
--- /dev/null
+#include <pthread.h>
+#include <unistd.h>
+#include "init_unwinder.h"
+
+static pthread_mutex_t dummy_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t dummy_cond = PTHREAD_COND_INITIALIZER;
+
+static void *dummy_thread(void *arg __attribute__((unused)))
+{
+ pthread_mutex_lock(&dummy_mtx);
+ pthread_cond_broadcast(&dummy_cond);
+ pthread_mutex_unlock(&dummy_mtx);
+ pause();
+ return NULL;
+}
+
+int init_unwinder(void)
+{
+ pthread_t dummy;
+ int rc;
+
+ pthread_mutex_lock(&dummy_mtx);
+
+ rc = pthread_create(&dummy, NULL, dummy_thread, NULL);
+ if (rc != 0) {
+ pthread_mutex_unlock(&dummy_mtx);
+ return rc;
+ }
+
+ pthread_cond_wait(&dummy_cond, &dummy_mtx);
+ pthread_mutex_unlock(&dummy_mtx);
+
+ return pthread_cancel(dummy);
+}
--- /dev/null
+#ifndef _INIT_UNWINDER_H
+#define _INIT_UNWINDER_H 1
+
+/*
+ * init_unwinder(): make sure unwinder symbols are loaded
+ *
+ * libc's implementation of pthread_cancel() loads symbols from
+ * libgcc_s.so using dlopen() when pthread_cancel() is called
+ * for the first time. This happens even with LD_BIND_NOW=1.
+ * This may imply the need for file system access when a thread is
+ * cancelled, which in the case of multipath-tools might be in a
+ * dangerous situation where multipathd must avoid blocking.
+ *
+ * Call load_unwinder() during startup to make sure the dynamic
+ * linker has all necessary symbols resolved early on.
+ *
+ * Return: 0 if successful, an error number otherwise.
+ */
+int init_unwinder(void);
+
+#endif
#include "wwids.h"
#include "foreign.h"
#include "../third-party/valgrind/drd.h"
+#include "init_unwinder.h"
#define FILE_NAME_SIZE 256
#define CMDSIZE 160
#define MSG_SIZE 32
-#define LOG_MSG(lvl, verb, pp) \
+#define LOG_MSG(lvl, pp) \
do { \
if (pp->mpp && checker_selected(&pp->checker) && \
- lvl <= verb) { \
+ lvl <= libmp_verbosity) { \
if (pp->offline) \
condlog(lvl, "%s: %s - path offline", \
pp->mpp->alias, pp->dev); \
struct multipath *mpp;
};
-int logsink;
int uxsock_timeout;
-int verbosity;
-int bindings_read_only;
+static int verbosity;
+static int bindings_read_only;
int ignore_new_devs;
#ifdef NO_DMEVENTS_POLL
-int poll_dmevents = 0;
+static int poll_dmevents = 0;
#else
-int poll_dmevents = 1;
+static int poll_dmevents = 1;
#endif
/* Don't access this variable without holding config_lock */
-enum daemon_status running_state = DAEMON_INIT;
+static volatile enum daemon_status running_state = DAEMON_INIT;
pid_t daemon_pid;
-pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER;
-pthread_cond_t config_cond;
+static pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t config_cond;
+static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr;
+static bool check_thr_started, uevent_thr_started, uxlsnr_thr_started,
+ uevq_thr_started, dmevent_thr_started;
+static int pid_fd = -1;
static inline enum daemon_status get_running_state(void)
{
return st;
}
+int should_exit(void)
+{
+ return get_running_state() == DAEMON_SHUTDOWN;
+}
+
/*
* global copy of vecs for use in sig handlers
*/
-struct vectors * gvecs;
-
-struct udev * udev;
+static struct vectors * gvecs;
struct config *multipath_conf;
{
char notify_msg[MSG_SIZE];
const char *msg;
+ static bool startup_done = false;
+
/*
* Checkerloop switches back and forth between idle and running state.
* No need to tell systemd each time.
if (msg && !safe_sprintf(notify_msg, "STATUS=%s", msg))
sd_notify(0, notify_msg);
+
+ if (new_state == DAEMON_SHUTDOWN)
+ sd_notify(0, "STOPPING=1");
+ else if (new_state == DAEMON_IDLE && old_state == DAEMON_CONFIGURE) {
+ sd_notify(0, "READY=1");
+ startup_done = true;
+ } else if (new_state == DAEMON_CONFIGURE && startup_done)
+ sd_notify(0, "RELOADING=1");
}
#endif
pthread_mutex_unlock(&config_lock);
}
+#define __wait_for_state_change(condition, ms) \
+ ({ \
+ struct timespec tmo; \
+ int rc = 0; \
+ \
+ if (condition) { \
+ get_monotonic_time(&tmo); \
+ tmo.tv_nsec += (ms) * 1000 * 1000; \
+ normalize_timespec(&tmo); \
+ do \
+ rc = pthread_cond_timedwait( \
+ &config_cond, &config_lock, &tmo); \
+ while (rc == 0 && (condition)); \
+ } \
+ rc; \
+ })
+
/*
* If the current status is @oldstate, wait for at most @ms milliseconds
* for the state to change, and return the new state, which may still be
unsigned long ms)
{
enum daemon_status st;
- struct timespec tmo;
if (oldstate == DAEMON_SHUTDOWN)
return DAEMON_SHUTDOWN;
pthread_mutex_lock(&config_lock);
pthread_cleanup_push(config_cleanup, NULL);
+ __wait_for_state_change(running_state == oldstate, ms);
st = running_state;
- if (st == oldstate && clock_gettime(CLOCK_MONOTONIC, &tmo) == 0) {
- tmo.tv_nsec += ms * 1000 * 1000;
- normalize_timespec(&tmo);
- (void)pthread_cond_timedwait(&config_cond, &config_lock, &tmo);
- st = running_state;
- }
pthread_cleanup_pop(1);
return st;
}
pthread_cleanup_push(config_cleanup, NULL);
pthread_mutex_lock(&config_lock);
if (running_state != state) {
-#ifdef USE_SYSTEMD
- enum daemon_status old_state = running_state;
-#endif
if (running_state == DAEMON_SHUTDOWN)
rc = EINVAL;
- else if (running_state != DAEMON_IDLE) {
- struct timespec ts;
-
- get_monotonic_time(&ts);
- ts.tv_sec += 1;
- rc = pthread_cond_timedwait(&config_cond,
- &config_lock, &ts);
- }
- if (!rc && (running_state != DAEMON_SHUTDOWN)) {
- running_state = state;
- pthread_cond_broadcast(&config_cond);
-#ifdef USE_SYSTEMD
- do_sd_notify(old_state, state);
-#endif
- }
+ else
+ rc = __wait_for_state_change(
+ running_state != DAEMON_IDLE, 1000);
+ if (!rc)
+ __post_config_state(state);
}
pthread_cleanup_pop(1);
return rc;
sleep(1);
goto retry;
}
- dm_lib_release();
fail:
if (new_map && (retries < 0 || wait_for_events(mpp, vecs))) {
if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK)
goto out;
- if (update_multipath_status(mpp) != DMP_OK)
- goto out;
if (!vector_alloc_slot(vecs->mpvec))
goto out;
vector_del_slot(ompv, i);
i--;
}
- else {
- dm_lib_release();
+ else
condlog(2, "%s devmap removed", ompp->alias);
- }
} else if (reassign_maps) {
condlog(3, "%s: Reassign existing device-mapper"
" devices", ompp->alias);
}
return r;
}
- else {
- dm_lib_release();
+ else
condlog(2, "%s: map flushed", mpp->alias);
- }
orphan_paths(vecs->pathvec, mpp, "map flushed");
remove_map_and_stop_waiter(mpp, vecs);
return flush_map(mpp, vecs, 0);
}
+static void
+rescan_path(struct udev_device *ud)
+{
+ ud = udev_device_get_parent_with_subsystem_devtype(ud, "scsi",
+ "scsi_device");
+ if (ud)
+ sysfs_attr_set_value(ud, "rescan", "1", strlen("1"));
+}
+
+void
+handle_path_wwid_change(struct path *pp, struct vectors *vecs)
+{
+ struct udev_device *udd;
+
+ if (!pp || !pp->udev)
+ return;
+
+ udd = udev_device_ref(pp->udev);
+ if (ev_remove_path(pp, vecs, 1) != 0 && pp->mpp) {
+ pp->dmstate = PSTATE_FAILED;
+ dm_fail_path(pp->mpp->alias, pp->dev_t);
+ }
+ rescan_path(udd);
+ sysfs_attr_set_value(udd, "uevent", "add", strlen("add"));
+ udev_device_unref(udd);
+}
+
+bool
+check_path_wwid_change(struct path *pp)
+{
+ char wwid[WWID_SIZE];
+ int len = 0;
+ size_t i;
+
+ if (!strlen(pp->wwid))
+ return false;
+
+ /* Get the real fresh device wwid by sgio. sysfs still has old
+ * data, so only get_vpd_sgio will work to get the new wwid */
+ len = get_vpd_sgio(pp->fd, 0x83, 0, wwid, WWID_SIZE);
+
+ if (len <= 0) {
+ condlog(2, "%s: failed to check wwid by sgio: len = %d",
+ pp->dev, len);
+ return false;
+ }
+
+ /*Strip any trailing blanks */
+ for (i = strlen(pp->wwid); i > 0 && pp->wwid[i-1] == ' '; i--);
+ /* no-op */
+ pp->wwid[i] = '\0';
+ condlog(4, "%s: Got wwid %s by sgio", pp->dev, wwid);
+
+ if (strncmp(wwid, pp->wwid, WWID_SIZE)) {
+ condlog(0, "%s: wwid '%s' doesn't match wwid '%s' from device",
+ pp->dev, pp->wwid, wwid);
+ return true;
+ }
+
+ return false;
+}
+
static int
uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map)
{
*/
pp->mpp = prev_mpp;
ret = ev_remove_path(pp, vecs, true);
- if (r == PATHINFO_OK && !ret)
- /*
- * Path successfully freed, move on to
- * "new path" code path below
- */
- pp = NULL;
- else {
+ if (ret != 0) {
/*
* Failure in ev_remove_path will keep
* path in pathvec in INIT_REMOVED state
dm_fail_path(pp->mpp->alias, pp->dev_t);
condlog(1, "%s: failed to re-add path still mapped in %s",
pp->dev, pp->mpp->alias);
- }
+ } else if (r == PATHINFO_OK)
+ /*
+ * Path successfully freed, move on to
+ * "new path" code path below
+ */
+ pp = NULL;
} else if (r == PATHINFO_SKIPPED) {
condlog(3, "%s: remove blacklisted path",
uev->kernel);
if (mpp) {
condlog(4,"%s: adopting all paths for path %s",
mpp->alias, pp->dev);
- if (adopt_paths(vecs->pathvec, mpp) ||
- find_slot(vecs->pathvec, pp) == -1)
+ if (adopt_paths(vecs->pathvec, mpp) || pp->mpp != mpp ||
+ find_slot(mpp->paths, pp) == -1)
goto fail; /* leave path added to pathvec */
verify_paths(mpp);
*/
start_waiter = 1;
}
- if (!start_waiter)
+ else
goto fail; /* leave path added to pathvec */
}
else
goto fail_map;
}
- dm_lib_release();
if ((mpp->action == ACT_CREATE ||
(mpp->action == ACT_NOTHING && start_waiter && !mpp->waiter)) &&
vector_del_slot(mpp->paths, i);
/*
- * Make sure mpp->hwe doesn't point to freed memory
- * We call extract_hwe_from_path() below to restore mpp->hwe
- */
- if (mpp->hwe == pp->hwe)
- mpp->hwe = NULL;
-
- /*
* remove the map IF removing the last path
*/
if (VECTOR_SIZE(mpp->paths) == 0) {
*/
}
- if (mpp->hwe == NULL)
- extract_hwe_from_path(mpp);
-
if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
condlog(0, "%s: failed to setup map for"
" removal of path %s", mpp->alias, pp->dev);
condlog(0, "%s: path wwid changed from '%s' to '%s'",
uev->kernel, wwid, pp->wwid);
ev_remove_path(pp, vecs, 1);
+ rescan_path(uev->udev);
needs_reinit = 1;
goto out;
} else {
return 1;
vector_foreach_slot (vecs->mpvec, mpp, i)
- if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK ||
- update_multipath_status(mpp) != DMP_OK) {
+ if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK) {
remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC);
i--;
}
uev_pathfail_check(uev, vecs);
} else if (!strncmp(uev->action, "remove", 6)) {
r = uev_remove_map(uev, vecs);
- } else if (!strncmp(uev->action, "add", 3)) {
- const char *ev_name;
- char *dm_name;
- int major = -1, minor = -1;
-
- /*
- * If DM_NAME is not set for a valid map, trigger a
- * change event. This can happen during coldplug
- * if udev was killed between handling the 'add' and
- * 'change' events before.
- */
- ev_name = uevent_get_dm_name(uev);
- if (!ev_name) {
- major = uevent_get_major(uev);
- minor = uevent_get_minor(uev);
- dm_name = dm_mapname(major, minor);
- if (dm_name && *dm_name) {
- condlog(2, "%s: received incomplete 'add' uevent, triggering change",
- dm_name);
- udev_device_set_sysattr_value(uev->udev,
- "uevent",
- "change");
- free(dm_name);
- }
- }
}
goto out;
}
{
if (reload_map(vecs, mpp, refresh, 1))
return 1;
-
- dm_lib_release();
if (setup_multipath(vecs, mpp) != 0)
return 2;
sync_map_state(mpp);
int chkr_new_path_up = 0;
int disable_reinstate = 0;
int oldchkrstate = pp->chkrstate;
- int retrigger_tries, verbosity;
+ int retrigger_tries;
unsigned int checkint, max_checkint;
struct config *conf;
int marginal_pathgroups, marginal_changed = 0;
retrigger_tries = conf->retrigger_tries;
checkint = conf->checkint;
max_checkint = conf->max_checkint;
- verbosity = conf->verbosity;
marginal_pathgroups = conf->marginal_pathgroups;
put_multipath_config(conf);
if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) {
condlog(2, "%s: unusable path (%s) - checker failed",
pp->dev, checker_state_name(newstate));
- LOG_MSG(2, verbosity, pp);
+ LOG_MSG(2, pp);
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
pathinfo(pp, conf, 0);
pthread_cleanup_pop(1);
return 1;
- } else if ((newstate != PATH_UP && newstate != PATH_GHOST) &&
- (pp->state == PATH_DELAYED)) {
+ } else if ((newstate != PATH_UP && newstate != PATH_GHOST &&
+ newstate != PATH_PENDING) && (pp->state == PATH_DELAYED)) {
/* If path state become failed again cancel path delay state */
pp->state = newstate;
+ /*
+ * path state bad again should change the check interval time
+ * to the shortest delay
+ */
+ pp->checkint = checkint;
return 1;
}
if (!pp->mpp) {
ev_add_path(pp, vecs, 1);
pp->tick = 1;
} else {
+ if (ret == PATHINFO_SKIPPED)
+ return -1;
/*
* We failed multiple times to initialize this
* path properly. Don't re-check too often.
*/
pp->checkint = max_checkint;
- if (ret == PATHINFO_SKIPPED)
- return -1;
}
}
return 0;
if (ret == DMP_NOT_FOUND) {
/* multipath device missing. Likely removed */
condlog(1, "%s: multipath device '%s' not found",
- pp->dev, pp->mpp->alias);
+ pp->dev, pp->mpp ? pp->mpp->alias : "");
return 0;
} else
condlog(1, "%s: Couldn't synchronize with kernel state",
return 0;
set_no_path_retry(pp->mpp);
+ if (pp->recheck_wwid == RECHECK_WWID_ON &&
+ (newstate == PATH_UP || newstate == PATH_GHOST) &&
+ ((pp->state != PATH_UP && pp->state != PATH_GHOST) ||
+ pp->dmstate == PSTATE_FAILED) &&
+ check_path_wwid_change(pp)) {
+ condlog(0, "%s: path wwid change detected. Removing", pp->dev);
+ handle_path_wwid_change(pp, vecs);
+ return 0;
+ }
+
if ((newstate == PATH_UP || newstate == PATH_GHOST) &&
(san_path_check_enabled(pp->mpp) ||
marginal_path_check_enabled(pp->mpp))) {
- int was_marginal = pp->marginal;
if (should_skip_path(pp)) {
+ if (!pp->marginal && pp->state != PATH_DELAYED)
+ condlog(2, "%s: path is now marginal", pp->dev);
if (!marginal_pathgroups) {
if (marginal_path_check_enabled(pp->mpp))
/* to reschedule as soon as possible,
pp->state = PATH_DELAYED;
return 1;
}
- if (!was_marginal) {
+ if (!pp->marginal) {
pp->marginal = 1;
marginal_changed = 1;
}
- } else if (marginal_pathgroups && was_marginal) {
- pp->marginal = 0;
- marginal_changed = 1;
+ } else {
+ if (pp->marginal || pp->state == PATH_DELAYED)
+ condlog(2, "%s: path is no longer marginal",
+ pp->dev);
+ if (marginal_pathgroups && pp->marginal) {
+ pp->marginal = 0;
+ marginal_changed = 1;
+ }
}
}
int oldstate = pp->state;
pp->state = newstate;
- LOG_MSG(1, verbosity, pp);
+ LOG_MSG(1, pp);
/*
* upon state change, reset the checkint
/* Clear IO errors */
reinstate_path(pp);
else {
- LOG_MSG(4, verbosity, pp);
+ LOG_MSG(4, pp);
if (pp->checkint != max_checkint) {
/*
* double the next check delay.
log_checker_err = conf->log_checker_err;
put_multipath_config(conf);
if (log_checker_err == LOG_CHKR_ERR_ONCE)
- LOG_MSG(3, verbosity, pp);
+ LOG_MSG(3, pp);
else
- LOG_MSG(2, verbosity, pp);
+ LOG_MSG(2, pp);
}
}
*/
condlog(4, "path prio refresh");
- if (marginal_changed) {
- condlog(2, "%s: path is %s marginal", pp->dev,
- (pp->marginal)? "now" : "no longer");
+ 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) {
get_monotonic_time(&start_time);
if (start_time.tv_sec && last_time.tv_sec) {
timespecsub(&start_time, &last_time, &diff_time);
- condlog(4, "tick (%lu.%06lu secs)",
- diff_time.tv_sec, diff_time.tv_nsec / 1000);
+ condlog(4, "tick (%ld.%06lu secs)",
+ (long)diff_time.tv_sec, diff_time.tv_nsec / 1000);
last_time = start_time;
ticks = diff_time.tv_sec;
} else {
vector_foreach_slot (vecs->pathvec, pp, i) {
rc = check_path(vecs, pp, ticks);
if (rc < 0) {
+ condlog(1, "%s: check_path() failed, removing",
+ pp->dev);
vector_del_slot(vecs->pathvec, i);
free_path(pp);
i--;
if (num_paths) {
unsigned int max_checkint;
- condlog(4, "checked %d path%s in %lu.%06lu secs",
+ condlog(4, "checked %d path%s in %ld.%06lu secs",
num_paths, num_paths > 1 ? "s" : "",
- diff_time.tv_sec,
+ (long)diff_time.tv_sec,
diff_time.tv_nsec / 1000);
conf = get_multipath_config();
max_checkint = conf->max_checkint;
put_multipath_config(conf);
if (diff_time.tv_sec > (time_t)max_checkint)
condlog(1, "path checkers took longer "
- "than %lu seconds, consider "
+ "than %ld seconds, consider "
"increasing max_polling_interval",
- diff_time.tv_sec);
+ (long)diff_time.tv_sec);
}
}
} else
diff_time.tv_sec = 1;
- condlog(3, "waiting for %lu.%06lu secs",
- diff_time.tv_sec,
+ condlog(3, "waiting for %ld.%06lu secs",
+ (long)diff_time.tv_sec,
diff_time.tv_nsec / 1000);
if (nanosleep(&diff_time, NULL) != 0) {
condlog(3, "nanosleep failed with error %d",
goto fail;
}
+ if (should_exit())
+ goto fail;
+
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
vector_foreach_slot (vecs->pathvec, pp, i){
goto fail;
}
+ if (should_exit())
+ goto fail;
+
/*
* create new set of maps & push changed ones into dm
* In the first call, use FORCE_RELOAD_WEAK to avoid making
goto fail;
}
+ if (should_exit())
+ goto fail;
+
/*
* may need to remove some maps which are no longer relevant
* e.g., due to blacklist changes in conf file
goto fail;
}
- dm_lib_release();
+ if (should_exit())
+ goto fail;
sync_maps_state(mpvec);
vector_foreach_slot(mpvec, mpp, i){
}
/*
- * purge dm of old maps
+ * purge dm of old maps and save new set of maps formed by
+ * considering current path state
*/
remove_maps(vecs);
-
- /*
- * save new set of maps formed by considering current path state
- */
- vector_free(vecs->mpvec);
vecs->mpvec = mpvec;
/*
if (!conf)
return 1;
+ if (verbosity)
+ libmp_verbosity = verbosity;
+ setlogmask(LOG_UPTO(libmp_verbosity + 3));
+
/*
* free old map and path vectors ... they use old conf state
*/
delete_all_foreign();
reset_checker_classes();
- /* Re-read any timezone changes */
- tzset();
-
- dm_tgt_version(conf->version, TGT_MPATH);
- if (verbosity)
- conf->verbosity = verbosity;
if (bindings_read_only)
conf->bindings_read_only = bindings_read_only;
check_alias_settings(conf);
}
if (log_reset_sig) {
condlog(2, "reset log (signal)");
- if (logsink == 1)
+ if (logsink == LOGSINK_SYSLOG)
log_thread_reset();
}
reconfig_sig = 0;
condlog(0, "couldn't adjust oom score");
}
+static void cleanup_pidfile(void)
+{
+ if (pid_fd >= 0)
+ close(pid_fd);
+ condlog(3, "unlink pidfile");
+ unlink(DEFAULT_PIDFILE);
+}
+
+static void cleanup_conf(void) {
+ struct config *conf;
+
+ conf = rcu_dereference(multipath_conf);
+ if (!conf)
+ return;
+ rcu_assign_pointer(multipath_conf, NULL);
+ call_rcu(&conf->rcu, rcu_free_config);
+}
+
+static void cleanup_maps(struct vectors *vecs)
+{
+ int queue_without_daemon, i;
+ struct multipath *mpp;
+ struct config *conf;
+
+ conf = get_multipath_config();
+ queue_without_daemon = conf->queue_without_daemon;
+ put_multipath_config(conf);
+ if (queue_without_daemon == QUE_NO_DAEMON_OFF)
+ vector_foreach_slot(vecs->mpvec, mpp, i)
+ dm_queue_if_no_path(mpp->alias, 0);
+ remove_maps_and_stop_waiters(vecs);
+ vecs->mpvec = NULL;
+}
+
+static void cleanup_paths(struct vectors *vecs)
+{
+ free_pathvec(vecs->pathvec, FREE_PATHS);
+ vecs->pathvec = NULL;
+}
+
+static void cleanup_vecs(void)
+{
+ if (!gvecs)
+ return;
+ /*
+ * We can't take the vecs lock here, because exit() may
+ * have been called from the child() thread, holding the lock already.
+ * Anyway, by the time we get here, all threads that might access
+ * vecs should have been joined already (in cleanup_threads).
+ */
+ cleanup_maps(gvecs);
+ cleanup_paths(gvecs);
+ pthread_mutex_destroy(&gvecs->lock.mutex);
+ FREE(gvecs);
+}
+
+static void cleanup_threads(void)
+{
+ stop_io_err_stat_thread();
+
+ if (check_thr_started)
+ pthread_cancel(check_thr);
+ if (uevent_thr_started)
+ pthread_cancel(uevent_thr);
+ if (uxlsnr_thr_started)
+ pthread_cancel(uxlsnr_thr);
+ if (uevq_thr_started)
+ pthread_cancel(uevq_thr);
+ if (dmevent_thr_started)
+ pthread_cancel(dmevent_thr);
+
+ if (check_thr_started)
+ pthread_join(check_thr, NULL);
+ if (uevent_thr_started)
+ pthread_join(uevent_thr, NULL);
+ if (uxlsnr_thr_started)
+ pthread_join(uxlsnr_thr, NULL);
+ if (uevq_thr_started)
+ pthread_join(uevq_thr, NULL);
+ if (dmevent_thr_started)
+ pthread_join(dmevent_thr, NULL);
+
+ /*
+ * As all threads are joined now, and we're in DAEMON_SHUTDOWN
+ * state, no new waiter threads will be created any more.
+ */
+ pthread_attr_destroy(&waiter_attr);
+}
+
+/*
+ * Use a non-default call_rcu_data for child().
+ *
+ * We do this to avoid a memory leak from liburcu.
+ * liburcu never frees the default rcu handler (see comments on
+ * call_rcu_data_free() in urcu-call-rcu-impl.h), its thread
+ * can't be joined with pthread_join(), leaving a memory leak.
+ *
+ * Therefore we create our own, which can be destroyed and joined.
+ */
+static struct call_rcu_data *setup_rcu(void)
+{
+ struct call_rcu_data *crdp;
+
+ rcu_init();
+ rcu_register_thread();
+ crdp = create_call_rcu_data(0UL, -1);
+ if (crdp != NULL)
+ set_thread_call_rcu_data(crdp);
+ return crdp;
+}
+
+static struct call_rcu_data *mp_rcu_data;
+
+static void cleanup_rcu(void)
+{
+ pthread_t rcu_thread;
+
+ /* Wait for any pending RCU calls */
+ rcu_barrier();
+ if (mp_rcu_data != NULL) {
+ rcu_thread = get_call_rcu_thread(mp_rcu_data);
+ /* detach this thread from the RCU thread */
+ set_thread_call_rcu_data(NULL);
+ synchronize_rcu();
+ /* tell RCU thread to exit */
+ call_rcu_data_free(mp_rcu_data);
+ pthread_join(rcu_thread, NULL);
+ }
+ rcu_unregister_thread();
+}
+
+static void cleanup_child(void)
+{
+ cleanup_threads();
+ cleanup_vecs();
+ if (poll_dmevents)
+ cleanup_dmevent_waiter();
+
+ cleanup_pidfile();
+ if (logsink == LOGSINK_SYSLOG)
+ log_thread_stop();
+
+ cleanup_conf();
+
+#ifdef _DEBUG_
+ dbg_free_final(NULL);
+#endif
+}
+
+static int sd_notify_exit(int err)
+{
+#ifdef USE_SYSTEMD
+ char msg[24];
+
+ snprintf(msg, sizeof(msg), "ERRNO=%d", err);
+ sd_notify(0, msg);
+#endif
+ return err;
+}
+
static int
child (__attribute__((unused)) void *param)
{
- pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr;
pthread_attr_t log_attr, misc_attr, uevent_attr;
struct vectors * vecs;
- struct multipath * mpp;
- int i;
-#ifdef USE_SYSTEMD
- int startup_done = 0;
-#endif
int rc;
- int pid_fd = -1;
struct config *conf;
char *envp;
- int queue_without_daemon;
enum daemon_status state;
+ int exit_code = 1;
+ init_unwinder();
mlockall(MCL_CURRENT | MCL_FUTURE);
signal_init();
- rcu_init();
+ mp_rcu_data = setup_rcu();
+
+ if (atexit(cleanup_rcu) || atexit(cleanup_child))
+ fprintf(stderr, "failed to register cleanup handlers\n");
setup_thread_attr(&misc_attr, 64 * 1024, 0);
setup_thread_attr(&uevent_attr, DEFAULT_UEVENT_STACKSIZE * 1024, 0);
setup_thread_attr(&waiter_attr, 32 * 1024, 1);
- setup_thread_attr(&io_err_stat_attr, 32 * 1024, 0);
- if (logsink == 1) {
+ if (logsink == LOGSINK_SYSLOG) {
setup_thread_attr(&log_attr, 64 * 1024, 0);
log_thread_start(&log_attr);
pthread_attr_destroy(&log_attr);
pid_fd = pidfile_create(DEFAULT_PIDFILE, daemon_pid);
if (pid_fd < 0) {
condlog(1, "failed to create pidfile");
- if (logsink == 1)
- log_thread_stop();
exit(1);
}
condlog(2, "--------start up--------");
condlog(2, "read " DEFAULT_CONFIGFILE);
+ if (verbosity)
+ libmp_verbosity = verbosity;
conf = load_config(DEFAULT_CONFIGFILE);
- if (!conf)
+ if (verbosity)
+ libmp_verbosity = verbosity;
+ setlogmask(LOG_UPTO(libmp_verbosity + 3));
+
+ if (!conf) {
+ condlog(0, "failed to load configuration");
goto failed;
+ }
- if (verbosity)
- conf->verbosity = verbosity;
if (bindings_read_only)
conf->bindings_read_only = bindings_read_only;
uxsock_timeout = conf->uxsock_timeout;
if (poll_dmevents)
poll_dmevents = dmevent_poll_supported();
- setlogmask(LOG_UPTO(conf->verbosity + 3));
envp = getenv("LimitNOFILE");
condlog(0, "failed to create cli listener: %d", rc);
goto failed;
}
- else if (state != DAEMON_CONFIGURE) {
- condlog(0, "cli listener failed to start");
- goto failed;
+ else {
+ uxlsnr_thr_started = true;
+ if (state != DAEMON_CONFIGURE) {
+ condlog(0, "cli listener failed to start");
+ goto failed;
+ }
}
if (poll_dmevents) {
condlog(0, "failed to create dmevent waiter thread: %d",
rc);
goto failed;
- }
+ } else
+ dmevent_thr_started = true;
}
/*
if ((rc = pthread_create(&uevent_thr, &uevent_attr, ueventloop, udev))) {
condlog(0, "failed to create uevent thread: %d", rc);
goto failed;
- }
+ } else
+ uevent_thr_started = true;
pthread_attr_destroy(&uevent_attr);
/*
if ((rc = pthread_create(&check_thr, &misc_attr, checkerloop, vecs))) {
condlog(0,"failed to create checker loop thread: %d", rc);
goto failed;
- }
+ } else
+ check_thr_started = true;
if ((rc = pthread_create(&uevq_thr, &misc_attr, uevqloop, vecs))) {
condlog(0, "failed to create uevent dispatcher: %d", rc);
goto failed;
- }
+ } else
+ uevq_thr_started = true;
pthread_attr_destroy(&misc_attr);
while (1) {
}
lock_cleanup_pop(vecs->lock);
post_config_state(DAEMON_IDLE);
-#ifdef USE_SYSTEMD
- if (!startup_done) {
- sd_notify(0, "READY=1");
- startup_done = 1;
- }
-#endif
}
}
- lock(&vecs->lock);
- conf = get_multipath_config();
- queue_without_daemon = conf->queue_without_daemon;
- put_multipath_config(conf);
- if (queue_without_daemon == QUE_NO_DAEMON_OFF)
- vector_foreach_slot(vecs->mpvec, mpp, i)
- dm_queue_if_no_path(mpp->alias, 0);
- remove_maps_and_stop_waiters(vecs);
- unlock(&vecs->lock);
-
- pthread_cancel(check_thr);
- pthread_cancel(uevent_thr);
- pthread_cancel(uxlsnr_thr);
- pthread_cancel(uevq_thr);
- if (poll_dmevents)
- pthread_cancel(dmevent_thr);
-
- pthread_join(check_thr, NULL);
- pthread_join(uevent_thr, NULL);
- pthread_join(uxlsnr_thr, NULL);
- pthread_join(uevq_thr, NULL);
- if (poll_dmevents)
- pthread_join(dmevent_thr, NULL);
-
- stop_io_err_stat_thread();
-
- lock(&vecs->lock);
- free_pathvec(vecs->pathvec, FREE_PATHS);
- vecs->pathvec = NULL;
- unlock(&vecs->lock);
-
- pthread_mutex_destroy(&vecs->lock.mutex);
- FREE(vecs);
- vecs = NULL;
-
- cleanup_foreign();
- cleanup_checkers();
- cleanup_prio();
- if (poll_dmevents)
- cleanup_dmevent_waiter();
-
- dm_lib_release();
- dm_lib_exit();
-
- /* We're done here */
- condlog(3, "unlink pidfile");
- unlink(DEFAULT_PIDFILE);
-
- condlog(2, "--------shut down-------");
-
- if (logsink == 1)
- log_thread_stop();
-
- /*
- * Freeing config must be done after condlog() and dm_lib_exit(),
- * because logging functions like dlog() and dm_write_log()
- * reference the config.
- */
- conf = rcu_dereference(multipath_conf);
- rcu_assign_pointer(multipath_conf, NULL);
- call_rcu(&conf->rcu, rcu_free_config);
- udev_unref(udev);
- udev = NULL;
- pthread_attr_destroy(&waiter_attr);
- pthread_attr_destroy(&io_err_stat_attr);
-#ifdef _DEBUG_
- dbg_free_final(NULL);
-#endif
-
-#ifdef USE_SYSTEMD
- sd_notify(0, "ERRNO=0");
-#endif
- exit(0);
-
+ exit_code = 0;
failed:
-#ifdef USE_SYSTEMD
- sd_notify(0, "ERRNO=1");
-#endif
- if (pid_fd >= 0)
- close(pid_fd);
- exit(1);
+ condlog(2, "--------shut down-------");
+ /* All cleanup is done in the cleanup_child() exit handler */
+ return sd_notify_exit(exit_code);
}
static int
int err;
int foreground = 0;
struct config *conf;
+ char *opt_k_arg = NULL;
+ bool opt_k = false;
ANNOTATE_BENIGN_RACE_SIZED(&multipath_conf, sizeof(multipath_conf),
"Manipulated through RCU");
ANNOTATE_BENIGN_RACE_SIZED(&uxsock_timeout, sizeof(uxsock_timeout),
"Suppress complaints about this scalar variable");
- logsink = 1;
+ logsink = LOGSINK_SYSLOG;
if (getuid() != 0) {
fprintf(stderr, "need to be root\n");
pthread_cond_init_mono(&config_cond);
- udev = udev_new();
+ if (atexit(dm_lib_exit))
+ condlog(3, "failed to register exit handler for libdm");
+
+ libmultipath_init();
+ if (atexit(libmultipath_exit))
+ condlog(3, "failed to register exit handler for libmultipath");
libmp_udev_set_sync_support(0);
while ((arg = getopt(argc, argv, ":dsv:k::Bniw")) != EOF ) {
switch(arg) {
case 'd':
foreground = 1;
- if (logsink > 0)
- logsink = 0;
- //debug=1; /* ### comment me out ### */
+ if (logsink == LOGSINK_SYSLOG)
+ logsink = LOGSINK_STDERR_WITH_TIME;
break;
case 'v':
if (sizeof(optarg) > sizeof(char *) ||
!isdigit(optarg[0]))
exit(1);
- verbosity = atoi(optarg);
+ libmp_verbosity = verbosity = atoi(optarg);
break;
case 's':
- logsink = -1;
+ logsink = LOGSINK_STDERR_WITHOUT_TIME;
break;
case 'k':
- logsink = 0;
- conf = load_config(DEFAULT_CONFIGFILE);
- if (!conf)
- exit(1);
- if (verbosity)
- conf->verbosity = verbosity;
- uxsock_timeout = conf->uxsock_timeout;
- err = uxclnt(optarg, uxsock_timeout + 100);
- free_config(conf);
- return err;
+ opt_k = true;
+ opt_k_arg = optarg;
+ break;
case 'B':
bindings_read_only = 1;
break;
exit(1);
}
}
- if (optind < argc) {
+ if (opt_k || optind < argc) {
char cmd[CMDSIZE];
char * s = cmd;
char * c = s;
- logsink = 0;
+ logsink = LOGSINK_STDERR_WITH_TIME;
+ if (verbosity)
+ libmp_verbosity = verbosity;
conf = load_config(DEFAULT_CONFIGFILE);
if (!conf)
exit(1);
if (verbosity)
- conf->verbosity = verbosity;
+ libmp_verbosity = verbosity;
uxsock_timeout = conf->uxsock_timeout;
memset(cmd, 0x0, CMDSIZE);
- while (optind < argc) {
- if (strchr(argv[optind], ' '))
- c += snprintf(c, s + CMDSIZE - c, "\"%s\" ", argv[optind]);
- else
- c += snprintf(c, s + CMDSIZE - c, "%s ", argv[optind]);
- optind++;
+ if (opt_k)
+ s = opt_k_arg;
+ else {
+ while (optind < argc) {
+ if (strchr(argv[optind], ' '))
+ c += snprintf(c, s + CMDSIZE - c,
+ "\"%s\" ", argv[optind]);
+ else
+ c += snprintf(c, s + CMDSIZE - c,
+ "%s ", argv[optind]);
+ optind++;
+ }
+ c += snprintf(c, s + CMDSIZE - c, "\n");
}
- c += snprintf(c, s + CMDSIZE - c, "\n");
err = uxclnt(s, uxsock_timeout + 100);
free_config(conf);
return err;
int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs,
int refresh);
+void handle_path_wwid_change(struct path *pp, struct vectors *vecs);
+bool check_path_wwid_change(struct path *pp);
#endif /* MAIN_H */
Description=Device-Mapper Multipath Device Controller
Wants=systemd-udev-trigger.service systemd-udev-settle.service
Before=iscsi.service iscsid.service lvm2-activation-early.service
-Before=local-fs-pre.target blk-availability.service
+Before=local-fs-pre.target blk-availability.service shutdown.target
After=multipathd.socket systemd-udev-trigger.service systemd-udev-settle.service
DefaultDependencies=no
Conflicts=shutdown.target
#include "config.h"
#include "mpath_cmd.h"
#include "time-util.h"
+#include "util.h"
#include "main.h"
#include "cli.h"
#include "uxlsnr.h"
-static struct timespec sleep_time = {5, 0};
-
struct client {
struct list_head node;
int fd;
};
-#define MIN_POLLS 1023
+/* The number of fds we poll on, other than individual client connections */
+#define POLLFDS_BASE 2
+#define POLLFD_CHUNK (4096 / sizeof(struct pollfd))
+/* Minimum mumber of pollfds to reserve for clients */
+#define MIN_POLLS (POLLFD_CHUNK - POLLFDS_BASE)
+/*
+ * Max number of client connections allowed
+ * During coldplug, there may be a large number of "multipath -u"
+ * processes connecting.
+ */
+#define MAX_CLIENTS (16384 - POLLFDS_BASE)
+
+/* Compile-time error if POLLFD_CHUNK is too small */
+static __attribute__((unused)) char ___a[-(MIN_POLLS <= 0)];
static LIST_HEAD(clients);
static pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER;
static void dead_client(struct client *c)
{
- pthread_cleanup_push(cleanup_lock, &client_lock);
+ pthread_cleanup_push(cleanup_mutex, &client_lock);
pthread_mutex_lock(&client_lock);
_dead_client(c);
pthread_cleanup_pop(1);
diff_time.tv_nsec / (1000 * 1000);
if (msecs > timeout)
condlog(2, "cli cmd '%s' timeout reached "
- "after %lu.%06lu secs", inbuf,
- diff_time.tv_sec, diff_time.tv_nsec / 1000);
+ "after %ld.%06lu secs", inbuf,
+ (long)diff_time.tv_sec, diff_time.tv_nsec / 1000);
}
}
char *inbuf;
char *reply;
sigset_t mask;
- int old_clients = MIN_POLLS;
+ 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 };
condlog(3, "uxsock: startup listener");
- polls = (struct pollfd *)MALLOC((MIN_POLLS + 2) * sizeof(struct pollfd));
+ polls = MALLOC(max_pfds * sizeof(*polls));
if (!polls) {
condlog(0, "uxsock: failed to allocate poll fds");
exit_daemon();
sigdelset(&mask, SIGUSR1);
while (1) {
struct client *c, *tmp;
- int i, poll_count, num_clients;
+ int i, n_pfds, poll_count, num_clients;
/* setup for a poll */
pthread_mutex_lock(&client_lock);
+ pthread_cleanup_push(cleanup_mutex, &client_lock);
num_clients = 0;
list_for_each_entry(c, &clients, node) {
num_clients++;
}
- if (num_clients != old_clients) {
+ if (num_clients + POLLFDS_BASE > max_pfds) {
struct pollfd *new;
- if (num_clients <= MIN_POLLS && old_clients > MIN_POLLS) {
- new = REALLOC(polls, (2 + MIN_POLLS) *
- sizeof(struct pollfd));
- } else if (num_clients <= MIN_POLLS && old_clients <= MIN_POLLS) {
- new = polls;
+ int n_new = max_pfds + POLLFD_CHUNK;
+
+ new = REALLOC(polls, n_new * sizeof(*polls));
+ if (new) {
+ max_pfds = n_new;
+ polls = new;
} else {
- new = REALLOC(polls, (2 + num_clients) *
- sizeof(struct pollfd));
+ condlog(1, "%s: realloc failure, %d clients not served",
+ __func__,
+ num_clients + POLLFDS_BASE - max_pfds);
+ num_clients = max_pfds - POLLFDS_BASE;
}
- if (!new) {
- pthread_mutex_unlock(&client_lock);
- condlog(0, "%s: failed to realloc %d poll fds",
- "uxsock", 2 + num_clients);
- sched_yield();
- continue;
- }
- old_clients = num_clients;
- polls = new;
}
- polls[0].fd = ux_sock;
- polls[0].events = POLLIN;
+ if (num_clients < MAX_CLIENTS) {
+ polls[0].fd = ux_sock;
+ polls[0].events = POLLIN;
+ } else {
+ /*
+ * New clients can't connect, num_clients won't grow
+ * to MAX_CLIENTS or higher
+ */
+ condlog(1, "%s: max client connections reached, pausing polling",
+ __func__);
+ polls[0].fd = -1;
+ }
reset_watch(notify_fd, &wds, &sequence_nr);
if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1))
polls[1].events = POLLIN;
/* setup the clients */
- i = 2;
+ i = POLLFDS_BASE;
list_for_each_entry(c, &clients, node) {
polls[i].fd = c->fd;
polls[i].events = POLLIN;
i++;
+ if (i >= max_pfds)
+ break;
}
- pthread_mutex_unlock(&client_lock);
+ n_pfds = i;
+ pthread_cleanup_pop(1);
/* most of our life is spent in this call */
- poll_count = ppoll(polls, i, &sleep_time, &mask);
+ poll_count = ppoll(polls, n_pfds, NULL, &mask);
handle_signals(false);
if (poll_count == -1) {
}
/* see if a client wants to speak to us */
- for (i = 2; i < num_clients + 2; i++) {
+ for (i = POLLFDS_BASE; i < n_pfds; i++) {
if (polls[i].revents & POLLIN) {
struct timespec start_time;
pthread_sigmask(SIG_UNBLOCK, &set, &oldset);
pthread_testcancel();
- r = dm_task_run(waiter->dmt);
+ r = libmp_dm_task_run(waiter->dmt);
if (!r)
dm_log_error(2, DM_DEVICE_WAITEVENT, waiter->dmt);
pthread_testcancel();
CFLAGS += $(BIN_CFLAGS) -I$(multipathdir) -I$(mpathcmddir) \
-Wno-unused-parameter $(W_MISSING_INITIALIZERS)
-LIBDEPS += -L$(multipathdir) -L$(mpathcmddir) -lmultipath -lmpathcmd -lcmocka
+LIBDEPS += -L. -L$(mpathcmddir) -lmultipath -lmpathcmd -lcmocka
TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \
- alias directio valid devt
+ alias directio valid devt mpathvalid
HELPERS := test-lib.o test-log.o
.SILENT: $(TESTS:%=%.o)
.PRECIOUS: $(TESTS:%=%-test)
all: $(TESTS:%=%.out)
+progs: $(TESTS:%=%-test) lib/libchecktur.so
valgrind: $(TESTS:%=%.vgr)
# test-specific compiler flags
ifneq ($(DIO_TEST_DEV),)
directio-test_FLAGS := -DDIO_TEST_DEV=\"$(DIO_TEST_DEV)\"
endif
+mpathvalid-test_FLAGS := -I$(mpathvaliddir)
# test-specific linker flags
# XYZ-test_TESTDEPS: test libraries containing __wrap_xyz functions
# linker input file).
# XYZ-test_LIBDEPS: Additional libs to link for this test
+dmevents-test_OBJDEPS = ../libmultipath/devmapper.o
dmevents-test_LIBDEPS = -lpthread -ldevmapper -lurcu
hwtable-test_TESTDEPS := test-lib.o
hwtable-test_OBJDEPS := ../libmultipath/discovery.o ../libmultipath/blacklist.o \
- ../libmultipath/structs.o
+ ../libmultipath/structs.o ../libmultipath/propsel.o
hwtable-test_LIBDEPS := -ludev -lpthread -ldl
blacklist-test_TESTDEPS := test-log.o
blacklist-test_OBJDEPS := ../libmultipath/blacklist.o
vpd-test_LIBDEPS := -ludev -lpthread -ldl
alias-test_TESTDEPS := test-log.o
alias-test_LIBDEPS := -lpthread -ldl
-valid-test_OBJDEPS := ../libmultipath/valid.o
+valid-test_OBJDEPS := ../libmultipath/valid.o ../libmultipath/discovery.o
valid-test_LIBDEPS := -ludev -lpthread -ldl
devt-test_LIBDEPS := -ludev
+mpathvalid-test_LIBDEPS := -ludev -lpthread -ldl
+mpathvalid-test_OBJDEPS := ../libmpathvalid/mpath_valid.o
ifneq ($(DIO_TEST_DEV),)
directio-test_LIBDEPS := -laio
endif
%.out: %-test lib/libchecktur.so
@echo == running $< ==
- @LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) ./$< >$@
+ @LD_LIBRARY_PATH=.:$(mpathcmddir) ./$< >$@
%.vgr: %-test lib/libchecktur.so
@echo == running valgrind for $< ==
- @LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) \
+ @LD_LIBRARY_PATH=.:$(mpathcmddir) \
valgrind --leak-check=full --error-exitcode=128 ./$< >$@ 2>&1
OBJS = $(TESTS:%=%.o) $(HELPERS)
test_clean:
- $(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr)
+ $(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) *.so*
valgrind_clean:
$(RM) $(TESTS:%=%.vgr)
@sed -n 's/^.*__wrap_\([a-zA-Z0-9_]*\).*$$/-Wl,--wrap=\1/p' $< | \
sort -u | tr '\n' ' ' >$@
+libmultipath.so.0:
+ $(MAKE) -C $(multipathdir) test-lib
# COLON will get expanded during second expansion below
COLON:=:
.SECONDEXPANSION:
%-test: %.o %.o.wrap $$($$@_OBJDEPS) $$($$@_TESTDEPS) $$($$@_TESTDEPS$$(COLON).o=.o.wrap) \
- $(multipathdir)/libmultipath.so Makefile
+ libmultipath.so.0 Makefile
$(CC) $(CFLAGS) -o $@ $(LDFLAGS) $< $($@_TESTDEPS) $($@_OBJDEPS) \
$(LIBDEPS) $($@_LIBDEPS) \
$(shell cat $<.wrap) $(foreach dep,$($@_TESTDEPS),$(shell cat $(dep).wrap))
output of the test run, including valgrind output, is stored as
`<testname>.vgr`.
+## Controlling verbosity for unit tests
+
+Some test programs use the environment variable `MPATHTEST_VERBOSITY` to
+control the log level during test execution.
+
## Notes on individual tests
### Tests that require root permissions
+#include <pthread.h>
#include <stdint.h>
#include <setjmp.h>
#include <stdio.h>
return __set_errno(mock_type(int));
}
+int __wrap_dm_map_present(const char * str)
+{
+ check_expected(str);
+ return mock_type(int);
+}
+
+int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len)
+{
+ int ret;
+
+ check_expected(name);
+ check_expected(uuid_len);
+ assert_non_null(uuid);
+ ret = mock_type(int);
+ if (ret == 0)
+ strcpy(uuid, mock_ptr_type(char *));
+ return ret;
+}
+
static void fd_mpatha(void **state)
{
char buf[32];
return cmocka_run_group_tests(tests, NULL, NULL);
}
+static void mock_unused_alias(const char *alias)
+{
+ expect_string(__wrap_dm_map_present, str, alias);
+ will_return(__wrap_dm_map_present, 0);
+}
+
+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"
+
+static void mock_failed_alias(const char *alias, char *msg)
+{
+ 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);
+}
+
+static void mock_used_alias(const char *alias, char *msg)
+{
+ 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);
+}
+
static void lb_empty(void **state)
{
int rc;
will_return(__wrap_fgets, NULL);
expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n");
- rc = lookup_binding(NULL, "WWID0", &alias, NULL);
+ rc = lookup_binding(NULL, "WWID0", &alias, NULL, 0);
+ assert_int_equal(rc, 1);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_empty_unused(void **state)
+{
+ int rc;
+ char *alias;
+
+ will_return(__wrap_fgets, NULL);
+ mock_unused_alias("MPATHa");
+ expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n");
+ rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1);
assert_int_equal(rc, 1);
assert_ptr_equal(alias, NULL);
+ free(alias);
+}
+
+static void lb_empty_failed(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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");
+ rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1);
+ assert_int_equal(rc, 2);
+ assert_ptr_equal(alias, NULL);
+ free(alias);
+}
+
+static void lb_empty_1_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ will_return(__wrap_fgets, NULL);
+ mock_used_alias("MPATHa", USED_STR("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);
+ free(alias);
+}
+
+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_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);
+ free(alias);
}
static void lb_match_a(void **state)
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");
+ rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 0);
assert_int_equal(rc, 0);
assert_ptr_not_equal(alias, NULL);
assert_string_equal(alias, "MPATHa");
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");
+ rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 0);
assert_int_equal(rc, 2);
assert_ptr_equal(alias, NULL);
}
-static void lb_match_c(void **state)
+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");
+ rc = lookup_binding(NULL, "WWID1", &alias, NULL, 1);
+ assert_int_equal(rc, -1);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_nomatch_a_unused(void **state)
+{
+ int rc;
+ char *alias;
+
+ will_return(__wrap_fgets, "MPATHa WWID0\n");
+ will_return(__wrap_fgets, NULL);
+ mock_unused_alias("MPATHb");
+ expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n");
+ rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1);
+ assert_int_equal(rc, 2);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_nomatch_a_3_used_failed_self(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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");
+ rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1);
+ assert_int_equal(rc, 6);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void do_lb_match_c(void **state, int check_if_taken)
{
int rc;
char *alias;
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");
+ rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", check_if_taken);
assert_int_equal(rc, 0);
assert_ptr_not_equal(alias, NULL);
assert_string_equal(alias, "MPATHc");
free(alias);
}
+static void lb_match_c(void **state)
+{
+ do_lb_match_c(state, 0);
+}
+
+static void lb_match_c_check(void **state)
+{
+ do_lb_match_c(state, 1);
+}
+
static void lb_nomatch_a_c(void **state)
{
int rc;
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
+ assert_int_equal(rc, 2);
+ assert_ptr_equal(alias, NULL);
+}
+
+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_unused_alias("MPATHb");
+ expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1);
assert_int_equal(rc, 2);
assert_ptr_equal(alias, NULL);
}
+static void lb_nomatch_a_d_1_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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_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);
+}
+
+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_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);
+}
+
+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_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);
+}
+
static void lb_nomatch_c_a(void **state)
{
int rc;
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
+ assert_int_equal(rc, 2);
+ assert_ptr_equal(alias, NULL);
+}
+
+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_unused_alias("MPATHb");
+ expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1);
assert_int_equal(rc, 2);
assert_ptr_equal(alias, NULL);
}
+static void lb_nomatch_d_a_1_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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_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);
+}
+
static void lb_nomatch_a_b(void **state)
{
int rc;
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
assert_int_equal(rc, 3);
assert_ptr_equal(alias, NULL);
}
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
+ assert_int_equal(rc, 3);
+ assert_ptr_equal(alias, NULL);
+}
+
+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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1);
assert_int_equal(rc, 3);
assert_ptr_equal(alias, NULL);
}
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
assert_int_equal(rc, 27);
assert_ptr_equal(alias, NULL);
}
+static void lb_nomatch_b_a_3_used(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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1);
+ assert_int_equal(rc, 30);
+ assert_ptr_equal(alias, NULL);
+}
+
#ifdef MPATH_ID_INT_MAX
-static void lb_nomatch_int_max(void **state)
+static void do_lb_nomatch_int_max(void **state, int check_if_taken)
{
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");
- rc = lookup_binding(NULL, "WWID2", &alias, "MPATH");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", check_if_taken);
+ assert_int_equal(rc, -1);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_nomatch_int_max(void **state)
+{
+ do_lb_nomatch_int_max(state, 0);
+}
+
+static void lb_nomatch_int_max_check(void **state)
+{
+ do_lb_nomatch_int_max(state, 1);
+}
+
+static void lb_nomatch_int_max_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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);
assert_ptr_equal(alias, NULL);
}
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");
+ rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0);
+ assert_int_equal(rc, INT_MAX);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_nomatch_int_max_m1_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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);
+ assert_ptr_equal(alias, NULL);
+}
+
+static void lb_nomatch_int_max_m1_1_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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);
assert_ptr_equal(alias, NULL);
}
+
+static void lb_nomatch_int_max_m1_2_used(void **state)
+{
+ int rc;
+ char *alias;
+
+ 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);
+ assert_ptr_equal(alias, NULL);
+}
#endif
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),
#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),
#endif
};
int main(void)
{
int ret = 0;
+ init_test_verbosity(3);
ret += test_format_devname();
ret += test_scan_devname();
#include "globals.c"
#include "blacklist.h"
#include "test-log.h"
+#include "debug.h"
struct udev_device {
const char *sysname;
store_ble(blist_property_wwn_inv, "!ID_WWN", ORIGIN_CONFIG))
return -1;
+ init_test_verbosity(4);
return 0;
}
#include <cmocka.h>
#include <libudev.h>
#include <sys/sysmacros.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <dirent.h>
#include "util.h"
#include "debug.h"
#include "globals.c"
+static bool sys_dev_block_exists(void)
+{
+ DIR *dir;
+ bool rc = false;
+
+ dir = opendir("/sys/dev/block");
+ if (dir != NULL) {
+ struct dirent *de;
+
+ while((de = readdir(dir)) != NULL) {
+ if (strcmp(de->d_name, ".") &&
+ strcmp(de->d_name, "..")) {
+ rc = true;
+ break;
+ }
+ }
+ closedir(dir);
+ }
+ return rc;
+}
+
static int get_one_devt(char *devt, size_t len)
{
struct udev_enumerate *enm;
{
char dummy[BLK_DEV_SIZE];
+ if (!sys_dev_block_exists())
+ skip();
assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 0);
}
struct udev_list_entry *first, *item;
unsigned int i = 0;
+ if (!sys_dev_block_exists())
+ skip();
enm = udev_enumerate_new(udev);
assert_non_null(enm);
r = udev_enumerate_add_match_subsystem(enm, "block");
{
int ret = 0;
+ init_test_verbosity(-1);
ret += devt2devname_tests();
return ret;
}
{
int ret = 0;
- conf.verbosity = 2;
+ init_test_verbosity(2);
ret += test_directio();
return ret;
}
*
*/
+#include <pthread.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
return names;
}
+static bool setup_done;
+
static int setup(void **state)
{
if (dmevent_poll_supported()) {
*state = &data;
} else
*state = NULL;
+ setup_done = true;
return 0;
}
return mock_type(struct dm_task *);
}
+int __real_dm_task_no_open_count(struct dm_task *dmt);
int __wrap_dm_task_no_open_count(struct dm_task *dmt)
{
+ if (!setup_done)
+ return __real_dm_task_no_open_count(dmt);
assert_ptr_equal((struct test_data *)dmt, &data);
return mock_type(int);
}
+int __real_dm_task_run(struct dm_task *dmt);
int __wrap_dm_task_run(struct dm_task *dmt)
{
+ if (!setup_done)
+ return __real_dm_task_run(dmt);
assert_ptr_equal((struct test_data *)dmt, &data);
return mock_type(int);
}
return data.names;
}
+void __real_dm_task_destroy(struct dm_task *dmt);
void __wrap_dm_task_destroy(struct dm_task *dmt)
{
+ if (!setup_done)
+ return __real_dm_task_destroy(dmt);
assert_ptr_equal((struct test_data *)dmt, &data);
if (data.names) {
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_dmevents();
return ret;
}
+#include <stdlib.h>
+#include <string.h>
+
+#include "defaults.h"
#include "structs.h"
#include "config.h"
+#include "debug.h"
-/* Required globals */
-struct udev *udev;
-int logsink = -1;
-struct config conf = {
- .verbosity = 4,
-};
+struct config conf;
struct config *get_multipath_config(void)
{
void put_multipath_config(void *arg)
{}
+
+static __attribute__((unused)) void init_test_verbosity(int test_verbosity)
+{
+ char *verb = getenv("MPATHTEST_VERBOSITY");
+
+ libmp_verbosity = test_verbosity >= 0 ? test_verbosity :
+ DEFAULT_VERBOSITY;
+ if (verb && *verb) {
+ char *c;
+ int vb;
+
+ vb = strtoul(verb, &c, 10);
+ if (!*c && vb >= 0 && vb <= 5)
+ libmp_verbosity = vb;
+ }
+}
#define N_CONF_FILES 2
static const char tmplate[] = "/tmp/hwtable-XXXXXX";
-/* pretend new dm, use minio_rq */
-static const unsigned int dm_tgt_version[3] = { 1, 1, 1 };
struct key_value {
const char *key;
static struct config *_conf;
struct udev *udev;
-int logsink = -1;
+int logsink = LOGSINK_STDERR_WITHOUT_TIME;
struct config *get_multipath_config(void)
{
assert_ptr_not_equal(__cf, NULL); \
assert_ptr_not_equal(__cf->hwtable, NULL); \
__cf->verbosity = VERBOSITY; \
- memcpy(&__cf->version, dm_tgt_version, sizeof(__cf->version)); \
__cf; })
#define FREE_CONFIG(conf) do { \
{
int ret = 0;
+ /* We can't use init_test_verbosity in this test */
+ libmp_verbosity = VERBOSITY;
ret += test_hwtable();
return ret;
}
--- /dev/null
+/*
+ * Copyright (c) 2020 Benjamin Marzinski, Red Hat
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <libudev.h>
+#include <cmocka.h>
+#include "structs.h"
+#include "config.h"
+#include "mpath_valid.h"
+#include "util.h"
+#include "debug.h"
+
+const char *test_dev = "test_name";
+#define TEST_WWID "WWID_123"
+#define CONF_TEMPLATE "mpathvalid-testconf-XXXXXXXX"
+char conf_name[] = CONF_TEMPLATE;
+bool initialized;
+
+#if 0
+static int mode_to_findmp(unsigned int mode)
+{
+ switch (mode) {
+ case MPATH_SMART:
+ return FIND_MULTIPATHS_SMART;
+ case MPATH_GREEDY:
+ return FIND_MULTIPATHS_GREEDY;
+ case MPATH_STRICT:
+ return FIND_MULTIPATHS_STRICT;
+ }
+ fail_msg("invalid mode: %u", mode);
+ return FIND_MULTIPATHS_UNDEF;
+}
+#endif
+
+static unsigned int findmp_to_mode(int findmp)
+{
+ switch (findmp) {
+ case FIND_MULTIPATHS_SMART:
+ return MPATH_SMART;
+ case FIND_MULTIPATHS_GREEDY:
+ return MPATH_GREEDY;
+ case FIND_MULTIPATHS_STRICT:
+ case FIND_MULTIPATHS_OFF:
+ case FIND_MULTIPATHS_ON:
+ return MPATH_STRICT;
+ }
+ fail_msg("invalid find_multipaths value: %d", findmp);
+ return MPATH_DEFAULT;
+}
+
+int __wrap_is_path_valid(const char *name, struct config *conf, struct path *pp,
+ bool check_multipathd)
+{
+ int r = mock_type(int);
+ int findmp = mock_type(int);
+
+ assert_ptr_equal(name, test_dev);
+ assert_ptr_not_equal(conf, NULL);
+ assert_ptr_not_equal(pp, NULL);
+ assert_true(check_multipathd);
+
+ assert_int_equal(findmp, conf->find_multipaths);
+ if (r == MPATH_IS_ERROR || r == MPATH_IS_NOT_VALID)
+ return r;
+
+ strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE);
+ return r;
+}
+
+int __wrap_libmultipath_init(void)
+{
+ int r = mock_type(int);
+
+ assert_false(initialized);
+ if (r != 0)
+ return r;
+ initialized = true;
+ return 0;
+}
+
+void __wrap_libmultipath_exit(void)
+{
+ assert_true(initialized);
+ initialized = false;
+}
+
+int __wrap_dm_prereq(unsigned int *v)
+{
+ assert_ptr_not_equal(v, NULL);
+ return mock_type(int);
+}
+
+int __real_init_config(const char *file);
+
+int __wrap_init_config(const char *file)
+{
+ int r = mock_type(int);
+ struct config *conf;
+
+ assert_ptr_equal(file, DEFAULT_CONFIGFILE);
+ if (r != 0)
+ return r;
+
+ assert_string_not_equal(conf_name, CONF_TEMPLATE);
+ r = __real_init_config(conf_name);
+ conf = get_multipath_config();
+ assert_ptr_not_equal(conf, NULL);
+ assert_int_equal(conf->find_multipaths, mock_type(int));
+ return 0;
+}
+
+static const char * const find_multipaths_optvals[] = {
+ [FIND_MULTIPATHS_OFF] = "off",
+ [FIND_MULTIPATHS_ON] = "on",
+ [FIND_MULTIPATHS_STRICT] = "strict",
+ [FIND_MULTIPATHS_GREEDY] = "greedy",
+ [FIND_MULTIPATHS_SMART] = "smart",
+};
+
+void make_config_file(int findmp)
+{
+ int r, fd;
+ char buf[64];
+
+ assert_true(findmp > FIND_MULTIPATHS_UNDEF &&
+ findmp < __FIND_MULTIPATHS_LAST);
+
+ r = snprintf(buf, sizeof(buf), "defaults {\nfind_multipaths %s\n}\n",
+ find_multipaths_optvals[findmp]);
+ assert_true(r > 0 && (long unsigned int)r < sizeof(buf));
+
+ memcpy(conf_name, CONF_TEMPLATE, sizeof(conf_name));
+ fd = mkstemp(conf_name);
+ assert_true(fd >= 0);
+ assert_int_equal(safe_write(fd, buf, r), 0);
+ assert_int_equal(close(fd), 0);
+}
+
+int setup(void **state)
+{
+ initialized = false;
+ udev = udev_new();
+ if (udev == NULL)
+ return -1;
+ return 0;
+}
+
+int teardown(void **state)
+{
+ struct config *conf;
+ conf = get_multipath_config();
+ put_multipath_config(conf);
+ if (conf)
+ uninit_config();
+ if (strcmp(conf_name, CONF_TEMPLATE) != 0)
+ unlink(conf_name);
+ udev_unref(udev);
+ udev = NULL;
+ return 0;
+}
+
+static void check_config(bool valid_config)
+{
+ struct config *conf;
+
+ conf = get_multipath_config();
+ put_multipath_config(conf);
+ if (valid_config)
+ assert_ptr_not_equal(conf, NULL);
+}
+
+/* libmultipath_init fails */
+static void test_mpathvalid_init_bad1(void **state)
+{
+ will_return(__wrap_libmultipath_init, 1);
+ assert_int_equal(mpathvalid_init(MPATH_LOG_PRIO_DEBUG,
+ MPATH_LOG_STDERR), -1);
+ assert_false(initialized);
+ check_config(false);
+}
+
+/* init_config fails */
+static void test_mpathvalid_init_bad2(void **state)
+{
+ will_return(__wrap_libmultipath_init, 0);
+ will_return(__wrap_init_config, 1);
+ assert_int_equal(mpathvalid_init(MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR_TIMESTAMP), -1);
+ assert_false(initialized);
+ check_config(false);
+}
+
+/* dm_prereq fails */
+static void test_mpathvalid_init_bad3(void **state)
+{
+ make_config_file(FIND_MULTIPATHS_STRICT);
+ will_return(__wrap_libmultipath_init, 0);
+ will_return(__wrap_init_config, 0);
+ will_return(__wrap_init_config, FIND_MULTIPATHS_STRICT);
+ will_return(__wrap_dm_prereq, 1);
+ assert_int_equal(mpathvalid_init(MPATH_LOG_STDERR, MPATH_LOG_PRIO_ERR),
+ -1);
+ assert_false(initialized);
+ check_config(false);
+}
+
+static void check_mpathvalid_init(int findmp, int prio, int log_style)
+{
+ make_config_file(findmp);
+ will_return(__wrap_libmultipath_init, 0);
+ will_return(__wrap_init_config, 0);
+ will_return(__wrap_init_config, findmp);
+ will_return(__wrap_dm_prereq, 0);
+ assert_int_equal(mpathvalid_init(prio, log_style), 0);
+ assert_true(initialized);
+ check_config(true);
+ assert_int_equal(logsink, log_style);
+ assert_int_equal(libmp_verbosity, prio);
+ assert_int_equal(findmp_to_mode(findmp), mpathvalid_get_mode());
+}
+
+static void check_mpathvalid_exit(void)
+{
+ assert_int_equal(mpathvalid_exit(), 0);
+ assert_false(initialized);
+ check_config(false);
+}
+
+static void test_mpathvalid_init_good1(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR_TIMESTAMP);
+}
+
+static void test_mpathvalid_init_good2(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_STRICT, MPATH_LOG_PRIO_DEBUG,
+ MPATH_LOG_STDERR);
+}
+
+static void test_mpathvalid_init_good3(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_NOLOG,
+ MPATH_LOG_SYSLOG);
+}
+
+static void test_mpathvalid_exit(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ check_mpathvalid_exit();
+}
+
+/* fails if config hasn't been set */
+static void test_mpathvalid_get_mode_bad(void **state)
+{
+#if 1
+ assert_int_equal(mpathvalid_get_mode(), MPATH_MODE_ERROR);
+#else
+ assert_int_equal(mpathvalid_get_mode(), 1);
+#endif
+}
+
+/*fails if config hasn't been set */
+static void test_mpathvalid_reload_config_bad1(void **state)
+{
+#if 1
+ will_return(__wrap_init_config, 1);
+#endif
+ assert_int_equal(mpathvalid_reload_config(), -1);
+ check_config(false);
+}
+
+/* init_config fails */
+static void test_mpathvalid_reload_config_bad2(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_init_config, 1);
+ assert_int_equal(mpathvalid_reload_config(), -1);
+ check_config(false);
+ check_mpathvalid_exit();
+}
+
+static void check_mpathvalid_reload_config(int findmp)
+{
+ assert_string_not_equal(conf_name, CONF_TEMPLATE);
+ unlink(conf_name);
+ make_config_file(findmp);
+ will_return(__wrap_init_config, 0);
+ will_return(__wrap_init_config, findmp);
+ assert_int_equal(mpathvalid_reload_config(), 0);
+ check_config(true);
+ assert_int_equal(findmp_to_mode(findmp), mpathvalid_get_mode());
+}
+
+static void test_mpathvalid_reload_config_good(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ check_mpathvalid_reload_config(FIND_MULTIPATHS_ON);
+ check_mpathvalid_reload_config(FIND_MULTIPATHS_GREEDY);
+ check_mpathvalid_reload_config(FIND_MULTIPATHS_SMART);
+ check_mpathvalid_reload_config(FIND_MULTIPATHS_STRICT);
+ check_mpathvalid_exit();
+}
+
+/* NULL name */
+static void test_mpathvalid_is_path_bad1(void **state)
+{
+ assert_int_equal(mpathvalid_is_path(NULL, MPATH_STRICT, NULL, NULL, 0),
+ MPATH_IS_ERROR);
+}
+
+/* bad mode */
+static void test_mpathvalid_is_path_bad2(void **state)
+{
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_MODE_ERROR, NULL,
+ NULL, 0), MPATH_IS_ERROR);
+}
+
+/* NULL path_wwids and non-zero nr_paths */
+static void test_mpathvalid_is_path_bad3(void **state)
+{
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_MODE_ERROR, NULL,
+ NULL, 1), MPATH_IS_ERROR);
+}
+
+/*fails if config hasn't been set */
+static void test_mpathvalid_is_path_bad4(void **state)
+{
+#if 0
+ will_return(__wrap_is_path_valid, MPATH_IS_ERROR);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_STRICT);
+#endif
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_STRICT, NULL,
+ NULL, 0), MPATH_IS_ERROR);
+}
+
+/* is_path_valid fails */
+static void test_mpathvalid_is_path_bad5(void **state)
+{
+ check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_ERROR);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_GREEDY);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_GREEDY, NULL,
+ NULL, 0), MPATH_IS_ERROR);
+ check_mpathvalid_exit();
+}
+
+static void test_mpathvalid_is_path_good1(void **state)
+{
+ char *wwid;
+ check_mpathvalid_init(FIND_MULTIPATHS_STRICT, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_NOT_VALID);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_STRICT);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid,
+ NULL, 0), MPATH_IS_NOT_VALID);
+ assert_ptr_equal(wwid, NULL);
+ check_mpathvalid_exit();
+}
+
+static void test_mpathvalid_is_path_good2(void **state)
+{
+ const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" };
+ char *wwid;
+ check_mpathvalid_init(FIND_MULTIPATHS_ON, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_VALID);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_ON);
+ will_return(__wrap_is_path_valid, TEST_WWID);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid,
+ wwids, 4), MPATH_IS_VALID);
+ assert_string_equal(wwid, TEST_WWID);
+ free(wwid);
+}
+
+static void test_mpathvalid_is_path_good3(void **state)
+{
+ const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" };
+ char *wwid;
+ check_mpathvalid_init(FIND_MULTIPATHS_OFF, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_VALID);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART);
+ will_return(__wrap_is_path_valid, TEST_WWID);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_SMART, &wwid,
+ wwids, 4), MPATH_IS_VALID);
+ assert_string_equal(wwid, TEST_WWID);
+ free(wwid);
+}
+
+/* mabybe valid with no matching paths */
+static void test_mpathvalid_is_path_good4(void **state)
+{
+ const char *wwids[] = { "WWID_A", "WWID_B", "WWID_C", "WWID_D" };
+ char *wwid;
+ check_mpathvalid_init(FIND_MULTIPATHS_SMART, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_MAYBE_VALID);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART);
+ will_return(__wrap_is_path_valid, TEST_WWID);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid,
+ wwids, 4), MPATH_IS_MAYBE_VALID);
+ assert_string_equal(wwid, TEST_WWID);
+ free(wwid);
+}
+
+/* maybe valid with matching paths */
+static void test_mpathvalid_is_path_good5(void **state)
+{
+ const char *wwids[] = { "WWID_A", "WWID_B", TEST_WWID, "WWID_D" };
+ char *wwid;
+ check_mpathvalid_init(FIND_MULTIPATHS_SMART, MPATH_LOG_PRIO_ERR,
+ MPATH_LOG_STDERR);
+ will_return(__wrap_is_path_valid, MPATH_IS_MAYBE_VALID);
+ will_return(__wrap_is_path_valid, FIND_MULTIPATHS_SMART);
+ will_return(__wrap_is_path_valid, TEST_WWID);
+ assert_int_equal(mpathvalid_is_path(test_dev, MPATH_DEFAULT, &wwid,
+ wwids, 4), MPATH_IS_VALID);
+ assert_string_equal(wwid, TEST_WWID);
+ free(wwid);
+}
+
+#define setup_test(name) \
+ cmocka_unit_test_setup_teardown(name, setup, teardown)
+
+int test_mpathvalid(void)
+{
+ const struct CMUnitTest tests[] = {
+ setup_test(test_mpathvalid_init_bad1),
+ setup_test(test_mpathvalid_init_bad2),
+ setup_test(test_mpathvalid_init_bad3),
+ setup_test(test_mpathvalid_init_good1),
+ setup_test(test_mpathvalid_init_good2),
+ setup_test(test_mpathvalid_init_good3),
+ setup_test(test_mpathvalid_exit),
+ setup_test(test_mpathvalid_get_mode_bad),
+ setup_test(test_mpathvalid_reload_config_bad1),
+ setup_test(test_mpathvalid_reload_config_bad2),
+ setup_test(test_mpathvalid_reload_config_good),
+ setup_test(test_mpathvalid_is_path_bad1),
+ setup_test(test_mpathvalid_is_path_bad2),
+ setup_test(test_mpathvalid_is_path_bad3),
+ setup_test(test_mpathvalid_is_path_bad4),
+ setup_test(test_mpathvalid_is_path_bad5),
+ setup_test(test_mpathvalid_is_path_good1),
+ setup_test(test_mpathvalid_is_path_good2),
+ setup_test(test_mpathvalid_is_path_good3),
+ setup_test(test_mpathvalid_is_path_good4),
+ setup_test(test_mpathvalid_is_path_good5),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
+
+int main(void)
+{
+ int r = 0;
+
+ r += test_mpathvalid();
+ return r;
+}
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_config_parser();
return ret;
}
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_pgpolicies();
return ret;
}
return 0;
}
+int __wrap_libmp_get_version(int which, unsigned int version[3])
+{
+ unsigned int *vers = mock_ptr_type(unsigned int *);
+
+ condlog(4, "%s: %d", __func__, which);
+ memcpy(version, vers, 3 * sizeof(unsigned int));
+ return 0;
+}
+
struct udev_list_entry
*__wrap_udev_device_get_properties_list_entry(struct udev_device *ud)
{
} else
will_return(__wrap_udev_device_get_sysattr_value, "0");
- /* filter_property */
- will_return(__wrap_udev_device_get_sysname, mp->devnode);
- if (mp->flags & BL_BY_PROPERTY) {
- will_return(__wrap_udev_list_entry_get_name, "BAZ");
- return;
- } else
- will_return(__wrap_udev_list_entry_get_name,
- "SCSI_IDENT_LUN_NAA_EXT");
if (mask & DI_SYSFS)
mock_sysfs_pathinfo(mp);
+ if (mask & DI_BLACKLIST) {
+ will_return(__wrap_udev_device_get_sysname, mp->devnode);
+ if (mp->flags & BL_BY_PROPERTY) {
+ will_return(__wrap_udev_list_entry_get_name, "BAZ");
+ return;
+ } else
+ will_return(__wrap_udev_list_entry_get_name,
+ "SCSI_IDENT_LUN_NAA_EXT");
+ }
+
if (mp->flags & BL_BY_DEVICE &&
(mask & DI_BLACKLIST && mask & DI_SYSFS))
return;
struct multipath *mp;
struct config *conf;
struct mocked_path mop;
+ /* pretend new dm, use minio_rq, */
+ static const unsigned int fake_dm_tgt_version[3] = { 1, 1, 1 };
mocked_path_from_path(&mop, pp);
/* pathinfo() call in adopt_paths */
conf = get_multipath_config();
select_pgpolicy(conf, mp);
select_no_path_retry(conf, mp);
+ will_return(__wrap_libmp_get_version, fake_dm_tgt_version);
select_retain_hwhandler(conf, mp);
+ will_return(__wrap_libmp_get_version, fake_dm_tgt_version);
select_minio(conf, mp);
put_multipath_config(conf);
#include "log.h"
#include "test-log.h"
-__attribute__((format(printf, 3, 0)))
-void __wrap_dlog (int sink, int prio, const char * fmt, ...)
+__attribute__((format(printf, 2, 0)))
+void __wrap_dlog (int prio, const char * fmt, ...)
{
char buff[MAX_MSG_SIZE];
va_list ap;
#ifndef _TEST_LOG_H
#define _TEST_LOG_H
-void __wrap_dlog (int sink, int prio, const char * fmt, ...);
+__attribute__((format(printf, 2, 0)))
+void __wrap_dlog (int prio, const char * fmt, ...);
void expect_condlog(int prio, char *string);
#endif
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_uevent_get_XXX();
return ret;
}
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_unaligned();
return ret;
}
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_basenamecpy();
ret += test_bitmasks();
ret += test_strlcpy();
#include <stdlib.h>
#include <errno.h>
#include <cmocka.h>
+#include <sys/sysmacros.h>
+
#include "globals.c"
#include "util.h"
#include "discovery.h"
#include "wwids.h"
#include "blacklist.h"
+#include "foreign.h"
#include "valid.h"
+#define PATHINFO_REAL 9999
+
int test_fd;
struct udev_device {
int unused;
return NULL;
}
+/* For the "hidden" check in pathinfo() */
+const char *__wrap_udev_device_get_sysattr_value(struct udev_device *udev_device,
+ const char *sysattr)
+{
+ check_expected(sysattr);
+ return mock_ptr_type(char *);
+}
+
+/* For pathinfo() -> is_claimed_by_foreign() */
+int __wrap_add_foreign(struct udev_device *udev_device)
+{
+ return mock_type(int);
+}
+
+/* called from pathinfo() */
+int __wrap_filter_devnode(struct config *conf, const struct _vector *elist,
+ const char *vendor, const char * product, const char *dev)
+{
+ return mock_type(int);
+}
+
+/* called from pathinfo() */
+int __wrap_filter_device(const struct _vector *blist, const struct _vector *elist,
+ const char *vendor, const char * product, const char *dev)
+{
+ return mock_type(int);
+}
+
+/* for common_sysfs_pathinfo() */
+dev_t __wrap_udev_device_get_devnum(struct udev_device *ud)
+{
+ return mock_type(dev_t);
+}
+
+/* for common_sysfs_pathinfo() */
+int __wrap_sysfs_get_size(struct path *pp, unsigned long long * size)
+{
+ return mock_type(int);
+}
+
+/* called in pathinfo() before filter_property() */
+int __wrap_select_getuid(struct config *conf, struct path *pp)
+{
+ pp->uid_attribute = mock_ptr_type(char *);
+ return 0;
+}
+
+int __real_pathinfo(struct path *pp, struct config *conf, int mask);
+
int __wrap_pathinfo(struct path *pp, struct config *conf, int mask)
{
int ret = mock_type(int);
+
assert_string_equal(pp->dev, mock_ptr_type(char *));
assert_int_equal(mask, DI_SYSFS | DI_WWID | DI_BLACKLIST);
- if (ret == PATHINFO_OK) {
+ if (ret == PATHINFO_REAL) {
+ /* for test_filter_property() */
+ ret = __real_pathinfo(pp, conf, mask);
+ return ret;
+ } else if (ret == PATHINFO_OK) {
pp->uid_attribute = "ID_TEST";
strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE);
} else
STAGE_IS_MULTIPATHED,
STAGE_CHECK_MULTIPATHD,
STAGE_GET_UDEV_DEVICE,
+ STAGE_PATHINFO_REAL,
STAGE_PATHINFO,
STAGE_FILTER_PROPERTY,
STAGE_IS_FAILED,
name);
if (stage == STAGE_GET_UDEV_DEVICE)
return;
+ if (stage == STAGE_PATHINFO_REAL) {
+ /* special case for test_filter_property() */
+ will_return(__wrap_pathinfo, PATHINFO_REAL);
+ will_return(__wrap_pathinfo, name);
+ expect_string(__wrap_udev_device_get_sysattr_value,
+ sysattr, "hidden");
+ will_return(__wrap_udev_device_get_sysattr_value, NULL);
+ will_return(__wrap_add_foreign, FOREIGN_IGNORED);
+ will_return(__wrap_filter_devnode, MATCH_NOTHING);
+ will_return(__wrap_udev_device_get_devnum, makedev(259, 0));
+ will_return(__wrap_sysfs_get_size, 0);
+ will_return(__wrap_select_getuid, "ID_TEST");
+ return;
+ }
will_return(__wrap_pathinfo, PATHINFO_OK);
will_return(__wrap_pathinfo, name);
will_return(__wrap_pathinfo, wwid);
if (stage == STAGE_PATHINFO)
return;
- will_return(__wrap_filter_property, MATCH_PROPERTY_BLIST_EXCEPT);
if (stage == STAGE_FILTER_PROPERTY)
return;
will_return(__wrap_is_failed_wwid, WWID_IS_NOT_FAILED);
/* test blacklist property */
memset(&pp, 0, sizeof(pp));
conf.find_multipaths = FIND_MULTIPATHS_STRICT;
- setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO);
+ setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL);
will_return(__wrap_filter_property, MATCH_PROPERTY_BLIST);
assert_int_equal(is_path_valid(name, &conf, &pp, false),
PATH_IS_NOT_VALID);
assert_ptr_equal(pp.udev, &test_udev);
- assert_string_equal(pp.wwid, wwid);
+
/* test missing property */
memset(&pp, 0, sizeof(pp));
- setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO);
+ setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL);
will_return(__wrap_filter_property, MATCH_PROPERTY_BLIST_MISSING);
assert_int_equal(is_path_valid(name, &conf, &pp, false),
PATH_IS_NOT_VALID);
- /* test MATCH_NOTHING fail on is_failed_wwid */
+
+ /* test MATCH_NOTHING fail on filter_device */
memset(&pp, 0, sizeof(pp));
- setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO);
+ setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO_REAL);
will_return(__wrap_filter_property, MATCH_NOTHING);
- will_return(__wrap_is_failed_wwid, WWID_IS_FAILED);
- will_return(__wrap_is_failed_wwid, wwid);
+ will_return(__wrap_filter_device, MATCH_DEVICE_BLIST);
assert_int_equal(is_path_valid(name, &conf, &pp, false),
PATH_IS_NOT_VALID);
}
int main(void)
{
int ret = 0;
+
+ init_test_verbosity(-1);
ret += test_valid();
return ret;
}
{
int ret = 0;
+ init_test_verbosity(-1);
ret += test_vpd();
return ret;
}
--- /dev/null
+{
+ glibc _dlerror_run leak: https://stackoverflow.com/questions/1542457/memory-leak-reported-by-valgrind-in-dlopen
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:calloc
+ fun:_dlerror_run
+ fun:dlopen*
+}
+
+{
+ systemd mempools are never freed: https://bugzilla.redhat.com/show_bug.cgi?id=1215670
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:malloc
+ fun:mempool_alloc_tile
+ fun:mempool_alloc0_tile
+ fun:hashmap_base_new
+ fun:hashmap_base_ensure_allocated
+}
+
+{
+ libgcrypt library initialization
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:malloc
+ ...
+ fun:_gcry_xmalloc
+ ...
+ fun:global_init.*
+ ...
+ fun:_dl_init
+}