From 933a8f5266fafcf35da760a4e3e60dafd3cd0d42 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Fri, 14 Jan 2022 13:50:19 +0900 Subject: [PATCH] Imported Upstream version 0.8.5 --- Makefile | 12 +- Makefile.inc | 18 +- README | 60 --- README.md | 87 ++++ kpartx/bsd.c | 16 +- kpartx/dasd.c | 7 +- kpartx/devmapper.c | 2 +- kpartx/gpt.c | 22 +- kpartx/kpartx.c | 94 ++-- kpartx/kpartx.h | 2 + kpartx/kpartx.rules | 8 +- libdmmp/libdmmp_private.h | 3 +- libmpathpersist/mpath_persist.c | 71 +-- libmpathpersist/mpath_pr_ioctl.c | 45 +- libmultipath/Makefile | 11 +- libmultipath/alias.c | 296 +++++++++++- libmultipath/alias.h | 15 +- libmultipath/blacklist.c | 204 ++++---- libmultipath/blacklist.h | 24 +- libmultipath/checkers.c | 26 +- libmultipath/checkers.h | 3 +- libmultipath/checkers/Makefile | 6 +- libmultipath/checkers/directio.c | 2 +- libmultipath/config.c | 12 +- libmultipath/config.h | 7 +- libmultipath/configure.c | 391 +++++++-------- libmultipath/configure.h | 8 +- libmultipath/defaults.h | 7 +- libmultipath/devmapper.c | 256 +++++++--- libmultipath/devmapper.h | 21 +- libmultipath/dict.c | 31 +- libmultipath/discovery.c | 190 +++++--- libmultipath/dmparser.c | 98 ++-- libmultipath/dmparser.h | 4 +- libmultipath/foreign.c | 6 +- libmultipath/foreign/nvme.c | 20 +- libmultipath/hwtable.c | 64 ++- libmultipath/io_err_stat.c | 25 +- libmultipath/{checkers => }/libsg.c | 0 libmultipath/{checkers => }/libsg.h | 0 libmultipath/parser.c | 23 +- libmultipath/pgpolicies.c | 12 +- libmultipath/print.c | 104 ++-- libmultipath/print.h | 2 +- libmultipath/prioritizers/Makefile | 2 +- libmultipath/prioritizers/alua_rtpg.c | 6 +- libmultipath/prioritizers/alua_spc3.h | 2 +- libmultipath/propsel.c | 16 +- libmultipath/structs.c | 75 ++- libmultipath/structs.h | 33 +- libmultipath/structs_vec.c | 399 +++++++++++++--- libmultipath/structs_vec.h | 19 +- libmultipath/sysfs.c | 30 +- libmultipath/sysfs.h | 2 +- libmultipath/uevent.c | 326 +------------ libmultipath/uevent.h | 47 +- libmultipath/util.c | 188 ++++---- libmultipath/util.h | 83 +++- libmultipath/valid.c | 118 +++++ libmultipath/valid.h | 42 ++ libmultipath/vector.c | 29 +- libmultipath/vector.h | 8 +- libmultipath/version.h | 4 +- libmultipath/wwids.c | 165 ++++--- mpathpersist/main.c | 65 ++- multipath/11-dm-mpath.rules | 2 +- multipath/main.c | 452 ++++++++---------- multipath/multipath.8 | 10 +- multipath/multipath.conf.5 | 39 +- multipathd/Makefile | 4 + multipathd/cli.c | 1 + multipathd/cli_handlers.c | 105 ++++- multipathd/cli_handlers.h | 1 + multipathd/dmevents.c | 6 +- multipathd/main.c | 357 +++++++++----- multipathd/main.h | 8 +- multipathd/waiter.c | 4 +- tests/Makefile | 29 +- tests/README.md | 8 + tests/alias.c | 8 +- tests/blacklist.c | 123 ++++- tests/devt.c | 192 ++++++++ tests/directio.c | 30 +- tests/hwtable.c | 5 + tests/parser.c | 42 ++ tests/test-lib.c | 8 +- tests/test-log.c | 10 +- tests/uevent.c | 4 - tests/util.c | 656 +++++++++++++++++++++++--- tests/valid.c | 486 +++++++++++++++++++ tests/vpd.c | 18 +- 91 files changed, 4495 insertions(+), 2087 deletions(-) delete mode 100644 README create mode 100644 README.md rename libmultipath/{checkers => }/libsg.c (100%) rename libmultipath/{checkers => }/libsg.h (100%) create mode 100644 libmultipath/valid.c create mode 100644 libmultipath/valid.h create mode 100644 tests/devt.c create mode 100644 tests/valid.c diff --git a/Makefile b/Makefile index 1dee368..4a3491d 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,13 @@ all: $(BUILDDIRS) $(BUILDDIRS): $(MAKE) -C $@ -multipath multipathd mpathpersist: libmultipath -mpathpersist: libmpathpersist +libmultipath libdmmp: libmpathcmd +libmpathpersist multipath multipathd: libmultipath +mpathpersist multipathd: libmpathpersist + +libmultipath/checkers.install \ + libmultipath/prioritizers.install \ + libmultipath/foreign.install: libmultipath.install $(BUILDDIRS.clean): $(MAKE) -C ${@:.clean=} clean @@ -47,6 +52,9 @@ uninstall: $(BUILDDIRS:=.uninstall) test: all $(MAKE) -C tests +valgrind-test: all + $(MAKE) -C tests valgrind + .PHONY: TAGS TAGS: etags -a libmultipath/*.c diff --git a/Makefile.inc b/Makefile.inc index d4d1e0d..e05f3a9 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -37,7 +37,7 @@ endif ifndef SYSTEMD ifeq ($(shell pkg-config --modversion libsystemd >/dev/null 2>&1 && echo 1), 1) - SYSTEMD = $(shell pkg-config --modversion libsystemd) + SYSTEMD = $(shell pkg-config --modversion libsystemd | awk '{print $$1}') else ifeq ($(shell systemctl --version >/dev/null 2>&1 && echo 1), 1) SYSTEMD = $(shell systemctl --version 2> /dev/null | \ @@ -91,20 +91,20 @@ TEST_CC_OPTION = $(shell \ STACKPROT := $(call TEST_CC_OPTION,-fstack-protector-strong,-fstack-protector) ERROR_DISCARDED_QUALIFIERS := $(call TEST_CC_OPTION,-Werror=discarded-qualifiers,) -WNOCLOBBERED := $(call TEST_CC_OPTION,-Wno-clobbered,) +WNOCLOBBERED := $(call TEST_CC_OPTION,-Wno-clobbered -Wno-error=clobbered,) -OPTFLAGS = -O2 -g -pipe -Werror -Wall -Wextra -Wformat=2 -Werror=implicit-int \ +OPTFLAGS := -O2 -g $(STACKPROT) --param=ssp-buffer-size=4 +WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 -Werror=implicit-int \ -Werror=implicit-function-declaration -Werror=format-security \ - $(WNOCLOBBERED) \ - -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) \ - $(STACKPROT) --param=ssp-buffer-size=4 + $(WNOCLOBBERED) -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) CPPFLAGS := -Wp,-D_FORTIFY_SOURCE=2 -CFLAGS := $(OPTFLAGS) -DBIN_DIR=\"$(bindir)\" -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\" \ - -MMD -MP $(CFLAGS) +CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe \ + -DBIN_DIR=\"$(bindir)\" -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\" \ + -MMD -MP BIN_CFLAGS = -fPIE -DPIE LIB_CFLAGS = -fPIC SHARED_FLAGS = -shared -LDFLAGS = -Wl,-z,relro -Wl,-z,now +LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,now BIN_LDFLAGS = -pie # Check whether a function with name $1 has been declared in header file $2. diff --git a/README b/README deleted file mode 100644 index 2fc4a81..0000000 --- a/README +++ /dev/null @@ -1,60 +0,0 @@ - multipath-tools for Linux - - -This package provides the following binaries to drive the Device Mapper -multipathing driver: - -multipath - Device mapper target autoconfig. -multipathd - Multipath daemon. -mpathpersist - Manages SCSI persistent reservations on dm multipath devices. -kpartx - Create device maps from partition tables. - - -Releases -======== -Tarballs are not generated anymore, to get a specific release do: -git clone https://git.opensvc.com/multipath-tools/.git -cd multipath-tools -git tag -git archive --format=tar.gz --prefix=multipath-tools-X.Y.Z/ X.Y.Z > ../multipath-tools-X.Y.Z.tar.gz - -Alternatively it may be obtained from gitweb, go to: -https://git.opensvc.com/?p=multipath-tools/.git;a=tags -select a release-tag and then click on "snapshot". Or get it with -wget "https://git.opensvc.com/?p=multipath-tools/.git;a=snapshot;sf=tgz;h=refs/tags/X.Y.Z" -O multipath-tools-X.Y.Z.tar.gz - - -Source code -=========== -To get latest devel code: git clone https://git.opensvc.com/multipath-tools/.git -Gitweb: https://git.opensvc.com/?p=multipath-tools/.git - - -Add storage devices -=================== -Follow the instructions in the libmultipath/hwtable.c header. - - -Mailing list (subscribers-only) -============ -To subscribe and archives: https://www.redhat.com/mailman/listinfo/dm-devel -Searchable: https://marc.info/?l=dm-devel - - -Changelog -========= -pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog -post-0.4.5: https://git.opensvc.com/?p=multipath-tools/.git;a=log - - -Maintainer -========== -Christophe Varoqui -Device-mapper development mailing list - -Licence -======= -The multipath-tools source code is covered by several different -licences. Refer to the individual source files for details. -Source files which do not specify a licence are shipped under -LGPL-2.0 (see LICENSES/LGPL-2.0). diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e3ef67 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +multipath-tools for Linux +************************* + +https://github.com/opensvc/multipath-tools + +This package provides the following binaries to drive the Device Mapper multipathing driver: + +* multipath - Device mapper target autoconfig. +* multipathd - Multipath daemon. +* mpathpersist - Manages SCSI persistent reservations on dm multipath devices. +* kpartx - Create device maps from partition tables. + + +Releases +======== + +To get a specific X.Y.Z release, use one of the following method: + + +Git +--- + + git clone https://github.com/opensvc/multipath-tools.git + cd multipath-tools + git tag + git archive --format=tar.gz --prefix=multipath-tools-X.Y.Z/ X.Y.Z > ../multipath-tools-X.Y.Z.tar.gz + + +Direct download +--------------- + + wget "https://github.com/opensvc/multipath-tools/archive/X.Y.Z.tar.gz" -O multipath-tools-X.Y.Z.tar.gz + + +Browser +------- + +Go to: https://github.com/opensvc/multipath-tools/tags +Select a release-tag and then click on "zip" or "tar.gz". + + +Source code +=========== + +To get latest devel code: + + git clone https://github.com/opensvc/multipath-tools.git + +Github page: https://github.com/opensvc/multipath-tools + + +Add storage devices +=================== + +Follow the instructions in the `libmultipath/hwtable.c` header. + + +Mailing list +============ + +(subscribers-only) +To subscribe and archives: https://www.redhat.com/mailman/listinfo/dm-devel +Searchable: https://marc.info/?l=dm-devel + + +Changelog +========= + +pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog +post-0.4.5: https://github.com/opensvc/multipath-tools/commits/master + + +Maintainer +========== + +Christophe Varoqui +Device-mapper development mailing list + + +Licence +======= + +The multipath-tools source code is covered by several different licences. +Refer to the individual source files for details. +Source files which do not specify a licence are shipped under LGPL-2.0 +(see `LICENSES/LGPL-2.0`). + diff --git a/kpartx/bsd.c b/kpartx/bsd.c index 0e661fb..950b0f9 100644 --- a/kpartx/bsd.c +++ b/kpartx/bsd.c @@ -1,6 +1,7 @@ #include "kpartx.h" #include +#define BSD_LABEL_OFFSET 64 #define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */ #define XBSD_MAXPARTITIONS 16 #define BSD_FS_UNUSED 0 @@ -60,8 +61,19 @@ read_bsd_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { return -1; l = (struct bsd_disklabel *) bp; - if (l->d_magic != BSD_DISKMAGIC) - return -1; + if (l->d_magic != BSD_DISKMAGIC) { + /* + * BSD disklabels can also start 64 bytes offset from the + * start of the first sector + */ + bp = getblock(fd, offset); + if (bp == NULL) + return -1; + + l = (struct bsd_disklabel *)(bp + 64); + if (l->d_magic != BSD_DISKMAGIC) + return -1; + } max_partitions = 16; if (l->d_npartitions < max_partitions) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 14b9d3a..f039864 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -22,6 +22,7 @@ * along with this program. If not, see . */ +#define _GNU_SOURCE #include #include #include @@ -117,13 +118,13 @@ read_dasd_pt(int fd, __attribute__((unused)) struct slice all, sprintf(pathname, "/dev/.kpartx-node-%u-%u", (unsigned int)major(dev), (unsigned int)minor(dev)); - if ((fd_dasd = open(pathname, O_RDONLY)) == -1) { + if ((fd_dasd = open(pathname, O_RDONLY | O_DIRECT)) == -1) { /* Devicenode does not exist. Try to create one */ if (mknod(pathname, 0600 | S_IFBLK, dev) == -1) { /* Couldn't create a device node */ return -1; } - fd_dasd = open(pathname, O_RDONLY); + fd_dasd = open(pathname, O_RDONLY | O_DIRECT); /* * The file will vanish when the last process (we) * has ceased to access it. @@ -175,7 +176,7 @@ read_dasd_pt(int fd, __attribute__((unused)) struct slice all, * Get volume label, extract name and type. */ - if (!(data = (unsigned char *)malloc(blocksize))) + if (aligned_malloc((void **)&data, blocksize, NULL)) goto out; diff --git a/kpartx/devmapper.c b/kpartx/devmapper.c index 86731ea..3efd6df 100644 --- a/kpartx/devmapper.c +++ b/kpartx/devmapper.c @@ -618,7 +618,7 @@ remove_partmap(const char *name, void *data) if (dm_get_opencount(name)) { if (rd->verbose) - printf("%s is in use. Not removing", name); + printf("%s is in use. Not removing\n", name); return 1; } if (!dm_simplecmd(DM_DEVICE_REMOVE, name, 0, 0)) { diff --git a/kpartx/gpt.c b/kpartx/gpt.c index 785b34e..f7fefb7 100644 --- a/kpartx/gpt.c +++ b/kpartx/gpt.c @@ -243,8 +243,7 @@ alloc_read_gpt_entries(int fd, gpt_header * gpt) if (!count) return NULL; - pte = (gpt_entry *)malloc(count); - if (!pte) + if (aligned_malloc((void **)&pte, get_sector_size(fd), &count)) return NULL; memset(pte, 0, count); @@ -269,12 +268,11 @@ static gpt_header * alloc_read_gpt_header(int fd, uint64_t lba) { gpt_header *gpt; - gpt = (gpt_header *) - malloc(sizeof (gpt_header)); - if (!gpt) + size_t size = sizeof (gpt_header); + if (aligned_malloc((void **)&gpt, get_sector_size(fd), &size)) return NULL; - memset(gpt, 0, sizeof (*gpt)); - if (!read_lba(fd, lba, gpt, sizeof (gpt_header))) { + memset(gpt, 0, size); + if (!read_lba(fd, lba, gpt, size)) { free(gpt); return NULL; } @@ -498,6 +496,7 @@ find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes) gpt_header *pgpt = NULL, *agpt = NULL; gpt_entry *pptes = NULL, *aptes = NULL; legacy_mbr *legacymbr = NULL; + size_t size = sizeof(legacy_mbr); uint64_t lastlba; if (!gpt || !ptes) return 0; @@ -526,11 +525,10 @@ find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes) } /* This will be added to the EFI Spec. per Intel after v1.02. */ - legacymbr = malloc(sizeof (*legacymbr)); - if (legacymbr) { - memset(legacymbr, 0, sizeof (*legacymbr)); - read_lba(fd, 0, (uint8_t *) legacymbr, - sizeof (*legacymbr)); + if (aligned_malloc((void **)&legacymbr, get_sector_size(fd), + &size) == 0) { + memset(legacymbr, 0, size); + read_lba(fd, 0, (uint8_t *) legacymbr, size); good_pmbr = is_pmbr_valid(legacymbr); free(legacymbr); legacymbr=NULL; diff --git a/kpartx/kpartx.c b/kpartx/kpartx.c index d3620c5..4a0aae9 100644 --- a/kpartx/kpartx.c +++ b/kpartx/kpartx.c @@ -19,6 +19,7 @@ * cva, 2002-10-26 */ +#define _GNU_SOURCE #include #include #include @@ -41,7 +42,6 @@ #define SIZE(a) (sizeof(a)/sizeof((a)[0])) -#define READ_SIZE 1024 #define MAXTYPES 64 #define MAXSLICES 256 #define DM_TARGET "linear" @@ -209,6 +209,23 @@ check_uuid(char *uuid, char *part_uuid, char **err_msg) { return 0; } +static void * +xmalloc (size_t size) { + void *t; + + if (size == 0) + return NULL; + + t = malloc (size); + + if (t == NULL) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + + return t; +} + int main(int argc, char **argv){ int i, j, m, n, op, off, arg, c, d, ro=0; @@ -383,12 +400,12 @@ main(int argc, char **argv){ mapname = device + off; if (delim == NULL) { - delim = malloc(DELIM_SIZE); + delim = xmalloc(DELIM_SIZE); memset(delim, 0, DELIM_SIZE); set_delimiter(mapname, delim); } - fd = open(device, O_RDONLY); + fd = open(device, O_RDONLY | O_DIRECT); if (fd == -1) { perror(device); @@ -635,6 +652,8 @@ main(int argc, char **argv){ if (!dm_simplecmd(DM_DEVICE_REMOVE, partname, 1, 0)) { + fprintf(stderr, "failed to remove %s", + partname); r++; continue; } @@ -668,31 +687,14 @@ end: return r; } -void * -xmalloc (size_t size) { - void *t; - - if (size == 0) - return NULL; - - t = malloc (size); - - if (t == NULL) { - fprintf(stderr, "Out of memory\n"); - exit(1); - } - - return t; -} - /* * sseek: seek to specified sector */ static int -sseek(int fd, unsigned int secnr) { +sseek(int fd, unsigned int secnr, int secsz) { off64_t in, out; - in = ((off64_t) secnr << 9); + in = ((off64_t) secnr * secsz); out = 1; if ((out = lseek64(fd, in, SEEK_SET)) != in) @@ -703,6 +705,31 @@ sseek(int fd, unsigned int secnr) { return 0; } +int +aligned_malloc(void **mem_p, size_t align, size_t *size_p) +{ + static size_t pgsize = 0; + size_t size; + int err; + + if (!mem_p || !align || (size_p && !*size_p)) + return EINVAL; + + if (!pgsize) + pgsize = getpagesize(); + + if (size_p) + size = ((*size_p + align - 1) / align) * align; + else + size = pgsize; + + err = posix_memalign(mem_p, pgsize, size); + if (!err && size_p) + *size_p = size; + return err; +} + +/* always in sector size blocks */ static struct block { unsigned int secnr; @@ -710,30 +737,39 @@ struct block { struct block *next; } *blockhead; +/* blknr is always in 512 byte blocks */ char * -getblock (int fd, unsigned int secnr) { +getblock (int fd, unsigned int blknr) { + int secsz = get_sector_size(fd); + unsigned int blks_per_sec = secsz / 512; + unsigned int secnr = blknr / blks_per_sec; + unsigned int blk_off = (blknr % blks_per_sec) * 512; struct block *bp; for (bp = blockhead; bp; bp = bp->next) if (bp->secnr == secnr) - return bp->block; + return bp->block + blk_off; - if (sseek(fd, secnr)) + if (sseek(fd, secnr, secsz)) return NULL; bp = xmalloc(sizeof(struct block)); bp->secnr = secnr; bp->next = blockhead; blockhead = bp; - bp->block = (char *) xmalloc(READ_SIZE); + if (aligned_malloc((void **)&bp->block, secsz, NULL)) { + fprintf(stderr, "aligned_malloc failed\n"); + exit(1); + } - if (read(fd, bp->block, READ_SIZE) != READ_SIZE) { + if (read(fd, bp->block, secsz) != secsz) { fprintf(stderr, "read error, sector %d\n", secnr); - bp->block = NULL; + blockhead = bp->next; + return NULL; } - return bp->block; + return bp->block + blk_off; } int diff --git a/kpartx/kpartx.h b/kpartx/kpartx.h index 67edeb8..727632c 100644 --- a/kpartx/kpartx.h +++ b/kpartx/kpartx.h @@ -1,6 +1,7 @@ #ifndef _KPARTX_H #define _KPARTX_H +#include #include #include @@ -61,6 +62,7 @@ extern ptreader read_mac_pt; extern ptreader read_sun_pt; extern ptreader read_ps3_pt; +int aligned_malloc(void **mem_p, size_t align, size_t *size_p); char *getblock(int fd, unsigned int secnr); static inline unsigned int diff --git a/kpartx/kpartx.rules b/kpartx/kpartx.rules index 8f99049..d7527d7 100644 --- a/kpartx/kpartx.rules +++ b/kpartx/kpartx.rules @@ -7,13 +7,17 @@ KERNEL!="dm-*", GOTO="kpartx_end" ACTION!="add|change", GOTO="kpartx_end" ENV{DM_UUID}!="?*", GOTO="kpartx_end" +ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", GOTO="kpartx_end" # Create dm tables for partitions on multipath devices. ENV{DM_UUID}!="mpath-?*", GOTO="mpath_kpartx_end" # DM_SUBSYSTEM_UDEV_FLAG1 is the "skip_kpartx" flag. -# For events not generated by libdevmapper, we need to fetch it from db. -ENV{DM_UDEV_PRIMARY_SOURCE_FLAG}!="1", IMPORT{db}="DM_SUBSYSTEM_UDEV_FLAG1" +# For events not generated by libdevmapper, we need to fetch it from db: +# - "change" events with DM_ACTIVATION!="1" (e.g. partition table changes) +# - "add" events for which rules are not disabled ("coldplug" case) +ENV{DM_ACTIVATION}!="1", IMPORT{db}="DM_SUBSYSTEM_UDEV_FLAG1" +ACTION=="add", IMPORT{db}="DM_SUBSYSTEM_UDEV_FLAG1" ENV{DM_SUBSYSTEM_UDEV_FLAG1}=="1", GOTO="mpath_kpartx_end" # 11-dm-mpath.rules sets MPATH_UNCHANGED for events that can be ignored. diff --git a/libdmmp/libdmmp_private.h b/libdmmp/libdmmp_private.h index ac85b63..b1a6dde 100644 --- a/libdmmp/libdmmp_private.h +++ b/libdmmp/libdmmp_private.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "libdmmp/libdmmp.h" @@ -82,7 +83,7 @@ static out_type func_name(struct dmmp_context *ctx, const char *var_name) { \ do { \ json_type j_type = json_type_null; \ json_object *j_obj_tmp = NULL; \ - if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != TRUE) { \ + if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != true) { \ _error(ctx, "Invalid JSON output from multipathd IPC: " \ "key '%s' not found", key); \ rc = DMMP_ERR_IPC_ERROR; \ diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c index 3da7a6c..1f9817e 100644 --- a/libmpathpersist/mpath_persist.c +++ b/libmpathpersist/mpath_persist.c @@ -65,52 +65,6 @@ mpath_lib_exit (struct config *conf) return 0; } -static int -updatepaths (struct multipath * mpp) -{ - int i, j; - struct pathgroup * pgp; - struct path * pp; - struct config *conf; - - if (!mpp->pg) - return 0; - - vector_foreach_slot (mpp->pg, pgp, i){ - if (!pgp->paths) - continue; - - vector_foreach_slot (pgp->paths, pp, j){ - if (!strlen(pp->dev)){ - /* - * path is not in sysfs anymore - */ - pp->state = PATH_DOWN; - continue; - } - pp->mpp = mpp; - if (pp->udev == NULL) { - pp->udev = get_udev_device(pp->dev_t, DEV_DEVT); - if (pp->udev == NULL) { - pp->state = PATH_DOWN; - continue; - } - conf = get_multipath_config(); - pathinfo(pp, conf, DI_SYSFS|DI_CHECKER); - put_multipath_config(conf); - continue; - } - if (pp->state == PATH_UNCHECKED || - pp->state == PATH_WILD) { - conf = get_multipath_config(); - pathinfo(pp, conf, DI_CHECKER); - put_multipath_config(conf); - } - } - } - return 0; -} - int mpath_prin_activepath (struct multipath *mpp, int rq_servact, struct prin_resp * resp, int noisy) @@ -369,7 +323,6 @@ get_mpvec (vector curmp, vector pathvec, char * refwwid) { int i; struct multipath *mpp; - char params[PARAMS_SIZE], status[PARAMS_SIZE]; vector_foreach_slot (curmp, mpp, i){ /* @@ -387,20 +340,12 @@ get_mpvec (vector curmp, vector pathvec, char * refwwid) if (refwwid && strncmp (mpp->alias, refwwid, WWID_SIZE - 1)) continue; - dm_get_map(mpp->alias, &mpp->size, params); - condlog(3, "params = %s", params); - dm_get_status(mpp->alias, status); - condlog(3, "status = %s", status); - disassemble_map (pathvec, params, mpp, 0); - - /* - * disassemble_map() can add new paths to pathvec. - * If not in "fast list mode", we need to fetch information - * about them - */ - updatepaths(mpp); - disassemble_status (status, mpp); - + if (update_multipath_table(mpp, pathvec, DI_CHECKER) != DMP_OK || + update_multipath_status(mpp) != DMP_OK) { + condlog(1, "error parsing map %s", mpp->wwid); + remove_map(mpp, pathvec, curmp, PURGE_VEC); + i--; + } } return MPATH_PR_SUCCESS ; } @@ -436,7 +381,7 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, all_tg_pt = (mpp->all_tg_pt == ALL_TG_PT_ON || paramp->sa_flags & MPATH_F_ALL_TG_PT_MASK); - active_pathcount = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST); + active_pathcount = count_active_paths(mpp); if (active_pathcount == 0) { condlog (0, "%s: no path available", mpp->wwid); @@ -648,7 +593,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, if (!mpp) return MPATH_PR_DMMP_ERROR; - active_pathcount = pathcount (mpp, PATH_UP) + pathcount (mpp, PATH_GHOST); + active_pathcount = count_active_paths(mpp); struct threadinfo thread[active_pathcount]; memset(thread, 0, sizeof(thread)); diff --git a/libmpathpersist/mpath_pr_ioctl.c b/libmpathpersist/mpath_pr_ioctl.c index 74b26b0..126601c 100644 --- a/libmpathpersist/mpath_pr_ioctl.c +++ b/libmpathpersist/mpath_pr_ioctl.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -138,38 +139,64 @@ retry : return status; } +/* + * Helper macro to avoid overflow of prout_param_descriptor in + * format_transportids(). Data must not be written past + * MPATH_MAX_PARAM_LEN bytes from struct prout_param_descriptor. + */ +#define check_overflow(ofs, n, start, label) \ + do { \ + if ((ofs) + (n) + \ + offsetof(struct prout_param_descriptor, private_buffer) \ + > MPATH_MAX_PARAM_LEN) \ + { \ + (ofs) = (start); \ + goto label; \ + } \ + } while(0) + uint32_t format_transportids(struct prout_param_descriptor *paramp) { unsigned int i = 0, len; uint32_t buff_offset = 4; - memset(paramp->private_buffer, 0, MPATH_MAX_PARAM_LEN); + memset(paramp->private_buffer, 0, sizeof(paramp->private_buffer)); for (i=0; i < paramp->num_transportid; i++ ) { + uint32_t start_offset = buff_offset; + + check_overflow(buff_offset, 1, start_offset, end_loop); paramp->private_buffer[buff_offset] = (uint8_t)((paramp->trnptid_list[i]->format_code & 0xff)| (paramp->trnptid_list[i]->protocol_id & 0xff)); buff_offset += 1; switch(paramp->trnptid_list[i]->protocol_id) { case MPATH_PROTOCOL_ID_FC: + check_overflow(buff_offset, 7 + 8 + 8, + start_offset, end_loop); buff_offset += 7; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->n_port_name, 8); buff_offset +=8 ; buff_offset +=8 ; break; case MPATH_PROTOCOL_ID_SAS: + check_overflow(buff_offset, 3 + 12, + start_offset, end_loop); buff_offset += 3; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->sas_address, 8); buff_offset += 12; break; case MPATH_PROTOCOL_ID_ISCSI: - buff_offset += 1; len = (paramp->trnptid_list[i]->iscsi_name[1] & 0xff)+2; + check_overflow(buff_offset, 1 + len, + start_offset, end_loop); + buff_offset += 1; memcpy(¶mp->private_buffer[buff_offset], ¶mp->trnptid_list[i]->iscsi_name,len); buff_offset += len ; break; } } +end_loop: buff_offset -= 4; paramp->private_buffer[0] = (unsigned char)((buff_offset >> 24) & 0xff); paramp->private_buffer[1] = (unsigned char)((buff_offset >> 16) & 0xff); @@ -211,6 +238,8 @@ static void mpath_format_readfullstatus(struct prin_resp *pr_buff) uint32_t additional_length, k, tid_len_len = 0; char tempbuff[MPATH_MAX_PARAM_LEN]; struct prin_fulldescr fdesc; + static const unsigned int pbuf_size = + sizeof(pr_buff->prin_descriptor.prin_readfd.private_buffer); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readfd.prgeneration); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readfd.number_of_descriptor); @@ -222,16 +251,18 @@ static void mpath_format_readfullstatus(struct prin_resp *pr_buff) } additional_length = pr_buff->prin_descriptor.prin_readfd.number_of_descriptor; - if (additional_length > MPATH_MAX_PARAM_LEN) { + if (additional_length > pbuf_size) { condlog(3, "PRIN length %u exceeds max length %d", additional_length, - MPATH_MAX_PARAM_LEN); + pbuf_size); return; } memset(&fdesc, 0, sizeof(struct prin_fulldescr)); - memcpy( tempbuff, pr_buff->prin_descriptor.prin_readfd.private_buffer,MPATH_MAX_PARAM_LEN ); - memset(&pr_buff->prin_descriptor.prin_readfd.private_buffer, 0, MPATH_MAX_PARAM_LEN); + memcpy( tempbuff, pr_buff->prin_descriptor.prin_readfd.private_buffer, + pbuf_size); + memset(&pr_buff->prin_descriptor.prin_readfd.private_buffer, 0, + pbuf_size); p =(unsigned char *)tempbuff; ppbuff = (char *)pr_buff->prin_descriptor.prin_readfd.private_buffer; @@ -543,5 +574,7 @@ int get_prin_length(int rq_servact) mx_resp_len = 0; break; } + if (mx_resp_len > MPATH_MAX_PARAM_LEN) + mx_resp_len = MPATH_MAX_PARAM_LEN; return mx_resp_len; } diff --git a/libmultipath/Makefile b/libmultipath/Makefile index e5651e4..62ba16e 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -24,6 +24,10 @@ ifneq ($(call check_func,dm_task_no_flush,/usr/include/libdevmapper.h),0) CFLAGS += -DLIBDM_API_FLUSH -D_GNU_SOURCE endif +ifneq ($(call check_func,dm_task_get_errno,/usr/include/libdevmapper.h),0) + CFLAGS += -DLIBDM_API_GET_ERRNO +endif + ifneq ($(call check_func,dm_task_set_cookie,/usr/include/libdevmapper.h),0) CFLAGS += -DLIBDM_API_COOKIE endif @@ -36,6 +40,10 @@ ifneq ($(call check_func,dm_task_deferred_remove,/usr/include/libdevmapper.h),0) CFLAGS += -DLIBDM_API_DEFERRED endif +ifneq ($(call check_func,dm_hold_control_dev,/usr/include/libdevmapper.h),0) + CFLAGS += -DLIBDM_API_HOLD_CONTROL +endif + OBJS = memory.o parser.o vector.o devmapper.o callout.o \ hwtable.o blacklist.o util.o dmparser.o config.o \ structs.o discovery.o propsel.o dict.o \ @@ -43,7 +51,8 @@ OBJS = memory.o parser.o vector.o devmapper.o callout.o \ switchgroup.o uxsock.o print.o alias.o log_pthread.o \ log.o configure.o structs_vec.o sysfs.o prio.o checkers.o \ lock.o file.o wwids.o prioritizers/alua_rtpg.o prkey.o \ - io_err_stat.o dm-generic.o generic.o foreign.o nvme-lib.o + io_err_stat.o dm-generic.o generic.o foreign.o nvme-lib.o \ + libsg.o valid.o all: $(LIBS) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 14401ca..a7ba485 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -4,6 +4,7 @@ */ #include #include +#include #include #include #include @@ -17,6 +18,9 @@ #include "vector.h" #include "checkers.h" #include "structs.h" +#include "config.h" +#include "util.h" +#include "errno.h" /* @@ -37,6 +41,17 @@ * See the file COPYING included with this distribution for more details. */ +#define BINDINGS_FILE_HEADER \ +"# Multipath bindings, Version : 1.0\n" \ +"# NOTE: this file is automatically maintained by the multipath program.\n" \ +"# You should not need to edit this file in normal circumstances.\n" \ +"#\n" \ +"# Format:\n" \ +"# alias wwid\n" \ +"#\n" + +static const char bindings_file_header[] = BINDINGS_FILE_HEADER; + int valid_alias(const char *alias) { @@ -126,14 +141,14 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, rewind(f); while (fgets(buf, LINE_MAX, f)) { const char *alias, *wwid; - char *c; + char *c, *saveptr; int curr_id; line_nr++; c = strpbrk(buf, "#\n\r"); if (c) *c = '\0'; - alias = strtok(buf, " \t"); + alias = strtok_r(buf, " \t", &saveptr); if (!alias) /* blank line */ continue; curr_id = scan_devname(alias, prefix); @@ -149,7 +164,7 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, biggest_id = curr_id; if (curr_id > id && curr_id < smallest_bigger_id) smallest_bigger_id = curr_id; - wwid = strtok(NULL, " \t"); + wwid = strtok_r(NULL, " \t", &saveptr); if (!wwid){ condlog(3, "Ignoring malformed line %u in bindings file", @@ -191,17 +206,17 @@ rlookup_binding(FILE *f, char *buff, const char *map_alias) buff[0] = '\0'; while (fgets(line, LINE_MAX, f)) { - char *c; + char *c, *saveptr; const char *alias, *wwid; line_nr++; c = strpbrk(line, "#\n\r"); if (c) *c = '\0'; - alias = strtok(line, " \t"); + alias = strtok_r(line, " \t", &saveptr); if (!alias) /* blank line */ continue; - wwid = strtok(NULL, " \t"); + wwid = strtok_r(NULL, " \t", &saveptr); if (!wwid){ condlog(3, "Ignoring malformed line %u in bindings file", @@ -215,7 +230,7 @@ rlookup_binding(FILE *f, char *buff, const char *map_alias) } if (strcmp(alias, map_alias) == 0){ condlog(3, "Found matching alias [%s] in bindings file." - "\nSetting wwid to %s", alias, wwid); + " Setting wwid to %s", alias, wwid); strlcpy(buff, wwid, WWID_SIZE); return 0; } @@ -287,7 +302,7 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, char buff[WWID_SIZE]; FILE *f; - fd = open_file(file, &can_write, BINDINGS_FILE_HEADER); + fd = open_file(file, &can_write, bindings_file_header); if (fd < 0) return NULL; @@ -361,7 +376,7 @@ get_user_friendly_alias(const char *wwid, const char *file, const char *prefix, return NULL; } - fd = open_file(file, &can_write, BINDINGS_FILE_HEADER); + fd = open_file(file, &can_write, bindings_file_header); if (fd < 0) return NULL; @@ -406,7 +421,7 @@ get_user_friendly_wwid(const char *alias, char *buff, const char *file) return -1; } - fd = open_file(file, &unused, BINDINGS_FILE_HEADER); + fd = open_file(file, &unused, bindings_file_header); if (fd < 0) return -1; @@ -427,3 +442,264 @@ get_user_friendly_wwid(const char *alias, char *buff, const char *file) fclose(f); return 0; } + +struct binding { + char *alias; + char *wwid; +}; + +static void _free_binding(struct binding *bdg) +{ + free(bdg->wwid); + free(bdg->alias); + free(bdg); +} + +/* + * Perhaps one day we'll implement this more efficiently, thus use + * an abstract type. + */ +typedef struct _vector Bindings; + +static void free_bindings(Bindings *bindings) +{ + struct binding *bdg; + int i; + + vector_foreach_slot(bindings, bdg, i) + _free_binding(bdg); + vector_reset(bindings); +} + +enum { + BINDING_EXISTS, + BINDING_CONFLICT, + BINDING_ADDED, + BINDING_DELETED, + BINDING_NOTFOUND, + BINDING_ERROR, +}; + +static int add_binding(Bindings *bindings, const char *alias, const char *wwid) +{ + struct binding *bdg; + int i, cmp = 0; + + /* + * Keep the bindings array sorted by alias. + * Optimization: Search backwards, assuming that the bindings file is + * sorted already. + */ + vector_foreach_slot_backwards(bindings, bdg, i) { + if ((cmp = strcmp(bdg->alias, alias)) <= 0) + break; + } + + /* Check for exact match */ + if (i >= 0 && cmp == 0) + return strcmp(bdg->wwid, wwid) ? + BINDING_CONFLICT : BINDING_EXISTS; + + i++; + bdg = calloc(1, sizeof(*bdg)); + if (bdg) { + bdg->wwid = strdup(wwid); + bdg->alias = strdup(alias); + if (bdg->wwid && bdg->alias && + vector_insert_slot(bindings, i, bdg)) + return BINDING_ADDED; + else + _free_binding(bdg); + } + + return BINDING_ERROR; +} + +static int write_bindings_file(const Bindings *bindings, int fd) +{ + struct binding *bnd; + char line[LINE_MAX]; + int i; + + if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) + != sizeof(BINDINGS_FILE_HEADER) - 1) + return -1; + + vector_foreach_slot(bindings, bnd, i) { + int len; + + len = snprintf(line, sizeof(line), "%s %s\n", + bnd->alias, bnd->wwid); + + if (len < 0 || (size_t)len >= sizeof(line)) { + condlog(1, "%s: line overflow", __func__); + return -1; + } + + if (write(fd, line, len) != len) + return -1; + } + return 0; +} + +static int fix_bindings_file(const struct config *conf, + const Bindings *bindings) +{ + int rc; + long fd; + char tempname[PATH_MAX]; + + if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) + return -1; + if ((fd = mkstemp(tempname)) == -1) { + condlog(1, "%s: mkstemp: %m", __func__); + return -1; + } + pthread_cleanup_push(close_fd, (void*)fd); + rc = write_bindings_file(bindings, fd); + pthread_cleanup_pop(1); + if (rc == -1) { + condlog(1, "failed to write new bindings file %s", + tempname); + unlink(tempname); + return rc; + } + if ((rc = rename(tempname, conf->bindings_file)) == -1) + condlog(0, "%s: rename: %m", __func__); + else + condlog(1, "updated bindings file %s", conf->bindings_file); + return rc; +} + +static int _check_bindings_file(const struct config *conf, FILE *file, + Bindings *bindings) +{ + int rc = 0; + unsigned int linenr = 0; + char *line = NULL; + size_t line_len = 0; + ssize_t n; + + pthread_cleanup_push(cleanup_free_ptr, &line); + while ((n = getline(&line, &line_len, file)) >= 0) { + char *c, *alias, *wwid, *saveptr; + const char *mpe_wwid; + + linenr++; + c = strpbrk(line, "#\n\r"); + if (c) + *c = '\0'; + alias = strtok_r(line, " \t", &saveptr); + if (!alias) /* blank line */ + continue; + wwid = strtok_r(NULL, " \t", &saveptr); + if (!wwid) { + condlog(1, "invalid line %d in bindings file, missing WWID", + linenr); + continue; + } + c = strtok_r(NULL, " \t", &saveptr); + if (c) + /* This is non-fatal */ + condlog(1, "invalid line %d in bindings file, extra args \"%s\"", + linenr, c); + + mpe_wwid = get_mpe_wwid(conf->mptable, alias); + if (mpe_wwid && strcmp(mpe_wwid, wwid)) { + condlog(0, "ERROR: alias \"%s\" for WWID %s in bindings file " + "on line %u conflicts with multipath.conf entry for %s", + alias, wwid, linenr, mpe_wwid); + rc = -1; + continue; + } + + switch (add_binding(bindings, alias, wwid)) { + case BINDING_CONFLICT: + condlog(0, "ERROR: multiple bindings for alias \"%s\" in " + "bindings file on line %u, discarding binding to WWID %s", + alias, linenr, wwid); + rc = -1; + break; + case BINDING_EXISTS: + condlog(2, "duplicate line for alias %s in bindings file on line %u", + alias, linenr); + break; + case BINDING_ERROR: + condlog(2, "error adding binding %s -> %s", + alias, wwid); + break; + default: + break; + } + } + pthread_cleanup_pop(1); + return rc; +} + +static void cleanup_fclose(void *p) +{ + fclose(p); +} + +/* + * check_alias_settings(): test for inconsistent alias configuration + * + * It's a fatal configuration error if the same alias is assigned to + * multiple WWIDs. In the worst case, it can cause data corruption + * by mangling devices with different WWIDs into the same multipath map. + * This function tests the configuration from multipath.conf and the + * bindings file for consistency, drops inconsistent multipath.conf + * alias settings, and rewrites the bindings file if necessary, dropping + * conflicting lines (if user_friendly_names is on, multipathd will + * fill in the deleted lines with a newly generated alias later). + * Note that multipath.conf is not rewritten. Use "multipath -T" for that. + * + * Returns: 0 in case of success, -1 if the configuration was bad + * and couldn't be fixed. + */ +int check_alias_settings(const struct config *conf) +{ + int can_write; + int rc = 0, i, fd; + Bindings bindings = {.allocated = 0, }; + struct mpentry *mpe; + + pthread_cleanup_push_cast(free_bindings, &bindings); + vector_foreach_slot(conf->mptable, mpe, i) { + if (!mpe->wwid || !mpe->alias) + continue; + if (add_binding(&bindings, mpe->alias, mpe->wwid) == + BINDING_CONFLICT) { + condlog(0, "ERROR: alias \"%s\" bound to multiple wwids in multipath.conf, " + "discarding binding to %s", + mpe->alias, mpe->wwid); + free(mpe->alias); + mpe->alias = NULL; + } + } + /* This clears the bindings */ + pthread_cleanup_pop(1); + + pthread_cleanup_push_cast(free_bindings, &bindings); + fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); + if (fd != -1) { + FILE *file = fdopen(fd, "r"); + + if (file != NULL) { + pthread_cleanup_push(cleanup_fclose, file); + rc = _check_bindings_file(conf, file, &bindings); + pthread_cleanup_pop(1); + if (rc == -1 && can_write && !conf->bindings_read_only) + rc = fix_bindings_file(conf, &bindings); + else if (rc == -1) + condlog(0, "ERROR: bad settings in read-only bindings file %s", + conf->bindings_file); + } else { + condlog(1, "failed to fdopen %s: %m", + conf->bindings_file); + close(fd); + } + } + pthread_cleanup_pop(1); + return rc; +} diff --git a/libmultipath/alias.h b/libmultipath/alias.h index 7c4b302..dbc950c 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -1,11 +1,5 @@ -#define BINDINGS_FILE_HEADER \ -"# Multipath bindings, Version : 1.0\n" \ -"# NOTE: this file is automatically maintained by the multipath program.\n" \ -"# You should not need to edit this file in normal circumstances.\n" \ -"#\n" \ -"# Format:\n" \ -"# alias wwid\n" \ -"#\n" +#ifndef _ALIAS_H +#define _ALIAS_H int valid_alias(const char *alias); char *get_user_friendly_alias(const char *wwid, const char *file, @@ -15,3 +9,8 @@ int get_user_friendly_wwid(const char *alias, char *buff, const char *file); char *use_existing_alias (const char *wwid, const char *file, const char *alias_old, const char *prefix, int bindings_read_only); + +struct config; +int check_alias_settings(const struct config *); + +#endif /* _ALIAS_H */ diff --git a/libmultipath/blacklist.c b/libmultipath/blacklist.c index 00e8dbd..6c6a597 100644 --- a/libmultipath/blacklist.c +++ b/libmultipath/blacklist.c @@ -15,13 +15,33 @@ #include "structs_vec.h" #include "print.h" -int store_ble(vector blist, char * str, int origin) +char *check_invert(char *str, bool *invert) +{ + if (str[0] == '!') { + *invert = true; + return str + 1; + } + if (str[0] == '\\' && str[1] == '!') { + *invert = false; + return str + 1; + } + *invert = false; + return str; +} + +int store_ble(vector blist, const char *str, int origin) { struct blentry * ble; + char *regex_str; + char *strdup_str = NULL; if (!str) return 0; + strdup_str = strdup(str); + if (!strdup_str) + return 1; + if (!blist) goto out; @@ -30,32 +50,37 @@ int store_ble(vector blist, char * str, int origin) if (!ble) goto out; - if (regcomp(&ble->regex, str, REG_EXTENDED|REG_NOSUB)) + regex_str = check_invert(strdup_str, &ble->invert); + if (regcomp(&ble->regex, regex_str, REG_EXTENDED|REG_NOSUB)) goto out1; if (!vector_alloc_slot(blist)) goto out1; - ble->str = str; + ble->str = strdup_str; ble->origin = origin; vector_set_slot(blist, ble); return 0; out1: FREE(ble); out: - FREE(str); + FREE(strdup_str); return 1; } int alloc_ble_device(vector blist) { - struct blentry_device * ble = MALLOC(sizeof(struct blentry_device)); + struct blentry_device *ble; + if (!blist) + return 1; + + ble = MALLOC(sizeof(struct blentry_device)); if (!ble) return 1; - if (!blist || !vector_alloc_slot(blist)) { + if (!vector_alloc_slot(blist)) { FREE(ble); return 1; } @@ -63,9 +88,12 @@ int alloc_ble_device(vector blist) return 0; } -int set_ble_device(vector blist, char * vendor, char * product, int origin) +int set_ble_device(vector blist, const char *vendor, const char *product, int origin) { struct blentry_device * ble; + char *regex_str; + char *vendor_str = NULL; + char *product_str = NULL; if (!blist) return 1; @@ -76,79 +104,56 @@ int set_ble_device(vector blist, char * vendor, char * product, int origin) return 1; if (vendor) { - if (regcomp(&ble->vendor_reg, vendor, - REG_EXTENDED|REG_NOSUB)) { - FREE(vendor); - if (product) - FREE(product); - return 1; - } - ble->vendor = vendor; + vendor_str = STRDUP(vendor); + if (!vendor_str) + goto out; + + regex_str = check_invert(vendor_str, &ble->vendor_invert); + if (regcomp(&ble->vendor_reg, regex_str, REG_EXTENDED|REG_NOSUB)) + goto out; + + ble->vendor = vendor_str; } if (product) { - if (regcomp(&ble->product_reg, product, - REG_EXTENDED|REG_NOSUB)) { - FREE(product); - if (vendor) { - ble->vendor = NULL; - FREE(vendor); - } - return 1; - } - ble->product = product; - } - ble->origin = origin; - return 0; -} + product_str = STRDUP(product); + if (!product_str) + goto out1; -int -_blacklist_exceptions (vector elist, const char * str) -{ - int i; - struct blentry * ele; + regex_str = check_invert(product_str, &ble->product_invert); + if (regcomp(&ble->product_reg, regex_str, REG_EXTENDED|REG_NOSUB)) + goto out1; - vector_foreach_slot (elist, ele, i) { - if (!regexec(&ele->regex, str, 0, NULL, 0)) - return 1; + ble->product = product_str; } + ble->origin = origin; return 0; +out1: + if (vendor) { + regfree(&ble->vendor_reg); + ble->vendor = NULL; + } +out: + free(vendor_str); + free(product_str); + return 1; } -int -_blacklist (vector blist, const char * str) +static int +match_reglist (const struct _vector *blist, const char *str) { int i; struct blentry * ble; vector_foreach_slot (blist, ble, i) { - if (!regexec(&ble->regex, str, 0, NULL, 0)) - return 1; - } - return 0; -} - -int -_blacklist_exceptions_device(const struct _vector *elist, const char * vendor, - const char * product) -{ - int i; - struct blentry_device * ble; - - vector_foreach_slot (elist, ble, i) { - if (!ble->vendor && !ble->product) - continue; - if ((!ble->vendor || - !regexec(&ble->vendor_reg, vendor, 0, NULL, 0)) && - (!ble->product || - !regexec(&ble->product_reg, product, 0, NULL, 0))) + if (!!regexec(&ble->regex, str, 0, NULL, 0) == ble->invert) return 1; } return 0; } -int -_blacklist_device (const struct _vector *blist, const char * vendor, - const char * product) +static int +match_reglist_device (const struct _vector *blist, const char *vendor, + const char * product) { int i; struct blentry_device * ble; @@ -157,17 +162,19 @@ _blacklist_device (const struct _vector *blist, const char * vendor, if (!ble->vendor && !ble->product) continue; if ((!ble->vendor || - !regexec(&ble->vendor_reg, vendor, 0, NULL, 0)) && + !!regexec(&ble->vendor_reg, vendor, 0, NULL, 0) == + ble->vendor_invert) && (!ble->product || - !regexec(&ble->product_reg, product, 0, NULL, 0))) + !!regexec(&ble->product_reg, product, 0, NULL, 0) == + ble->product_invert)) return 1; } return 0; } static int -find_blacklist_device (const struct _vector *blist, const char * vendor, - const char * product) +find_blacklist_device (const struct _vector *blist, const char *vendor, + const char *product) { int i; struct blentry_device * ble; @@ -189,25 +196,12 @@ setup_default_blist (struct config * conf) { struct blentry * ble; struct hwentry *hwe; - char * str; int i; - str = STRDUP("^(ram|zram|raw|loop|fd|md|dm-|sr|scd|st|dcssblk)[0-9]"); - if (!str) - return 1; - if (store_ble(conf->blist_devnode, str, ORIGIN_DEFAULT)) - return 1; - - str = STRDUP("^(td|hd|vd)[a-z]"); - if (!str) - return 1; - if (store_ble(conf->blist_devnode, str, ORIGIN_DEFAULT)) + if (store_ble(conf->blist_devnode, "!^(sd[a-z]|dasd[a-z]|nvme[0-9])", ORIGIN_DEFAULT)) return 1; - str = STRDUP("(SCSI_IDENT_|ID_WWN)"); - if (!str) - return 1; - if (store_ble(conf->elist_property, str, ORIGIN_DEFAULT)) + if (store_ble(conf->elist_property, "(SCSI_IDENT_|ID_WWN)", ORIGIN_DEFAULT)) return 1; vector_foreach_slot (conf->hwtable, hwe, i) { @@ -219,9 +213,7 @@ setup_default_blist (struct config * conf) return 1; ble = VECTOR_SLOT(conf->blist_device, VECTOR_SIZE(conf->blist_device) - 1); - if (set_ble_device(conf->blist_device, - STRDUP(hwe->vendor), - STRDUP(hwe->bl_product), + if (set_ble_device(conf->blist_device, hwe->vendor, hwe->bl_product, ORIGIN_DEFAULT)) { FREE(ble); vector_del_slot(conf->blist_device, VECTOR_SIZE(conf->blist_device) - 1); @@ -248,8 +240,9 @@ setup_default_blist (struct config * conf) condlog(lvl, "%s: %s %s", dev, (M), (S)) static void -log_filter (const char *dev, char *vendor, char *product, char *wwid, - const char *env, const char *protocol, int r, int lvl) +log_filter (const char *dev, const char *vendor, const char *product, + const char *wwid, const char *env, const char *protocol, + int r, int lvl) { /* * Try to sort from most likely to least. @@ -294,15 +287,15 @@ log_filter (const char *dev, char *vendor, char *product, char *wwid, } int -filter_device (vector blist, vector elist, char * vendor, char * product, - char * dev) +filter_device (const struct _vector *blist, const struct _vector *elist, + const char *vendor, const char * product, const char *dev) { int r = MATCH_NOTHING; if (vendor && product) { - if (_blacklist_exceptions_device(elist, vendor, product)) + if (match_reglist_device(elist, vendor, product)) r = MATCH_DEVICE_BLIST_EXCEPT; - else if (_blacklist_device(blist, vendor, product)) + else if (match_reglist_device(blist, vendor, product)) r = MATCH_DEVICE_BLIST; } @@ -311,14 +304,15 @@ filter_device (vector blist, vector elist, char * vendor, char * product, } int -filter_devnode (vector blist, vector elist, char * dev) +filter_devnode (const struct _vector *blist, const struct _vector *elist, + const char *dev) { int r = MATCH_NOTHING; if (dev) { - if (_blacklist_exceptions(elist, dev)) + if (match_reglist(elist, dev)) r = MATCH_DEVNODE_BLIST_EXCEPT; - else if (_blacklist(blist, dev)) + else if (match_reglist(blist, dev)) r = MATCH_DEVNODE_BLIST; } @@ -327,14 +321,15 @@ filter_devnode (vector blist, vector elist, char * dev) } int -filter_wwid (vector blist, vector elist, char * wwid, char * dev) +filter_wwid (const struct _vector *blist, const struct _vector *elist, + const char *wwid, const char *dev) { int r = MATCH_NOTHING; if (wwid) { - if (_blacklist_exceptions(elist, wwid)) + if (match_reglist(elist, wwid)) r = MATCH_WWID_BLIST_EXCEPT; - else if (_blacklist(blist, wwid)) + else if (match_reglist(blist, wwid)) r = MATCH_WWID_BLIST; } @@ -343,7 +338,8 @@ filter_wwid (vector blist, vector elist, char * wwid, char * dev) } int -filter_protocol(vector blist, vector elist, struct path * pp) +filter_protocol(const struct _vector *blist, const struct _vector *elist, + const struct path *pp) { char buf[PROTOCOL_BUF_SIZE]; int r = MATCH_NOTHING; @@ -351,9 +347,9 @@ filter_protocol(vector blist, vector elist, struct path * pp) if (pp) { snprint_path_protocol(buf, sizeof(buf), pp); - if (_blacklist_exceptions(elist, buf)) + if (match_reglist(elist, buf)) r = MATCH_PROTOCOL_BLIST_EXCEPT; - else if (_blacklist(blist, buf)) + else if (match_reglist(blist, buf)) r = MATCH_PROTOCOL_BLIST; } @@ -362,7 +358,7 @@ filter_protocol(vector blist, vector elist, struct path * pp) } int -filter_path (struct config * conf, struct path * pp) +filter_path (const struct config *conf, const struct path *pp) { int r; @@ -384,8 +380,8 @@ filter_path (struct config * conf, struct path * pp) } int -filter_property(struct config *conf, struct udev_device *udev, int lvl, - const char *uid_attribute) +filter_property(const struct config *conf, struct udev_device *udev, + int lvl, const char *uid_attribute) { const char *devname = udev_device_get_sysname(udev); struct udev_list_entry *list_entry; @@ -422,11 +418,11 @@ filter_property(struct config *conf, struct udev_device *udev, int lvl, if (check_missing_prop && !strcmp(env, uid_attribute)) uid_attr_seen = true; - if (_blacklist_exceptions(conf->elist_property, env)) { + if (match_reglist(conf->elist_property, env)) { r = MATCH_PROPERTY_BLIST_EXCEPT; break; } - if (_blacklist(conf->blist_property, env)) { + if (match_reglist(conf->blist_property, env)) { r = MATCH_PROPERTY_BLIST; break; } diff --git a/libmultipath/blacklist.h b/libmultipath/blacklist.h index 2d721f6..dde5cea 100644 --- a/libmultipath/blacklist.h +++ b/libmultipath/blacklist.h @@ -20,6 +20,7 @@ struct blentry { char * str; regex_t regex; + bool invert; int origin; }; @@ -28,19 +29,26 @@ struct blentry_device { char * product; regex_t vendor_reg; regex_t product_reg; + bool vendor_invert; + bool product_invert; int origin; }; int setup_default_blist (struct config *); int alloc_ble_device (vector); -int filter_devnode (vector, vector, char *); -int filter_wwid (vector, vector, char *, char *); -int filter_device (vector, vector, char *, char *, char *); -int filter_path (struct config *, struct path *); -int filter_property(struct config *, struct udev_device *, int, const char*); -int filter_protocol(vector, vector, struct path *); -int store_ble (vector, char *, int); -int set_ble_device (vector, char *, char *, int); +int filter_devnode (const struct _vector *, const struct _vector *, + const char *); +int filter_wwid (const struct _vector *, const struct _vector *, + const char *, const char *); +int filter_device (const struct _vector *, const struct _vector *, + const char *, const char *, const char *); +int filter_path (const struct config *, const struct path *); +int filter_property(const struct config *, struct udev_device *, + int, const char*); +int filter_protocol(const struct _vector *, const struct _vector *, + const struct path *); +int store_ble (vector, const char *, int); +int set_ble_device (vector, const char *, const char *, int); void free_blacklist (vector); void free_blacklist_device (vector); void merge_blacklist(vector); diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index 8d2be8a..f7ddd53 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -23,23 +23,27 @@ struct checker_class { short msgtable_size; }; -char *checker_state_names[] = { - "wild", - "unchecked", - "down", - "up", - "shaky", - "ghost", - "pending", - "timeout", - "removed", - "delayed", +static const char *checker_state_names[PATH_MAX_STATE] = { + [PATH_WILD] = "wild", + [PATH_UNCHECKED] = "unchecked", + [PATH_DOWN] = "down", + [PATH_UP] = "up", + [PATH_SHAKY] = "shaky", + [PATH_GHOST] = "ghost", + [PATH_PENDING] = "pending", + [PATH_TIMEOUT] = "timeout", + [PATH_REMOVED] = "removed", + [PATH_DELAYED] = "delayed", }; static LIST_HEAD(checkers); const char *checker_state_name(int i) { + if (i < 0 || i >= PATH_MAX_STATE) { + condlog (2, "invalid state index = %d", i); + return INVALID; + } return checker_state_names[i]; } diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index b458118..9d5f90b 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -67,7 +67,7 @@ * During this time, it is marked as "delayed" */ enum path_check_state { - PATH_WILD, + PATH_WILD = 0, PATH_UNCHECKED, PATH_DOWN, PATH_UP, @@ -88,6 +88,7 @@ enum path_check_state { #define READSECTOR0 "readsector0" #define CCISS_TUR "cciss_tur" #define NONE "none" +#define INVALID "invalid" #define ASYNC_TIMEOUT_SEC 30 diff --git a/libmultipath/checkers/Makefile b/libmultipath/checkers/Makefile index 02caea6..01c0451 100644 --- a/libmultipath/checkers/Makefile +++ b/libmultipath/checkers/Makefile @@ -17,10 +17,10 @@ LIBS= \ all: $(LIBS) -libcheckdirectio.so: libsg.o directio.o +libcheckdirectio.so: directio.o $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -laio -libcheck%.so: libsg.o %.o +libcheck%.so: %.o $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ install: @@ -32,7 +32,7 @@ uninstall: clean: dep_clean $(RM) core *.a *.o *.gz *.so -OBJS := $(LIBS:libcheck%.so=%.o) libsg.o directio.o +OBJS := $(LIBS:libcheck%.so=%.o) .SECONDARY: $(OBJS) include $(wildcard $(OBJS:.o=.d)) diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 503519e..f73cbe3 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -256,7 +256,7 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) { struct io_event events[128]; int i, nr, got_events = 0; - struct timespec zero_timeout = {0}; + struct timespec zero_timeout = { .tv_sec = 0, }; struct timespec *timep = timeout; do { diff --git a/libmultipath/config.c b/libmultipath/config.c index b4d8768..b9bdbdb 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -131,7 +131,7 @@ find_hwe (const struct _vector *hwtable, vector_foreach_slot_backwards (hwtable, tmp, i) { if (hwe_regmatch(tmp, vendor, product, revision)) continue; - if (vector_alloc_slot(result) != NULL) { + if (vector_alloc_slot(result)) { vector_set_slot(result, tmp); n++; } @@ -157,7 +157,7 @@ struct mpentry *find_mpe(vector mptable, char *wwid) return NULL; } -char *get_mpe_wwid(vector mptable, char *alias) +const char *get_mpe_wwid(const struct _vector *mptable, const char *alias) { int i; struct mpentry * mpe; @@ -631,6 +631,8 @@ free_config (struct config * conf) if (conf->config_dir) FREE(conf->config_dir); + if (conf->enable_foreign) + FREE(conf->enable_foreign); free_blacklist(conf->blist_devnode); free_blacklist(conf->blist_wwid); @@ -696,9 +698,9 @@ process_config_dir(struct config *conf, char *dir) pthread_cleanup_pop(1); } +#ifdef USE_SYSTEMD static void set_max_checkint_from_watchdog(struct config *conf) { -#ifdef USE_SYSTEMD char *envp = getenv("WATCHDOG_USEC"); unsigned long checkint; @@ -714,8 +716,8 @@ static void set_max_checkint_from_watchdog(struct config *conf) condlog(3, "enabling watchdog, interval %ld", checkint); conf->use_watchdog = true; } -#endif } +#endif struct config * load_config (char * file) @@ -789,7 +791,9 @@ load_config (char * file) /* * fill the voids left in the config file */ +#ifdef USE_SYSTEMD set_max_checkint_from_watchdog(conf); +#endif if (conf->max_checkint == 0) { if (conf->checkint == CHECKINT_UNDEF) conf->checkint = DEFAULT_CHECKINT; diff --git a/libmultipath/config.h b/libmultipath/config.h index ceecff2..290aea5 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -38,6 +38,8 @@ enum mpath_cmds { CMD_ADD_WWID, CMD_USABLE_PATHS, CMD_DUMP_CONFIG, + CMD_FLUSH_ONE, + CMD_FLUSH_ALL, }; enum force_reload_types { @@ -142,7 +144,6 @@ struct config { unsigned int max_checkint; bool use_watchdog; int pgfailback; - int remove; int rr_weight; int no_path_retry; int user_friendly_names; @@ -157,6 +158,7 @@ struct config { unsigned int dev_loss; int log_checker_err; int allow_queueing; + int allow_usb_devices; int find_multipaths; uid_t uid; gid_t gid; @@ -189,6 +191,7 @@ struct config { int ghost_delay; int find_multipaths_timeout; int marginal_pathgroups; + int skip_delegate; unsigned int version[3]; unsigned int sequence_nr; @@ -237,7 +240,7 @@ int find_hwe (const struct _vector *hwtable, const char * vendor, const char * product, const char *revision, vector result); struct mpentry * find_mpe (vector mptable, char * wwid); -char * get_mpe_wwid (vector mptable, char * alias); +const char *get_mpe_wwid (const struct _vector *mptable, const char *alias); struct hwentry * alloc_hwe (void); struct mpentry * alloc_mpe (void); diff --git a/libmultipath/configure.c b/libmultipath/configure.c index c95848a..6fb477f 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -298,6 +298,7 @@ int setup_map(struct multipath *mpp, char *params, int params_size, struct pathgroup * pgp; struct config *conf; int i, n_paths, marginal_pathgroups; + char *save_attr; /* * don't bother if devmap size is unknown @@ -307,10 +308,6 @@ int setup_map(struct multipath *mpp, char *params, int params_size, return 1; } - /* - * free features, selector, and hwhandler properties if they are being reused - */ - free_multipath_attributes(mpp); if (mpp->disable_queueing && VECTOR_SIZE(mpp->paths) != 0) mpp->disable_queueing = 0; @@ -328,11 +325,35 @@ int setup_map(struct multipath *mpp, char *params, int params_size, select_pgfailback(conf, mpp); select_pgpolicy(conf, mpp); + + /* + * If setup_map() is called from e.g. from reload_map() or resize_map(), + * make sure that we don't corrupt attributes. + */ + save_attr = steal_ptr(mpp->selector); select_selector(conf, mpp); + if (!mpp->selector) + mpp->selector = save_attr; + else + free(save_attr); + select_no_path_retry(conf, mpp); select_retain_hwhandler(conf, mpp); + + save_attr = steal_ptr(mpp->features); select_features(conf, mpp); + if (!mpp->features) + mpp->features = save_attr; + else + free(save_attr); + + save_attr = steal_ptr(mpp->hwhandler); select_hwhandler(conf, mpp); + if (!mpp->hwhandler) + mpp->hwhandler = save_attr; + else + free(save_attr); + select_rr_weight(conf, mpp); select_minio(conf, mpp); select_mode(conf, mpp); @@ -359,6 +380,11 @@ int setup_map(struct multipath *mpp, char *params, int params_size, marginal_pathgroups = conf->marginal_pathgroups; pthread_cleanup_pop(1); + if (!mpp->features || !mpp->hwhandler || !mpp->selector) { + condlog(0, "%s: map select failed", mpp->alias); + return 1; + } + if (marginal_path_check_enabled(mpp)) start_io_err_stat_thread(vecs); @@ -510,6 +536,7 @@ static void trigger_partitions_udev_change(struct udev_device *dev, { struct udev_enumerate *part_enum; struct udev_list_entry *item; + const char *devtype; part_enum = udev_enumerate_new(udev); if (!part_enum) @@ -530,7 +557,8 @@ static void trigger_partitions_udev_change(struct udev_device *dev, if (!part) continue; - if (!strcmp("partition", udev_device_get_devtype(part))) { + devtype = udev_device_get_devtype(part); + if (devtype && !strcmp("partition", devtype)) { condlog(4, "%s: triggering %s event for %s", __func__, action, syspath); sysfs_attr_set_value(part, "uevent", action, len); @@ -661,7 +689,34 @@ sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload) } static void -select_action (struct multipath * mpp, vector curmp, int force_reload) +select_reload_action(struct multipath *mpp, const struct multipath *cmpp, + const char *reason) +{ + struct udev_device *mpp_ud; + const char *env; + + /* + * MPATH_DEVICE_READY != 1 can mean two things: + * (a) no usable paths + * (b) device was never fully processed (e.g. udev killed) + * If we are in this code path (startup or forced reconfigure), + * (b) can mean that upper layers like kpartx have never been + * run for this map. Thus force udev reload. + */ + + mpp_ud = get_udev_for_mpp(cmpp); + 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; + udev_device_unref(mpp_ud); + mpp->action = ACT_RELOAD; + condlog(3, "%s: set ACT_RELOAD (%s%s)", mpp->alias, + mpp->force_udev_reload ? "forced, " : "", + reason); +} + +void select_action (struct multipath *mpp, const struct _vector *curmp, + int force_reload) { struct multipath * cmpp; struct multipath * cmpp_by_name; @@ -689,12 +744,13 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) } if (!cmpp) { - condlog(2, "%s: remove (wwid changed)", mpp->alias); - dm_flush_map(mpp->alias); - strlcpy(cmpp_by_name->wwid, mpp->wwid, WWID_SIZE); - drop_multipath(curmp, cmpp_by_name->wwid, KEEP_PATHS); + condlog(1, "%s: can't use alias \"%s\" used by %s, falling back to WWID", + mpp->wwid, mpp->alias, cmpp_by_name->wwid); + /* We can do this because wwid wasn't found */ + free(mpp->alias); + mpp->alias = strdup(mpp->wwid); mpp->action = ACT_CREATE; - condlog(3, "%s: set ACT_CREATE (map wwid change)", + condlog(3, "%s: set ACT_CREATE (map does not exist, name changed)", mpp->alias); return; } @@ -710,12 +766,6 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) return; } - if (pathcount(mpp, PATH_UP) == 0) { - mpp->action = ACT_IMPOSSIBLE; - condlog(3, "%s: set ACT_IMPOSSIBLE (no usable path)", - mpp->alias); - return; - } if (force_reload) { mpp->force_udev_reload = 1; mpp->action = ACT_RELOAD; @@ -734,9 +784,7 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF && !!strstr(mpp->features, "queue_if_no_path") != !!strstr(cmpp->features, "queue_if_no_path")) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (no_path_retry change)", - mpp->alias); + select_reload_action(mpp, cmpp, "no_path_retry change"); return; } if ((mpp->retain_hwhandler != RETAIN_HWHANDLER_ON || @@ -744,9 +792,7 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) (strlen(cmpp->hwhandler) != strlen(mpp->hwhandler) || strncmp(cmpp->hwhandler, mpp->hwhandler, strlen(mpp->hwhandler)))) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (hwhandler change)", - mpp->alias); + select_reload_action(mpp, cmpp, "hwhandler change"); return; } @@ -754,9 +800,7 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) !!strstr(mpp->features, "retain_attached_hw_handler") != !!strstr(cmpp->features, "retain_attached_hw_handler") && get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (retain_hwhandler change)", - mpp->alias); + select_reload_action(mpp, cmpp, "retain_hwhandler change"); return; } @@ -768,9 +812,10 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) remove_feature(&cmpp_feat, "queue_if_no_path"); remove_feature(&cmpp_feat, "retain_attached_hw_handler"); if (strncmp(mpp_feat, cmpp_feat, PARAMS_SIZE)) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (features change)", - mpp->alias); + select_reload_action(mpp, cmpp, "features change"); + FREE(cmpp_feat); + FREE(mpp_feat); + return; } } FREE(cmpp_feat); @@ -778,27 +823,19 @@ select_action (struct multipath * mpp, vector curmp, int force_reload) if (!cmpp->selector || strncmp(cmpp->selector, mpp->selector, strlen(mpp->selector))) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (selector change)", - mpp->alias); + select_reload_action(mpp, cmpp, "selector change"); return; } if (cmpp->minio != mpp->minio) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (minio change, %u->%u)", - mpp->alias, cmpp->minio, mpp->minio); + select_reload_action(mpp, cmpp, "minio change"); return; } if (!cmpp->pg || VECTOR_SIZE(cmpp->pg) != VECTOR_SIZE(mpp->pg)) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (path group number change)", - mpp->alias); + select_reload_action(mpp, cmpp, "path group number change"); return; } if (pgcmp(mpp, cmpp)) { - mpp->action = ACT_RELOAD; - condlog(3, "%s: set ACT_RELOAD (path group topology change)", - mpp->alias); + select_reload_action(mpp, cmpp, "path group topology change"); return; } if (cmpp->nextpg != mpp->bestpg) { @@ -903,10 +940,21 @@ int domap(struct multipath *mpp, char *params, int is_daemon) return DOMAP_DRY; } - if (mpp->action == ACT_CREATE && - dm_map_present(mpp->alias)) { - condlog(3, "%s: map already present", mpp->alias); - mpp->action = ACT_RELOAD; + if (mpp->action == ACT_CREATE && dm_map_present(mpp->alias)) { + char wwid[WWID_SIZE]; + + if (dm_get_uuid(mpp->alias, wwid, sizeof(wwid)) == 0) { + if (!strncmp(mpp->wwid, wwid, sizeof(wwid))) { + condlog(3, "%s: map already present", + mpp->alias); + mpp->action = ACT_RELOAD; + } else { + condlog(0, "%s: map \"%s\" already present with WWID %s, skipping", + mpp->wwid, mpp->alias, wwid); + condlog(0, "please check alias settings in config and bindings file"); + mpp->action = ACT_REJECT; + } + } } switch (mpp->action) { @@ -998,7 +1046,7 @@ int domap(struct multipath *mpp, char *params, int is_daemon) } else { /* multipath daemon mode */ mpp->stat_map_loads++; - condlog(2, "%s: load table [0 %llu %s %s]", mpp->alias, + condlog(4, "%s: load table [0 %llu %s %s]", mpp->alias, mpp->size, TGT_MPATH, params); /* * Required action is over, reset for the stateful daemon. @@ -1098,7 +1146,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, vector pathvec = vecs->pathvec; struct config *conf; int allow_queueing; - uint64_t *size_mismatch_seen; + struct bitfield *size_mismatch_seen; /* ignore refwwid if it's empty */ if (refwwid && !strlen(refwwid)) @@ -1112,8 +1160,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, if (VECTOR_SIZE(pathvec) == 0) return CP_OK; - size_mismatch_seen = calloc((VECTOR_SIZE(pathvec) - 1) / 64 + 1, - sizeof(uint64_t)); + size_mismatch_seen = alloc_bitfield(VECTOR_SIZE(pathvec)); if (size_mismatch_seen == NULL) return CP_FAIL; @@ -1137,7 +1184,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, } /* 2. if path already coalesced, or seen and discarded */ - if (pp1->mpp || is_bit_set_in_array(k, size_mismatch_seen)) + if (pp1->mpp || is_bit_set_in_bitfield(k, size_mismatch_seen)) continue; /* 3. if path has disappeared */ @@ -1167,7 +1214,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, if (!mpp->paths) { condlog(0, "%s: skip coalesce (no paths)", mpp->alias); - remove_map(mpp, vecs, 0); + remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC); continue; } @@ -1189,14 +1236,14 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, "Discard", pp2->dev, pp2->size, mpp->size); mpp->action = ACT_REJECT; - set_bit_in_array(i, size_mismatch_seen); + set_bit_in_bitfield(i, size_mismatch_seen); } } - verify_paths(mpp, vecs); + verify_paths(mpp); params[0] = '\0'; if (setup_map(mpp, params, PARAMS_SIZE, vecs)) { - remove_map(mpp, vecs, 0); + remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC); continue; } @@ -1216,7 +1263,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, condlog(2, "%s: %s map", mpp->alias, (mpp->action == ACT_CREATE)? "ignoring" : "removing"); - remove_map(mpp, vecs, 0); + remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC); continue; } else /* if (r == DOMAP_RETRY && !is_daemon) */ { ret = CP_RETRY; @@ -1267,7 +1314,8 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, vector_set_slot(newmp, mpp); } else - remove_map(mpp, vecs, 0); + remove_map(mpp, vecs->pathvec, vecs->mpvec, + KEEP_VEC); } } /* @@ -1285,7 +1333,7 @@ int coalesce_paths (struct vectors * vecs, vector newmp, char * refwwid, vector_del_slot(newmp, i); i--; - remove_map(mpp, vecs, 0); + remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC); if (dm_flush_map(alias)) condlog(2, "%s: remove failed (dead)", @@ -1335,223 +1383,122 @@ struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type) return ud; } -/* - * returns: - * 0 - success - * 1 - failure - * 2 - blacklist - */ -int get_refwwid(enum mpath_cmds cmd, char *dev, enum devtypes dev_type, - vector pathvec, char **wwid) +static int _get_refwwid(enum mpath_cmds cmd, const char *dev, + enum devtypes dev_type, + vector pathvec, struct config *conf, char **wwid) { int ret = 1; struct path * pp; char buff[FILE_NAME_SIZE]; - char * refwwid = NULL, tmpwwid[WWID_SIZE]; + const char *refwwid = NULL; + char tmpwwid[WWID_SIZE]; + struct udev_device *udevice; int flags = DI_SYSFS | DI_WWID; - struct config *conf; - int invalid = 0; if (!wwid) - return 1; + return PATHINFO_FAILED; *wwid = NULL; if (dev_type == DEV_NONE) - return 1; + return PATHINFO_FAILED; if (cmd != CMD_REMOVE_WWID) flags |= DI_BLACKLIST; - if (dev_type == DEV_DEVNODE) { + switch (dev_type) { + case DEV_DEVNODE: if (basenamecpy(dev, buff, FILE_NAME_SIZE) == 0) { condlog(1, "basename failed for '%s' (%s)", dev, buff); - return 1; + return PATHINFO_FAILED; } - pp = find_path_by_dev(pathvec, buff); - if (!pp) { - struct udev_device *udevice = - get_udev_device(buff, dev_type); - - if (!udevice) - return 1; + /* dev is used in common code below */ + dev = buff; + pp = find_path_by_dev(pathvec, dev); + goto common; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - ret = store_pathinfo(pathvec, conf, udevice, - flags, &pp); - pthread_cleanup_pop(1); - udev_device_unref(udevice); - if (!pp) { - if (ret == 1) - condlog(0, "%s: can't store path info", - dev); - return ret; - } - } - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - if (pp->udev && pp->uid_attribute && - filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0) - invalid = 1; - pthread_cleanup_pop(1); - if (invalid) - return 2; - - refwwid = pp->wwid; - goto out; - } + case DEV_DEVT: + pp = find_path_by_devt(pathvec, dev); + goto common; - if (dev_type == DEV_DEVT) { - strchop(dev); - if (devt2devname(buff, FILE_NAME_SIZE, dev)) { - condlog(0, "%s: cannot find block device\n", dev); - return 1; - } - pp = find_path_by_dev(pathvec, buff); + case DEV_UEVENT: + pp = NULL; + /* For condlog below, dev is unused in get_udev_device() */ + dev = "environment"; + common: if (!pp) { - struct udev_device *udevice = - get_udev_device(dev, dev_type); + udevice = get_udev_device(dev, dev_type); - if (!udevice) - return 1; + if (!udevice) { + condlog(0, "%s: cannot find block device", dev); + return PATHINFO_FAILED; + } - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); ret = store_pathinfo(pathvec, conf, udevice, flags, &pp); - pthread_cleanup_pop(1); udev_device_unref(udevice); if (!pp) { - if (ret == 1) - condlog(0, "%s can't store path info", - buff); + if (ret == PATHINFO_FAILED) + condlog(0, "%s: can't store path info", + dev); return ret; } } - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); if (pp->udev && pp->uid_attribute && filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0) - invalid = 1; - pthread_cleanup_pop(1); - if (invalid) - return 2; + return PATHINFO_SKIPPED; refwwid = pp->wwid; - goto out; - } - - if (dev_type == DEV_UEVENT) { - struct udev_device *udevice = get_udev_device(dev, dev_type); - - if (!udevice) - return 1; - - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - ret = store_pathinfo(pathvec, conf, udevice, - flags, &pp); - pthread_cleanup_pop(1); - udev_device_unref(udevice); - if (!pp) { - if (ret == 1) - condlog(0, "%s: can't store path info", dev); - return ret; - } - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - if (pp->udev && pp->uid_attribute && - filter_property(conf, pp->udev, 3, pp->uid_attribute) > 0) - invalid = 1; - pthread_cleanup_pop(1); - if (invalid) - return 2; - refwwid = pp->wwid; - goto out; - } - - if (dev_type == DEV_DEVMAP) { + break; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); + case DEV_DEVMAP: if (((dm_get_uuid(dev, tmpwwid, WWID_SIZE)) == 0) - && (strlen(tmpwwid))) { + && (strlen(tmpwwid))) refwwid = tmpwwid; - goto check; - } - /* - * may be a binding - */ - if (get_user_friendly_wwid(dev, tmpwwid, - conf->bindings_file) == 0) { + /* or may be a binding */ + else if (get_user_friendly_wwid(dev, tmpwwid, + conf->bindings_file) == 0) refwwid = tmpwwid; - goto check; - } - /* - * or may be an alias - */ - refwwid = get_mpe_wwid(conf->mptable, dev); + /* or may be an alias */ + else { + refwwid = get_mpe_wwid(conf->mptable, dev); - /* - * or directly a wwid - */ - if (!refwwid) - refwwid = dev; + /* or directly a wwid */ + if (!refwwid) + refwwid = dev; + } -check: if (refwwid && strlen(refwwid) && filter_wwid(conf->blist_wwid, conf->elist_wwid, refwwid, NULL) > 0) - invalid = 1; - pthread_cleanup_pop(1); - if (invalid) - return 2; + return PATHINFO_SKIPPED; + break; + default: + break; } -out: + if (refwwid && strlen(refwwid)) { *wwid = STRDUP(refwwid); - return 0; + return PATHINFO_OK; } - return 1; + return PATHINFO_FAILED; } -int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, - int is_daemon) -{ - char params[PARAMS_SIZE] = {0}; - struct path *pp; - int i, r; - - update_mpp_paths(mpp, vecs->pathvec); - if (refresh) { - vector_foreach_slot (mpp->paths, pp, i) { - struct config *conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - r = pathinfo(pp, conf, DI_PRIO); - pthread_cleanup_pop(1); - if (r) { - condlog(2, "%s: failed to refresh pathinfo", - mpp->alias); - return 1; - } - } - } - if (setup_map(mpp, params, PARAMS_SIZE, vecs)) { - condlog(0, "%s: failed to setup map", mpp->alias); - return 1; - } - select_action(mpp, vecs->mpvec, 1); +/* + * Returns: PATHINFO_OK, PATHINFO_FAILED, or PATHINFO_SKIPPED (see pathinfo()) + */ +int get_refwwid(enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, + vector pathvec, char **wwid) - r = domap(mpp, params, is_daemon); - if (r == DOMAP_FAIL || r == DOMAP_RETRY) { - condlog(3, "%s: domap (%u) failure " - "for reload map", mpp->alias, r); - return 1; - } +{ + int ret; + struct config *conf = get_multipath_config(); - return 0; + pthread_cleanup_push(put_multipath_config, conf); + ret = _get_refwwid(cmd, dev, dev_type, pathvec, conf, wwid); + pthread_cleanup_pop(1); + return ret; } diff --git a/libmultipath/configure.h b/libmultipath/configure.h index d750900..6b23ccb 100644 --- a/libmultipath/configure.h +++ b/libmultipath/configure.h @@ -45,18 +45,16 @@ enum { CP_RETRY, }; -#define FLUSH_ONE 1 -#define FLUSH_ALL 2 - struct vectors; int setup_map (struct multipath * mpp, char * params, int params_size, struct vectors *vecs ); +void select_action (struct multipath *mpp, const struct _vector *curmp, + int force_reload); int domap (struct multipath * mpp, char * params, int is_daemon); int reinstate_paths (struct multipath *mpp); int coalesce_paths (struct vectors *vecs, vector curmp, char * refwwid, int force_reload, enum mpath_cmds cmd); -int get_refwwid (enum mpath_cmds cmd, char * dev, enum devtypes dev_type, +int get_refwwid (enum mpath_cmds cmd, const char *dev, enum devtypes dev_type, vector pathvec, char **wwid); -int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, int is_daemon); struct udev_device *get_udev_device(const char *dev, enum devtypes dev_type); void trigger_paths_udev_change(struct multipath *mpp, bool is_mpath); diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index e5ee6af..39a5e41 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -31,6 +31,8 @@ #define DEFAULT_DEFERRED_REMOVE DEFERRED_REMOVE_OFF #define DEFAULT_DELAY_CHECKS NU_NO #define DEFAULT_ERR_CHECKS NU_NO +/* half of minimum value for marginal_path_err_sample_time */ +#define IOTIMEOUT_SEC 60 #define DEFAULT_UEVENT_STACKSIZE 256 #define DEFAULT_RETRIGGER_DELAY 10 #define DEFAULT_RETRIGGER_TRIES 3 @@ -50,12 +52,13 @@ #define DEFAULT_FIND_MULTIPATHS_TIMEOUT -10 #define DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT 1 #define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF -/* Enable all foreign libraries by default */ -#define DEFAULT_ENABLE_FOREIGN "" +/* Enable no foreign libraries by default */ +#define DEFAULT_ENABLE_FOREIGN "NONE" #define CHECKINT_UNDEF UINT_MAX #define DEFAULT_CHECKINT 5 +#define DEV_LOSS_TMO_UNSET 0U #define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE "/" RUN_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index bed8ddc..7f09361 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,8 @@ #define MAX_WAIT 5 #define LOOPS_PER_SEC 5 +static pthread_once_t dm_initialized = PTHREAD_ONCE_INIT; + static int dm_conf_verbosity; #ifdef LIBDM_API_DEFERRED @@ -63,13 +66,15 @@ __attribute__((format(printf, 4, 5))) static void dm_write_log (int level, const char *file, int line, const char *f, ...) { va_list ap; - int thres; - if (level > 6) - level = 6; + /* + * libdm uses the same log levels as syslog, + * except that EMERG/ALERT are not used + */ + if (level > LOG_DEBUG) + level = LOG_DEBUG; - thres = dm_conf_verbosity; - if (thres <= 3 || level > thres) + if (level > dm_conf_verbosity) return; va_start(ap, f); @@ -88,8 +93,9 @@ dm_write_log (int level, const char *file, int line, const char *f, ...) vfprintf(stderr, f, ap); fprintf(stderr, "\n"); } else { - condlog(level, "libdevmapper: %s(%i): ", file, line); - log_safe(level + 3, f, ap); + condlog(level >= LOG_ERR ? level - LOG_ERR : 0, + "libdevmapper: %s(%i): ", file, line); + log_safe(level, f, ap); } va_end(ap); @@ -98,9 +104,12 @@ dm_write_log (int level, const char *file, int line, const char *f, ...) void dm_init(int v) { - dm_conf_verbosity = v; + /* + * This maps libdm's standard loglevel _LOG_WARN (= 4), which is rather + * quiet in practice, to multipathd's default verbosity 2 + */ + dm_conf_verbosity = v + 2; dm_log_init(&dm_write_log); - dm_log_init_verbose(v + 3); } static int @@ -108,7 +117,11 @@ dm_lib_prereq (void) { char version[64]; int v[3]; -#if defined(LIBDM_API_DEFERRED) +#if defined(LIBDM_API_HOLD_CONTROL) + int minv[3] = {1, 2, 111}; +#elif defined(LIBDM_API_GET_ERRNO) + int minv[3] = {1, 2, 99}; +#elif defined(LIBDM_API_DEFERRED) int minv[3] = {1, 2, 89}; #elif defined(DM_SUBSYSTEM_UDEV_FLAG0) int minv[3] = {1, 2, 82}; @@ -171,6 +184,7 @@ dm_tgt_version (unsigned int * version, char * str) dm_task_no_open_count(dmt); if (!dm_task_run(dmt)) { + dm_log_error(2, DM_DEVICE_LIST_VERSIONS, dmt); condlog(0, "Can not communicate with kernel DM"); goto out; } @@ -227,7 +241,7 @@ dm_tgt_prereq (unsigned int *ver) return 1; } -static int dm_prereq(unsigned int *v) +int dm_prereq(unsigned int *v) { if (dm_lib_prereq()) return 1; @@ -241,7 +255,7 @@ void libmp_udev_set_sync_support(int on) libmp_dm_udev_sync = !!on; } -void libmp_dm_init(void) +static void libmp_dm_init(void) { struct config *conf; int verbosity; @@ -254,14 +268,24 @@ void libmp_dm_init(void) memcpy(conf->version, version, sizeof(version)); put_multipath_config(conf); dm_init(verbosity); +#ifdef LIBDM_API_HOLD_CONTROL + dm_hold_control_dev(1); +#endif dm_udev_set_sync_support(libmp_dm_udev_sync); } +static void _do_skip_libmp_dm_init(void) +{ +} + +void skip_libmp_dm_init(void) +{ + pthread_once(&dm_initialized, _do_skip_libmp_dm_init); +} + struct dm_task* libmp_dm_task_create(int task) { - static pthread_once_t dm_initialized = PTHREAD_ONCE_INIT; - pthread_once(&dm_initialized, libmp_dm_init); return dm_task_create(task); } @@ -299,6 +323,8 @@ dm_simplecmd (int task, const char *name, int no_flush, int need_sync, uint16_t goto out; r = dm_task_run (dmt); + if (!r) + dm_log_error(2, task, dmt); if (udev_wait_flag) dm_udev_wait(cookie); @@ -331,6 +357,12 @@ dm_addmap (int task, const char *target, struct multipath *mpp, char *prefixed_uuid = NULL; uint32_t cookie = 0; + if (task == DM_DEVICE_CREATE && strlen(mpp->wwid) == 0) { + condlog(1, "%s: refusing to create map with empty WWID", + mpp->alias); + return 0; + } + /* Need to add this here to allow 0 to be passed in udev_flags */ udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; @@ -347,18 +379,16 @@ dm_addmap (int task, const char *target, struct multipath *mpp, dm_task_set_ro(dmt); if (task == DM_DEVICE_CREATE) { - if (strlen(mpp->wwid) > 0) { - prefixed_uuid = MALLOC(UUID_PREFIX_LEN + - strlen(mpp->wwid) + 1); - if (!prefixed_uuid) { - condlog(0, "cannot create prefixed uuid : %s", - strerror(errno)); - goto addout; - } - sprintf(prefixed_uuid, UUID_PREFIX "%s", mpp->wwid); - if (!dm_task_set_uuid(dmt, prefixed_uuid)) - goto freeout; + prefixed_uuid = MALLOC(UUID_PREFIX_LEN + + strlen(mpp->wwid) + 1); + if (!prefixed_uuid) { + condlog(0, "cannot create prefixed uuid : %s", + strerror(errno)); + goto addout; } + sprintf(prefixed_uuid, UUID_PREFIX "%s", mpp->wwid); + if (!dm_task_set_uuid(dmt, prefixed_uuid)) + goto freeout; dm_task_skip_lockfs(dmt); #ifdef LIBDM_API_FLUSH dm_task_no_flush(dmt); @@ -374,7 +404,7 @@ dm_addmap (int task, const char *target, struct multipath *mpp, if (mpp->attribute_flags & (1 << ATTR_GID) && !dm_task_set_gid(dmt, mpp->gid)) goto freeout; - condlog(4, "%s: %s [0 %llu %s %s]", mpp->alias, + condlog(2, "%s: %s [0 %llu %s %s]", mpp->alias, task == DM_DEVICE_RELOAD ? "reload" : "addmap", mpp->size, target, params); @@ -385,6 +415,8 @@ dm_addmap (int task, const char *target, struct multipath *mpp, goto freeout; r = dm_task_run (dmt); + if (!r) + dm_log_error(2, task, dmt); if (task == DM_DEVICE_CREATE) dm_udev_wait(cookie); @@ -403,7 +435,8 @@ static uint16_t build_udev_flags(const struct multipath *mpp, int reload) /* DM_UDEV_DISABLE_LIBRARY_FALLBACK is added in dm_addmap */ return (mpp->skip_kpartx == SKIP_KPARTX_ON ? MPATH_UDEV_NO_KPARTX_FLAG : 0) | - ((count_active_paths(mpp) == 0 || mpp->ghost_delay_tick > 0) ? + ((count_active_pending_paths(mpp) == 0 || + mpp->ghost_delay_tick > 0) ? MPATH_UDEV_NO_PATHS_FLAG : 0) | (reload && !mpp->force_udev_reload ? MPATH_UDEV_RELOAD_FLAG : 0); @@ -496,8 +529,10 @@ do_get_info(const char *name, struct dm_info *info) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_INFO, dmt); goto out; + } if (!dm_task_get_info(dmt, info)) goto out; @@ -520,36 +555,44 @@ int dm_map_present(const char * str) int dm_get_map(const char *name, unsigned long long *size, char *outparams) { - int r = 1; + int r = DMP_ERR; struct dm_task *dmt; uint64_t start, length; char *target_type = NULL; char *params = NULL; if (!(dmt = libmp_dm_task_create(DM_DEVICE_TABLE))) - return 1; + return r; if (!dm_task_set_name(dmt, name)) goto out; dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + errno = 0; + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_TABLE, dmt); + if (dm_task_get_errno(dmt) == ENXIO) + r = DMP_NOT_FOUND; goto out; + } + r = DMP_NOT_FOUND; /* Fetch 1st target */ - dm_get_next_target(dmt, NULL, &start, &length, - &target_type, ¶ms); + if (dm_get_next_target(dmt, NULL, &start, &length, + &target_type, ¶ms) != NULL) + /* more than one target */ + goto out; if (size) *size = length; if (!outparams) { - r = 0; + r = DMP_OK; goto out; } if (snprintf(outparams, PARAMS_SIZE, "%s", params) <= PARAMS_SIZE) - r = 0; + r = DMP_OK; out: dm_task_destroy(dmt); return r; @@ -569,8 +612,10 @@ 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 (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_INFO, dmt); goto uuidout; + } uuidtmp = dm_task_get_uuid(dmt); if (uuidtmp) @@ -623,35 +668,46 @@ is_mpath_part(const char *part_name, const char *map_name) int dm_get_status(const char *name, char *outstatus) { - int r = 1; + int r = DMP_ERR; struct dm_task *dmt; uint64_t start, length; char *target_type = NULL; char *status = NULL; if (!(dmt = libmp_dm_task_create(DM_DEVICE_STATUS))) - return 1; + return r; if (!dm_task_set_name(dmt, name)) goto out; dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + errno = 0; + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_STATUS, dmt); + if (dm_task_get_errno(dmt) == ENXIO) + r = DMP_NOT_FOUND; goto out; + } + r = DMP_NOT_FOUND; /* Fetch 1st target */ - dm_get_next_target(dmt, NULL, &start, &length, - &target_type, &status); + if (dm_get_next_target(dmt, NULL, &start, &length, + &target_type, &status) != NULL) + goto out; + + if (!target_type || strcmp(target_type, TGT_MPATH) != 0) + goto out; + if (!status) { condlog(2, "get null status."); goto out; } if (snprintf(outstatus, PARAMS_SIZE, "%s", status) <= PARAMS_SIZE) - r = 0; + r = DMP_OK; out: - if (r) + if (r != DMP_OK) condlog(0, "%s: error getting map status string", name); dm_task_destroy(dmt); @@ -680,8 +736,10 @@ int dm_type(const char *name, char *type) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_TABLE, dmt); goto out; + } /* Fetch 1st target */ if (dm_get_next_target(dmt, NULL, &start, &length, @@ -722,8 +780,10 @@ int dm_is_mpath(const char *name) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_TABLE, dmt); goto out_task; + } if (!dm_task_get_info(dmt, &info)) goto out_task; @@ -756,6 +816,53 @@ out: return r; } +/* + * Return + * 1 : map with uuid exists + * 0 : map with uuid doesn't exist + * -1 : error + */ +int +dm_map_present_by_uuid(const char *uuid) +{ + struct dm_task *dmt; + struct dm_info info; + char prefixed_uuid[WWID_SIZE + UUID_PREFIX_LEN]; + int r = -1; + + if (!uuid || uuid[0] == '\0') + return 0; + + if (safe_sprintf(prefixed_uuid, UUID_PREFIX "%s", uuid)) + goto out; + + if (!(dmt = dm_task_create(DM_DEVICE_INFO))) + goto out; + + dm_task_no_open_count(dmt); + + if (!dm_task_set_uuid(dmt, prefixed_uuid)) + goto out_task; + + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_INFO, dmt); + goto out_task; + } + + if (!dm_task_get_info(dmt, &info)) + goto out_task; + + r = !!info.exists; + +out_task: + dm_task_destroy(dmt); +out: + if (r < 0) + condlog(3, "%s: dm command failed in %s: %s", uuid, + __FUNCTION__, strerror(errno)); + return r; +} + static int dm_dev_t (const char * mapname, char * dev_t, int len) { @@ -783,8 +890,10 @@ dm_get_opencount (const char * mapname) if (!dm_task_set_name(dmt, mapname)) goto out; - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_INFO, dmt); goto out; + } if (!dm_task_get_info(dmt, &info)) goto out; @@ -861,7 +970,7 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove, return 1; if (need_suspend && - !dm_get_map(mapname, &mapsize, params) && + dm_get_map(mapname, &mapsize, params) == DMP_OK && strstr(params, "queue_if_no_path")) { if (!dm_queue_if_no_path(mapname, 0)) queue_if_no_path = 1; @@ -929,29 +1038,35 @@ dm_flush_map_nopaths(const char * mapname, int deferred_remove) #endif -int dm_flush_maps (int retries) +int dm_flush_maps (int need_suspend, int retries) { - int r = 0; + int r = 1; struct dm_task *dmt; struct dm_names *names; unsigned next = 0; if (!(dmt = libmp_dm_task_create (DM_DEVICE_LIST))) - return 0; + return r; dm_task_no_open_count(dmt); - if (!dm_task_run (dmt)) + if (!dm_task_run (dmt)) { + dm_log_error(3, DM_DEVICE_LIST, dmt); goto out; + } if (!(names = dm_task_get_names (dmt))) goto out; + r = 0; if (!names->dev) goto out; do { - r |= dm_suspend_and_flush_map(names->name, retries); + if (need_suspend) + r |= dm_suspend_and_flush_map(names->name, retries); + else + r |= dm_flush_map(names->name); next = names->next; names = (void *) names + next; } while (next); @@ -981,8 +1096,10 @@ dm_message(const char * mapname, char * message) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(2, DM_DEVICE_TARGET_MSG, dmt); goto out; + } r = 0; out: @@ -1070,7 +1187,7 @@ struct multipath *dm_get_multipath(const char *name) if (!mpp->alias) goto out; - if (dm_get_map(name, &mpp->size, NULL)) + if (dm_get_map(name, &mpp->size, NULL) != DMP_OK) goto out; dm_get_uuid(name, mpp->wwid, WWID_SIZE); @@ -1099,8 +1216,10 @@ dm_get_maps (vector mp) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_LIST, dmt); goto out; + } if (!(names = dm_task_get_names(dmt))) goto out; @@ -1118,8 +1237,10 @@ dm_get_maps (vector mp) if (!mpp) goto out; - if (!vector_alloc_slot(mp)) + if (!vector_alloc_slot(mp)) { + free_multipath(mpp, KEEP_PATHS); goto out; + } vector_set_slot(mp, mpp); mpp = NULL; @@ -1189,13 +1310,14 @@ dm_mapname(int major, int minor) } if (!r) { + dm_log_error(2, DM_DEVICE_STATUS, dmt); condlog(0, "%i:%i: timeout fetching map name", major, minor); goto bad; } map = dm_task_get_name(dmt); if (map && strlen(map)) - response = STRDUP((const char *)dm_task_get_name(dmt)); + response = STRDUP((const char *)map); dm_task_destroy(dmt); return response; @@ -1224,8 +1346,10 @@ do_foreach_partmaps (const char * mapname, dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_LIST, dmt); goto out; + } if (!(names = dm_task_get_names(dmt))) goto out; @@ -1254,7 +1378,7 @@ do_foreach_partmaps (const char * mapname, /* * and we can fetch the map table from the kernel */ - !dm_get_map(names->name, &size, ¶ms[0]) && + dm_get_map(names->name, &size, ¶ms[0]) == DMP_OK && /* * and the table maps over the multipath map @@ -1384,7 +1508,6 @@ dm_get_info (const char * mapname, struct dm_info ** dmi) return 1; if (do_get_info(mapname, *dmi) != 0) { - memset(*dmi, 0, sizeof(struct dm_info)); FREE(*dmi); *dmi = NULL; return 1; @@ -1459,6 +1582,8 @@ 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); + if (!r) + dm_log_error(2, DM_DEVICE_RENAME, dmt); dm_udev_wait(cookie); @@ -1502,8 +1627,10 @@ int dm_reassign_table(const char *name, char *old, char *new) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_TABLE, dmt); goto out; + } if (!(reload_dmt = libmp_dm_task_create(DM_DEVICE_RELOAD))) goto out; if (!dm_task_set_name(reload_dmt, name)) @@ -1534,6 +1661,7 @@ int dm_reassign_table(const char *name, char *old, char *new) dm_task_no_open_count(reload_dmt); if (!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; } @@ -1579,8 +1707,10 @@ int dm_reassign(const char *mapname) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_DEPS, dmt); goto out; + } if (!dm_task_get_info(dmt, &info)) goto out; @@ -1646,6 +1776,8 @@ int dm_setgeometry(struct multipath *mpp) } r = dm_task_run(dmt); + if (!r) + dm_log_error(3, DM_DEVICE_SET_GEOMETRY, dmt); out: dm_task_destroy(dmt); diff --git a/libmultipath/devmapper.h b/libmultipath/devmapper.h index 7557a86..f469c98 100644 --- a/libmultipath/devmapper.h +++ b/libmultipath/devmapper.h @@ -27,8 +27,15 @@ #define UUID_PREFIX "mpath-" #define UUID_PREFIX_LEN (sizeof(UUID_PREFIX) - 1) +enum { + DMP_ERR, + DMP_OK, + DMP_NOT_FOUND, +}; + void dm_init(int verbosity); -void libmp_dm_init(void); +int dm_prereq(unsigned int *v); +void skip_libmp_dm_init(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); @@ -38,6 +45,7 @@ int dm_simplecmd_noflush (int, const char *, uint16_t); int dm_addmap_create (struct multipath *mpp, char *params); int dm_addmap_reload (struct multipath *mpp, char *params, int flush); int dm_map_present (const char *); +int dm_map_present_by_uuid(const char *uuid); int dm_get_map(const char *, unsigned long long *, char *); int dm_get_status(const char *, char *); int dm_type(const char *, char *); @@ -49,7 +57,7 @@ int dm_flush_map_nopaths(const char * mapname, int deferred_remove); #define dm_suspend_and_flush_map(mapname, retries) \ _dm_flush_map(mapname, 1, 0, 1, retries) int dm_cancel_deferred_remove(struct multipath *mpp); -int dm_flush_maps (int retries); +int dm_flush_maps (int need_suspend, int retries); int dm_fail_path(const char * mapname, char * path); int dm_reinstate_path(const char * mapname, char * path); int dm_queue_if_no_path(const char *mapname, int enable); @@ -77,4 +85,13 @@ struct multipath *dm_get_multipath(const char *name); ((v[0] == minv[0]) && (v[1] == minv[1]) && (v[2] >= minv[2])) \ ) +#ifndef LIBDM_API_GET_ERRNO +#include +#define dm_task_get_errno(x) errno +#endif + +#define dm_log_error(lvl, cmd, dmt) \ + condlog(lvl, "%s: libdm task=%d error: %s", __func__, \ + cmd, strerror(dm_task_get_errno(dmt))) \ + #endif /* _DEVMAPPER_H */ diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 3e25e74..f12c2e5 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -60,19 +60,22 @@ static int set_uint(vector strvec, void *ptr) { unsigned int *uint_ptr = (unsigned int *)ptr; - char *buff, *eptr; - long res; + char *buff, *eptr, *p; + unsigned long res; int rc; buff = set_value(strvec); if (!buff) return 1; - res = strtol(buff, &eptr, 10); + p = buff; + while (isspace(*p)) + p++; + res = strtoul(p, &eptr, 10); if (eptr > buff) while (isspace(*eptr)) eptr++; - if (*buff == '\0' || *eptr != '\0' || res < 0 || res > UINT_MAX) { + if (*buff == '\0' || *eptr != '\0' || !isdigit(*p) || res > UINT_MAX) { condlog(1, "%s: invalid value for %s: \"%s\"", __func__, (char*)VECTOR_SLOT(strvec, 0), buff); rc = 1; @@ -540,6 +543,9 @@ snprint_def_queue_without_daemon (struct config *conf, declare_def_handler(checker_timeout, set_int) declare_def_snprint(checker_timeout, print_nonzero) +declare_def_handler(allow_usb_devices, set_yes_no) +declare_def_snprint(allow_usb_devices, print_yes_no) + declare_def_handler(flush_on_last_del, set_yes_no_undef) declare_def_snprint_defint(flush_on_last_del, print_yes_no_undef, DEFAULT_FLUSH) declare_ovr_handler(flush_on_last_del, set_yes_no_undef) @@ -870,7 +876,7 @@ set_dev_loss(vector strvec, void *ptr) if (!strcmp(buff, "infinity")) *uint_ptr = MAX_DEV_LOSS_TMO; else if (sscanf(buff, "%u", uint_ptr) != 1) - *uint_ptr = 0; + *uint_ptr = DEV_LOSS_TMO_UNSET; FREE(buff); return 0; @@ -879,7 +885,7 @@ set_dev_loss(vector strvec, void *ptr) int print_dev_loss(char * buff, int len, unsigned long v) { - if (!v) + if (v == DEV_LOSS_TMO_UNSET) return 0; if (v >= MAX_DEV_LOSS_TMO) return snprintf(buff, len, "\"infinity\""); @@ -1496,7 +1502,8 @@ blacklist_exceptions_handler(struct config *conf, vector strvec) static int \ ble_ ## option ## _handler (struct config *conf, vector strvec) \ { \ - char * buff; \ + char *buff; \ + int rc; \ \ if (!conf->option) \ return 1; \ @@ -1505,7 +1512,9 @@ ble_ ## option ## _handler (struct config *conf, vector strvec) \ if (!buff) \ return 1; \ \ - return store_ble(conf->option, buff, ORIGIN_CONFIG); \ + rc = store_ble(conf->option, buff, ORIGIN_CONFIG); \ + free(buff); \ + return rc; \ } #define declare_ble_device_handler(name, option, vend, prod) \ @@ -1513,6 +1522,7 @@ static int \ ble_ ## option ## _ ## name ## _handler (struct config *conf, vector strvec) \ { \ char * buff; \ + int rc; \ \ if (!conf->option) \ return 1; \ @@ -1521,7 +1531,9 @@ ble_ ## option ## _ ## name ## _handler (struct config *conf, vector strvec) \ if (!buff) \ return 1; \ \ - return set_ble_device(conf->option, vend, prod, ORIGIN_CONFIG); \ + rc = set_ble_device(conf->option, vend, prod, ORIGIN_CONFIG); \ + free(buff); \ + return rc; \ } declare_ble_handler(blist_devnode) @@ -1750,6 +1762,7 @@ init_keywords(vector keywords) install_keyword("no_path_retry", &def_no_path_retry_handler, &snprint_def_no_path_retry); install_keyword("queue_without_daemon", &def_queue_without_daemon_handler, &snprint_def_queue_without_daemon); install_keyword("checker_timeout", &def_checker_timeout_handler, &snprint_def_checker_timeout); + install_keyword("allow_usb_devices", &def_allow_usb_devices_handler, &snprint_def_allow_usb_devices); install_keyword("pg_timeout", &deprecated_handler, &snprint_deprecated); install_keyword("flush_on_last_del", &def_flush_on_last_del_handler, &snprint_def_flush_on_last_del); install_keyword("user_friendly_names", &def_user_friendly_names_handler, &snprint_def_user_friendly_names); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index ee3290c..c2e1754 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -33,6 +33,8 @@ #include "unaligned.h" #include "prioritizers/alua_rtpg.h" #include "foreign.h" +#include "configure.h" +#include "print.h" struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE] = { [VPD_VP_UNDEF] = { 0x00, "undef" }, @@ -64,6 +66,7 @@ alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice, if (safe_sprintf(pp->dev, "%s", devname)) { condlog(0, "pp->dev too small"); + err = 1; } else { pp->udev = udev_device_ref(udevice); err = pathinfo(pp, conf, flag | DI_BLACKLIST); @@ -122,27 +125,24 @@ static int path_discover (vector pathvec, struct config * conf, struct udev_device *udevice, int flag) { - struct path * pp; - const char * devname; - - devname = udev_device_get_sysname(udevice); - if (!devname) - return PATHINFO_FAILED; - - pp = find_path_by_dev(pathvec, devname); - if (!pp) { - char devt[BLK_DEV_SIZE]; - dev_t devnum = udev_device_get_devnum(udevice); + struct path *pp; + char devt[BLK_DEV_SIZE]; + dev_t devnum = udev_device_get_devnum(udevice); - snprintf(devt, BLK_DEV_SIZE, "%d:%d", - major(devnum), minor(devnum)); - pp = find_path_by_devt(pathvec, devt); - if (!pp) - return store_pathinfo(pathvec, conf, - udevice, flag | DI_BLACKLIST, - NULL); - } - return pathinfo(pp, conf, flag); + snprintf(devt, BLK_DEV_SIZE, "%d:%d", + major(devnum), minor(devnum)); + pp = find_path_by_devt(pathvec, devt); + if (!pp) + return store_pathinfo(pathvec, conf, + udevice, flag | DI_BLACKLIST, + NULL); + else + /* + * Don't use DI_BLACKLIST on paths already in pathvec. We rely + * on the caller to pre-populate the pathvec with valid paths + * only. + */ + return pathinfo(pp, conf, flag); } static void cleanup_udev_enumerate_ptr(void *arg) @@ -344,7 +344,10 @@ sysfs_get_tgt_nodename(struct path *pp, char *node) struct udev_device *parent, *tgtdev; int host, channel, tgtid = -1; - parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_device"); + if (!pp->udev) + return 1; + parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, + "scsi", "scsi_device"); if (!parent) return 1; /* Check for SAS */ @@ -353,7 +356,7 @@ sysfs_get_tgt_nodename(struct path *pp, char *node) tgtdev = udev_device_get_parent(parent); while (tgtdev) { tgtname = udev_device_get_sysname(tgtdev); - if (sscanf(tgtname, "end_device-%d:%d", + if (tgtname && sscanf(tgtname, "end_device-%d:%d", &host, &tgtid) == 2) break; tgtdev = udev_device_get_parent(tgtdev); @@ -372,11 +375,10 @@ sysfs_get_tgt_nodename(struct path *pp, char *node) while (tgtdev) { value = udev_device_get_subsystem(tgtdev); if (value && !strcmp(value, "usb")) { - pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; + pp->sg_id.proto_id = SCSI_PROTOCOL_USB; tgtname = udev_device_get_sysname(tgtdev); strlcpy(node, tgtname, NODE_NAME_SIZE); - condlog(3, "%s: skip USB device %s", pp->dev, node); - return 1; + return 0; } tgtdev = udev_device_get_parent(tgtdev); } @@ -386,12 +388,12 @@ sysfs_get_tgt_nodename(struct path *pp, char *node) /* Check for FibreChannel */ tgtdev = udev_device_get_parent(parent); value = udev_device_get_sysname(tgtdev); - if (sscanf(value, "rport-%d:%d-%d", + if (value && sscanf(value, "rport-%d:%d-%d", &host, &channel, &tgtid) == 3) { tgtdev = udev_device_new_from_subsystem_sysname(udev, "fc_remote_ports", value); if (tgtdev) { - condlog(3, "SCSI target %d:%d:%d -> " + condlog(4, "SCSI target %d:%d:%d -> " "FC rport %d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.scsi_id, host, channel, @@ -516,6 +518,11 @@ int sysfs_get_host_pci_name(const struct path *pp, char *pci_name) */ value = udev_device_get_sysname(parent); + if (!value) { + udev_device_unref(hostdev); + return 1; + } + strncpy(pci_name, value, SLOT_NAME_SIZE); udev_device_unref(hostdev); return 0; @@ -583,7 +590,7 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) struct udev_device *rport_dev = NULL; char value[16], *eptr; char rport_id[32]; - unsigned long long tmo = 0; + unsigned int tmo; int ret; sprintf(rport_id, "rport-%d:%d-%d", @@ -607,8 +614,8 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) "error %d", rport_id, -ret); goto out; } - tmo = strtoull(value, &eptr, 0); - if (value == eptr || tmo == ULLONG_MAX) { + tmo = strtoul(value, &eptr, 0); + if (value == eptr) { condlog(0, "%s: Cannot parse dev_loss_tmo " "attribute '%s'", rport_id, value); goto out; @@ -648,7 +655,7 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) } } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO && mpp->no_path_retry != NO_PATH_RETRY_QUEUE) { - condlog(3, "%s: limiting dev_loss_tmo to %d, since " + condlog(2, "%s: limiting dev_loss_tmo to %d, since " "fast_io_fail is not set", rport_id, DEFAULT_DEV_LOSS_TMO); mpp->dev_loss = DEFAULT_DEV_LOSS_TMO; @@ -670,7 +677,7 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) rport_id, value, -ret); } } - if (mpp->dev_loss > 0) { + if (mpp->dev_loss != DEV_LOSS_TMO_UNSET) { snprintf(value, 16, "%u", mpp->dev_loss); ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo", value, strlen(value)); @@ -704,7 +711,7 @@ 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) { + 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) { @@ -746,7 +753,7 @@ sysfs_set_nexus_loss_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, end_dev_id); - if (mpp->dev_loss) { + if (mpp->dev_loss != DEV_LOSS_TMO_UNSET) { snprintf(value, 11, "%u", mpp->dev_loss); if (sysfs_attr_set_value(sas_dev, "I_T_nexus_loss_timeout", value, strlen(value)) <= 0) @@ -764,6 +771,7 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint) struct path *pp; int i; unsigned int dev_loss_tmo = mpp->dev_loss; + struct path *err_path = NULL; if (mpp->no_path_retry > 0) { uint64_t no_path_retry_tmo = @@ -773,30 +781,55 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint) no_path_retry_tmo = MAX_DEV_LOSS_TMO; if (no_path_retry_tmo > dev_loss_tmo) dev_loss_tmo = no_path_retry_tmo; - condlog(3, "%s: update dev_loss_tmo to %u", - mpp->alias, dev_loss_tmo); } else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE) { dev_loss_tmo = MAX_DEV_LOSS_TMO; - condlog(3, "%s: update dev_loss_tmo to %u", - mpp->alias, dev_loss_tmo); } - mpp->dev_loss = dev_loss_tmo; - if (mpp->dev_loss && mpp->fast_io_fail > 0 && + if (mpp->dev_loss != DEV_LOSS_TMO_UNSET && + mpp->dev_loss != dev_loss_tmo) { + condlog(2, "%s: Using dev_loss_tmo=%u instead of %u because of no_path_retry setting", + mpp->alias, dev_loss_tmo, mpp->dev_loss); + mpp->dev_loss = dev_loss_tmo; + } + if (mpp->dev_loss != DEV_LOSS_TMO_UNSET && + mpp->fast_io_fail != MP_FAST_IO_FAIL_UNSET && (unsigned int)mpp->fast_io_fail >= mpp->dev_loss) { condlog(3, "%s: turning off fast_io_fail (%d is not smaller than dev_loss_tmo)", mpp->alias, mpp->fast_io_fail); mpp->fast_io_fail = MP_FAST_IO_FAIL_OFF; } - if (!mpp->dev_loss && mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) + if (mpp->dev_loss == DEV_LOSS_TMO_UNSET && + mpp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) return 0; vector_foreach_slot(mpp->paths, pp, i) { - if (pp->sg_id.proto_id == SCSI_PROTOCOL_FCP) + if (pp->bus != SYSFS_BUS_SCSI) { + if (!err_path) + err_path = pp; + continue; + } + + switch (pp->sg_id.proto_id) { + case SCSI_PROTOCOL_FCP: sysfs_set_rport_tmo(mpp, pp); - if (pp->sg_id.proto_id == SCSI_PROTOCOL_ISCSI) + continue; + case SCSI_PROTOCOL_ISCSI: sysfs_set_session_tmo(mpp, pp); - if (pp->sg_id.proto_id == SCSI_PROTOCOL_SAS) + continue; + case SCSI_PROTOCOL_SAS: sysfs_set_nexus_loss_tmo(mpp, pp); + continue; + default: + if (!err_path) + err_path = pp; + } + } + + if (err_path) { + char proto_buf[32]; + + snprint_path_protocol(proto_buf, sizeof(proto_buf), err_path); + condlog(2, "%s: setting dev_loss_tmo is unsupported for protocol %s", + mpp->alias, proto_buf); } return 0; } @@ -887,6 +920,12 @@ detect_alua(struct path * pp) int tpgs; unsigned int timeout; + + if (pp->bus != SYSFS_BUS_SCSI) { + pp->tpgs = TPGS_NONE; + return; + } + if (sysfs_get_timeout(pp, &timeout) <= 0) timeout = DEF_TIMEOUT; @@ -986,7 +1025,7 @@ parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len) } if (len >= out_len) { - condlog(2, "vpd pg80 overflow, %lu/%lu bytes required", + condlog(2, "vpd pg80 overflow, %zu/%zu bytes required", len + 1, out_len); len = out_len - 1; } @@ -1087,7 +1126,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, len = sprintf(out, "%d", vpd_type); if (2 * vpd_len >= out_len - len) { - condlog(1, "%s: WWID overflow, type %d, %lu/%lu bytes required", + condlog(1, "%s: WWID overflow, type %d, %zu/%zu bytes required", __func__, vpd_type, 2 * vpd_len + len + 1, out_len); vpd_len = (out_len - len - 1) / 2; @@ -1096,7 +1135,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, len += sprintf(out + len, "%02x", vpd[i]); } else if (vpd_type == 0x8 && vpd_len < 4) { - condlog(1, "%s: VPD length %lu too small for designator type 8", + condlog(1, "%s: VPD length %zu too small for designator type 8", __func__, vpd_len); return -EINVAL; } else if (vpd_type == 0x8) { @@ -1112,7 +1151,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, while (len > 2 && vpd[len - 2] == '\0') --len; if (len > out_len - 1) { - condlog(1, "%s: WWID overflow, type 8/%c, %lu/%lu bytes required", + condlog(1, "%s: WWID overflow, type 8/%c, %zu/%zu bytes required", __func__, out[0], len + 1, out_len); len = out_len - 1; } @@ -1136,7 +1175,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, while ((p = memchr(vpd, ' ', vpd_len))) { p_len = p - vpd; if (len + p_len > out_len - 1) { - condlog(1, "%s: WWID overflow, type 1, %lu/%lu bytes required", + condlog(1, "%s: WWID overflow, type 1, %zu/%zu bytes required", __func__, len + p_len, out_len); p_len = out_len - len - 1; } @@ -1162,7 +1201,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, p_len = vpd_len; if (p_len > 0 && len < out_len - 1) { if (len + p_len > out_len - 1) { - condlog(1, "%s: WWID overflow, type 1, %lu/%lu bytes required", + condlog(1, "%s: WWID overflow, type 1, %zu/%zu bytes required", __func__, len + p_len + 1, out_len); p_len = out_len - len - 1; } @@ -1186,14 +1225,14 @@ parse_vpd_c0_hp3par(const unsigned char *in, size_t in_len, memset(out, 0x0, out_len); if (in_len <= 4 || (in[4] > 3 && in_len < 44)) { - condlog(3, "HP/3PAR vendor specific VPD page length too short: %lu", in_len); + condlog(3, "HP/3PAR vendor specific VPD page length too short: %zu", in_len); return -EINVAL; } if (in[4] <= 3) /* revision must be > 3 to have Vomlume Name */ return -ENODATA; len = get_unaligned_be32(&in[40]); if (len > out_len || len + 44 > in_len) { - condlog(3, "HP/3PAR vendor specific Volume name too long: %lu", + condlog(3, "HP/3PAR vendor specific Volume name too long: %zu", len); return -EINVAL; } @@ -1278,7 +1317,7 @@ get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen) } static int -scsi_sysfs_pathinfo (struct path * pp, vector hwtable) +scsi_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) { struct udev_device *parent; const char *attr_path = NULL; @@ -1345,13 +1384,14 @@ scsi_sysfs_pathinfo (struct path * pp, vector hwtable) } static int -nvme_sysfs_pathinfo (struct path * pp, vector hwtable) +nvme_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) { struct udev_device *parent; const char *attr_path = NULL; const char *attr; - attr_path = udev_device_get_sysname(pp->udev); + if (pp->udev) + attr_path = udev_device_get_sysname(pp->udev); if (!attr_path) return PATHINFO_FAILED; @@ -1390,7 +1430,7 @@ nvme_sysfs_pathinfo (struct path * pp, vector hwtable) } static int -ccw_sysfs_pathinfo (struct path * pp, vector hwtable) +ccw_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) { struct udev_device *parent; char attr_buff[NAME_SIZE]; @@ -1432,6 +1472,8 @@ ccw_sysfs_pathinfo (struct path * pp, vector hwtable) * host / bus / target / lun */ attr_path = udev_device_get_sysname(parent); + if (!attr_path) + return PATHINFO_FAILED; pp->sg_id.lun = 0; if (sscanf(attr_path, "%i.%i.%x", &pp->sg_id.host_no, @@ -1449,7 +1491,7 @@ ccw_sysfs_pathinfo (struct path * pp, vector hwtable) } static int -cciss_sysfs_pathinfo (struct path * pp, vector hwtable) +cciss_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) { const char * attr_path = NULL; struct udev_device *parent; @@ -1603,8 +1645,8 @@ path_offline (struct path * pp) return PATH_DOWN; } -int -sysfs_pathinfo(struct path * pp, vector hwtable) +static int +sysfs_pathinfo(struct path *pp, const struct _vector *hwtable) { int r = common_sysfs_pathinfo(pp); @@ -1891,7 +1933,7 @@ get_udev_uid(struct path * pp, char *uid_attribute, struct udev_device *udev) } else { condlog(3, "%s: no %s attribute", pp->dev, uid_attribute); - len = -EINVAL; + len = -ENODATA; } return len; } @@ -1931,6 +1973,9 @@ static ssize_t uid_fallback(struct path *pp, int path_state, } } else if (pp->bus == SYSFS_BUS_NVME) { char value[256]; + + if (!pp->udev) + return -1; len = sysfs_attr_get_value(pp->udev, "wwid", value, sizeof(value)); if (len <= 0) @@ -2008,12 +2053,9 @@ get_uid (struct path * pp, int path_state, struct udev_device *udev, if (udev_available) { len = get_udev_uid(pp, pp->uid_attribute, udev); - if (len <= 0) - condlog(1, - "%s: failed to get udev uid: %s", - pp->dev, strerror(-len)); - else - origin = "udev"; + origin = "udev"; + if (len == 0) + condlog(1, "%s: empty udev uid", pp->dev); } if ((!udev_available || (len <= 0 && allow_fallback)) && has_uid_fallback(pp)) { @@ -2047,6 +2089,10 @@ int pathinfo(struct path *pp, struct config *conf, int mask) if (!pp || !conf) return PATHINFO_FAILED; + /* Treat removed paths as if they didn't exist */ + if (pp->initialized == INIT_REMOVED) + return PATHINFO_FAILED; + /* * For behavior backward-compatibility with multipathd, * the blacklisting by filter_property|devnode() is not @@ -2066,7 +2112,7 @@ int pathinfo(struct path *pp, struct config *conf, int mask) return PATHINFO_SKIPPED; } - if (filter_devnode(conf->blist_devnode, + if (strlen(pp->dev) != 0 && filter_devnode(conf->blist_devnode, conf->elist_devnode, pp->dev) > 0) return PATHINFO_SKIPPED; @@ -2091,6 +2137,14 @@ int pathinfo(struct path *pp, struct config *conf, int mask) if (rc != PATHINFO_OK) return rc; + + if (pp->bus == SYSFS_BUS_SCSI && + pp->sg_id.proto_id == SCSI_PROTOCOL_USB && + !conf->allow_usb_devices) { + condlog(3, "%s: skip USB device %s", pp->dev, + pp->tgt_node_name); + return PATHINFO_SKIPPED; + } } if (mask & DI_BLACKLIST && mask & DI_SYSFS) { @@ -2121,7 +2175,7 @@ int pathinfo(struct path *pp, struct config *conf, int mask) pp->fd = open(udev_device_get_devnode(pp->udev), O_RDONLY); if (pp->fd < 0) { - condlog(4, "Couldn't open node for %s: %s", + condlog(4, "Couldn't open device node for %s: %s", pp->dev, strerror(errno)); goto blank; } diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c index b856a07..b306c46 100644 --- a/libmultipath/dmparser.c +++ b/libmultipath/dmparser.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "checkers.h" #include "vector.h" @@ -18,7 +19,7 @@ #define WORD_SIZE 64 static int -merge_words(char **dst, char *word) +merge_words(char **dst, const char *word) { char * p = *dst; int len, dstlen; @@ -65,7 +66,7 @@ assemble_map (struct multipath * mp, char * params, int len) int i, j; int minio; int nr_priority_groups, initial_pg_nr; - char * p, * f; + char * p; const char *const end = params + len; char no_path_retry[] = "queue_if_no_path"; char retain_hwhandler[] = "retain_attached_hw_handler"; @@ -86,10 +87,9 @@ assemble_map (struct multipath * mp, char * params, int len) get_linux_version_code() < KERNEL_VERSION(4, 3, 0)) add_feature(&mp->features, retain_hwhandler); - f = STRDUP(mp->features); - - APPEND(p, end, "%s %s %i %i", f, mp->hwhandler, nr_priority_groups, - initial_pg_nr); + /* mp->features must not be NULL */ + APPEND(p, end, "%s %s %i %i", mp->features, mp->hwhandler, + nr_priority_groups, initial_pg_nr); vector_foreach_slot (mp->pg, pgp, i) { pgp = VECTOR_SLOT(mp->pg, i); @@ -110,22 +110,26 @@ assemble_map (struct multipath * mp, char * params, int len) } } - FREE(f); condlog(4, "%s: assembled map [%s]", mp->alias, params); return 0; err: - FREE(f); return 1; } #undef APPEND -int disassemble_map(vector pathvec, char *params, struct multipath *mpp, - int is_daemon) +/* + * Caution callers: If this function encounters yet unkown path devices, it + * adds them uninitialized to the mpp. + * Call update_pathvec_from_dm() after this function to make sure + * all data structures are in a sane state. + */ +int disassemble_map(const struct _vector *pathvec, + const char *params, struct multipath *mpp) { char * word; - char * p; + const char *p; int i, j, k; int num_features = 0; int num_hwhandler = 0; @@ -137,6 +141,7 @@ int disassemble_map(vector pathvec, char *params, struct multipath *mpp, struct path * pp; struct pathgroup * pgp; + assert(pathvec != NULL); p = params; condlog(4, "%s: disassemble map [%s]", mpp->alias, params); @@ -261,8 +266,10 @@ int disassemble_map(vector pathvec, char *params, struct multipath *mpp, if (!pgp) goto out; - if (add_pathgroup(mpp, pgp)) + if (add_pathgroup(mpp, pgp)) { + free_pathgroup(pgp, KEEP_PATHS); goto out; + } p += get_word(p, &word); @@ -281,26 +288,13 @@ int disassemble_map(vector pathvec, char *params, struct multipath *mpp, FREE(word); for (j = 0; j < num_paths; j++) { - char devname[FILE_NAME_SIZE]; - pp = NULL; p += get_word(p, &word); if (!word) goto out; - if (devt2devname(devname, FILE_NAME_SIZE, word)) { - condlog(2, "%s: cannot find block device", - word); - devname[0] = '\0'; - } - - if (pathvec) { - if (strlen(devname)) - pp = find_path_by_dev(pathvec, devname); - else - pp = find_path_by_devt(pathvec, word); - } + pp = find_path_by_devt(pathvec, word); if (!pp) { pp = alloc_path(); @@ -309,46 +303,15 @@ int disassemble_map(vector pathvec, char *params, struct multipath *mpp, goto out1; strlcpy(pp->dev_t, word, BLK_DEV_SIZE); - strlcpy(pp->dev, devname, FILE_NAME_SIZE); - if (strlen(mpp->wwid)) { - strlcpy(pp->wwid, mpp->wwid, - WWID_SIZE); - } - /* Only call this in multipath client mode */ - if (!is_daemon && store_path(pathvec, pp)) - goto out1; - } else { - if (!strlen(pp->wwid) && - strlen(mpp->wwid)) - strlcpy(pp->wwid, mpp->wwid, - WWID_SIZE); - } - FREE(word); - - if (store_path(pgp->paths, pp)) - goto out; - - /* - * Update wwid for multipaths which are not setup - * in the get_dm_mpvec() code path - */ - if (!strlen(mpp->wwid)) - strlcpy(mpp->wwid, pp->wwid, WWID_SIZE); - /* - * Update wwid for paths which may not have been - * active at the time the getuid callout was run - */ - else if (!strlen(pp->wwid)) - strlcpy(pp->wwid, mpp->wwid, WWID_SIZE); + if (store_path(pgp->paths, pp)) { + free_path(pp); + goto out1; + } + } else if (store_path(pgp->paths, pp)) + goto out1; - /* - * Do not allow in-use patch to change wwid - */ - else if (strcmp(pp->wwid, mpp->wwid) != 0) { - condlog(0, "%s: path wwid appears to have changed. Using map wwid.\n", pp->dev_t); - strlcpy(pp->wwid, mpp->wwid, WWID_SIZE); - } + FREE(word); pgp->id ^= (long)pp; pp->pgindex = i + 1; @@ -385,10 +348,10 @@ out: return 1; } -int disassemble_status(char *params, struct multipath *mpp) +int disassemble_status(const char *params, struct multipath *mpp) { - char * word; - char * p; + char *word; + const char *p; int i, j, k; int num_feature_args; int num_hwhandler_args; @@ -562,6 +525,7 @@ int disassemble_status(char *params, struct multipath *mpp) &def_minio) == 1 && def_minio != mpp->minio) mpp->minio = def_minio; + FREE(word); } else p += get_word(p, NULL); } diff --git a/libmultipath/dmparser.h b/libmultipath/dmparser.h index e1badb0..212fee5 100644 --- a/libmultipath/dmparser.h +++ b/libmultipath/dmparser.h @@ -1,3 +1,3 @@ int assemble_map (struct multipath *, char *, int); -int disassemble_map (vector, char *, struct multipath *, int); -int disassemble_status (char *, struct multipath *); +int disassemble_map (const struct _vector *, const char *, struct multipath *); +int disassemble_status (const char *, struct multipath *); diff --git a/libmultipath/foreign.c b/libmultipath/foreign.c index 0159a83..fce1934 100644 --- a/libmultipath/foreign.c +++ b/libmultipath/foreign.c @@ -236,7 +236,7 @@ static int _init_foreign(const char *multipath_dir, const char *enable) goto dl_err; } - if (vector_alloc_slot(foreigns) == NULL) { + if (!vector_alloc_slot(foreigns)) { goto dl_err; } @@ -544,8 +544,8 @@ void print_foreign_topology(int verbosity) int buflen = MAX_LINE_LEN * MAX_LINES; char *buf = NULL, *tmp = NULL; - buf = malloc(buflen); - buf[0] = '\0'; + buf = calloc(1, buflen); + while (buf != NULL) { char *c = buf; diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c index 09cdddf..b726be2 100644 --- a/libmultipath/foreign/nvme.c +++ b/libmultipath/foreign/nvme.c @@ -482,6 +482,7 @@ _find_path_by_syspath(struct nvme_map *map, const char *syspath) struct nvme_pathgroup *pg; char real[PATH_MAX]; const char *ppath; + const char *psyspath; int i; ppath = realpath(syspath, real); @@ -493,8 +494,8 @@ _find_path_by_syspath(struct nvme_map *map, const char *syspath) vector_foreach_slot(&map->pgvec, pg, i) { struct nvme_path *path = nvme_pg_to_path(pg); - if (!strcmp(ppath, - udev_device_get_syspath(path->udev))) + psyspath = udev_device_get_syspath(path->udev); + if (psyspath && !strcmp(ppath, psyspath)) return path; } condlog(4, "%s: %s: %s not found", __func__, THIS, ppath); @@ -538,6 +539,7 @@ struct udev_device *get_ctrl_blkdev(const struct context *ctx, struct udev_list_entry *item; struct udev_device *blkdev = NULL; struct udev_enumerate *enm = udev_enumerate_new(ctx->udev); + const char *devtype; if (enm == NULL) return NULL; @@ -562,7 +564,9 @@ struct udev_device *get_ctrl_blkdev(const struct context *ctx, udev_list_entry_get_name(item)); if (tmp == NULL) continue; - if (!strcmp(udev_device_get_devtype(tmp), "disk")) { + + devtype = udev_device_get_devtype(tmp); + if (devtype && !strcmp(devtype, "disk")) { blkdev = tmp; break; } else @@ -718,12 +722,12 @@ static void _find_controllers(struct context *ctx, struct nvme_map *map) test_ana_support(map, path->ctl); path->pg.gen.ops = &nvme_pg_ops; - if (vector_alloc_slot(&path->pg.pathvec) == NULL) { + if (!vector_alloc_slot(&path->pg.pathvec)) { cleanup_nvme_path(path); continue; } vector_set_slot(&path->pg.pathvec, path); - if (vector_alloc_slot(&map->pgvec) == NULL) { + if (!vector_alloc_slot(&map->pgvec)) { cleanup_nvme_path(path); continue; } @@ -779,7 +783,7 @@ static int _add_map(struct context *ctx, struct udev_device *ud, map->subsys = subsys; map->gen.ops = &nvme_map_ops; - if (vector_alloc_slot(ctx->mpvec) == NULL) { + if (!vector_alloc_slot(ctx->mpvec)) { cleanup_nvme_map(map); return FOREIGN_ERR; } @@ -793,12 +797,14 @@ int add(struct context *ctx, struct udev_device *ud) { struct udev_device *subsys; int rc; + const char *devtype; condlog(5, "%s called for \"%s\"", __func__, THIS); if (ud == NULL) return FOREIGN_ERR; - if (strcmp("disk", udev_device_get_devtype(ud))) + if ((devtype = udev_device_get_devtype(ud)) == NULL || + strcmp("disk", devtype)) return FOREIGN_IGNORED; subsys = udev_device_get_parent_with_subsystem_devtype(ud, diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index d1fcfdb..cd65afc 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -181,9 +181,9 @@ static struct hwentry default_hw[] = { .prio_name = PRIO_ALUA, }, { - /* MSA 1040, 1050, 2040 and 2050 families */ + /* MSA 1040, 1050, 1060, 2040, 2050 and 2060 families */ .vendor = "HP", - .product = "MSA [12]0[45]0 SA[NS]", + .product = "MSA [12]0[456]0 SA[NS]", .pgpolicy = GROUP_BY_PRIO, .pgfailback = -FAILBACK_IMMEDIATE, .no_path_retry = 18, @@ -428,6 +428,22 @@ static struct hwentry default_hw[] = { .pgpolicy = MULTIBUS, .no_path_retry = 10, }, + { + /* + * ETERNUS AB/HB + * Maintainer: NetApp RDAC team + */ + .vendor = "FUJITSU", + .product = "ETERNUS_AHB", + .bl_product = "Universal Xport", + .pgpolicy = GROUP_BY_PRIO, + .checker_name = RDAC, + .features = "2 pg_init_retries 50", + .hwhandler = "1 rdac", + .prio_name = PRIO_RDAC, + .pgfailback = -FAILBACK_IMMEDIATE, + .no_path_retry = 30, + }, /* * Hitachi Vantara * @@ -729,26 +745,26 @@ static struct hwentry default_hw[] = { .no_path_retry = (300 / DEFAULT_CHECKINT), .prio_name = PRIO_ALUA, }, - /* - * Lenovo - */ - { - /* + /* + * Lenovo + */ + { + /* * DE Series * * Maintainer: NetApp RDAC team */ - .vendor = "LENOVO", - .product = "DE_Series", - .bl_product = "Universal Xport", - .pgpolicy = GROUP_BY_PRIO, - .checker_name = RDAC, - .features = "2 pg_init_retries 50", - .hwhandler = "1 rdac", - .prio_name = PRIO_RDAC, - .pgfailback = -FAILBACK_IMMEDIATE, - .no_path_retry = 30, - }, + .vendor = "LENOVO", + .product = "DE_Series", + .bl_product = "Universal Xport", + .pgpolicy = GROUP_BY_PRIO, + .checker_name = RDAC, + .features = "2 pg_init_retries 50", + .hwhandler = "1 rdac", + .prio_name = PRIO_RDAC, + .pgfailback = -FAILBACK_IMMEDIATE, + .no_path_retry = 30, + }, /* * NetApp */ @@ -1262,6 +1278,18 @@ static struct hwentry default_hw[] = { .pgfailback = -FAILBACK_IMMEDIATE, .prio_name = PRIO_ALUA, }, + /* + * MacroSAN Technologies + */ + { + /* MS family */ + .vendor = "MacroSAN", + .product = "LU", + .pgpolicy = GROUP_BY_PRIO, + .pgfailback = -FAILBACK_IMMEDIATE, + .prio_name = PRIO_ALUA, + .no_path_retry = 30, + }, /* * EOL */ diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index 1b9cd6c..58bc1dd 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -35,7 +35,6 @@ #include "time-util.h" #include "io_err_stat.h" -#define IOTIMEOUT_SEC 60 #define TIMEOUT_NO_IO_NSEC 10000000 /*10ms = 10000000ns*/ #define FLAKY_PATHFAIL_THRESHOLD 2 #define CONCUR_NR_EVENT 32 @@ -301,30 +300,22 @@ int io_err_stat_handle_pathfail(struct path *path) struct timespec curr_time; if (uatomic_read(&io_err_thread_running) == 0) - return 1; + return 0; if (path->io_err_disable_reinstate) { io_err_stat_log(3, "%s: reinstate is already disabled", path->dev); - return 1; + return 0; } if (path->io_err_pathfail_cnt < 0) - return 1; + return 0; if (!path->mpp) - return 1; - if (path->mpp->marginal_path_double_failed_time <= 0 || - path->mpp->marginal_path_err_sample_time <= 0 || - path->mpp->marginal_path_err_recheck_gap_time <= 0 || - path->mpp->marginal_path_err_rate_threshold < 0) { - io_err_stat_log(4, "%s: parameter not set", path->mpp->alias); - return 1; - } - if (path->mpp->marginal_path_err_sample_time < (2 * IOTIMEOUT_SEC)) { - io_err_stat_log(2, "%s: marginal_path_err_sample_time should not less than %d", - path->mpp->alias, 2 * IOTIMEOUT_SEC); - return 1; - } + return 0; + + if (!marginal_path_check_enabled(path->mpp)) + return 0; + /* * The test should only be started for paths that have failed * repeatedly in a certain time frame, so that we have reason diff --git a/libmultipath/checkers/libsg.c b/libmultipath/libsg.c similarity index 100% rename from libmultipath/checkers/libsg.c rename to libmultipath/libsg.c diff --git a/libmultipath/checkers/libsg.h b/libmultipath/libsg.h similarity index 100% rename from libmultipath/checkers/libsg.h rename to libmultipath/libsg.h diff --git a/libmultipath/parser.c b/libmultipath/parser.c index d478b17..ed6d5d6 100644 --- a/libmultipath/parser.c +++ b/libmultipath/parser.c @@ -194,7 +194,9 @@ snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw, static const char quote_marker[] = { '\0', '"', '\0' }; bool is_quote(const char* token) { - return !memcmp(token, quote_marker, sizeof(quote_marker)); + return token[0] == quote_marker[0] && + token[1] == quote_marker[1] && + token[2] == quote_marker[2]; } vector @@ -235,6 +237,7 @@ alloc_strvec(char *string) if (!vector_alloc_slot(strvec)) goto out; + vector_set_slot(strvec, NULL); start = cp; if (*cp == '"' && !(in_string && *(cp + 1) == '"')) { cp++; @@ -300,8 +303,10 @@ alloc_strvec(char *string) (isspace((int) *cp) || !isascii((int) *cp))) && *cp != '\0') cp++; - if (*cp == '\0' || *cp == '!' || *cp == '#') + if (*cp == '\0' || + (!in_string && (*cp == '!' || *cp == '#'))) { return strvec; + } } out: vector_free(strvec); @@ -372,7 +377,7 @@ set_value(vector strvec) goto oom; } if (*alloc != '\0') - strncat(alloc, " ", 1); + strncat(alloc, " ", len - strlen(alloc)); strncat(alloc, str, len - strlen(alloc) - 1); } return alloc; @@ -431,14 +436,16 @@ is_sublevel_keyword(char *str) int validate_config_strvec(vector strvec, char *file) { - char *str; + char *str = NULL; int i; - str = VECTOR_SLOT(strvec, 0); + if (strvec && VECTOR_SIZE(strvec) > 0) + str = VECTOR_SLOT(strvec, 0); + if (str == NULL) { condlog(0, "can't parse option on line %d of %s", line_nr, file); - return -1; + return -1; } if (*str == '}') { if (VECTOR_SIZE(strvec) > 1) @@ -451,7 +458,7 @@ validate_config_strvec(vector strvec, char *file) return -1; } if (is_sublevel_keyword(str)) { - str = VECTOR_SLOT(strvec, 1); + str = VECTOR_SIZE(strvec) > 1 ? VECTOR_SLOT(strvec, 1) : NULL; if (str == NULL) condlog(0, "missing '{' on line %d of %s", line_nr, file); @@ -462,7 +469,7 @@ validate_config_strvec(vector strvec, char *file) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file); return 0; } - str = VECTOR_SLOT(strvec, 1); + str = VECTOR_SIZE(strvec) > 1 ? VECTOR_SLOT(strvec, 1) : NULL; if (str == NULL) { condlog(0, "missing value for option '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 0), line_nr, file); diff --git a/libmultipath/pgpolicies.c b/libmultipath/pgpolicies.c index 02cafdc..0e55109 100644 --- a/libmultipath/pgpolicies.c +++ b/libmultipath/pgpolicies.c @@ -196,20 +196,20 @@ int group_by_match(struct multipath * mp, vector paths, bool (*path_match_fn)(struct path *, struct path *)) { int i, j; - int * bitmap; + struct bitfield *bitmap; struct path * pp; struct pathgroup * pgp; struct path * pp2; /* init the bitmap */ - bitmap = (int *)MALLOC(VECTOR_SIZE(paths) * sizeof (int)); + bitmap = alloc_bitfield(VECTOR_SIZE(paths)); if (!bitmap) goto out; for (i = 0; i < VECTOR_SIZE(paths); i++) { - if (bitmap[i]) + if (is_bit_set_in_bitfield(i, bitmap)) continue; pp = VECTOR_SLOT(paths, i); @@ -227,11 +227,11 @@ int group_by_match(struct multipath * mp, vector paths, if (store_path(pgp->paths, pp)) goto out1; - bitmap[i] = 1; + set_bit_in_bitfield(i, bitmap); for (j = i + 1; j < VECTOR_SIZE(paths); j++) { - if (bitmap[j]) + if (is_bit_set_in_bitfield(j, bitmap)) continue; pp2 = VECTOR_SLOT(paths, j); @@ -240,7 +240,7 @@ int group_by_match(struct multipath * mp, vector paths, if (store_path(pgp->paths, pp2)) goto out1; - bitmap[j] = 1; + set_bit_in_bitfield(j, bitmap); } } } diff --git a/libmultipath/print.c b/libmultipath/print.c index b944ef3..19de2c7 100644 --- a/libmultipath/print.c +++ b/libmultipath/print.c @@ -30,6 +30,7 @@ #include "debug.h" #include "discovery.h" #include "util.h" +#include "foreign.h" #define MAX(x,y) (((x) > (y)) ? (x) : (y)) #define MIN(x,y) (((x) > (y)) ? (y) : (x)) @@ -683,6 +684,8 @@ snprint_path_protocol(char * buff, size_t len, const struct path * pp) return snprintf(buff, len, "scsi:adt"); case SCSI_PROTOCOL_ATA: return snprintf(buff, len, "scsi:ata"); + case SCSI_PROTOCOL_USB: + return snprintf(buff, len, "scsi:usb"); case SCSI_PROTOCOL_UNSPEC: default: return snprintf(buff, len, "scsi:unspec"); @@ -1958,25 +1961,25 @@ char *snprint_config(const struct config *conf, int *len, } c = reply + snprint_defaults(conf, reply, maxlen); - if ((c - reply) == maxlen) + if (c == reply + maxlen) continue; c += snprint_blacklist(conf, c, reply + maxlen - c); - if ((c - reply) == maxlen) + if (c == reply + maxlen) continue; c += snprint_blacklist_except(conf, c, reply + maxlen - c); - if ((c - reply) == maxlen) + if (c == reply + maxlen) continue; c += snprint_hwtable(conf, c, reply + maxlen - c, hwtable ? hwtable : conf->hwtable); - if ((c - reply) == maxlen) + if (c == reply + maxlen) continue; c += snprint_overrides(conf, c, reply + maxlen - c, conf->overrides); - if ((c - reply) == maxlen) + if (c == reply + maxlen) continue; if (VECTOR_SIZE(conf->mptable) > 0 || @@ -1984,7 +1987,7 @@ char *snprint_config(const struct config *conf, int *len, c += snprint_mptable(conf, c, reply + maxlen - c, mpvec); - if ((c - reply) < maxlen) { + if (c < reply + maxlen) { if (len) *len = c - reply; return reply; @@ -2026,65 +2029,68 @@ int snprint_status(char *buff, int len, const struct vectors *vecs) return fwd; } -int snprint_devices(struct config *conf, char * buff, int len, +int snprint_devices(struct config *conf, char *buff, size_t len, const struct vectors *vecs) { - DIR *blkdir; - struct dirent *blkdev; - struct stat statbuf; - char devpath[PATH_MAX]; - int threshold = MAX_LINE_LEN; - int fwd = 0; + size_t fwd = 0; int r; + struct udev_enumerate *enm; + struct udev_list_entry *item, *first; struct path * pp; - if (!(blkdir = opendir("/sys/block"))) + enm = udev_enumerate_new(udev); + if (!enm) return 1; + udev_enumerate_add_match_subsystem(enm, "block"); - if ((len - fwd - threshold) <= 0) { - closedir(blkdir); - return len; - } fwd += snprintf(buff + fwd, len - fwd, "available block devices:\n"); + r = udev_enumerate_scan_devices(enm); + if (r < 0) + goto out; - while ((blkdev = readdir(blkdir)) != NULL) { - if ((strcmp(blkdev->d_name,".") == 0) || - (strcmp(blkdev->d_name,"..") == 0)) - continue; - - if (safe_sprintf(devpath, "/sys/block/%s", blkdev->d_name)) - continue; - - if (stat(devpath, &statbuf) < 0) - continue; + first = udev_enumerate_get_list_entry(enm); + udev_list_entry_foreach(item, first) { + const char *path, *devname, *status; + struct udev_device *u_dev; - if (S_ISDIR(statbuf.st_mode) == 0) - continue; + path = udev_list_entry_get_name(item); + u_dev = udev_device_new_from_syspath(udev, path); + devname = udev_device_get_sysname(u_dev); - if ((len - fwd - threshold) <= 0) { - closedir(blkdir); - return len; - } + fwd += snprintf(buff + fwd, len - fwd, " %s", devname); + if (fwd >= len) + break; - fwd += snprintf(buff + fwd, len - fwd, " %s", - blkdev->d_name); - pp = find_path_by_dev(vecs->pathvec, blkdev->d_name); + pp = find_path_by_dev(vecs->pathvec, devname); if (!pp) { - r = filter_devnode(conf->blist_devnode, - conf->elist_devnode, blkdev->d_name); - if (r > 0) - fwd += snprintf(buff + fwd, len - fwd, - " devnode blacklisted, unmonitored"); - else if (r <= 0) - fwd += snprintf(buff + fwd, len - fwd, - " devnode whitelisted, unmonitored"); + const char *hidden; + + hidden = udev_device_get_sysattr_value(u_dev, + "hidden"); + if (hidden && !strcmp(hidden, "1")) + status = "hidden, unmonitored"; + else if (is_claimed_by_foreign(u_dev)) + status = "foreign, monitored"; + else { + r = filter_devnode(conf->blist_devnode, + conf->elist_devnode, + devname); + if (r > 0) + status = "devnode blacklisted, unmonitored"; + else + status = "devnode whitelisted, unmonitored"; + } } else - fwd += snprintf(buff + fwd, len - fwd, - " devnode whitelisted, monitored"); - fwd += snprintf(buff + fwd, len - fwd, "\n"); + status = " devnode whitelisted, monitored"; + + fwd += snprintf(buff + fwd, len - fwd, " %s\n", status); + udev_device_unref(u_dev); + if (fwd >= len) + break; } - closedir(blkdir); +out: + udev_enumerate_unref(enm); if (fwd >= len) return len; diff --git a/libmultipath/print.h b/libmultipath/print.h index e8260d0..0042cef 100644 --- a/libmultipath/print.h +++ b/libmultipath/print.h @@ -129,7 +129,7 @@ int snprint_multipath_map_json (char * buff, int len, int snprint_blacklist_report (struct config *, char *, int); int snprint_wildcards (char *, int); int snprint_status (char *, int, const struct vectors *); -int snprint_devices (struct config *, char *, int, const struct vectors *); +int snprint_devices (struct config *, char *, size_t, const struct vectors *); int snprint_path_serial (char *, size_t, const struct path *); int snprint_host_wwnn (char *, size_t, const struct path *); int snprint_host_wwpn (char *, size_t, const struct path *); diff --git a/libmultipath/prioritizers/Makefile b/libmultipath/prioritizers/Makefile index 9d0fe03..fc6e0e0 100644 --- a/libmultipath/prioritizers/Makefile +++ b/libmultipath/prioritizers/Makefile @@ -28,7 +28,7 @@ endif all: $(LIBS) -libpriopath_latency.so: path_latency.o ../checkers/libsg.o +libpriopath_latency.so: path_latency.o $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -lm libprio%.so: %.o diff --git a/libmultipath/prioritizers/alua_rtpg.c b/libmultipath/prioritizers/alua_rtpg.c index bbf5aac..420a2e3 100644 --- a/libmultipath/prioritizers/alua_rtpg.c +++ b/libmultipath/prioritizers/alua_rtpg.c @@ -188,9 +188,11 @@ retry: int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, void *resp, int resplen, unsigned int timeout) { - struct udev_device *ud; + struct udev_device *ud = NULL; - ud = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", + if (pp->udev) + ud = udev_device_get_parent_with_subsystem_devtype(pp->udev, + "scsi", "scsi_device"); if (ud != NULL) { int rc; diff --git a/libmultipath/prioritizers/alua_spc3.h b/libmultipath/prioritizers/alua_spc3.h index 18b495e..7ba2cf4 100644 --- a/libmultipath/prioritizers/alua_spc3.h +++ b/libmultipath/prioritizers/alua_spc3.h @@ -284,7 +284,7 @@ struct rtpg_data { #define RTPG_FOR_EACH_PORT_GROUP(p, g) \ for( \ g = &(p->data[0]); \ - (((char *) g) - ((char *) p)) < get_unaligned_be32(p->length); \ + ((char *) g) < ((char *) p) + get_unaligned_be32(p->length); \ g = (struct rtpg_tpg_dscr *) ( \ ((char *) g) + \ sizeof(struct rtpg_tpg_dscr) + \ diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 897e48c..7e6e0d6 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -65,7 +65,9 @@ do { \ __do_set_from_vec(struct hwentry, var, (src)->hwe, dest) #define do_set_from_hwe(var, src, dest, msg) \ - if (__do_set_from_hwe(var, src, dest)) { \ + if (!src->hwe) { \ + condlog(0, "BUG: do_set_from_hwe called with hwe == NULL"); \ + } else if (__do_set_from_hwe(var, src, dest)) { \ origin = msg; \ goto out; \ } @@ -521,7 +523,9 @@ int select_checker(struct config *conf, struct path *pp) if (check_rdac(pp)) { ckr_name = RDAC; goto out; - } else if (path_get_tpgs(pp) != TPGS_NONE) { + } + path_get_tpgs(pp); + if (pp->tpgs != TPGS_NONE && pp->tpgs != TPGS_UNDEF) { ckr_name = TUR; goto out; } @@ -764,7 +768,7 @@ int select_dev_loss(struct config *conf, struct multipath *mp) mp_set_ovr(dev_loss); mp_set_hwe(dev_loss); mp_set_conf(dev_loss); - mp->dev_loss = 0; + mp->dev_loss = DEV_LOSS_TMO_UNSET; return 0; out: print_dev_loss(buff, 12, mp->dev_loss); @@ -1064,6 +1068,12 @@ int select_marginal_path_err_sample_time(struct config *conf, struct multipath * mp_set_conf(marginal_path_err_sample_time); mp_set_default(marginal_path_err_sample_time, DEFAULT_ERR_CHECKS); out: + if (mp->marginal_path_err_sample_time > 0 && + mp->marginal_path_err_sample_time < 2 * IOTIMEOUT_SEC) { + condlog(2, "%s: configuration error: marginal_path_err_sample_time must be >= %d", + mp->alias, 2 * IOTIMEOUT_SEC); + mp->marginal_path_err_sample_time = 2 * IOTIMEOUT_SEC; + } if (print_off_int_undef(buff, 12, mp->marginal_path_err_sample_time) != 0) condlog(3, "%s: marginal_path_err_sample_time = %s %s", diff --git a/libmultipath/structs.c b/libmultipath/structs.c index 2dd378c..464596f 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -92,6 +92,7 @@ alloc_path (void) pp = (struct path *)MALLOC(sizeof(struct path)); if (pp) { + pp->initialized = INIT_NEW; pp->sg_id.host_no = -1; pp->sg_id.channel = -1; pp->sg_id.scsi_id = -1; @@ -113,19 +114,34 @@ alloc_path (void) } void -free_path (struct path * pp) +uninitialize_path(struct path *pp) { if (!pp) return; + pp->dmstate = PSTATE_UNDEF; + pp->uid_attribute = NULL; + pp->getuid = NULL; + if (checker_selected(&pp->checker)) checker_put(&pp->checker); if (prio_selected(&pp->prio)) prio_put(&pp->prio); - if (pp->fd >= 0) + if (pp->fd >= 0) { close(pp->fd); + pp->fd = -1; + } +} + +void +free_path (struct path * pp) +{ + if (!pp) + return; + + uninitialize_path(pp); if (pp->udev) { udev_device_unref(pp->udev); @@ -257,6 +273,21 @@ free_multipath (struct multipath * mpp, enum free_path_mode free_paths) mpp->dmi = NULL; } + if (!free_paths && mpp->pg) { + struct pathgroup *pgp; + struct path *pp; + int i, j; + + /* + * Make sure paths carry no reference to this mpp any more + */ + vector_foreach_slot(mpp->pg, pgp, i) { + vector_foreach_slot(pgp->paths, pp, j) + if (pp->mpp == mpp) + pp->mpp = NULL; + } + } + free_pathvec(mpp->paths, free_paths); free_pgvec(mpp->pg, free_paths); FREE_PTR(mpp->mpcontext); @@ -306,7 +337,7 @@ store_path (vector pathvec, struct path * pp) err++; } if (!strlen(pp->dev)) { - condlog(2, "%s: Empty device name", pp->dev_t); + condlog(3, "%s: Empty device name", pp->dev_t); err++; } @@ -455,30 +486,33 @@ find_path_by_devt (const struct _vector *pathvec, const char * dev_t) return NULL; } -int pathcountgr(const struct pathgroup *pgp, int state) +static int do_pathcount(const struct multipath *mpp, const int *states, + unsigned int nr_states) { + struct pathgroup *pgp; struct path *pp; int count = 0; - int i; + unsigned int i, j, k; - vector_foreach_slot (pgp->paths, pp, i) - if ((pp->state == state) || (state == PATH_WILD)) - count++; + if (!mpp->pg || !nr_states) + return count; + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { + for (k = 0; k < nr_states; k++) { + if (pp->state == states[k]) { + count++; + break; + } + } + } + } return count; } int pathcount(const struct multipath *mpp, int state) { - struct pathgroup *pgp; - int count = 0; - int i; - - if (mpp->pg) { - vector_foreach_slot (mpp->pg, pgp, i) - count += pathcountgr(pgp, state); - } - return count; + return do_pathcount(mpp, &state, 1); } int count_active_paths(const struct multipath *mpp) @@ -500,6 +534,13 @@ int count_active_paths(const struct multipath *mpp) return count; } +int count_active_pending_paths(const struct multipath *mpp) +{ + int states[] = {PATH_UP, PATH_GHOST, PATH_PENDING}; + + return do_pathcount(mpp, states, 3); +} + int pathcmp(const struct pathgroup *pgp, const struct pathgroup *cpgp) { int i, j; diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 9bd39eb..7de93d6 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -101,29 +101,13 @@ enum yes_no_undef_states { YNU_YES, }; -#define _FIND_MULTIPATHS_F (1 << 1) -#define _FIND_MULTIPATHS_I (1 << 2) -#define _FIND_MULTIPATHS_N (1 << 3) -/* - * _FIND_MULTIPATHS_F must have the same value as YNU_YES. - * Generate a compile time error if that isn't the case. - */ -extern char ___error1___[-(_FIND_MULTIPATHS_F != YNU_YES)]; - -#define find_multipaths_on(conf) \ - (!!((conf)->find_multipaths & _FIND_MULTIPATHS_F)) -#define ignore_wwids_on(conf) \ - (!!((conf)->find_multipaths & _FIND_MULTIPATHS_I)) -#define ignore_new_devs_on(conf) \ - (!!((conf)->find_multipaths & _FIND_MULTIPATHS_N)) - enum find_multipaths_states { FIND_MULTIPATHS_UNDEF = YNU_UNDEF, FIND_MULTIPATHS_OFF = YNU_NO, - FIND_MULTIPATHS_ON = _FIND_MULTIPATHS_F, - FIND_MULTIPATHS_GREEDY = _FIND_MULTIPATHS_I, - FIND_MULTIPATHS_SMART = _FIND_MULTIPATHS_F|_FIND_MULTIPATHS_I, - FIND_MULTIPATHS_STRICT = _FIND_MULTIPATHS_F|_FIND_MULTIPATHS_N, + FIND_MULTIPATHS_ON = YNU_YES, + FIND_MULTIPATHS_GREEDY, + FIND_MULTIPATHS_SMART, + FIND_MULTIPATHS_STRICT, __FIND_MULTIPATHS_LAST, }; @@ -190,6 +174,7 @@ enum scsi_protocol { SCSI_PROTOCOL_SAS = 6, SCSI_PROTOCOL_ADT = 7, /* Media Changers */ SCSI_PROTOCOL_ATA = 8, + SCSI_PROTOCOL_USB = 9, /* USB Attached SCSI (UAS), and others */ SCSI_PROTOCOL_UNSPEC = 0xf, /* No specific protocol */ }; @@ -209,6 +194,11 @@ enum initialized_states { INIT_MISSING_UDEV, INIT_REQUESTED_UDEV, INIT_OK, + /* + * INIT_REMOVED: supposed to be removed from pathvec, but still + * mapped by some multipath map because of map reload failure. + */ + INIT_REMOVED, }; enum prkey_sources { @@ -432,6 +422,7 @@ struct host_group { struct path * alloc_path (void); struct pathgroup * alloc_pathgroup (void); struct multipath * alloc_multipath (void); +void uninitialize_path(struct path *pp); void free_path (struct path *); void free_pathvec (vector vec, enum free_path_mode free_paths); void free_pathgroup (struct pathgroup * pgp, enum free_path_mode free_paths); @@ -462,9 +453,9 @@ struct path * find_path_by_devt (const struct _vector *pathvec, const char *devt struct path * find_path_by_dev (const struct _vector *pathvec, const char *dev); struct path * first_path (const struct multipath *mpp); -int pathcountgr (const struct pathgroup *, int); int pathcount (const struct multipath *, int); int count_active_paths(const struct multipath *); +int count_active_pending_paths(const struct multipath *); int pathcmp (const struct pathgroup *, const struct pathgroup *); int add_feature (char **, const char *); int remove_feature (char **, const char *); diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c index 3dbbaa0..8895fa7 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -29,6 +29,7 @@ int update_mpp_paths(struct multipath *mpp, vector pathvec) struct pathgroup * pgp; struct path * pp; int i,j; + bool store_failure = false; if (!mpp || !mpp->pg) return 0; @@ -39,13 +40,204 @@ int update_mpp_paths(struct multipath *mpp, vector pathvec) vector_foreach_slot (mpp->pg, pgp, i) { vector_foreach_slot (pgp->paths, pp, j) { - if (!find_path_by_devt(mpp->paths, pp->dev_t) && - (find_path_by_devt(pathvec, pp->dev_t)) && - store_path(mpp->paths, pp)) - return 1; + if (!find_path_by_devt(mpp->paths, pp->dev_t)) { + struct path *pp1; + + /* + * Avoid adding removed paths to the map again + * when we reload it. Such paths may exist if + * domap fails in ev_remove_path(). + */ + pp1 = find_path_by_devt(pathvec, pp->dev_t); + if (pp1 && pp->initialized != INIT_REMOVED && + store_path(mpp->paths, pp)) + store_failure = true; + } } } - return 0; + + return store_failure; +} + +static bool guess_mpp_wwid(struct multipath *mpp) +{ + int i, j; + struct pathgroup *pgp; + struct path *pp; + + if (strlen(mpp->wwid) || !mpp->pg) + return true; + + vector_foreach_slot(mpp->pg, pgp, i) { + if (!pgp->paths) + continue; + vector_foreach_slot(pgp->paths, pp, j) { + if (pp->initialized == INIT_OK && strlen(pp->wwid)) { + strlcpy(mpp->wwid, pp->wwid, sizeof(mpp->wwid)); + condlog(2, "%s: guessed WWID %s from path %s", + mpp->alias, mpp->wwid, pp->dev); + return true; + } + } + } + condlog(1, "%s: unable to guess WWID", mpp->alias); + return false; +} + +/* + * update_pathvec_from_dm() - update pathvec after disassemble_map() + * + * disassemble_map() may return block devices that are members in + * multipath maps but haven't been discovered. Check whether they + * need to be added to pathvec or discarded. + * + * Returns: true if immediate map reload is desirable + * + * Side effects: + * - may delete non-existing paths and empty pathgroups from mpp + * - may set pp->wwid and / or mpp->wwid + * - calls pathinfo() on existing paths is pathinfo_flags is not 0 + */ +bool update_pathvec_from_dm(vector pathvec, struct multipath *mpp, + int pathinfo_flags) +{ + int i, j; + struct pathgroup *pgp; + struct path *pp; + struct config *conf; + bool mpp_has_wwid; + bool must_reload = false; + + if (!mpp->pg) + return false; + + /* + * This will initialize mpp->wwid with an educated guess, + * either from the dm uuid or from a member path with properly + * determined WWID. + */ + mpp_has_wwid = guess_mpp_wwid(mpp); + + vector_foreach_slot(mpp->pg, pgp, i) { + if (!pgp->paths) + goto delete_pg; + + vector_foreach_slot(pgp->paths, pp, j) { + + if (pp->mpp && pp->mpp != mpp) { + condlog(0, "BUG: %s: found path %s which is already in %s", + mpp->alias, pp->dev, pp->mpp->alias); + + /* + * Either we added this path to the other mpp + * explicitly, or we came by here earlier and + * decided it belonged there. In both cases, + * the path should remain in the other map, + * and be deleted here. + */ + must_reload = true; + dm_fail_path(mpp->alias, pp->dev_t); + vector_del_slot(pgp->paths, j--); + continue; + } + pp->mpp = mpp; + + /* + * The way disassemble_map() works: If it encounters a + * path device which isn't found in pathvec, it adds an + * uninitialized struct path to pgp->paths, with only + * pp->dev_t filled in. Thus if pp->udev is set here, + * we know that the path is in pathvec already. + * However, it's possible that the path in pathvec is + * different from the one the kernel still had in its + * map. + */ + if (pp->udev) { + if (pathinfo_flags & ~DI_NOIO) { + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, + conf); + pathinfo(pp, conf, pathinfo_flags|DI_WWID); + pthread_cleanup_pop(1); + } + } else { + /* If this fails, the device is not in sysfs */ + pp->udev = get_udev_device(pp->dev_t, DEV_DEVT); + + if (!pp->udev) { + condlog(2, "%s: discarding non-existing path %s", + mpp->alias, pp->dev_t); + vector_del_slot(pgp->paths, j--); + free_path(pp); + must_reload = true; + continue; + } else { + int rc; + + devt2devname(pp->dev, sizeof(pp->dev), + pp->dev_t); + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, + conf); + pp->checkint = conf->checkint; + rc = pathinfo(pp, conf, + DI_SYSFS|DI_WWID|DI_BLACKLIST| + pathinfo_flags); + pthread_cleanup_pop(1); + if (rc != PATHINFO_OK) { + condlog(1, "%s: error %d in pathinfo, discarding path", + pp->dev, rc); + vector_del_slot(pgp->paths, j--); + free_path(pp); + must_reload = true; + continue; + } + condlog(2, "%s: adding new path %s", + mpp->alias, pp->dev); + store_path(pathvec, pp); + pp->tick = 1; + } + } + + /* We don't set the map WWID from paths here */ + if (!mpp_has_wwid) + continue; + + /* + * At this point, pp->udev is valid and and pp->wwid + * is the best we could get + */ + if (*pp->wwid && strcmp(mpp->wwid, pp->wwid)) { + condlog(0, "%s: path %s WWID %s doesn't match, removing from map", + mpp->wwid, pp->dev_t, pp->wwid); + /* + * This path exists, but in the wrong map. + * We can't reload the map from here. + * Make sure it isn't used in this map + * any more, and let the checker re-add + * it as it sees fit. + */ + dm_fail_path(mpp->alias, pp->dev_t); + vector_del_slot(pgp->paths, j--); + orphan_path(pp, "WWID mismatch"); + pp->tick = 1; + must_reload = true; + } else if (!*pp->wwid) { + condlog(3, "%s: setting wwid from map: %s", + pp->dev, mpp->wwid); + strlcpy(pp->wwid, mpp->wwid, + sizeof(pp->wwid)); + } + } + if (VECTOR_SIZE(pgp->paths) != 0) + continue; + delete_pg: + condlog(2, "%s: removing empty pathgroup %d", mpp->alias, i); + vector_del_slot(mpp->pg, i--); + free_pathgroup(pgp, KEEP_PATHS); + must_reload = true; + } + return must_reload; } int adopt_paths(vector pathvec, struct multipath *mpp) @@ -68,40 +260,47 @@ int adopt_paths(vector pathvec, struct multipath *mpp) pp->dev, mpp->alias); continue; } - condlog(3, "%s: ownership set to %s", - pp->dev, mpp->alias); - pp->mpp = mpp; - + if (pp->initialized == INIT_REMOVED) + continue; if (!mpp->paths && !(mpp->paths = vector_alloc())) - return 1; + goto err; - if (!find_path_by_dev(mpp->paths, pp->dev) && - store_path(mpp->paths, pp)) - return 1; conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); ret = pathinfo(pp, conf, DI_PRIO | DI_CHECKER); pthread_cleanup_pop(1); - if (ret) - return 1; + if (ret) { + condlog(3, "%s: pathinfo failed for %s", + __func__, pp->dev); + continue; + } + + if (!find_path_by_devt(mpp->paths, pp->dev_t) && + store_path(mpp->paths, pp)) + goto err; + + pp->mpp = mpp; + condlog(3, "%s: ownership set to %s", + pp->dev, mpp->alias); } } return 0; +err: + condlog(1, "error setting ownership of %s to %s", pp->dev, mpp->alias); + return 1; } void orphan_path(struct path *pp, const char *reason) { condlog(3, "%s: orphan path, %s", pp->dev, reason); + 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; - pp->dmstate = PSTATE_UNDEF; - pp->uid_attribute = NULL; - pp->getuid = NULL; - prio_put(&pp->prio); - checker_put(&pp->checker); - if (pp->fd >= 0) - close(pp->fd); - pp->fd = -1; + uninitialize_path(pp); } void orphan_paths(vector pathvec, struct multipath *mpp, const char *reason) @@ -109,26 +308,51 @@ 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) { - orphan_path(pp, reason); + if (pp->initialized == INIT_REMOVED) { + condlog(3, "%s: freeing path in removed state", + pp->dev); + vector_del_slot(pathvec, i--); + free_path(pp); + } else + orphan_path(pp, reason); } } } +void set_path_removed(struct path *pp) +{ + struct multipath *mpp = pp->mpp; + + orphan_path(pp, "removed"); + /* + * Keep link to mpp. It will be removed when the path + * is successfully removed from the map. + */ + if (!mpp) { + condlog(0, "%s: internal error: mpp == NULL", pp->dev); + return; + } + pp->mpp = mpp; + pp->initialized = INIT_REMOVED; +} + void -remove_map(struct multipath * mpp, struct vectors * vecs, int purge_vec) +remove_map(struct multipath *mpp, vector pathvec, vector mpvec, int purge_vec) { int i; /* * clear references to this map */ - orphan_paths(vecs->pathvec, mpp, "map removed internally"); + orphan_paths(pathvec, mpp, "map removed internally"); if (purge_vec && - (i = find_slot(vecs->mpvec, (void *)mpp)) != -1) - vector_del_slot(vecs->mpvec, i); + (i = find_slot(mpvec, (void *)mpp)) != -1) + vector_del_slot(mpvec, i); /* * final free @@ -142,7 +366,7 @@ remove_map_by_alias(const char *alias, struct vectors * vecs, int purge_vec) struct multipath * mpp = find_mp_by_alias(vecs->mpvec, alias); if (mpp) { condlog(2, "%s: removing map by alias", alias); - remove_map(mpp, vecs, purge_vec); + remove_map(mpp, vecs->pathvec, vecs->mpvec, purge_vec); } } @@ -156,7 +380,7 @@ remove_maps(struct vectors * vecs) return; vector_foreach_slot (vecs->mpvec, mpp, i) { - remove_map(mpp, vecs, 1); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); i--; } @@ -194,45 +418,84 @@ extract_hwe_from_path(struct multipath * mpp) } int -update_multipath_table (struct multipath *mpp, vector pathvec, int is_daemon) +update_multipath_table (struct multipath *mpp, vector pathvec, int flags) { + int r = DMP_ERR; char params[PARAMS_SIZE] = {0}; if (!mpp) - return 1; + return r; - if (dm_get_map(mpp->alias, &mpp->size, params)) { - condlog(3, "%s: cannot get map", mpp->alias); - return 1; + 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"); + return r; } - if (disassemble_map(pathvec, params, mpp, is_daemon)) { + if (disassemble_map(pathvec, params, mpp)) { condlog(3, "%s: cannot disassemble map", mpp->alias); - return 1; + return DMP_ERR; } - return 0; + /* 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 1; + return r; - if (dm_get_status(mpp->alias, status)) { - condlog(3, "%s: cannot get status", mpp->alias); - return 1; + 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 1; + return DMP_ERR; } - return 0; + return DMP_OK; +} + +static struct path *find_devt_in_pathgroups(const struct multipath *mpp, + const char *dev_t) +{ + struct pathgroup *pgp; + struct path *pp; + int j; + + vector_foreach_slot(mpp->pg, pgp, j) { + pp = find_path_by_devt(pgp->paths, dev_t); + if (pp) + return pp; + } + return NULL; +} + +static void check_removed_paths(const struct multipath *mpp, vector pathvec) +{ + struct path *pp; + int i; + + vector_foreach_slot(pathvec, pp, i) { + if (pp->initialized != INIT_REMOVED || pp->mpp != mpp) + continue; + if (!find_devt_in_pathgroups(mpp, pp->dev_t)) { + condlog(2, "%s: %s: freeing path in removed state", + __func__, pp->dev); + vector_del_slot(pathvec, i--); + free_path(pp); + } + } } void sync_paths(struct multipath *mpp, vector pathvec) @@ -251,23 +514,28 @@ 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"); } } + check_removed_paths(mpp, 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 -update_multipath_strings(struct multipath *mpp, vector pathvec, int is_daemon) +update_multipath_strings(struct multipath *mpp, vector pathvec) { struct pathgroup *pgp; - int i; + int i, r = DMP_ERR; if (!mpp) - return 1; + return r; update_mpp_paths(mpp, pathvec); condlog(4, "%s: %s", mpp->alias, __FUNCTION__); @@ -276,18 +544,20 @@ update_multipath_strings(struct multipath *mpp, vector pathvec, int is_daemon) free_pgvec(mpp->pg, KEEP_PATHS); mpp->pg = NULL; - if (update_multipath_table(mpp, pathvec, is_daemon)) - return 1; + r = update_multipath_table(mpp, pathvec, 0); + if (r != DMP_OK) + return r; sync_paths(mpp, pathvec); - if (update_multipath_status(mpp)) - return 1; + r = update_multipath_status(mpp); + if (r != DMP_OK) + return r; vector_foreach_slot(mpp->pg, pgp, i) if (pgp->paths) path_group_prio_update(pgp); - return 0; + return DMP_OK; } static void enter_recovery_mode(struct multipath *mpp) @@ -336,7 +606,7 @@ static void leave_recovery_mode(struct multipath *mpp) void __set_no_path_retry(struct multipath *mpp, bool check_features) { - bool is_queueing; + bool is_queueing = false; /* assign a value to make gcc happy */ check_features = check_features && mpp->features != NULL; if (check_features) @@ -440,7 +710,8 @@ struct multipath *add_map_with_path(struct vectors *vecs, struct path *pp, goto out; mpp->size = pp->size; - if (adopt_paths(vecs->pathvec, mpp)) + if (adopt_paths(vecs->pathvec, mpp) || + find_slot(vecs->pathvec, pp) == -1) goto out; if (add_vec) { @@ -453,15 +724,15 @@ struct multipath *add_map_with_path(struct vectors *vecs, struct path *pp, return mpp; out: - remove_map(mpp, vecs, PURGE_VEC); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); return NULL; } -int verify_paths(struct multipath *mpp, struct vectors *vecs) +int verify_paths(struct multipath *mpp) { struct path * pp; int count = 0; - int i, j; + int i; if (!mpp) return 0; @@ -470,13 +741,13 @@ int verify_paths(struct multipath *mpp, struct vectors *vecs) /* * see if path is in sysfs */ - if (sysfs_attr_get_value(pp->udev, "dev", + if (!pp->udev || sysfs_attr_get_value(pp->udev, "dev", pp->dev_t, BLK_DEV_SIZE) < 0) { if (pp->state != PATH_DOWN) { condlog(1, "%s: removing valid path %s in state %d", mpp->alias, pp->dev, pp->state); } else { - condlog(3, "%s: failed to access path %s", + condlog(2, "%s: failed to access path %s", mpp->alias, pp->dev); } count++; @@ -489,10 +760,12 @@ int verify_paths(struct multipath *mpp, struct vectors *vecs) */ if (mpp->hwe == pp->hwe) mpp->hwe = NULL; - if ((j = find_slot(vecs->pathvec, - (void *)pp)) != -1) - vector_del_slot(vecs->pathvec, j); - free_path(pp); + /* + * Don't delete path from pathvec yet. We'll do this + * after the path has been removed from the map, in + * sync_paths(). + */ + set_path_removed(pp); } else { condlog(4, "%s: verified path %s dev_t %s", mpp->alias, pp->dev, pp->dev_t); diff --git a/libmultipath/structs_vec.h b/libmultipath/structs_vec.h index 2a5e3d6..ee2b723 100644 --- a/libmultipath/structs_vec.h +++ b/libmultipath/structs_vec.h @@ -18,16 +18,22 @@ int adopt_paths (vector pathvec, struct multipath * mpp); void orphan_paths(vector pathvec, struct multipath *mpp, const char *reason); void orphan_path (struct path * pp, const char *reason); +void set_path_removed(struct path *pp); -int verify_paths(struct multipath * mpp, struct vectors * vecs); +int verify_paths(struct multipath *mpp); +bool update_pathvec_from_dm(vector pathvec, struct multipath *mpp, + int pathinfo_flags); int update_mpp_paths(struct multipath * mpp, vector pathvec); -int update_multipath_strings (struct multipath *mpp, vector pathvec, - int is_daemon); +int update_multipath_strings (struct multipath *mpp, vector pathvec); void extract_hwe_from_path(struct multipath * mpp); -#define PURGE_VEC 1 +enum { + KEEP_VEC, + PURGE_VEC, +}; -void remove_map (struct multipath * mpp, struct vectors * vecs, int purge_vec); +void remove_map (struct multipath *mpp, vector pathvec, vector mpvec, + int purge_vec); void remove_map_by_alias(const char *alias, struct vectors * vecs, int purge_vec); void remove_maps (struct vectors * vecs); @@ -37,8 +43,7 @@ struct multipath * add_map_with_path (struct vectors * vecs, struct path * pp, int add_vec); void update_queue_mode_del_path(struct multipath *mpp); void update_queue_mode_add_path(struct multipath *mpp); -int update_multipath_table (struct multipath *mpp, vector pathvec, - int is_daemon); +int update_multipath_table (struct multipath *mpp, vector pathvec, int flags); int update_multipath_status (struct multipath *mpp); vector get_used_hwes(const struct _vector *pathvec); diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c index 62ec2ed..5390de6 100644 --- a/libmultipath/sysfs.c +++ b/libmultipath/sysfs.c @@ -278,7 +278,11 @@ int sysfs_check_holders(char * check_devt, char * new_devt) continue; } table_name = dm_mapname(major, table_minor); - + if (!table_name) { + condlog(2, "%s: mapname not found for %d:%d", check_dev, + major, table_minor); + continue; + } condlog(0, "%s: reassign table %s old %s new %s", check_dev, table_name, check_devt, new_devt); @@ -295,7 +299,7 @@ static int select_dm_devs(const struct dirent *di) return fnmatch("dm-*", di->d_name, FNM_FILE_NAME) == 0; } -bool sysfs_is_multipathed(const struct path *pp) +bool sysfs_is_multipathed(struct path *pp, bool set_wwid) { char pathbuf[PATH_MAX]; struct scandir_result sr; @@ -325,7 +329,7 @@ bool sysfs_is_multipathed(const struct path *pp) for (i = 0; i < r && !found; i++) { long fd; int nr; - char uuid[6]; + char uuid[WWID_SIZE + UUID_PREFIX_LEN]; if (safe_snprintf(pathbuf + n, sizeof(pathbuf) - n, "/%s/dm/uuid", di[i]->d_name)) @@ -339,12 +343,26 @@ bool sysfs_is_multipathed(const struct path *pp) pthread_cleanup_push(close_fd, (void *)fd); nr = read(fd, uuid, sizeof(uuid)); - if (nr == sizeof(uuid) && !memcmp(uuid, "mpath-", sizeof(uuid))) + if (nr > (int)UUID_PREFIX_LEN && + !memcmp(uuid, UUID_PREFIX, UUID_PREFIX_LEN)) found = true; else if (nr < 0) { - condlog(1, "%s: error reading from %s: %s", - __func__, pathbuf, strerror(errno)); + 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); } pthread_cleanup_pop(1); diff --git a/libmultipath/sysfs.h b/libmultipath/sysfs.h index 9ae30b3..72b39ab 100644 --- a/libmultipath/sysfs.h +++ b/libmultipath/sysfs.h @@ -14,5 +14,5 @@ ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name, unsigned char * value, size_t value_len); int sysfs_get_size (struct path *pp, unsigned long long * size); int sysfs_check_holders(char * check_devt, char * new_devt); -bool sysfs_is_multipathed(const struct path *pp); +bool sysfs_is_multipathed(struct path *pp, bool set_wwid); #endif diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c index d38e8a7..d3061bf 100644 --- a/libmultipath/uevent.c +++ b/libmultipath/uevent.c @@ -60,14 +60,14 @@ typedef int (uev_trigger)(struct uevent *, void * trigger_data); -LIST_HEAD(uevq); -pthread_mutex_t uevq_lock = PTHREAD_MUTEX_INITIALIZER; -pthread_mutex_t *uevq_lockp = &uevq_lock; -pthread_cond_t uev_cond = PTHREAD_COND_INITIALIZER; -pthread_cond_t *uev_condp = &uev_cond; -uev_trigger *my_uev_trigger; -void * my_trigger_data; -int servicing_uev; +static LIST_HEAD(uevq); +static pthread_mutex_t uevq_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t *uevq_lockp = &uevq_lock; +static pthread_cond_t uev_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t *uev_condp = &uev_cond; +static uev_trigger *my_uev_trigger; +static void *my_trigger_data; +static int servicing_uev; int is_uevent_busy(void) { @@ -91,8 +91,7 @@ struct uevent * alloc_uevent (void) return uev; } -void -uevq_cleanup(struct list_head *tmpq) +static void uevq_cleanup(struct list_head *tmpq) { struct uevent *uev, *tmp; @@ -129,7 +128,7 @@ static const char* uevent_get_env_var(const struct uevent *uev, } } - condlog(4, "%s: %s -> '%s'", __func__, attr, p); + condlog(4, "%s: %s -> '%s'", __func__, attr, p ?: "(null)"); return p; invalid: @@ -137,7 +136,7 @@ invalid: return NULL; } -static int uevent_get_env_positive_int(const struct uevent *uev, +int uevent_get_env_positive_int(const struct uevent *uev, const char *attr) { const char *p = uevent_get_env_var(uev, attr); @@ -172,8 +171,7 @@ uevent_get_wwid(struct uevent *uev) uev->wwid = val; } -bool -uevent_need_merge(void) +static bool uevent_need_merge(void) { struct config * conf; bool need_merge = false; @@ -186,8 +184,7 @@ uevent_need_merge(void) return need_merge; } -bool -uevent_can_discard(struct uevent *uev) +static bool uevent_can_discard(struct uevent *uev) { int invalid = 0; struct config * conf; @@ -212,7 +209,7 @@ uevent_can_discard(struct uevent *uev) return false; } -bool +static bool uevent_can_filter(struct uevent *earlier, struct uevent *later) { @@ -246,7 +243,7 @@ uevent_can_filter(struct uevent *earlier, struct uevent *later) return false; } -bool +static bool merge_need_stop(struct uevent *earlier, struct uevent *later) { /* @@ -283,7 +280,7 @@ merge_need_stop(struct uevent *earlier, struct uevent *later) return false; } -bool +static bool uevent_can_merge(struct uevent *earlier, struct uevent *later) { /* merge paths uevents @@ -302,7 +299,7 @@ uevent_can_merge(struct uevent *earlier, struct uevent *later) return false; } -void +static void uevent_prepare(struct list_head *tmpq) { struct uevent *uev, *tmp; @@ -322,7 +319,7 @@ uevent_prepare(struct list_head *tmpq) } } -void +static void uevent_filter(struct uevent *later, struct list_head *tmpq) { struct uevent *earlier, *tmp; @@ -345,7 +342,7 @@ uevent_filter(struct uevent *later, struct list_head *tmpq) } } -void +static void uevent_merge(struct uevent *later, struct list_head *tmpq) { struct uevent *earlier, *tmp; @@ -366,7 +363,7 @@ uevent_merge(struct uevent *later, struct list_head *tmpq) } } -void +static void merge_uevq(struct list_head *tmpq) { struct uevent *later; @@ -379,7 +376,7 @@ merge_uevq(struct list_head *tmpq) } } -void +static void service_uevq(struct list_head *tmpq) { struct uevent *uev, *tmp; @@ -450,244 +447,7 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), return 0; } -struct uevent *uevent_from_buffer(char *buf, ssize_t buflen) -{ - struct uevent *uev; - char *buffer; - size_t bufpos; - int i; - char *pos; - - uev = alloc_uevent(); - if (!uev) { - condlog(1, "lost uevent, oom"); - return NULL; - } - - if ((size_t)buflen > sizeof(buf)-1) - buflen = sizeof(buf)-1; - - /* - * Copy the shared receive buffer contents to buffer private - * to this uevent so we can immediately reuse the shared buffer. - */ - memcpy(uev->buffer, buf, HOTPLUG_BUFFER_SIZE + OBJECT_SIZE); - buffer = uev->buffer; - buffer[buflen] = '\0'; - - /* save start of payload */ - bufpos = strlen(buffer) + 1; - - /* action string */ - uev->action = buffer; - pos = strchr(buffer, '@'); - if (!pos) { - condlog(3, "bad action string '%s'", buffer); - FREE(uev); - return NULL; - } - pos[0] = '\0'; - - /* sysfs path */ - uev->devpath = &pos[1]; - - /* hotplug events have the environment attached - reconstruct envp[] */ - for (i = 0; (bufpos < (size_t)buflen) && (i < HOTPLUG_NUM_ENVP-1); i++) { - int keylen; - char *key; - - key = &buffer[bufpos]; - keylen = strlen(key); - uev->envp[i] = key; - /* Filter out sequence number */ - if (strncmp(key, "SEQNUM=", 7) == 0) { - char *eptr; - - uev->seqnum = strtoul(key + 7, &eptr, 10); - if (eptr == key + 7) - uev->seqnum = -1; - } - bufpos += keylen + 1; - } - uev->envp[i] = NULL; - - condlog(3, "uevent %ld '%s' from '%s'", uev->seqnum, - uev->action, uev->devpath); - uev->kernel = strrchr(uev->devpath, '/'); - if (uev->kernel) - uev->kernel++; - - /* print payload environment */ - for (i = 0; uev->envp[i] != NULL; i++) - condlog(5, "%s", uev->envp[i]); - - return uev; -} - -int failback_listen(void) -{ - int sock; - struct sockaddr_nl snl; - struct sockaddr_un sun; - socklen_t addrlen; - int retval; - int rcvbufsz = 128*1024; - int rcvsz = 0; - int rcvszsz = sizeof(rcvsz); - unsigned int *prcvszsz = (unsigned int *)&rcvszsz; - const int feature_on = 1; - /* - * First check whether we have a udev socket - */ - memset(&sun, 0x00, sizeof(struct sockaddr_un)); - sun.sun_family = AF_LOCAL; - strcpy(&sun.sun_path[1], "/org/kernel/dm/multipath_event"); - addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(sun.sun_path+1) + 1; - - sock = socket(AF_LOCAL, SOCK_DGRAM, 0); - if (sock >= 0) { - - condlog(3, "reading events from udev socket."); - - /* the bind takes care of ensuring only one copy running */ - retval = bind(sock, (struct sockaddr *) &sun, addrlen); - if (retval < 0) { - condlog(0, "bind failed, exit"); - goto exit; - } - - /* enable receiving of the sender credentials */ - retval = setsockopt(sock, SOL_SOCKET, SO_PASSCRED, - &feature_on, sizeof(feature_on)); - if (retval < 0) { - condlog(0, "failed to enable credential passing, exit"); - goto exit; - } - - } else { - /* Fallback to read kernel netlink events */ - memset(&snl, 0x00, sizeof(struct sockaddr_nl)); - snl.nl_family = AF_NETLINK; - snl.nl_pid = getpid(); - snl.nl_groups = 0x01; - - sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); - if (sock == -1) { - condlog(0, "error getting socket, exit"); - return 1; - } - - condlog(3, "reading events from kernel."); - - /* - * try to avoid dropping uevents, even so, this is not a guarantee, - * but it does help to change the netlink uevent socket's - * receive buffer threshold from the default value of 106,496 to - * the maximum value of 262,142. - */ - retval = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbufsz, - sizeof(rcvbufsz)); - - if (retval < 0) { - condlog(0, "error setting receive buffer size for socket, exit"); - exit(1); - } - retval = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvsz, prcvszsz); - if (retval < 0) { - condlog(0, "error setting receive buffer size for socket, exit"); - exit(1); - } - condlog(3, "receive buffer size for socket is %u.", rcvsz); - - /* enable receiving of the sender credentials */ - if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, - &feature_on, sizeof(feature_on)) < 0) { - condlog(0, "error on enabling credential passing for socket"); - exit(1); - } - - retval = bind(sock, (struct sockaddr *) &snl, - sizeof(struct sockaddr_nl)); - if (retval < 0) { - condlog(0, "bind failed, exit"); - goto exit; - } - } - - while (1) { - size_t bufpos; - ssize_t buflen; - struct uevent *uev; - struct msghdr smsg; - struct iovec iov; - char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; - struct cmsghdr *cmsg; - struct ucred *cred; - static char buf[HOTPLUG_BUFFER_SIZE + OBJECT_SIZE]; - - memset(buf, 0x00, sizeof(buf)); - iov.iov_base = &buf; - iov.iov_len = sizeof(buf); - memset (&smsg, 0x00, sizeof(struct msghdr)); - smsg.msg_iov = &iov; - smsg.msg_iovlen = 1; - smsg.msg_control = cred_msg; - smsg.msg_controllen = sizeof(cred_msg); - - buflen = recvmsg(sock, &smsg, 0); - if (buflen < 0) { - if (errno != EINTR) - condlog(0, "error receiving message, errno %d", errno); - continue; - } - - cmsg = CMSG_FIRSTHDR(&smsg); - if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { - condlog(3, "no sender credentials received, message ignored"); - continue; - } - - cred = (struct ucred *)CMSG_DATA(cmsg); - if (cred->uid != 0) { - condlog(3, "sender uid=%d, message ignored", cred->uid); - continue; - } - - /* skip header */ - bufpos = strlen(buf) + 1; - if (bufpos < sizeof("a@/d") || bufpos >= sizeof(buf)) { - condlog(3, "invalid message length"); - continue; - } - - /* check message header */ - if (strstr(buf, "@/") == NULL) { - condlog(3, "unrecognized message header"); - continue; - } - if ((size_t)buflen > sizeof(buf)-1) { - condlog(2, "buffer overflow for received uevent"); - buflen = sizeof(buf)-1; - } - - uev = uevent_from_buffer(buf, buflen); - if (!uev) - continue; - /* - * Queue uevent and poke service pthread. - */ - pthread_mutex_lock(uevq_lockp); - list_add_tail(&uev->node, &uevq); - pthread_cond_signal(uev_condp); - pthread_mutex_unlock(uevq_lockp); - } - -exit: - close(sock); - return 1; -} - -struct uevent *uevent_from_udev_device(struct udev_device *dev) +static struct uevent *uevent_from_udev_device(struct udev_device *dev) { struct uevent *uev; int i = 0; @@ -749,7 +509,7 @@ struct uevent *uevent_from_udev_device(struct udev_device *dev) return uev; } -bool uevent_burst(struct timeval *start_time, int events) +static bool uevent_burst(struct timeval *start_time, int events) { struct timeval diff_time, end_time; unsigned long speed; @@ -786,7 +546,6 @@ int uevent_listen(struct udev *udev) struct udev_monitor *monitor = NULL; int fd, socket_flags, events; struct timeval start_time; - int need_failback = 1; int timeout = 30; LIST_HEAD(uevlisten_tmp); @@ -806,7 +565,7 @@ int uevent_listen(struct udev *udev) monitor = udev_monitor_new_from_netlink(udev, "udev"); if (!monitor) { condlog(2, "failed to create udev monitor"); - goto failback; + goto out_udev; } pthread_cleanup_push(monitor_cleanup, monitor); #ifdef LIBUDEV_API_RECVBUF @@ -891,32 +650,14 @@ int uevent_listen(struct udev *udev) gettimeofday(&start_time, NULL); timeout = 30; } - need_failback = 0; out: pthread_cleanup_pop(1); -failback: - if (need_failback) - err = failback_listen(); +out_udev: pthread_cleanup_pop(1); return err; } -int uevent_get_major(const struct uevent *uev) -{ - return uevent_get_env_positive_int(uev, "MAJOR"); -} - -int uevent_get_minor(const struct uevent *uev) -{ - return uevent_get_env_positive_int(uev, "MINOR"); -} - -int uevent_get_disk_ro(const struct uevent *uev) -{ - return uevent_get_env_positive_int(uev, "DISK_RO"); -} - -static char *uevent_get_dm_str(const struct uevent *uev, char *attr) +char *uevent_get_dm_str(const struct uevent *uev, char *attr) { const char *tmp = uevent_get_env_var(uev, attr); @@ -925,21 +666,6 @@ static char *uevent_get_dm_str(const struct uevent *uev, char *attr) return strdup(tmp); } -char *uevent_get_dm_name(const struct uevent *uev) -{ - return uevent_get_dm_str(uev, "DM_NAME"); -} - -char *uevent_get_dm_path(const struct uevent *uev) -{ - return uevent_get_dm_str(uev, "DM_PATH"); -} - -char *uevent_get_dm_action(const struct uevent *uev) -{ - return uevent_get_dm_str(uev, "DM_ACTION"); -} - bool uevent_is_mpath(const struct uevent *uev) { const char *uuid = uevent_get_env_var(uev, "DM_UUID"); diff --git a/libmultipath/uevent.h b/libmultipath/uevent.h index 0aa8675..61ca1b5 100644 --- a/libmultipath/uevent.h +++ b/libmultipath/uevent.h @@ -9,10 +9,6 @@ #define HOTPLUG_NUM_ENVP 32 #define OBJECT_SIZE 512 -#ifndef NETLINK_KOBJECT_UEVENT -#define NETLINK_KOBJECT_UEVENT 15 -#endif - struct udev; struct uevent { @@ -28,17 +24,48 @@ struct uevent { char *envp[HOTPLUG_NUM_ENVP]; }; +struct uevent *alloc_uevent(void); int is_uevent_busy(void); int uevent_listen(struct udev *udev); int uevent_dispatch(int (*store_uev)(struct uevent *, void * trigger_data), void * trigger_data); -int uevent_get_major(const struct uevent *uev); -int uevent_get_minor(const struct uevent *uev); -int uevent_get_disk_ro(const struct uevent *uev); -char *uevent_get_dm_name(const struct uevent *uev); -char *uevent_get_dm_path(const struct uevent *uev); -char *uevent_get_dm_action(const struct uevent *uev); bool uevent_is_mpath(const struct uevent *uev); +void uevent_get_wwid(struct uevent *uev); + +int uevent_get_env_positive_int(const struct uevent *uev, + const char *attr); + +static inline int uevent_get_major(const struct uevent *uev) +{ + return uevent_get_env_positive_int(uev, "MAJOR"); +} + +static inline int uevent_get_minor(const struct uevent *uev) +{ + return uevent_get_env_positive_int(uev, "MINOR"); +} + +static inline int uevent_get_disk_ro(const struct uevent *uev) +{ + return uevent_get_env_positive_int(uev, "DISK_RO"); +} + +char *uevent_get_dm_str(const struct uevent *uev, char *attr); + +static inline char *uevent_get_dm_name(const struct uevent *uev) +{ + return uevent_get_dm_str(uev, "DM_NAME"); +} + +static inline char *uevent_get_dm_path(const struct uevent *uev) +{ + return uevent_get_dm_str(uev, "DM_PATH"); +} + +static inline char *uevent_get_dm_action(const struct uevent *uev) +{ + return uevent_get_dm_str(uev, "DM_ACTION"); +} #endif /* _UEVENT_H */ diff --git a/libmultipath/util.c b/libmultipath/util.c index 51c38c8..1748eaf 100644 --- a/libmultipath/util.c +++ b/libmultipath/util.c @@ -21,17 +21,31 @@ #include "checkers.h" #include "vector.h" #include "structs.h" +#include "config.h" #include "log.h" size_t strchop(char *str) { - int i; + size_t i; - for (i=strlen(str)-1; i >=0 && isspace(str[i]); --i) ; + for (i = strlen(str) - 1; i != (size_t) -1 && isspace(str[i]); i--) ; str[++i] = '\0'; - return strlen(str); + return i; +} + +#ifndef __GLIBC__ +/* + * glibc's non-destructive version of basename() + * License: LGPL-2.1-or-later + */ +static const char *__basename(const char *filename) +{ + char *p = strrchr(filename, '/'); + return p ? p + 1 : filename; } +#define basename(x) __basename(x) +#endif int basenamecpy (const char *src, char *dst, size_t size) @@ -52,7 +66,7 @@ basenamecpy (const char *src, char *dst, size_t size) } int -filepresent (char * run) { +filepresent (const char *run) { struct stat buf; if(!stat(run, &buf)) @@ -60,7 +74,7 @@ filepresent (char * run) { return 0; } -char *get_next_string(char **temp, char *split_char) +char *get_next_string(char **temp, const char *split_char) { char *token = NULL; token = strsep(temp, split_char); @@ -70,9 +84,9 @@ char *get_next_string(char **temp, char *split_char) } int -get_word (char * sentence, char ** word) +get_word (const char *sentence, char **word) { - char * p; + const char *p; int len; int skip = 0; @@ -112,132 +126,62 @@ get_word (char * sentence, char ** word) return skip + len; } -size_t strlcpy(char *dst, const char *src, size_t size) +size_t strlcpy(char * restrict dst, const char * restrict src, size_t size) { size_t bytes = 0; - char *q = dst; - const char *p = src; char ch; - while ((ch = *p++)) { - if (bytes+1 < size) - *q++ = ch; + while ((ch = *src++)) { + if (bytes + 1 < size) + *dst++ = ch; bytes++; } /* If size == 0 there is no space for a final null... */ if (size) - *q = '\0'; + *dst = '\0'; return bytes; } -size_t strlcat(char *dst, const char *src, size_t size) +size_t strlcat(char * restrict dst, const char * restrict src, size_t size) { size_t bytes = 0; - char *q = dst; - const char *p = src; char ch; - while (bytes < size && *q) { - q++; + while (bytes < size && *dst) { + dst++; bytes++; } if (bytes == size) return (bytes + strlen(src)); - while ((ch = *p++)) { - if (bytes+1 < size) - *q++ = ch; + while ((ch = *src++)) { + if (bytes + 1 < size) + *dst++ = ch; bytes++; } - *q = '\0'; + *dst = '\0'; return bytes; } -int devt2devname(char *devname, int devname_len, char *devt) +int devt2devname(char *devname, int devname_len, const char *devt) { - FILE *fd; - unsigned int tmpmaj, tmpmin, major, minor; - char dev[FILE_NAME_SIZE]; - char block_path[PATH_SIZE]; - struct stat statbuf; - - memset(block_path, 0, sizeof(block_path)); - memset(dev, 0, sizeof(dev)); - if (sscanf(devt, "%u:%u", &major, &minor) != 2) { - condlog(0, "Invalid device number %s", devt); - return 1; - } - - if (devname_len > FILE_NAME_SIZE) - devname_len = FILE_NAME_SIZE; - - if (stat("/sys/dev/block", &statbuf) == 0) { - /* Newer kernels have /sys/dev/block */ - sprintf(block_path,"/sys/dev/block/%u:%u", major, minor); - dev[FILE_NAME_SIZE - 1] = '\0'; - if (lstat(block_path, &statbuf) == 0) { - if (S_ISLNK(statbuf.st_mode) && - readlink(block_path, dev, FILE_NAME_SIZE-1) > 0) { - char *p = strrchr(dev, '/'); - - if (!p) { - condlog(0, "No sysfs entry for %s", - block_path); - return 1; - } - p++; - strlcpy(devname, p, devname_len); - return 0; - } - } - condlog(4, "%s is invalid", block_path); - return 1; - } - memset(block_path, 0, sizeof(block_path)); - - if (!(fd = fopen("/proc/partitions", "r"))) { - condlog(0, "Cannot open /proc/partitions"); - return 1; - } - - while (!feof(fd)) { - int r = fscanf(fd,"%u %u %*d %s",&tmpmaj, &tmpmin, dev); - if (!r) { - r = fscanf(fd,"%*s\n"); - continue; - } - if (r != 3) - continue; - - if ((major == tmpmaj) && (minor == tmpmin)) { - if (safe_sprintf(block_path, "/sys/block/%s", dev)) { - condlog(0, "device name %s is too long", dev); - fclose(fd); - return 1; - } - break; - } - } - fclose(fd); + struct udev_device *u_dev; + int r; - if (strncmp(block_path,"/sys/block", 10)) { - condlog(3, "No device found for %u:%u", major, minor); + if (!devname || !devname_len || !devt) return 1; - } - if (stat(block_path, &statbuf) < 0) { - condlog(0, "No sysfs entry for %s", block_path); + u_dev = udev_device_new_from_devnum(udev, 'b', parse_devt(devt)); + if (!u_dev) { + condlog(0, "\"%s\": invalid major/minor numbers, not found in sysfs", devt); return 1; } + r = strlcpy(devname, udev_device_get_sysname(u_dev), devname_len); + udev_device_unref(u_dev); - if (S_ISDIR(statbuf.st_mode) == 0) { - condlog(0, "sysfs entry %s is not a directory", block_path); - return 1; - } - basenamecpy((const char *)block_path, devname, devname_len); - return 0; + return !(r < devname_len); } /* This function returns a pointer inside of the supplied pathname string. @@ -319,9 +263,9 @@ int systemd_service_enabled_in(const char *dev, const char *prefix) p = d->d_name + strlen(d->d_name) - 6; if (strcmp(p, ".wants")) continue; - snprintf(file, sizeof(file), "%s/%s/%s", - path, d->d_name, service); - if (stat(file, &stbuf) == 0) { + if (!safe_sprintf(file, "%s/%s/%s", + path, d->d_name, service) + && stat(file, &stbuf) == 0) { condlog(3, "%s: found %s", dev, file); found++; break; @@ -381,7 +325,7 @@ int get_linux_version_code(void) return _linux_version_code; } -int parse_prkey(char *ptr, uint64_t *prkey) +int parse_prkey(const char *ptr, uint64_t *prkey) { if (!ptr) return 1; @@ -398,7 +342,7 @@ int parse_prkey(char *ptr, uint64_t *prkey) return 0; } -int parse_prkey_flags(char *ptr, uint64_t *prkey, uint8_t *flags) +int parse_prkey_flags(const char *ptr, uint64_t *prkey, uint8_t *flags) { char *flagstr; @@ -447,11 +391,13 @@ void set_max_fds(rlim_t max_fds) if (setrlimit(RLIMIT_NOFILE, &fd_limit) < 0) { condlog(0, "can't set open fds limit to " "%lu/%lu : %s", - fd_limit.rlim_cur, fd_limit.rlim_max, + (unsigned long)fd_limit.rlim_cur, + (unsigned long)fd_limit.rlim_max, strerror(errno)); } else { condlog(3, "set open fds limit to %lu/%lu", - fd_limit.rlim_cur, fd_limit.rlim_max); + (unsigned long)fd_limit.rlim_cur, + (unsigned long)fd_limit.rlim_max); } } } @@ -469,3 +415,33 @@ void close_fd(void *arg) { close((long)arg); } + +void cleanup_free_ptr(void *arg) +{ + void **p = arg; + + if (p && *p) + free(*p); +} + +struct bitfield *alloc_bitfield(unsigned int maxbit) +{ + unsigned int n; + struct bitfield *bf; + + if (maxbit == 0) { + errno = EINVAL; + return NULL; + } + + n = (maxbit - 1) / bits_per_slot + 1; + bf = calloc(1, sizeof(struct bitfield) + n * sizeof(bitfield_t)); + if (bf) + bf->len = maxbit; + return bf; +} + +void _log_bitfield_overflow(const char *f, unsigned int bit, unsigned int len) +{ + condlog(0, "%s: bitfield overflow: %u >= %u", f, bit, len); +} diff --git a/libmultipath/util.h b/libmultipath/util.h index 56bd78c..2b9703a 100644 --- a/libmultipath/util.h +++ b/libmultipath/util.h @@ -1,6 +1,9 @@ #ifndef _UTIL_H #define _UTIL_H +#include +#include +#include #include /* for rlim_t */ #include @@ -9,19 +12,19 @@ size_t strchop(char *); int basenamecpy (const char *src, char *dst, size_t size); -int filepresent (char * run); -char *get_next_string(char **temp, char *split_char); -int get_word (char * sentence, char ** word); -size_t strlcpy(char *dst, const char *src, size_t size); -size_t strlcat(char *dst, const char *src, size_t size); -int devt2devname (char *, int, char *); +int filepresent (const char *run); +char *get_next_string(char **temp, const char *split_char); +int get_word (const char * sentence, char ** word); +size_t strlcpy(char * restrict dst, const char * restrict src, size_t size); +size_t strlcat(char * restrict dst, const char * restrict src, size_t size); +int devt2devname (char *, int, const char *); dev_t parse_devt(const char *dev_t); char *convert_dev(char *dev, int is_path_device); void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached); int systemd_service_enabled(const char *dev); int get_linux_version_code(void); -int parse_prkey(char *ptr, uint64_t *prkey); -int parse_prkey_flags(char *ptr, uint64_t *prkey, uint8_t *flags); +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); @@ -44,6 +47,7 @@ void set_max_fds(rlim_t max_fds); pthread_cleanup_push(((void (*)(void *))&f), (arg)) void close_fd(void *arg); +void cleanup_free_ptr(void *arg); struct scandir_result { struct dirent **di; @@ -51,19 +55,70 @@ struct scandir_result { }; void free_scandir_result(struct scandir_result *); -static inline bool is_bit_set_in_array(unsigned int bit, const uint64_t *arr) +#ifndef __GLIBC_PREREQ +#define __GLIBC_PREREQ(x, y) 0 +#endif +/* + * ffsll() is also available on glibc < 2.27 if _GNU_SOURCE is defined. + * But relying on that would require that every program using this header file + * set _GNU_SOURCE during compilation, because otherwise the library and the + * program would use different types for bitfield_t, causing errors. + * That's too error prone, so if in doubt, use ffs(). + */ +#if __GLIBC_PREREQ(2, 27) +typedef unsigned long long int bitfield_t; +#define _ffs(x) ffsll(x) +#else +typedef unsigned int bitfield_t; +#define _ffs(x) ffs(x) +#endif +#define bits_per_slot (sizeof(bitfield_t) * CHAR_BIT) + +struct bitfield { + unsigned int len; + bitfield_t bits[]; +}; + +struct bitfield *alloc_bitfield(unsigned int maxbit); + +void _log_bitfield_overflow(const char *f, unsigned int bit, unsigned int len); +#define log_bitfield_overflow(bit, len) \ + _log_bitfield_overflow(__func__, bit, len) + +static inline bool is_bit_set_in_bitfield(unsigned int bit, + const struct bitfield *bf) { - return arr[bit / 64] & (1ULL << (bit % 64)) ? 1 : 0; + if (bit >= bf->len) { + log_bitfield_overflow(bit, bf->len); + return false; + } + return !!(bf->bits[bit / bits_per_slot] & + (1ULL << (bit % bits_per_slot))); } -static inline void set_bit_in_array(unsigned int bit, uint64_t *arr) +static inline void set_bit_in_bitfield(unsigned int bit, struct bitfield *bf) { - arr[bit / 64] |= (1ULL << (bit % 64)); + if (bit >= bf->len) { + log_bitfield_overflow(bit, bf->len); + return; + } + bf->bits[bit / bits_per_slot] |= (1ULL << (bit % bits_per_slot)); } -static inline void clear_bit_in_array(unsigned int bit, uint64_t *arr) +static inline void clear_bit_in_bitfield(unsigned int bit, struct bitfield *bf) { - arr[bit / 64] &= ~(1ULL << (bit % 64)); + if (bit >= bf->len) { + log_bitfield_overflow(bit, bf->len); + return; + } + bf->bits[bit / bits_per_slot] &= ~(1ULL << (bit % bits_per_slot)); } +#define steal_ptr(x) \ + ({ \ + void *___p = x; \ + x = NULL; \ + ___p; \ + }) + #endif /* _UTIL_H */ diff --git a/libmultipath/valid.c b/libmultipath/valid.c new file mode 100644 index 0000000..456b1f6 --- /dev/null +++ b/libmultipath/valid.c @@ -0,0 +1,118 @@ +/* + Copyright (c) 2020 Benjamin Marzinski, IBM + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include +#include +#include + +#include "vector.h" +#include "config.h" +#include "debug.h" +#include "util.h" +#include "devmapper.h" +#include "discovery.h" +#include "wwids.h" +#include "sysfs.h" +#include "blacklist.h" +#include "mpath_cmd.h" +#include "valid.h" + +int +is_path_valid(const char *name, struct config *conf, struct path *pp, + bool check_multipathd) +{ + int r; + int fd; + + if (!pp || !name || !conf) + return PATH_IS_ERROR; + + if (conf->find_multipaths <= FIND_MULTIPATHS_UNDEF || + conf->find_multipaths >= __FIND_MULTIPATHS_LAST) + return PATH_IS_ERROR; + + if (safe_sprintf(pp->dev, "%s", name)) + return PATH_IS_ERROR; + + if (sysfs_is_multipathed(pp, true)) { + if (pp->wwid[0] == '\0') + return PATH_IS_ERROR; + return PATH_IS_VALID_NO_CHECK; + } + + /* + * "multipath -u" may be run before the daemon is started. In this + * case, systemd might own the socket but might delay multipathd + * startup until some other unit (udev settle!) has finished + * starting. With many LUNs, the listen backlog may be exceeded, which + * would cause connect() to block. This causes udev workers calling + * "multipath -u" to hang, and thus creates a deadlock, until "udev + * settle" times out. To avoid this, call connect() in non-blocking + * mode here, and take EAGAIN as indication for a filled-up systemd + * backlog. + */ + + if (check_multipathd) { + fd = __mpath_connect(1); + if (fd < 0) { + if (errno != EAGAIN && !systemd_service_enabled(name)) { + condlog(3, "multipathd not running or enabled"); + return PATH_IS_NOT_VALID; + } + } else + mpath_disconnect(fd); + } + + pp->udev = udev_device_new_from_subsystem_sysname(udev, "block", name); + if (!pp->udev) + return PATH_IS_ERROR; + + r = pathinfo(pp, conf, DI_SYSFS | DI_WWID | DI_BLACKLIST); + if (r == PATHINFO_SKIPPED) + return PATH_IS_NOT_VALID; + else if (r) + return PATH_IS_ERROR; + + if (pp->wwid[0] == '\0') + return PATH_IS_NOT_VALID; + + 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) + return PATH_IS_NOT_VALID; + return PATH_IS_ERROR; + } + + if (conf->find_multipaths == FIND_MULTIPATHS_GREEDY) + return PATH_IS_VALID; + + if (check_wwids_file(pp->wwid, 0) == 0) + return PATH_IS_VALID_NO_CHECK; + + if (dm_map_present_by_uuid(pp->wwid) == 1) + return PATH_IS_VALID; + + /* all these act like FIND_MULTIPATHS_STRICT for finding if a + * path is valid */ + if (conf->find_multipaths != FIND_MULTIPATHS_SMART) + return PATH_IS_NOT_VALID; + + return PATH_IS_MAYBE_VALID; +} diff --git a/libmultipath/valid.h b/libmultipath/valid.h new file mode 100644 index 0000000..ce1c7cb --- /dev/null +++ b/libmultipath/valid.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2020 Benjamin Marzinski, IBM + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#ifndef _VALID_H +#define _VALID_H + +/* + * PATH_IS_VALID_NO_CHECK is returned when multipath should claim + * the path, regardless of whether is has been released to systemd + * already. + * PATH_IS_VALID is returned by is_path_valid, when the path is + * valid only if it hasn't been released to systemd already. + * PATH_IS_MAYBE_VALID is returned when the the path would be valid + * if other paths with the same wwid existed. It is up to the caller + * to check for these other paths. + */ +enum is_path_valid_result { + PATH_IS_ERROR = -1, + PATH_IS_NOT_VALID, + PATH_IS_VALID, + PATH_IS_VALID_NO_CHECK, + PATH_IS_MAYBE_VALID, + PATH_MAX_VALID_RESULT, /* only for bounds checking */ +}; + +int is_path_valid(const char *name, struct config *conf, struct path *pp, + bool check_multipathd); + +#endif /* _VALID_D */ diff --git a/libmultipath/vector.c b/libmultipath/vector.c index 501cf4c..6605eb2 100644 --- a/libmultipath/vector.c +++ b/libmultipath/vector.c @@ -35,26 +35,27 @@ vector_alloc(void) } /* allocated one slot */ -void * +bool vector_alloc_slot(vector v) { void *new_slot = NULL; + int new_allocated; + int i; if (!v) - return NULL; - - v->allocated += VECTOR_DEFAULT_SIZE; - if (v->slot) - new_slot = REALLOC(v->slot, sizeof (void *) * v->allocated); - else - new_slot = (void *) MALLOC(sizeof (void *) * v->allocated); + return false; + new_allocated = v->allocated + VECTOR_DEFAULT_SIZE; + new_slot = REALLOC(v->slot, sizeof (void *) * new_allocated); if (!new_slot) - v->allocated -= VECTOR_DEFAULT_SIZE; - else - v->slot = new_slot; + return false; - return v->slot; + v->slot = new_slot; + for (i = v->allocated; i < new_allocated; i++) + v->slot[i] = NULL; + + v->allocated = new_allocated; + return true; } int @@ -109,7 +110,7 @@ vector_del_slot(vector v, int slot) { int i; - if (!v || !v->allocated || slot < 0 || slot > VECTOR_SIZE(v)) + if (!v || !v->allocated || slot < 0 || slot >= VECTOR_SIZE(v)) return; for (i = slot + 1; i < VECTOR_SIZE(v); i++) @@ -203,7 +204,7 @@ int vector_find_or_add_slot(vector v, void *value) if (n >= 0) return n; - if (vector_alloc_slot(v) == NULL) + if (!vector_alloc_slot(v)) return -1; vector_set_slot(v, value); return VECTOR_SIZE(v) - 1; diff --git a/libmultipath/vector.h b/libmultipath/vector.h index e16ec46..2862dc2 100644 --- a/libmultipath/vector.h +++ b/libmultipath/vector.h @@ -23,6 +23,8 @@ #ifndef _VECTOR_H #define _VECTOR_H +#include + /* vector definition */ struct _vector { int allocated; @@ -32,7 +34,7 @@ typedef struct _vector *vector; #define VECTOR_DEFAULT_SIZE 1 #define VECTOR_SIZE(V) ((V) ? ((V)->allocated) / VECTOR_DEFAULT_SIZE : 0) -#define VECTOR_SLOT(V,E) (((V) && (E) < VECTOR_SIZE(V)) ? (V)->slot[(E)] : NULL) +#define VECTOR_SLOT(V,E) (((V) && (E) < VECTOR_SIZE(V) && (E) >= 0) ? (V)->slot[(E)] : NULL) #define VECTOR_LAST_SLOT(V) (((V) && VECTOR_SIZE(V) > 0) ? (V)->slot[(VECTOR_SIZE(V) - 1)] : NULL) #define vector_foreach_slot(v,p,i) \ @@ -60,7 +62,7 @@ typedef struct _vector *vector; __t = vector_alloc(); \ if (__t != NULL) { \ vector_foreach_slot(__v, __j, __i) { \ - if (vector_alloc_slot(__t) == NULL) { \ + if (!vector_alloc_slot(__t)) { \ vector_free(__t); \ __t = NULL; \ break; \ @@ -73,7 +75,7 @@ typedef struct _vector *vector; /* Prototypes */ extern vector vector_alloc(void); -extern void *vector_alloc_slot(vector v); +extern bool vector_alloc_slot(vector v); vector vector_reset(vector v); extern void vector_free(vector v); #define vector_free_const(x) vector_free((vector)(long)(x)) diff --git a/libmultipath/version.h b/libmultipath/version.h index 7ddb4e8..6ceed53 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,8 +20,8 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION_CODE 0x000804 -#define DATE_CODE 0x050414 +#define VERSION_CODE 0x000805 +#define DATE_CODE 0x0b0914 #define PROG "multipath-tools" diff --git a/libmultipath/wwids.c b/libmultipath/wwids.c index 28a2150..61d9c39 100644 --- a/libmultipath/wwids.c +++ b/libmultipath/wwids.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "util.h" #include "checkers.h" @@ -289,19 +290,19 @@ out: int should_multipath(struct path *pp1, vector pathvec, vector mpvec) { - int i, ignore_new_devs, find_multipaths; + int i, find_multipaths; struct path *pp2; struct config *conf; conf = get_multipath_config(); - ignore_new_devs = ignore_new_devs_on(conf); - find_multipaths = find_multipaths_on(conf); + find_multipaths = conf->find_multipaths; put_multipath_config(conf); - if (!find_multipaths && !ignore_new_devs) + if (find_multipaths == FIND_MULTIPATHS_OFF || + find_multipaths == FIND_MULTIPATHS_GREEDY) return 1; condlog(4, "checking if %s should be multipathed", pp1->dev); - if (!ignore_new_devs) { + if (find_multipaths != FIND_MULTIPATHS_STRICT) { char tmp_wwid[WWID_SIZE]; struct multipath *mp = find_mp_by_wwid(mpvec, pp1->wwid); @@ -348,109 +349,105 @@ remember_wwid(char *wwid) } static const char shm_dir[] = MULTIPATH_SHM_BASE "failed_wwids"; -static const char shm_lock[] = ".lock"; -static const char shm_header[] = "multipath shm lock file, don't edit"; -static char _shm_lock_path[sizeof(shm_dir)+sizeof(shm_lock)]; -static const char *shm_lock_path = &_shm_lock_path[0]; -static void init_shm_paths(void) +static void print_failed_wwid_result(const char * msg, const char *wwid, int r) { - snprintf(_shm_lock_path, sizeof(_shm_lock_path), - "%s/%s", shm_dir, shm_lock); + switch(r) { + case WWID_FAILED_ERROR: + condlog(1, "%s: %s: %m", msg, wwid); + return; + case WWID_IS_FAILED: + case WWID_IS_NOT_FAILED: + condlog(4, "%s: %s is %s", msg, wwid, + r == WWID_IS_FAILED ? "failed" : "good"); + return; + case WWID_FAILED_CHANGED: + condlog(3, "%s: %s", msg, wwid); + } } -static pthread_once_t shm_path_once = PTHREAD_ONCE_INIT; - -static int multipath_shm_open(bool rw) +int is_failed_wwid(const char *wwid) { - int fd; - int can_write; - - pthread_once(&shm_path_once, init_shm_paths); - fd = open_file(shm_lock_path, &can_write, shm_header); + struct stat st; + char path[PATH_MAX]; + int r; - if (fd >= 0 && rw && !can_write) { - close(fd); - condlog(1, "failed to open %s for writing", shm_dir); - return -1; + if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { + condlog(1, "%s: path name overflow", __func__); + return WWID_FAILED_ERROR; } - return fd; -} - -static void multipath_shm_close(void *arg) -{ - long fd = (long)arg; + if (lstat(path, &st) == 0) + r = WWID_IS_FAILED; + else if (errno == ENOENT) + r = WWID_IS_NOT_FAILED; + else + r = WWID_FAILED_ERROR; - close(fd); - unlink(shm_lock_path); + print_failed_wwid_result("is_failed", wwid, r); + return r; } -static int _failed_wwid_op(const char *wwid, bool rw, - int (*func)(const char *), const char *msg) +int mark_failed_wwid(const char *wwid) { - char path[PATH_MAX]; - long lockfd; - int r = -1; + char tmpfile[WWID_SIZE + 2 * sizeof(long) + 1]; + int r = WWID_FAILED_ERROR, fd, dfd; - if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { - condlog(1, "%s: path name overflow", __func__); - return -1; + dfd = open(shm_dir, O_RDONLY|O_DIRECTORY); + if (dfd == -1 && errno == ENOENT) { + char path[sizeof(shm_dir) + 2]; + + /* arg for ensure_directories_exist() must not end with "/" */ + safe_sprintf(path, "%s/_", shm_dir); + ensure_directories_exist(path, 0700); + dfd = open(shm_dir, O_RDONLY|O_DIRECTORY); + } + if (dfd == -1) { + condlog(1, "%s: can't setup %s: %m", __func__, shm_dir); + return WWID_FAILED_ERROR; } - lockfd = multipath_shm_open(rw); - if (lockfd == -1) - return -1; + safe_sprintf(tmpfile, "%s.%lx", wwid, (long)getpid()); + fd = openat(dfd, tmpfile, O_RDONLY | O_CREAT | O_EXCL, S_IRUSR); + if (fd >= 0) + close(fd); + else + goto out_closedir; - pthread_cleanup_push(multipath_shm_close, (void *)lockfd); - r = func(path); - pthread_cleanup_pop(1); + if (linkat(dfd, tmpfile, dfd, wwid, 0) == 0) + r = WWID_FAILED_CHANGED; + else if (errno == EEXIST) + r = WWID_FAILED_UNCHANGED; + else + r = WWID_FAILED_ERROR; - if (r == WWID_FAILED_ERROR) - condlog(1, "%s: %s: %s", msg, wwid, strerror(errno)); - else if (r == WWID_FAILED_CHANGED) - condlog(3, "%s: %s", msg, wwid); - else if (!rw) - condlog(4, "%s: %s is %s", msg, wwid, - r == WWID_IS_FAILED ? "failed" : "good"); + if (unlinkat(dfd, tmpfile, 0) == -1) + condlog(2, "%s: failed to unlink %s/%s: %m", + __func__, shm_dir, tmpfile); +out_closedir: + close(dfd); + print_failed_wwid_result("mark_failed", wwid, r); return r; } -static int _is_failed(const char *path) +int unmark_failed_wwid(const char *wwid) { - struct stat st; + char path[PATH_MAX]; + int r; - if (lstat(path, &st) == 0) - return WWID_IS_FAILED; - else if (errno == ENOENT) - return WWID_IS_NOT_FAILED; - else + if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { + condlog(1, "%s: path name overflow", __func__); return WWID_FAILED_ERROR; -} - -static int _mark_failed(const char *path) -{ - /* Called from _failed_wwid_op: we know that shm_lock_path exists */ - if (_is_failed(path) == WWID_IS_FAILED) - return WWID_FAILED_UNCHANGED; - return (link(shm_lock_path, path) == 0 ? WWID_FAILED_CHANGED : - WWID_FAILED_ERROR); -} + } -static int _unmark_failed(const char *path) -{ - if (_is_failed(path) == WWID_IS_NOT_FAILED) - return WWID_FAILED_UNCHANGED; - return (unlink(path) == 0 ? WWID_FAILED_CHANGED : WWID_FAILED_ERROR); -} + if (unlink(path) == 0) + r = WWID_FAILED_CHANGED; + else if (errno == ENOENT) + r = WWID_FAILED_UNCHANGED; + else + r = WWID_FAILED_ERROR; -#define declare_failed_wwid_op(op, rw) \ -int op ## _wwid(const char *wwid) \ -{ \ - return _failed_wwid_op(wwid, (rw), _ ## op, #op); \ + print_failed_wwid_result("unmark_failed", wwid, r); + return r; } - -declare_failed_wwid_op(is_failed, false) -declare_failed_wwid_op(mark_failed, true) -declare_failed_wwid_op(unmark_failed, true) diff --git a/mpathpersist/main.c b/mpathpersist/main.c index 28bfe41..a6a3bcf 100644 --- a/mpathpersist/main.c +++ b/mpathpersist/main.c @@ -153,6 +153,37 @@ static int do_batch_file(const char *batch_fn) return ret; } +static struct prout_param_descriptor * +alloc_prout_param_descriptor(int num_transportid) +{ + struct prout_param_descriptor *paramp; + + if (num_transportid < 0 || num_transportid > MPATH_MX_TIDS) + return NULL; + + paramp= malloc(sizeof(struct prout_param_descriptor) + + (sizeof(struct transportid *) * num_transportid)); + + if (!paramp) + return NULL; + + memset(paramp, 0, sizeof(struct prout_param_descriptor) + + (sizeof(struct transportid *) * num_transportid)); + return paramp; +} + +static void free_prout_param_descriptor(struct prout_param_descriptor *paramp) +{ + unsigned int i; + if (!paramp) + return; + + for (i = 0; i < paramp->num_transportid; i++) + free(paramp->trnptid_list[i]); + + free(paramp); +} + static int handle_args(int argc, char * argv[], int nline) { int c; @@ -177,10 +208,8 @@ static int handle_args(int argc, char * argv[], int nline) int prin = 1; int prin_sa = -1; int prout_sa = -1; - int num_transport =0; char *batch_fn = NULL; void *resp = NULL; - struct transportid * tmp; memset(transportids, 0, MPATH_MX_TIDS * sizeof(struct transportid)); @@ -334,13 +363,13 @@ static int handle_args(int argc, char * argv[], int nline) break; case 'X': - if (0 != construct_transportid(optarg, transportids, num_transport)) { + if (0 != construct_transportid(optarg, transportids, num_transportids)) { fprintf(stderr, "bad argument to '--transport-id'\n"); ret = MPATH_PR_SYNTAX_ERROR; goto out; } - ++num_transport; + ++num_transportids; break; case 'l': @@ -525,9 +554,12 @@ static int handle_args(int argc, char * argv[], int nline) int j; struct prout_param_descriptor *paramp; - paramp= malloc(sizeof(struct prout_param_descriptor) + (sizeof(struct transportid *)*(MPATH_MX_TIDS ))); - - memset(paramp, 0, sizeof(struct prout_param_descriptor) + (sizeof(struct transportid *)*(MPATH_MX_TIDS))); + paramp = alloc_prout_param_descriptor(num_transportids); + if (!paramp) { + fprintf(stderr, "malloc paramp failed\n"); + ret = MPATH_PR_OTHER; + goto out_fd; + } for (j = 7; j >= 0; --j) { paramp->key[j] = (param_rk & 0xff); @@ -544,13 +576,19 @@ static int handle_args(int argc, char * argv[], int nline) if (param_aptpl) paramp->sa_flags |= MPATH_F_APTPL_MASK; - if (num_transport) + if (num_transportids) { paramp->sa_flags |= MPATH_F_SPEC_I_PT_MASK; - paramp->num_transportid = num_transport; - for (j = 0 ; j < num_transport; j++) + paramp->num_transportid = num_transportids; + for (j = 0 ; j < num_transportids; j++) { paramp->trnptid_list[j] = (struct transportid *)malloc(sizeof(struct transportid)); + if (!paramp->trnptid_list[j]) { + fprintf(stderr, "malloc paramp->trnptid_list[%d] failed.\n", j); + ret = MPATH_PR_OTHER; + free_prout_param_descriptor(paramp); + goto out_fd; + } memcpy(paramp->trnptid_list[j], &transportids[j],sizeof(struct transportid)); } } @@ -558,12 +596,7 @@ static int handle_args(int argc, char * argv[], int nline) /* PROUT commands other than 'register and move' */ ret = __mpath_persistent_reserve_out (fd, prout_sa, 0, prout_type, paramp, noisy); - for (j = 0 ; j < num_transport; j++) - { - tmp = paramp->trnptid_list[j]; - free(tmp); - } - free(paramp); + free_prout_param_descriptor(paramp); } if (ret != MPATH_PR_SUCCESS) diff --git a/multipath/11-dm-mpath.rules b/multipath/11-dm-mpath.rules index 07320a1..cd522e8 100644 --- a/multipath/11-dm-mpath.rules +++ b/multipath/11-dm-mpath.rules @@ -75,7 +75,7 @@ ENV{MPATH_DEVICE_READY}=="0", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" ENV{MPATH_DEVICE_READY}!="0", ENV{.MPATH_DEVICE_READY_OLD}=="0",\ ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{DM_DISABLE_OTHER_RULES_FLAG_OLD}",\ ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="",\ - ENV{DM_ACTIVATION}="1" + ENV{DM_ACTIVATION}="1", ENV{MPATH_UNCHANGED}="0" # The code to check multipath state ends here. We need to set # properties and symlinks regardless whether the map is usable or diff --git a/multipath/main.c b/multipath/main.c index cf9d2a2..9e920d8 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -63,21 +63,19 @@ #include "propsel.h" #include "time-util.h" #include "file.h" +#include "valid.h" +#include "alias.h" int logsink; struct udev *udev; struct config *multipath_conf; /* - * Return values of configure(), print_cmd_valid(), and main(). - * RTVL_{YES,NO} are synonyms for RTVL_{OK,FAIL} for the CMD_VALID_PATH case. + * Return values of configure(), check_path_valid(), and main(). */ enum { RTVL_OK = 0, - RTVL_YES = RTVL_OK, RTVL_FAIL = 1, - RTVL_NO = RTVL_FAIL, - RTVL_MAYBE, /* only used internally, never returned */ RTVL_RETRY, /* returned by configure(), not by main() */ }; @@ -148,6 +146,7 @@ usage (char * progname) " -h print this usage text\n" " -l show multipath topology (sysfs and DM info)\n" " -ll show multipath topology (maximum info)\n" + " -e enable foreign libraries with -l/-ll\n" " -f flush a multipath device map\n" " -F flush all multipath device maps\n" " -a add a device wwid to the wwids file\n" @@ -189,69 +188,12 @@ usage (char * progname) } -static int -update_paths (struct multipath * mpp, int quick) -{ - int i, j; - struct pathgroup * pgp; - struct path * pp; - struct config *conf; - - if (!mpp->pg) - return 0; - - vector_foreach_slot (mpp->pg, pgp, i) { - if (!pgp->paths) - continue; - - vector_foreach_slot (pgp->paths, pp, j) { - if (!strlen(pp->dev)) { - if (devt2devname(pp->dev, FILE_NAME_SIZE, - pp->dev_t)) { - /* - * path is not in sysfs anymore - */ - pp->chkrstate = pp->state = PATH_DOWN; - pp->offline = 1; - continue; - } - pp->mpp = mpp; - if (quick) - continue; - conf = get_multipath_config(); - if (pathinfo(pp, conf, DI_ALL)) - pp->state = PATH_UNCHECKED; - put_multipath_config(conf); - continue; - } - pp->mpp = mpp; - if (quick) - continue; - if (pp->state == PATH_UNCHECKED || - pp->state == PATH_WILD) { - conf = get_multipath_config(); - if (pathinfo(pp, conf, DI_CHECKER)) - pp->state = PATH_UNCHECKED; - put_multipath_config(conf); - } - - if (pp->priority == PRIO_UNDEF) { - conf = get_multipath_config(); - if (pathinfo(pp, conf, DI_PRIO)) - pp->priority = PRIO_UNDEF; - put_multipath_config(conf); - } - } - } - return 0; -} - static int get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid) { int i; struct multipath * mpp; - char params[PARAMS_SIZE], status[PARAMS_SIZE]; + int flags = (cmd == CMD_LIST_SHORT ? DI_NOIO : DI_ALL); if (dm_get_maps(curmp)) return 1; @@ -263,34 +205,22 @@ get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid) if (refwwid && strlen(refwwid) && strncmp(mpp->wwid, refwwid, WWID_SIZE)) { condlog(3, "skip map %s: out of scope", mpp->alias); - free_multipath(mpp, KEEP_PATHS); - vector_del_slot(curmp, i); + remove_map(mpp, pathvec, curmp, PURGE_VEC); i--; continue; } - if (cmd == CMD_VALID_PATH) + if (update_multipath_table(mpp, pathvec, flags) != DMP_OK || + update_multipath_status(mpp) != DMP_OK) { + condlog(1, "error parsing map %s", mpp->wwid); + remove_map(mpp, pathvec, curmp, PURGE_VEC); + i--; continue; - - dm_get_map(mpp->alias, &mpp->size, params); - condlog(3, "params = %s", params); - dm_get_status(mpp->alias, status); - condlog(3, "status = %s", status); - - disassemble_map(pathvec, params, mpp, 0); - - /* - * disassemble_map() can add new paths to pathvec. - * If not in "fast list mode", we need to fetch information - * about them - */ - update_paths(mpp, (cmd == CMD_LIST_SHORT)); + } if (cmd == CMD_LIST_LONG) mpp->bestpg = select_path_group(mpp); - disassemble_status(status, mpp); - if (cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) { struct config *conf = get_multipath_config(); @@ -321,7 +251,6 @@ static int check_usable_paths(struct config *conf, struct path *pp; char *mapname; vector pathvec = NULL; - char params[PARAMS_SIZE], status[PARAMS_SIZE]; dev_t devt; int r = 1, i, j; @@ -355,10 +284,9 @@ static int check_usable_paths(struct config *conf, if (mpp == NULL) goto free; - dm_get_map(mpp->alias, &mpp->size, params); - dm_get_status(mpp->alias, status); - disassemble_map(pathvec, params, mpp, 0); - disassemble_status(status, mpp); + if (update_multipath_table(mpp, pathvec, 0) != DMP_OK || + update_multipath_status(mpp) != DMP_OK) + goto free; vector_foreach_slot (mpp->pg, pg, i) { vector_foreach_slot (pg->paths, pp, j) { @@ -491,10 +419,11 @@ static int print_cmd_valid(int k, const vector pathvec, struct timespec until; struct path *pp; - if (k != RTVL_YES && k != RTVL_NO && k != RTVL_MAYBE) - return RTVL_NO; + if (k != PATH_IS_VALID && k != PATH_IS_NOT_VALID && + k != PATH_IS_MAYBE_VALID) + return PATH_IS_NOT_VALID; - if (k == RTVL_MAYBE) { + if (k == PATH_IS_MAYBE_VALID) { /* * Caller ensures that pathvec[0] is the path to * examine. @@ -504,7 +433,7 @@ static int print_cmd_valid(int k, const vector pathvec, wait = find_multipaths_check_timeout( pp, pp->find_multipaths_timeout, &until); if (wait != FIND_MULTIPATHS_WAITING) - k = RTVL_NO; + k = PATH_IS_NOT_VALID; } else if (pathvec != NULL && (pp = VECTOR_SLOT(pathvec, 0))) wait = find_multipaths_check_timeout(pp, 0, &until); if (wait == FIND_MULTIPATHS_WAITING) @@ -513,9 +442,9 @@ static int print_cmd_valid(int k, const vector pathvec, else if (wait == FIND_MULTIPATHS_WAIT_DONE) printf("FIND_MULTIPATHS_WAIT_UNTIL=\"0\"\n"); printf("DM_MULTIPATH_DEVICE_PATH=\"%d\"\n", - k == RTVL_MAYBE ? 2 : k == RTVL_YES ? 1 : 0); + k == PATH_IS_MAYBE_VALID ? 2 : k == PATH_IS_VALID ? 1 : 0); /* Never return RTVL_MAYBE */ - return k == RTVL_NO ? RTVL_NO : RTVL_YES; + return k == PATH_IS_NOT_VALID ? PATH_IS_NOT_VALID : PATH_IS_VALID; } /* @@ -548,7 +477,6 @@ configure (struct config *conf, enum mpath_cmds cmd, int di_flag = 0; char * refwwid = NULL; char * dev = NULL; - bool released = released_to_systemd(); /* * allocate core vectors to store paths and multipaths @@ -573,7 +501,7 @@ configure (struct config *conf, enum mpath_cmds cmd, cmd != CMD_REMOVE_WWID && (filter_devnode(conf->blist_devnode, conf->elist_devnode, dev) > 0)) { - goto print_valid; + goto out; } /* @@ -581,14 +509,10 @@ configure (struct config *conf, enum mpath_cmds cmd, * failing the translation is fatal (by policy) */ if (devpath) { - int failed = get_refwwid(cmd, devpath, dev_type, - pathvec, &refwwid); + get_refwwid(cmd, devpath, dev_type, pathvec, &refwwid); if (!refwwid) { condlog(4, "%s: failed to get wwid", devpath); - if (failed == 2 && cmd == CMD_VALID_PATH) - goto print_valid; - else - condlog(3, "scope is null"); + condlog(3, "scope is null"); goto out; } if (cmd == CMD_REMOVE_WWID) { @@ -614,52 +538,6 @@ configure (struct config *conf, enum mpath_cmds cmd, goto out; } condlog(3, "scope limited to %s", refwwid); - /* If you are ignoring the wwids file and find_multipaths is - * set, you need to actually check if there are two available - * paths to determine if this path should be multipathed. To - * do this, we put off the check until after discovering all - * the paths. - * Paths listed in the wwids file are always considered valid. - */ - if (cmd == CMD_VALID_PATH) { - if (is_failed_wwid(refwwid) == WWID_IS_FAILED) { - r = RTVL_NO; - goto print_valid; - } - if ((!find_multipaths_on(conf) && - ignore_wwids_on(conf)) || - check_wwids_file(refwwid, 0) == 0) - r = RTVL_YES; - if (!ignore_wwids_on(conf)) - goto print_valid; - /* At this point, either r==0 or find_multipaths_on. */ - - /* - * Shortcut for find_multipaths smart: - * Quick check if path is already multipathed. - */ - if (sysfs_is_multipathed(VECTOR_SLOT(pathvec, 0))) { - r = RTVL_YES; - goto print_valid; - } - - /* - * DM_MULTIPATH_DEVICE_PATH=="0" means that we have - * been called for this device already, and have - * released it to systemd. Unless the device is now - * already multipathed (see above), we can't try to - * grab it, because setting SYSTEMD_READY=0 would - * cause file systems to be unmounted. - * Leave DM_MULTIPATH_DEVICE_PATH="0". - */ - if (released) { - r = RTVL_NO; - goto print_valid; - } - if (r == RTVL_YES) - goto print_valid; - /* find_multipaths_on: Fall through to path detection */ - } } /* @@ -700,59 +578,6 @@ configure (struct config *conf, enum mpath_cmds cmd, goto out; } - if (cmd == CMD_VALID_PATH) { - struct path *pp; - int fd; - - /* This only happens if find_multipaths and - * ignore_wwids is set, and the path is not in WWIDs - * file, not currently multipathed, and has - * never been released to systemd. - * If there is currently a multipath device matching - * the refwwid, or there is more than one path matching - * the refwwid, then the path is valid */ - if (VECTOR_SIZE(curmp) != 0) { - r = RTVL_YES; - goto print_valid; - } else if (VECTOR_SIZE(pathvec) > 1) - r = RTVL_YES; - else - r = RTVL_MAYBE; - - /* - * If opening the path with O_EXCL fails, the path - * is in use (e.g. mounted during initramfs processing). - * We know that it's not used by dm-multipath. - * We may not set SYSTEMD_READY=0 on such devices, it - * might cause systemd to umount the device. - * Use O_RDONLY, because udevd would trigger another - * uevent for close-after-write. - * - * The O_EXCL check is potentially dangerous, because it may - * race with other tasks trying to access the device. Therefore - * this code is only executed if the path hasn't been released - * to systemd earlier (see above). - * - * get_refwwid() above stores the path we examine in slot 0. - */ - pp = VECTOR_SLOT(pathvec, 0); - fd = open(udev_device_get_devnode(pp->udev), - O_RDONLY|O_EXCL); - if (fd >= 0) - close(fd); - else { - condlog(3, "%s: path %s is in use: %s", - __func__, pp->dev, - strerror(errno)); - /* - * Check if we raced with multipathd - */ - r = sysfs_is_multipathed(VECTOR_SLOT(pathvec, 0)) ? - RTVL_YES : RTVL_NO; - } - goto print_valid; - } - if (cmd != CMD_CREATE && cmd != CMD_DRY_RUN) { r = RTVL_OK; goto out; @@ -765,10 +590,6 @@ configure (struct config *conf, enum mpath_cmds cmd, conf->force_reload, cmd); r = rc == CP_RETRY ? RTVL_RETRY : rc == CP_OK ? RTVL_OK : RTVL_FAIL; -print_valid: - if (cmd == CMD_VALID_PATH) - r = print_cmd_valid(r, pathvec, conf); - out: if (refwwid) FREE(refwwid); @@ -779,6 +600,112 @@ out: return r; } +static int +check_path_valid(const char *name, struct config *conf, bool is_uevent) +{ + int fd, r = PATH_IS_ERROR; + struct path *pp = NULL; + vector pathvec = NULL; + + pp = alloc_path(); + if (!pp) + return RTVL_FAIL; + + r = is_path_valid(name, conf, pp, is_uevent); + if (r <= PATH_IS_ERROR || r >= PATH_MAX_VALID_RESULT) + goto fail; + + /* set path values if is_path_valid() didn't */ + if (!pp->udev) + pp->udev = udev_device_new_from_subsystem_sysname(udev, "block", + name); + if (!pp->udev) + goto fail; + + if (!strlen(pp->dev_t)) { + dev_t devt = udev_device_get_devnum(pp->udev); + if (major(devt) == 0 && minor(devt) == 0) + goto fail; + snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), + minor(devt)); + } + + if ((r == PATH_IS_VALID || r == PATH_IS_MAYBE_VALID) && + released_to_systemd()) + r = PATH_IS_NOT_VALID; + + /* This state is only used to skip the released_to_systemd() check */ + if (r == PATH_IS_VALID_NO_CHECK) + r = PATH_IS_VALID; + + if (r != PATH_IS_MAYBE_VALID) + goto out; + + /* + * If opening the path with O_EXCL fails, the path + * is in use (e.g. mounted during initramfs processing). + * We know that it's not used by dm-multipath. + * We may not set SYSTEMD_READY=0 on such devices, it + * might cause systemd to umount the device. + * Use O_RDONLY, because udevd would trigger another + * uevent for close-after-write. + * + * The O_EXCL check is potentially dangerous, because it may + * race with other tasks trying to access the device. Therefore + * this code is only executed if the path hasn't been released + * to systemd earlier (see above). + */ + fd = open(udev_device_get_devnode(pp->udev), O_RDONLY|O_EXCL); + if (fd >= 0) + close(fd); + else { + condlog(3, "%s: path %s is in use: %m", __func__, pp->dev); + /* Check if we raced with multipathd */ + if (sysfs_is_multipathed(pp, false)) + r = PATH_IS_VALID; + else + r = PATH_IS_NOT_VALID; + goto out; + } + + pathvec = vector_alloc(); + if (!pathvec) + goto fail; + + if (store_path(pathvec, pp) != 0) { + free_path(pp); + goto fail; + } + + /* 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); + if (VECTOR_SIZE(pathvec) > 1) + r = PATH_IS_VALID; + else + r = PATH_IS_MAYBE_VALID; + +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; + +fail: + if (pathvec) + free_pathvec(pathvec, FREE_PATHS); + else + free_path(pp); + return RTVL_FAIL; +} + static int get_dev_type(char *dev) { struct stat buf; @@ -823,9 +750,26 @@ int delegate_to_multipathd(enum mpath_cmds cmd, *p = '\0'; n = sizeof(command); + if (conf->skip_delegate) + return NOT_DELEGATED; + if (cmd == CMD_CREATE && conf->force_reload == FORCE_RELOAD_YES) { p += snprintf(p, n, "reconfigure"); } + else if (cmd == CMD_FLUSH_ONE && dev && dev_type == DEV_DEVMAP) { + p += snprintf(p, n, "del map %s", dev); + /* multipathd doesn't try as hard, to avoid potentially + * hanging. If it fails, retry with the regular multipath + * command */ + r = NOT_DELEGATED; + } + else if (cmd == CMD_FLUSH_ALL) { + p += snprintf(p, n, "del maps"); + /* multipathd doesn't try as hard, to avoid potentially + * hanging. If it fails, retry with the regular multipath + * command */ + r = NOT_DELEGATED; + } /* Add other translations here */ if (strlen(command) == 0) @@ -850,9 +794,12 @@ int delegate_to_multipathd(enum mpath_cmds cmd, goto out; } - if (reply != NULL && *reply != '\0' && strcmp(reply, "ok\n")) - printf("%s", reply); - r = DELEGATE_OK; + if (reply != NULL && *reply != '\0') { + if (strcmp(reply, "fail\n")) + r = DELEGATE_OK; + if (r != NOT_DELEGATED && strcmp(reply, "ok\n")) + printf("%s", reply); + } out: FREE(reply); @@ -860,32 +807,6 @@ out: return r; } -static int test_multipathd_socket(void) -{ - int fd; - /* - * "multipath -u" may be run before the daemon is started. In this - * case, systemd might own the socket but might delay multipathd - * startup until some other unit (udev settle!) has finished - * starting. With many LUNs, the listen backlog may be exceeded, which - * would cause connect() to block. This causes udev workers calling - * "multipath -u" to hang, and thus creates a deadlock, until "udev - * settle" times out. To avoid this, call connect() in non-blocking - * mode here, and take EAGAIN as indication for a filled-up systemd - * backlog. - */ - - fd = __mpath_connect(1); - if (fd == -1) { - if (errno == EAGAIN) - condlog(3, "daemon backlog exceeded"); - else - return 0; - } else - close(fd); - return 1; -} - int main (int argc, char *argv[]) { @@ -898,6 +819,7 @@ main (int argc, char *argv[]) char *dev = NULL; struct config *conf; int retries = -1; + bool enable_foreign = false; udev = udev_new(); logsink = 0; @@ -907,7 +829,7 @@ main (int argc, char *argv[]) multipath_conf = conf; conf->retrigger_tries = 0; conf->force_sync = 1; - while ((arg = getopt(argc, argv, ":adcChl::FfM:v:p:b:BrR:itTquUwW")) != EOF ) { + while ((arg = getopt(argc, argv, ":adDcChl::eFfM:v:p:b:BrR:itTquUwW")) != EOF ) { switch(arg) { case 1: printf("optarg : %s\n",optarg); break; @@ -939,11 +861,14 @@ main (int argc, char *argv[]) if (cmd == CMD_CREATE) cmd = CMD_DRY_RUN; break; + case 'D': + conf->skip_delegate = 1; + break; case 'f': - conf->remove = FLUSH_ONE; + cmd = CMD_FLUSH_ONE; break; case 'F': - conf->remove = FLUSH_ALL; + cmd = CMD_FLUSH_ALL; break; case 'l': if (optarg && !strncmp(optarg, "l", 1)) @@ -969,7 +894,11 @@ main (int argc, char *argv[]) conf->force_reload = FORCE_RELOAD_YES; break; case 'i': - conf->find_multipaths |= _FIND_MULTIPATHS_I; + if (conf->find_multipaths == FIND_MULTIPATHS_ON || + conf->find_multipaths == FIND_MULTIPATHS_STRICT) + conf->find_multipaths = FIND_MULTIPATHS_SMART; + else if (conf->find_multipaths == FIND_MULTIPATHS_OFF) + conf->find_multipaths = FIND_MULTIPATHS_GREEDY; break; case 't': r = dump_config(conf, NULL, NULL) ? RTVL_FAIL : RTVL_OK; @@ -1000,6 +929,9 @@ main (int argc, char *argv[]) case 'R': retries = atoi(optarg); break; + case 'e': + enable_foreign = true; + break; case ':': fprintf(stderr, "Missing option argument\n"); usage(argv[0]); @@ -1019,6 +951,8 @@ main (int argc, char *argv[]) exit(RTVL_FAIL); } + check_alias_settings(conf); + if (optind < argc) { dev = MALLOC(FILE_NAME_SIZE); @@ -1032,6 +966,8 @@ main (int argc, char *argv[]) condlog(0, "'%s' is not a valid argument\n", dev); goto out; } + if (dev_type == DEV_DEVNODE || dev_type == DEV_DEVT) + strchop(dev); } if (dev_type == DEV_UEVENT) { openlog("multipath", 0, LOG_DAEMON); @@ -1051,6 +987,10 @@ main (int argc, char *argv[]) condlog(0, "failed to initialize prioritizers"); goto out; } + + if ((cmd == CMD_LIST_SHORT || cmd == CMD_LIST_LONG) && enable_foreign) + conf->enable_foreign = strdup(""); + /* Failing here is non-fatal */ init_foreign(conf->multipath_dir, conf->enable_foreign); if (cmd == CMD_USABLE_PATHS) { @@ -1063,21 +1003,20 @@ main (int argc, char *argv[]) condlog(0, "the -c option requires a path to check"); goto out; } - if (cmd == CMD_VALID_PATH && - dev_type == DEV_UEVENT) { - if (!test_multipathd_socket()) { - condlog(3, "%s: daemon is not running", dev); - if (!systemd_service_enabled(dev)) { - r = print_cmd_valid(RTVL_NO, NULL, conf); - goto out; - } - } + if (cmd == CMD_VALID_PATH) { + char * name = convert_dev(dev, (dev_type == DEV_DEVNODE)); + r = check_path_valid(name, conf, dev_type == DEV_UEVENT); + goto out; } if (cmd == CMD_REMOVE_WWID && !dev) { condlog(0, "the -w option requires a device"); goto out; } + if (cmd == CMD_FLUSH_ONE && dev_type != DEV_DEVMAP) { + condlog(0, "the -f option requires a map name to remove"); + goto out; + } switch(delegate_to_multipathd(cmd, dev, dev_type, conf)) { case DELEGATE_OK: @@ -1111,17 +1050,13 @@ main (int argc, char *argv[]) } if (retries < 0) retries = conf->remove_retries; - if (conf->remove == FLUSH_ONE) { - if (dev_type == DEV_DEVMAP) { - r = dm_suspend_and_flush_map(dev, retries) ? - RTVL_FAIL : RTVL_OK; - } else - condlog(0, "must provide a map name to remove"); - + if (cmd == CMD_FLUSH_ONE) { + r = dm_suspend_and_flush_map(dev, retries) ? + RTVL_FAIL : RTVL_OK; goto out; } - else if (conf->remove == FLUSH_ALL) { - r = dm_flush_maps(retries) ? RTVL_FAIL : RTVL_OK; + else if (cmd == CMD_FLUSH_ALL) { + r = dm_flush_maps(1, retries) ? RTVL_FAIL : RTVL_OK; goto out; } while ((r = configure(conf, cmd, dev_type, dev)) == RTVL_RETRY) @@ -1135,13 +1070,6 @@ out: cleanup_prio(); cleanup_checkers(); - /* - * multipath -u must exit with status 0, otherwise udev won't - * import its output. - */ - if (cmd == CMD_VALID_PATH && dev_type == DEV_UEVENT && r == RTVL_NO) - r = RTVL_OK; - if (dev_type == DEV_UEVENT) closelog(); diff --git a/multipath/multipath.8 b/multipath/multipath.8 index 9cdd05a..5b29a5d 100644 --- a/multipath/multipath.8 +++ b/multipath/multipath.8 @@ -125,11 +125,11 @@ the system. Other operation modes are chosen by using one of the following command line switches: .TP .B \-f -Flush (remove) a multipath device map specified as parameter, if unused. +Flush (remove) a multipath device map specified as parameter, if unused. This operation is delegated to the multipathd daemon if it's running. . .TP .B \-F -Flush (remove) all unused multipath device maps. +Flush (remove) all unused multipath device maps. This operation is delegated to the multipathd daemon if it's running. . .TP .B \-l @@ -223,6 +223,12 @@ The verbosity level also controls the level of log and debug messages printed to Dry run, do not create or update devmaps. . .TP +.B \-e +Enable all foreign libraries. This overrides the +.I enable_foreign +option from \fBmultipath.conf(5)\fR. +. +.TP .B \-i Ignore WWIDs file when processing devices. If \fIfind_multipaths strict\fR or \fIfind_multipaths no\fR is set in diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 index 05a5e8f..d2101ed 100644 --- a/multipath/multipath.conf.5 +++ b/multipath/multipath.conf.5 @@ -205,6 +205,11 @@ of outstanding I/O to the path. (Since 2.6.31 kernel) Choose the path for the next bunch of I/O based on the amount of outstanding I/O to the path and its relative throughput. .TP +.I "historical-service-time 0" +(Since 5.8 kernel) Choose the path for the next bunch of IOs based on the +estimation of future service time based on the history of previous I/O submitted +to each path. +.TP The default is: \fBservice-time 0\fR .RE . @@ -643,6 +648,18 @@ The default is: in \fB/sys/block/sd/device/timeout\fR . . .TP +.B allow_usb_devices +If set to +.I no +, all USB devices will be skipped during path discovery. If you intend to use +multipath on USB attached devices, set this to \fIyes\fR. +.RS +.TP +The default is: \fBno\fR +.RE +. +. +.TP .B flush_on_last_del If set to .I yes @@ -1228,10 +1245,11 @@ Enables or disables foreign libraries (see section .I FOREIGN MULTIPATH SUPPORT below). The value is a regular expression; foreign libraries are loaded if their name (e.g. \(dqnvme\(dq) matches the expression. By default, -all foreign libraries are enabled. +no foreign libraries are enabled. Set this to \(dqnvme\(dq to enable NVMe native +multipath support, or \(dq.*\(dq to enable all foreign libraries. .RS .TP -The default is: \fB\(dq\(dq\fR (the empty regular expression) +The default is: \fB\(dqNONE\(dq\fR .RE . . @@ -1248,6 +1266,16 @@ being handled by multipath-tools. .LP . . +In the \fIblacklist\fR and \fIblacklist_exceptions\fR sections, starting a +quoted value with an exclamation mark \fB"!"\fR will invert the matching +of the rest of the regular expression. For instance, \fB"!^sd[a-z]"\fR will +match all values that do not start with \fB"sd[a-z]"\fR. The exclamation mark +can be escaped \fB"\\!"\fR to match a literal \fB!\fR at the start of a +regular expression. \fBNote:\fR The exclamation mark must be inside quotes, +otherwise it will be treated as starting a comment. +.LP +. +. The \fIblacklist_exceptions\fR section is used to revert the actions of the \fIblacklist\fR section. This allows one to selectively include ("whitelist") devices which would normally be excluded via the \fIblacklist\fR section. A common usage is @@ -1264,10 +1292,9 @@ unless explicitly stated. Regular expression matching the device nodes to be excluded/included. .RS .PP -The default \fIblacklist\fR consists of the regular expressions -"^(ram|zram|raw|loop|fd|md|dm-|sr|scd|st|dcssblk)[0-9]" and -"^(td|hd|vd)[a-z]". This causes virtual devices, non-disk devices, and some other -device types to be excluded from multipath handling by default. +The default \fIblacklist\fR consists of the regular expression +\fB"!^(sd[a-z]|dasd[a-z]|nvme[0-9])"\fR. This causes all device types other +than scsi, dasd, and nvme to be excluded from multipath handling by default. .RE .TP .B wwid diff --git a/multipathd/Makefile b/multipathd/Makefile index 8d90117..632b82b 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -1,5 +1,9 @@ include ../Makefile.inc +ifneq ($(call check_func,dm_task_get_errno,/usr/include/libdevmapper.h),0) + CFLAGS += -DLIBDM_API_GET_ERRNO +endif + # # debugging stuff # diff --git a/multipathd/cli.c b/multipathd/cli.c index 800c0fb..bdc9fb1 100644 --- a/multipathd/cli.c +++ b/multipathd/cli.c @@ -568,6 +568,7 @@ cli_init (void) { add_handler(DEL+PATH, NULL); add_handler(ADD+MAP, NULL); add_handler(DEL+MAP, NULL); + add_handler(DEL+MAPS, NULL); add_handler(SWITCH+MAP+GROUP, NULL); add_handler(RECONFIGURE, NULL); add_handler(SUSPEND+MAP, NULL); diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index 7d878c8..235e2a2 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -66,7 +66,7 @@ show_paths (char ** r, int * len, struct vectors * vecs, char * style, c += snprint_foreign_paths(c, reply + maxlen - c, style, pretty); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -102,7 +102,7 @@ show_path (char ** r, int * len, struct vectors * vecs, struct path *pp, c += snprint_path(c, reply + maxlen - c, style, pp, 0); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -131,7 +131,7 @@ show_map_topology (char ** r, int * len, struct multipath * mpp, c = reply; c += snprint_multipath_topology(c, reply + maxlen - c, mpp, 2); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -171,7 +171,7 @@ show_maps_topology (char ** r, int * len, struct vectors * vecs) } c += snprint_foreign_topology(c, reply + maxlen - c, 2); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -209,7 +209,7 @@ show_maps_json (char ** r, int * len, struct vectors * vecs) c = reply; c += snprint_multipath_topology_json(c, maxlen, vecs); - again = ((c - reply) == maxlen); + again = (c == reply + maxlen); REALLOC_REPLY(reply, again, maxlen); } @@ -238,7 +238,7 @@ show_map_json (char ** r, int * len, struct multipath * mpp, c = reply; c += snprint_multipath_map_json(c, maxlen, mpp); - again = ((c - reply) == maxlen); + again = (c == reply + maxlen); REALLOC_REPLY(reply, again, maxlen); } @@ -487,7 +487,7 @@ show_map (char ** r, int *len, struct multipath * mpp, char * style, c += snprint_multipath(c, reply + maxlen - c, style, mpp, pretty); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -533,7 +533,7 @@ show_maps (char ** r, int *len, struct vectors * vecs, char * style, } c += snprint_foreign_multipaths(c, reply + maxlen - c, style, pretty); - again = ((c - reply) == (maxlen - 1)); + again = (c == reply + maxlen - 1); REALLOC_REPLY(reply, again, maxlen); } @@ -713,11 +713,61 @@ cli_add_path (void * v, char ** reply, int * len, void * data) goto blacklisted; pp = find_path_by_dev(vecs->pathvec, param); - if (pp) { + if (pp && pp->initialized != INIT_REMOVED) { condlog(2, "%s: path already in pathvec", param); if (pp->mpp) return 0; - } else { + } else if (pp) { + /* Trying to add a path in INIT_REMOVED state */ + struct multipath *prev_mpp; + + prev_mpp = pp->mpp; + if (prev_mpp == NULL) + condlog(0, "Bug: %s was in INIT_REMOVED state without being a multipath member", + pp->dev); + pp->mpp = NULL; + pp->initialized = INIT_NEW; + pp->wwid[0] = '\0'; + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); + r = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST); + pthread_cleanup_pop(1); + + if (prev_mpp) { + /* Similar logic as in uev_add_path() */ + pp->mpp = prev_mpp; + if (r == PATHINFO_OK && + !strncmp(prev_mpp->wwid, pp->wwid, WWID_SIZE)) { + condlog(2, "%s: path re-added to %s", pp->dev, + pp->mpp->alias); + /* Have the checker reinstate this path asap */ + pp->tick = 1; + return 0; + } else if (!ev_remove_path(pp, vecs, true)) + /* Path removed in ev_remove_path() */ + pp = NULL; + else { + /* Init state is now INIT_REMOVED again */ + pp->dmstate = PSTATE_FAILED; + dm_fail_path(pp->mpp->alias, pp->dev_t); + condlog(1, "%s: failed to re-add path still mapped in %s", + pp->dev, pp->mpp->alias); + return 1; + } + } else { + switch (r) { + case PATHINFO_SKIPPED: + goto blacklisted; + case PATHINFO_OK: + break; + default: + condlog(0, "%s: failed to get pathinfo", param); + return 1; + } + } + } + + if (!pp) { struct udev_device *udevice; udevice = udev_device_new_from_subsystem_sysname(udev, @@ -770,7 +820,7 @@ cli_add_map (void * v, char ** reply, int * len, void * data) { struct vectors * vecs = (struct vectors *)data; char * param = get_keyparam(v, MAP); - int major, minor; + int major = -1, minor = -1; char dev_path[PATH_SIZE]; char *refwwid, *alias = NULL; int rc, count = 0; @@ -852,6 +902,25 @@ cli_del_map (void * v, char ** reply, int * len, void * data) return rc; } +int +cli_del_maps (void *v, char **reply, int *len, void *data) +{ + struct vectors * vecs = (struct vectors *)data; + struct multipath *mpp; + int i, ret = 0; + + condlog(2, "remove maps (operator)"); + vector_foreach_slot(vecs->mpvec, mpp, i) { + if (flush_map(mpp, vecs, 0)) + ret++; + else + i--; + } + /* flush any multipath maps that aren't currently known by multipathd */ + ret |= dm_flush_maps(0, 0); + return ret; +} + int cli_reload(void *v, char **reply, int *len, void *data) { @@ -877,7 +946,7 @@ cli_reload(void *v, char **reply, int *len, void *data) return 1; } - return update_path_groups(mpp, vecs, 0); + return reload_and_sync_map(mpp, vecs, 0); } int resize_map(struct multipath *mpp, unsigned long long size, @@ -1297,7 +1366,7 @@ show_blacklist (char ** r, int * len) c = reply; c += snprint_blacklist_report(conf, c, maxlen); - again = ((c - reply) == maxlen); + again = (c == reply + maxlen); REALLOC_REPLY(reply, again, maxlen); } pthread_cleanup_pop(1); @@ -1339,7 +1408,7 @@ show_devices (char ** r, int * len, struct vectors *vecs) c = reply; c += snprint_devices(conf, c, maxlen, vecs); - again = ((c - reply) == maxlen); + again = (c == reply + maxlen); REALLOC_REPLY(reply, again, maxlen); } pthread_cleanup_pop(1); @@ -1462,6 +1531,8 @@ cli_getprkey(void * v, char ** reply, int * len, void * data) return 1; *reply = malloc(26); + if (!*reply) + return 1; if (!get_be64(mpp->reservation_key)) { sprintf(*reply, "none\n"); @@ -1557,7 +1628,7 @@ int cli_set_marginal(void * v, char ** reply, int * len, void * data) } pp->marginal = 1; - return update_path_groups(pp->mpp, vecs, 0); + return reload_and_sync_map(pp->mpp, vecs, 0); } int cli_unset_marginal(void * v, char ** reply, int * len, void * data) @@ -1584,7 +1655,7 @@ int cli_unset_marginal(void * v, char ** reply, int * len, void * data) } pp->marginal = 0; - return update_path_groups(pp->mpp, vecs, 0); + return reload_and_sync_map(pp->mpp, vecs, 0); } int cli_unset_all_marginal(void * v, char ** reply, int * len, void * data) @@ -1621,5 +1692,5 @@ int cli_unset_all_marginal(void * v, char ** reply, int * len, void * data) vector_foreach_slot (pgp->paths, pp, j) pp->marginal = 0; - return update_path_groups(mpp, vecs, 0); + return reload_and_sync_map(mpp, vecs, 0); } diff --git a/multipathd/cli_handlers.h b/multipathd/cli_handlers.h index 0f45106..6f57b42 100644 --- a/multipathd/cli_handlers.h +++ b/multipathd/cli_handlers.h @@ -26,6 +26,7 @@ int cli_add_path (void * v, char ** reply, int * len, void * data); int cli_del_path (void * v, char ** reply, int * len, void * data); int cli_add_map (void * v, char ** reply, int * len, void * data); int cli_del_map (void * v, char ** reply, int * len, void * data); +int cli_del_maps (void * v, char ** reply, int * len, void * data); int cli_switch_group(void * v, char ** reply, int * len, void * data); int cli_reconfigure(void * v, char ** reply, int * len, void * data); int cli_resize(void * v, char ** reply, int * len, void * data); diff --git a/multipathd/dmevents.c b/multipathd/dmevents.c index b22b47d..5f2d210 100644 --- a/multipathd/dmevents.c +++ b/multipathd/dmevents.c @@ -156,8 +156,10 @@ static int dm_get_events(void) dm_task_no_open_count(dmt); - if (!dm_task_run(dmt)) + if (!dm_task_run(dmt)) { + dm_log_error(3, DM_DEVICE_LIST, dmt); goto fail; + } if (!(names = dm_task_get_names(dmt))) goto fail; @@ -355,7 +357,7 @@ static int dmevent_loop (void) pthread_testcancel(); r = 0; if (curr_dev.action == EVENT_REMOVE) - remove_map_by_alias(curr_dev.name, waiter->vecs, 1); + remove_map_by_alias(curr_dev.name, waiter->vecs, PURGE_VEC); else r = update_multipath(waiter->vecs, curr_dev.name, 1); pthread_cleanup_pop(1); diff --git a/multipathd/main.c b/multipathd/main.c index 8baf9ab..a4abbb2 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -63,6 +63,7 @@ #include "uevent.h" #include "log.h" #include "uxsock.h" +#include "alias.h" #include "mpath_cmd.h" #include "mpath_persist.h" @@ -85,6 +86,7 @@ #define FILE_NAME_SIZE 256 #define CMDSIZE 160 +#define MSG_SIZE 32 #define LOG_MSG(lvl, verb, pp) \ do { \ @@ -153,52 +155,35 @@ static volatile sig_atomic_t exit_sig; static volatile sig_atomic_t reconfig_sig; static volatile sig_atomic_t log_reset_sig; +static const char *daemon_status_msg[DAEMON_STATUS_SIZE] = { + [DAEMON_INIT] = "init", + [DAEMON_START] = "startup", + [DAEMON_CONFIGURE] = "configure", + [DAEMON_IDLE] = "idle", + [DAEMON_RUNNING] = "running", + [DAEMON_SHUTDOWN] = "shutdown", +}; + const char * daemon_status(void) { - switch (get_running_state()) { - case DAEMON_INIT: - return "init"; - case DAEMON_START: - return "startup"; - case DAEMON_CONFIGURE: - return "configure"; - case DAEMON_IDLE: - return "idle"; - case DAEMON_RUNNING: - return "running"; - case DAEMON_SHUTDOWN: - return "shutdown"; - } - return NULL; + int status = get_running_state(); + + if (status < DAEMON_INIT || status >= DAEMON_STATUS_SIZE) + return NULL; + + return daemon_status_msg[status]; } /* * I love you too, systemd ... */ -static const char * -sd_notify_status(enum daemon_status state) -{ - switch (state) { - case DAEMON_INIT: - return "STATUS=init"; - case DAEMON_START: - return "STATUS=startup"; - case DAEMON_CONFIGURE: - return "STATUS=configure"; - case DAEMON_IDLE: - case DAEMON_RUNNING: - return "STATUS=up"; - case DAEMON_SHUTDOWN: - return "STATUS=shutdown"; - } - return NULL; -} - #ifdef USE_SYSTEMD static void do_sd_notify(enum daemon_status old_state, enum daemon_status new_state) { + char notify_msg[MSG_SIZE]; + const char *msg; /* * Checkerloop switches back and forth between idle and running state. * No need to tell systemd each time. @@ -207,7 +192,14 @@ static void do_sd_notify(enum daemon_status old_state, if ((new_state == DAEMON_IDLE || new_state == DAEMON_RUNNING) && (old_state == DAEMON_IDLE || old_state == DAEMON_RUNNING)) return; - sd_notify(0, sd_notify_status(new_state)); + + if (new_state == DAEMON_IDLE || new_state == DAEMON_RUNNING) + msg = "up"; + else + msg = daemon_status_msg[new_state]; + + if (msg && !safe_sprintf(notify_msg, "STATUS=%s", msg)) + sd_notify(0, notify_msg); } #endif @@ -247,7 +239,9 @@ enum daemon_status wait_for_state_change_if(enum daemon_status oldstate, static void __post_config_state(enum daemon_status state) { if (state != running_state && running_state != DAEMON_SHUTDOWN) { +#ifdef USE_SYSTEMD enum daemon_status old_state = running_state; +#endif running_state = state; pthread_cond_broadcast(&config_cond); @@ -272,7 +266,9 @@ 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; @@ -374,7 +370,7 @@ remove_map_and_stop_waiter(struct multipath *mpp, struct vectors *vecs) condlog(3, "%s: removing map from internal tables", mpp->alias); if (!poll_dmevents) stop_waiter_thread(mpp); - remove_map(mpp, vecs, PURGE_VEC); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); } static void @@ -396,25 +392,16 @@ remove_maps_and_stop_waiters(struct vectors *vecs) remove_maps(vecs); } -static void -set_multipath_wwid (struct multipath * mpp) -{ - if (strlen(mpp->wwid)) - return; - - dm_get_uuid(mpp->alias, mpp->wwid, WWID_SIZE); -} - int __setup_multipath(struct vectors *vecs, struct multipath *mpp, int reset) { if (dm_get_info(mpp->alias, &mpp->dmi)) { /* Error accessing table */ - condlog(3, "%s: cannot access table", mpp->alias); + condlog(2, "%s: cannot access table", mpp->alias); goto out; } - if (update_multipath_strings(mpp, vecs->pathvec, 1)) { + if (update_multipath_strings(mpp, vecs->pathvec) != DMP_OK) { condlog(0, "%s: failed to setup multipath", mpp->alias); goto out; } @@ -497,7 +484,7 @@ retry: retries = -1; goto fail; } - verify_paths(mpp, vecs); + verify_paths(mpp); mpp->action = ACT_RELOAD; if (setup_map(mpp, params, PARAMS_SIZE, vecs)) { @@ -515,7 +502,7 @@ retry: fail: if (new_map && (retries < 0 || wait_for_events(mpp, vecs))) { condlog(0, "%s: failed to create new map", mpp->alias); - remove_map(mpp, vecs, 1); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); return 1; } @@ -548,14 +535,17 @@ add_map_without_path (struct vectors *vecs, const char *alias) condlog(3, "%s: cannot access table", mpp->alias); goto out; } - set_multipath_wwid(mpp); + if (!strlen(mpp->wwid)) + dm_get_uuid(mpp->alias, mpp->wwid, WWID_SIZE); + if (!strlen(mpp->wwid)) + condlog(1, "%s: adding map with empty WWID", mpp->alias); conf = get_multipath_config(); mpp->mpe = find_mpe(conf->mptable, mpp->wwid); put_multipath_config(conf); - if (update_multipath_table(mpp, vecs->pathvec, 1)) + if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK) goto out; - if (update_multipath_status(mpp)) + if (update_multipath_status(mpp) != DMP_OK) goto out; if (!vector_alloc_slot(vecs->mpvec)) @@ -568,7 +558,7 @@ add_map_without_path (struct vectors *vecs, const char *alias) return mpp; out: - remove_map(mpp, vecs, PURGE_VEC); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); return NULL; } @@ -631,7 +621,7 @@ sync_maps_state(vector mpvec) sync_map_state(mpp); } -static int +int flush_map(struct multipath * mpp, struct vectors * vecs, int nopaths) { int r; @@ -794,6 +784,7 @@ uev_remove_map (struct uevent * uev, struct vectors * vecs) goto out; } + dm_queue_if_no_path(alias, 0); remove_map_and_stop_waiter(mpp, vecs); out: lock_cleanup_pop(vecs->lock); @@ -844,9 +835,23 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) pp = find_path_by_dev(vecs->pathvec, uev->kernel); if (pp) { int r; + struct multipath *prev_mpp = NULL; + + if (pp->initialized == INIT_REMOVED) { + condlog(3, "%s: re-adding removed path", pp->dev); + pp->initialized = INIT_NEW; + prev_mpp = pp->mpp; + if (prev_mpp == NULL) + condlog(0, "Bug: %s was in INIT_REMOVED state without being a multipath member", + pp->dev); + pp->mpp = NULL; + /* make sure get_uid() is called */ + pp->wwid[0] = '\0'; + } else + condlog(3, + "%s: spurious uevent, path already in pathvec", + uev->kernel); - condlog(3, "%s: spurious uevent, path already in pathvec", - uev->kernel); if (!pp->mpp && !strlen(pp->wwid)) { condlog(3, "%s: reinitialize path", uev->kernel); udev_device_unref(pp->udev); @@ -856,9 +861,44 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) r = pathinfo(pp, conf, DI_ALL | DI_BLACKLIST); pthread_cleanup_pop(1); - if (r == PATHINFO_OK) + if (r == PATHINFO_OK && !prev_mpp) ret = ev_add_path(pp, vecs, need_do_map); - else if (r == PATHINFO_SKIPPED) { + else if (r == PATHINFO_OK && + !strncmp(pp->wwid, prev_mpp->wwid, WWID_SIZE)) { + /* + * Path was unsuccessfully removed, but now + * re-added, and still belongs to the right map + * - all fine, reinstate asap + */ + pp->mpp = prev_mpp; + pp->tick = 1; + ret = 0; + } else if (prev_mpp) { + /* + * Bad: re-added path still hangs in wrong map + * Make another attempt to remove the path + */ + pp->mpp = prev_mpp; + 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 { + /* + * Failure in ev_remove_path will keep + * path in pathvec in INIT_REMOVED state + * Fail the path to make sure it isn't + * used any more. + */ + pp->dmstate = PSTATE_FAILED; + dm_fail_path(pp->mpp->alias, pp->dev_t); + condlog(1, "%s: failed to re-add path still mapped in %s", + pp->dev, pp->mpp->alias); + } + } else if (r == PATHINFO_SKIPPED) { condlog(3, "%s: remove blacklisted path", uev->kernel); i = find_slot(vecs->pathvec, (void *)pp); @@ -958,10 +998,11 @@ rescan: if (mpp) { condlog(4,"%s: adopting all paths for path %s", mpp->alias, pp->dev); - if (adopt_paths(vecs->pathvec, mpp)) + if (adopt_paths(vecs->pathvec, mpp) || + find_slot(vecs->pathvec, pp) == -1) goto fail; /* leave path added to pathvec */ - verify_paths(mpp, vecs); + verify_paths(mpp); mpp->action = ACT_RELOAD; } else { if (!should_multipath(pp, vecs->pathvec, vecs->mpvec)) { @@ -1049,7 +1090,7 @@ rescan: goto fail; fail_map: - remove_map(mpp, vecs, 1); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); fail: orphan_path(pp, "failed to add path"); return 1; @@ -1100,6 +1141,18 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) goto fail; } + /* + * Mark the path as removed. In case of success, we + * will delete it for good. Otherwise, it will be deleted + * later, unless all attempts to reload this map fail. + * Note: we have to explicitly remove pp from mpp->paths, + * update_mpp_paths() doesn't do that. + */ + set_path_removed(pp); + i = find_slot(mpp->paths, pp); + if (i != -1) + 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 @@ -1107,9 +1160,6 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) if (mpp->hwe == pp->hwe) mpp->hwe = NULL; - if ((i = find_slot(mpp->paths, (void *)pp)) != -1) - vector_del_slot(mpp->paths, i); - /* * remove the map IF removing the last path */ @@ -1133,6 +1183,7 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) " removing all paths", alias); retval = 0; + /* flush_map() has freed the path */ goto out; } /* @@ -1169,21 +1220,27 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map) /* * update our state from kernel */ + char devt[BLK_DEV_SIZE]; + + strlcpy(devt, pp->dev_t, sizeof(devt)); if (setup_multipath(vecs, mpp)) return 1; + /* + * Successful map reload without this path: + * sync_map_state() will free it. + */ sync_map_state(mpp); - condlog(2, "%s [%s]: path removed from map %s", - pp->dev, pp->dev_t, mpp->alias); + condlog(2, "%s: path removed from map %s", + devt, mpp->alias); } + } else { + /* mpp == NULL */ + if ((i = find_slot(vecs->pathvec, (void *)pp)) != -1) + vector_del_slot(vecs->pathvec, i); + free_path(pp); } - out: - if ((i = find_slot(vecs->pathvec, (void *)pp)) != -1) - vector_del_slot(vecs->pathvec, i); - - free_path(pp); - return retval; fail: @@ -1263,7 +1320,7 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) else { if (ro == 1) pp->mpp->force_readonly = 1; - retval = update_path_groups(mpp, vecs, 0); + retval = reload_and_sync_map(mpp, vecs, 0); if (retval == 2) condlog(2, "%s: map removed during reload", pp->dev); else { @@ -1346,9 +1403,9 @@ map_discovery (struct vectors * vecs) return 1; vector_foreach_slot (vecs->mpvec, mpp, i) - if (update_multipath_table(mpp, vecs->pathvec, 1) || - update_multipath_status(mpp)) { - remove_map(mpp, vecs, 1); + if (update_multipath_table(mpp, vecs->pathvec, 0) != DMP_OK || + update_multipath_status(mpp) != DMP_OK) { + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); i--; } @@ -1444,6 +1501,31 @@ 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; } @@ -1551,6 +1633,7 @@ uxlsnrloop (void * ap) set_handler_callback(DEL+PATH, cli_del_path); set_handler_callback(ADD+MAP, cli_add_map); set_handler_callback(DEL+MAP, cli_del_map); + set_handler_callback(DEL+MAPS, cli_del_maps); set_handler_callback(SWITCH+MAP+GROUP, cli_switch_group); set_unlocked_handler_callback(RECONFIGURE, cli_reconfigure); set_handler_callback(SUSPEND+MAP, cli_suspend); @@ -1611,22 +1694,18 @@ fail_path (struct path * pp, int del_active) /* * caller must have locked the path list before calling that function */ -static int +static void reinstate_path (struct path * pp) { - int ret = 0; - if (!pp->mpp) - return 0; + return; - if (dm_reinstate_path(pp->mpp->alias, pp->dev_t)) { + if (dm_reinstate_path(pp->mpp->alias, pp->dev_t)) condlog(0, "%s: reinstate failed", pp->dev_t); - ret = 1; - } else { + else { condlog(2, "%s: reinstated", pp->dev_t); update_queue_mode_add_path(pp->mpp); } - return ret; } static void @@ -1823,7 +1902,45 @@ int update_prio(struct path *pp, int refresh_all) return 1; } -int update_path_groups(struct multipath *mpp, struct vectors *vecs, int refresh) +static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh, + int is_daemon) +{ + char params[PARAMS_SIZE] = {0}; + struct path *pp; + int i, r; + + update_mpp_paths(mpp, vecs->pathvec); + if (refresh) { + vector_foreach_slot (mpp->paths, pp, i) { + struct config *conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); + r = pathinfo(pp, conf, DI_PRIO); + pthread_cleanup_pop(1); + if (r) { + condlog(2, "%s: failed to refresh pathinfo", + mpp->alias); + return 1; + } + } + } + if (setup_map(mpp, params, PARAMS_SIZE, vecs)) { + condlog(0, "%s: failed to setup map", mpp->alias); + return 1; + } + select_action(mpp, vecs->mpvec, 1); + + r = domap(mpp, params, is_daemon); + if (r == DOMAP_FAIL || r == DOMAP_RETRY) { + condlog(3, "%s: domap (%u) failure " + "for reload map", mpp->alias, r); + return 1; + } + + return 0; +} + +int reload_and_sync_map(struct multipath *mpp, + struct vectors *vecs, int refresh) { if (reload_map(vecs, mpp, refresh, 1)) return 1; @@ -1963,8 +2080,9 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) int marginal_pathgroups, marginal_changed = 0; int ret; - if ((pp->initialized == INIT_OK || - pp->initialized == INIT_REQUESTED_UDEV) && !pp->mpp) + if (((pp->initialized == INIT_OK || + pp->initialized == INIT_REQUESTED_UDEV) && !pp->mpp) || + pp->initialized == INIT_REMOVED) return 0; if (pp->tick) @@ -2087,9 +2205,16 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) /* * Synchronize with kernel state */ - if (update_multipath_strings(pp->mpp, vecs->pathvec, 1)) { - condlog(1, "%s: Could not synchronize with kernel state", - pp->dev); + ret = update_multipath_strings(pp->mpp, vecs->pathvec); + if (ret != DMP_OK) { + if (ret == DMP_NOT_FOUND) { + /* multipath device missing. Likely removed */ + condlog(1, "%s: multipath device '%s' not found", + pp->dev, pp->mpp->alias); + return 0; + } else + condlog(1, "%s: Couldn't synchronize with kernel state", + pp->dev); pp->dmstate = PSTATE_UNDEF; } /* if update_multipath_strings orphaned the path, quit early */ @@ -2179,12 +2304,8 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) /* * reinstate this path */ - if (!disable_reinstate && reinstate_path(pp)) { - condlog(3, "%s: reload map", pp->dev); - ev_add_path(pp, vecs, 1); - pp->tick = 1; - return 0; - } + if (!disable_reinstate) + reinstate_path(pp); new_path_up = 1; if (oldchkrstate != PATH_UP && oldchkrstate != PATH_GHOST) @@ -2200,15 +2321,10 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) else if (newstate == PATH_UP || newstate == PATH_GHOST) { if ((pp->dmstate == PSTATE_FAILED || pp->dmstate == PSTATE_UNDEF) && - !disable_reinstate) { + !disable_reinstate) /* Clear IO errors */ - if (reinstate_path(pp)) { - condlog(3, "%s: reload map", pp->dev); - ev_add_path(pp, vecs, 1); - pp->tick = 1; - return 0; - } - } else { + reinstate_path(pp); + else { LOG_MSG(4, verbosity, pp); if (pp->checkint != max_checkint) { /* @@ -2252,13 +2368,18 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) */ condlog(4, "path prio refresh"); - if (marginal_changed) - update_path_groups(pp->mpp, vecs, 1); + if (marginal_changed) { + condlog(2, "%s: path is %s marginal", pp->dev, + (pp->marginal)? "now" : "no longer"); + 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) - update_path_groups(pp->mpp, vecs, !new_path_up); - else if (need_switch_pathgroup(pp->mpp, 0)) { + pp->mpp->pgfailback == -FAILBACK_IMMEDIATE) { + condlog(2, "%s: path priorities changed. reloading", + pp->mpp->alias); + reload_and_sync_map(pp->mpp, vecs, !new_path_up); + } else if (need_switch_pathgroup(pp->mpp, 0)) { if (pp->mpp->pgfailback > 0 && (new_path_up || pp->mpp->failback_tick <= 0)) pp->mpp->failback_tick = @@ -2280,7 +2401,9 @@ checkerloop (void *ap) struct timespec last_time; struct config *conf; int foreign_tick = 0; +#ifdef USE_SYSTEMD bool use_watchdog; +#endif pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); @@ -2294,7 +2417,9 @@ checkerloop (void *ap) /* use_watchdog is set from process environment and never changes */ conf = get_multipath_config(); +#ifdef USE_SYSTEMD use_watchdog = conf->use_watchdog; +#endif put_multipath_config(conf); while (1) { @@ -2374,7 +2499,7 @@ checkerloop (void *ap) conf = get_multipath_config(); max_checkint = conf->max_checkint; put_multipath_config(conf); - if (diff_time.tv_sec > max_checkint) + if (diff_time.tv_sec > (time_t)max_checkint) condlog(1, "path checkers took longer " "than %lu seconds, consider " "increasing max_polling_interval", @@ -2519,7 +2644,7 @@ configure (struct vectors * vecs) */ vector_foreach_slot(vecs->mpvec, mpp, i) { if (wait_for_events(mpp, vecs)) { - remove_map(mpp, vecs, 1); + remove_map(mpp, vecs->pathvec, vecs->mpvec, PURGE_VEC); i--; continue; } @@ -2584,6 +2709,8 @@ reconfigure (struct vectors * vecs) conf->verbosity = verbosity; if (bindings_read_only) conf->bindings_read_only = bindings_read_only; + check_alias_settings(conf); + uxsock_timeout = conf->uxsock_timeout; old = rcu_dereference(multipath_conf); @@ -3229,7 +3356,6 @@ void * mpath_pr_event_handler_fn (void * pathp ) if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) { condlog(1, "%s: No key found. Device may not be registered.", pp->dev); - ret = MPATH_PR_SUCCESS; goto out; } condlog(2, "Multipath reservation_key: 0x%" PRIx64 " ", @@ -3251,12 +3377,13 @@ void * mpath_pr_event_handler_fn (void * pathp ) { condlog(0, "%s: Either device not registered or ", pp->dev); condlog(0, "host is not authorised for registration. Skip path"); - ret = MPATH_PR_OTHER; goto out; } - param= malloc(sizeof(struct prout_param_descriptor)); - memset(param, 0 , sizeof(struct prout_param_descriptor)); + param = (struct prout_param_descriptor *)MALLOC(sizeof(struct prout_param_descriptor)); + if (!param) + goto out; + param->sa_flags = mpp->sa_flags; memcpy(param->sa_key, &mpp->reservation_key, 8); param->num_transportid = 0; diff --git a/multipathd/main.h b/multipathd/main.h index 7bb8463..5abbe97 100644 --- a/multipathd/main.h +++ b/multipathd/main.h @@ -4,12 +4,13 @@ #define MAPGCINT 5 enum daemon_status { - DAEMON_INIT, + DAEMON_INIT = 0, DAEMON_START, DAEMON_CONFIGURE, DAEMON_IDLE, DAEMON_RUNNING, DAEMON_SHUTDOWN, + DAEMON_STATUS_SIZE, }; struct prout_param_descriptor; @@ -28,6 +29,7 @@ int ev_add_path (struct path *, struct vectors *, int); int ev_remove_path (struct path *, struct vectors *, int); int ev_add_map (char *, const char *, struct vectors *); int ev_remove_map (char *, char *, int, struct vectors *); +int flush_map(struct multipath *, struct vectors *, int); int set_config_state(enum daemon_status); void * mpath_alloc_prin_response(int prin_sa); int prin_do_scsi_ioctl(char *, int rq_servact, struct prin_resp * resp, @@ -45,7 +47,7 @@ int __setup_multipath (struct vectors * vecs, struct multipath * mpp, int reset); #define setup_multipath(vecs, mpp) __setup_multipath(vecs, mpp, 1) int update_multipath (struct vectors *vecs, char *mapname, int reset); -int update_path_groups(struct multipath *mpp, struct vectors *vecs, - int refresh); +int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs, + int refresh); #endif /* MAIN_H */ diff --git a/multipathd/waiter.c b/multipathd/waiter.c index e645766..3bc6980 100644 --- a/multipathd/waiter.c +++ b/multipathd/waiter.c @@ -64,7 +64,7 @@ void stop_waiter_thread (struct multipath *mpp) return; condlog(3, "%s: stop event checker thread (%lu)", mpp->alias, - mpp->waiter); + (unsigned long)mpp->waiter); thread = mpp->waiter; mpp->waiter = (pthread_t)0; pthread_cleanup_push(cleanup_lock, &waiter_lock); @@ -119,6 +119,8 @@ static int waiteventloop (struct event_thread *waiter) pthread_testcancel(); r = dm_task_run(waiter->dmt); + if (!r) + dm_log_error(2, DM_DEVICE_WAITEVENT, waiter->dmt); pthread_testcancel(); pthread_sigmask(SIG_SETMASK, &oldset, NULL); diff --git a/tests/Makefile b/tests/Makefile index 77ff324..d26b3ce 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,15 +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) -lmultipath -lcmocka +LIBDEPS += -L$(multipathdir) -L$(mpathcmddir) -lmultipath -lmpathcmd -lcmocka TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ - alias directio + alias directio valid devt +HELPERS := test-lib.o test-log.o .SILENT: $(TESTS:%=%.o) .PRECIOUS: $(TESTS:%=%-test) all: $(TESTS:%=%.out) +valgrind: $(TESTS:%=%.vgr) # test-specific compiler flags # XYZ-test_FLAGS: Additional compiler flags for this test @@ -41,7 +43,7 @@ endif dmevents-test_LIBDEPS = -lpthread -ldevmapper -lurcu hwtable-test_TESTDEPS := test-lib.o hwtable-test_OBJDEPS := ../libmultipath/discovery.o ../libmultipath/blacklist.o \ - ../libmultipath/prio.o ../libmultipath/callout.o ../libmultipath/structs.o + ../libmultipath/structs.o hwtable-test_LIBDEPS := -ludev -lpthread -ldl blacklist-test_TESTDEPS := test-log.o blacklist-test_OBJDEPS := ../libmultipath/blacklist.o @@ -50,6 +52,9 @@ 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_LIBDEPS := -ludev -lpthread -ldl +devt-test_LIBDEPS := -ludev ifneq ($(DIO_TEST_DEV),) directio-test_LIBDEPS := -laio endif @@ -58,19 +63,27 @@ endif $(CC) $(CFLAGS) $($*-test_FLAGS) -c -o $@ $< lib/libchecktur.so: - mkdir lib - ln -t lib ../libmultipath/{checkers,prioritizers,foreign}/*.so + mkdir -p lib + ln ../libmultipath/*/*.so lib %.out: %-test lib/libchecktur.so @echo == running $< == @LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) ./$< >$@ -OBJS = $(TESTS:%=%.o) test-lib.o +%.vgr: %-test lib/libchecktur.so + @echo == running valgrind for $< == + @LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) \ + valgrind --leak-check=full --error-exitcode=128 ./$< >$@ 2>&1 + +OBJS = $(TESTS:%=%.o) $(HELPERS) test_clean: - $(RM) $(TESTS:%=%.out) + $(RM) $(TESTS:%=%.out) $(TESTS:%=%.vgr) + +valgrind_clean: + $(RM) $(TESTS:%=%.vgr) -clean: test_clean dep_clean +clean: test_clean valgrind_clean dep_clean $(RM) $(TESTS:%=%-test) $(OBJS) *.o.wrap $(RM) -rf lib diff --git a/tests/README.md b/tests/README.md index 6438a82..6e7ad40 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,6 +5,14 @@ or simply `make` in the `tests` subdirectory. The test output is saved as `.out`. The test programs are called `-test`, and can be run standalone e.g. for debugging purposes. +## Running tests under valgrind + +The unit tests can be run under the valgrind debugger with `make valgrind` +in the `tests` directory, or `make valgrind-test` in the top directory. +If valgrind detects a bad memory access or leak, the test will fail. The +output of the test run, including valgrind output, is stored as +`.vgr`. + ## Notes on individual tests ### Tests that require root permissions diff --git a/tests/alias.c b/tests/alias.c index 30414db..7fda679 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -552,7 +552,7 @@ static void rl_match_a(void **state) buf[0] = '\0'; will_return(__wrap_fgets, "MPATHa WWID0\n"); - expect_condlog(3, "Found matching alias [MPATHa] in bindings file.\n" + expect_condlog(3, "Found matching alias [MPATHa] in bindings file. " "Setting wwid to WWID0\n"); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, 0); @@ -617,7 +617,7 @@ static void rl_match_b(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHz WWID26\n"); will_return(__wrap_fgets, "MPATHb WWID2\n"); - expect_condlog(3, "Found matching alias [MPATHb] in bindings file.\n" + expect_condlog(3, "Found matching alias [MPATHb] in bindings file. " "Setting wwid to WWID2\n"); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, 0); @@ -652,6 +652,7 @@ static void al_a(void **state) alias = allocate_binding(0, "WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); + free(alias); } static void al_zz(void **state) @@ -668,6 +669,7 @@ static void al_zz(void **state) alias = allocate_binding(0, "WWIDzz", 26*26 + 26, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHzz"); + free(alias); } static void al_0(void **state) @@ -710,7 +712,7 @@ static void al_write_err(void **state) will_return(__wrap_write, strlen(ln) - 1); expect_value(__wrap_ftruncate, length, offset); will_return(__wrap_ftruncate, 0); - expect_condlog(0, "Cannot write binding to bindings file : Success\n"); + expect_condlog(0, "Cannot write binding to bindings file :"); alias = allocate_binding(0, "WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); diff --git a/tests/blacklist.c b/tests/blacklist.c index 6e7c186..84a3ba2 100644 --- a/tests/blacklist.c +++ b/tests/blacklist.c @@ -60,50 +60,96 @@ __wrap_udev_list_entry_get_name(struct udev_list_entry *list_entry) return *(const char **)list_entry; } +vector elist_property_default; +vector blist_devnode_default; vector blist_devnode_sdb; +vector blist_devnode_sdb_inv; vector blist_all; vector blist_device_foo_bar; +vector blist_device_foo_inv_bar; +vector blist_device_foo_bar_inv; vector blist_device_all; vector blist_wwid_xyzzy; +vector blist_wwid_xyzzy_inv; vector blist_protocol_fcp; +vector blist_protocol_fcp_inv; vector blist_property_wwn; +vector blist_property_wwn_inv; static int setup(void **state) { + struct config conf; + + memset(&conf, 0, sizeof(conf)); + conf.blist_devnode = vector_alloc(); + if (!conf.blist_devnode) + return -1; + conf.elist_property = vector_alloc(); + if (!conf.elist_property) + return -1; + if (setup_default_blist(&conf) != 0) + return -1; + elist_property_default = conf.elist_property; + blist_devnode_default = conf.blist_devnode; + blist_devnode_sdb = vector_alloc(); if (!blist_devnode_sdb || - store_ble(blist_devnode_sdb, strdup("sdb"), ORIGIN_CONFIG)) + store_ble(blist_devnode_sdb, "sdb", ORIGIN_CONFIG)) + return -1; + blist_devnode_sdb_inv = vector_alloc(); + if (!blist_devnode_sdb_inv || + store_ble(blist_devnode_sdb_inv, "!sdb", ORIGIN_CONFIG)) return -1; blist_all = vector_alloc(); - if (!blist_all || store_ble(blist_all, strdup(".*"), ORIGIN_CONFIG)) + if (!blist_all || store_ble(blist_all, ".*", ORIGIN_CONFIG)) return -1; blist_device_foo_bar = vector_alloc(); if (!blist_device_foo_bar || alloc_ble_device(blist_device_foo_bar) || - set_ble_device(blist_device_foo_bar, strdup("foo"), strdup("bar"), - ORIGIN_CONFIG)) + set_ble_device(blist_device_foo_bar, "foo", "bar", ORIGIN_CONFIG)) + return -1; + blist_device_foo_inv_bar = vector_alloc(); + if (!blist_device_foo_inv_bar || + alloc_ble_device(blist_device_foo_inv_bar) || + set_ble_device(blist_device_foo_inv_bar, "!foo", "bar", ORIGIN_CONFIG)) + return -1; + blist_device_foo_bar_inv = vector_alloc(); + if (!blist_device_foo_bar_inv || + alloc_ble_device(blist_device_foo_bar_inv) || + set_ble_device(blist_device_foo_bar_inv, "foo", "!bar", ORIGIN_CONFIG)) return -1; blist_device_all = vector_alloc(); if (!blist_device_all || alloc_ble_device(blist_device_all) || - set_ble_device(blist_device_all, strdup(".*"), strdup(".*"), - ORIGIN_CONFIG)) + set_ble_device(blist_device_all, ".*", ".*", ORIGIN_CONFIG)) return -1; blist_wwid_xyzzy = vector_alloc(); if (!blist_wwid_xyzzy || - store_ble(blist_wwid_xyzzy, strdup("xyzzy"), ORIGIN_CONFIG)) + store_ble(blist_wwid_xyzzy, "xyzzy", ORIGIN_CONFIG)) + return -1; + blist_wwid_xyzzy_inv = vector_alloc(); + if (!blist_wwid_xyzzy_inv || + store_ble(blist_wwid_xyzzy_inv, "!xyzzy", ORIGIN_CONFIG)) return -1; blist_protocol_fcp = vector_alloc(); if (!blist_protocol_fcp || - store_ble(blist_protocol_fcp, strdup("scsi:fcp"), ORIGIN_CONFIG)) + store_ble(blist_protocol_fcp, "scsi:fcp", ORIGIN_CONFIG)) + return -1; + blist_protocol_fcp_inv = vector_alloc(); + if (!blist_protocol_fcp_inv || + store_ble(blist_protocol_fcp_inv, "!scsi:fcp", ORIGIN_CONFIG)) return -1; blist_property_wwn = vector_alloc(); if (!blist_property_wwn || - store_ble(blist_property_wwn, strdup("ID_WWN"), ORIGIN_CONFIG)) + store_ble(blist_property_wwn, "ID_WWN", ORIGIN_CONFIG)) + return -1; + blist_property_wwn_inv = vector_alloc(); + if (!blist_property_wwn_inv || + store_ble(blist_property_wwn_inv, "!ID_WWN", ORIGIN_CONFIG)) return -1; return 0; @@ -111,13 +157,21 @@ static int setup(void **state) static int teardown(void **state) { + free_blacklist(elist_property_default); + free_blacklist(blist_devnode_default); free_blacklist(blist_devnode_sdb); + free_blacklist(blist_devnode_sdb_inv); free_blacklist(blist_all); free_blacklist_device(blist_device_foo_bar); + free_blacklist_device(blist_device_foo_inv_bar); + free_blacklist_device(blist_device_foo_bar_inv); free_blacklist_device(blist_device_all); free_blacklist(blist_wwid_xyzzy); + free_blacklist(blist_wwid_xyzzy_inv); free_blacklist(blist_protocol_fcp); + free_blacklist(blist_protocol_fcp_inv); free_blacklist(blist_property_wwn); + free_blacklist(blist_property_wwn_inv); return 0; } @@ -141,6 +195,11 @@ static void test_devnode_blacklist(void **state) expect_condlog(3, "sdb: device node name blacklisted\n"); assert_int_equal(filter_devnode(blist_devnode_sdb, NULL, "sdb"), MATCH_DEVNODE_BLIST); + assert_int_equal(filter_devnode(blist_devnode_sdb_inv, NULL, "sdb"), + MATCH_NOTHING); + expect_condlog(3, "sdc: device node name blacklisted\n"); + assert_int_equal(filter_devnode(blist_devnode_sdb_inv, NULL, "sdc"), + MATCH_DEVNODE_BLIST); } static void test_devnode_whitelist(void **state) @@ -159,12 +218,39 @@ static void test_devnode_missing(void **state) MATCH_NOTHING); } +static void test_devnode_default(void **state) +{ + assert_int_equal(filter_devnode(blist_devnode_default, NULL, "sdaa"), + MATCH_NOTHING); + assert_int_equal(filter_devnode(blist_devnode_default, NULL, "nvme0n1"), + MATCH_NOTHING); + assert_int_equal(filter_devnode(blist_devnode_default, NULL, "dasda"), + MATCH_NOTHING); + expect_condlog(3, "hda: device node name blacklisted\n"); + assert_int_equal(filter_devnode(blist_devnode_default, NULL, "hda"), + MATCH_DEVNODE_BLIST); +} + static void test_device_blacklist(void **state) { expect_condlog(3, "sdb: (foo:bar) vendor/product blacklisted\n"); assert_int_equal(filter_device(blist_device_foo_bar, NULL, "foo", "bar", "sdb"), MATCH_DEVICE_BLIST); + assert_int_equal(filter_device(blist_device_foo_inv_bar, NULL, "foo", + "bar", "sdb"), + MATCH_NOTHING); + assert_int_equal(filter_device(blist_device_foo_bar_inv, NULL, "foo", + "bar", "sdb"), + MATCH_NOTHING); + expect_condlog(3, "sdb: (baz:bar) vendor/product blacklisted\n"); + assert_int_equal(filter_device(blist_device_foo_inv_bar, NULL, "baz", + "bar", "sdb"), + MATCH_DEVICE_BLIST); + expect_condlog(3, "sdb: (foo:baz) vendor/product blacklisted\n"); + assert_int_equal(filter_device(blist_device_foo_bar_inv, NULL, "foo", + "baz", "sdb"), + MATCH_DEVICE_BLIST); } static void test_device_whitelist(void **state) @@ -191,6 +277,11 @@ static void test_wwid_blacklist(void **state) expect_condlog(3, "sdb: wwid xyzzy blacklisted\n"); assert_int_equal(filter_wwid(blist_wwid_xyzzy, NULL, "xyzzy", "sdb"), MATCH_WWID_BLIST); + assert_int_equal(filter_wwid(blist_wwid_xyzzy_inv, NULL, "xyzzy", + "sdb"), MATCH_NOTHING); + expect_condlog(3, "sdb: wwid plugh blacklisted\n"); + assert_int_equal(filter_wwid(blist_wwid_xyzzy_inv, NULL, "plugh", + "sdb"), MATCH_WWID_BLIST); } static void test_wwid_whitelist(void **state) @@ -218,6 +309,12 @@ static void test_protocol_blacklist(void **state) expect_condlog(3, "sdb: protocol scsi:fcp blacklisted\n"); assert_int_equal(filter_protocol(blist_protocol_fcp, NULL, &pp), MATCH_PROTOCOL_BLIST); + assert_int_equal(filter_protocol(blist_protocol_fcp_inv, NULL, &pp), + MATCH_NOTHING); + pp.sg_id.proto_id = SCSI_PROTOCOL_ATA; + expect_condlog(3, "sdb: protocol scsi:ata blacklisted\n"); + assert_int_equal(filter_protocol(blist_protocol_fcp_inv, NULL, &pp), + MATCH_PROTOCOL_BLIST); } static void test_protocol_whitelist(void **state) @@ -245,10 +342,17 @@ static void test_protocol_missing(void **state) static void test_property_blacklist(void **state) { static struct udev_device udev = { "sdb", { "ID_FOO", "ID_WWN", "ID_BAR", NULL } }; + static struct udev_device udev_inv = { "sdb", { "ID_WWN", NULL } }; conf.blist_property = blist_property_wwn; expect_condlog(3, "sdb: udev property ID_WWN blacklisted\n"); assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), MATCH_PROPERTY_BLIST); + conf.blist_property = blist_property_wwn_inv; + expect_condlog(3, "sdb: udev property ID_FOO blacklisted\n"); + assert_int_equal(filter_property(&conf, &udev, 3, "ID_SERIAL"), + MATCH_PROPERTY_BLIST); + assert_int_equal(filter_property(&conf, &udev_inv, 3, "ID_SERIAL"), + MATCH_NOTHING); } /* the property check works different in that you check all the property @@ -484,6 +588,7 @@ int test_blacklist(void) cmocka_unit_test(test_devnode_blacklist), cmocka_unit_test(test_devnode_whitelist), cmocka_unit_test(test_devnode_missing), + cmocka_unit_test(test_devnode_default), cmocka_unit_test(test_device_blacklist), cmocka_unit_test(test_device_whitelist), cmocka_unit_test(test_device_missing), diff --git a/tests/devt.c b/tests/devt.c new file mode 100644 index 0000000..fd4d74a --- /dev/null +++ b/tests/devt.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020 Martin Wilck, SUSE + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "debug.h" + +#include "globals.c" + +static int get_one_devt(char *devt, size_t len) +{ + struct udev_enumerate *enm; + int r, ret = -1; + struct udev_list_entry *first; + struct udev_device *u_dev; + const char *path; + dev_t devnum; + + enm = udev_enumerate_new(udev); + if (!enm) + return -1; + r = udev_enumerate_add_match_subsystem(enm, "block"); + r = udev_enumerate_scan_devices(enm); + if (r < 0) + goto out; + first = udev_enumerate_get_list_entry(enm); + if (!first) + goto out; + path = udev_list_entry_get_name(first); + u_dev = udev_device_new_from_syspath(udev, path); + if (!u_dev) + goto out; + devnum = udev_device_get_devnum(u_dev); + snprintf(devt, len, "%d:%d", + major(devnum), minor(devnum)); + udev_device_unref(u_dev); + condlog(3, "found block device: %s", devt); + ret = 0; +out: + udev_enumerate_unref(enm); + return ret; +} + +int setup(void **state) +{ + static char dev_t[BLK_DEV_SIZE]; + + udev = udev_new(); + if (udev == NULL) + return -1; + *state = dev_t; + return get_one_devt(dev_t, sizeof(dev_t)); +} + +int teardown(void **state) +{ + udev_unref(udev); + return 0; +} + +static void test_devt2devname_devt_good(void **state) +{ + char dummy[BLK_DEV_SIZE]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 0); +} + +static void test_devt2devname_devname_null(void **state) +{ + assert_int_equal(devt2devname(NULL, 0, ""), 1); +} + +/* buffer length 0 */ +static void test_devt2devname_length_0(void **state) +{ + char dummy[] = ""; + + assert_int_equal(devt2devname(dummy, 0, ""), 1); +} + +/* buffer too small */ +static void test_devt2devname_length_1(void **state) +{ + char dummy[] = ""; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), *state), 1); +} + +static void test_devt2devname_devt_null(void **state) +{ + char dummy[32]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), NULL), 1); +} + +static void test_devt2devname_devt_empty(void **state) +{ + char dummy[32]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), ""), 1); +} + +static void test_devt2devname_devt_invalid_1(void **state) +{ + char dummy[32]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), "foo"), 1); +} + +static void test_devt2devname_devt_invalid_2(void **state) +{ + char dummy[32]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), "1234"), 1); +} + +static void test_devt2devname_devt_invalid_3(void **state) +{ + char dummy[32]; + + assert_int_equal(devt2devname(dummy, sizeof(dummy), "0:0"), 1); +} + +static void test_devt2devname_real(void **state) +{ + struct udev_enumerate *enm; + int r; + struct udev_list_entry *first, *item; + unsigned int i = 0; + + enm = udev_enumerate_new(udev); + assert_non_null(enm); + r = udev_enumerate_add_match_subsystem(enm, "block"); + assert_in_range(r, 0, INT_MAX); + r = udev_enumerate_scan_devices(enm); + first = udev_enumerate_get_list_entry(enm); + udev_list_entry_foreach(item, first) { + const char *path = udev_list_entry_get_name(item); + struct udev_device *u_dev; + dev_t devnum; + char devt[BLK_DEV_SIZE]; + char devname[FILE_NAME_SIZE]; + + u_dev = udev_device_new_from_syspath(udev, path); + assert_non_null(u_dev); + devnum = udev_device_get_devnum(u_dev); + snprintf(devt, sizeof(devt), "%d:%d", + major(devnum), minor(devnum)); + r = devt2devname(devname, sizeof(devname), devt); + assert_int_equal(r, 0); + assert_string_equal(devname, udev_device_get_sysname(u_dev)); + i++; + udev_device_unref(u_dev); + } + udev_enumerate_unref(enm); + condlog(2, "devt2devname test passed for %u block devices", i); +} + +static int devt2devname_tests(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_devt2devname_devt_good), + cmocka_unit_test(test_devt2devname_devname_null), + cmocka_unit_test(test_devt2devname_length_0), + cmocka_unit_test(test_devt2devname_length_1), + cmocka_unit_test(test_devt2devname_devt_null), + cmocka_unit_test(test_devt2devname_devt_empty), + cmocka_unit_test(test_devt2devname_devt_invalid_1), + cmocka_unit_test(test_devt2devname_devt_invalid_2), + cmocka_unit_test(test_devt2devname_devt_invalid_3), + cmocka_unit_test(test_devt2devname_real), + }; + + return cmocka_run_group_tests(tests, setup, teardown); +} + +int main(void) +{ + int ret = 0; + + ret += devt2devname_tests(); + return ret; +} diff --git a/tests/directio.c b/tests/directio.c index 3cd7a52..9895409 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -31,7 +31,7 @@ int test_fd = 111; int ioctx_count = 0; struct io_event mock_events[AIO_GROUP_SIZE]; /* same as the checker max */ int ev_off = 0; -struct timespec zero_timeout = {0}; +struct timespec zero_timeout = { .tv_sec = 0 }; struct timespec full_timeout = { .tv_sec = -1 }; int __real_ioctl(int fd, unsigned long request, void *argp); @@ -287,7 +287,7 @@ static void test_reset(void **state) /* tests initializing, then resetting, and then initializing again */ static void test_init_reset_init(void **state) { - struct checker c = {0}; + struct checker c = {.cls = NULL}; struct aio_group *aio_grp, *tmp_grp; assert_true(list_empty(&aio_grp_list)); @@ -315,8 +315,8 @@ static void test_init_reset_init(void **state) static void test_init_free(void **state) { int i, count = 0; - struct checker c[4096] = {0}; - struct aio_group *aio_grp; + struct checker c[4096] = {{.cls = NULL}}; + struct aio_group *aio_grp = NULL; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); @@ -361,7 +361,7 @@ static void test_init_free(void **state) static void test_multi_init_free(void **state) { int i, count; - struct checker c[4096] = {0}; + struct checker c[4096] = {{.cls = NULL}}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); @@ -401,7 +401,7 @@ static void test_multi_init_free(void **state) /* simple single checker sync test */ static void test_check_state_simple(void **state) { - struct checker c = {0}; + struct checker c = {.cls = NULL}; struct async_req *req; int res = 0; @@ -417,7 +417,7 @@ static void test_check_state_simple(void **state) /* test sync timeout */ static void test_check_state_timeout(void **state) { - struct checker c = {0}; + struct checker c = {.cls = NULL}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); @@ -440,7 +440,7 @@ static void test_check_state_timeout(void **state) /* test async timeout */ static void test_check_state_async_timeout(void **state) { - struct checker c = {0}; + struct checker c = {.cls = NULL}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); @@ -467,7 +467,7 @@ static void test_check_state_async_timeout(void **state) /* test freeing checkers with outstanding requests */ static void test_free_with_pending(void **state) { - struct checker c[2] = {0}; + struct checker c[2] = {{.cls = NULL}}; struct aio_group *aio_grp; struct async_req *req; int res = 0; @@ -500,7 +500,7 @@ static void test_free_with_pending(void **state) /* test removing orpahed aio_group on free */ static void test_orphaned_aio_group(void **state) { - struct checker c[AIO_GROUP_SIZE] = {0}; + struct checker c[AIO_GROUP_SIZE] = {{.cls = NULL}}; struct aio_group *aio_grp, *tmp_grp; int i; @@ -533,7 +533,7 @@ static void test_orphaned_aio_group(void **state) * checker */ static void test_timeout_cancel_failed(void **state) { - struct checker c[2] = {0}; + struct checker c[2] = {{.cls = NULL}}; struct aio_group *aio_grp; struct async_req *reqs[2]; int res[] = {0,0}; @@ -568,7 +568,7 @@ static void test_timeout_cancel_failed(void **state) * checker */ static void test_async_timeout_cancel_failed(void **state) { - struct checker c[2] = {0}; + struct checker c[2] = {{.cls = NULL}}; struct async_req *reqs[2]; int res[] = {0,0}; int i; @@ -610,7 +610,7 @@ static void test_async_timeout_cancel_failed(void **state) /* test orphaning a request, and having another checker clean it up */ static void test_orphan_checker_cleanup(void **state) { - struct checker c[2] = {0}; + struct checker c[2] = {{.cls = NULL}}; struct async_req *reqs[2]; int res[] = {0,0}; struct aio_group *aio_grp; @@ -667,7 +667,7 @@ static void test_orphan_reset_cleanup(void **state) static void test_check_state_blksize(void **state) { int i; - struct checker c[3] = {0}; + struct checker c[3] = {{.cls = NULL}}; int blksize[] = {4096, 1024, 512}; struct async_req *reqs[3]; int res[] = {0,1,0}; @@ -698,7 +698,7 @@ static void test_check_state_blksize(void **state) static void test_check_state_async(void **state) { int i; - struct checker c[257] = {0}; + struct checker c[257] = {{.cls = NULL}}; struct async_req *reqs[257]; int res[257] = {0}; diff --git a/tests/hwtable.c b/tests/hwtable.c index 473028b..12660da 100644 --- a/tests/hwtable.c +++ b/tests/hwtable.c @@ -25,6 +25,7 @@ #include "test-lib.h" #include "print.h" #include "util.h" +#include "foreign.h" #define N_CONF_FILES 2 @@ -187,6 +188,9 @@ static int teardown(void **state) free_hwt(*state); *state = NULL; + cleanup_prio(); + cleanup_checkers(); + cleanup_foreign(); return 0; } @@ -468,6 +472,7 @@ static void replicate_config(const struct hwt_state *hwt, bool local) /* "local" configuration */ hwtable = get_used_hwes(hwt->vecs->pathvec); cfg1 = snprint_config(conf, NULL, hwtable, hwt->vecs->mpvec); + vector_free(hwtable); } assert_non_null(cfg1); diff --git a/tests/parser.c b/tests/parser.c index 29859da..5772391 100644 --- a/tests/parser.c +++ b/tests/parser.c @@ -440,6 +440,46 @@ static void test18(void **state) free_strvec(v); } +static void test19(void **state) +{ +#define QUOTED19 "!value" + vector v = alloc_strvec("key \"" QUOTED19 "\""); + char *val; + + assert_int_equal(VECTOR_SIZE(v), 4); + assert_string_equal(VECTOR_SLOT(v, 0), "key"); + assert_true(is_quote(VECTOR_SLOT(v, 1))); + assert_string_equal(VECTOR_SLOT(v, 2), QUOTED19); + assert_true(is_quote(VECTOR_SLOT(v, 3))); + assert_int_equal(validate_config_strvec(v, test_file), 0); + + val = set_value(v); + assert_string_equal(val, QUOTED19); + + free(val); + free_strvec(v); +} + +static void test20(void **state) +{ +#define QUOTED20 "#value" + vector v = alloc_strvec("key \"" QUOTED20 "\""); + char *val; + + assert_int_equal(VECTOR_SIZE(v), 4); + assert_string_equal(VECTOR_SLOT(v, 0), "key"); + assert_true(is_quote(VECTOR_SLOT(v, 1))); + assert_string_equal(VECTOR_SLOT(v, 2), QUOTED20); + assert_true(is_quote(VECTOR_SLOT(v, 3))); + assert_int_equal(validate_config_strvec(v, test_file), 0); + + val = set_value(v); + assert_string_equal(val, QUOTED20); + + free(val); + free_strvec(v); +} + int test_config_parser(void) { const struct CMUnitTest tests[] = { @@ -461,6 +501,8 @@ int test_config_parser(void) cmocka_unit_test(test16), cmocka_unit_test(test17), cmocka_unit_test(test18), + cmocka_unit_test(test19), + cmocka_unit_test(test20), }; return cmocka_run_group_tests(tests, setup, teardown); } diff --git a/tests/test-lib.c b/tests/test-lib.c index 5927516..b7c09cc 100644 --- a/tests/test-lib.c +++ b/tests/test-lib.c @@ -15,7 +15,7 @@ #include "test-lib.h" const int default_mask = (DI_SYSFS|DI_BLACKLIST|DI_WWID|DI_CHECKER|DI_PRIO); -const char default_devnode[] = "sdTEST"; +const char default_devnode[] = "sdxTEST"; const char default_wwid[] = "TEST-WWID"; /* default_wwid should be a substring of default_wwid_1! */ const char default_wwid_1[] = "TEST-WWID-1"; @@ -56,12 +56,6 @@ int __wrap_execute_program(char *path, char *value, int len) return 0; } -bool __wrap_is_claimed_by_foreign(struct udev_device *ud) -{ - condlog(5, "%s: %p", __func__, ud); - return false; -} - struct udev_list_entry *__wrap_udev_device_get_properties_list_entry(struct udev_device *ud) { diff --git a/tests/test-log.c b/tests/test-log.c index d685d58..1c901cb 100644 --- a/tests/test-log.c +++ b/tests/test-log.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "log.h" #include "test-log.h" @@ -11,17 +12,18 @@ void __wrap_dlog (int sink, int prio, const char * fmt, ...) { char buff[MAX_MSG_SIZE]; va_list ap; + char *expected; - assert_int_equal(prio, mock_type(int)); + check_expected(prio); va_start(ap, fmt); vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); va_end(ap); - assert_string_equal(buff, mock_ptr_type(char *)); + expected = mock_ptr_type(char *); + assert_memory_equal(buff, expected, strlen(expected)); } void expect_condlog(int prio, char *string) { - will_return(__wrap_dlog, prio); + expect_value(__wrap_dlog, prio, prio); will_return(__wrap_dlog, string); } - diff --git a/tests/uevent.c b/tests/uevent.c index f4afd9b..9ffcd2d 100644 --- a/tests/uevent.c +++ b/tests/uevent.c @@ -27,10 +27,6 @@ #include "globals.c" -/* Private prototypes missing in uevent.h */ -struct uevent * alloc_uevent(void); -void uevent_get_wwid(struct uevent *uev); - /* Stringify helpers */ #define _str_(x) #x #define str(x) _str_(x) diff --git a/tests/util.c b/tests/util.c index 7c486fc..c3c49b6 100644 --- a/tests/util.c +++ b/tests/util.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "util.h" #include "globals.c" @@ -141,61 +142,109 @@ static void test_basenamecpy_bad5(void **state) assert_int_equal(basenamecpy("baz/qux", NULL, sizeof(dst)), 0); } +static int test_basenamecpy(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_basenamecpy_good0), + cmocka_unit_test(test_basenamecpy_good1), + cmocka_unit_test(test_basenamecpy_good2), + cmocka_unit_test(test_basenamecpy_good3), + cmocka_unit_test(test_basenamecpy_good4), + cmocka_unit_test(test_basenamecpy_good5), + cmocka_unit_test(test_basenamecpy_good6), + cmocka_unit_test(test_basenamecpy_good7), + cmocka_unit_test(test_basenamecpy_bad0), + cmocka_unit_test(test_basenamecpy_bad1), + cmocka_unit_test(test_basenamecpy_bad2), + cmocka_unit_test(test_basenamecpy_bad3), + cmocka_unit_test(test_basenamecpy_bad4), + cmocka_unit_test(test_basenamecpy_bad5), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +/* + * On big endian systems, if bitfield_t is 32bit, we need + * to swap the two 32 bit parts of a 64bit value to make + * the tests below work. + */ +static uint64_t maybe_swap(uint64_t v) +{ + uint32_t *s = (uint32_t *)&v; + + if (sizeof(bitfield_t) == 4) + /* this is identity for little endian */ + return ((uint64_t)s[1] << 32) | s[0]; + else + return v; +} + static void test_bitmask_1(void **state) { - uint64_t arr[BITARR_SZ]; + struct bitfield *bf; + uint64_t *arr; int i, j, k, m, b; - memset(arr, 0, sizeof(arr)); + bf = alloc_bitfield(BITARR_SZ * 64); + assert_non_null(bf); + assert_int_equal(bf->len, BITARR_SZ * 64); + arr = (uint64_t *)bf->bits; for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(!is_bit_set_in_array(b, arr)); - set_bit_in_array(b, arr); + assert(!is_bit_set_in_bitfield(b, bf)); + set_bit_in_bitfield(b, bf); for (k = 0; k < BITARR_SZ; k++) { +#if 0 printf("b = %d j = %d k = %d a = %"PRIx64"\n", b, j, k, arr[k]); +#endif if (k == j) - assert_int_equal(arr[j], 1ULL << i); + assert_int_equal(maybe_swap(arr[j]), 1ULL << i); else assert_int_equal(arr[k], 0ULL); } for (m = 0; m < 64; m++) if (i == m) - assert(is_bit_set_in_array(64 * j + m, - arr)); + assert(is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(!is_bit_set_in_array(64 * j + m, - arr)); - clear_bit_in_array(b, arr); - assert(!is_bit_set_in_array(b, arr)); + assert(!is_bit_set_in_bitfield(64 * j + m, + bf)); + clear_bit_in_bitfield(b, bf); + assert(!is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) assert_int_equal(arr[k], 0ULL); } } + free(bf); } static void test_bitmask_2(void **state) { - uint64_t arr[BITARR_SZ]; + struct bitfield *bf; + uint64_t *arr; int i, j, k, m, b; - memset(arr, 0, sizeof(arr)); + bf = alloc_bitfield(BITARR_SZ * 64); + assert_non_null(bf); + assert_int_equal(bf->len, BITARR_SZ * 64); + arr = (uint64_t *)bf->bits; for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(!is_bit_set_in_array(b, arr)); - set_bit_in_array(b, arr); + assert(!is_bit_set_in_bitfield(b, bf)); + set_bit_in_bitfield(b, bf); for (m = 0; m < 64; m++) if (m <= i) - assert(is_bit_set_in_array(64 * j + m, - arr)); + assert(is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(!is_bit_set_in_array(64 * j + m, - arr)); - assert(is_bit_set_in_array(b, arr)); + assert(!is_bit_set_in_bitfield(64 * j + m, + bf)); + assert(is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) { if (k < j || (k == j && i == 63)) assert_int_equal(arr[k], ~0ULL); @@ -203,7 +252,7 @@ static void test_bitmask_2(void **state) assert_int_equal(arr[k], 0ULL); else assert_int_equal( - arr[k], + maybe_swap(arr[k]), (1ULL << (i + 1)) - 1); } } @@ -211,16 +260,16 @@ static void test_bitmask_2(void **state) for (j = 0; j < BITARR_SZ; j++) { for (i = 0; i < 64; i++) { b = 64 * j + i; - assert(is_bit_set_in_array(b, arr)); - clear_bit_in_array(b, arr); + assert(is_bit_set_in_bitfield(b, bf)); + clear_bit_in_bitfield(b, bf); for (m = 0; m < 64; m++) if (m <= i) - assert(!is_bit_set_in_array(64 * j + m, - arr)); + assert(!is_bit_set_in_bitfield(64 * j + m, + bf)); else - assert(is_bit_set_in_array(64 * j + m, - arr)); - assert(!is_bit_set_in_array(b, arr)); + assert(is_bit_set_in_bitfield(64 * j + m, + bf)); + assert(!is_bit_set_in_bitfield(b, bf)); for (k = 0; k < BITARR_SZ; k++) { if (k < j || (k == j && i == 63)) assert_int_equal(arr[k], 0ULL); @@ -228,78 +277,315 @@ static void test_bitmask_2(void **state) assert_int_equal(arr[k], ~0ULL); else assert_int_equal( - arr[k], + maybe_swap(arr[k]), ~((1ULL << (i + 1)) - 1)); } } } + free(bf); +} + +/* + * Test operations on a 0-length bitfield + */ +static void test_bitmask_len_0(void **state) +{ + struct bitfield *bf; + + bf = alloc_bitfield(0); + assert_null(bf); +} + +/* + * We use uint32_t in the "small bitmask" tests below. + * This means that we may have to swap 32bit words if bitfield_t + * is 64bit wide. + */ +static unsigned int maybe_swap_idx(unsigned int i) +{ + if (BYTE_ORDER == LITTLE_ENDIAN || sizeof(bitfield_t) == 4) + return i; + else + /* 0<->1, 2<->3, ... */ + return i + (i % 2 == 0 ? 1 : -1); +} + +static void _test_bitmask_small(unsigned int n) +{ + struct bitfield *bf; + uint32_t *arr; + unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; + + assert(sizeof(bitfield_t) == 4 || sizeof(bitfield_t) == 8); + assert(n <= 64); + assert(n >= 1); + + bf = alloc_bitfield(n); + assert_non_null(bf); + assert_int_equal(bf->len, n); + arr = (uint32_t *)bf->bits; + + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n + 1, bf); + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n, bf); + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n - 1, bf); + for (i = 0; i < size; i++) { + unsigned int k = (n - 1) / 32; + unsigned int j = (n - 1) - k * 32; + unsigned int i1 = maybe_swap_idx(i); + + if (i == k) + assert_int_equal(arr[i1], 1UL << j); + else + assert_int_equal(arr[i1], 0); + } + + clear_bit_in_bitfield(n - 1, bf); + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(0, bf); + assert_int_equal(arr[maybe_swap_idx(0)], 1); + for (i = 1; i < size; i++) + assert_int_equal(arr[maybe_swap_idx(i)], 0); + + free(bf); } -int test_basenamecpy(void) +static void _test_bitmask_small_2(unsigned int n) +{ + struct bitfield *bf; + uint32_t *arr; + unsigned int size = maybe_swap_idx((n - 1) / 32) + 1, i; + + assert(n <= 128); + assert(n >= 65); + + bf = alloc_bitfield(n); + assert_non_null(bf); + assert_int_equal(bf->len, n); + arr = (uint32_t *)bf->bits; + + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n + 1, bf); + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n, bf); + for (i = 0; i < size; i++) + assert_int_equal(arr[i], 0); + + set_bit_in_bitfield(n - 1, bf); + assert_int_equal(arr[0], 0); + for (i = 0; i < size; i++) { + unsigned int k = (n - 1) / 32; + unsigned int j = (n - 1) - k * 32; + unsigned int i1 = maybe_swap_idx(i); + + if (i == k) + assert_int_equal(arr[i1], 1UL << j); + else + assert_int_equal(arr[i1], 0); + } + + set_bit_in_bitfield(0, bf); + for (i = 0; i < size; i++) { + unsigned int k = (n - 1) / 32; + unsigned int j = (n - 1) - k * 32; + unsigned int i1 = maybe_swap_idx(i); + + if (i == k && k == 0) + assert_int_equal(arr[i1], (1UL << j) | 1); + else if (i == k) + assert_int_equal(arr[i1], 1UL << j); + else if (i == 0) + assert_int_equal(arr[i1], 1); + else + assert_int_equal(arr[i1], 0); + } + + set_bit_in_bitfield(64, bf); + for (i = 0; i < size; i++) { + unsigned int k = (n - 1) / 32; + unsigned int j = (n - 1) - k * 32; + unsigned int i1 = maybe_swap_idx(i); + + if (i == k && (k == 0 || k == 2)) + assert_int_equal(arr[i1], (1UL << j) | 1); + else if (i == k) + assert_int_equal(arr[i1], 1UL << j); + else if (i == 2 || i == 0) + assert_int_equal(arr[i1], 1); + else + assert_int_equal(arr[i1], 0); + } + + clear_bit_in_bitfield(0, bf); + for (i = 0; i < size; i++) { + unsigned int k = (n - 1) / 32; + unsigned int j = (n - 1) - k * 32; + unsigned int i1 = maybe_swap_idx(i); + + if (i == k && k == 2) + assert_int_equal(arr[i1], (1UL << j) | 1); + else if (i == k) + assert_int_equal(arr[i1], 1UL << j); + else if (i == 2) + assert_int_equal(arr[i1], 1); + else + assert_int_equal(arr[i1], 0); + } + + free(bf); +} + +static void test_bitmask_len_1(void **state) +{ + _test_bitmask_small(1); +} + +static void test_bitmask_len_2(void **state) +{ + _test_bitmask_small(2); +} + +static void test_bitmask_len_3(void **state) +{ + _test_bitmask_small(3); +} + +static void test_bitmask_len_23(void **state) +{ + _test_bitmask_small(23); +} + +static void test_bitmask_len_63(void **state) +{ + _test_bitmask_small(63); +} + +static void test_bitmask_len_64(void **state) +{ + _test_bitmask_small(63); +} + +static void test_bitmask_len_65(void **state) +{ + _test_bitmask_small_2(65); +} + +static void test_bitmask_len_66(void **state) +{ + _test_bitmask_small_2(66); +} + +static void test_bitmask_len_67(void **state) +{ + _test_bitmask_small_2(67); +} + +static void test_bitmask_len_103(void **state) +{ + _test_bitmask_small_2(103); +} + +static void test_bitmask_len_126(void **state) +{ + _test_bitmask_small_2(126); +} + +static void test_bitmask_len_127(void **state) +{ + _test_bitmask_small_2(127); +} + +static void test_bitmask_len_128(void **state) +{ + _test_bitmask_small_2(128); +} + + +static int test_bitmasks(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_basenamecpy_good0), - cmocka_unit_test(test_basenamecpy_good1), - cmocka_unit_test(test_basenamecpy_good2), - cmocka_unit_test(test_basenamecpy_good3), - cmocka_unit_test(test_basenamecpy_good4), - cmocka_unit_test(test_basenamecpy_good5), - cmocka_unit_test(test_basenamecpy_good6), - cmocka_unit_test(test_basenamecpy_good7), - cmocka_unit_test(test_basenamecpy_bad0), - cmocka_unit_test(test_basenamecpy_bad1), - cmocka_unit_test(test_basenamecpy_bad2), - cmocka_unit_test(test_basenamecpy_bad3), - cmocka_unit_test(test_basenamecpy_bad4), - cmocka_unit_test(test_basenamecpy_bad5), cmocka_unit_test(test_bitmask_1), cmocka_unit_test(test_bitmask_2), + cmocka_unit_test(test_bitmask_len_0), + cmocka_unit_test(test_bitmask_len_1), + cmocka_unit_test(test_bitmask_len_2), + cmocka_unit_test(test_bitmask_len_3), + cmocka_unit_test(test_bitmask_len_23), + cmocka_unit_test(test_bitmask_len_63), + cmocka_unit_test(test_bitmask_len_64), + cmocka_unit_test(test_bitmask_len_65), + cmocka_unit_test(test_bitmask_len_66), + cmocka_unit_test(test_bitmask_len_67), + cmocka_unit_test(test_bitmask_len_103), + cmocka_unit_test(test_bitmask_len_126), + cmocka_unit_test(test_bitmask_len_127), + cmocka_unit_test(test_bitmask_len_128), }; return cmocka_run_group_tests(tests, NULL, NULL); } -static const char src_str[] = "Hello"; +#define DST_STR "Hello" +static const char dst_str[] = DST_STR; +/* length of src_str and dst_str should be different */ +static const char src_str[] = " World"; +/* Must be big enough to hold dst_str and src_str */ +#define ARRSZ 16 +#define FILL '@' /* strlcpy with length 0 */ static void test_strlcpy_0(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 0); assert_int_equal(rc, strlen(src_str)); - assert_string_equal(tst, "word"); + assert_string_equal(tst, dst_str); } /* strlcpy with length 1 */ static void test_strlcpy_1(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 1); assert_int_equal(rc, strlen(src_str)); assert_int_equal(tst[0], '\0'); - assert_string_equal(tst + 1, "ord"); + assert_string_equal(tst + 1, dst_str + 1); } /* strlcpy with length 2 */ static void test_strlcpy_2(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, 2); assert_int_equal(rc, strlen(src_str)); assert_int_equal(tst[0], src_str[0]); assert_int_equal(tst[1], '\0'); - assert_string_equal(tst + 2, "rd"); + assert_string_equal(tst + 2, dst_str + 2); } /* strlcpy with dst length < src length */ static void test_strlcpy_3(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; int rc; rc = strlcpy(tst, src_str, sizeof(tst)); @@ -331,6 +617,7 @@ static void test_strlcpy_5(void **state) const int sz = sizeof(src_str); tst = malloc(sz); + assert_non_null(tst); memset(tst, 'f', sizeof(src_str)); rc = strlcpy(tst, src_str, sz); @@ -348,6 +635,7 @@ static void test_strlcpy_6(void **state) const int sz = sizeof(src_str); tst = malloc(sz + 2); + assert_non_null(tst); memset(tst, 'f', sz + 2); rc = strlcpy(tst, src_str, sz + 2); @@ -362,26 +650,26 @@ static void test_strlcpy_6(void **state) /* strlcpy with empty src */ static void test_strlcpy_7(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; static const char empty[] = ""; int rc; rc = strlcpy(tst, empty, sizeof(tst)); assert_int_equal(rc, strlen(empty)); assert_string_equal(empty, tst); - assert_string_equal(tst + 1, "ord"); + assert_string_equal(tst + 1, dst_str + 1); } /* strlcpy with empty src, length 0 */ static void test_strlcpy_8(void **state) { - char tst[] = "word"; + char tst[] = DST_STR; static const char empty[] = ""; int rc; rc = strlcpy(tst, empty, 0); assert_int_equal(rc, strlen(empty)); - assert_string_equal("word", tst); + assert_string_equal(dst_str, tst); } static int test_strlcpy(void) @@ -401,11 +689,267 @@ static int test_strlcpy(void) return cmocka_run_group_tests(tests, NULL, NULL); } + +/* 0-terminated string, filled with non-0 after the terminator */ +static void prep_buf(char *buf, size_t size, const char *word) +{ + memset(buf, FILL, size); + assert_in_range(strlen(word), 0, size - 1); + memcpy(buf, word, strlen(word) + 1); +} + +/* strlcat with size 0, dst not 0-terminated */ +static void test_strlcat_0(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, 0); + assert_int_equal(rc, strlen(src_str)); + assert_string_equal(tst, dst_str); + assert_int_equal(tst[sizeof(dst_str)], FILL); +} + +/* strlcat with length 1, dst not 0-terminated */ +static void test_strlcat_1(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, 1); + assert_int_equal(rc, 1 + strlen(src_str)); + assert_string_equal(tst, dst_str); + assert_int_equal(tst[sizeof(dst_str)], FILL); +} + +/* strlcat with length = dst - 1 */ +static void test_strlcat_2(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, strlen(dst_str)); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_string_equal(tst, dst_str); + assert_int_equal(tst[sizeof(dst_str)], FILL); +} + +/* strlcat with length = dst */ +static void test_strlcat_3(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, strlen(dst_str) + 1); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_string_equal(tst, dst_str); + assert_int_equal(tst[sizeof(dst_str)], FILL); +} + +/* strlcat with len = dst + 1 */ +static void test_strlcat_4(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, strlen(dst_str) + 2); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_false(strncmp(tst, dst_str, strlen(dst_str))); + assert_int_equal(tst[strlen(dst_str)], src_str[0]); + assert_int_equal(tst[strlen(dst_str) + 1], '\0'); + assert_int_equal(tst[strlen(dst_str) + 2], FILL); +} + +/* strlcat with len = needed - 1 */ +static void test_strlcat_5(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, strlen(dst_str) + strlen(src_str)); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_false(strncmp(tst, dst_str, strlen(dst_str))); + assert_false(strncmp(tst + strlen(dst_str), src_str, + strlen(src_str) - 1)); + assert_int_equal(tst[strlen(dst_str) + strlen(src_str) - 1], '\0'); + assert_int_equal(tst[strlen(dst_str) + strlen(src_str)], FILL); +} + +/* strlcat with exactly sufficient space */ +static void test_strlcat_6(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, strlen(dst_str) + strlen(src_str) + 1); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_false(strncmp(tst, dst_str, strlen(dst_str))); + assert_string_equal(tst + strlen(dst_str), src_str); + assert_int_equal(tst[strlen(dst_str) + strlen(src_str) + 1], FILL); +} + +/* strlcat with sufficient space */ +static void test_strlcat_7(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, src_str, sizeof(tst)); + assert_int_equal(rc, strlen(src_str) + strlen(dst_str)); + assert_false(strncmp(tst, dst_str, strlen(dst_str))); + assert_string_equal(tst + strlen(dst_str), src_str); +} + +/* strlcat with 0-length string */ +static void test_strlcat_8(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), dst_str); + rc = strlcat(tst, "", sizeof(tst)); + assert_int_equal(rc, strlen(dst_str)); + assert_string_equal(tst, dst_str); + assert_int_equal(tst[sizeof(dst_str)], FILL); +} + +/* strlcat with empty dst */ +static void test_strlcat_9(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), ""); + rc = strlcat(tst, src_str, ARRSZ); + assert_int_equal(rc, strlen(src_str)); + assert_string_equal(tst, src_str); + assert_int_equal(tst[sizeof(src_str)], FILL); +} + +/* strlcat with empty dst and src */ +static void test_strlcat_10(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), ""); + rc = strlcat(tst, "", ARRSZ); + assert_int_equal(rc, 0); + assert_string_equal(tst, ""); + assert_int_equal(tst[1], FILL); +} + +/* strlcat with no space to store 0 */ +static void test_strlcat_11(void **state) +{ + char tst[ARRSZ]; + int rc; + + prep_buf(tst, sizeof(tst), ""); + tst[0] = FILL; + rc = strlcat(tst, src_str, 0); + assert_int_equal(rc, strlen(src_str)); + assert_int_equal(tst[0], FILL); +} + +static int test_strlcat(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_strlcat_0), + cmocka_unit_test(test_strlcat_1), + cmocka_unit_test(test_strlcat_2), + cmocka_unit_test(test_strlcat_3), + cmocka_unit_test(test_strlcat_4), + cmocka_unit_test(test_strlcat_5), + cmocka_unit_test(test_strlcat_6), + cmocka_unit_test(test_strlcat_7), + cmocka_unit_test(test_strlcat_8), + cmocka_unit_test(test_strlcat_9), + cmocka_unit_test(test_strlcat_10), + cmocka_unit_test(test_strlcat_11), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void test_strchop_nochop(void **state) +{ + char hello[] = "hello"; + + assert_int_equal(strchop(hello), 5); + assert_string_equal(hello, "hello"); +} + +static void test_strchop_newline(void **state) +{ + char hello[] = "hello\n"; + + assert_int_equal(strchop(hello), 5); + assert_string_equal(hello, "hello"); +} + +static void test_strchop_space(void **state) +{ + char hello[] = " ello "; + + assert_int_equal(strchop(hello), 5); + assert_string_equal(hello, " ello"); +} + +static void test_strchop_mix(void **state) +{ + char hello[] = " el\no \t \n\n \t \n"; + + assert_int_equal(strchop(hello), 5); + assert_string_equal(hello, " el\no"); +} + +static void test_strchop_blank(void **state) +{ + char hello[] = " \t \n\n \t \n"; + + assert_int_equal(strchop(hello), 0); + assert_string_equal(hello, ""); +} + +static void test_strchop_empty(void **state) +{ + char hello[] = ""; + + assert_int_equal(strchop(hello), 0); + assert_string_equal(hello, ""); +} + +static int test_strchop(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_strchop_nochop), + cmocka_unit_test(test_strchop_newline), + cmocka_unit_test(test_strchop_space), + cmocka_unit_test(test_strchop_mix), + cmocka_unit_test(test_strchop_blank), + cmocka_unit_test(test_strchop_empty), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + int main(void) { int ret = 0; ret += test_basenamecpy(); + ret += test_bitmasks(); ret += test_strlcpy(); + ret += test_strlcat(); + ret += test_strchop(); return ret; } diff --git a/tests/valid.c b/tests/valid.c new file mode 100644 index 0000000..693c72c --- /dev/null +++ b/tests/valid.c @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2020 Benjamin Marzinski, Redhat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "globals.c" +#include "util.h" +#include "discovery.h" +#include "wwids.h" +#include "blacklist.h" +#include "valid.h" + +int test_fd; +struct udev_device { + int unused; +} test_udev; + +bool __wrap_sysfs_is_multipathed(struct path *pp, bool set_wwid) +{ + bool is_multipathed = mock_type(bool); + assert_non_null(pp); + assert_int_not_equal(strlen(pp->dev), 0); + if (is_multipathed && set_wwid) + strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); + return is_multipathed; +} + +int __wrap___mpath_connect(int nonblocking) +{ + bool connected = mock_type(bool); + assert_int_equal(nonblocking, 1); + if (connected) + return test_fd; + errno = mock_type(int); + return -1; +} + +int __wrap_systemd_service_enabled(const char *dev) +{ + return (int)mock_type(bool); +} + +/* There's no point in checking the return value here */ +int __wrap_mpath_disconnect(int fd) +{ + assert_int_equal(fd, test_fd); + return 0; +} + +struct udev_device *__wrap_udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) +{ + bool passed = mock_type(bool); + assert_string_equal(sysname, mock_ptr_type(char *)); + if (passed) + return &test_udev; + return NULL; +} + +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) { + pp->uid_attribute = "ID_TEST"; + strlcpy(pp->wwid, mock_ptr_type(char *), WWID_SIZE); + } else + memset(pp->wwid, 0, WWID_SIZE); + return ret; +} + +int __wrap_filter_property(struct config *conf, struct udev_device *udev, + int lvl, const char *uid_attribute) +{ + int ret = mock_type(int); + assert_string_equal(uid_attribute, "ID_TEST"); + return ret; +} + +int __wrap_is_failed_wwid(const char *wwid) +{ + int ret = mock_type(int); + assert_string_equal(wwid, mock_ptr_type(char *)); + return ret; +} + +int __wrap_check_wwids_file(char *wwid, int write_wwid) +{ + bool passed = mock_type(bool); + assert_int_equal(write_wwid, 0); + assert_string_equal(wwid, mock_ptr_type(char *)); + if (passed) + return 0; + else + return -1; +} + +int __wrap_dm_map_present_by_uuid(const char *uuid) +{ + int ret = mock_type(int); + assert_string_equal(uuid, mock_ptr_type(char *)); + return ret; +} + +enum { + STAGE_IS_MULTIPATHED, + STAGE_CHECK_MULTIPATHD, + STAGE_GET_UDEV_DEVICE, + STAGE_PATHINFO, + STAGE_FILTER_PROPERTY, + STAGE_IS_FAILED, + STAGE_CHECK_WWIDS, + STAGE_UUID_PRESENT, +}; + +enum { + CHECK_MPATHD_RUNNING, + CHECK_MPATHD_EAGAIN, + CHECK_MPATHD_ENABLED, + CHECK_MPATHD_SKIP, +}; + +/* setup the test to continue past the given stage in is_path_valid() */ +static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, + unsigned int stage) +{ + will_return(__wrap_sysfs_is_multipathed, false); + if (stage == STAGE_IS_MULTIPATHED) + return; + if (check_multipathd == CHECK_MPATHD_RUNNING) + will_return(__wrap___mpath_connect, true); + else if (check_multipathd == CHECK_MPATHD_EAGAIN) { + will_return(__wrap___mpath_connect, false); + will_return(__wrap___mpath_connect, EAGAIN); + } else if (check_multipathd == CHECK_MPATHD_ENABLED) { + will_return(__wrap___mpath_connect, false); + will_return(__wrap___mpath_connect, ECONNREFUSED); + will_return(__wrap_systemd_service_enabled, true); + } + /* nothing for CHECK_MPATHD_SKIP */ + if (stage == STAGE_CHECK_MULTIPATHD) + return; + will_return(__wrap_udev_device_new_from_subsystem_sysname, true); + will_return(__wrap_udev_device_new_from_subsystem_sysname, + name); + if (stage == STAGE_GET_UDEV_DEVICE) + 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); + will_return(__wrap_is_failed_wwid, wwid); + if (stage == STAGE_IS_FAILED) + return; + will_return(__wrap_check_wwids_file, false); + will_return(__wrap_check_wwids_file, wwid); + if (stage == STAGE_CHECK_WWIDS) + return; + will_return(__wrap_dm_map_present_by_uuid, 0); + will_return(__wrap_dm_map_present_by_uuid, wwid); +} + +static void test_bad_arguments(void **state) +{ + struct path pp; + char too_long[FILE_NAME_SIZE + 1]; + + memset(&pp, 0, sizeof(pp)); + /* test NULL pointers */ + assert_int_equal(is_path_valid("test", &conf, NULL, true), + PATH_IS_ERROR); + assert_int_equal(is_path_valid("test", NULL, &pp, true), + PATH_IS_ERROR); + assert_int_equal(is_path_valid(NULL, &conf, &pp, true), + PATH_IS_ERROR); + /* test undefined find_multipaths */ + conf.find_multipaths = FIND_MULTIPATHS_UNDEF; + assert_int_equal(is_path_valid("test", &conf, &pp, true), + PATH_IS_ERROR); + /* test name too long */ + memset(too_long, 'x', sizeof(too_long)); + too_long[sizeof(too_long) - 1] = '\0'; + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + assert_int_equal(is_path_valid(too_long, &conf, &pp, true), + PATH_IS_ERROR); +} + +static void test_sysfs_is_multipathed(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test_wwid"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + /* test for already existing multiapthed device */ + will_return(__wrap_sysfs_is_multipathed, true); + will_return(__wrap_sysfs_is_multipathed, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_VALID_NO_CHECK); + assert_string_equal(pp.dev, name); + assert_string_equal(pp.wwid, wwid); + /* test for wwid device with empty wwid */ + will_return(__wrap_sysfs_is_multipathed, true); + will_return(__wrap_sysfs_is_multipathed, ""); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_ERROR); +} + +static void test_check_multipathd(void **state) +{ + struct path pp; + char *name = "test"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + /* test failed check to see if multipathd is active */ + will_return(__wrap_sysfs_is_multipathed, false); + will_return(__wrap___mpath_connect, false); + will_return(__wrap___mpath_connect, ECONNREFUSED); + will_return(__wrap_systemd_service_enabled, false); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_NOT_VALID); + assert_string_equal(pp.dev, name); + /* test pass because service is enabled. fail getting udev */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, NULL, CHECK_MPATHD_ENABLED, STAGE_CHECK_MULTIPATHD); + will_return(__wrap_udev_device_new_from_subsystem_sysname, false); + will_return(__wrap_udev_device_new_from_subsystem_sysname, + name); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_ERROR); + assert_string_equal(pp.dev, name); + /* test pass because connect returned EAGAIN. fail getting udev */ + setup_passing(name, NULL, CHECK_MPATHD_EAGAIN, STAGE_CHECK_MULTIPATHD); + will_return(__wrap_udev_device_new_from_subsystem_sysname, false); + will_return(__wrap_udev_device_new_from_subsystem_sysname, + name); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_ERROR); + /* test pass because connect succeeded. fail getting udev */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, NULL, CHECK_MPATHD_RUNNING, STAGE_CHECK_MULTIPATHD); + will_return(__wrap_udev_device_new_from_subsystem_sysname, false); + will_return(__wrap_udev_device_new_from_subsystem_sysname, + name); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_ERROR); + assert_string_equal(pp.dev, name); +} + +static void test_pathinfo(void **state) +{ + struct path pp; + char *name = "test"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + /* Test pathinfo blacklisting device */ + setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); + will_return(__wrap_pathinfo, PATHINFO_SKIPPED); + will_return(__wrap_pathinfo, name); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + /* Test pathinfo failing */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); + will_return(__wrap_pathinfo, PATHINFO_FAILED); + will_return(__wrap_pathinfo, name); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_ERROR); + /* Test blank wwid */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, NULL, CHECK_MPATHD_SKIP, STAGE_GET_UDEV_DEVICE); + will_return(__wrap_pathinfo, PATHINFO_OK); + will_return(__wrap_pathinfo, name); + will_return(__wrap_pathinfo, ""); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); +} + +static void test_filter_property(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + /* test blacklist property */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO); + 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); + 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 */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_PATHINFO); + will_return(__wrap_filter_property, MATCH_NOTHING); + will_return(__wrap_is_failed_wwid, WWID_IS_FAILED); + will_return(__wrap_is_failed_wwid, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); +} + +static void test_is_failed_wwid(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + /* Test wwid failed */ + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_FILTER_PROPERTY); + will_return(__wrap_is_failed_wwid, WWID_IS_FAILED); + will_return(__wrap_is_failed_wwid, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); + /* test is_failed_wwid error */ + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_FILTER_PROPERTY); + will_return(__wrap_is_failed_wwid, WWID_FAILED_ERROR); + will_return(__wrap_is_failed_wwid, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_ERROR); +} + +static void test_greedy(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + /* test greedy success with checking multipathd */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_GREEDY; + setup_passing(name, wwid, CHECK_MPATHD_RUNNING, STAGE_IS_FAILED); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); + /* test greedy success without checking multiapthd */ + memset(&pp, 0, sizeof(pp)); + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_IS_FAILED); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_VALID); +} + +static void test_check_wwids(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + setup_passing(name, wwid, CHECK_MPATHD_EAGAIN, STAGE_IS_FAILED); + will_return(__wrap_check_wwids_file, true); + will_return(__wrap_check_wwids_file, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_VALID_NO_CHECK); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); +} + +static void test_check_uuid_present(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + setup_passing(name, wwid, CHECK_MPATHD_ENABLED, STAGE_CHECK_WWIDS); + will_return(__wrap_dm_map_present_by_uuid, 1); + will_return(__wrap_dm_map_present_by_uuid, wwid); + assert_int_equal(is_path_valid(name, &conf, &pp, true), + PATH_IS_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); +} + + +static void test_find_multipaths(void **state) +{ + struct path pp; + char *name = "test"; + char *wwid = "test-wwid"; + + /* test find_multipaths = FIND_MULTIPATHS_STRICT */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_STRICT; + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); + /* test find_multipaths = FIND_MULTIPATHS_OFF */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_OFF; + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); + /* test find_multipaths = FIND_MULTIPATHS_ON */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_ON; + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_NOT_VALID); + /* test find_multipaths = FIND_MULTIPATHS_SMART */ + memset(&pp, 0, sizeof(pp)); + conf.find_multipaths = FIND_MULTIPATHS_SMART; + setup_passing(name, wwid, CHECK_MPATHD_SKIP, STAGE_UUID_PRESENT); + assert_int_equal(is_path_valid(name, &conf, &pp, false), + PATH_IS_MAYBE_VALID); + assert_string_equal(pp.dev, name); + assert_ptr_equal(pp.udev, &test_udev); + assert_string_equal(pp.wwid, wwid); +} + +int test_valid(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_bad_arguments), + cmocka_unit_test(test_sysfs_is_multipathed), + cmocka_unit_test(test_check_multipathd), + cmocka_unit_test(test_pathinfo), + cmocka_unit_test(test_filter_property), + cmocka_unit_test(test_is_failed_wwid), + cmocka_unit_test(test_greedy), + cmocka_unit_test(test_check_wwids), + cmocka_unit_test(test_check_uuid_present), + cmocka_unit_test(test_find_multipaths), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +int main(void) +{ + int ret = 0; + ret += test_valid(); + return ret; +} diff --git a/tests/vpd.c b/tests/vpd.c index 3cbad81..e2ec65e 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -28,13 +28,17 @@ struct vpdtest { char wwid[WWID_SIZE]; }; +static regex_t space_re; static int setup(void **state) { struct vpdtest *vt = malloc(sizeof(*vt)); + int rc; if (vt == NULL) return -1; *state = vt; + rc = regcomp(&space_re, " +", REG_EXTENDED); + assert_int_equal(rc, 0); return 0; } @@ -44,6 +48,7 @@ static int teardown(void **state) free(vt); *state = NULL; + regfree(&space_re); return 0; } @@ -360,21 +365,14 @@ static char *subst_spaces(const char *src) { char *dst = calloc(1, strlen(src) + 1); char *p; - static regex_t *re; regmatch_t match; - int rc; + int rc = 0; assert_non_null(dst); - if (re == NULL) { - re = calloc(1, sizeof(*re)); - assert_non_null(re); - rc = regcomp(re, " +", REG_EXTENDED); - assert_int_equal(rc, 0); - } - for (rc = regexec(re, src, 1, &match, 0), p = dst; + for (rc = regexec(&space_re, src, 1, &match, 0), p = dst; rc == 0; - src += match.rm_eo, rc = regexec(re, src, 1, &match, 0)) { + src += match.rm_eo, rc = regexec(&space_re, src, 1, &match, 0)) { memcpy(p, src, match.rm_so); p += match.rm_so; *p = '_'; -- 2.34.1