From: DongHun Kwak Date: Fri, 14 Jan 2022 04:50:20 +0000 (+0900) Subject: Imported Upstream version 0.8.6 X-Git-Tag: upstream/0.8.6 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Ftags%2Fupstream%2F0.8.6;p=platform%2Fupstream%2Fmultipath-tools.git Imported Upstream version 0.8.6 --- diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml new file mode 100644 index 0000000..7ff4584 --- /dev/null +++ b/.github/workflows/build-and-unittest.yaml @@ -0,0 +1,102 @@ +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 diff --git a/.github/workflows/foreign.yaml b/.github/workflows/foreign.yaml new file mode 100644 index 0000000..c164cb3 --- /dev/null +++ b/.github/workflows/foreign.yaml @@ -0,0 +1,66 @@ +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 diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml new file mode 100644 index 0000000..2bb1886 --- /dev/null +++ b/.github/workflows/native.yaml @@ -0,0 +1,32 @@ +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 + diff --git a/Makefile b/Makefile index 4a3491d..7f21db8 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ BUILDDIRS := \ libmultipath/checkers \ libmultipath/foreign \ libmpathpersist \ + libmpathvalid \ multipath \ multipathd \ mpathpersist \ @@ -29,7 +30,8 @@ $(BUILDDIRS): $(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 \ @@ -46,11 +48,14 @@ $(BUILDDIRS:=.uninstall): $(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 diff --git a/Makefile.inc b/Makefile.inc index e05f3a9..f1e2313 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -15,6 +15,8 @@ # Uncomment to disable dmevents polling support # ENABLE_DMEVENTS_POLL = 0 +PKGCONFIG ?= pkg-config + ifeq ($(TOPDIR),) TOPDIR = .. endif @@ -36,8 +38,8 @@ ifndef RUN 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 | \ @@ -66,6 +68,7 @@ libdir = $(prefix)/$(LIB)/multipath 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 @@ -104,7 +107,7 @@ CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe \ 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. diff --git a/README.alua b/README.alua index 340ccba..b15eb48 100644 --- a/README.alua +++ b/README.alua @@ -4,7 +4,7 @@ ALUA is supported in some devices, but usually it's disabled by default. To enable ALUA, the following options should be changed: - EMC CLARiiON/VNX: - "Failover Mode" should be changed to "4". + "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)". @@ -19,3 +19,6 @@ To enable ALUA, the following options should be changed: - NetApp ONTAP: To check ALUA state: "igroup show -v ", and to enable ALUA: "igroup set alua yes". + +- Huawei OceanStor: + "Host Access Mode" should be changed to "Asymmetric". diff --git a/README.md b/README.md index 3e3ef67..b15c265 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ +[![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 diff --git a/kpartx/kpartx.c b/kpartx/kpartx.c index 4a0aae9..8ff116b 100644 --- a/kpartx/kpartx.c +++ b/kpartx/kpartx.c @@ -424,7 +424,7 @@ main(int argc, char **argv){ fprintf(stderr, "can't del loop : %s\n", loopdev); r = 1; - } else + } else if (verbose) fprintf(stderr, "loop deleted : %s\n", loopdev); } goto end; @@ -668,20 +668,20 @@ main(int argc, char **argv){ 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; diff --git a/libdmmp/Makefile b/libdmmp/Makefile index 1dd3f34..764a0bc 100644 --- a/libdmmp/Makefile +++ b/libdmmp/Makefile @@ -16,17 +16,18 @@ HEADERS = libdmmp/libdmmp.h 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 \ @@ -40,11 +41,7 @@ install: $(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) @@ -58,7 +55,7 @@ uninstall: 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)) @@ -69,23 +66,19 @@ check: all 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) diff --git a/libdmmp/docs/man/dmmp_context_free.3 b/libdmmp/docs/man/dmmp_context_free.3 new file mode 100644 index 0000000..0d26f42 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_free.3 @@ -0,0 +1,15 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_context_log_func_set.3 b/libdmmp/docs/man/dmmp_context_log_func_set.3 new file mode 100644 index 0000000..986793d --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_log_func_set.3 @@ -0,0 +1,21 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_context_log_priority_get.3 b/libdmmp/docs/man/dmmp_context_log_priority_get.3 new file mode 100644 index 0000000..9a273a2 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_log_priority_get.3 @@ -0,0 +1,23 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_context_log_priority_set.3 b/libdmmp/docs/man/dmmp_context_log_priority_set.3 new file mode 100644 index 0000000..469c5a4 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_log_priority_set.3 @@ -0,0 +1,29 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_context_new.3 b/libdmmp/docs/man/dmmp_context_new.3 new file mode 100644 index 0000000..0eaeb00 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_new.3 @@ -0,0 +1,19 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_context_timeout_get.3 b/libdmmp/docs/man/dmmp_context_timeout_get.3 new file mode 100644 index 0000000..1df2793 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_timeout_get.3 @@ -0,0 +1,15 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_context_timeout_set.3 b/libdmmp/docs/man/dmmp_context_timeout_set.3 new file mode 100644 index 0000000..f3d7709 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_timeout_set.3 @@ -0,0 +1,19 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_context_userdata_get.3 b/libdmmp/docs/man/dmmp_context_userdata_get.3 new file mode 100644 index 0000000..fb713d5 --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_userdata_get.3 @@ -0,0 +1,15 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_context_userdata_set.3 b/libdmmp/docs/man/dmmp_context_userdata_set.3 new file mode 100644 index 0000000..c5bf63f --- /dev/null +++ b/libdmmp/docs/man/dmmp_context_userdata_set.3 @@ -0,0 +1,18 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_flush_mpath.3 b/libdmmp/docs/man/dmmp_flush_mpath.3 new file mode 100644 index 0000000..cdfd526 --- /dev/null +++ b/libdmmp/docs/man/dmmp_flush_mpath.3 @@ -0,0 +1,36 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_last_error_msg.3 b/libdmmp/docs/man/dmmp_last_error_msg.3 new file mode 100644 index 0000000..20acbc6 --- /dev/null +++ b/libdmmp/docs/man/dmmp_last_error_msg.3 @@ -0,0 +1,16 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_log_priority_str.3 b/libdmmp/docs/man/dmmp_log_priority_str.3 new file mode 100644 index 0000000..3b5f828 --- /dev/null +++ b/libdmmp/docs/man/dmmp_log_priority_str.3 @@ -0,0 +1,24 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_mpath_array_free.3 b/libdmmp/docs/man/dmmp_mpath_array_free.3 new file mode 100644 index 0000000..8c294e0 --- /dev/null +++ b/libdmmp/docs/man/dmmp_mpath_array_free.3 @@ -0,0 +1,18 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_mpath_array_get.3 b/libdmmp/docs/man/dmmp_mpath_array_get.3 new file mode 100644 index 0000000..e211db4 --- /dev/null +++ b/libdmmp/docs/man/dmmp_mpath_array_get.3 @@ -0,0 +1,36 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_mpath_kdev_name_get.3 b/libdmmp/docs/man/dmmp_mpath_kdev_name_get.3 new file mode 100644 index 0000000..e802fe6 --- /dev/null +++ b/libdmmp/docs/man/dmmp_mpath_kdev_name_get.3 @@ -0,0 +1,17 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_mpath_name_get.3 b/libdmmp/docs/man/dmmp_mpath_name_get.3 new file mode 100644 index 0000000..d70579e --- /dev/null +++ b/libdmmp/docs/man/dmmp_mpath_name_get.3 @@ -0,0 +1,18 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_mpath_wwid_get.3 b/libdmmp/docs/man/dmmp_mpath_wwid_get.3 new file mode 100644 index 0000000..3d060e9 --- /dev/null +++ b/libdmmp/docs/man/dmmp_mpath_wwid_get.3 @@ -0,0 +1,13 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_array_get.3 b/libdmmp/docs/man/dmmp_path_array_get.3 new file mode 100644 index 0000000..53340b3 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_array_get.3 @@ -0,0 +1,25 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_path_blk_name_get.3 b/libdmmp/docs/man/dmmp_path_blk_name_get.3 new file mode 100644 index 0000000..da5f9f0 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_blk_name_get.3 @@ -0,0 +1,17 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_group_array_get.3 b/libdmmp/docs/man/dmmp_path_group_array_get.3 new file mode 100644 index 0000000..6eee4a2 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_array_get.3 @@ -0,0 +1,27 @@ +.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 diff --git a/libdmmp/docs/man/dmmp_path_group_id_get.3 b/libdmmp/docs/man/dmmp_path_group_id_get.3 new file mode 100644 index 0000000..4f07b53 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_id_get.3 @@ -0,0 +1,18 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_group_priority_get.3 b/libdmmp/docs/man/dmmp_path_group_priority_get.3 new file mode 100644 index 0000000..a48b270 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_priority_get.3 @@ -0,0 +1,16 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_group_selector_get.3 b/libdmmp/docs/man/dmmp_path_group_selector_get.3 new file mode 100644 index 0000000..407b3f4 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_selector_get.3 @@ -0,0 +1,16 @@ +.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 *. diff --git a/libdmmp/docs/man/dmmp_path_group_status_get.3 b/libdmmp/docs/man/dmmp_path_group_status_get.3 new file mode 100644 index 0000000..a81aeb3 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_status_get.3 @@ -0,0 +1,23 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_group_status_str.3 b/libdmmp/docs/man/dmmp_path_group_status_str.3 new file mode 100644 index 0000000..e4a9f74 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_group_status_str.3 @@ -0,0 +1,26 @@ +.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" diff --git a/libdmmp/docs/man/dmmp_path_status_get.3 b/libdmmp/docs/man/dmmp_path_status_get.3 new file mode 100644 index 0000000..025cfee --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_status_get.3 @@ -0,0 +1,54 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_path_status_str.3 b/libdmmp/docs/man/dmmp_path_status_str.3 new file mode 100644 index 0000000..3944d39 --- /dev/null +++ b/libdmmp/docs/man/dmmp_path_status_str.3 @@ -0,0 +1,34 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_reconfig.3 b/libdmmp/docs/man/dmmp_reconfig.3 new file mode 100644 index 0000000..a743e30 --- /dev/null +++ b/libdmmp/docs/man/dmmp_reconfig.3 @@ -0,0 +1,27 @@ +.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. diff --git a/libdmmp/docs/man/dmmp_strerror.3 b/libdmmp/docs/man/dmmp_strerror.3 new file mode 100644 index 0000000..4d753d3 --- /dev/null +++ b/libdmmp/docs/man/dmmp_strerror.3 @@ -0,0 +1,33 @@ +.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. diff --git a/libdmmp/docs/libdmmp.h.3 b/libdmmp/docs/man/libdmmp.h.3 similarity index 100% rename from libdmmp/docs/libdmmp.h.3 rename to libdmmp/docs/man/libdmmp.h.3 diff --git a/libdmmp/test/libdmmp_speed_test.c b/libdmmp/test/libdmmp_speed_test.c index 372cd39..d91ba50 100644 --- a/libdmmp/test/libdmmp_speed_test.c +++ b/libdmmp/test/libdmmp_speed_test.c @@ -27,7 +27,7 @@ #include -int main(int argc, char *argv[]) +int main(void) { struct dmmp_context *ctx = NULL; struct dmmp_mpath **dmmp_mps = NULL; diff --git a/libdmmp/test/libdmmp_test.c b/libdmmp/test/libdmmp_test.c index d944e1e..a940b57 100644 --- a/libdmmp/test/libdmmp_test.c +++ b/libdmmp/test/libdmmp_test.c @@ -102,7 +102,7 @@ out: return rc; } -int main(int argc, char *argv[]) +int main(void) { struct dmmp_context *ctx = NULL; struct dmmp_mpath **dmmp_mps = NULL; diff --git a/libmpathcmd/Makefile b/libmpathcmd/Makefile index 0f6b816..2591019 100644 --- a/libmpathcmd/Makefile +++ b/libmpathcmd/Makefile @@ -3,18 +3,22 @@ include ../Makefile.inc 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) diff --git a/libmpathcmd/libmpathcmd.version b/libmpathcmd/libmpathcmd.version new file mode 100644 index 0000000..f100628 --- /dev/null +++ b/libmpathcmd/libmpathcmd.version @@ -0,0 +1,25 @@ +/* + * 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: + *; +}; diff --git a/libmpathpersist/Makefile b/libmpathpersist/Makefile index 21fdad8..57103e5 100644 --- a/libmpathpersist/Makefile +++ b/libmpathpersist/Makefile @@ -3,23 +3,29 @@ include ../Makefile.inc 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) diff --git a/libmpathpersist/libmpathpersist.version b/libmpathpersist/libmpathpersist.version new file mode 100644 index 0000000..e074813 --- /dev/null +++ b/libmpathpersist/libmpathpersist.version @@ -0,0 +1,38 @@ +/* + * 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; diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c index 1f9817e..190e970 100644 --- a/libmpathpersist/mpath_persist.c +++ b/libmpathpersist/mpath_persist.c @@ -37,6 +37,31 @@ 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) { @@ -47,21 +72,28 @@ 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; } @@ -101,72 +133,57 @@ mpath_prin_activepath (struct multipath *mpp, int rq_servact, 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; @@ -226,13 +243,13 @@ out: 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; @@ -241,8 +258,17 @@ int __mpath_persistent_reserve_in (int fd, int rq_servact, 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; @@ -250,7 +276,7 @@ int __mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, 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; @@ -261,9 +287,10 @@ int __mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, 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)) { @@ -275,7 +302,8 @@ int __mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, } 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; @@ -318,6 +346,45 @@ 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) { @@ -341,11 +408,12 @@ 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 ; } diff --git a/libmpathpersist/mpath_persist.h b/libmpathpersist/mpath_persist.h index 7cf4faf..9e9c0a8 100644 --- a/libmpathpersist/mpath_persist.h +++ b/libmpathpersist/mpath_persist.h @@ -175,6 +175,24 @@ struct prout_param_descriptor { /* PROUT parameter descriptor */ * 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. @@ -186,12 +204,25 @@ extern struct config * mpath_lib_init (void); * DESCRIPTION : * Release device mapper multipath configuration. This function must be invoked after * performing reservation management functions. + * Use this after initialization with mpath_lib_init(). * RESTRICTIONS: * * RETURNS: 0->Success, 1->Failed. */ 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 : @@ -215,9 +246,13 @@ extern int mpath_persistent_reserve_in (int fd, int rq_servact, struct prin_resp /* * 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); @@ -249,9 +284,13 @@ extern int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, 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, @@ -265,6 +304,7 @@ extern int __mpath_persistent_reserve_out( int fd, int rq_servact, int rq_scope, * @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. @@ -275,6 +315,9 @@ int mpath_persistent_reserve_init_vecs(int verbose); * DESCRIPTION : * This function frees data structures allocated by * mpath_persistent_reserve_init_vecs(). + * + * RESTRICTIONS: + * This function uses static internal variables, and is not thread-safe. */ void mpath_persistent_reserve_free_vecs(void); diff --git a/libmpathvalid/Makefile b/libmpathvalid/Makefile new file mode 100644 index 0000000..6bea4bc --- /dev/null +++ b/libmpathvalid/Makefile @@ -0,0 +1,39 @@ +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) diff --git a/libmpathvalid/libmpathvalid.version b/libmpathvalid/libmpathvalid.version new file mode 100644 index 0000000..3bd0d3c --- /dev/null +++ b/libmpathvalid/libmpathvalid.version @@ -0,0 +1,10 @@ +MPATH_1.0 { + global: + mpathvalid_init; + mpathvalid_reload_config; + mpathvalid_exit; + mpathvalid_is_path; + mpathvalid_get_mode; + local: + *; +}; diff --git a/libmpathvalid/mpath_valid.c b/libmpathvalid/mpath_valid.c new file mode 100644 index 0000000..7073d17 --- /dev/null +++ b/libmpathvalid/mpath_valid.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include + +#include "devmapper.h" +#include "structs.h" +#include "util.h" +#include "config.h" +#include "discovery.h" +#include "wwids.h" +#include "sysfs.h" +#include "mpath_cmd.h" +#include "valid.h" +#include "mpath_valid.h" +#include "debug.h" + +static unsigned int +get_conf_mode(struct config *conf) +{ + if (conf->find_multipaths == FIND_MULTIPATHS_SMART) + return MPATH_SMART; + if (conf->find_multipaths == FIND_MULTIPATHS_GREEDY) + return MPATH_GREEDY; + return MPATH_STRICT; +} + +static void +set_conf_mode(struct config *conf, unsigned int mode) +{ + if (mode == MPATH_SMART) + conf->find_multipaths = FIND_MULTIPATHS_SMART; + else if (mode == MPATH_GREEDY) + conf->find_multipaths = FIND_MULTIPATHS_GREEDY; + else + conf->find_multipaths = FIND_MULTIPATHS_STRICT; +} + +unsigned int +mpathvalid_get_mode(void) +{ + int mode; + struct config *conf; + + conf = get_multipath_config(); + if (!conf) + mode = MPATH_MODE_ERROR; + else + mode = get_conf_mode(conf); + put_multipath_config(conf); + return mode; +} + +static int +convert_result(int result) { + switch (result) { + case PATH_IS_ERROR: + return MPATH_IS_ERROR; + case PATH_IS_NOT_VALID: + return MPATH_IS_NOT_VALID; + case PATH_IS_VALID: + return MPATH_IS_VALID; + case PATH_IS_VALID_NO_CHECK: + return MPATH_IS_VALID_NO_CHECK; + case PATH_IS_MAYBE_VALID: + return MPATH_IS_MAYBE_VALID; + } + return MPATH_IS_ERROR; +} + +static void +set_log_style(int log_style) +{ + /* + * convert MPATH_LOG_* to LOGSINK_* + * currently there is no work to do here. + */ + logsink = log_style; +} + +static int +load_default_config(int verbosity) +{ + /* need to set verbosity here to control logging during init_config() */ + libmp_verbosity = verbosity; + if (init_config(DEFAULT_CONFIGFILE)) + return -1; + /* Need to override verbosity from init_config() */ + libmp_verbosity = verbosity; + + return 0; +} + +int +mpathvalid_init(int verbosity, int log_style) +{ + unsigned int version[3]; + + set_log_style(log_style); + if (libmultipath_init()) + return -1; + + skip_libmp_dm_init(); + if (load_default_config(verbosity)) + goto fail; + + if (dm_prereq(version)) + goto fail_config; + + return 0; + +fail_config: + uninit_config(); +fail: + libmultipath_exit(); + return -1; +} + +int +mpathvalid_reload_config(void) +{ + uninit_config(); + return load_default_config(libmp_verbosity); +} + +int +mpathvalid_exit(void) +{ + uninit_config(); + libmultipath_exit(); + return 0; +} + +/* + * name: name of path to check + * mode: mode to use for determination. MPATH_DEFAULT uses configured mode + * info: on success, contains the path wwid + * paths: array of the returned mpath_info from other claimed paths + * nr_paths: the size of the paths array + */ +int +mpathvalid_is_path(const char *name, unsigned int mode, char **wwid, + const char **path_wwids, unsigned int nr_paths) +{ + struct config *conf; + int find_multipaths_saved, r = MPATH_IS_ERROR; + unsigned int i; + struct path *pp; + + if (!name || mode >= MPATH_MODE_ERROR) + return r; + if (nr_paths > 0 && !path_wwids) + return r; + if (!udev) + return r; + + pp = alloc_path(); + if (!pp) + return r; + + if (wwid) { + *wwid = (char *)malloc(WWID_SIZE); + if (!*wwid) + goto out; + } + + conf = get_multipath_config(); + if (!conf) + goto out_wwid; + find_multipaths_saved = conf->find_multipaths; + if (mode != MPATH_DEFAULT) + set_conf_mode(conf, mode); + r = convert_result(is_path_valid(name, conf, pp, true)); + conf->find_multipaths = find_multipaths_saved; + put_multipath_config(conf); + + if (r == MPATH_IS_MAYBE_VALID) { + for (i = 0; i < nr_paths; i++) { + if (path_wwids[i] && + strncmp(path_wwids[i], pp->wwid, WWID_SIZE) == 0) { + r = MPATH_IS_VALID; + break; + } + } + } + +out_wwid: + if (wwid) { + if (r == MPATH_IS_VALID || r == MPATH_IS_VALID_NO_CHECK || + r == MPATH_IS_MAYBE_VALID) + strlcpy(*wwid, pp->wwid, WWID_SIZE); + else { + free(*wwid); + *wwid = NULL; + } + } +out: + free_path(pp); + return r; +} diff --git a/libmpathvalid/mpath_valid.h b/libmpathvalid/mpath_valid.h new file mode 100644 index 0000000..63de4e1 --- /dev/null +++ b/libmpathvalid/mpath_valid.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * This file is part of the device-mapper multipath userspace tools. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef 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 */ diff --git a/libmultipath/Makefile b/libmultipath/Makefile index 62ba16e..e7254f3 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -6,6 +6,7 @@ include ../Makefile.inc SONAME = 0 DEVLIB = libmultipath.so LIBS = $(DEVLIB).$(SONAME) +VERSION_SCRIPT := libmultipath.version CFLAGS += $(LIB_CFLAGS) -I$(mpathcmddir) -I$(mpathpersistdir) -I$(nvmedir) @@ -54,7 +55,7 @@ OBJS = memory.o parser.o vector.o devmapper.o callout.o \ 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 $@ $< @@ -72,11 +73,22 @@ nvme-ioctl.c: nvme/nvme-ioctl.c 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) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index a7ba485..02bc9d6 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -21,6 +21,7 @@ #include "config.h" #include "util.h" #include "errno.h" +#include "devmapper.h" /* @@ -119,6 +120,28 @@ scan_devname(const char *alias, const char *prefix) 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 @@ -128,7 +151,7 @@ scan_devname(const char *alias, const char *prefix) */ 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; @@ -183,12 +206,31 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, 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; @@ -331,7 +373,7 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, 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); @@ -388,7 +430,7 @@ get_user_friendly_alias(const char *wwid, const char *file, const char *prefix, return NULL; } - id = lookup_binding(f, wwid, &alias, prefix); + id = lookup_binding(f, wwid, &alias, prefix, 1); if (id < 0) { fclose(f); return NULL; diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index f7ddd53..2dd9915 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -3,10 +3,13 @@ #include #include #include +#include +#include #include "debug.h" #include "checkers.h" #include "vector.h" +#include "util.h" struct checker_class { struct list_head node; @@ -19,6 +22,7 @@ struct checker_class { 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; }; @@ -54,19 +58,32 @@ static struct checker_class *alloc_checker_class(void) 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); @@ -160,7 +177,8 @@ static struct checker_class *add_checker_class(const char *multipath_dir, 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(); @@ -346,6 +364,43 @@ bad_id: 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) @@ -370,12 +425,28 @@ void checker_get(const char *multipath_dir, struct checker *dst, 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; } diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 9d5f90b..2fd1d1c 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -1,6 +1,7 @@ #ifndef _CHECKERS_H #define _CHECKERS_H +#include #include "list.h" #include "memory.h" #include "defaults.h" @@ -148,6 +149,28 @@ void checker_set_async (struct checker *); void checker_set_fd (struct checker *, int); void checker_enable (struct checker *); void checker_disable (struct checker *); +/* + * start_checker_thread(): start async path checker thread + * + * This function provides a wrapper around pthread_create(). + * The created thread will call the DSO's "libcheck_thread" function with the + * checker context as argument. + * + * Rationale: + * Path checkers that do I/O may hang forever. To avoid blocking, some + * checkers therefore use 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 *); @@ -164,6 +187,8 @@ void checker_get(const char *, struct checker *, const char *); 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. * diff --git a/libmultipath/checkers/Makefile b/libmultipath/checkers/Makefile index 01c0451..8e0ed5e 100644 --- a/libmultipath/checkers/Makefile +++ b/libmultipath/checkers/Makefile @@ -4,6 +4,8 @@ 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= \ @@ -17,11 +19,8 @@ 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) diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c index e886fcf..a4b4a21 100644 --- a/libmultipath/checkers/tur.c +++ b/libmultipath/checkers/tur.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "checkers.h" @@ -55,6 +54,7 @@ struct tur_checker_context { pthread_cond_t active; int holders; /* uatomic access only */ int msgid; + struct checker_context ctx; }; int libcheck_init (struct checker * c) @@ -74,6 +74,7 @@ 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; @@ -204,7 +205,6 @@ static void cleanup_func(void *data) holders = uatomic_sub_return(&ct->holders, 1); if (!holders) cleanup_context(ct); - rcu_unregister_thread(); } /* @@ -251,15 +251,15 @@ static void tur_deep_sleep(const struct tur_checker_context *ct) #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)); @@ -394,7 +394,7 @@ int libcheck_check(struct checker * c) 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); diff --git a/libmultipath/config.c b/libmultipath/config.c index b9bdbdb..30046a1 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -26,6 +26,72 @@ #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) @@ -147,7 +213,7 @@ struct mpentry *find_mpe(vector mptable, char *wwid) int i; struct mpentry * mpe; - if (!wwid) + if (!wwid || !*wwid) return NULL; vector_foreach_slot (mptable, mpe, i) @@ -358,6 +424,7 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) 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); @@ -369,6 +436,7 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) 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); @@ -442,9 +510,16 @@ void merge_mptable(vector mptable) 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); @@ -513,6 +588,7 @@ store_hwe (vector hwtable, struct hwentry * dhwe) 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; @@ -574,17 +650,15 @@ restart: 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); @@ -650,7 +724,27 @@ free_config (struct config * conf) 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, @@ -719,19 +813,39 @@ static void set_max_checkint_from_watchdog(struct config *conf) } #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); @@ -754,6 +868,7 @@ load_config (char * 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 */ @@ -897,10 +1012,11 @@ load_config (char * file) !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, diff --git a/libmultipath/config.h b/libmultipath/config.h index 290aea5..933fe0d 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -10,14 +10,6 @@ #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, @@ -71,6 +63,7 @@ struct hwentry { 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; @@ -90,6 +83,7 @@ struct hwentry { int ghost_delay; int all_tg_pt; int vpd_vendor_id; + int recheck_wwid; char * bl_product; }; @@ -156,6 +150,7 @@ struct config { 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; @@ -192,8 +187,8 @@ struct config { 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; @@ -234,7 +229,53 @@ struct config { 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, @@ -252,11 +293,26 @@ void free_mptable (vector mptable); 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, diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 6fb477f..6ca1f4b 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -312,6 +312,13 @@ int setup_map(struct multipath *mpp, char *params, int params_size, 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: @@ -361,6 +368,7 @@ int setup_map(struct multipath *mpp, char *params, int params_size, 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); @@ -519,20 +527,8 @@ get_udev_for_mpp(const struct multipath *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; @@ -628,15 +624,6 @@ trigger_paths_udev_change(struct multipath *mpp, bool is_mpath) } 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; @@ -688,12 +675,11 @@ sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload) 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: @@ -705,14 +691,20 @@ select_reload_action(struct multipath *mpp, const struct multipath *cmpp, */ 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, @@ -781,10 +773,18 @@ 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 || @@ -792,7 +792,7 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, (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; } @@ -800,7 +800,7 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, !!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; } @@ -812,7 +812,7 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, 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; @@ -823,19 +823,19 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, 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) { @@ -844,12 +844,6 @@ void select_action (struct multipath *mpp, const struct _vector *curmp, 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); @@ -927,16 +921,12 @@ int domap(struct multipath *mpp, char *params, int is_daemon) { 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; } @@ -1132,7 +1122,7 @@ out: * 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; @@ -1144,6 +1134,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, struct path * pp2; vector curmp = vecs->mpvec; vector pathvec = vecs->pathvec; + vector newmp; struct config *conf; int allow_queueing; struct bitfield *size_mismatch_seen; @@ -1164,8 +1155,23 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, 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 */ @@ -1270,20 +1276,14 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, 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; @@ -1298,31 +1298,25 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, "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]; @@ -1345,6 +1339,8 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, ret = CP_OK; out: free(size_mismatch_seen); + if (!mpvec) + free_multipathvec(newmp, KEEP_PATHS); return ret; } @@ -1445,7 +1441,7 @@ static int _get_refwwid(enum mpath_cmds cmd, const char *dev, 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; @@ -1470,7 +1466,7 @@ static int _get_refwwid(enum mpath_cmds cmd, const char *dev, 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; diff --git a/libmultipath/configure.h b/libmultipath/configure.h index 6b23ccb..70cf77a 100644 --- a/libmultipath/configure.h +++ b/libmultipath/configure.h @@ -58,3 +58,5 @@ int get_refwwid (enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, vector pathvec, char **wwid); struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type); void trigger_paths_udev_change(struct multipath *mpp, bool is_mpath); +void trigger_partitions_udev_change(struct udev_device *dev, const char *action, + int len); diff --git a/libmultipath/debug.c b/libmultipath/debug.c index 4128cb9..510e15e 100644 --- a/libmultipath/debug.c +++ b/libmultipath/debug.c @@ -14,37 +14,31 @@ #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); } diff --git a/libmultipath/debug.h b/libmultipath/debug.h index c6120c1..705a5d7 100644 --- a/libmultipath/debug.h +++ b/libmultipath/debug.h @@ -1,5 +1,7 @@ -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 @@ -8,6 +10,23 @@ void dlog (int sink, int prio, const char * fmt, ...) #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 */ diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index 39a5e41..c27946c 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -8,6 +8,7 @@ */ #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" @@ -52,6 +53,7 @@ #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" diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index 7f09361..095cbc0 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -26,6 +26,8 @@ #include "sysfs.h" #include "config.h" #include "wwids.h" +#include "version.h" +#include "time-util.h" #include "log_pthread.h" #include @@ -34,7 +36,14 @@ #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; @@ -52,16 +61,34 @@ static inline int dm_task_set_cookie(struct dm_task *dmt, uint32_t *c, int a) 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, ...) { @@ -78,15 +105,14 @@ 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); @@ -102,7 +128,7 @@ dm_write_log (int level, const char *file, int line, const char *f, ...) return; } -void dm_init(int v) +static void dm_init(int v) { /* * This maps libdm's standard loglevel _LOG_WARN (= 4), which is rather @@ -112,61 +138,68 @@ void dm_init(int v) 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; @@ -174,16 +207,17 @@ dm_tgt_version (unsigned int * version, char * str) 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; @@ -213,26 +247,25 @@ 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; } @@ -241,13 +274,62 @@ dm_tgt_prereq (unsigned int *ver) 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) @@ -255,23 +337,32 @@ 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) @@ -322,12 +413,12 @@ dm_simplecmd (int task, const char *name, int no_flush, int need_sync, uint16_t 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; @@ -414,12 +505,12 @@ dm_addmap (int task, const char *target, struct multipath *mpp, !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); @@ -529,7 +620,7 @@ do_get_info(const char *name, struct dm_info *info) 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; } @@ -570,7 +661,7 @@ int dm_get_map(const char *name, unsigned long long *size, char *outparams) 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; @@ -612,7 +703,7 @@ dm_get_prefixed_uuid(const char *name, char *uuid, int uuid_len) 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; } @@ -683,7 +774,7 @@ int dm_get_status(const char *name, char *outstatus) 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; @@ -736,7 +827,7 @@ int dm_type(const char *name, char *type) 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; } @@ -780,7 +871,7 @@ int dm_is_mpath(const char *name) 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; } @@ -836,7 +927,7 @@ dm_map_present_by_uuid(const char *uuid) 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); @@ -844,7 +935,7 @@ dm_map_present_by_uuid(const char *uuid) 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; } @@ -890,7 +981,7 @@ dm_get_opencount (const char * mapname) 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; } @@ -1050,7 +1141,7 @@ int dm_flush_maps (int need_suspend, int retries) 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; } @@ -1096,7 +1187,7 @@ dm_message(const char * mapname, char * message) 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; } @@ -1216,7 +1307,7 @@ dm_get_maps (vector mp) 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; } @@ -1301,7 +1392,7 @@ dm_mapname(int major, int minor) * daemon uev_trigger -> uev_add_map */ while (--loop) { - r = dm_task_run(dmt); + r = libmp_dm_task_run(dmt); if (r) break; @@ -1346,7 +1437,7 @@ do_foreach_partmaps (const char * mapname, 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; } @@ -1581,11 +1672,11 @@ dm_rename (const char * old, char * new, char *delim, int skip_kpartx) 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); @@ -1627,7 +1718,7 @@ int dm_reassign_table(const char *name, char *old, char *new) 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; } @@ -1660,7 +1751,7 @@ int dm_reassign_table(const char *name, char *old, char *new) 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; @@ -1707,7 +1798,7 @@ int dm_reassign(const char *mapname) 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; } @@ -1775,7 +1866,7 @@ int dm_setgeometry(struct multipath *mpp) 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: diff --git a/libmultipath/devmapper.h b/libmultipath/devmapper.h index f469c98..e29b4d4 100644 --- a/libmultipath/devmapper.h +++ b/libmultipath/devmapper.h @@ -33,13 +33,11 @@ enum { 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); @@ -89,6 +87,15 @@ struct multipath *dm_get_multipath(const char *name); #include #define dm_task_get_errno(x) errno #endif +enum { + DM_LIBRARY_VERSION, + DM_KERNEL_VERSION, + DM_MPATH_TARGET_VERSION, + MULTIPATH_VERSION +}; +int libmp_get_version(int which, unsigned int version[3]); +struct dm_task; +int libmp_dm_task_run(struct dm_task *dmt); #define dm_log_error(lvl, cmd, dmt) \ condlog(lvl, "%s: libdm task=%d error: %s", __func__, \ diff --git a/libmultipath/dict.c b/libmultipath/dict.c index f12c2e5..dd08abf 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -822,7 +822,7 @@ declare_mp_attr_handler(gid, set_gid) 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; @@ -832,36 +832,36 @@ set_fast_io_fail(vector strvec, void *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) @@ -899,6 +899,13 @@ declare_ovr_snprint(dev_loss, print_dev_loss) declare_hw_handler(dev_loss, set_dev_loss) declare_hw_snprint(dev_loss, print_dev_loss) +declare_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) { @@ -1394,6 +1401,14 @@ declare_hw_snprint(all_tg_pt, print_yes_no_undef) 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) { @@ -1771,6 +1786,7 @@ init_keywords(vector keywords) 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); @@ -1811,6 +1827,7 @@ init_keywords(vector keywords) 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); @@ -1880,6 +1897,7 @@ init_keywords(vector keywords) 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); @@ -1899,6 +1917,7 @@ init_keywords(vector keywords) 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); @@ -1920,6 +1939,7 @@ init_keywords(vector keywords) 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); @@ -1939,6 +1959,7 @@ init_keywords(vector keywords) 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); diff --git a/libmultipath/dict.h b/libmultipath/dict.h index a40ac66..a917e1c 100644 --- a/libmultipath/dict.h +++ b/libmultipath/dict.h @@ -13,7 +13,7 @@ int print_rr_weight(char *buff, int len, long v); 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); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index c2e1754..ec99a7a 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -200,6 +200,9 @@ path_discovery (vector pathvec, int flag) 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); @@ -355,10 +358,17 @@ sysfs_get_tgt_nodename(struct path *pp, char *node) 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; } @@ -584,6 +594,42 @@ sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen) 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) { @@ -593,6 +639,10 @@ 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, @@ -700,6 +750,11 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp) 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); @@ -711,9 +766,6 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp) 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 " @@ -737,12 +789,28 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp) 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) { @@ -798,7 +866,8 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint) 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) { @@ -811,17 +880,18 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint) 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) { @@ -1082,19 +1152,19 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, 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; @@ -1272,14 +1342,13 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen) 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", @@ -1293,10 +1362,39 @@ get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen) 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) @@ -1561,6 +1659,9 @@ common_sysfs_pathinfo (struct path * pp) 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); @@ -1956,12 +2057,44 @@ get_vpd_uid(struct path * pp) 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) { @@ -1994,7 +2127,7 @@ static ssize_t uid_fallback(struct path *pp, int path_state, 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 @@ -2009,6 +2142,9 @@ static bool has_uid_fallback(struct path *pp) !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, "")))); } @@ -2016,16 +2152,17 @@ int 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); } @@ -2047,19 +2184,22 @@ get_uid (struct path * pp, int path_state, struct udev_device *udev, } 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); } } @@ -2070,12 +2210,9 @@ get_uid (struct path * pp, int path_state, struct udev_device *udev, 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' ? "" : pp->wwid, origin); @@ -2107,9 +2244,17 @@ int pathinfo(struct path *pp, struct config *conf, int mask) 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, @@ -2148,7 +2293,14 @@ int pathinfo(struct path *pp, struct config *conf, int mask) } 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) @@ -2218,6 +2370,22 @@ int pathinfo(struct path *pp, struct config *conf, int mask) 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; } diff --git a/libmultipath/discovery.h b/libmultipath/discovery.h index 6444887..a5446b4 100644 --- a/libmultipath/discovery.h +++ b/libmultipath/discovery.h @@ -54,8 +54,10 @@ ssize_t sysfs_get_inquiry(struct udev_device *udev, unsigned char *buff, size_t len); int sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen); +bool has_uid_fallback(struct path *pp); int get_uid(struct path * pp, int path_state, struct udev_device *udev, int allow_fallback); +bool is_vpd_page_supported(int fd, int pg); /* * discovery bitmask diff --git a/libmultipath/foreign/Makefile b/libmultipath/foreign/Makefile index fae58a0..f447a1c 100644 --- a/libmultipath/foreign/Makefile +++ b/libmultipath/foreign/Makefile @@ -5,13 +5,15 @@ TOPDIR=../.. 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) diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index cd65afc..58fa738 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -388,6 +388,17 @@ static struct hwentry default_hw[] = { .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 */ @@ -819,7 +830,7 @@ static struct hwentry default_hw[] = { * * 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, @@ -1113,8 +1124,9 @@ static struct hwentry default_hw[] = { .pgpolicy = MULTIBUS, }, /* - * Imation/Nexsan + * StorCentric */ + /* Nexsan */ { /* E-Series */ .vendor = "NEXSAN", @@ -1143,9 +1155,7 @@ static struct hwentry default_hw[] = { .prio_name = PRIO_ALUA, .no_path_retry = 30, }, - /* - * Violin Systems - */ + /* Violin Systems */ { /* 3000 / 6000 Series */ .vendor = "VIOLIN", @@ -1192,6 +1202,14 @@ static struct hwentry default_hw[] = { .pgpolicy = MULTIBUS, .no_path_retry = 30, }, + /* Vexata */ + { + /* VX */ + .vendor = "Vexata", + .product = "VX", + .pgpolicy = MULTIBUS, + .no_path_retry = 30, + }, /* * Promise Technology */ diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index 58bc1dd..d8d91f6 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -34,6 +34,7 @@ #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 @@ -45,12 +46,6 @@ #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; @@ -70,14 +65,14 @@ struct io_err_stat_path { 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; @@ -88,7 +83,7 @@ static void rcu_unregister(__attribute__((unused)) void *param) 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; @@ -166,12 +161,15 @@ fail_close: 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++) @@ -180,6 +178,8 @@ static void destroy_directio_ctx(struct io_err_stat_path *p) if (p->fd > 0) close(p->fd); +free_path: + FREE(p); } static struct io_err_stat_path *alloc_io_err_stat_path(void) @@ -202,51 +202,21 @@ 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); } /* @@ -258,13 +228,13 @@ static int enqueue_io_err_stat_by_path(struct path *path) { 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) @@ -276,19 +246,18 @@ static int enqueue_io_err_stat_by_path(struct path *path) 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); @@ -323,8 +292,7 @@ int io_err_stat_handle_pathfail(struct path *path) * 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; @@ -375,14 +343,14 @@ int need_io_err_check(struct path *pp) 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); @@ -393,8 +361,7 @@ int need_io_err_check(struct path *pp) * 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; @@ -408,20 +375,6 @@ recover: 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) { @@ -438,17 +391,24 @@ static void account_async_io_state(struct io_err_stat_path *pp, int 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); @@ -487,10 +447,6 @@ static int poll_io_err_stat(struct vectors *vecs, struct io_err_stat_path *pp) 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) @@ -501,11 +457,7 @@ 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", @@ -524,8 +476,7 @@ static void send_batch_async_ios(struct io_err_stat_path *pp) 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 */ @@ -540,11 +491,8 @@ static void send_batch_async_ios(struct io_err_stat_path *pp) 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, @@ -583,9 +531,8 @@ static void poll_async_io_timeout(void) 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); @@ -631,7 +578,7 @@ static void handle_async_io_done_event(struct io_event *io_evt) 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) { @@ -650,6 +597,7 @@ static void process_async_ios_event(int timeout_nsecs, char *dev) 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)", @@ -662,22 +610,32 @@ static void process_async_ios_event(int timeout_nsecs, char *dev) 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) @@ -727,6 +685,7 @@ static void *io_err_stat_loop(void *data) 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; @@ -735,12 +694,18 @@ int start_io_err_stat_thread(void *data) 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); @@ -750,6 +715,7 @@ int start_io_err_stat_thread(void *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"); @@ -760,7 +726,10 @@ int start_io_err_stat_thread(void *data) 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"); @@ -776,6 +745,6 @@ void stop_io_err_stat_thread(void) 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); } diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version new file mode 100644 index 0000000..0cff311 --- /dev/null +++ b/libmultipath/libmultipath.version @@ -0,0 +1,276 @@ +/* + * 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: + *; +}; diff --git a/libmultipath/log.c b/libmultipath/log.c index debd36d..10fa32c 100644 --- a/libmultipath/log.c +++ b/libmultipath/log.c @@ -9,13 +9,16 @@ #include #include #include +#include #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) @@ -74,16 +77,23 @@ static int logarea_init (int size) 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); @@ -93,20 +103,30 @@ void free_logarea (void) 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]; @@ -165,7 +185,19 @@ int log_enqueue (int prio, const char * fmt, va_list ap) 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; @@ -194,6 +226,18 @@ int log_dequeue (void * 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 */ diff --git a/libmultipath/log.h b/libmultipath/log.h index d2448f6..fa224e4 100644 --- a/libmultipath/log.h +++ b/libmultipath/log.h @@ -39,6 +39,5 @@ int log_enqueue (int prio, const char * fmt, va_list ap) int log_dequeue (void *); void log_syslog (void *); void dump_logmsg (void *); -void free_logarea (void); #endif /* LOG_H */ diff --git a/libmultipath/log_pthread.c b/libmultipath/log_pthread.c index 15baef8..6599210 100644 --- a/libmultipath/log_pthread.c +++ b/libmultipath/log_pthread.c @@ -13,34 +13,42 @@ #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) @@ -48,52 +56,72 @@ 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); } @@ -104,32 +132,30 @@ void log_thread_start (pthread_attr_t *attr) 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(); } diff --git a/libmultipath/parser.c b/libmultipath/parser.c index ed6d5d6..c70243c 100644 --- a/libmultipath/parser.c +++ b/libmultipath/parser.c @@ -390,7 +390,7 @@ oom: /* 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; @@ -434,7 +434,7 @@ is_sublevel_keyword(char *str) } int -validate_config_strvec(vector strvec, char *file) +validate_config_strvec(vector strvec, const char *file) { char *str = NULL; int i; @@ -499,7 +499,8 @@ validate_config_strvec(vector strvec, char *file) } 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; @@ -536,7 +537,7 @@ process_stream(struct config *conf, FILE *stream, vector keywords, char *file) 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); @@ -575,7 +576,8 @@ process_stream(struct config *conf, FILE *stream, vector keywords, char *file) free_strvec(strvec); } - + if (kw_level == 1) + condlog(1, "missing '%s' at end of %s", EOB, file); out: FREE(buf); free_uniques(uniques); @@ -584,7 +586,7 @@ out: /* Data initialization */ int -process_file(struct config *conf, char *file) +process_file(struct config *conf, const char *file) { int r; FILE *stream; diff --git a/libmultipath/parser.h b/libmultipath/parser.h index 62906e9..06666cc 100644 --- a/libmultipath/parser.h +++ b/libmultipath/parser.h @@ -77,7 +77,7 @@ extern void dump_keywords(vector keydump, int level); 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); diff --git a/libmultipath/print.c b/libmultipath/print.c index 19de2c7..8151e11 100644 --- a/libmultipath/print.c +++ b/libmultipath/print.c @@ -2055,8 +2055,16 @@ int snprint_devices(struct config *conf, char *buff, size_t len, 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) diff --git a/libmultipath/prio.c b/libmultipath/prio.c index 194563c..c92bde7 100644 --- a/libmultipath/prio.c +++ b/libmultipath/prio.c @@ -18,10 +18,32 @@ unsigned int get_prio_timeout(unsigned int timeout_ms, 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; } @@ -87,7 +109,7 @@ int prio_set_args (struct prio * p, const char * args) 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; diff --git a/libmultipath/prio.h b/libmultipath/prio.h index 599d1d8..26754f7 100644 --- a/libmultipath/prio.h +++ b/libmultipath/prio.h @@ -55,9 +55,9 @@ struct prio { 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 *); diff --git a/libmultipath/prioritizers/Makefile b/libmultipath/prioritizers/Makefile index fc6e0e0..8d34ae3 100644 --- a/libmultipath/prioritizers/Makefile +++ b/libmultipath/prioritizers/Makefile @@ -4,6 +4,8 @@ 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 = \ @@ -28,11 +30,8 @@ endif 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) diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 7e6e0d6..b7b3379 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -65,9 +65,7 @@ do { \ __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; \ } @@ -498,13 +496,15 @@ check_rdac(struct path * pp) { 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) @@ -581,6 +581,27 @@ out: 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) { @@ -737,9 +758,10 @@ out: 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); @@ -755,7 +777,7 @@ int select_fast_io_fail(struct config *conf, struct multipath *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; } @@ -776,6 +798,23 @@ out: 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; @@ -822,9 +861,10 @@ out: 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; diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h index 3d6edd8..72a7e33 100644 --- a/libmultipath/propsel.h +++ b/libmultipath/propsel.h @@ -7,6 +7,7 @@ int select_features (struct config *conf, struct multipath * mp); int select_hwhandler (struct config *conf, struct multipath * mp); int select_checker(struct config *conf, struct path *pp); int select_getuid (struct config *conf, struct path * pp); +int select_recheck_wwid(struct config *conf, struct path * pp); int select_prio (struct config *conf, struct path * pp); int select_find_multipaths_timeout(struct config *conf, struct path *pp); int select_no_path_retry(struct config *conf, struct multipath *mp); @@ -17,6 +18,7 @@ int select_uid(struct config *conf, struct multipath *mp); int select_gid(struct config *conf, struct multipath *mp); int select_fast_io_fail(struct config *conf, struct 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); diff --git a/libmultipath/structs.c b/libmultipath/structs.c index 464596f..8751fc2 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -234,6 +234,17 @@ alloc_multipath (void) 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) @@ -290,6 +301,10 @@ free_multipath (struct multipath * mpp, enum free_path_mode free_paths) 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); } @@ -453,12 +468,12 @@ find_mp_by_str (const struct _vector *mpvec, const char * str) } 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) diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 7de93d6..c8447e5 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -219,6 +219,35 @@ enum vpd_vendor_ids { 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; @@ -293,6 +322,7 @@ struct path { int find_multipaths_timeout; int marginal; int vpd_vendor_id; + int recheck_wwid; /* configlet pointers */ vector hwe; struct gen_path generic_path; @@ -339,6 +369,7 @@ struct multipath { int ghost_delay; int ghost_delay_tick; unsigned int dev_loss; + int eh_deadline; uid_t uid; gid_t gid; mode_t mode; @@ -422,6 +453,7 @@ struct host_group { 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); diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c index 8895fa7..d242c06 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -294,11 +294,6 @@ err: 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); } @@ -308,8 +303,6 @@ void orphan_paths(vector pathvec, struct multipath *mpp, const char *reason) 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) { @@ -397,24 +390,26 @@ extract_hwe_from_path(struct multipath * mpp) 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 @@ -428,44 +423,27 @@ update_multipath_table (struct multipath *mpp, vector pathvec, int flags) 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) { @@ -514,8 +492,6 @@ void sync_paths(struct multipath *mpp, vector pathvec) } 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"); } @@ -524,8 +500,6 @@ void sync_paths(struct multipath *mpp, vector pathvec) 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 @@ -547,11 +521,8 @@ update_multipath_strings(struct multipath *mpp, vector pathvec) 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) @@ -701,17 +672,23 @@ struct multipath *add_map_with_path(struct vectors *vecs, struct path *pp, 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) { @@ -754,12 +731,6 @@ int verify_paths(struct multipath *mpp) 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 @@ -771,7 +742,6 @@ int verify_paths(struct multipath *mpp) mpp->alias, pp->dev, pp->dev_t); } } - extract_hwe_from_path(mpp); return count; } diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c index 5390de6..7a2af1e 100644 --- a/libmultipath/sysfs.c +++ b/libmultipath/sysfs.c @@ -344,24 +344,23 @@ bool sysfs_is_multipathed(struct path *pp, bool set_wwid) 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); } diff --git a/libmultipath/util.c b/libmultipath/util.c index 1748eaf..0e37f3f 100644 --- a/libmultipath/util.c +++ b/libmultipath/util.c @@ -424,6 +424,11 @@ void cleanup_free_ptr(void *arg) free(*p); } +void cleanup_mutex(void *arg) +{ + pthread_mutex_unlock(arg); +} + struct bitfield *alloc_bitfield(unsigned int maxbit) { unsigned int n; @@ -445,3 +450,8 @@ void _log_bitfield_overflow(const char *f, unsigned int bit, unsigned int len) { condlog(0, "%s: bitfield overflow: %u >= %u", f, bit, len); } + +int should_exit(void) +{ + return 0; +} diff --git a/libmultipath/util.h b/libmultipath/util.h index 2b9703a..e9b48f9 100644 --- a/libmultipath/util.h +++ b/libmultipath/util.h @@ -27,6 +27,7 @@ int parse_prkey(const char *ptr, uint64_t *prkey); 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])) @@ -48,6 +49,7 @@ void set_max_fds(rlim_t max_fds); void close_fd(void *arg); void cleanup_free_ptr(void *arg); +void cleanup_mutex(void *arg); struct scandir_result { struct dirent **di; diff --git a/libmultipath/valid.c b/libmultipath/valid.c index 456b1f6..a6aa921 100644 --- a/libmultipath/valid.c +++ b/libmultipath/valid.c @@ -89,10 +89,6 @@ is_path_valid(const char *name, struct config *conf, struct path *pp, 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) diff --git a/libmultipath/version.h b/libmultipath/version.h index 6ceed53..6e68199 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,8 +20,8 @@ #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" diff --git a/mpathpersist/main.c b/mpathpersist/main.c index a6a3bcf..14245cc 100644 --- a/mpathpersist/main.c +++ b/mpathpersist/main.c @@ -42,24 +42,10 @@ void * mpath_alloc_prin_response(int prin_sa); void mpath_print_transport_id(struct prin_fulldescr *fdesc); int construct_transportid(const char * inp, struct transportid transid[], int num_transportids); -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; @@ -652,18 +638,13 @@ int main(int argc, char *argv[]) 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; } diff --git a/multipath/11-dm-mpath.rules b/multipath/11-dm-mpath.rules index cd522e8..d191ae8 100644 --- a/multipath/11-dm-mpath.rules +++ b/multipath/11-dm-mpath.rules @@ -32,7 +32,7 @@ ACTION=="add", ENV{.MPATH_DEVICE_READY_OLD}=="1", GOTO="paths_ok" # Check the map state directly with multipath -U. # This doesn't attempt I/O on the device. -PROGRAM=="$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" diff --git a/multipath/main.c b/multipath/main.c index 9e920d8..ef89c7c 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -66,10 +66,6 @@ #include "valid.h" #include "alias.h" -int logsink; -struct udev *udev; -struct config *multipath_conf; - /* * Return values of configure(), check_path_valid(), and main(). */ @@ -79,16 +75,6 @@ enum { 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) { @@ -107,7 +93,7 @@ void rcu_register_thread_memb(void) {} 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; @@ -210,8 +196,7 @@ get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid) 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--; @@ -222,22 +207,15 @@ get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid) 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; } @@ -284,8 +262,7 @@ static int check_usable_paths(struct config *conf, 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) { @@ -438,7 +415,7 @@ static int print_cmd_valid(int k, const vector pathvec, 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", @@ -466,13 +443,19 @@ static bool released_to_systemd(void) 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; @@ -483,6 +466,7 @@ configure (struct config *conf, enum mpath_cmds cmd, */ curmp = vector_alloc(); pathvec = vector_alloc(); + atexit(cleanup_vecs); if (!curmp || !pathvec) { condlog(0, "can not allocate memory"); @@ -559,7 +543,7 @@ configure (struct config *conf, enum mpath_cmds cmd, 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); @@ -594,9 +578,6 @@ out: if (refwwid) FREE(refwwid); - free_multipathvec(curmp, KEEP_PATHS); - free_pathvec(pathvec, FREE_PATHS); - return r; } @@ -604,8 +585,9 @@ static int 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) @@ -674,14 +656,19 @@ check_path_valid(const char *name, struct config *conf, bool is_uevent) 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 @@ -689,21 +676,25 @@ check_path_valid(const char *name, struct config *conf, bool is_uevent) 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 @@ -821,12 +812,15 @@ main (int argc, char *argv[]) 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 ) { @@ -840,7 +834,7 @@ main (int argc, char *argv[]) exit(RTVL_FAIL); } - conf->verbosity = atoi(optarg); + libmp_verbosity = atoi(optarg); break; case 'b': conf->bindings_file = strdup(optarg); @@ -902,7 +896,7 @@ main (int argc, char *argv[]) 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; @@ -971,8 +965,8 @@ main (int argc, char *argv[]) } 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); @@ -1063,27 +1057,13 @@ main (int argc, char *argv[]) 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 diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 index d2101ed..064e482 100644 --- a/multipath/multipath.conf.5 +++ b/multipath/multipath.conf.5 @@ -472,8 +472,12 @@ The default is: \fB\fR . .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 @@ -499,10 +503,8 @@ Check the path state for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF 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) @@ -639,11 +641,13 @@ The default is: \fBno\fR . .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/device/timeout\fR +The default is: in \fB/sys/block//device/timeout\fR .RE . . @@ -717,6 +721,22 @@ The default is: \fB600\fR . . .TP +.B eh_deadline +Specify the maximum number of seconds the SCSI layer will spend doing error +handling when scsi devices fail. After this timeout the scsi layer will perform +a full HBA reset. Setting this may be necessary in cases where the rport is +never lost, so \fIfast_io_fail_tmo\fR and \fIdev_loss_tmo\fR will never +trigger, but (frequently do to load) scsi commands still hang. \fBNote:\fR when +the scsi error handler performs the HBA reset, all target paths on that HBA +will be affected. eh_deadline should only be set in cases where all targets on +the affected HBAs are multipathed. +.RS +.TP +The default is: \fB\fR +.RE +. +. +.TP .B bindings_file The full pathname of the binding file to be used when the user_friendly_names option is set. @@ -1075,7 +1095,7 @@ for the configured time, and is declared healthy, it will be returned to its normal pathgroup. See "Shaky paths detection" below for more information. .RS .TP -The default is: \fBno\fR +The default is: \fBno\fR .RE . . @@ -1153,7 +1173,7 @@ In these cases it is recommended to increase the CLI timeout to avoid those issues. .RS .TP -The default is: \fB1000\fR +The default is: \fB4000\fR .RE . . @@ -1222,7 +1242,7 @@ Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value. .RS .TP -The default is: \fB\fR +The default is: in \fB/sys/block//queue/max_sectors_kb\fR .RE . . @@ -1253,6 +1273,20 @@ The default is: \fB\(dqNONE\(dq\fR .RE . . +.TP +.B recheck_wwid +If set to \fIyes\fR, when a failed path is restored, its wwid is rechecked. If +the wwid has changed, the path is removed from the current multipath device, +and re-added as a new path. Multipathd will also recheck a path's wwid if it is +manually re-added. This option only works for SCSI devices that are configured +to use the default uid_attribute, \fIID_SERIAL\fR, or sysfs for getting their +wwid. +.RS +.TP +The default is: \fBno\fR +.RE +. +. . .\" ---------------------------------------------------------------------------- diff --git a/multipathd/Makefile b/multipathd/Makefile index 632b82b..d053c1e 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -30,7 +30,7 @@ ifeq ($(ENABLE_DMEVENTS_POLL),0) 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 diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index 235e2a2..1de6ad8 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -715,6 +715,15 @@ cli_add_path (void * v, char ** reply, int * len, void * data) 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) { @@ -843,14 +852,15 @@ cli_add_map (void * v, char ** reply, int * len, void * data) } 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); @@ -860,7 +870,6 @@ cli_add_map (void * v, char ** reply, int * len, void * data) != CP_OK) condlog(2, "%s: coalesce_paths failed", param); - dm_lib_release(); FREE(refwwid); } } /*we attempt to create device only once*/ @@ -1032,7 +1041,6 @@ cli_resize(void *v, char **reply, int *len, void *data) if (resize_map(mpp, size, vecs) != 0) return 1; - dm_lib_release(); if (setup_multipath(vecs, mpp) != 0) return 1; sync_map_state(mpp); diff --git a/multipathd/dmevents.c b/multipathd/dmevents.c index 5f2d210..f52f597 100644 --- a/multipathd/dmevents.c +++ b/multipathd/dmevents.c @@ -60,7 +60,7 @@ int dmevent_poll_supported(void) { 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)) @@ -156,7 +156,7 @@ static int dm_get_events(void) 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; } @@ -257,6 +257,8 @@ void unwatch_all_dmevents(void) struct dev_event *dev_evt; int i; + if (!waiter) + return; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) free(dev_evt); diff --git a/multipathd/init_unwinder.c b/multipathd/init_unwinder.c new file mode 100644 index 0000000..14467f3 --- /dev/null +++ b/multipathd/init_unwinder.c @@ -0,0 +1,34 @@ +#include +#include +#include "init_unwinder.h" + +static pthread_mutex_t dummy_mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t dummy_cond = PTHREAD_COND_INITIALIZER; + +static 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); +} diff --git a/multipathd/init_unwinder.h b/multipathd/init_unwinder.h new file mode 100644 index 0000000..ada09f8 --- /dev/null +++ b/multipathd/init_unwinder.h @@ -0,0 +1,21 @@ +#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 diff --git a/multipathd/main.c b/multipathd/main.c index a4abbb2..102946b 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -83,15 +83,16 @@ #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); \ @@ -115,21 +116,24 @@ struct mpath_event_param 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) { @@ -141,12 +145,15 @@ 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; @@ -184,6 +191,8 @@ static void do_sd_notify(enum daemon_status old_state, { char notify_msg[MSG_SIZE]; const char *msg; + static bool startup_done = false; + /* * Checkerloop switches back and forth between idle and running state. * No need to tell systemd each time. @@ -200,6 +209,14 @@ static void do_sd_notify(enum daemon_status old_state, 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 @@ -208,6 +225,23 @@ static void config_cleanup(__attribute__((unused)) void *arg) pthread_mutex_unlock(&config_lock); } +#define __wait_for_state_change(condition, ms) \ + ({ \ + struct timespec tmo; \ + int rc = 0; \ + \ + if (condition) { \ + get_monotonic_time(&tmo); \ + tmo.tv_nsec += (ms) * 1000 * 1000; \ + normalize_timespec(&tmo); \ + do \ + rc = pthread_cond_timedwait( \ + &config_cond, &config_lock, &tmo); \ + while (rc == 0 && (condition)); \ + } \ + rc; \ + }) + /* * If the current status is @oldstate, wait for at most @ms milliseconds * for the state to change, and return the new state, which may still be @@ -217,20 +251,14 @@ enum daemon_status wait_for_state_change_if(enum daemon_status oldstate, 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; } @@ -266,27 +294,14 @@ int set_config_state(enum daemon_status state) 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; @@ -497,7 +512,6 @@ retry: sleep(1); goto retry; } - dm_lib_release(); fail: if (new_map && (retries < 0 || wait_for_events(mpp, vecs))) { @@ -545,8 +559,6 @@ add_map_without_path (struct vectors *vecs, const char *alias) 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; @@ -598,10 +610,8 @@ coalesce_maps(struct vectors *vecs, vector nmpv) 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); @@ -647,10 +657,8 @@ flush_map(struct multipath * mpp, struct vectors * vecs, int nopaths) } 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); @@ -813,6 +821,68 @@ ev_remove_map (char * devname, char * alias, int minor, struct vectors * 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) { @@ -880,13 +950,7 @@ 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 @@ -897,7 +961,12 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) 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); @@ -998,8 +1067,8 @@ rescan: 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); @@ -1018,7 +1087,7 @@ rescan: */ start_waiter = 1; } - if (!start_waiter) + else goto fail; /* leave path added to pathvec */ } @@ -1067,7 +1136,6 @@ rescan: else goto fail_map; } - dm_lib_release(); if ((mpp->action == ACT_CREATE || (mpp->action == ACT_NOTHING && start_waiter && !mpp->waiter)) && @@ -1154,13 +1222,6 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) 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) { @@ -1191,9 +1252,6 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) */ } - 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); @@ -1298,6 +1356,7 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) 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 { @@ -1403,8 +1462,7 @@ map_discovery (struct vectors * vecs) 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--; } @@ -1501,31 +1559,6 @@ uev_trigger (struct uevent * uev, void * trigger_data) 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; } @@ -1944,8 +1977,6 @@ int reload_and_sync_map(struct multipath *mpp, { if (reload_map(vecs, mpp, refresh, 1)) return 1; - - dm_lib_release(); if (setup_multipath(vecs, mpp) != 0) return 2; sync_map_state(mpp); @@ -2074,7 +2105,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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; @@ -2094,7 +2125,6 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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); @@ -2156,16 +2186,21 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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) { @@ -2183,13 +2218,13 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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; @@ -2210,7 +2245,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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", @@ -2222,11 +2257,22 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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, @@ -2236,13 +2282,18 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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; + } } } @@ -2261,7 +2312,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) int oldstate = pp->state; pp->state = newstate; - LOG_MSG(1, verbosity, pp); + LOG_MSG(1, pp); /* * upon state change, reset the checkint @@ -2325,7 +2376,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) /* 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. @@ -2353,9 +2404,9 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) 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); } } @@ -2368,11 +2419,8 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) */ 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) { @@ -2430,8 +2478,8 @@ checkerloop (void *ap) 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 { @@ -2456,6 +2504,8 @@ checkerloop (void *ap) 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--; @@ -2492,18 +2542,18 @@ checkerloop (void *ap) 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); } } @@ -2529,8 +2579,8 @@ checkerloop (void *ap) } 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", @@ -2580,6 +2630,9 @@ configure (struct vectors * vecs) 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){ @@ -2596,6 +2649,9 @@ configure (struct vectors * vecs) 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 @@ -2610,6 +2666,9 @@ configure (struct vectors * vecs) 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 @@ -2619,7 +2678,8 @@ configure (struct vectors * vecs) goto fail; } - dm_lib_release(); + if (should_exit()) + goto fail; sync_maps_state(mpvec); vector_foreach_slot(mpvec, mpp, i){ @@ -2629,14 +2689,10 @@ configure (struct vectors * vecs) } /* - * 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; /* @@ -2690,6 +2746,10 @@ reconfigure (struct vectors * vecs) 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 */ @@ -2701,12 +2761,6 @@ reconfigure (struct vectors * vecs) 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); @@ -2774,7 +2828,7 @@ handle_signals(bool nonfatal) } if (log_reset_sig) { condlog(2, "reset log (signal)"); - if (logsink == 1) + if (logsink == LOGSINK_SYSLOG) log_thread_reset(); } reconfig_sig = 0; @@ -2888,34 +2942,190 @@ set_oom_adj (void) 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); @@ -2923,8 +3133,6 @@ child (__attribute__((unused)) void *param) 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); } @@ -2933,12 +3141,18 @@ child (__attribute__((unused)) void *param) 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; @@ -2957,7 +3171,6 @@ child (__attribute__((unused)) void *param) if (poll_dmevents) poll_dmevents = dmevent_poll_supported(); - setlogmask(LOG_UPTO(conf->verbosity + 3)); envp = getenv("LimitNOFILE"); @@ -2995,9 +3208,12 @@ child (__attribute__((unused)) void *param) 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) { @@ -3010,7 +3226,8 @@ child (__attribute__((unused)) void *param) condlog(0, "failed to create dmevent waiter thread: %d", rc); goto failed; - } + } else + dmevent_thr_started = true; } /* @@ -3019,7 +3236,8 @@ child (__attribute__((unused)) void *param) 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); /* @@ -3028,11 +3246,13 @@ child (__attribute__((unused)) void *param) 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) { @@ -3058,96 +3278,14 @@ child (__attribute__((unused)) void *param) } 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 @@ -3212,13 +3350,15 @@ main (int argc, char *argv[]) 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"); @@ -3233,38 +3373,35 @@ main (int argc, char *argv[]) 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; @@ -3280,27 +3417,35 @@ main (int argc, char *argv[]) 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; diff --git a/multipathd/main.h b/multipathd/main.h index 5abbe97..ddd953f 100644 --- a/multipathd/main.h +++ b/multipathd/main.h @@ -50,4 +50,6 @@ int update_multipath (struct vectors *vecs, char *mapname, int reset); 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 */ diff --git a/multipathd/multipathd.service b/multipathd/multipathd.service index ba24983..7d547fa 100644 --- a/multipathd/multipathd.service +++ b/multipathd/multipathd.service @@ -2,7 +2,7 @@ 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 diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index 1c5ce9d..dbee0d6 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -35,19 +35,31 @@ #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; @@ -116,7 +128,7 @@ static void _dead_client(struct client *c) 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); @@ -142,8 +154,8 @@ static void check_timeout(struct timespec start_time, char *inbuf, 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); } } @@ -281,13 +293,13 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, 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(); @@ -302,37 +314,42 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, 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)) @@ -342,16 +359,19 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, 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) { @@ -384,7 +404,7 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, } /* 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; diff --git a/multipathd/waiter.c b/multipathd/waiter.c index 3bc6980..bbe6c2a 100644 --- a/multipathd/waiter.c +++ b/multipathd/waiter.c @@ -118,7 +118,7 @@ static int waiteventloop (struct event_thread *waiter) 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(); diff --git a/tests/Makefile b/tests/Makefile index d26b3ce..e70c8ed 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,16 +10,17 @@ W_MISSING_INITIALIZERS := $(call TEST_MISSING_INITIALIZERS) 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 @@ -31,6 +32,7 @@ endif 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 @@ -40,10 +42,11 @@ endif # 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 @@ -52,9 +55,11 @@ vpd-test_OBJDEPS := ../libmultipath/discovery.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 @@ -68,17 +73,17 @@ lib/libchecktur.so: %.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) @@ -98,12 +103,14 @@ dep_clean: @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)) diff --git a/tests/README.md b/tests/README.md index 6e7ad40..47c0f0b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,6 +13,11 @@ If valgrind detects a bad memory access or leak, the test will fail. The output of the test run, including valgrind output, is stored as `.vgr`. +## 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 diff --git a/tests/alias.c b/tests/alias.c index 7fda679..7e7c187 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -61,6 +62,25 @@ int __wrap_ftruncate(int fd, off_t length) 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]; @@ -349,6 +369,45 @@ static int test_scan_devname(void) 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; @@ -356,9 +415,68 @@ static void lb_empty(void **state) 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) @@ -369,7 +487,7 @@ 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"); @@ -384,12 +502,57 @@ static void lb_nomatch_a(void **state) 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; @@ -398,13 +561,23 @@ static void lb_match_c(void **state) 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; @@ -414,11 +587,77 @@ static void lb_nomatch_a_c(void **state) 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; @@ -428,11 +667,44 @@ static void lb_nomatch_c_a(void **state) 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; @@ -443,7 +715,7 @@ static void lb_nomatch_a_b(void **state) 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); } @@ -459,7 +731,24 @@ static void lb_nomatch_a_b_bad(void **state) 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); } @@ -474,13 +763,32 @@ static void lb_nomatch_b_a(void **state) 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; @@ -490,7 +798,32 @@ static void lb_nomatch_int_max(void **state) 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); } @@ -505,27 +838,96 @@ static void lb_nomatch_int_max_m1(void **state) 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 }; @@ -735,6 +1137,7 @@ static int test_allocate_binding(void) int main(void) { int ret = 0; + init_test_verbosity(3); ret += test_format_devname(); ret += test_scan_devname(); diff --git a/tests/blacklist.c b/tests/blacklist.c index 84a3ba2..882aa3a 100644 --- a/tests/blacklist.c +++ b/tests/blacklist.c @@ -22,6 +22,7 @@ #include "globals.c" #include "blacklist.h" #include "test-log.h" +#include "debug.h" struct udev_device { const char *sysname; @@ -152,6 +153,7 @@ static int setup(void **state) store_ble(blist_property_wwn_inv, "!ID_WWN", ORIGIN_CONFIG)) return -1; + init_test_verbosity(4); return 0; } diff --git a/tests/devt.c b/tests/devt.c index fd4d74a..0ad100a 100644 --- a/tests/devt.c +++ b/tests/devt.c @@ -11,11 +11,37 @@ #include #include #include +#include +#include +#include +#include +#include #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; @@ -71,6 +97,8 @@ static void test_devt2devname_devt_good(void **state) { char dummy[BLK_DEV_SIZE]; + if (!sys_dev_block_exists()) + skip(); assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 0); } @@ -137,6 +165,8 @@ static void test_devt2devname_real(void **state) 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"); @@ -187,6 +217,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += devt2devname_tests(); return ret; } diff --git a/tests/directio.c b/tests/directio.c index 9895409..9f7d388 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -770,7 +770,7 @@ int main(void) { int ret = 0; - conf.verbosity = 2; + init_test_verbosity(2); ret += test_directio(); return ret; } diff --git a/tests/dmevents.c b/tests/dmevents.c index bee117a..204cf1d 100644 --- a/tests/dmevents.c +++ b/tests/dmevents.c @@ -16,6 +16,7 @@ * */ +#include #include #include #include @@ -179,6 +180,8 @@ struct dm_names *build_dm_names(void) return names; } +static bool setup_done; + static int setup(void **state) { if (dmevent_poll_supported()) { @@ -186,6 +189,7 @@ static int setup(void **state) *state = &data; } else *state = NULL; + setup_done = true; return 0; } @@ -262,14 +266,20 @@ struct dm_task *__wrap_libmp_dm_task_create(int task) 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); } @@ -291,8 +301,11 @@ struct dm_names * __wrap_dm_task_get_names(struct dm_task *dmt) 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) { @@ -912,6 +925,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_dmevents(); return ret; } diff --git a/tests/globals.c b/tests/globals.c index 8add5eb..36319ed 100644 --- a/tests/globals.c +++ b/tests/globals.c @@ -1,12 +1,12 @@ +#include +#include + +#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) { @@ -15,3 +15,19 @@ 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; + } +} diff --git a/tests/hwtable.c b/tests/hwtable.c index 12660da..6f5766f 100644 --- a/tests/hwtable.c +++ b/tests/hwtable.c @@ -30,8 +30,6 @@ #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; @@ -55,7 +53,7 @@ struct hwt_state { static struct config *_conf; struct udev *udev; -int logsink = -1; +int logsink = LOGSINK_STDERR_WITHOUT_TIME; struct config *get_multipath_config(void) { @@ -360,7 +358,6 @@ static void write_device(FILE *ff, int nkv, const struct key_value *kv) 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 { \ @@ -1781,6 +1778,8 @@ int main(void) { int ret = 0; + /* We can't use init_test_verbosity in this test */ + libmp_verbosity = VERBOSITY; ret += test_hwtable(); return ret; } diff --git a/tests/mpathvalid.c b/tests/mpathvalid.c new file mode 100644 index 0000000..cfe4bae --- /dev/null +++ b/tests/mpathvalid.c @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2020 Benjamin Marzinski, Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "structs.h" +#include "config.h" +#include "mpath_valid.h" +#include "util.h" +#include "debug.h" + +const char *test_dev = "test_name"; +#define TEST_WWID "WWID_123" +#define CONF_TEMPLATE "mpathvalid-testconf-XXXXXXXX" +char conf_name[] = CONF_TEMPLATE; +bool initialized; + +#if 0 +static int mode_to_findmp(unsigned int mode) +{ + switch (mode) { + case MPATH_SMART: + return FIND_MULTIPATHS_SMART; + case MPATH_GREEDY: + return FIND_MULTIPATHS_GREEDY; + case MPATH_STRICT: + return FIND_MULTIPATHS_STRICT; + } + fail_msg("invalid mode: %u", mode); + return FIND_MULTIPATHS_UNDEF; +} +#endif + +static unsigned int findmp_to_mode(int findmp) +{ + switch (findmp) { + case FIND_MULTIPATHS_SMART: + return MPATH_SMART; + case FIND_MULTIPATHS_GREEDY: + return MPATH_GREEDY; + case FIND_MULTIPATHS_STRICT: + case FIND_MULTIPATHS_OFF: + case FIND_MULTIPATHS_ON: + return MPATH_STRICT; + } + fail_msg("invalid find_multipaths value: %d", findmp); + return MPATH_DEFAULT; +} + +int __wrap_is_path_valid(const char *name, struct config *conf, struct path *pp, + bool check_multipathd) +{ + int r = mock_type(int); + int findmp = mock_type(int); + + assert_ptr_equal(name, test_dev); + assert_ptr_not_equal(conf, NULL); + assert_ptr_not_equal(pp, NULL); + assert_true(check_multipathd); + + assert_int_equal(findmp, conf->find_multipaths); + if (r == MPATH_IS_ERROR || r == MPATH_IS_NOT_VALID) + return r; + + strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); + return r; +} + +int __wrap_libmultipath_init(void) +{ + int r = mock_type(int); + + assert_false(initialized); + if (r != 0) + return r; + initialized = true; + return 0; +} + +void __wrap_libmultipath_exit(void) +{ + assert_true(initialized); + initialized = false; +} + +int __wrap_dm_prereq(unsigned int *v) +{ + assert_ptr_not_equal(v, NULL); + return mock_type(int); +} + +int __real_init_config(const char *file); + +int __wrap_init_config(const char *file) +{ + int r = mock_type(int); + struct config *conf; + + assert_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; +} diff --git a/tests/parser.c b/tests/parser.c index 5772391..cf96d81 100644 --- a/tests/parser.c +++ b/tests/parser.c @@ -511,6 +511,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_config_parser(); return ret; } diff --git a/tests/pgpolicy.c b/tests/pgpolicy.c index 3f61b12..57ad338 100644 --- a/tests/pgpolicy.c +++ b/tests/pgpolicy.c @@ -1031,6 +1031,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_pgpolicies(); return ret; } diff --git a/tests/test-lib.c b/tests/test-lib.c index b7c09cc..f5542ed 100644 --- a/tests/test-lib.c +++ b/tests/test-lib.c @@ -56,6 +56,15 @@ int __wrap_execute_program(char *path, char *value, int len) 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) { @@ -248,17 +257,19 @@ void mock_pathinfo(int mask, const struct mocked_path *mp) } 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; @@ -339,6 +350,8 @@ struct multipath *__mock_multipath(struct vectors *vecs, struct path *pp) struct multipath *mp; struct config *conf; struct mocked_path mop; + /* pretend new dm, use minio_rq, */ + static const unsigned int fake_dm_tgt_version[3] = { 1, 1, 1 }; mocked_path_from_path(&mop, pp); /* pathinfo() call in adopt_paths */ @@ -351,7 +364,9 @@ struct multipath *__mock_multipath(struct vectors *vecs, struct path *pp) 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); diff --git a/tests/test-log.c b/tests/test-log.c index 1c901cb..14f25b9 100644 --- a/tests/test-log.c +++ b/tests/test-log.c @@ -7,8 +7,8 @@ #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; diff --git a/tests/test-log.h b/tests/test-log.h index 2c878c6..6d22cd2 100644 --- a/tests/test-log.h +++ b/tests/test-log.h @@ -1,7 +1,8 @@ #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 diff --git a/tests/uevent.c b/tests/uevent.c index 9ffcd2d..648ff26 100644 --- a/tests/uevent.c +++ b/tests/uevent.c @@ -322,6 +322,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_uevent_get_XXX(); return ret; } diff --git a/tests/unaligned.c b/tests/unaligned.c index 7ece1de..e43b64d 100644 --- a/tests/unaligned.c +++ b/tests/unaligned.c @@ -91,6 +91,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_unaligned(); return ret; } diff --git a/tests/util.c b/tests/util.c index c3c49b6..9affb0e 100644 --- a/tests/util.c +++ b/tests/util.c @@ -946,6 +946,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_basenamecpy(); ret += test_bitmasks(); ret += test_strlcpy(); diff --git a/tests/valid.c b/tests/valid.c index 693c72c..e7393a1 100644 --- a/tests/valid.c +++ b/tests/valid.c @@ -25,13 +25,18 @@ #include #include #include +#include + #include "globals.c" #include "util.h" #include "discovery.h" #include "wwids.h" #include "blacklist.h" +#include "foreign.h" #include "valid.h" +#define PATHINFO_REAL 9999 + int test_fd; struct udev_device { int unused; @@ -78,12 +83,66 @@ struct udev_device *__wrap_udev_device_new_from_subsystem_sysname(struct udev *u 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 @@ -128,6 +187,7 @@ enum { STAGE_IS_MULTIPATHED, STAGE_CHECK_MULTIPATHD, STAGE_GET_UDEV_DEVICE, + STAGE_PATHINFO_REAL, STAGE_PATHINFO, STAGE_FILTER_PROPERTY, STAGE_IS_FAILED, @@ -167,12 +227,25 @@ static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, 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); @@ -317,24 +390,24 @@ static void test_filter_property(void **state) /* 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); } @@ -481,6 +554,8 @@ int test_valid(void) int main(void) { int ret = 0; + + init_test_verbosity(-1); ret += test_valid(); return ret; } diff --git a/tests/vpd.c b/tests/vpd.c index e2ec65e..8e730d3 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -799,6 +799,7 @@ int main(void) { int ret = 0; + init_test_verbosity(-1); ret += test_vpd(); return ret; } diff --git a/third-party/valgrind/mpath-tools.supp b/third-party/valgrind/mpath-tools.supp new file mode 100644 index 0000000..0537fd5 --- /dev/null +++ b/third-party/valgrind/mpath-tools.supp @@ -0,0 +1,32 @@ +{ + 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 +}