From e15d163ead9b0d93282a756add758e11bea75879 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.4 --- Makefile | 38 +- Makefile.inc | 15 +- kpartx/bsd.c | 4 +- kpartx/dasd.c | 9 +- kpartx/devmapper.c | 13 +- kpartx/devmapper.h | 7 +- kpartx/dos.c | 4 +- kpartx/gpt.c | 15 +- kpartx/gpt.h | 2 +- kpartx/kpartx.h | 20 +- kpartx/mac.c | 5 +- kpartx/ps3.c | 5 +- kpartx/solaris.c | 4 +- kpartx/sun.c | 4 +- kpartx/unixware.c | 4 +- libdmmp/libdmmp_private.h | 8 +- libmpathcmd/mpath_cmd.c | 5 +- libmpathpersist/mpath_persist.c | 5 +- libmpathpersist/mpath_pr_ioctl.c | 40 +- libmultipath/Makefile | 5 + libmultipath/alias.c | 134 ++++-- libmultipath/alias.h | 12 +- libmultipath/byteorder.h | 12 +- libmultipath/checkers.c | 17 +- libmultipath/checkers.h | 1 + libmultipath/checkers/cciss_tur.c | 4 +- libmultipath/checkers/directio.c | 298 +++++++++--- libmultipath/checkers/hp_sw.c | 8 +- libmultipath/checkers/rdac.c | 2 +- libmultipath/checkers/readsector0.c | 4 +- libmultipath/config.c | 59 ++- libmultipath/config.h | 7 +- libmultipath/configure.c | 5 +- libmultipath/defaults.h | 7 +- libmultipath/devmapper.c | 12 +- libmultipath/dict.c | 100 +++- libmultipath/discovery.c | 399 ++++++++++------ libmultipath/discovery.h | 4 +- libmultipath/dm-generic.c | 6 +- libmultipath/file.c | 5 +- libmultipath/foreign.c | 20 +- libmultipath/foreign/nvme.c | 26 +- libmultipath/generic.c | 2 +- libmultipath/hwtable.c | 8 +- libmultipath/io_err_stat.c | 14 +- libmultipath/log.h | 3 +- libmultipath/log_pthread.c | 2 +- libmultipath/log_pthread.h | 3 +- libmultipath/nvme/linux/nvme.h | 136 ++++-- libmultipath/nvme/nvme-ioctl.c | 229 +++++---- libmultipath/nvme/nvme-ioctl.h | 31 +- libmultipath/nvme/nvme.h | 121 ++++- libmultipath/parser.c | 2 +- libmultipath/pgpolicies.c | 2 +- libmultipath/print.c | 60 ++- libmultipath/print.h | 8 +- libmultipath/prio.c | 6 +- libmultipath/prioritizers/alua_rtpg.c | 2 +- libmultipath/prioritizers/ana.c | 14 +- libmultipath/prioritizers/const.c | 4 +- libmultipath/prioritizers/datacore.c | 3 +- libmultipath/prioritizers/emc.c | 3 +- libmultipath/prioritizers/hds.c | 3 +- libmultipath/prioritizers/hp_sw.c | 3 +- libmultipath/prioritizers/iet.c | 3 +- libmultipath/prioritizers/ontap.c | 3 +- libmultipath/prioritizers/random.c | 4 +- libmultipath/prioritizers/rdac.c | 3 +- libmultipath/prioritizers/sysfs.c | 3 +- libmultipath/prioritizers/weightedpath.c | 3 +- libmultipath/propsel.c | 40 +- libmultipath/propsel.h | 1 + libmultipath/structs.c | 26 +- libmultipath/structs.h | 25 +- libmultipath/structs_vec.c | 91 +++- libmultipath/structs_vec.h | 3 +- libmultipath/sysfs.c | 13 +- libmultipath/time-util.c | 6 +- libmultipath/uevent.c | 5 +- libmultipath/unaligned.h | 4 +- libmultipath/util.c | 7 +- libmultipath/util.h | 15 +- libmultipath/uxsock.c | 3 +- libmultipath/vector.h | 4 +- libmultipath/version.h | 4 +- libmultipath/wwids.c | 40 +- mpathpersist/main.c | 3 +- multipath/main.c | 14 +- multipath/multipath.conf.5 | 15 +- multipathd/Makefile | 3 + multipathd/cli_handlers.c | 41 +- multipathd/dmevents.c | 4 +- multipathd/main.c | 129 ++--- multipathd/pidfile.c | 2 +- multipathd/uxlsnr.c | 150 +++++- multipathd/waiter.c | 2 +- multipathd/waiter.h | 2 +- tests/Makefile | 43 +- tests/README.md | 72 +++ tests/alias.c | 744 +++++++++++++++++++++++++++++ tests/blacklist.c | 22 +- tests/directio.c | 776 +++++++++++++++++++++++++++++++ tests/hwtable.c | 2 +- tests/test-log.c | 27 ++ tests/test-log.h | 7 + tests/util.c | 16 +- tests/vpd.c | 88 ++-- 107 files changed, 3587 insertions(+), 894 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/alias.c create mode 100644 tests/directio.c create mode 100644 tests/test-log.c create mode 100644 tests/test-log.h diff --git a/Makefile b/Makefile index 4b145c5..1dee368 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # Copyright (C) 2003 Christophe Varoqui, # -BUILDDIRS = \ +BUILDDIRS := \ libmpathcmd \ libmultipath \ libmultipath/prioritizers \ @@ -19,32 +19,30 @@ BUILDDIRS += \ libdmmp endif -all: recurse +BUILDDIRS.clean := $(BUILDDIRS:=.clean) tests.clean -recurse: - @for dir in $(BUILDDIRS); do $(MAKE) -C $$dir || exit $?; done +.PHONY: $(BUILDDIRS) $(BUILDDIRS:=.uninstall) $(BUILDDIRS:=.install) $(BUILDDIRS.clean) -recurse_clean: - @for dir in $(BUILDDIRS); do \ - $(MAKE) -C $$dir clean || exit $?; \ - done - $(MAKE) -C tests clean +all: $(BUILDDIRS) -recurse_install: - @for dir in $(BUILDDIRS); do \ - $(MAKE) -C $$dir install || exit $?; \ - done +$(BUILDDIRS): + $(MAKE) -C $@ -recurse_uninstall: - @for dir in $(BUILDDIRS); do \ - $(MAKE) -C $$dir uninstall || exit $?; \ - done +multipath multipathd mpathpersist: libmultipath +mpathpersist: libmpathpersist -clean: recurse_clean +$(BUILDDIRS.clean): + $(MAKE) -C ${@:.clean=} clean -install: recurse_install +$(BUILDDIRS:=.install): + $(MAKE) -C ${@:.install=} install -uninstall: recurse_uninstall +$(BUILDDIRS:=.uninstall): + $(MAKE) -C ${@:.uninstall=} uninstall + +clean: $(BUILDDIRS.clean) +install: $(BUILDDIRS:=.install) +uninstall: $(BUILDDIRS:=.uninstall) test: all $(MAKE) -C tests diff --git a/Makefile.inc b/Makefile.inc index 56c3eda..d4d1e0d 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -81,7 +81,8 @@ INSTALL_PROGRAM = install # Test if the C compiler supports the option. # Evaluates to "option" if yes, and "fallback" otherwise. TEST_CC_OPTION = $(shell \ - if echo 'int main(void){return 0;}' | $(CC) -o /dev/null -c "$(1)" -xc - >/dev/null 2>&1; \ + if echo 'int main(void){return 0;}' | \ + $(CC) -o /dev/null -c -Werror "$(1)" -xc - >/dev/null 2>&1; \ then \ echo "$(1)"; \ else \ @@ -90,14 +91,14 @@ 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,) -OPTFLAGS = -O2 -g -pipe -Wall -Wextra -Wformat=2 -Werror=implicit-int \ +OPTFLAGS = -O2 -g -pipe -Werror -Wall -Wextra -Wformat=2 -Werror=implicit-int \ -Werror=implicit-function-declaration -Werror=format-security \ - -Wno-sign-compare -Wno-unused-parameter -Wno-clobbered \ + $(WNOCLOBBERED) \ -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) \ - -Wp,-D_FORTIFY_SOURCE=2 $(STACKPROT) \ - --param=ssp-buffer-size=4 - + $(STACKPROT) --param=ssp-buffer-size=4 +CPPFLAGS := -Wp,-D_FORTIFY_SOURCE=2 CFLAGS := $(OPTFLAGS) -DBIN_DIR=\"$(bindir)\" -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\" \ -MMD -MP $(CFLAGS) BIN_CFLAGS = -fPIE -DPIE @@ -134,4 +135,4 @@ check_file = $(shell \ %.o: %.c @echo building $@ because of $? - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< diff --git a/kpartx/bsd.c b/kpartx/bsd.c index f87175e..0e661fb 100644 --- a/kpartx/bsd.c +++ b/kpartx/bsd.c @@ -47,13 +47,13 @@ struct bsd_disklabel { }; int -read_bsd_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_bsd_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct bsd_disklabel *l; struct bsd_partition *p; unsigned int offset = all.start, end; int max_partitions; char *bp; - int n = 0, i, j; + unsigned int n = 0, i, j; bp = getblock(fd, offset+1); /* 1 sector suffices */ if (bp == NULL) diff --git a/kpartx/dasd.c b/kpartx/dasd.c index 4e7e474..14b9d3a 100644 --- a/kpartx/dasd.c +++ b/kpartx/dasd.c @@ -65,7 +65,8 @@ typedef unsigned int __attribute__((__may_alias__)) label_ints_t; /* */ int -read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns) +read_dasd_pt(int fd, __attribute__((unused)) struct slice all, + struct slice *sp, __attribute__((unused)) unsigned int ns) { int retval = -1; int blocksize; @@ -185,7 +186,7 @@ read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns) goto out; } - if ((!info.FBA_layout) && (!strcmp(info.type, "ECKD"))) + if ((!info.FBA_layout) && (!memcmp(info.type, "ECKD", 4))) memcpy (&vlabel, data, sizeof(vlabel)); else { bzero(&vlabel,4); @@ -215,7 +216,7 @@ read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns) sp[0].size = size - sp[0].start; retval = 1; } else if ((strncmp(type, "VOL1", 4) == 0) && - (!info.FBA_layout) && (!strcmp(info.type, "ECKD"))) { + (!info.FBA_layout) && (!memcmp(info.type, "ECKD",4))) { /* * New style VOL1 labeled disk */ @@ -264,7 +265,7 @@ read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns) if (vlabel.ldl_version == 0xf2) { fmt_size = sectors512(vlabel.formatted_blocks, blocksize); - } else if (!strcmp(info.type, "ECKD")) { + } else if (!memcmp(info.type, "ECKD",4)) { /* formatted w/o large volume support */ fmt_size = geo.cylinders * geo.heads * geo.sectors * (blocksize >> 9); diff --git a/kpartx/devmapper.c b/kpartx/devmapper.c index 3aa4988..86731ea 100644 --- a/kpartx/devmapper.c +++ b/kpartx/devmapper.c @@ -10,6 +10,7 @@ #include #include #include "devmapper.h" +#include "kpartx.h" #define _UUID_PREFIX "part" #define UUID_PREFIX _UUID_PREFIX "%d-" @@ -17,7 +18,7 @@ #define MAX_PREFIX_LEN (_UUID_PREFIX_LEN + 4) #define PARAMS_SIZE 1024 -int dm_prereq(char * str, int x, int y, int z) +int dm_prereq(char * str, uint32_t x, uint32_t y, uint32_t z) { int r = 1; struct dm_task *dmt; @@ -107,7 +108,7 @@ strip_slash (char * device) static int format_partname(char *buf, size_t bufsiz, const char *mapname, const char *delim, int part) { - if (snprintf(buf, bufsiz, "%s%s%d", mapname, delim, part) >= bufsiz) + if (safe_snprintf(buf, bufsiz, "%s%s%d", mapname, delim, part)) return 0; strip_slash(buf); return 1; @@ -358,7 +359,7 @@ out: } int -dm_devn (const char * mapname, int *major, int *minor) +dm_devn (const char * mapname, unsigned int *major, unsigned int *minor) { int r = 1; struct dm_task *dmt; @@ -527,7 +528,7 @@ do_foreach_partmaps (const char * mapname, const char *uuid, struct remove_data *rd = data; unsigned next = 0; char params[PARAMS_SIZE]; - int major, minor; + unsigned int major, minor; char dev_t[32]; int r = 1; int is_dmdev = 1; @@ -644,7 +645,7 @@ int dm_find_part(const char *parent, const char *delim, int part, char params[PARAMS_SIZE]; char *tmp; char *uuid; - int major, minor; + unsigned int major, minor; char dev_t[32]; if (!format_partname(name, namesiz, parent, delim, part)) { @@ -714,7 +715,7 @@ char *nondm_create_uuid(dev_t devt) return uuid_buf; } -int nondm_parse_uuid(const char *uuid, int *major, int *minor) +int nondm_parse_uuid(const char *uuid, unsigned int *major, unsigned int *minor) { const char *p; char *e; diff --git a/kpartx/devmapper.h b/kpartx/devmapper.h index 73b80f2..701bdf6 100644 --- a/kpartx/devmapper.h +++ b/kpartx/devmapper.h @@ -9,14 +9,14 @@ extern int udev_sync; -int dm_prereq (char *, int, int, int); +int dm_prereq (char *, uint32_t, uint32_t, uint32_t); int dm_simplecmd (int, const char *, int, uint16_t); int dm_addmap (int, const char *, const char *, const char *, uint64_t, int, const char *, int, mode_t, uid_t, gid_t); char * dm_mapname(int major, int minor); dev_t dm_get_first_dep(char *devname); char * dm_mapuuid(const char *mapname); -int dm_devn (const char * mapname, int *major, int *minor); +int dm_devn (const char * mapname, unsigned int *major, unsigned int *minor); int dm_remove_partmaps (char * mapname, char *uuid, dev_t devt, int verbose); int dm_find_part(const char *parent, const char *delim, int part, const char *parent_uuid, @@ -34,5 +34,6 @@ int dm_find_part(const char *parent, const char *delim, int part, #define NONDM_UUID_PREFIX "devnode" #define NONDM_UUID_SUFFIX "Wh5pYvM" char *nondm_create_uuid(dev_t devt); -int nondm_parse_uuid(const char *uuid, int *major, int *minor); +int nondm_parse_uuid(const char *uuid, + unsigned int *major, unsigned int *minor); #endif /* _KPARTX_DEVMAPPER_H */ diff --git a/kpartx/dos.c b/kpartx/dos.c index 4985152..0c70669 100644 --- a/kpartx/dos.c +++ b/kpartx/dos.c @@ -74,10 +74,10 @@ is_gpt(int type) { } int -read_dos_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_dos_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct partition p; unsigned long offset = all.start; - int i, n=4; + unsigned int i, n=4; unsigned char *bp; uint64_t sector_size_mul = get_sector_size(fd)/512; diff --git a/kpartx/gpt.c b/kpartx/gpt.c index e31611a..785b34e 100644 --- a/kpartx/gpt.c +++ b/kpartx/gpt.c @@ -40,9 +40,9 @@ #include "kpartx.h" #if BYTE_ORDER == LITTLE_ENDIAN -# define __le16_to_cpu(x) (x) -# define __le32_to_cpu(x) (x) -# define __le64_to_cpu(x) (x) +# define __le16_to_cpu(x) (uint16_t)(x) +# define __le32_to_cpu(x) (uint32_t)(x) +# define __le64_to_cpu(x) (uint64_t)(x) # define __cpu_to_le32(x) (x) #elif BYTE_ORDER == BIG_ENDIAN # define __le16_to_cpu(x) bswap_16(x) @@ -182,7 +182,7 @@ last_lba(int filedes) static ssize_t -read_lastoddsector(int fd, uint64_t lba, void *buffer, size_t count) +read_lastoddsector(int fd, void *buffer, size_t count) { int rc; struct blkdev_ioctl_param ioctl_param; @@ -221,7 +221,7 @@ read_lba(int fd, uint64_t lba, void *buffer, size_t bytes) one sector, so we don't have to be fancy. */ if (!bytesread && !(lastlba & 1) && lba == lastlba) { - bytesread = read_lastoddsector(fd, lba, buffer, bytes); + bytesread = read_lastoddsector(fd, buffer, bytes); } return bytesread; } @@ -601,11 +601,12 @@ fail: * */ int -read_gpt_pt (int fd, struct slice all, struct slice *sp, int ns) +read_gpt_pt (int fd, __attribute__((unused)) struct slice all, + struct slice *sp, unsigned int ns) { gpt_header *gpt = NULL; gpt_entry *ptes = NULL; - uint32_t i; + unsigned int i; int n = 0; int last_used_index=-1; int sector_size_mul = get_sector_size(fd)/512; diff --git a/kpartx/gpt.h b/kpartx/gpt.h index 7bb54b7..4e1b49a 100644 --- a/kpartx/gpt.h +++ b/kpartx/gpt.h @@ -105,7 +105,7 @@ typedef struct _legacy_mbr { #define EFI_GPT_PRIMARY_PARTITION_TABLE_LBA 1 /* Functions */ -int read_gpt_pt (int fd, struct slice all, struct slice *sp, int ns); +int read_gpt_pt (int fd, struct slice all, struct slice *sp, unsigned int ns); #endif diff --git a/kpartx/kpartx.h b/kpartx/kpartx.h index 52920e4..67edeb8 100644 --- a/kpartx/kpartx.h +++ b/kpartx/kpartx.h @@ -16,8 +16,17 @@ #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) +#define safe_snprintf(var, size, format, args...) \ + ({ \ + size_t __size = size; \ + int __ret; \ + \ + __ret = snprintf(var, __size, format, ##args); \ + __ret < 0 || (size_t)__ret >= __size; \ + }) + #define safe_sprintf(var, format, args...) \ - snprintf(var, sizeof(var), format, ##args) >= sizeof(var) + safe_snprintf(var, sizeof(var), format, ##args) #ifndef BLKSSZGET #define BLKSSZGET _IO(0x12,104) /* get block device sector size */ @@ -33,11 +42,12 @@ struct slice { uint64_t start; uint64_t size; int container; - int major; - int minor; + unsigned int major; + unsigned int minor; }; -typedef int (ptreader)(int fd, struct slice all, struct slice *sp, int ns); +typedef int (ptreader)(int fd, struct slice all, struct slice *sp, + unsigned int ns); extern int force_gpt; @@ -53,7 +63,7 @@ extern ptreader read_ps3_pt; char *getblock(int fd, unsigned int secnr); -static inline int +static inline unsigned int four2int(unsigned char *p) { return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24); } diff --git a/kpartx/mac.c b/kpartx/mac.c index 6e82c95..c21ac70 100644 --- a/kpartx/mac.c +++ b/kpartx/mac.c @@ -5,12 +5,13 @@ #include "mac.h" int -read_mac_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_mac_pt(int fd, __attribute__((unused)) struct slice all, + struct slice *sp, unsigned int ns) { struct mac_driver_desc *md; struct mac_partition *part; unsigned secsize; char *data; - int blk, blocks_in_map; + unsigned int blk, blocks_in_map; int n = 0; md = (struct mac_driver_desc *) getblock(fd, 0); diff --git a/kpartx/ps3.c b/kpartx/ps3.c index 8455097..42551bc 100644 --- a/kpartx/ps3.c +++ b/kpartx/ps3.c @@ -34,7 +34,7 @@ struct disklabel { static int read_disklabel(int fd, struct disklabel *label) { unsigned char *data; - int i; + unsigned int i; for (i = 0; i < sizeof(struct disklabel) / SECTOR_SIZE; i++) { data = (unsigned char *) getblock(fd, i); @@ -48,7 +48,8 @@ read_disklabel(int fd, struct disklabel *label) { } int -read_ps3_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_ps3_pt(int fd, __attribute__((unused)) struct slice all, + struct slice *sp, __attribute__((unused)) unsigned int ns) { struct disklabel label; int n = 0; int i; diff --git a/kpartx/solaris.c b/kpartx/solaris.c index e7826c6..c2480b5 100644 --- a/kpartx/solaris.c +++ b/kpartx/solaris.c @@ -28,11 +28,11 @@ struct solaris_x86_vtoc { }; int -read_solaris_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_solaris_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct solaris_x86_vtoc *v; struct solaris_x86_slice *s; unsigned int offset = all.start; - int i, n; + unsigned int i, n; char *bp; bp = getblock(fd, offset+1); /* 1 sector suffices */ diff --git a/kpartx/sun.c b/kpartx/sun.c index 276066d..df630a7 100644 --- a/kpartx/sun.c +++ b/kpartx/sun.c @@ -59,11 +59,11 @@ sun_verify_checksum (struct sun_disk_label *label) } int -read_sun_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_sun_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct sun_disk_label *l; struct sun_raw_part *s; unsigned int offset = all.start, end; - int i, j, n; + unsigned int i, j, n; char *bp; bp = getblock(fd, offset); diff --git a/kpartx/unixware.c b/kpartx/unixware.c index c7b9786..2f663af 100644 --- a/kpartx/unixware.c +++ b/kpartx/unixware.c @@ -48,12 +48,12 @@ struct unixware_disklabel { }; /* 408 */ int -read_unixware_pt(int fd, struct slice all, struct slice *sp, int ns) { +read_unixware_pt(int fd, struct slice all, struct slice *sp, unsigned int ns) { struct unixware_disklabel *l; struct unixware_slice *p; unsigned int offset = all.start; char *bp; - int n = 0; + unsigned int n = 0; bp = getblock(fd, offset+29); /* 1 sector suffices */ if (bp == NULL) diff --git a/libdmmp/libdmmp_private.h b/libdmmp/libdmmp_private.h index 3e813cb..ac85b63 100644 --- a/libdmmp/libdmmp_private.h +++ b/libdmmp/libdmmp_private.h @@ -131,13 +131,15 @@ DMMP_DLL_LOCAL void _dmmp_path_free(struct dmmp_path *dmmp_p); DMMP_DLL_LOCAL void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, - const char *format, ...); + const char *format, ...) + __attribute__((format(printf, 6, 7))); DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc); DMMP_DLL_LOCAL void _dmmp_log_stderr(struct dmmp_context *ctx, int priority, const char *file, int line, const char *func_name, const char *format, - va_list args); + va_list args) + __attribute__((format(printf, 6, 0))); #define _dmmp_log_cond(ctx, prio, arg...) \ @@ -164,7 +166,7 @@ DMMP_DLL_LOCAL void _dmmp_log_stderr(struct dmmp_context *ctx, int priority, do { \ if (ptr == NULL) { \ rc = DMMP_ERR_NO_MEMORY; \ - _error(ctx, dmmp_strerror(rc)); \ + _error(ctx, "%s", dmmp_strerror(rc)); \ goto goto_out; \ } \ } while(0) diff --git a/libmpathcmd/mpath_cmd.c b/libmpathcmd/mpath_cmd.c index f00bf7e..60b2d96 100644 --- a/libmpathcmd/mpath_cmd.c +++ b/libmpathcmd/mpath_cmd.c @@ -96,7 +96,8 @@ static size_t write_all(int fd, const void *buf, size_t len) */ int __mpath_connect(int nonblocking) { - int fd, len; + int fd; + size_t len; struct sockaddr_un addr; int flags = 0; @@ -172,7 +173,7 @@ int mpath_recv_reply_data(int fd, char *reply, size_t len, ret = read_all(fd, reply, len, timeout); if (ret < 0) return ret; - if (ret != len) { + if ((size_t)ret != len) { errno = EIO; return -1; } diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c index 603cfc3..3da7a6c 100644 --- a/libmpathpersist/mpath_persist.c +++ b/libmpathpersist/mpath_persist.c @@ -47,7 +47,7 @@ mpath_lib_init (void) condlog(0, "Failed to initialize multipath config."); return NULL; } - + conf->force_sync = 1; set_max_fds(conf->max_fds); return conf; @@ -854,7 +854,8 @@ int update_map_pr(struct multipath *mpp) { int noisy=0; struct prin_resp *resp; - int i, ret, isFound; + unsigned int i; + int ret, isFound; if (!get_be64(mpp->reservation_key)) { diff --git a/libmpathpersist/mpath_pr_ioctl.c b/libmpathpersist/mpath_pr_ioctl.c index cf528fe..74b26b0 100644 --- a/libmpathpersist/mpath_pr_ioctl.c +++ b/libmpathpersist/mpath_pr_ioctl.c @@ -23,10 +23,8 @@ #define MAXRETRY 5 int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp *resp, int noisy); -void mpath_format_readkeys(struct prin_resp *pr_buff, int len , int noisy); -void mpath_format_readfullstatus(struct prin_resp *pr_buff, int len, int noisy); int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, - SenseData_t *Sensedata, int noisy); + SenseData_t *Sensedata); void dumpHex(const char* str, int len, int no_ascii); int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); @@ -115,7 +113,7 @@ retry : condlog(4, "%s: Duration=%u (ms)", dev, io_hdr.duration); - status = mpath_translate_response(dev, io_hdr, &Sensedata, noisy); + status = mpath_translate_response(dev, io_hdr, &Sensedata); condlog(3, "%s: status = %d", dev, status); if (status == MPATH_PR_SENSE_UNIT_ATTENTION && (retry > 0)) @@ -142,7 +140,7 @@ retry : uint32_t format_transportids(struct prout_param_descriptor *paramp) { - int i = 0, len; + unsigned int i = 0, len; uint32_t buff_offset = 4; memset(paramp->private_buffer, 0, MPATH_MAX_PARAM_LEN); for (i=0; i < paramp->num_transportid; i++ ) @@ -181,22 +179,22 @@ uint32_t format_transportids(struct prout_param_descriptor *paramp) return buff_offset; } -void mpath_format_readkeys( struct prin_resp *pr_buff, int len, int noisy) +static void mpath_format_readkeys(struct prin_resp *pr_buff) { convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.prgeneration); convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.additional_length); } -void mpath_format_readresv(struct prin_resp *pr_buff, int len, int noisy) +static void mpath_format_readresv(struct prin_resp *pr_buff) { - convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.prgeneration); - convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readkeys.additional_length); + convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readresv.prgeneration); + convert_be32_to_cpu(&pr_buff->prin_descriptor.prin_readresv.additional_length); return; } -void mpath_format_reportcapabilities(struct prin_resp *pr_buff, int len, int noisy) +static void mpath_format_reportcapabilities(struct prin_resp *pr_buff) { convert_be16_to_cpu(&pr_buff->prin_descriptor.prin_readcap.length); convert_be16_to_cpu(&pr_buff->prin_descriptor.prin_readcap.pr_type_mask); @@ -204,13 +202,13 @@ void mpath_format_reportcapabilities(struct prin_resp *pr_buff, int len, int noi return; } -void mpath_format_readfullstatus(struct prin_resp *pr_buff, int len, int noisy) +static void mpath_format_readfullstatus(struct prin_resp *pr_buff) { - int num, k, tid_len_len=0; + int num; uint32_t fdesc_count=0; unsigned char *p; char *ppbuff; - uint32_t additional_length; + uint32_t additional_length, k, tid_len_len = 0; char tempbuff[MPATH_MAX_PARAM_LEN]; struct prin_fulldescr fdesc; @@ -271,8 +269,8 @@ void mpath_format_readfullstatus(struct prin_resp *pr_buff, int len, int noisy) void decode_transport_id(struct prin_fulldescr *fdesc, unsigned char * p, int length) { - int num, k; - int jump; + unsigned int num; + int jump, k; for (k = 0, jump = 24; k < length; k += jump, p += jump) { fdesc->trnptid.format_code = ((p[0] >> 6) & 0x3); fdesc->trnptid.protocol_id = (p[0] & 0xf); @@ -361,7 +359,7 @@ retry : condlog(3, "%s: duration = %u (ms)", dev, io_hdr.duration); condlog(4, "%s: persistent reservation in: requested %d bytes but got %d bytes)", dev, mx_resp_len, got); - status = mpath_translate_response(dev, io_hdr, &Sensedata, noisy); + status = mpath_translate_response(dev, io_hdr, &Sensedata); if (status == MPATH_PR_SENSE_UNIT_ATTENTION && (retry > 0)) { @@ -389,16 +387,16 @@ retry : switch (rq_servact) { case MPATH_PRIN_RKEY_SA : - mpath_format_readkeys(resp, got, noisy); + mpath_format_readkeys(resp); break; case MPATH_PRIN_RRES_SA : - mpath_format_readresv(resp, got, noisy); + mpath_format_readresv(resp); break; case MPATH_PRIN_RCAP_SA : - mpath_format_reportcapabilities(resp, got, noisy); + mpath_format_reportcapabilities(resp); break; case MPATH_PRIN_RFSTAT_SA : - mpath_format_readfullstatus(resp, got, noisy); + mpath_format_readfullstatus(resp); } out: @@ -407,7 +405,7 @@ out: } int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, - SenseData_t *Sensedata, int noisy) + SenseData_t *Sensedata) { condlog(3, "%s: status driver:%02x host:%02x scsi:%02x", dev, io_hdr.driver_status, io_hdr.host_status ,io_hdr.status); diff --git a/libmultipath/Makefile b/libmultipath/Makefile index a2be42e..e5651e4 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -50,6 +50,11 @@ all: $(LIBS) nvme-lib.o: nvme-lib.c nvme-ioctl.c nvme-ioctl.h $(CC) $(CFLAGS) -Wno-unused-function -c -o $@ $< +# there are lots of "unused parameters" in dict.c +# because not all handler / snprint methods nees all parameters +dict.o: dict.c + $(CC) $(CFLAGS) -Wno-unused-parameter -c -o $@ $< + make_static = $(shell sed '/^static/!s/^\([a-z]\{1,\} \)/static \1/' <$1 >$2) nvme-ioctl.c: nvme/nvme-ioctl.c diff --git a/libmultipath/alias.c b/libmultipath/alias.c index fd6b7f9..14401ca 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -10,6 +10,7 @@ #include #include "debug.h" +#include "util.h" #include "uxsock.h" #include "alias.h" #include "file.h" @@ -37,7 +38,7 @@ */ int -valid_alias(char *alias) +valid_alias(const char *alias) { if (strchr(alias, '/') != NULL) return 0; @@ -46,30 +47,37 @@ valid_alias(char *alias) static int -format_devname(char *name, int id, int len, char *prefix) +format_devname(char *name, int id, int len, const char *prefix) { int pos; int prefix_len = strlen(prefix); - memset(name,0, len); + if (len <= prefix_len + 1 || id <= 0) + return -1; + + memset(name, 0, len); strcpy(name, prefix); - for (pos = len - 1; pos >= prefix_len; pos--) { + name[len - 1] = '\0'; + for (pos = len - 2; pos >= prefix_len; pos--) { id--; name[pos] = 'a' + id % 26; if (id < 26) break; id /= 26; } + if (pos < prefix_len) + return -1; + memmove(name + prefix_len, name + pos, len - pos); - name[prefix_len + len - pos] = '\0'; - return (prefix_len + len - pos); + return (prefix_len + len - pos - 1); } static int -scan_devname(char *alias, char *prefix) +scan_devname(const char *alias, const char *prefix) { - char *c; + const char *c; int i, n = 0; + static const int last_26 = INT_MAX / 26; if (!prefix || strncmp(alias, prefix, strlen(prefix))) return -1; @@ -86,9 +94,9 @@ scan_devname(char *alias, char *prefix) if (*c < 'a' || *c > 'z') return -1; i = *c - 'a'; - n = ( n * 26 ) + i; - if (n < 0) + if (n > last_26 || (n == last_26 && i >= INT_MAX % 26)) return -1; + n = n * 26 + i; c++; n++; } @@ -96,8 +104,16 @@ scan_devname(char *alias, char *prefix) return n; } +/* + * Returns: 0 if matching entry in WWIDs file found + * -1 if an error occurs + * >0 a free ID that could be used for the WWID at hand + * *map_alias is set to a freshly allocated string with the matching alias if + * the function returns 0, or to NULL otherwise. + */ static int -lookup_binding(FILE *f, char *map_wwid, char **map_alias, char *prefix) +lookup_binding(FILE *f, const char *map_wwid, char **map_alias, + const char *prefix) { char buf[LINE_MAX]; unsigned int line_nr = 0; @@ -109,7 +125,8 @@ lookup_binding(FILE *f, char *map_wwid, char **map_alias, char *prefix) rewind(f); while (fgets(buf, LINE_MAX, f)) { - char *c, *alias, *wwid; + const char *alias, *wwid; + char *c; int curr_id; line_nr++; @@ -120,8 +137,14 @@ lookup_binding(FILE *f, char *map_wwid, char **map_alias, char *prefix) if (!alias) /* blank line */ continue; curr_id = scan_devname(alias, prefix); - if (curr_id == id) - id++; + if (curr_id == id) { + if (id < INT_MAX) + id++; + else { + id = -1; + break; + } + } if (curr_id > biggest_id) biggest_id = curr_id; if (curr_id > id && curr_id < smallest_bigger_id) @@ -137,24 +160,30 @@ lookup_binding(FILE *f, char *map_wwid, char **map_alias, char *prefix) condlog(3, "Found matching wwid [%s] in bindings file." " Setting alias to %s", wwid, alias); *map_alias = strdup(alias); - if (*map_alias == NULL) + if (*map_alias == NULL) { condlog(0, "Cannot copy alias from bindings " - "file : %s", strerror(errno)); + "file: out of memory"); + return -1; + } return 0; } } - condlog(3, "No matching wwid [%s] in bindings file.", map_wwid); + if (id >= smallest_bigger_id) { + if (biggest_id < INT_MAX) + id = biggest_id + 1; + else + id = -1; + } if (id < 0) { condlog(0, "no more available user_friendly_names"); - return 0; - } - if (id < smallest_bigger_id) - return id; - return biggest_id + 1; + return -1; + } else + condlog(3, "No matching wwid [%s] in bindings file.", map_wwid); + return id; } static int -rlookup_binding(FILE *f, char *buff, char *map_alias, char *prefix) +rlookup_binding(FILE *f, char *buff, const char *map_alias) { char line[LINE_MAX]; unsigned int line_nr = 0; @@ -162,7 +191,8 @@ rlookup_binding(FILE *f, char *buff, char *map_alias, char *prefix) buff[0] = '\0'; while (fgets(line, LINE_MAX, f)) { - char *c, *alias, *wwid; + char *c; + const char *alias, *wwid; line_nr++; c = strpbrk(line, "#\n\r"); @@ -186,8 +216,7 @@ rlookup_binding(FILE *f, char *buff, char *map_alias, char *prefix) if (strcmp(alias, map_alias) == 0){ condlog(3, "Found matching alias [%s] in bindings file." "\nSetting wwid to %s", alias, wwid); - strncpy(buff, wwid, WWID_SIZE); - buff[WWID_SIZE - 1] = '\0'; + strlcpy(buff, wwid, WWID_SIZE); return 0; } } @@ -197,21 +226,28 @@ rlookup_binding(FILE *f, char *buff, char *map_alias, char *prefix) } static char * -allocate_binding(int fd, char *wwid, int id, char *prefix) +allocate_binding(int fd, const char *wwid, int id, const char *prefix) { char buf[LINE_MAX]; off_t offset; char *alias, *c; int i; - if (id < 0) { - condlog(0, "Bindings file full. Cannot allocate new binding"); + if (id <= 0) { + condlog(0, "%s: cannot allocate new binding for id %d", + __func__, id); return NULL; } i = format_devname(buf, id, LINE_MAX, prefix); + if (i == -1) + return NULL; + c = buf + i; - snprintf(c,LINE_MAX - i, " %s\n", wwid); + if (snprintf(c, LINE_MAX - i, " %s\n", wwid) >= LINE_MAX - i) { + condlog(1, "%s: line too long for %s\n", __func__, wwid); + return NULL; + } buf[LINE_MAX - 1] = '\0'; offset = lseek(fd, 0, SEEK_END); @@ -220,7 +256,7 @@ allocate_binding(int fd, char *wwid, int id, char *prefix) strerror(errno)); return NULL; } - if (write(fd, buf, strlen(buf)) != strlen(buf)){ + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)){ condlog(0, "Cannot write binding to bindings file : %s", strerror(errno)); /* clear partial write */ @@ -232,19 +268,18 @@ allocate_binding(int fd, char *wwid, int id, char *prefix) c = strchr(buf, ' '); if (c) *c = '\0'; + + condlog(3, "Created new binding [%s] for WWID [%s]", buf, wwid); alias = strdup(buf); if (alias == NULL) - condlog(0, "cannot copy new alias from bindings file : %s", - strerror(errno)); - else - condlog(3, "Created new binding [%s] for WWID [%s]", alias, - wwid); + condlog(0, "cannot copy new alias from bindings file: out of memory"); + return alias; } char * -use_existing_alias (char *wwid, char *file, char *alias_old, - char *prefix, int bindings_read_only) +use_existing_alias (const char *wwid, const char *file, const char *alias_old, + const char *prefix, int bindings_read_only) { char *alias = NULL; int id = 0; @@ -262,10 +297,10 @@ use_existing_alias (char *wwid, char *file, char *alias_old, close(fd); return NULL; } - /* lookup the binding. if it exsists, the wwid will be in buff + /* lookup the binding. if it exists, the wwid will be in buff * either way, id contains the id for the alias */ - rlookup_binding(f, buff, alias_old, prefix); + rlookup_binding(f, buff, alias_old); if (strlen(buff) > 0) { /* if buff is our wwid, it's already @@ -306,12 +341,14 @@ use_existing_alias (char *wwid, char *file, char *alias_old, } out: + pthread_cleanup_push(free, alias); fclose(f); + pthread_cleanup_pop(0); return alias; } char * -get_user_friendly_alias(char *wwid, char *file, char *prefix, +get_user_friendly_alias(const char *wwid, const char *file, const char *prefix, int bindings_read_only) { char *alias; @@ -342,23 +379,24 @@ get_user_friendly_alias(char *wwid, char *file, char *prefix, return NULL; } + pthread_cleanup_push(free, alias); + if (fflush(f) != 0) { condlog(0, "cannot fflush bindings file stream : %s", strerror(errno)); free(alias); - fclose(f); - return NULL; - } - - if (!alias && can_write && !bindings_read_only && id) + alias = NULL; + } else if (can_write && !bindings_read_only && !alias) alias = allocate_binding(fd, wwid, id, prefix); fclose(f); + + pthread_cleanup_pop(0); return alias; } int -get_user_friendly_wwid(char *alias, char *buff, char *file) +get_user_friendly_wwid(const char *alias, char *buff, const char *file) { int fd, unused; FILE *f; @@ -380,7 +418,7 @@ get_user_friendly_wwid(char *alias, char *buff, char *file) return -1; } - rlookup_binding(f, buff, alias, NULL); + rlookup_binding(f, buff, alias); if (!strlen(buff)) { fclose(f); return -1; diff --git a/libmultipath/alias.h b/libmultipath/alias.h index 95473ff..7c4b302 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -7,9 +7,11 @@ "# alias wwid\n" \ "#\n" -int valid_alias(char *alias); -char *get_user_friendly_alias(char *wwid, char *file, char *prefix, +int valid_alias(const char *alias); +char *get_user_friendly_alias(const char *wwid, const char *file, + const char *prefix, int bindings_readonly); -int get_user_friendly_wwid(char *alias, char *buff, char *file); -char *use_existing_alias (char *wwid, char *file, char *alias_old, - char *prefix, int bindings_read_only); +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); diff --git a/libmultipath/byteorder.h b/libmultipath/byteorder.h index 5c77146..0a86244 100644 --- a/libmultipath/byteorder.h +++ b/libmultipath/byteorder.h @@ -9,19 +9,19 @@ #endif #if BYTE_ORDER == LITTLE_ENDIAN -# define le16_to_cpu(x) (x) +# define le16_to_cpu(x) (uint16_t)(x) # define be16_to_cpu(x) bswap_16(x) -# define le32_to_cpu(x) (x) -# define le64_to_cpu(x) (x) +# define le32_to_cpu(x) (uint32_t)(x) +# define le64_to_cpu(x) (uint64_t)(x) # define be32_to_cpu(x) bswap_32(x) # define be64_to_cpu(x) bswap_64(x) #elif BYTE_ORDER == BIG_ENDIAN # define le16_to_cpu(x) bswap_16(x) -# define be16_to_cpu(x) (x) +# define be16_to_cpu(x) (uint16_t)(x) # define le32_to_cpu(x) bswap_32(x) # define le64_to_cpu(x) bswap_64(x) -# define be32_to_cpu(x) (x) -# define be64_to_cpu(x) (x) +# define be32_to_cpu(x) (uint32_t)(x) +# define be64_to_cpu(x) (uint64_t)(x) #else # error unsupported #endif diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index a08bf41..8d2be8a 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -18,6 +18,7 @@ struct checker_class { int (*init)(struct checker *); /* to allocate the context */ int (*mp_init)(struct checker *); /* to allocate the mpcontext */ void (*free)(struct checker *); /* to free the context */ + void (*reset)(void); /* to reset the global variables */ const char **msgtable; short msgtable_size; }; @@ -66,6 +67,8 @@ void free_checker_class(struct checker_class *c) } condlog(3, "unloading %s checker", c->name); list_del(&c->node); + if (c->reset) + c->reset(); if (c->handle) { if (dlclose(c->handle) != 0) { condlog(0, "Cannot unload checker %s: %s", @@ -98,6 +101,16 @@ static struct checker_class *checker_class_lookup(const char *name) return NULL; } +void reset_checker_classes(void) +{ + struct checker_class *c; + + list_for_each_entry(c, &checkers, node) { + if (c->reset) + c->reset(); + } +} + static struct checker_class *add_checker_class(const char *multipath_dir, const char *name) { @@ -142,7 +155,9 @@ static struct checker_class *add_checker_class(const char *multipath_dir, goto out; c->mp_init = (int (*)(struct checker *)) dlsym(c->handle, "libcheck_mp_init"); - /* NULL mp_init is o.k. call dlerror() to clear out any error string */ + c->reset = (void (*)(void)) dlsym(c->handle, "libcheck_reset"); + /* These 2 functions can be NULL. call dlerror() to clear out any + * error string */ dlerror(); c->free = (void (*)(struct checker *)) dlsym(c->handle, "libcheck_free"); diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index 5237e7e..b458118 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -150,6 +150,7 @@ void checker_disable (struct checker *); int checker_check (struct checker *, int); int checker_is_sync(const struct checker *); const char *checker_name (const struct checker *); +void reset_checker_classes(void); /* * This returns a string that's best prepended with "$NAME checker", * where $NAME is the return value of checker_name(). diff --git a/libmultipath/checkers/cciss_tur.c b/libmultipath/checkers/cciss_tur.c index ea84374..eaf67b3 100644 --- a/libmultipath/checkers/cciss_tur.c +++ b/libmultipath/checkers/cciss_tur.c @@ -46,12 +46,12 @@ struct cciss_tur_checker_context { void * dummy; }; -int libcheck_init (struct checker * c) +int libcheck_init (__attribute__((unused)) struct checker * c) { return 0; } -void libcheck_free (struct checker * c) +void libcheck_free (__attribute__((unused)) struct checker * c) { return; } diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 1b00b77..503519e 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -17,6 +17,32 @@ #include "checkers.h" #include "../libmultipath/debug.h" +#include "../libmultipath/time-util.h" + +#define AIO_GROUP_SIZE 1024 + +/* Note: This checker type relies on the fact that only one checker can be run + * at a time, since multiple checkers share the same aio_group, and must be + * able to modify other checker's async_reqs. If multple checkers become able + * to be run at the same time, this checker will need to add locking, and + * probably polling on event fds, to deal with that */ + +struct aio_group { + struct list_head node; + int holders; + io_context_t ioctx; + struct list_head orphans; +}; + +struct async_req { + struct iocb io; + unsigned int blksize; + unsigned char * buf; + struct list_head node; + int state; /* PATH_REMOVED means this is an orphan */ +}; + +static LIST_HEAD(aio_grp_list); enum { MSG_DIRECTIO_UNKNOWN = CHECKER_FIRST_MSGID, @@ -37,18 +63,97 @@ const char *libcheck_msgtable[] = { struct directio_context { int running; int reset_flags; - int blksize; - unsigned char * buf; - unsigned char * ptr; - io_context_t ioctx; - struct iocb io; + struct aio_group *aio_grp; + struct async_req *req; }; +static struct aio_group * +add_aio_group(void) +{ + struct aio_group *aio_grp; + + aio_grp = malloc(sizeof(struct aio_group)); + if (!aio_grp) + return NULL; + memset(aio_grp, 0, sizeof(struct aio_group)); + INIT_LIST_HEAD(&aio_grp->orphans); + + if (io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx) != 0) { + LOG(1, "io_setup failed"); + if (errno == EAGAIN) + LOG(1, "global number of io events too small. Increase fs.aio-max-nr with sysctl"); + free(aio_grp); + return NULL; + } + list_add(&aio_grp->node, &aio_grp_list); + return aio_grp; +} + +static int +set_aio_group(struct directio_context *ct) +{ + struct aio_group *aio_grp = NULL; + + list_for_each_entry(aio_grp, &aio_grp_list, node) + if (aio_grp->holders < AIO_GROUP_SIZE) + goto found; + aio_grp = add_aio_group(); + if (!aio_grp) { + ct->aio_grp = NULL; + return -1; + } +found: + aio_grp->holders++; + ct->aio_grp = aio_grp; + return 0; +} + +static void +remove_aio_group(struct aio_group *aio_grp) +{ + struct async_req *req, *tmp; + + io_destroy(aio_grp->ioctx); + list_for_each_entry_safe(req, tmp, &aio_grp->orphans, node) { + list_del(&req->node); + free(req->buf); + free(req); + } + list_del(&aio_grp->node); + free(aio_grp); +} + +/* If an aio_group is completely full of orphans, then no checkers can + * use it, which means that no checkers can clear out the orphans. To + * avoid keeping the useless group around, simply remove remove the + * group */ +static void +check_orphaned_group(struct aio_group *aio_grp) +{ + int count = 0; + struct list_head *item; + + if (aio_grp->holders < AIO_GROUP_SIZE) + return; + list_for_each(item, &aio_grp->orphans) + count++; + if (count >= AIO_GROUP_SIZE) + remove_aio_group(aio_grp); +} + +void libcheck_reset (void) +{ + struct aio_group *aio_grp, *tmp; + + list_for_each_entry_safe(aio_grp, tmp, &aio_grp_list, node) + remove_aio_group(aio_grp); +} int libcheck_init (struct checker * c) { unsigned long pgsize = getpagesize(); struct directio_context * ct; + struct async_req *req = NULL; long flags; ct = malloc(sizeof(struct directio_context)); @@ -56,26 +161,30 @@ int libcheck_init (struct checker * c) return 1; memset(ct, 0, sizeof(struct directio_context)); - if (io_setup(1, &ct->ioctx) != 0) { - condlog(1, "io_setup failed"); - free(ct); - return 1; + if (set_aio_group(ct) < 0) + goto out; + + req = malloc(sizeof(struct async_req)); + if (!req) { + goto out; } + memset(req, 0, sizeof(struct async_req)); + INIT_LIST_HEAD(&req->node); - if (ioctl(c->fd, BLKBSZGET, &ct->blksize) < 0) { + if (ioctl(c->fd, BLKBSZGET, &req->blksize) < 0) { c->msgid = MSG_DIRECTIO_BLOCKSIZE; - ct->blksize = 512; + req->blksize = 4096; } - if (ct->blksize > 4096) { + if (req->blksize > 4096) { /* * Sanity check for DASD; BSZGET is broken */ - ct->blksize = 4096; + req->blksize = 4096; } - if (!ct->blksize) + if (!req->blksize) goto out; - ct->buf = (unsigned char *)malloc(ct->blksize + pgsize); - if (!ct->buf) + + if (posix_memalign((void **)&req->buf, pgsize, req->blksize) != 0) goto out; flags = fcntl(c->fd, F_GETFL); @@ -88,17 +197,19 @@ int libcheck_init (struct checker * c) ct->reset_flags = 1; } - ct->ptr = (unsigned char *) (((unsigned long)ct->buf + pgsize - 1) & - (~(pgsize - 1))); - /* Successfully initialized, return the context. */ + ct->req = req; c->context = (void *) ct; return 0; out: - if (ct->buf) - free(ct->buf); - io_destroy(ct->ioctx); + if (req) { + if (req->buf) + free(req->buf); + free(req); + } + if (ct->aio_grp) + ct->aio_grp->holders--; free(ct); return 1; } @@ -106,6 +217,7 @@ out: void libcheck_free (struct checker * c) { struct directio_context * ct = (struct directio_context *)c->context; + struct io_event event; long flags; if (!ct) @@ -121,20 +233,71 @@ void libcheck_free (struct checker * c) } } - if (ct->buf) - free(ct->buf); - io_destroy(ct->ioctx); + if (ct->running && + (ct->req->state != PATH_PENDING || + io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event) == 0)) + ct->running = 0; + if (!ct->running) { + free(ct->req->buf); + free(ct->req); + ct->aio_grp->holders--; + } else { + ct->req->state = PATH_REMOVED; + list_add(&ct->req->node, &ct->aio_grp->orphans); + check_orphaned_group(ct->aio_grp); + } + free(ct); + c->context = NULL; +} + +static int +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 *timep = timeout; + + do { + errno = 0; + nr = io_getevents(aio_grp->ioctx, 1, 128, events, timep); + got_events |= (nr > 0); + + for (i = 0; i < nr; i++) { + struct async_req *req = container_of(events[i].obj, struct async_req, io); + + LOG(3, "io finished %lu/%lu", events[i].res, + events[i].res2); + + /* got an orphaned request */ + if (req->state == PATH_REMOVED) { + list_del(&req->node); + free(req->buf); + free(req); + aio_grp->holders--; + } else + req->state = (events[i].res == req->blksize) ? + PATH_UP : PATH_DOWN; + } + timep = &zero_timeout; + } while (nr == 128); /* assume there are more events and try again */ + + if (nr < 0) + LOG(3, "async io getevents returned %i (errno=%s)", + nr, strerror(errno)); + + return got_events; } static int check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) { - struct timespec timeout = { .tv_nsec = 5 }; - struct io_event event; + struct timespec timeout = { .tv_nsec = 1000 }; struct stat sb; - int rc = PATH_UNCHECKED; + int rc; long r; + struct timespec currtime, endtime; if (fstat(fd, &sb) == 0) { LOG(4, "called for %x", (unsigned) sb.st_rdev); @@ -145,50 +308,61 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) timeout.tv_nsec = 0; } - if (!ct->running) { - struct iocb *ios[1] = { &ct->io }; + if (ct->running) { + if (ct->req->state != PATH_PENDING) { + ct->running = 0; + return ct->req->state; + } + } else { + struct iocb *ios[1] = { &ct->req->io }; LOG(3, "starting new request"); - memset(&ct->io, 0, sizeof(struct iocb)); - io_prep_pread(&ct->io, fd, ct->ptr, ct->blksize, 0); - if (io_submit(ct->ioctx, 1, ios) != 1) { + memset(&ct->req->io, 0, sizeof(struct iocb)); + io_prep_pread(&ct->req->io, fd, ct->req->buf, + ct->req->blksize, 0); + ct->req->state = PATH_PENDING; + if (io_submit(ct->aio_grp->ioctx, 1, ios) != 1) { LOG(3, "io_submit error %i", errno); return PATH_UNCHECKED; } } ct->running++; - errno = 0; - r = io_getevents(ct->ioctx, 1L, 1L, &event, &timeout); + get_monotonic_time(&endtime); + endtime.tv_sec += timeout.tv_sec; + endtime.tv_nsec += timeout.tv_nsec; + normalize_timespec(&endtime); + while(1) { + r = get_events(ct->aio_grp, &timeout); - if (r < 0 ) { - LOG(3, "async io getevents returned %li (errno=%s)", r, - strerror(errno)); - ct->running = 0; - rc = PATH_UNCHECKED; - } else if (r < 1L) { - if (ct->running > timeout_secs || sync) { - struct iocb *ios[1] = { &ct->io }; - - LOG(3, "abort check on timeout"); - r = io_cancel(ct->ioctx, ios[0], &event); - /* - * Only reset ct->running if we really - * could abort the pending I/O - */ - if (r) - LOG(3, "io_cancel error %i", errno); - else - ct->running = 0; - rc = PATH_DOWN; - } else { - LOG(3, "async io pending"); - rc = PATH_PENDING; - } + if (ct->req->state != PATH_PENDING) { + ct->running = 0; + return ct->req->state; + } else if (r == 0 || + (timeout.tv_sec == 0 && timeout.tv_nsec == 0)) + break; + + get_monotonic_time(&currtime); + timespecsub(&endtime, &currtime, &timeout); + if (timeout.tv_sec < 0) + timeout.tv_sec = timeout.tv_nsec = 0; + } + if (ct->running > timeout_secs || sync) { + struct io_event event; + + LOG(3, "abort check on timeout"); + + r = io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); + /* + * Only reset ct->running if we really + * could abort the pending I/O + */ + if (!r) + ct->running = 0; + rc = PATH_DOWN; } else { - LOG(3, "io finished %lu/%lu", event.res, event.res2); - ct->running = 0; - rc = (event.res == ct->blksize) ? PATH_UP : PATH_DOWN; + LOG(3, "async io pending"); + rc = PATH_PENDING; } return rc; diff --git a/libmultipath/checkers/hp_sw.c b/libmultipath/checkers/hp_sw.c index 1a82022..915918c 100644 --- a/libmultipath/checkers/hp_sw.c +++ b/libmultipath/checkers/hp_sw.c @@ -32,19 +32,19 @@ struct sw_checker_context { void * dummy; }; -int libcheck_init (struct checker * c) +int libcheck_init (__attribute__((unused)) struct checker * c) { return 0; } -void libcheck_free (struct checker * c) +void libcheck_free (__attribute__((unused)) struct checker * c) { return; } static int do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, - void *resp, int mx_resp_len, int noisy, unsigned int timeout) + void *resp, int mx_resp_len, unsigned int timeout) { unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY_CMD, 0, 0, 0, 0, 0 }; @@ -130,7 +130,7 @@ do_tur (int fd, unsigned int timeout) int libcheck_check(struct checker * c) { char buff[MX_ALLOC_LEN]; - int ret = do_inq(c->fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, 0, c->timeout); + int ret = do_inq(c->fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, c->timeout); if (ret == PATH_WILD) { c->msgid = CHECKER_MSGID_UNSUPPORTED; diff --git a/libmultipath/checkers/rdac.c b/libmultipath/checkers/rdac.c index 8a3b73e..d924a9f 100644 --- a/libmultipath/checkers/rdac.c +++ b/libmultipath/checkers/rdac.c @@ -133,7 +133,7 @@ out: return 0; } -void libcheck_free (struct checker * c) +void libcheck_free(__attribute__((unused)) struct checker *c) { return; } diff --git a/libmultipath/checkers/readsector0.c b/libmultipath/checkers/readsector0.c index cf79e06..b041f11 100644 --- a/libmultipath/checkers/readsector0.c +++ b/libmultipath/checkers/readsector0.c @@ -10,12 +10,12 @@ struct readsector0_checker_context { void * dummy; }; -int libcheck_init (struct checker * c) +int libcheck_init (__attribute__((unused)) struct checker * c) { return 0; } -void libcheck_free (struct checker * c) +void libcheck_free (__attribute__((unused)) struct checker * c) { return; } diff --git a/libmultipath/config.c b/libmultipath/config.c index 20e3b8b..b4d8768 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -369,9 +369,14 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) merge_num(max_sectors_kb); merge_num(ghost_delay); merge_num(all_tg_pt); + merge_num(vpd_vendor_id); merge_num(san_path_err_threshold); merge_num(san_path_err_forget_rate); merge_num(san_path_err_recovery_time); + merge_num(marginal_path_err_sample_time); + merge_num(marginal_path_err_rate_threshold); + merge_num(marginal_path_err_recheck_gap_time); + merge_num(marginal_path_double_failed_time); snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product); reconcile_features_with_options(id, &dst->features, @@ -397,6 +402,7 @@ merge_mpe(struct mpentry *dst, struct mpentry *src) if (dst->prkey_source == PRKEY_SOURCE_NONE && src->prkey_source != PRKEY_SOURCE_NONE) { dst->prkey_source = src->prkey_source; + dst->sa_flags = src->sa_flags; memcpy(&dst->reservation_key, &src->reservation_key, sizeof(dst->reservation_key)); } @@ -413,6 +419,9 @@ merge_mpe(struct mpentry *dst, struct mpentry *src) merge_num(deferred_remove); merge_num(delay_watch_checks); merge_num(delay_wait_checks); + merge_num(san_path_err_threshold); + merge_num(san_path_err_forget_rate); + merge_num(san_path_err_recovery_time); merge_num(marginal_path_err_sample_time); merge_num(marginal_path_err_rate_threshold); merge_num(marginal_path_err_recheck_gap_time); @@ -509,6 +518,7 @@ store_hwe (vector hwtable, struct hwentry * dhwe) hwe->detect_prio = dhwe->detect_prio; hwe->detect_checker = dhwe->detect_checker; hwe->ghost_delay = dhwe->ghost_delay; + hwe->vpd_vendor_id = dhwe->vpd_vendor_id; if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product))) goto out; @@ -644,7 +654,7 @@ free_config (struct config * conf) /* if multipath fails to process the config directory, it should continue, * with just a warning message */ static void -process_config_dir(struct config *conf, vector keywords, char *dir) +process_config_dir(struct config *conf, char *dir) { struct dirent **namelist; struct scandir_result sr; @@ -671,8 +681,11 @@ process_config_dir(struct config *conf, vector keywords, char *dir) sr.n = n; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < n; i++) { - if (!strstr(namelist[i]->d_name, ".conf")) + char *ext = strrchr(namelist[i]->d_name, '.'); + + if (!ext || strcmp(ext, ".conf")) continue; + old_hwtable_size = VECTOR_SIZE(conf->hwtable); snprintf(path, LINE_MAX, "%s/%s", dir, namelist[i]->d_name); path[LINE_MAX-1] = '\0'; @@ -683,6 +696,27 @@ process_config_dir(struct config *conf, vector keywords, char *dir) pthread_cleanup_pop(1); } +static void set_max_checkint_from_watchdog(struct config *conf) +{ +#ifdef USE_SYSTEMD + char *envp = getenv("WATCHDOG_USEC"); + unsigned long checkint; + + if (envp && sscanf(envp, "%lu", &checkint) == 1) { + /* Value is in microseconds */ + checkint /= 1000000; + if (checkint < 1 || checkint > UINT_MAX) { + condlog(1, "invalid value for WatchdogSec: \"%s\"", envp); + return; + } + if (conf->max_checkint == 0 || conf->max_checkint > checkint) + conf->max_checkint = checkint; + condlog(3, "enabling watchdog, interval %ld", checkint); + conf->use_watchdog = true; + } +#endif +} + struct config * load_config (char * file) { @@ -703,7 +737,8 @@ load_config (char * file) conf->multipath_dir = set_default(DEFAULT_MULTIPATHDIR); conf->attribute_flags = 0; conf->reassign_maps = DEFAULT_REASSIGN_MAPS; - conf->checkint = DEFAULT_CHECKINT; + conf->checkint = CHECKINT_UNDEF; + conf->use_watchdog = false; conf->max_checkint = 0; conf->force_sync = DEFAULT_FORCE_SYNC; conf->partition_delim = (default_partition_delim != NULL ? @@ -749,13 +784,25 @@ load_config (char * file) if (conf->config_dir == NULL) conf->config_dir = set_default(DEFAULT_CONFIG_DIR); if (conf->config_dir && conf->config_dir[0] != '\0') - process_config_dir(conf, conf->keywords, conf->config_dir); + process_config_dir(conf, conf->config_dir); /* * fill the voids left in the config file */ - if (conf->max_checkint == 0) - conf->max_checkint = MAX_CHECKINT(conf->checkint); + set_max_checkint_from_watchdog(conf); + if (conf->max_checkint == 0) { + if (conf->checkint == CHECKINT_UNDEF) + conf->checkint = DEFAULT_CHECKINT; + conf->max_checkint = (conf->checkint < UINT_MAX / 4 ? + conf->checkint * 4 : UINT_MAX); + } else if (conf->checkint == CHECKINT_UNDEF) + conf->checkint = (conf->max_checkint >= 4 ? + conf->max_checkint / 4 : 1); + else if (conf->checkint > conf->max_checkint) + conf->checkint = conf->max_checkint; + condlog(3, "polling interval: %d, max: %d", + conf->checkint, conf->max_checkint); + if (conf->blist_devnode == NULL) { conf->blist_devnode = vector_alloc(); diff --git a/libmultipath/config.h b/libmultipath/config.h index ffec310..ceecff2 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -87,6 +87,7 @@ struct hwentry { int max_sectors_kb; int ghost_delay; int all_tg_pt; + int vpd_vendor_id; char * bl_product; }; @@ -137,8 +138,9 @@ struct config { int pgpolicy; int minio; int minio_rq; - int checkint; - int max_checkint; + unsigned int checkint; + unsigned int max_checkint; + bool use_watchdog; int pgfailback; int remove; int rr_weight; @@ -188,6 +190,7 @@ struct config { int find_multipaths_timeout; int marginal_pathgroups; unsigned int version[3]; + unsigned int sequence_nr; char * multipath_dir; char * selector; diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 5ac7d90..c95848a 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -401,7 +401,6 @@ int setup_map(struct multipath *mpp, char *params, int params_size, condlog(2, "%s: setting up map with %d/%d path checkers pending", mpp->alias, n_pending, n_paths); } - mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST); /* * ponders each path group and determine highest prio pg @@ -934,8 +933,8 @@ int domap(struct multipath *mpp, char *params, int is_daemon) } sysfs_set_max_sectors_kb(mpp, 0); - if (is_daemon && mpp->ghost_delay > 0 && mpp->nr_active && - pathcount(mpp, PATH_GHOST) == mpp->nr_active) + if (is_daemon && mpp->ghost_delay > 0 && count_active_paths(mpp) && + pathcount(mpp, PATH_UP) == 0) mpp->ghost_delay_tick = mpp->ghost_delay; r = dm_addmap_create(mpp, params); diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index 4dfe007..e5ee6af 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -1,5 +1,7 @@ #ifndef _DEFAULTS_H #define _DEFAULTS_H +#include + /* * If you add or modify a value also update multipath/multipath.conf.5 * and the TEMPLATE in libmultipath/hwtable.c @@ -51,11 +53,10 @@ /* Enable all foreign libraries by default */ #define DEFAULT_ENABLE_FOREIGN "" -#define CHECKINT_UNDEF (~0U) +#define CHECKINT_UNDEF UINT_MAX #define DEFAULT_CHECKINT 5 -#define MAX_CHECKINT(a) (a << 2) -#define MAX_DEV_LOSS_TMO 0x7FFFFFFF +#define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE "/" RUN_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" #define DEFAULT_CONFIGFILE "/etc/multipath.conf" diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index 0f0c3a3..bed8ddc 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -59,7 +59,7 @@ void dm_udev_set_sync_support(int c) #endif -static void +__attribute__((format(printf, 4, 5))) static void dm_write_log (int level, const char *file, int line, const char *f, ...) { va_list ap; @@ -403,7 +403,7 @@ 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) | - ((mpp->nr_active == 0 || mpp->ghost_delay_tick > 0)? + ((count_active_paths(mpp) == 0 || mpp->ghost_delay_tick > 0) ? MPATH_UDEV_NO_PATHS_FLAG : 0) | (reload && !mpp->force_udev_reload ? MPATH_UDEV_RELOAD_FLAG : 0); @@ -812,7 +812,8 @@ dm_get_major_minor(const char *name, int *major, int *minor) } static int -has_partmap(const char *name, void *data) +has_partmap(const char *name __attribute__((unused)), + void *data __attribute__((unused))) { return 1; } @@ -1308,7 +1309,7 @@ dm_remove_partmaps (const char * mapname, int need_sync, int deferred_remove) #ifdef LIBDM_API_DEFERRED static int -cancel_remove_partmap (const char *name, void *unused) +cancel_remove_partmap (const char *name, void *unused __attribute__((unused))) { if (dm_get_opencount(name)) dm_cancel_remove_partmaps(name); @@ -1560,7 +1561,8 @@ int dm_reassign(const char *mapname) struct dm_task *dmt; struct dm_info info; char dev_t[32], dm_dep[32]; - int r = 0, i; + int r = 0; + unsigned int i; if (dm_dev_t(mapname, &dev_t[0], 32)) { condlog(3, "%s: failed to get device number", mapname); diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 2b046e1..3e25e74 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -31,16 +31,58 @@ static int set_int(vector strvec, void *ptr) { int *int_ptr = (int *)ptr; - char * buff; + char *buff, *eptr; + long res; + int rc; buff = set_value(strvec); if (!buff) return 1; - *int_ptr = atoi(buff); + res = strtol(buff, &eptr, 10); + if (eptr > buff) + while (isspace(*eptr)) + eptr++; + if (*buff == '\0' || *eptr != '\0' || res > INT_MAX || res < INT_MIN) { + condlog(1, "%s: invalid value for %s: \"%s\"", + __func__, (char*)VECTOR_SLOT(strvec, 0), buff); + rc = 1; + } else { + rc = 0; + *int_ptr = res; + } FREE(buff); - return 0; + return rc; +} + +static int +set_uint(vector strvec, void *ptr) +{ + unsigned int *uint_ptr = (unsigned int *)ptr; + char *buff, *eptr; + long res; + int rc; + + buff = set_value(strvec); + if (!buff) + return 1; + + res = strtol(buff, &eptr, 10); + if (eptr > buff) + while (isspace(*eptr)) + eptr++; + if (*buff == '\0' || *eptr != '\0' || res < 0 || res > UINT_MAX) { + condlog(1, "%s: invalid value for %s: \"%s\"", + __func__, (char*)VECTOR_SLOT(strvec, 0), buff); + rc = 1; + } else { + rc = 0; + *uint_ptr = res; + } + + FREE(buff); + return rc; } static int @@ -272,7 +314,7 @@ snprint_mp_ ## option (struct config *conf, char * buff, int len, \ static int checkint_handler(struct config *conf, vector strvec) { - int rc = set_int(strvec, &conf->checkint); + int rc = set_uint(strvec, &conf->checkint); if (rc) return rc; @@ -283,7 +325,7 @@ static int checkint_handler(struct config *conf, vector strvec) declare_def_snprint(checkint, print_int) -declare_def_handler(max_checkint, set_int) +declare_def_handler(max_checkint, set_uint) declare_def_snprint(max_checkint, print_int) declare_def_handler(verbosity, set_int) @@ -1057,7 +1099,7 @@ declare_mp_handler(pgfailback, set_pgfailback) declare_mp_snprint(pgfailback, print_pgfailback) static int -set_no_path_retry(vector strvec, void *ptr) +no_path_retry_helper(vector strvec, void *ptr) { int *int_ptr = (int *)ptr; char * buff; @@ -1092,13 +1134,13 @@ print_no_path_retry(char * buff, int len, long v) } } -declare_def_handler(no_path_retry, set_no_path_retry) +declare_def_handler(no_path_retry, no_path_retry_helper) declare_def_snprint(no_path_retry, print_no_path_retry) -declare_ovr_handler(no_path_retry, set_no_path_retry) +declare_ovr_handler(no_path_retry, no_path_retry_helper) declare_ovr_snprint(no_path_retry, print_no_path_retry) -declare_hw_handler(no_path_retry, set_no_path_retry) +declare_hw_handler(no_path_retry, no_path_retry_helper) declare_hw_snprint(no_path_retry, print_no_path_retry) -declare_mp_handler(no_path_retry, set_no_path_retry) +declare_mp_handler(no_path_retry, no_path_retry_helper) declare_mp_snprint(no_path_retry, print_no_path_retry) static int @@ -1366,6 +1408,43 @@ def_uxsock_timeout_handler(struct config *conf, vector strvec) return 0; } +static int +hw_vpd_vendor_handler(struct config *conf, vector strvec) +{ + int i; + char *buff; + + struct hwentry * hwe = VECTOR_LAST_SLOT(conf->hwtable); + if (!hwe) + return 1; + + buff = set_value(strvec); + if (!buff) + return 1; + for (i = 0; i < VPD_VP_ARRAY_SIZE; i++) { + if (strcmp(buff, vpd_vendor_pages[i].name) == 0) { + hwe->vpd_vendor_id = i; + goto out; + } + } + hwe->vpd_vendor_id = 0; +out: + FREE(buff); + return 0; +} + +static int +snprint_hw_vpd_vendor(struct config *conf, char * buff, int len, + const void * data) +{ + const struct hwentry * hwe = (const struct hwentry *)data; + + if (hwe->vpd_vendor_id > 0 && hwe->vpd_vendor_id < VPD_VP_ARRAY_SIZE) + return snprintf(buff, len, "%s", + vpd_vendor_pages[hwe->vpd_vendor_id].name); + return 0; +} + /* * blacklist block handlers */ @@ -1806,6 +1885,7 @@ init_keywords(vector keywords) install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb); install_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay); install_keyword("all_tg_pt", &hw_all_tg_pt_handler, &snprint_hw_all_tg_pt); + install_keyword("vpd_vendor", &hw_vpd_vendor_handler, &snprint_hw_vpd_vendor); install_sublevel_end(); install_keyword_root("overrides", &overrides_handler); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index 72f455e..ee3290c 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -34,6 +34,11 @@ #include "prioritizers/alua_rtpg.h" #include "foreign.h" +struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE] = { + [VPD_VP_UNDEF] = { 0x00, "undef" }, + [VPD_VP_HP3PAR] = { 0xc0, "hp3par" }, +}; + int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice, const char *wwid, int flag, struct path **pp_ptr) @@ -140,27 +145,61 @@ path_discover (vector pathvec, struct config * conf, return pathinfo(pp, conf, flag); } +static void cleanup_udev_enumerate_ptr(void *arg) +{ + struct udev_enumerate *ue; + + if (!arg) + return; + ue = *((struct udev_enumerate**) arg); + if (ue) + (void)udev_enumerate_unref(ue); +} + +static void cleanup_udev_device_ptr(void *arg) +{ + struct udev_device *ud; + + if (!arg) + return; + ud = *((struct udev_device**) arg); + if (ud) + (void)udev_device_unref(ud); +} + int path_discovery (vector pathvec, int flag) { - struct udev_enumerate *udev_iter; + struct udev_enumerate *udev_iter = NULL; struct udev_list_entry *entry; - struct udev_device *udevice; + struct udev_device *udevice = NULL; struct config *conf; - const char *devpath; - int num_paths = 0, total_paths = 0; + int num_paths = 0, total_paths = 0, ret; + + pthread_cleanup_push(cleanup_udev_enumerate_ptr, &udev_iter); + pthread_cleanup_push(cleanup_udev_device_ptr, &udevice); + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); udev_iter = udev_enumerate_new(udev); - if (!udev_iter) - return -ENOMEM; + if (!udev_iter) { + ret = -ENOMEM; + goto out; + } - udev_enumerate_add_match_subsystem(udev_iter, "block"); - udev_enumerate_add_match_is_initialized(udev_iter); - udev_enumerate_scan_devices(udev_iter); + if (udev_enumerate_add_match_subsystem(udev_iter, "block") < 0 || + udev_enumerate_add_match_is_initialized(udev_iter) < 0 || + udev_enumerate_scan_devices(udev_iter) < 0) { + condlog(1, "%s: error setting up udev_enumerate: %m", __func__); + ret = -1; + goto out; + } udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_iter)) { const char *devtype; + const char *devpath; + devpath = udev_list_entry_get_name(entry); condlog(4, "Discover device %s", devpath); udevice = udev_device_new_from_syspath(udev, devpath); @@ -171,25 +210,26 @@ path_discovery (vector pathvec, int flag) devtype = udev_device_get_devtype(udevice); if(devtype && !strncmp(devtype, "disk", 4)) { total_paths++; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); if (path_discover(pathvec, conf, udevice, flag) == PATHINFO_OK) num_paths++; - pthread_cleanup_pop(1); } - udev_device_unref(udevice); + udevice = udev_device_unref(udevice); } - udev_enumerate_unref(udev_iter); + ret = total_paths - num_paths; condlog(4, "Discovered %d/%d paths", num_paths, total_paths); - return (total_paths - num_paths); +out: + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + return ret; } #define declare_sysfs_get_str(fname) \ ssize_t \ sysfs_get_##fname (struct udev_device * udev, char * buff, size_t len) \ { \ - int l; \ + size_t l; \ const char * attr; \ const char * devname; \ \ @@ -589,9 +629,10 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) mpp->fast_io_fail != MP_FAST_IO_FAIL_ZERO && mpp->fast_io_fail != MP_FAST_IO_FAIL_OFF) { /* Check if we need to temporarily increase dev_loss_tmo */ - if (mpp->fast_io_fail >= tmo) { + if ((unsigned int)mpp->fast_io_fail >= tmo) { /* Increase dev_loss_tmo temporarily */ - snprintf(value, 16, "%u", mpp->fast_io_fail + 1); + snprintf(value, sizeof(value), "%u", + (unsigned int)mpp->fast_io_fail + 1); ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo", value, strlen(value)); if (ret <= 0) { @@ -718,14 +759,15 @@ sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp) } int -sysfs_set_scsi_tmo (struct multipath *mpp, int checkint) +sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint) { struct path *pp; int i; - int dev_loss_tmo = mpp->dev_loss; + unsigned int dev_loss_tmo = mpp->dev_loss; if (mpp->no_path_retry > 0) { - uint64_t no_path_retry_tmo = (uint64_t)mpp->no_path_retry * checkint; + uint64_t no_path_retry_tmo = + (uint64_t)mpp->no_path_retry * checkint; if (no_path_retry_tmo > MAX_DEV_LOSS_TMO) no_path_retry_tmo = MAX_DEV_LOSS_TMO; @@ -739,7 +781,8 @@ sysfs_set_scsi_tmo (struct multipath *mpp, int checkint) mpp->alias, dev_loss_tmo); } mpp->dev_loss = dev_loss_tmo; - if (mpp->dev_loss && mpp->fast_io_fail >= (int)mpp->dev_loss) { + if (mpp->dev_loss && mpp->fast_io_fail > 0 && + (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; @@ -833,6 +876,10 @@ get_serial (char * str, int maxlen, int fd) return 1; } +/* + * Side effect: sets pp->tpgs if it could be determined. + * If ALUA calls fail because paths are unreachable, pp->tpgs remains unchanged. + */ static void detect_alua(struct path * pp) { @@ -843,12 +890,28 @@ detect_alua(struct path * pp) if (sysfs_get_timeout(pp, &timeout) <= 0) timeout = DEF_TIMEOUT; - if ((tpgs = get_target_port_group_support(pp, timeout)) <= 0) { + tpgs = get_target_port_group_support(pp, timeout); + if (tpgs == -RTPG_INQUIRY_FAILED) + return; + else if (tpgs <= 0) { pp->tpgs = TPGS_NONE; return; } + + if (pp->fd == -1 || pp->offline) + return; + ret = get_target_port_group(pp, timeout); if (ret < 0 || get_asymmetric_access_state(pp, ret, timeout) < 0) { + int state; + + if (ret == -RTPG_INQUIRY_FAILED) + return; + + state = path_offline(pp); + if (state == PATH_DOWN || state == PATH_PENDING) + return; + pp->tpgs = TPGS_NONE; return; } @@ -870,6 +933,7 @@ static int sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg) { int len = DEFAULT_SGIO_LEN; + int rlen; if (fd < 0) { errno = EBADF; @@ -877,12 +941,11 @@ sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg) } retry: if (0 == do_inq(fd, 0, 1, pg, buff, len)) { - len = get_unaligned_be16(&buff[2]) + 4; - if (len >= maxlen) - return len; - if (len > DEFAULT_SGIO_LEN) - goto retry; - return len; + rlen = get_unaligned_be16(&buff[2]) + 4; + if (rlen <= len || len >= maxlen) + return rlen; + len = (rlen < maxlen)? rlen : maxlen; + goto retry; } return -1; } @@ -907,7 +970,10 @@ get_geometry(struct path *pp) static int parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len) { - int len = get_unaligned_be16(&in[2]); + size_t len = get_unaligned_be16(&in[2]); + + if (out_len == 0) + return 0; /* * Strip leading and trailing whitespace @@ -920,8 +986,8 @@ parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len) } if (len >= out_len) { - condlog(2, "vpd pg80 overflow, %d/%d bytes required", - len + 1, (int)out_len); + condlog(2, "vpd pg80 overflow, %lu/%lu bytes required", + len + 1, out_len); len = out_len - 1; } if (len > 0) { @@ -937,7 +1003,8 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, { const unsigned char *d; const unsigned char *vpd = NULL; - int len = -ENODATA, vpd_type, vpd_len, prio = -1, i, naa_prio; + size_t len, vpd_len, i; + int vpd_type, prio = -1, naa_prio; d = in + 4; while (d < in + in_len) { @@ -1004,106 +1071,138 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len, } d += d[3] + 4; } - if (prio > 0) { - vpd_type = vpd[1] & 0xf; - vpd_len = vpd[3]; + + if (prio <= 0) + return -ENODATA; + /* Need space at least for one digit */ + else if (out_len <= 1) + return 0; + + len = 0; + vpd_type = vpd[1] & 0xf; + vpd_len = vpd[3]; + vpd += 4; + if (vpd_type == 0x2 || vpd_type == 0x3) { + size_t i; + + len = sprintf(out, "%d", vpd_type); + if (2 * vpd_len >= out_len - len) { + condlog(1, "%s: WWID overflow, type %d, %lu/%lu bytes required", + __func__, vpd_type, + 2 * vpd_len + len + 1, out_len); + vpd_len = (out_len - len - 1) / 2; + } + for (i = 0; i < vpd_len; i++) + 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", + __func__, vpd_len); + return -EINVAL; + } else if (vpd_type == 0x8) { + if (!memcmp("eui.", vpd, 4)) + out[0] = '2'; + else if (!memcmp("naa.", vpd, 4)) + out[0] = '3'; + else + out[0] = '8'; + vpd += 4; - if (vpd_type == 0x2 || vpd_type == 0x3) { - int i; - - assert(out_len >= 2); - len = sprintf(out, "%d", vpd_type); - if (2 * vpd_len >= out_len - len) { - condlog(1, "%s: WWID overflow, type %d, %d/%lu bytes required", - __func__, vpd_type, - 2 * vpd_len + len + 1, out_len); - vpd_len = (out_len - len - 1) / 2; - } - for (i = 0; i < vpd_len; i++) - len += sprintf(out + len, - "%02x", vpd[i]); - } else if (vpd_type == 0x8) { - if (!memcmp("eui.", vpd, 4)) - out[0] = '2'; - else if (!memcmp("naa.", vpd, 4)) - out[0] = '3'; - else - out[0] = '8'; - - vpd += 4; - len = vpd_len - 4; - while (len > 2 && vpd[len - 2] == '\0') - --len; - if (len > out_len - 1) { - condlog(1, "%s: WWID overflow, type 8/%c, %d/%lu bytes required", - __func__, out[0], len + 1, out_len); - len = out_len - 1; - } + len = vpd_len - 4; + 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", + __func__, out[0], len + 1, out_len); + len = out_len - 1; + } - if (out[0] == '8') - for (i = 0; i < len; ++i) - out[1 + i] = vpd[i]; - else - for (i = 0; i < len; ++i) - out[1 + i] = tolower(vpd[i]); + if (out[0] == '8') + for (i = 0; i < len; ++i) + out[1 + i] = vpd[i]; + else + for (i = 0; i < len; ++i) + out[1 + i] = tolower(vpd[i]); - /* designator should be 0-terminated, but let's make sure */ - out[len] = '\0'; + /* designator should be 0-terminated, but let's make sure */ + out[len] = '\0'; - } else if (vpd_type == 0x1) { - const unsigned char *p; - int p_len; - - out[0] = '1'; - len = 1; - p = vpd; - while ((p = memchr(vpd, ' ', vpd_len))) { - p_len = p - vpd; - if (len + p_len > out_len - 1) { - condlog(1, "%s: WWID overflow, type 1, %d/%lu bytes required", - __func__, len + p_len, out_len); - p_len = out_len - len - 1; - } - memcpy(out + len, vpd, p_len); - len += p_len; - if (len >= out_len - 1) { - out[len] = '\0'; - break; - } - out[len] = '_'; - len ++; - if (len >= out_len - 1) { - out[len] = '\0'; - break; - } - vpd = p; - vpd_len -= p_len; - while (vpd && *vpd == ' ') { - vpd++; - vpd_len --; - } + } else if (vpd_type == 0x1) { + const unsigned char *p; + size_t p_len; + + out[0] = '1'; + len = 1; + 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", + __func__, len + p_len, out_len); + p_len = out_len - len - 1; + } + memcpy(out + len, vpd, p_len); + len += p_len; + if (len >= out_len - 1) { + out[len] = '\0'; + break; } - 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, %d/%lu bytes required", - __func__, len + p_len + 1, out_len); - p_len = out_len - len - 1; - } - memcpy(out + len, vpd, p_len); - len += p_len; + out[len] = '_'; + len ++; + if (len >= out_len - 1) { out[len] = '\0'; + break; } - if (len > 1 && out[len - 1] == '_') { - out[len - 1] = '\0'; - len--; + vpd = p; + vpd_len -= p_len; + while (vpd && *vpd == ' ') { + vpd++; + vpd_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", + __func__, len + p_len + 1, out_len); + p_len = out_len - len - 1; } + memcpy(out + len, vpd, p_len); + len += p_len; + out[len] = '\0'; + } + if (len > 1 && out[len - 1] == '_') { + out[len - 1] = '\0'; + len--; } } return len; } static int +parse_vpd_c0_hp3par(const unsigned char *in, size_t in_len, + char *out, size_t out_len) +{ + size_t 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); + 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", + len); + return -EINVAL; + } + memcpy(out, &in[44], len); + out[out_len - 1] = '\0'; + return len; +} + +static int get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen) { int len, buff_len; @@ -1135,7 +1234,7 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen) } int -get_vpd_sgio (int fd, int pg, char * str, int maxlen) +get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen) { int len, buff_len; unsigned char buff[4096]; @@ -1170,7 +1269,9 @@ get_vpd_sgio (int fd, int pg, char * str, int maxlen) len = (buff_len <= maxlen)? buff_len : maxlen; memcpy (str, buff, len); } - } else + } else if (pg == 0xc0 && vend_id == VPD_VP_HP3PAR) + len = parse_vpd_c0_hp3par(buff, buff_len, str, maxlen); + else len = -ENOSYS; return len; @@ -1536,14 +1637,34 @@ sysfs_pathinfo(struct path * pp, vector hwtable) } static void -scsi_ioctl_pathinfo (struct path * pp, struct config *conf, int mask) +scsi_ioctl_pathinfo (struct path * pp, int mask) { struct udev_device *parent; const char *attr_path = NULL; + int vpd_id; if (!(mask & DI_SERIAL)) return; + select_vpd_vendor_id(pp); + vpd_id = pp->vpd_vendor_id; + + if (vpd_id != VPD_VP_UNDEF) { + char vpd_data[VPD_DATA_SIZE] = {0}; + + if (get_vpd_sgio(pp->fd, vpd_vendor_pages[vpd_id].pg, vpd_id, + vpd_data, sizeof(vpd_data)) < 0) + condlog(3, "%s: failed to get extra vpd data", pp->dev); + else { + vpd_data[VPD_DATA_SIZE - 1] = '\0'; + if (pp->vpd_data) + free(pp->vpd_data); + pp->vpd_data = strdup(vpd_data); + if (!pp->vpd_data) + condlog(0, "%s: failed to allocate space for vpd data", pp->dev); + } + } + parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); @@ -1611,12 +1732,10 @@ get_state (struct path * pp, struct config *conf, int daemon, int oldstate) if (pp->mpp && !c->mpcontext) checker_mp_init(c, &pp->mpp->mpcontext); checker_clear_message(c); - if (daemon) { - if (conf->force_sync == 0) - checker_set_async(c); - else - checker_set_sync(c); - } + if (conf->force_sync == 0) + checker_set_async(c); + else + checker_set_sync(c); if (!conf->checker_timeout && sysfs_get_timeout(pp, &(c->timeout)) <= 0) c->timeout = DEF_TIMEOUT; @@ -1631,11 +1750,10 @@ get_state (struct path * pp, struct config *conf, int daemon, int oldstate) } static int -get_prio (struct path * pp) +get_prio (struct path * pp, int timeout) { struct prio * p; struct config *conf; - int checker_timeout; int old_prio; if (!pp) @@ -1654,11 +1772,8 @@ get_prio (struct path * pp) return 1; } } - conf = get_multipath_config(); - checker_timeout = conf->checker_timeout; - put_multipath_config(conf); old_prio = pp->priority; - pp->priority = prio_getprio(p, pp, checker_timeout); + pp->priority = prio_getprio(p, pp, timeout); if (pp->priority < 0) { /* this changes pp->offline, but why not */ int state = path_offline(pp); @@ -1687,7 +1802,7 @@ get_prio (struct path * pp) * Returns a pointer to the position where "end" was moved to. */ static char -*skip_zeroes_backward(char* start, int *len, char *end) +*skip_zeroes_backward(char* start, size_t *len, char *end) { char *p = end; @@ -1713,10 +1828,10 @@ static char * Otherwise, returns 0. */ static int -fix_broken_nvme_wwid(struct path *pp, const char *value, int size) +fix_broken_nvme_wwid(struct path *pp, const char *value, size_t size) { static const char _nvme[] = "nvme."; - int len, i; + size_t len, i; char mangled[256]; char *p; @@ -1810,7 +1925,7 @@ static ssize_t uid_fallback(struct path *pp, int path_state, if (len < 0 && path_state == PATH_UP) { condlog(1, "%s: failed to get sysfs uid: %s", pp->dev, strerror(-len)); - len = get_vpd_sgio(pp->fd, 0x83, pp->wwid, + len = get_vpd_sgio(pp->fd, 0x83, 0, pp->wwid, WWID_SIZE); *origin = "sgio"; } @@ -2015,7 +2130,7 @@ int pathinfo(struct path *pp, struct config *conf, int mask) get_geometry(pp); if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI) - scsi_ioctl_pathinfo(pp, conf, mask); + scsi_ioctl_pathinfo(pp, mask); if (pp->bus == SYSFS_BUS_CCISS && mask & DI_SERIAL) cciss_ioctl_pathinfo(pp); @@ -2065,11 +2180,13 @@ int pathinfo(struct path *pp, struct config *conf, int mask) /* * Retrieve path priority, even for PATH_DOWN paths if it has never - * been successfully obtained before. + * been successfully obtained before. If path is down don't try + * for too long. */ if ((mask & DI_PRIO) && path_state == PATH_UP && strlen(pp->wwid)) { if (pp->state != PATH_DOWN || pp->priority == PRIO_UNDEF) { - get_prio(pp); + get_prio(pp, (pp->state != PATH_DOWN)? + (conf->checker_timeout * 1000) : 10); } } diff --git a/libmultipath/discovery.h b/libmultipath/discovery.h index 8d04c2a..6444887 100644 --- a/libmultipath/discovery.h +++ b/libmultipath/discovery.h @@ -35,14 +35,14 @@ int path_get_tpgs(struct path *pp); /* This function never returns TPGS_UNDEF */ int do_tur (char *); int path_offline (struct path *); int get_state (struct path * pp, struct config * conf, int daemon, int state); -int get_vpd_sgio (int fd, int pg, char * str, int maxlen); +int get_vpd_sgio (int fd, int pg, int vend_id, char * str, int maxlen); int pathinfo (struct path * pp, struct config * conf, int mask); int alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice, const char *wwid, int flag, struct path **pp_ptr); int store_pathinfo (vector pathvec, struct config *conf, struct udev_device *udevice, int flag, struct path **pp_ptr); -int sysfs_set_scsi_tmo (struct multipath *mpp, int checkint); +int sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint); int sysfs_get_timeout(const struct path *pp, unsigned int *timeout); int sysfs_get_host_pci_name(const struct path *pp, char *pci_name); int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address); diff --git a/libmultipath/dm-generic.c b/libmultipath/dm-generic.c index d752991..1b42fa0 100644 --- a/libmultipath/dm-generic.c +++ b/libmultipath/dm-generic.c @@ -31,7 +31,8 @@ dm_mp_get_pgs(const struct gen_multipath *gmp) struct pathgroup, dm_pathgroup_to_gen); } -static void dm_mp_rel_pgs(const struct gen_multipath *gmp, +static void dm_mp_rel_pgs(__attribute__((unused)) + const struct gen_multipath *gmp, const struct _vector* v) { vector_free_const(v); @@ -44,7 +45,8 @@ dm_pg_get_paths(const struct gen_pathgroup *gpg) struct path, dm_path_to_gen); } -static void dm_mp_rel_paths(const struct gen_pathgroup *gpg, +static void dm_mp_rel_paths(__attribute__((unused)) + const struct gen_pathgroup *gpg, const struct _vector* v) { vector_free_const(v); diff --git a/libmultipath/file.c b/libmultipath/file.c index 8727f16..72f1d24 100644 --- a/libmultipath/file.c +++ b/libmultipath/file.c @@ -74,7 +74,7 @@ ensure_directories_exist(const char *str, mode_t dir_mode) } static void -sigalrm(int sig) +sigalrm(__attribute__((unused)) int sig) { /* do nothing */ } @@ -157,7 +157,8 @@ open_file(const char *file, int *can_write, const char *header) if (*can_write == 0) goto fail; /* If file is empty, write the header */ - size_t len = strlen(header); + int len = strlen(header); + if (write(fd, header, len) != len) { condlog(0, "Cannot write header to file %s : %s", file, diff --git a/libmultipath/foreign.c b/libmultipath/foreign.c index 4b34e14..0159a83 100644 --- a/libmultipath/foreign.c +++ b/libmultipath/foreign.c @@ -50,7 +50,7 @@ static void wrlock_foreigns(void) pthread_rwlock_wrlock(&foreign_lock); } -static void unlock_foreigns(void *unused) +static void unlock_foreigns(__attribute__((unused)) void *unused) { pthread_rwlock_unlock(&foreign_lock); } @@ -148,7 +148,7 @@ static int _init_foreign(const char *multipath_dir, const char *enable) (void)regerror(r, enable_re, errbuf, sizeof(errbuf)); condlog (2, "%s: error compiling enable_foreign = \"%s\": \"%s\"", __func__, str, errbuf); - free_pre(&enable_re); + goto out_free_pre; } } @@ -157,13 +157,13 @@ static int _init_foreign(const char *multipath_dir, const char *enable) if (r == 0) { condlog(3, "%s: no foreign multipath libraries found", __func__); - return 0; + goto out_free_pre; } else if (r < 0) { - r = errno; - condlog(1, "%s: error %d scanning foreign multipath libraries", - __func__, r); + r = -errno; + condlog(1, "%s: error scanning foreign multipath libraries: %m", + __func__); _cleanup_foreign(); - return -r; + goto out_free_pre; } sr.di = di; @@ -172,7 +172,7 @@ static int _init_foreign(const char *multipath_dir, const char *enable) for (i = 0; i < r; i++) { const char *msg, *fn, *c; struct foreign *fgn; - int len, namesz; + size_t len, namesz; fn = di[i]->d_name; @@ -249,9 +249,11 @@ static int _init_foreign(const char *multipath_dir, const char *enable) dl_err: free_foreign(fgn); } + r = 0; pthread_cleanup_pop(1); /* free_scandir_result */ +out_free_pre: pthread_cleanup_pop(1); /* free_pre */ - return 0; + return r; } int init_foreign(const char *multipath_dir, const char *enable) diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c index 7e654ec..09cdddf 100644 --- a/libmultipath/foreign/nvme.c +++ b/libmultipath/foreign/nvme.c @@ -123,7 +123,8 @@ nvme_mp_get_pgs(const struct gen_multipath *gmp) { } static void -nvme_mp_rel_pgs(const struct gen_multipath *gmp, const struct _vector *v) +nvme_mp_rel_pgs(__attribute__((unused)) const struct gen_multipath *gmp, + __attribute__((unused)) const struct _vector *v) { /* empty */ } @@ -207,7 +208,8 @@ nvme_pg_get_paths(const struct gen_pathgroup *gpg) { } static void -nvme_pg_rel_paths(const struct gen_pathgroup *gpg, const struct _vector *v) +nvme_pg_rel_paths(__attribute__((unused)) const struct gen_pathgroup *gpg, + __attribute__((unused)) const struct _vector *v) { /* empty */ } @@ -331,8 +333,9 @@ static int snprint_nvme_pg(const struct gen_pathgroup *gmp, } } -static int nvme_style(const struct gen_multipath* gm, - char *buf, int len, int verbosity) +static int nvme_style(__attribute__((unused)) const struct gen_multipath* gm, + char *buf, int len, + __attribute__((unused)) int verbosity) { int n = snprintf(buf, len, "%%w [%%G]:%%d %%s"); @@ -588,8 +591,7 @@ static void test_ana_support(struct nvme_map *map, struct udev_device *ctl) return; dev_t = udev_device_get_sysattr_value(ctl, "dev"); - if (snprintf(sys_path, sizeof(sys_path), "/dev/char/%s", dev_t) - >= sizeof(sys_path)) + if (safe_sprintf(sys_path, "/dev/char/%s", dev_t)) return; fd = open(sys_path, O_RDONLY); @@ -660,8 +662,7 @@ static void _find_controllers(struct context *ctx, struct nvme_map *map) char *fn = di[i]->d_name; struct udev_device *ctrl, *udev; - if (snprintf(pathbuf + n, sizeof(pathbuf) - n, "/%s", fn) - >= sizeof(pathbuf) - n) + if (safe_snprintf(pathbuf + n, sizeof(pathbuf) - n, "/%s", fn)) continue; if (realpath(pathbuf, realbuf) == NULL) { condlog(3, "%s: %s: realpath: %s", __func__, THIS, @@ -821,7 +822,8 @@ int add(struct context *ctx, struct udev_device *ud) return rc; } -int change(struct context *ctx, struct udev_device *ud) +int change(__attribute__((unused)) struct context *ctx, + __attribute__((unused)) struct udev_device *ud) { condlog(5, "%s called for \"%s\"", __func__, THIS); return FOREIGN_IGNORED; @@ -903,7 +905,8 @@ const struct _vector *get_multipaths(const struct context *ctx) return ctx->mpvec; } -void release_multipaths(const struct context *ctx, const struct _vector *mpvec) +void release_multipaths(__attribute__((unused)) const struct context *ctx, + __attribute__((unused)) const struct _vector *mpvec) { condlog(5, "%s called for \"%s\"", __func__, THIS); /* NOP */ @@ -927,7 +930,8 @@ const struct _vector * get_paths(const struct context *ctx) return paths; } -void release_paths(const struct context *ctx, const struct _vector *mpvec) +void release_paths(__attribute__((unused)) const struct context *ctx, + const struct _vector *mpvec) { condlog(5, "%s called for \"%s\"", __func__, THIS); vector_free_const(mpvec); diff --git a/libmultipath/generic.c b/libmultipath/generic.c index 0d1e632..5f03b9e 100644 --- a/libmultipath/generic.c +++ b/libmultipath/generic.c @@ -21,7 +21,7 @@ #include "structs.h" int generic_style(const struct gen_multipath* gm, - char *buf, int len, int verbosity) + char *buf, int len, __attribute__((unused)) int verbosity) { char alias_buf[WWID_SIZE]; char wwid_buf[WWID_SIZE]; diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c index 16627ec..d1fcfdb 100644 --- a/libmultipath/hwtable.c +++ b/libmultipath/hwtable.c @@ -117,6 +117,7 @@ static struct hwentry default_hw[] = { .no_path_retry = 18, .fast_io_fail = 10, .dev_loss = MAX_DEV_LOSS_TMO, + .vpd_vendor_id = VPD_VP_HP3PAR, }, { /* RA8000 / ESA12000 */ @@ -1039,7 +1040,12 @@ static struct hwentry default_hw[] = { /* FlashArray */ .vendor = "PURE", .product = "FlashArray", - .pgpolicy = MULTIBUS, + .pgpolicy = GROUP_BY_PRIO, + .pgfailback = -FAILBACK_IMMEDIATE, + .hwhandler = "1 alua", + .prio_name = PRIO_ALUA, + .fast_io_fail = 10, + .max_sectors_kb = 4096, }, /* * Huawei diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index 554b777..1b9cd6c 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -54,7 +54,7 @@ struct io_err_stat_pathvec { struct dio_ctx { struct timespec io_starttime; - int blksize; + unsigned int blksize; void *buf; struct iocb io; }; @@ -84,7 +84,7 @@ io_context_t ioctx; static void cancel_inflight_io(struct io_err_stat_path *pp); -static void rcu_unregister(void *param) +static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } @@ -128,7 +128,7 @@ static int setup_directio_ctx(struct io_err_stat_path *p) { unsigned long pgsize = getpagesize(); char fpath[PATH_MAX]; - int blksize = 0; + unsigned int blksize = 0; int i; if (snprintf(fpath, PATH_MAX, "/dev/%s", p->devname) >= PATH_MAX) @@ -357,7 +357,7 @@ int io_err_stat_handle_pathfail(struct path *path) if (path->state != PATH_DOWN) { struct config *conf; int oldstate = path->state; - int checkint; + unsigned int checkint; conf = get_multipath_config(); checkint = conf->checkint; @@ -383,7 +383,7 @@ int need_io_err_check(struct path *pp) if (uatomic_read(&io_err_thread_running) == 0) return 0; - if (pp->mpp->nr_active <= 0) { + if (count_active_paths(pp->mpp) <= 0) { io_err_stat_log(2, "%s: recover path early", pp->dev); goto recover; } @@ -481,7 +481,7 @@ static int poll_io_err_stat(struct vectors *vecs, struct io_err_stat_path *pp) */ path->tick = 1; - } else if (path->mpp && path->mpp->nr_active > 0) { + } else if (path->mpp && count_active_paths(path->mpp) > 0) { io_err_stat_log(3, "%s: keep failing the dm path %s", path->mpp->alias, path->dev); path->io_err_pathfail_cnt = PATH_IO_ERR_WAITING_TO_CHECK; @@ -689,7 +689,7 @@ static void cleanup_unlock(void *arg) pthread_mutex_unlock((pthread_mutex_t*) arg); } -static void cleanup_exited(void *arg) +static void cleanup_exited(__attribute__((unused)) void *arg) { uatomic_set(&io_err_thread_running, 0); } diff --git a/libmultipath/log.h b/libmultipath/log.h index 6551b5c..d2448f6 100644 --- a/libmultipath/log.h +++ b/libmultipath/log.h @@ -34,7 +34,8 @@ extern struct logarea* la; int log_init (char * progname, int size); void log_close (void); void log_reset (char * progname); -int log_enqueue (int prio, const char * fmt, va_list ap); +int log_enqueue (int prio, const char * fmt, va_list ap) + __attribute__((format(printf, 2, 0))); int log_dequeue (void *); void log_syslog (void *); void dump_logmsg (void *); diff --git a/libmultipath/log_pthread.c b/libmultipath/log_pthread.c index be57bb1..15baef8 100644 --- a/libmultipath/log_pthread.c +++ b/libmultipath/log_pthread.c @@ -56,7 +56,7 @@ static void flush_logqueue (void) } while (empty == 0); } -static void * log_thread (void * et) +static void * log_thread (__attribute__((unused)) void * et) { int running; diff --git a/libmultipath/log_pthread.h b/libmultipath/log_pthread.h index 7e138a0..810ac92 100644 --- a/libmultipath/log_pthread.h +++ b/libmultipath/log_pthread.h @@ -3,7 +3,8 @@ #include -void log_safe(int prio, const char * fmt, va_list ap); +void log_safe(int prio, const char * fmt, va_list ap) + __attribute__((format(printf, 2, 0))); void log_thread_start(pthread_attr_t *attr); void log_thread_reset (void); void log_thread_stop(void); diff --git a/libmultipath/nvme/linux/nvme.h b/libmultipath/nvme/linux/nvme.h index 68000eb..a697554 100644 --- a/libmultipath/nvme/linux/nvme.h +++ b/libmultipath/nvme/linux/nvme.h @@ -124,6 +124,9 @@ enum { NVME_REG_BPINFO = 0x0040, /* Boot Partition Information */ NVME_REG_BPRSEL = 0x0044, /* Boot Partition Read Select */ NVME_REG_BPMBL = 0x0048, /* Boot Partition Memory Buffer Location */ + NVME_REG_PMRCAP = 0x0e00, /* Persistent Memory Capabilities */ + NVME_REG_PMRCTL = 0x0e04, /* Persistent Memory Region Control */ + NVME_REG_PMRSTS = 0x0e08, /* Persistent Memory Region Status */ NVME_REG_DBS = 0x1000, /* SQ 0 Tail Doorbell */ }; @@ -221,7 +224,11 @@ struct nvme_id_ctrl { __le32 oaes; __le32 ctratt; __le16 rrls; - __u8 rsvd102[154]; + __u8 rsvd102[26]; + __le16 crdt1; + __le16 crdt2; + __le16 crdt3; + __u8 rsvd134[122]; __le16 oacs; __u8 acl; __u8 aerl; @@ -302,6 +309,8 @@ enum { NVME_CTRL_CTRATT_READ_RECV_LVLS = 1 << 3, NVME_CTRL_CTRATT_ENDURANCE_GROUPS = 1 << 4, NVME_CTRL_CTRATT_PREDICTABLE_LAT = 1 << 5, + NVME_CTRL_CTRATT_NAMESPACE_GRANULARITY = 1 << 7, + NVME_CTRL_CTRATT_UUID_LIST = 1 << 9, }; struct nvme_lbaf { @@ -332,7 +341,12 @@ struct nvme_id_ns { __le16 nabspf; __le16 noiob; __u8 nvmcap[16]; - __u8 rsvd64[28]; + __le16 npwg; + __le16 npwa; + __le16 npdg; + __le16 npda; + __le16 nows; + __u8 rsvd74[18]; __le32 anagrpid; __u8 rsvd96[3]; __u8 nsattr; @@ -355,6 +369,9 @@ enum { NVME_ID_CNS_NS_PRESENT = 0x11, NVME_ID_CNS_CTRL_NS_LIST = 0x12, NVME_ID_CNS_CTRL_LIST = 0x13, + NVME_ID_CNS_SCNDRY_CTRL_LIST = 0x15, + NVME_ID_CNS_NS_GRANULARITY = 0x16, + NVME_ID_CNS_UUID_LIST = 0x17, }; enum { @@ -425,26 +442,56 @@ struct nvme_id_nvmset { struct nvme_nvmset_attr_entry ent[NVME_MAX_NVMSET]; }; -/* Derived from 1.3a Figure 101: Get Log Page – Telemetry Host - * -Initiated Log (Log Identifier 07h) +struct nvme_id_ns_granularity_list_entry { + __le64 namespace_size_granularity; + __le64 namespace_capacity_granularity; +}; + +struct nvme_id_ns_granularity_list { + __le32 attributes; + __u8 num_descriptors; + __u8 rsvd[27]; + struct nvme_id_ns_granularity_list_entry entry[16]; +}; + +#define NVME_MAX_UUID_ENTRIES 128 +struct nvme_id_uuid_list_entry { + __u8 header; + __u8 rsvd1[15]; + __u8 uuid[16]; +}; + +struct nvme_id_uuid_list { + struct nvme_id_uuid_list_entry entry[NVME_MAX_UUID_ENTRIES]; +}; + +/** + * struct nvme_telemetry_log_page_hdr - structure for telemetry log page + * @lpi: Log page identifier + * @iee_oui: IEEE OUI Identifier + * @dalb1: Data area 1 last block + * @dalb2: Data area 2 last block + * @dalb3: Data area 3 last block + * @ctrlavail: Controller initiated data available + * @ctrldgn: Controller initiated telemetry Data Generation Number + * @rsnident: Reason Identifier + * @telemetry_dataarea: Contains telemetry data block + * + * This structure can be used for both telemetry host-initiated log page + * and controller-initiated log page. */ struct nvme_telemetry_log_page_hdr { - __u8 lpi; /* Log page identifier */ - __u8 rsvd[4]; - __u8 iee_oui[3]; - __u16 dalb1; /* Data area 1 last block */ - __u16 dalb2; /* Data area 2 last block */ - __u16 dalb3; /* Data area 3 last block */ - __u8 rsvd1[368]; /* TODO verify */ - __u8 ctrlavail; /* Controller initiated data avail?*/ - __u8 ctrldgn; /* Controller initiated telemetry Data Gen # */ - __u8 rsnident[128]; - /* We'll have to double fetch so we can get the header, - * parse dalb1->3 determine how much size we need for the - * log then alloc below. Or just do a secondary non-struct - * allocation. - */ - __u8 telemetry_dataarea[0]; + __u8 lpi; + __u8 rsvd[4]; + __u8 iee_oui[3]; + __le16 dalb1; + __le16 dalb2; + __le16 dalb3; + __u8 rsvd1[368]; + __u8 ctrlavail; + __u8 ctrldgn; + __u8 rsnident[128]; + __u8 telemetry_dataarea[0]; }; struct nvme_endurance_group_log { @@ -513,6 +560,21 @@ struct nvme_fw_slot_info_log { __u8 rsvd64[448]; }; +struct nvme_lba_status_desc { + __u64 dslba; + __u32 nlb; + __u8 rsvd_12; + __u8 status; + __u8 rsvd_15_14[2]; +}; + +struct nvme_lba_status { + __u32 nlsd; + __u8 cmpc; + __u8 rsvd_7_5[3]; + struct nvme_lba_status_desc descs[0]; +}; + /* NVMe Namespace Write Protect State */ enum { NVME_NS_NO_WRITE_PROTECT = 0, @@ -534,6 +596,7 @@ enum { NVME_CMD_EFFECTS_NIC = 1 << 3, NVME_CMD_EFFECTS_CCC = 1 << 4, NVME_CMD_EFFECTS_CSE_MASK = 3 << 16, + NVME_CMD_EFFECTS_UUID_SEL = 1 << 19, }; struct nvme_effects_log { @@ -581,9 +644,6 @@ enum { NVME_AER_SMART = 1, NVME_AER_CSS = 6, NVME_AER_VS = 7, - NVME_AER_NOTICE_NS_CHANGED = 0x0002, - NVME_AER_NOTICE_ANA = 0x0003, - NVME_AER_NOTICE_FW_ACT_STARTING = 0x0102, }; struct nvme_lba_range_type { @@ -606,12 +666,13 @@ enum { NVME_LBART_ATTRIB_HIDE = 1 << 1, }; +/* Predictable Latency Mode - Deterministic Threshold Configuration Data */ struct nvme_plm_config { - __u16 enable_event; + __le16 enable_event; __u8 rsvd2[30]; - __u64 dtwin_reads_thresh; - __u64 dtwin_writes_thresh; - __u64 dtwin_time_thresh; + __le64 dtwin_reads_thresh; + __le64 dtwin_writes_thresh; + __le64 dtwin_time_thresh; __u8 rsvd56[456]; }; @@ -665,6 +726,7 @@ enum nvme_opcode { nvme_cmd_compare = 0x05, nvme_cmd_write_zeroes = 0x08, nvme_cmd_dsm = 0x09, + nvme_cmd_verify = 0x0c, nvme_cmd_resv_register = 0x0d, nvme_cmd_resv_report = 0x0e, nvme_cmd_resv_acquire = 0x11, @@ -892,6 +954,7 @@ enum nvme_admin_opcode { nvme_admin_security_send = 0x81, nvme_admin_security_recv = 0x82, nvme_admin_sanitize_nvm = 0x84, + nvme_admin_get_lba_status = 0x86, }; enum { @@ -921,6 +984,8 @@ enum { NVME_FEAT_RRL = 0x12, NVME_FEAT_PLM_CONFIG = 0x13, NVME_FEAT_PLM_WINDOW = 0x14, + NVME_FEAT_HOST_BEHAVIOR = 0x16, + NVME_FEAT_SANITIZE = 0x17, NVME_FEAT_SW_PROGRESS = 0x80, NVME_FEAT_HOST_ID = 0x81, NVME_FEAT_RESV_MASK = 0x82, @@ -972,6 +1037,7 @@ enum { NVME_SANITIZE_LOG_COMPLETED_SUCCESS = 0x0001, NVME_SANITIZE_LOG_IN_PROGESS = 0x0002, NVME_SANITIZE_LOG_COMPLETED_FAILED = 0x0003, + NVME_SANITIZE_LOG_ND_COMPLETED_SUCCESS = 0x0004, }; enum { @@ -1131,6 +1197,9 @@ struct nvme_sanitize_log_page { __le32 est_ovrwrt_time; __le32 est_blk_erase_time; __le32 est_crypto_erase_time; + __le32 est_ovrwrt_time_with_no_deallocate; + __le32 est_blk_erase_time_with_no_deallocate; + __le32 est_crypto_erase_time_with_no_deallocate; }; /* @@ -1315,6 +1384,12 @@ static inline bool nvme_is_write(struct nvme_command *cmd) } enum { + NVME_SCT_GENERIC = 0x0, + NVME_SCT_CMD_SPECIFIC = 0x1, + NVME_SCT_MEDIA = 0x2, +}; + +enum { /* * Generic Command Status: */ @@ -1344,6 +1419,7 @@ enum { NVME_SC_SANITIZE_IN_PROGRESS = 0x1D, NVME_SC_NS_WRITE_PROTECTED = 0x20, + NVME_SC_CMD_INTERRUPTED = 0x21, NVME_SC_LBA_RANGE = 0x80, NVME_SC_CAP_EXCEEDED = 0x81, @@ -1372,9 +1448,9 @@ enum { NVME_SC_FW_NEEDS_SUBSYS_RESET = 0x110, NVME_SC_FW_NEEDS_RESET = 0x111, NVME_SC_FW_NEEDS_MAX_TIME = 0x112, - NVME_SC_FW_ACIVATE_PROHIBITED = 0x113, + NVME_SC_FW_ACTIVATE_PROHIBITED = 0x113, NVME_SC_OVERLAPPING_RANGE = 0x114, - NVME_SC_NS_INSUFFICENT_CAP = 0x115, + NVME_SC_NS_INSUFFICIENT_CAP = 0x115, NVME_SC_NS_ID_UNAVAILABLE = 0x116, NVME_SC_NS_ALREADY_ATTACHED = 0x118, NVME_SC_NS_IS_PRIVATE = 0x119, @@ -1382,6 +1458,7 @@ enum { NVME_SC_THIN_PROV_NOT_SUPP = 0x11b, NVME_SC_CTRL_LIST_INVALID = 0x11c, NVME_SC_BP_WRITE_PROHIBITED = 0x11e, + NVME_SC_PMR_SAN_PROHIBITED = 0x123, /* * I/O Command Set Specific - NVM commands: @@ -1422,6 +1499,7 @@ enum { NVME_SC_ANA_INACCESSIBLE = 0x302, NVME_SC_ANA_TRANSITION = 0x303, + NVME_SC_CRD = 0x1800, NVME_SC_DNR = 0x4000, }; diff --git a/libmultipath/nvme/nvme-ioctl.c b/libmultipath/nvme/nvme-ioctl.c index 70a16ce..6959976 100644 --- a/libmultipath/nvme/nvme-ioctl.c +++ b/libmultipath/nvme/nvme-ioctl.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -177,6 +178,22 @@ int nvme_compare(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, reftag, apptag, appmask, data, metadata); } +int nvme_verify(int fd, __u32 nsid, __u64 slba, __u16 nblocks, + __u16 control, __u32 reftag, __u16 apptag, __u16 appmask) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_verify, + .nsid = nsid, + .cdw10 = slba & 0xffffffff, + .cdw11 = slba >> 32, + .cdw12 = nblocks | (control << 16), + .cdw14 = reftag, + .cdw15 = apptag | (appmask << 16), + }; + + return nvme_submit_io_passthru(fd, &cmd); +} + int nvme_passthru_io(int fd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, @@ -370,6 +387,11 @@ int nvme_identify_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data) return nvme_identify(fd, nsid, (cntid << 16) | cns, data); } +int nvme_identify_secondary_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data) +{ + return nvme_identify(fd, nsid, (cntid << 16) | NVME_ID_CNS_SCNDRY_CTRL_LIST, data); +} + int nvme_identify_ns_descs(int fd, __u32 nsid, void *data) { @@ -381,8 +403,18 @@ int nvme_identify_nvmset(int fd, __u16 nvmset_id, void *data) return nvme_identify13(fd, 0, NVME_ID_CNS_NVMSET_LIST, nvmset_id, data); } -int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, - __u16 lsi, bool rae, __u32 data_len, void *data) +int nvme_identify_ns_granularity(int fd, void *data) +{ + return nvme_identify13(fd, 0, NVME_ID_CNS_NS_GRANULARITY, 0, data); +} + +int nvme_identify_uuid(int fd, void *data) +{ + return nvme_identify(fd, 0, NVME_ID_CNS_UUID_LIST, data); +} + +int nvme_get_log14(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, + __u16 lsi, bool rae, __u8 uuid_ix, __u32 data_len, void *data) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_get_log_page, @@ -400,6 +432,7 @@ int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, cmd.cdw11 = numdu | (lsi << 16); cmd.cdw12 = lpo; cmd.cdw13 = (lpo >> 32); + cmd.cdw14 = uuid_ix; return nvme_submit_admin_passthru(fd, &cmd); @@ -498,7 +531,7 @@ int nvme_self_test_log(int fd, struct nvme_self_test_log *self_test_log) int nvme_effects_log(int fd, struct nvme_effects_log_page *effects_log) { - return nvme_get_log(fd, 0, NVME_LOG_CMD_EFFECTS, false, + return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_CMD_EFFECTS, false, sizeof(*effects_log), effects_log); } @@ -542,77 +575,61 @@ int nvme_set_feature(int fd, __u32 nsid, __u8 fid, __u32 value, __u32 cdw12, cdw12, data_len, data, result); } -static int nvme_property(int fd, __u8 fctype, __le32 off, __le64 *value, __u8 attrib) -{ - int err; - struct nvme_admin_cmd cmd = { - .opcode = nvme_fabrics_command, - .cdw10 = attrib, - .cdw11 = off, - }; - - if (!value) { - errno = EINVAL; - return -errno; - } - - if (fctype == nvme_fabrics_type_property_get){ - cmd.nsid = nvme_fabrics_type_property_get; - } else if(fctype == nvme_fabrics_type_property_set) { - cmd.nsid = nvme_fabrics_type_property_set; - cmd.cdw12 = *value; - } else { - errno = EINVAL; - return -errno; - } - err = nvme_submit_admin_passthru(fd, &cmd); - if (!err && fctype == nvme_fabrics_type_property_get) - *value = cpu_to_le64(cmd.result); - return err; +/* + * Perform the opposite operation of the byte-swapping code at the start of the + * kernel function nvme_user_cmd(). + */ +static void nvme_to_passthru_cmd(struct nvme_passthru_cmd *pcmd, + const struct nvme_command *ncmd) +{ + assert(sizeof(*ncmd) < sizeof(*pcmd)); + memset(pcmd, 0, sizeof(*pcmd)); + pcmd->opcode = ncmd->common.opcode; + pcmd->flags = ncmd->common.flags; + pcmd->rsvd1 = ncmd->common.command_id; + pcmd->nsid = le32_to_cpu(ncmd->common.nsid); + pcmd->cdw2 = le32_to_cpu(ncmd->common.cdw2[0]); + pcmd->cdw3 = le32_to_cpu(ncmd->common.cdw2[1]); + /* Skip metadata and addr */ + pcmd->cdw10 = le32_to_cpu(ncmd->common.cdw10[0]); + pcmd->cdw11 = le32_to_cpu(ncmd->common.cdw10[1]); + pcmd->cdw12 = le32_to_cpu(ncmd->common.cdw10[2]); + pcmd->cdw13 = le32_to_cpu(ncmd->common.cdw10[3]); + pcmd->cdw14 = le32_to_cpu(ncmd->common.cdw10[4]); + pcmd->cdw15 = le32_to_cpu(ncmd->common.cdw10[5]); } -static int get_property_helper(int fd, int offset, void *value, int *advance) +int nvme_get_property(int fd, int offset, uint64_t *value) { - __le64 value64; - int err = -EINVAL; - - switch (offset) { - case NVME_REG_CAP: - case NVME_REG_ASQ: - case NVME_REG_ACQ: - *advance = 8; - break; - default: - *advance = 4; - } - - if (!value) - return err; - - err = nvme_property(fd, nvme_fabrics_type_property_get, - cpu_to_le32(offset), &value64, (*advance == 8)); + struct nvme_passthru_cmd pcmd; + struct nvmf_property_get_command pg = { + .opcode = nvme_fabrics_command, + .fctype = nvme_fabrics_type_property_get, + .offset = cpu_to_le32(offset), + .attrib = is_64bit_reg(offset), + }; + struct nvme_command gcmd; + int err; + gcmd.prop_get = pg; + nvme_to_passthru_cmd(&pcmd, &gcmd); + err = nvme_submit_admin_passthru(fd, &pcmd); if (!err) { - if (*advance == 8) - *((uint64_t *)value) = le64_to_cpu(value64); - else - *((uint32_t *)value) = le32_to_cpu(value64); + /* + * nvme_submit_admin_passthru() stores the lower 32 bits + * of the property value in pcmd.result using CPU endianness. + */ + *value = pcmd.result; } - return err; } -int nvme_get_property(int fd, int offset, uint64_t *value) -{ - int advance; - return get_property_helper(fd, offset, value, &advance); -} - int nvme_get_properties(int fd, void **pbar) { - int offset, advance; - int err, ret = -EINVAL; + int offset; + uint64_t value; + int err; int size = getpagesize(); *pbar = malloc(size); @@ -622,33 +639,42 @@ int nvme_get_properties(int fd, void **pbar) } memset(*pbar, 0xff, size); - for (offset = NVME_REG_CAP; offset <= NVME_REG_CMBSZ; offset += advance) { - err = get_property_helper(fd, offset, *pbar + offset, &advance); - if (!err) - ret = 0; + for (offset = NVME_REG_CAP; offset <= NVME_REG_CMBSZ;) { + err = nvme_get_property(fd, offset, &value); + if (err > 0 && (err & 0xff) == NVME_SC_INVALID_FIELD) { + err = 0; + value = -1; + } else if (err) { + free(*pbar); + break; + } + if (is_64bit_reg(offset)) { + *(uint64_t *)(*pbar + offset) = value; + offset += 8; + } else { + *(uint32_t *)(*pbar + offset) = value; + offset += 4; + } } - return ret; + return err; } -int nvme_set_property(int fd, int offset, int value) +int nvme_set_property(int fd, int offset, uint64_t value) { - __le64 val = cpu_to_le64(value); - __le32 off = cpu_to_le32(offset); - bool is64bit; - - switch (off) { - case NVME_REG_CAP: - case NVME_REG_ASQ: - case NVME_REG_ACQ: - is64bit = true; - break; - default: - is64bit = false; - } + struct nvmf_property_set_command ps = { + .opcode = nvme_fabrics_command, + .fctype = nvme_fabrics_type_property_set, + .offset = cpu_to_le32(offset), + .value = cpu_to_le64(value), + .attrib = is_64bit_reg(offset), + }; + struct nvme_command scmd; + struct nvme_passthru_cmd pcmd; - return nvme_property(fd, nvme_fabrics_type_property_set, - off, &val, is64bit ? 1: 0); + scmd.prop_set = ps; + nvme_to_passthru_cmd(&pcmd, &scmd); + return nvme_submit_admin_passthru(fd, &pcmd); } int nvme_get_feature(int fd, __u32 nsid, __u8 fid, __u8 sel, __u32 cdw11, @@ -675,7 +701,7 @@ int nvme_format(int fd, __u32 nsid, __u8 lbaf, __u8 ses, __u8 pi, } int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, - __u8 dps, __u8 nmic, __u32 *result) + __u8 dps, __u8 nmic, __u32 timeout, __u32 *result) { struct nvme_id_ns ns = { .nsze = cpu_to_le64(nsze), @@ -689,6 +715,7 @@ int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, .addr = (__u64)(uintptr_t) ((void *)&ns), .cdw10 = 0, .data_len = 0x1000, + .timeout_ms = timeout, }; int err; @@ -698,12 +725,13 @@ int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, return err; } -int nvme_ns_delete(int fd, __u32 nsid) +int nvme_ns_delete(int fd, __u32 nsid, __u32 timeout) { struct nvme_admin_cmd cmd = { .opcode = nvme_admin_ns_mgmt, .nsid = nsid, .cdw10 = 1, + .timeout_ms = timeout, }; return nvme_submit_admin_passthru(fd, &cmd); @@ -803,6 +831,21 @@ int nvme_sec_recv(int fd, __u32 nsid, __u8 nssf, __u16 spsp, return err; } +int nvme_get_lba_status(int fd, __u64 slba, __u32 mndw, __u8 atype, __u16 rl, + void *data) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_get_lba_status, + .addr = (__u64)(uintptr_t) data, + .cdw10 = slba & 0xffffffff, + .cdw11 = slba >> 32, + .cdw12 = mndw, + .cdw13 = (atype << 24) | rl, + }; + + return nvme_submit_admin_passthru(fd, &cmd); +} + int nvme_dir_send(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result) { @@ -867,3 +910,19 @@ int nvme_self_test_start(int fd, __u32 nsid, __u32 cdw10) return nvme_submit_admin_passthru(fd, &cmd); } + +int nvme_virtual_mgmt(int fd, __u32 cdw10, __u32 cdw11, __u32 *result) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_virtual_mgmt, + .cdw10 = cdw10, + .cdw11 = cdw11, + }; + int err; + + err = nvme_submit_admin_passthru(fd, &cmd); + if (!err && result) + *result = cmd.result; + + return err; +} diff --git a/libmultipath/nvme/nvme-ioctl.h b/libmultipath/nvme/nvme-ioctl.h index 3fb740c..565f764 100644 --- a/libmultipath/nvme/nvme-ioctl.h +++ b/libmultipath/nvme/nvme-ioctl.h @@ -6,6 +6,8 @@ #include "linux/nvme_ioctl.h" #include "nvme.h" +#define NVME_IOCTL_TIMEOUT 120000 /* in milliseconds */ + int nvme_get_nsid(int fd); /* Generic passthrough */ @@ -36,6 +38,9 @@ int nvme_compare(int fd, __u64 slba, __u16 nblocks, __u16 control, __u32 dsmgmt, __u32 reftag, __u16 apptag, __u16 appmask, void *data, void *metadata); +int nvme_verify(int fd, __u32 nsid, __u64 slba, __u16 nblocks, + __u16 control, __u32 reftag, __u16 apptag, __u16 appmask); + /* NVME_IO_CMD */ int nvme_passthru_io(int fd, __u8 opcode, __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3, @@ -73,11 +78,22 @@ int nvme_identify_ns_list(int fd, __u32 nsid, bool all, void *data); int nvme_identify_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data); int nvme_identify_ns_descs(int fd, __u32 nsid, void *data); int nvme_identify_nvmset(int fd, __u16 nvmset_id, void *data); -int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, - __u16 group_id, bool rae, __u32 data_len, void *data); +int nvme_identify_uuid(int fd, void *data); +int nvme_identify_secondary_ctrl_list(int fd, __u32 nsid, __u16 cntid, void *data); +int nvme_identify_ns_granularity(int fd, void *data); int nvme_get_log(int fd, __u32 nsid, __u8 log_id, bool rae, __u32 data_len, void *data); - +int nvme_get_log14(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo, + __u16 group_id, bool rae, __u8 uuid_ix, + __u32 data_len, void *data); + +static inline int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, + __u64 lpo, __u16 lsi, bool rae, __u32 data_len, + void *data) +{ + return nvme_get_log14(fd, nsid, log_id, lsp, lpo, lsi, rae, 0, + data_len, data); +} int nvme_get_telemetry_log(int fd, void *lp, int generate_report, int ctrl_gen, size_t log_page_size, __u64 offset); @@ -105,8 +121,8 @@ int nvme_format(int fd, __u32 nsid, __u8 lbaf, __u8 ses, __u8 pi, __u8 pil, __u8 ms, __u32 timeout); int nvme_ns_create(int fd, __u64 nsze, __u64 ncap, __u8 flbas, - __u8 dps, __u8 nmic, __u32 *result); -int nvme_ns_delete(int fd, __u32 nsid); + __u8 dps, __u8 nmic, __u32 timeout, __u32 *result); +int nvme_ns_delete(int fd, __u32 nsid, __u32 timeout); int nvme_ns_attachment(int fd, __u32 nsid, __u16 num_ctrls, __u16 *ctrlist, bool attach); @@ -125,15 +141,18 @@ int nvme_subsystem_reset(int fd); int nvme_reset_controller(int fd); int nvme_ns_rescan(int fd); +int nvme_get_lba_status(int fd, __u64 slba, __u32 mndw, __u8 atype, __u16 rl, + void *data); int nvme_dir_send(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result); int nvme_dir_recv(int fd, __u32 nsid, __u16 dspec, __u8 dtype, __u8 doper, __u32 data_len, __u32 dw12, void *data, __u32 *result); int nvme_get_properties(int fd, void **pbar); -int nvme_set_property(int fd, int offset, int value); +int nvme_set_property(int fd, int offset, uint64_t value); int nvme_get_property(int fd, int offset, uint64_t *value); int nvme_sanitize(int fd, __u8 sanact, __u8 ause, __u8 owpass, __u8 oipbp, __u8 no_dealloc, __u32 ovrpat); int nvme_self_test_start(int fd, __u32 nsid, __u32 cdw10); int nvme_self_test_log(int fd, struct nvme_self_test_log *self_test_log); +int nvme_virtual_mgmt(int fd, __u32 cdw10, __u32 cdw11, __u32 *result); #endif /* _NVME_LIB_H */ diff --git a/libmultipath/nvme/nvme.h b/libmultipath/nvme/nvme.h index 685d179..7e0278b 100644 --- a/libmultipath/nvme/nvme.h +++ b/libmultipath/nvme/nvme.h @@ -40,16 +40,16 @@ struct nvme_effects_log_page { }; struct nvme_error_log_page { - __u64 error_count; - __u16 sqid; - __u16 cmdid; - __u16 status_field; - __u16 parm_error_location; - __u64 lba; - __u32 nsid; + __le64 error_count; + __le16 sqid; + __le16 cmdid; + __le16 status_field; + __le16 parm_error_location; + __le64 lba; + __le32 nsid; __u8 vs; __u8 resv[3]; - __u64 cs; + __le64 cs; __u8 resv2[24]; }; @@ -87,13 +87,30 @@ struct nvme_controller_list { __le16 identifier[]; }; +struct nvme_secondary_controller_entry { + __le16 scid; /* Secondary Controller Identifier */ + __le16 pcid; /* Primary Controller Identifier */ + __u8 scs; /* Secondary Controller State */ + __u8 rsvd5[3]; + __le16 vfn; /* Virtual Function Number */ + __le16 nvq; /* Number of VQ Flexible Resources Assigned */ + __le16 nvi; /* Number of VI Flexible Resources Assigned */ + __u8 rsvd14[18]; +}; + +struct nvme_secondary_controllers_list { + __u8 num; + __u8 rsvd[31]; + struct nvme_secondary_controller_entry sc_entry[127]; +}; + struct nvme_bar_cap { __u16 mqes; __u8 ams_cqr; __u8 to; __u16 bps_css_nssrs_dstrd; __u8 mpsmax_mpsmin; - __u8 reserved; + __u8 rsvd_pmrs; }; #ifdef __CHECKER__ @@ -102,19 +119,31 @@ struct nvme_bar_cap { #define __force #endif -#define cpu_to_le16(x) \ - ((__force __le16)htole16(x)) -#define cpu_to_le32(x) \ - ((__force __le32)htole32(x)) -#define cpu_to_le64(x) \ - ((__force __le64)htole64(x)) - -#define le16_to_cpu(x) \ - le16toh((__force __u16)(x)) -#define le32_to_cpu(x) \ - le32toh((__force __u32)(x)) -#define le64_to_cpu(x) \ - le64toh((__force __u64)(x)) +static inline __le16 cpu_to_le16(uint16_t x) +{ + return (__force __le16)htole16(x); +} +static inline __le32 cpu_to_le32(uint32_t x) +{ + return (__force __le32)htole32(x); +} +static inline __le64 cpu_to_le64(uint64_t x) +{ + return (__force __le64)htole64(x); +} + +static inline uint16_t le16_to_cpu(__le16 x) +{ + return le16toh((__force __u16)x); +} +static inline uint32_t le32_to_cpu(__le32 x) +{ + return le32toh((__force __u32)x); +} +static inline uint64_t le64_to_cpu(__le64 x) +{ + return le64toh((__force __u64)x); +} #define MAX_LIST_ITEMS 256 struct list_item { @@ -131,6 +160,10 @@ struct ctrl_list_item { char *transport; char *state; char *ana_state; + char *subsysnqn; + char *traddr; + char *trsvcid; + char *host_traddr; }; struct subsys_list_item { @@ -146,6 +179,26 @@ enum { BINARY, }; +struct connect_args { + char *subsysnqn; + char *transport; + char *traddr; + char *trsvcid; + char *host_traddr; +}; + +#define SYS_NVME "/sys/class/nvme" + +bool ctrl_matches_connectargs(char *name, struct connect_args *args); +char *find_ctrl_with_connectargs(struct connect_args *args); +char *__parse_connect_arg(char *conargs, const char delim, const char *fieldnm); + +extern const char *conarg_nqn; +extern const char *conarg_transport; +extern const char *conarg_traddr; +extern const char *conarg_trsvcid; +extern const char *conarg_host_traddr; + void register_extension(struct plugin *plugin); #include "argconfig.h" @@ -160,4 +213,28 @@ int validate_output_format(char *format); struct subsys_list_item *get_subsys_list(int *subcnt, char *subsysnqn, __u32 nsid); void free_subsys_list(struct subsys_list_item *slist, int n); char *nvme_char_from_block(char *block); + +/* + * is_64bit_reg - It checks whether given offset of the controller register is + * 64bit or not. + * @offset: offset of controller register field in bytes + * + * It gives true if given offset is 64bit register, otherwise it returns false. + * + * Notes: This function does not care about transport so that the offset is + * not going to be checked inside of this function for the unsupported fields + * in a specific transport. For example, BPMBL(Boot Partition Memory Buffer + * Location) register is not supported by fabrics, but it can be chcked here. + */ +static inline bool is_64bit_reg(__u32 offset) +{ + if (offset == NVME_REG_CAP || + offset == NVME_REG_ASQ || + offset == NVME_REG_ACQ || + offset == NVME_REG_BPMBL) + return true; + + return false; +} + #endif /* _NVME_H */ diff --git a/libmultipath/parser.c b/libmultipath/parser.c index e00c5ff..d478b17 100644 --- a/libmultipath/parser.c +++ b/libmultipath/parser.c @@ -124,7 +124,7 @@ find_keyword(vector keywords, vector v, char * name) { struct keyword *keyword; int i; - int len; + size_t len; if (!name || !keywords) return NULL; diff --git a/libmultipath/pgpolicies.c b/libmultipath/pgpolicies.c index 8f7c6b1..02cafdc 100644 --- a/libmultipath/pgpolicies.c +++ b/libmultipath/pgpolicies.c @@ -54,7 +54,7 @@ int get_pgpolicy_name(char * buff, int len, int id) s = "undefined"; break; } - return snprintf(buff, POLICY_NAME_SIZE, "%s", s); + return snprintf(buff, len, "%s", s); } diff --git a/libmultipath/print.c b/libmultipath/print.c index 907469a..b944ef3 100644 --- a/libmultipath/print.c +++ b/libmultipath/print.c @@ -29,6 +29,7 @@ #include "uevent.h" #include "debug.h" #include "discovery.h" +#include "util.h" #define MAX(x,y) (((x) > (y)) ? (x) : (y)) #define MIN(x,y) (((x) > (y)) ? (y) : (x)) @@ -36,7 +37,7 @@ #define NOPAD s = c #define PAD(x) \ do { \ - while ((int)(c - s) < (x) && (c < (line + len - 1))) \ + while (c < (s + x) && (c < (line + len - 1))) \ *c++ = ' '; \ s = c; \ } while (0) @@ -181,9 +182,10 @@ snprint_queueing (char * buff, size_t len, const struct multipath * mpp) return snprintf(buff, len, "-"); else if (mpp->no_path_retry > 0) { if (mpp->retry_tick > 0) + return snprintf(buff, len, "%i sec", mpp->retry_tick); - else if (mpp->retry_tick == 0 && mpp->nr_active > 0) + else if (mpp->retry_tick == 0 && count_active_paths(mpp) > 0) return snprintf(buff, len, "%i chk", mpp->no_path_retry); else @@ -195,7 +197,7 @@ snprint_queueing (char * buff, size_t len, const struct multipath * mpp) static int snprint_nb_paths (char * buff, size_t len, const struct multipath * mpp) { - return snprint_int(buff, len, mpp->nr_active); + return snprint_int(buff, len, count_active_paths(mpp)); } static int @@ -334,7 +336,8 @@ snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp) } static int -snprint_multipath_foreign (char * buff, size_t len, const struct multipath * pp) +snprint_multipath_foreign (char * buff, size_t len, + __attribute__((unused)) const struct multipath * pp) { return snprintf(buff, len, "%s", "--"); } @@ -358,6 +361,21 @@ snprint_action (char * buff, size_t len, const struct multipath * mpp) } } +static int +snprint_multipath_vpd_data(char * buff, size_t len, + const struct multipath * mpp) +{ + struct pathgroup * pgp; + struct path * pp; + int i, j; + + vector_foreach_slot(mpp->pg, pgp, i) + vector_foreach_slot(pgp->paths, pp, j) + if (pp->vpd_data) + return snprintf(buff, len, "%s", pp->vpd_data); + return snprintf(buff, len, "[undef]"); +} + /* * path info printing functions */ @@ -627,7 +645,8 @@ snprint_path_checker (char * buff, size_t len, const struct path * pp) } static int -snprint_path_foreign (char * buff, size_t len, const struct path * pp) +snprint_path_foreign (char * buff, size_t len, + __attribute__((unused)) const struct path * pp) { return snprintf(buff, len, "%s", "--"); } @@ -688,6 +707,14 @@ snprint_path_marginal(char * buff, size_t len, const struct path * pp) return snprintf(buff, len, "normal"); } +static int +snprint_path_vpd_data(char * buff, size_t len, const struct path * pp) +{ + if (pp->vpd_data) + return snprintf(buff, len, "%s", pp->vpd_data); + return snprintf(buff, len, "[undef]"); +} + struct multipath_data mpd[] = { {'n', "name", 0, snprint_name}, {'w', "uuid", 0, snprint_multipath_uuid}, @@ -712,6 +739,7 @@ struct multipath_data mpd[] = { {'p', "prod", 0, snprint_multipath_prod}, {'e', "rev", 0, snprint_multipath_rev}, {'G', "foreign", 0, snprint_multipath_foreign}, + {'g', "vpd page data", 0, snprint_multipath_vpd_data}, {0, NULL, 0 , NULL} }; @@ -737,6 +765,7 @@ struct path_data pd[] = { {'r', "target WWPN", 0, snprint_tgt_wwpn}, {'a', "host adapter", 0, snprint_host_adapter}, {'G', "foreign", 0, snprint_path_foreign}, + {'g', "vpd page data", 0, snprint_path_vpd_data}, {'0', "failures", 0, snprint_path_failures}, {'P', "protocol", 0, snprint_path_protocol}, {0, NULL, 0 , NULL} @@ -781,7 +810,7 @@ get_path_layout(vector pathvec, int header) } static void -reset_width(int *width, enum layout_reset reset, const char *header) +reset_width(unsigned int *width, enum layout_reset reset, const char *header) { switch (reset) { case LAYOUT_RESET_HEADER: @@ -1337,8 +1366,8 @@ snprint_multipath_fields_json (char * buff, int len, } int -snprint_multipath_map_json (char * buff, int len, - const struct multipath * mpp, int last){ +snprint_multipath_map_json (char * buff, int len, const struct multipath * mpp) +{ int fwd = 0; fwd += snprint_json_header(buff, len); @@ -2004,7 +2033,6 @@ int snprint_devices(struct config *conf, char * buff, int len, struct dirent *blkdev; struct stat statbuf; char devpath[PATH_MAX]; - char *devptr; int threshold = MAX_LINE_LEN; int fwd = 0; int r; @@ -2020,15 +2048,14 @@ int snprint_devices(struct config *conf, char * buff, int len, } fwd += snprintf(buff + fwd, len - fwd, "available block devices:\n"); - strcpy(devpath,"/sys/block/"); while ((blkdev = readdir(blkdir)) != NULL) { if ((strcmp(blkdev->d_name,".") == 0) || (strcmp(blkdev->d_name,"..") == 0)) continue; - devptr = devpath + 11; - *devptr = '\0'; - strncat(devptr, blkdev->d_name, PATH_MAX-12); + if (safe_sprintf(devpath, "/sys/block/%s", blkdev->d_name)) + continue; + if (stat(devpath, &statbuf) < 0) continue; @@ -2040,11 +2067,12 @@ int snprint_devices(struct config *conf, char * buff, int len, return len; } - fwd += snprintf(buff + fwd, len - fwd, " %s", devptr); - pp = find_path_by_dev(vecs->pathvec, devptr); + fwd += snprintf(buff + fwd, len - fwd, " %s", + blkdev->d_name); + pp = find_path_by_dev(vecs->pathvec, blkdev->d_name); if (!pp) { r = filter_devnode(conf->blist_devnode, - conf->elist_devnode, devptr); + conf->elist_devnode, blkdev->d_name); if (r > 0) fwd += snprintf(buff + fwd, len - fwd, " devnode blacklisted, unmonitored"); diff --git a/libmultipath/print.h b/libmultipath/print.h index 7e36ec6..e8260d0 100644 --- a/libmultipath/print.h +++ b/libmultipath/print.h @@ -78,21 +78,21 @@ struct path_data { char wildcard; char * header; - int width; + unsigned int width; int (*snprint)(char * buff, size_t len, const struct path * pp); }; struct multipath_data { char wildcard; char * header; - int width; + unsigned int width; int (*snprint)(char * buff, size_t len, const struct multipath * mpp); }; struct pathgroup_data { char wildcard; char * header; - int width; + unsigned int width; int (*snprint)(char * buff, size_t len, const struct pathgroup * pgp); }; @@ -125,7 +125,7 @@ char *snprint_config(const struct config *conf, int *len, const struct _vector *hwtable, const struct _vector *mpvec); int snprint_multipath_map_json (char * buff, int len, - const struct multipath * mpp, int last); + const struct multipath * mpp); int snprint_blacklist_report (struct config *, char *, int); int snprint_wildcards (char *, int); int snprint_status (char *, int, const struct vectors *); diff --git a/libmultipath/prio.c b/libmultipath/prio.c index 87de1f9..194563c 100644 --- a/libmultipath/prio.c +++ b/libmultipath/prio.c @@ -10,11 +10,11 @@ static LIST_HEAD(prioritizers); -unsigned int get_prio_timeout(unsigned int checker_timeout, +unsigned int get_prio_timeout(unsigned int timeout_ms, unsigned int default_timeout) { - if (checker_timeout) - return checker_timeout * 1000; + if (timeout_ms) + return timeout_ms; return default_timeout; } diff --git a/libmultipath/prioritizers/alua_rtpg.c b/libmultipath/prioritizers/alua_rtpg.c index 271a019..bbf5aac 100644 --- a/libmultipath/prioritizers/alua_rtpg.c +++ b/libmultipath/prioritizers/alua_rtpg.c @@ -371,7 +371,7 @@ get_asymmetric_access_state(const struct path *pp, unsigned int tpg, struct rtpg_data * tpgd; struct rtpg_tpg_dscr * dscr; int rc; - int buflen; + unsigned int buflen; uint64_t scsi_buflen; int fd = pp->fd; diff --git a/libmultipath/prioritizers/ana.c b/libmultipath/prioritizers/ana.c index 2673d9d..b5c7873 100644 --- a/libmultipath/prioritizers/ana.c +++ b/libmultipath/prioritizers/ana.c @@ -62,7 +62,7 @@ static const char *anas_string[] = { static const char *aas_print_string(int rc) { rc &= 0xff; - if (rc >= 0 && rc < ARRAY_SIZE(anas_string) && + if (rc >= 0 && rc < (int)ARRAY_SIZE(anas_string) && anas_string[rc] != NULL) return anas_string[rc]; @@ -78,7 +78,8 @@ static int get_ana_state(__u32 nsid, __u32 anagrpid, void *ana_log, size_t offset = sizeof(struct nvme_ana_rsp_hdr); __u32 nr_nsids; size_t nsid_buf_size; - int i, j; + int i; + unsigned int j; for (i = 0; i < le16_to_cpu(hdr->ngrps); i++) { ana_desc = base + offset; @@ -106,7 +107,7 @@ static int get_ana_state(__u32 nsid, __u32 anagrpid, void *ana_log, return -ANA_ERR_GETANAS_NOTFOUND; } -int get_ana_info(struct path * pp, unsigned int timeout) +static int get_ana_info(struct path * pp) { int rc; __u32 nsid; @@ -202,14 +203,15 @@ int get_ana_info(struct path * pp, unsigned int timeout) * - ALUA's LBA-dependent state has no ANA equivalent. */ -int getprio(struct path *pp, char *args, unsigned int timeout) +int getprio(struct path *pp, __attribute__((unused)) char *args, + __attribute__((unused)) unsigned int timeout) { int rc; if (pp->fd < 0) rc = -ANA_ERR_NO_INFORMATION; else - rc = get_ana_info(pp, timeout); + rc = get_ana_info(pp); switch (rc) { case NVME_ANA_OPTIMIZED: @@ -224,7 +226,7 @@ int getprio(struct path *pp, char *args, unsigned int timeout) default: break; } - if (rc < 0 && -rc < ARRAY_SIZE(ana_errmsg)) + if (rc < 0 && -rc < (int)ARRAY_SIZE(ana_errmsg)) condlog(2, "%s: ANA error: %s", pp->dev, ana_errmsg[-rc]); else condlog(1, "%s: invalid ANA rc code %d", pp->dev, rc); diff --git a/libmultipath/prioritizers/const.c b/libmultipath/prioritizers/const.c index aad6927..059d859 100644 --- a/libmultipath/prioritizers/const.c +++ b/libmultipath/prioritizers/const.c @@ -2,7 +2,9 @@ #include "prio.h" -int getprio(struct path * pp, char * args, unsigned int timeout) +int getprio(__attribute__((unused)) struct path * pp, + __attribute__((unused)) char * args, + __attribute__((unused)) unsigned int timeout) { return 1; } diff --git a/libmultipath/prioritizers/datacore.c b/libmultipath/prioritizers/datacore.c index 59c9816..02dc2e2 100644 --- a/libmultipath/prioritizers/datacore.c +++ b/libmultipath/prioritizers/datacore.c @@ -98,7 +98,8 @@ int datacore_prio (const char *dev, int sg_fd, char * args) return 0; } -int getprio(struct path * pp, char * args, unsigned int timeout) +int getprio(struct path * pp, char * args, + __attribute__((unused)) unsigned int timeout) { return datacore_prio(pp->dev, pp->fd, args); } diff --git a/libmultipath/prioritizers/emc.c b/libmultipath/prioritizers/emc.c index a2f7487..3b63cca 100644 --- a/libmultipath/prioritizers/emc.c +++ b/libmultipath/prioritizers/emc.c @@ -81,7 +81,8 @@ out: return(ret); } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args, + unsigned int timeout) { return emc_clariion_prio(pp->dev, pp->fd, timeout); } diff --git a/libmultipath/prioritizers/hds.c b/libmultipath/prioritizers/hds.c index 70fb5d1..88cac5f 100644 --- a/libmultipath/prioritizers/hds.c +++ b/libmultipath/prioritizers/hds.c @@ -168,7 +168,8 @@ int hds_modular_prio (const char *dev, int fd, unsigned int timeout) return -1; } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path * pp, __attribute__((unused)) char *args, + unsigned int timeout) { return hds_modular_prio(pp->dev, pp->fd, timeout); } diff --git a/libmultipath/prioritizers/hp_sw.c b/libmultipath/prioritizers/hp_sw.c index 6b0ed39..5b85ad2 100644 --- a/libmultipath/prioritizers/hp_sw.c +++ b/libmultipath/prioritizers/hp_sw.c @@ -95,7 +95,8 @@ out: return(ret); } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args, + unsigned int timeout) { return hp_sw_prio(pp->dev, pp->fd, timeout); } diff --git a/libmultipath/prioritizers/iet.c b/libmultipath/prioritizers/iet.c index a4ea61e..e98773c 100644 --- a/libmultipath/prioritizers/iet.c +++ b/libmultipath/prioritizers/iet.c @@ -138,7 +138,8 @@ int iet_prio(const char *dev, char * args) return 10; } -int getprio(struct path * pp, char * args, unsigned int timeout) +int getprio(struct path * pp, char * args, + __attribute__((unused)) unsigned int timeout) { return iet_prio(pp->dev, args); } diff --git a/libmultipath/prioritizers/ontap.c b/libmultipath/prioritizers/ontap.c index 6505033..262e69d 100644 --- a/libmultipath/prioritizers/ontap.c +++ b/libmultipath/prioritizers/ontap.c @@ -241,7 +241,8 @@ prio_select: } } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args, + unsigned int timeout) { return ontap_prio(pp->dev, pp->fd, timeout); } diff --git a/libmultipath/prioritizers/random.c b/libmultipath/prioritizers/random.c index 4a27123..b742ac2 100644 --- a/libmultipath/prioritizers/random.c +++ b/libmultipath/prioritizers/random.c @@ -5,7 +5,9 @@ #include "prio.h" -int getprio(struct path * pp, char * args, unsigned int timeout) +int getprio(__attribute__((unused)) struct path *pp, + __attribute__((unused)) char *args, + __attribute__((unused)) unsigned int timeout) { struct timeval tv; diff --git a/libmultipath/prioritizers/rdac.c b/libmultipath/prioritizers/rdac.c index f5df032..92a2fb8 100644 --- a/libmultipath/prioritizers/rdac.c +++ b/libmultipath/prioritizers/rdac.c @@ -91,7 +91,8 @@ out: return(ret); } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path *pp, __attribute__((unused)) char *args, + unsigned int timeout) { return rdac_prio(pp->dev, pp->fd, timeout); } diff --git a/libmultipath/prioritizers/sysfs.c b/libmultipath/prioritizers/sysfs.c index ff567df..a6feb42 100644 --- a/libmultipath/prioritizers/sysfs.c +++ b/libmultipath/prioritizers/sysfs.c @@ -36,7 +36,8 @@ int get_exclusive_pref_arg(char *args) return 1; } -int getprio (struct path * pp, char * args, unsigned int timeout) +int getprio (struct path * pp, char *args, + __attribute__((unused)) unsigned int timeout) { int prio = 0, rc, i; char buff[512]; diff --git a/libmultipath/prioritizers/weightedpath.c b/libmultipath/prioritizers/weightedpath.c index e0f3efb..916970d 100644 --- a/libmultipath/prioritizers/weightedpath.c +++ b/libmultipath/prioritizers/weightedpath.c @@ -143,7 +143,8 @@ int prio_path_weight(struct path *pp, char *prio_args) return priority; } -int getprio(struct path *pp, char *args, unsigned int timeout) +int getprio(struct path *pp, char *args, + __attribute__((unused)) unsigned int timeout) { return prio_path_weight(pp, args); } diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 27e8d68..897e48c 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -432,12 +432,26 @@ int select_hwhandler(struct config *conf, struct multipath *mp) static const char tpgs_origin[]= "(setting: autodetected from TPGS)"; char *dh_state; int i; - bool all_tpgs = true; + bool all_tpgs = true, one_tpgs = false; dh_state = &handler[2]; - vector_foreach_slot(mp->paths, pp, i) - all_tpgs = all_tpgs && (path_get_tpgs(pp) > 0); + /* + * TPGS_UNDEF means that ALUA support couldn't determined either way + * yet, probably because the path was always down. + * If at least one path does have TPGS support, and no path has + * TPGS_NONE, assume that TPGS would be supported by all paths if + * all were up. + */ + vector_foreach_slot(mp->paths, pp, i) { + int tpgs = path_get_tpgs(pp); + + all_tpgs = all_tpgs && tpgs != TPGS_NONE; + one_tpgs = one_tpgs || + (tpgs != TPGS_NONE && tpgs != TPGS_UNDEF); + } + all_tpgs = all_tpgs && one_tpgs; + if (mp->retain_hwhandler != RETAIN_HWHANDLER_OFF) { vector_foreach_slot(mp->paths, pp, i) { if (get_dh_state(pp, dh_state, sizeof(handler) - 2) > 0 @@ -490,7 +504,7 @@ check_rdac(struct path * pp) if (__do_set_from_hwe(checker_name, pp, checker_name) && strcmp(checker_name, RDAC)) return 0; - len = get_vpd_sgio(pp->fd, 0xC9, buff, 44); + len = get_vpd_sgio(pp->fd, 0xC9, 0, buff, 44); if (len <= 0) return 0; return !(memcmp(buff + 4, "vac1", 4)); @@ -1203,3 +1217,21 @@ out: origin); return 0; } + +int select_vpd_vendor_id (struct path *pp) +{ + const char *origin; + + pp_set_hwe(vpd_vendor_id); + pp_set_default(vpd_vendor_id, 0); +out: + if (pp->vpd_vendor_id < 0 || pp->vpd_vendor_id >= VPD_VP_ARRAY_SIZE) { + condlog(3, "%s: vpd_vendor_id = %d (invalid, setting to 0)", + pp->dev, pp->vpd_vendor_id); + pp->vpd_vendor_id = 0; + } + condlog(3, "%s: vpd_vendor_id = %d \"%s\" %s", pp->dev, + pp->vpd_vendor_id, vpd_vendor_pages[pp->vpd_vendor_id].name, + origin); + return 0; +} diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h index ddfd626..3d6edd8 100644 --- a/libmultipath/propsel.h +++ b/libmultipath/propsel.h @@ -37,3 +37,4 @@ void reconcile_features_with_options(const char *id, char **features, int* no_path_retry, int *retain_hwhandler); int select_all_tg_pt (struct config *conf, struct multipath * mp); +int select_vpd_vendor_id (struct path *pp); diff --git a/libmultipath/structs.c b/libmultipath/structs.c index bf7fdd7..2dd378c 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -131,6 +131,9 @@ free_path (struct path * pp) udev_device_unref(pp->udev); pp->udev = NULL; } + if (pp->vpd_data) + free(pp->vpd_data); + vector_free(pp->hwe); FREE(pp); @@ -350,7 +353,7 @@ store_adaptergroup(vector adapters, struct adapter_group * agp) } struct multipath * -find_mp_by_minor (const struct _vector *mpvec, int minor) +find_mp_by_minor (const struct _vector *mpvec, unsigned int minor) { int i; struct multipath * mpp; @@ -388,7 +391,7 @@ struct multipath * find_mp_by_alias (const struct _vector *mpvec, const char * alias) { int i; - int len; + size_t len; struct multipath * mpp; if (!mpvec) @@ -478,6 +481,25 @@ int pathcount(const struct multipath *mpp, int state) return count; } +int count_active_paths(const struct multipath *mpp) +{ + struct pathgroup *pgp; + struct path *pp; + int count = 0; + int i, j; + + if (!mpp->pg) + return 0; + + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { + if (pp->state == PATH_UP || pp->state == PATH_GHOST) + count++; + } + } + return count; +} + int pathcmp(const struct pathgroup *pgp, const struct pathgroup *cpgp) { int i, j; diff --git a/libmultipath/structs.h b/libmultipath/structs.h index a3adf90..9bd39eb 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -3,6 +3,7 @@ #include #include +#include #include "prio.h" #include "byteorder.h" @@ -21,6 +22,7 @@ #define HOST_NAME_LEN 16 #define SLOT_NAME_SIZE 40 #define PRKEY_SIZE 19 +#define VPD_DATA_SIZE 128 #define SCSI_VENDOR_SIZE 9 #define SCSI_PRODUCT_SIZE 17 @@ -106,7 +108,7 @@ enum yes_no_undef_states { * _FIND_MULTIPATHS_F must have the same value as YNU_YES. * Generate a compile time error if that isn't the case. */ -char ___error1___[-(_FIND_MULTIPATHS_F != YNU_YES)]; +extern char ___error1___[-(_FIND_MULTIPATHS_F != YNU_YES)]; #define find_multipaths_on(conf) \ (!!((conf)->find_multipaths & _FIND_MULTIPATHS_F)) @@ -221,6 +223,18 @@ enum all_tg_pt_states { ALL_TG_PT_ON = YNU_YES, }; +enum vpd_vendor_ids { + VPD_VP_UNDEF, + VPD_VP_HP3PAR, + VPD_VP_ARRAY_SIZE, /* This must remain the last entry */ +}; + +struct vpd_vendor_page { + int pg; + const char *name; +}; +extern struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE]; + struct sg_id { int host_no; int channel; @@ -255,6 +269,7 @@ struct path { char rev[PATH_REV_SIZE]; char serial[SERIAL_SIZE]; char tgt_node_name[NODE_NAME_SIZE]; + char *vpd_data; unsigned long long size; unsigned int checkint; unsigned int tick; @@ -272,7 +287,6 @@ struct path { char * uid_attribute; char * getuid; struct prio prio; - char * prio_args; struct checker checker; struct multipath * mpp; int fd; @@ -288,6 +302,7 @@ struct path { int io_err_pathfail_starttime; int find_multipaths_timeout; int marginal; + int vpd_vendor_id; /* configlet pointers */ vector hwe; struct gen_path generic_path; @@ -309,7 +324,6 @@ struct multipath { int pgfailback; int failback_tick; int rr_weight; - int nr_active; /* current available(= not known as failed) paths */ int no_path_retry; /* number of retries after all paths are down */ int retry_tick; /* remaining times for retries */ int disable_queueing; @@ -319,6 +333,7 @@ struct multipath { int fast_io_fail; int retain_hwhandler; int deferred_remove; + bool in_recovery; int san_path_err_threshold; int san_path_err_forget_rate; int san_path_err_recovery_time; @@ -440,7 +455,8 @@ int add_pathgroup(struct multipath*, struct pathgroup *); struct multipath * find_mp_by_alias (const struct _vector *mp, const char *alias); struct multipath * find_mp_by_wwid (const struct _vector *mp, const char *wwid); struct multipath * find_mp_by_str (const struct _vector *mp, const char *wwid); -struct multipath * find_mp_by_minor (const struct _vector *mp, int minor); +struct multipath * find_mp_by_minor (const struct _vector *mp, + unsigned int minor); 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); @@ -448,6 +464,7 @@ 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 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 c43b58f..3dbbaa0 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -290,10 +290,15 @@ update_multipath_strings(struct multipath *mpp, vector pathvec, int is_daemon) return 0; } -void enter_recovery_mode(struct multipath *mpp) +static void enter_recovery_mode(struct multipath *mpp) { - int checkint; - struct config *conf = get_multipath_config(); + unsigned int checkint; + struct config *conf; + + if (mpp->in_recovery || mpp->no_path_retry <= 0) + return; + + conf = get_multipath_config(); checkint = conf->checkint; put_multipath_config(conf); @@ -302,12 +307,68 @@ void enter_recovery_mode(struct multipath *mpp) * meaning of +1: retry_tick may be decremented in checkerloop before * starting retry. */ + mpp->in_recovery = true; mpp->stat_queueing_timeouts++; mpp->retry_tick = mpp->no_path_retry * checkint + 1; condlog(1, "%s: Entering recovery mode: max_retries=%d", mpp->alias, mpp->no_path_retry); } +static void leave_recovery_mode(struct multipath *mpp) +{ + bool recovery = mpp->in_recovery; + + mpp->in_recovery = false; + mpp->retry_tick = 0; + + /* + * in_recovery is only ever set if mpp->no_path_retry > 0 + * (see enter_recovery_mode()). But no_path_retry may have been + * changed while the map was recovering, so test it here again. + */ + if (recovery && (mpp->no_path_retry == NO_PATH_RETRY_QUEUE || + mpp->no_path_retry > 0)) { + dm_queue_if_no_path(mpp->alias, 1); + condlog(2, "%s: queue_if_no_path enabled", mpp->alias); + condlog(1, "%s: Recovered to normal mode", mpp->alias); + } +} + +void __set_no_path_retry(struct multipath *mpp, bool check_features) +{ + bool is_queueing; + + check_features = check_features && mpp->features != NULL; + if (check_features) + is_queueing = strstr(mpp->features, "queue_if_no_path"); + + switch (mpp->no_path_retry) { + case NO_PATH_RETRY_UNDEF: + break; + case NO_PATH_RETRY_FAIL: + if (!check_features || is_queueing) + dm_queue_if_no_path(mpp->alias, 0); + break; + case NO_PATH_RETRY_QUEUE: + if (!check_features || !is_queueing) + dm_queue_if_no_path(mpp->alias, 1); + break; + default: + if (count_active_paths(mpp) > 0) { + /* + * If in_recovery is set, leave_recovery_mode() takes + * care of dm_queue_if_no_path. Otherwise, do it here. + */ + if ((!check_features || !is_queueing) && + !mpp->in_recovery) + dm_queue_if_no_path(mpp->alias, 1); + leave_recovery_mode(mpp); + } else + enter_recovery_mode(mpp); + break; + } +} + void sync_map_state(struct multipath *mpp) { @@ -450,25 +511,23 @@ int verify_paths(struct multipath *mpp, struct vectors *vecs) */ void update_queue_mode_del_path(struct multipath *mpp) { - if (--mpp->nr_active == 0) { - if (mpp->no_path_retry > 0) - enter_recovery_mode(mpp); - else if (mpp->no_path_retry != NO_PATH_RETRY_QUEUE) + int active = count_active_paths(mpp); + + if (active == 0) { + enter_recovery_mode(mpp); + if (mpp->no_path_retry != NO_PATH_RETRY_QUEUE) mpp->stat_map_failures++; } - condlog(2, "%s: remaining active paths: %d", mpp->alias, mpp->nr_active); + condlog(2, "%s: remaining active paths: %d", mpp->alias, active); } void update_queue_mode_add_path(struct multipath *mpp) { - if (mpp->nr_active++ == 0 && mpp->no_path_retry > 0) { - /* come back to normal mode from retry mode */ - mpp->retry_tick = 0; - dm_queue_if_no_path(mpp->alias, 1); - condlog(2, "%s: queue_if_no_path enabled", mpp->alias); - condlog(1, "%s: Recovered to normal mode", mpp->alias); - } - condlog(2, "%s: remaining active paths: %d", mpp->alias, mpp->nr_active); + int active = count_active_paths(mpp); + + if (active > 0) + leave_recovery_mode(mpp); + condlog(2, "%s: remaining active paths: %d", mpp->alias, active); } vector get_used_hwes(const struct _vector *pathvec) diff --git a/libmultipath/structs_vec.h b/libmultipath/structs_vec.h index f8b9f63..2a5e3d6 100644 --- a/libmultipath/structs_vec.h +++ b/libmultipath/structs_vec.h @@ -11,7 +11,8 @@ struct vectors { vector mpvec; }; -void enter_recovery_mode(struct multipath *mpp); +void __set_no_path_retry(struct multipath *mpp, bool check_features); +#define set_no_path_retry(mpp) __set_no_path_retry(mpp, true) int adopt_paths (vector pathvec, struct multipath * mpp); void orphan_paths(vector pathvec, struct multipath *mpp, diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c index 65904d7..62ec2ed 100644 --- a/libmultipath/sysfs.c +++ b/libmultipath/sysfs.c @@ -88,7 +88,7 @@ ssize_t sysfs_attr_get_value(struct udev_device *dev, const char *attr_name, condlog(4, "read from %s failed: %s", devpath, strerror(errno)); size = -errno; value[0] = '\0'; - } else if (size == value_len) { + } else if (size == (ssize_t)value_len) { value[size - 1] = '\0'; condlog(4, "overflow while reading from %s", devpath); size = 0; @@ -146,7 +146,7 @@ ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name, if (size < 0) { condlog(4, "read from %s failed: %s", devpath, strerror(errno)); size = -errno; - } else if (size == value_len) { + } else if (size == (ssize_t)value_len) { condlog(4, "overflow while reading from %s", devpath); size = 0; } @@ -200,7 +200,7 @@ ssize_t sysfs_attr_set_value(struct udev_device *dev, const char *attr_name, if (size < 0) { condlog(4, "write to %s failed: %s", devpath, strerror(errno)); size = -errno; - } else if (size < value_len) { + } else if (size < (ssize_t)value_len) { condlog(4, "tried to write %ld to %s. Wrote %ld", (long)value_len, devpath, (long)size); size = 0; @@ -306,7 +306,7 @@ bool sysfs_is_multipathed(const struct path *pp) n = snprintf(pathbuf, sizeof(pathbuf), "/sys/block/%s/holders", pp->dev); - if (n >= sizeof(pathbuf)) { + if (n < 0 || (size_t)n >= sizeof(pathbuf)) { condlog(1, "%s: pathname overflow", __func__); return false; } @@ -327,9 +327,8 @@ bool sysfs_is_multipathed(const struct path *pp) int nr; char uuid[6]; - if (snprintf(pathbuf + n, sizeof(pathbuf) - n, - "/%s/dm/uuid", di[i]->d_name) - >= sizeof(pathbuf) - n) + if (safe_snprintf(pathbuf + n, sizeof(pathbuf) - n, + "/%s/dm/uuid", di[i]->d_name)) continue; fd = open(pathbuf, O_RDONLY); diff --git a/libmultipath/time-util.c b/libmultipath/time-util.c index a3739a2..55f366c 100644 --- a/libmultipath/time-util.c +++ b/libmultipath/time-util.c @@ -32,11 +32,11 @@ void pthread_cond_init_mono(pthread_cond_t *cond) void normalize_timespec(struct timespec *ts) { while (ts->tv_nsec < 0) { - ts->tv_nsec += 1000UL * 1000 * 1000; + ts->tv_nsec += 1000L * 1000 * 1000; ts->tv_sec--; } - while (ts->tv_nsec >= 1000UL * 1000 * 1000) { - ts->tv_nsec -= 1000UL * 1000 * 1000; + while (ts->tv_nsec >= 1000L * 1000 * 1000) { + ts->tv_nsec -= 1000L * 1000 * 1000; ts->tv_sec++; } } diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c index 8f7b2ef..d38e8a7 100644 --- a/libmultipath/uevent.c +++ b/libmultipath/uevent.c @@ -108,7 +108,8 @@ uevq_cleanup(struct list_head *tmpq) static const char* uevent_get_env_var(const struct uevent *uev, const char *attr) { - int i, len; + int i; + size_t len; const char *p = NULL; if (attr == NULL) @@ -853,7 +854,7 @@ int uevent_listen(struct udev *udev) poll_timeout = timeout * 1000; errno = 0; fdcount = poll(&ev_poll, 1, poll_timeout); - if (fdcount && ev_poll.revents & POLLIN) { + if (fdcount > 0 && ev_poll.revents & POLLIN) { timeout = uevent_burst(&start_time, events + 1) ? 1 : 0; dev = udev_monitor_receive_device(monitor); if (!dev) { diff --git a/libmultipath/unaligned.h b/libmultipath/unaligned.h index 68c0774..b9eaa7c 100644 --- a/libmultipath/unaligned.h +++ b/libmultipath/unaligned.h @@ -10,14 +10,14 @@ static inline uint16_t get_unaligned_be16(const void *ptr) return p[0] << 8 | p[1]; } -static inline uint32_t get_unaligned_be32(void *ptr) +static inline uint32_t get_unaligned_be32(const void *ptr) { const uint8_t *p = ptr; return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; } -static inline uint64_t get_unaligned_be64(void *ptr) +static inline uint64_t get_unaligned_be64(const void *ptr) { uint32_t low = get_unaligned_be32(ptr + 4); uint64_t high = get_unaligned_be32(ptr); diff --git a/libmultipath/util.c b/libmultipath/util.c index 28cbf4b..51c38c8 100644 --- a/libmultipath/util.c +++ b/libmultipath/util.c @@ -44,7 +44,7 @@ basenamecpy (const char *src, char *dst, size_t size) p = basename(src); for (e = p + strlen(p) - 1; e >= p && isspace(*e); --e) ; - if (e < p || e - p > size - 2) + if (e < p || (size_t)(e - p) > size - 2) return 0; strlcpy(dst, p, e - p + 2); @@ -212,8 +212,7 @@ int devt2devname(char *devname, int devname_len, char *devt) continue; if ((major == tmpmaj) && (minor == tmpmin)) { - if (snprintf(block_path, sizeof(block_path), - "/sys/block/%s", dev) >= sizeof(block_path)) { + if (safe_sprintf(block_path, "/sys/block/%s", dev)) { condlog(0, "device name %s is too long", dev); fclose(fd); return 1; @@ -428,7 +427,7 @@ int safe_write(int fd, const void *buf, size_t count) return 0; } -void set_max_fds(int max_fds) +void set_max_fds(rlim_t max_fds) { struct rlimit fd_limit; diff --git a/libmultipath/util.h b/libmultipath/util.h index 693991c..56bd78c 100644 --- a/libmultipath/util.h +++ b/libmultipath/util.h @@ -2,6 +2,8 @@ #define _UTIL_H #include +/* for rlim_t */ +#include #include #include @@ -21,15 +23,22 @@ 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 safe_write(int fd, const void *buf, size_t count); -void set_max_fds(int max_fds); +void set_max_fds(rlim_t max_fds); #define KERNEL_VERSION(maj, min, ptc) ((((maj) * 256) + (min)) * 256 + (ptc)) #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) #define safe_sprintf(var, format, args...) \ - snprintf(var, sizeof(var), format, ##args) >= sizeof(var) + safe_snprintf(var, sizeof(var), format, ##args) + #define safe_snprintf(var, size, format, args...) \ - snprintf(var, size, format, ##args) >= size + ({ \ + size_t __size = size; \ + int __ret; \ + \ + __ret = snprintf(var, __size, format, ##args); \ + __ret < 0 || (size_t)__ret >= __size; \ + }) #define pthread_cleanup_push_cast(f, arg) \ pthread_cleanup_push(((void (*)(void *))&f), (arg)) diff --git a/libmultipath/uxsock.c b/libmultipath/uxsock.c index 9b4e978..6adeedf 100644 --- a/libmultipath/uxsock.c +++ b/libmultipath/uxsock.c @@ -40,7 +40,8 @@ static int _recv_packet(int fd, char **buf, unsigned int timeout, */ int ux_socket_listen(const char *name) { - int fd, len; + int fd; + size_t len; #ifdef USE_SYSTEMD int num; #endif diff --git a/libmultipath/vector.h b/libmultipath/vector.h index 344dffd..e16ec46 100644 --- a/libmultipath/vector.h +++ b/libmultipath/vector.h @@ -36,9 +36,9 @@ typedef struct _vector *vector; #define VECTOR_LAST_SLOT(V) (((V) && VECTOR_SIZE(V) > 0) ? (V)->slot[(VECTOR_SIZE(V) - 1)] : NULL) #define vector_foreach_slot(v,p,i) \ - for (i = 0; (v) && i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) + for (i = 0; (v) && (int)i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) #define vector_foreach_slot_after(v,p,i) \ - for (; (v) && i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) + for (; (v) && (int)i < VECTOR_SIZE(v) && ((p) = (v)->slot[i]); i++) #define vector_foreach_slot_backwards(v,p,i) \ for (i = VECTOR_SIZE(v) - 1; (int)i >= 0 && ((p) = (v)->slot[i]); i--) diff --git a/libmultipath/version.h b/libmultipath/version.h index 5e4c328..7ddb4e8 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,8 +20,8 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION_CODE 0x000803 -#define DATE_CODE 0x0a0213 +#define VERSION_CODE 0x000804 +#define DATE_CODE 0x050414 #define PROG "multipath-tools" diff --git a/libmultipath/wwids.c b/libmultipath/wwids.c index ef74812..28a2150 100644 --- a/libmultipath/wwids.c +++ b/libmultipath/wwids.c @@ -7,6 +7,7 @@ #include #include +#include "util.h" #include "checkers.h" #include "vector.h" #include "structs.h" @@ -73,7 +74,7 @@ write_out_wwid(int fd, char *wwid) { strerror(errno)); return -1; } - if (write(fd, buf, strlen(buf)) != strlen(buf)) { + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { condlog(0, "cannot write wwid to wwids file : %s", strerror(errno)); if (ftruncate(fd, offset)) @@ -87,7 +88,8 @@ write_out_wwid(int fd, char *wwid) { int replace_wwids(vector mp) { - int i, fd, can_write; + int i, can_write; + long fd; struct multipath * mpp; size_t len; int ret = -1; @@ -99,6 +101,8 @@ replace_wwids(vector mp) pthread_cleanup_pop(1); if (fd < 0) goto out; + + pthread_cleanup_push(close_fd, (void*)fd); if (!can_write) { condlog(0, "cannot replace wwids. wwids file is read-only"); goto out_file; @@ -113,7 +117,7 @@ replace_wwids(vector mp) goto out_file; } len = strlen(WWIDS_FILE_HEADER); - if (write(fd, WWIDS_FILE_HEADER, len) != len) { + if (write(fd, WWIDS_FILE_HEADER, len) != (ssize_t)len) { condlog(0, "Can't write wwid file header : %s", strerror(errno)); /* cleanup partially written header */ @@ -132,7 +136,7 @@ replace_wwids(vector mp) } ret = 0; out_file: - close(fd); + pthread_cleanup_pop(1); out: return ret; } @@ -191,7 +195,8 @@ do_remove_wwid(int fd, char *str) { int remove_wwid(char *wwid) { - int fd, len, can_write; + long fd; + int len, can_write; char *str; int ret = -1; struct config *conf; @@ -203,8 +208,10 @@ remove_wwid(char *wwid) { strerror(errno)); return -1; } + pthread_cleanup_push(free, str); if (snprintf(str, len, "/%s/\n", wwid) >= len) { condlog(0, "string overflow trying to remove wwid"); + ret = -1; goto out; } condlog(3, "removing line '%s' from wwids file", str); @@ -212,18 +219,22 @@ remove_wwid(char *wwid) { pthread_cleanup_push(put_multipath_config, conf); fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); pthread_cleanup_pop(1); - if (fd < 0) + + if (fd < 0) { + ret = -1; goto out; - if (!can_write) { - condlog(0, "cannot remove wwid. wwids file is read-only"); - goto out_file; } - ret = do_remove_wwid(fd, str); -out_file: - close(fd); + pthread_cleanup_push(close_fd, (void*)fd); + if (!can_write) { + ret = -1; + condlog(0, "cannot remove wwid. wwids file is read-only"); + } else + ret = do_remove_wwid(fd, str); + pthread_cleanup_pop(1); out: - free(str); + /* free(str) */ + pthread_cleanup_pop(1); return ret; } @@ -382,8 +393,7 @@ static int _failed_wwid_op(const char *wwid, bool rw, long lockfd; int r = -1; - if (snprintf(path, sizeof(path), "%s/%s", shm_dir, wwid) - >= sizeof(path)) { + if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) { condlog(1, "%s: path name overflow", __func__); return -1; } diff --git a/mpathpersist/main.c b/mpathpersist/main.c index 278b8d5..28bfe41 100644 --- a/mpathpersist/main.c +++ b/mpathpersist/main.c @@ -50,7 +50,7 @@ struct config *get_multipath_config(void) return multipath_conf; } -void put_multipath_config(void * arg) +void put_multipath_config(__attribute__((unused)) void * arg) { /* Noop for now */ } @@ -499,6 +499,7 @@ static int handle_args(int argc, char * argv[], int nline) if (ret != MPATH_PR_SUCCESS ) { fprintf (stderr, "Persistent Reserve IN command failed\n"); + free(resp); goto out_fd; } diff --git a/multipath/main.c b/multipath/main.c index 4f4d8e8..cf9d2a2 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -86,7 +86,7 @@ struct config *get_multipath_config(void) return multipath_conf; } -void put_multipath_config(void *arg) +void put_multipath_config(__attribute__((unused)) void *arg) { /* Noop for now */ } @@ -423,8 +423,7 @@ static int find_multipaths_check_timeout(const struct path *pp, long tmo, clock_gettime(CLOCK_REALTIME, &now); - if (snprintf(path, sizeof(path), "%s/%s", shm_find_mp_dir, pp->dev_t) - >= sizeof(path)) { + if (safe_sprintf(path, "%s/%s", shm_find_mp_dir, pp->dev_t)) { condlog(1, "%s: path name overflow", __func__); return FIND_MULTIPATHS_ERROR; } @@ -811,8 +810,10 @@ enum { NOT_DELEGATED = 1, }; -int delegate_to_multipathd(enum mpath_cmds cmd, const char *dev, - enum devtypes dev_type, const struct config *conf) +int delegate_to_multipathd(enum mpath_cmds cmd, + __attribute__((unused)) const char *dev, + __attribute__((unused)) enum devtypes dev_type, + const struct config *conf) { int fd; char command[1024], *p, *reply = NULL; @@ -905,6 +906,7 @@ main (int argc, char *argv[]) exit(RTVL_FAIL); multipath_conf = conf; conf->retrigger_tries = 0; + conf->force_sync = 1; while ((arg = getopt(argc, argv, ":adcChl::FfM:v:p:b:BrR:itTquUwW")) != EOF ) { switch(arg) { case 1: printf("optarg : %s\n",optarg); @@ -1023,7 +1025,7 @@ main (int argc, char *argv[]) if (!dev) goto out; - strncpy(dev, argv[optind], FILE_NAME_SIZE); + strlcpy(dev, argv[optind], FILE_NAME_SIZE); if (dev_type != DEV_UEVENT) dev_type = get_dev_type(dev); if (dev_type == DEV_NONE) { diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 index e866da2..05a5e8f 100644 --- a/multipath/multipath.conf.5 +++ b/multipath/multipath.conf.5 @@ -494,9 +494,10 @@ Check the path state for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF Series, and OEM arrays from IBM DELL SGI STK and SUN. .TP .I directio -(Deprecated) Read the first sector with direct I/O. This checker is being -deprecated, it could cause spurious path failures under high load. -Please use \fItur\fR instead. +(Deprecated) Read the first sector with direct I/O. If you have a large number +of paths, or many AIO users on a system, you may need to use sysctl to +increase fs.aio-max-nr. This checker is being deprecated, it could cause +spurious path failures under high load. Please use \fItur\fR instead. .TP .I cciss_tur (Hardware-dependent) @@ -1472,6 +1473,14 @@ the \fIproduct\fR attribute set to the value of \fIproduct_blacklist\fR. The user_friendly_names prefix to use for this device type, instead of the default "mpath". .TP +.B vpd_vendor +The vendor specific vpd page information, using the vpd page abbreviation. +The vpd page abbreviation can be found by running \fIsg_vpd -e\fR. multipathd +will use this information to gather device specific information that can be +displayed with the \fI%g\fR wilcard for the \fImultipathd show maps format\fR +and \fImultipathd show paths format\fR commands. Currently only the +\fBhp3par\fR vpd page is supported. +.TP .B hardware_handler The hardware handler to use for this device type. The following hardware handler are implemented: diff --git a/multipathd/Makefile b/multipathd/Makefile index d1a9863..8d90117 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -36,6 +36,9 @@ $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(EXEC) $(LIBDEPS) $(GZIP) $(EXEC).8 > $(EXEC).8.gz +cli_handlers.o: cli_handlers.c + $(CC) $(CFLAGS) -Wno-unused-parameter -c -o $@ $< + install: $(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir) $(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir) diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index 8a89904..7d878c8 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -237,7 +237,7 @@ show_map_json (char ** r, int * len, struct multipath * mpp, c = reply; - c += snprint_multipath_map_json(c, maxlen, mpp, 1); + c += snprint_multipath_map_json(c, maxlen, mpp); again = ((c - reply) == maxlen); REALLOC_REPLY(reply, again, maxlen); @@ -1024,16 +1024,17 @@ cli_restore_queueing(void *v, char **reply, int *len, void *data) select_no_path_retry(conf, mpp); pthread_cleanup_pop(1); + /* + * Don't call set_no_path_retry() for the NO_PATH_RETRY_FAIL case. + * That would disable queueing when "restorequeueing" is called, + * and the code never behaved that way. Users might not expect it. + * In almost all cases, queueing will be disabled anyway when we + * are here. + */ if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF && - mpp->no_path_retry != NO_PATH_RETRY_FAIL) { - dm_queue_if_no_path(mpp->alias, 1); - if (mpp->no_path_retry > 0) { - if (mpp->nr_active > 0) - mpp->retry_tick = 0; - else - enter_recovery_mode(mpp); - } - } + mpp->no_path_retry != NO_PATH_RETRY_FAIL) + set_no_path_retry(mpp); + return 0; } @@ -1051,16 +1052,10 @@ cli_restore_all_queueing(void *v, char **reply, int *len, void *data) pthread_cleanup_push(put_multipath_config, conf); select_no_path_retry(conf, mpp); pthread_cleanup_pop(1); + /* See comment in cli_restore_queueing() */ if (mpp->no_path_retry != NO_PATH_RETRY_UNDEF && - mpp->no_path_retry != NO_PATH_RETRY_FAIL) { - dm_queue_if_no_path(mpp->alias, 1); - if (mpp->no_path_retry > 0) { - if (mpp->nr_active > 0) - mpp->retry_tick = 0; - else - enter_recovery_mode(mpp); - } - } + mpp->no_path_retry != NO_PATH_RETRY_FAIL) + set_no_path_retry(mpp); } return 0; } @@ -1085,12 +1080,12 @@ cli_disable_queueing(void *v, char **reply, int *len, void *data) return 1; } - if (mpp->nr_active == 0) + if (count_active_paths(mpp) == 0) mpp->stat_map_failures++; mpp->retry_tick = 0; mpp->no_path_retry = NO_PATH_RETRY_FAIL; mpp->disable_queueing = 1; - dm_queue_if_no_path(mpp->alias, 0); + set_no_path_retry(mpp); return 0; } @@ -1103,12 +1098,12 @@ cli_disable_all_queueing(void *v, char **reply, int *len, void *data) condlog(2, "disable queueing (operator)"); vector_foreach_slot(vecs->mpvec, mpp, i) { - if (mpp->nr_active == 0) + if (count_active_paths(mpp) == 0) mpp->stat_map_failures++; mpp->retry_tick = 0; mpp->no_path_retry = NO_PATH_RETRY_FAIL; mpp->disable_queueing = 1; - dm_queue_if_no_path(mpp->alias, 0); + set_no_path_retry(mpp); } return 0; } diff --git a/multipathd/dmevents.c b/multipathd/dmevents.c index 0034892..b22b47d 100644 --- a/multipathd/dmevents.c +++ b/multipathd/dmevents.c @@ -370,12 +370,12 @@ static int dmevent_loop (void) return -1; /* never reach there */ } -static void rcu_unregister(void *param) +static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } -void *wait_dmevents (void *unused) +void *wait_dmevents (__attribute__((unused)) void *unused) { int r; diff --git a/multipathd/main.c b/multipathd/main.c index 34a5768..8baf9ab 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -33,10 +33,6 @@ */ #include "checkers.h" -#ifdef USE_SYSTEMD -static int use_watchdog; -#endif - /* * libmultipath */ @@ -215,7 +211,7 @@ static void do_sd_notify(enum daemon_status old_state, } #endif -static void config_cleanup(void *arg) +static void config_cleanup(__attribute__((unused)) void *arg) { pthread_mutex_unlock(&config_lock); } @@ -306,7 +302,7 @@ struct config *get_multipath_config(void) return rcu_dereference(multipath_conf); } -void put_multipath_config(void *arg) +void put_multipath_config(__attribute__((unused)) void *arg) { rcu_read_unlock(); } @@ -377,7 +373,7 @@ remove_map_and_stop_waiter(struct multipath *mpp, struct vectors *vecs) * so they don't need to be manually removed here */ condlog(3, "%s: removing map from internal tables", mpp->alias); if (!poll_dmevents) - stop_waiter_thread(mpp, vecs); + stop_waiter_thread(mpp); remove_map(mpp, vecs, PURGE_VEC); } @@ -392,7 +388,7 @@ remove_maps_and_stop_waiters(struct vectors *vecs) if (!poll_dmevents) { vector_foreach_slot(vecs->mpvec, mpp, i) - stop_waiter_thread(mpp, vecs); + stop_waiter_thread(mpp); } else unwatch_all_dmevents(); @@ -409,36 +405,6 @@ set_multipath_wwid (struct multipath * mpp) dm_get_uuid(mpp->alias, mpp->wwid, WWID_SIZE); } -static void set_no_path_retry(struct multipath *mpp) -{ - char is_queueing = 0; - - mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST); - if (mpp->features && strstr(mpp->features, "queue_if_no_path")) - is_queueing = 1; - - switch (mpp->no_path_retry) { - case NO_PATH_RETRY_UNDEF: - break; - case NO_PATH_RETRY_FAIL: - if (is_queueing) - dm_queue_if_no_path(mpp->alias, 0); - break; - case NO_PATH_RETRY_QUEUE: - if (!is_queueing) - dm_queue_if_no_path(mpp->alias, 1); - break; - default: - if (mpp->nr_active > 0) { - mpp->retry_tick = 0; - if (!is_queueing) - dm_queue_if_no_path(mpp->alias, 1); - } else if (is_queueing && mpp->retry_tick == 0) - enter_recovery_mode(mpp); - break; - } -} - int __setup_multipath(struct vectors *vecs, struct multipath *mpp, int reset) { @@ -493,7 +459,7 @@ int update_multipath (struct vectors *vecs, char *mapname, int reset) if (pp->state != PATH_DOWN) { struct config *conf; int oldstate = pp->state; - int checkint; + unsigned int checkint; conf = get_multipath_config(); checkint = conf->checkint; @@ -906,9 +872,8 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) } } } - lock_cleanup_pop(vecs->lock); if (pp) - return ret; + goto out; /* * get path vital state @@ -920,13 +885,13 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) pthread_cleanup_pop(1); if (!pp) { if (ret == PATHINFO_SKIPPED) - return 0; - condlog(3, "%s: failed to get path info", uev->kernel); - return 1; + ret = 0; + else { + condlog(3, "%s: failed to get path info", uev->kernel); + ret = 1; + } + goto out; } - pthread_cleanup_push(cleanup_lock, &vecs->lock); - lock(&vecs->lock); - pthread_testcancel(); ret = store_path(vecs->pathvec, pp); if (!ret) { conf = get_multipath_config(); @@ -940,6 +905,7 @@ uev_add_path (struct uevent *uev, struct vectors * vecs, int need_do_map) free_path(pp); ret = 1; } +out: lock_cleanup_pop(vecs->lock); return ret; } @@ -1503,7 +1469,7 @@ out: return r; } -static void rcu_unregister(void *param) +static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } @@ -1646,7 +1612,7 @@ fail_path (struct path * pp, int del_active) * caller must have locked the path list before calling that function */ static int -reinstate_path (struct path * pp, int add_active) +reinstate_path (struct path * pp) { int ret = 0; @@ -1658,8 +1624,7 @@ reinstate_path (struct path * pp, int add_active) ret = 1; } else { condlog(2, "%s: reinstated", pp->dev_t); - if (add_active) - update_queue_mode_add_path(pp->mpp); + update_queue_mode_add_path(pp->mpp); } return ret; } @@ -1891,7 +1856,7 @@ static int check_path_reinstate_state(struct path * pp) { if (pp->disable_reinstate) { /* If there are no other usable paths, reinstate the path */ - if (pp->mpp->nr_active == 0) { + if (count_active_paths(pp->mpp) == 0) { condlog(2, "%s : reinstating path early", pp->dev); goto reinstate_path; } @@ -1949,8 +1914,9 @@ static int check_path_reinstate_state(struct path * pp) { * so that the cutomer can rectify the issue within this time. After * the completion of san_path_err_recovery_time it should * automatically reinstate the path + * (note: we know that san_path_err_threshold > 0 here). */ - if (pp->path_failures > pp->mpp->san_path_err_threshold) { + if (pp->path_failures > (unsigned int)pp->mpp->san_path_err_threshold) { condlog(2, "%s : hit error threshold. Delaying path reinstatement", pp->dev); pp->dis_reinstate_time = curr_time.tv_sec; pp->disable_reinstate = 1; @@ -1984,15 +1950,15 @@ should_skip_path(struct path *pp){ * and '0' otherwise */ int -check_path (struct vectors * vecs, struct path * pp, int ticks) +check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) { int newstate; int new_path_up = 0; int chkr_new_path_up = 0; - int add_active; int disable_reinstate = 0; int oldchkrstate = pp->chkrstate; - int retrigger_tries, checkint, max_checkint, verbosity; + int retrigger_tries, verbosity; + unsigned int checkint, max_checkint; struct config *conf; int marginal_pathgroups, marginal_changed = 0; int ret; @@ -2162,7 +2128,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks) * paths if there are no other active paths in map. */ disable_reinstate = (newstate == PATH_GHOST && - pp->mpp->nr_active == 0 && + count_active_paths(pp->mpp) == 0 && path_get_tpgs(pp) == TPGS_IMPLICIT) ? 1 : 0; pp->chkrstate = newstate; @@ -2213,12 +2179,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks) /* * reinstate this path */ - if (oldstate != PATH_UP && - oldstate != PATH_GHOST) - add_active = 1; - else - add_active = 0; - if (!disable_reinstate && reinstate_path(pp, add_active)) { + if (!disable_reinstate && reinstate_path(pp)) { condlog(3, "%s: reload map", pp->dev); ev_add_path(pp, vecs, 1); pp->tick = 1; @@ -2241,7 +2202,7 @@ check_path (struct vectors * vecs, struct path * pp, int ticks) pp->dmstate == PSTATE_UNDEF) && !disable_reinstate) { /* Clear IO errors */ - if (reinstate_path(pp, 0)) { + if (reinstate_path(pp)) { condlog(3, "%s: reload map", pp->dev); ev_add_path(pp, vecs, 1); pp->tick = 1; @@ -2319,6 +2280,7 @@ checkerloop (void *ap) struct timespec last_time; struct config *conf; int foreign_tick = 0; + bool use_watchdog; pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); @@ -2330,9 +2292,15 @@ checkerloop (void *ap) get_monotonic_time(&last_time); last_time.tv_sec -= 1; + /* use_watchdog is set from process environment and never changes */ + conf = get_multipath_config(); + use_watchdog = conf->use_watchdog; + put_multipath_config(conf); + while (1) { struct timespec diff_time, start_time, end_time; - int num_paths = 0, ticks = 0, strict_timing, rc = 0; + int num_paths = 0, strict_timing, rc = 0; + unsigned int ticks = 0; get_monotonic_time(&start_time); if (start_time.tv_sec && last_time.tv_sec) { @@ -2607,6 +2575,7 @@ reconfigure (struct vectors * vecs) vecs->pathvec = NULL; delete_all_foreign(); + reset_checker_classes(); /* Re-read any timezone changes */ tzset(); @@ -2618,6 +2587,7 @@ reconfigure (struct vectors * vecs) uxsock_timeout = conf->uxsock_timeout; old = rcu_dereference(multipath_conf); + conf->sequence_nr = old->sequence_nr + 1; rcu_assign_pointer(multipath_conf, conf); call_rcu(&old->rcu, rcu_free_config); @@ -2685,25 +2655,25 @@ handle_signals(bool nonfatal) } static void -sighup (int sig) +sighup(__attribute__((unused)) int sig) { reconfig_sig = 1; } static void -sigend (int sig) +sigend(__attribute__((unused)) int sig) { exit_sig = 1; } static void -sigusr1 (int sig) +sigusr1(__attribute__((unused)) int sig) { log_reset_sig = 1; } static void -sigusr2 (int sig) +sigusr2(__attribute__((unused)) int sig) { condlog(3, "SIGUSR2 received"); } @@ -2792,7 +2762,7 @@ set_oom_adj (void) } static int -child (void * param) +child (__attribute__((unused)) void *param) { pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr; pthread_attr_t log_attr, misc_attr, uevent_attr; @@ -2800,7 +2770,6 @@ child (void * param) struct multipath * mpp; int i; #ifdef USE_SYSTEMD - unsigned long checkint; int startup_done = 0; #endif int rc; @@ -2877,21 +2846,6 @@ child (void * param) setscheduler(); set_oom_adj(); -#ifdef USE_SYSTEMD - envp = getenv("WATCHDOG_USEC"); - if (envp && sscanf(envp, "%lu", &checkint) == 1) { - /* Value is in microseconds */ - conf->max_checkint = checkint / 1000000; - /* Rescale checkint */ - if (conf->checkint > conf->max_checkint) - conf->checkint = conf->max_checkint; - else - conf->checkint = conf->max_checkint / 4; - condlog(3, "enabling watchdog, interval %d max %d", - conf->checkint, conf->max_checkint); - use_watchdog = conf->checkint; - } -#endif /* * Startup done, invalidate configuration */ @@ -3247,7 +3201,8 @@ main (int argc, char *argv[]) void * mpath_pr_event_handler_fn (void * pathp ) { struct multipath * mpp; - int i, ret, isFound; + unsigned int i; + int ret, isFound; struct path * pp = (struct path *)pathp; struct prout_param_descriptor *param; struct prin_resp *resp; diff --git a/multipathd/pidfile.c b/multipathd/pidfile.c index 47d18e2..cc0fbec 100644 --- a/multipathd/pidfile.c +++ b/multipathd/pidfile.c @@ -42,7 +42,7 @@ int pidfile_create(const char *pidFile, pid_t pid) } memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf)-1, "%u", pid); - if (write(fd, buf, strlen(buf)) != strlen(buf)) { + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { condlog(0, "Cannot write pid to pidfile [%s], error was [%s]", pidFile, strerror(errno)); goto fail; diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index bc71679..1c5ce9d 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "checkers.h" #include "memory.h" #include "debug.h" @@ -39,7 +40,7 @@ #include "cli.h" #include "uxlsnr.h" -struct timespec sleep_time = {5, 0}; +static struct timespec sleep_time = {5, 0}; struct client { struct list_head node; @@ -48,9 +49,11 @@ struct client { #define MIN_POLLS 1023 -LIST_HEAD(clients); -pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER; -struct pollfd *polls; +static LIST_HEAD(clients); +static pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER; +static struct pollfd *polls; +static int notify_fd = -1; +static char *watch_config_dir; static bool _socket_client_is_root(int fd); @@ -119,13 +122,13 @@ static void dead_client(struct client *c) pthread_cleanup_pop(1); } -void free_polls (void) +static void free_polls (void) { if (polls) FREE(polls); } -void check_timeout(struct timespec start_time, char *inbuf, +static void check_timeout(struct timespec start_time, char *inbuf, unsigned int timeout) { struct timespec diff_time, end_time; @@ -151,6 +154,8 @@ void uxsock_cleanup(void *arg) long ux_sock = (long)arg; close(ux_sock); + close(notify_fd); + free(watch_config_dir); pthread_mutex_lock(&client_lock); list_for_each_entry_safe(client_loop, client_tmp, &clients, node) { @@ -162,6 +167,110 @@ void uxsock_cleanup(void *arg) free_polls(); } +struct watch_descriptors { + int conf_wd; + int dir_wd; +}; + +/* failing to set the watch descriptor is o.k. we just miss a warning + * message */ +static void reset_watch(int notify_fd, struct watch_descriptors *wds, + unsigned int *sequence_nr) +{ + struct config *conf; + int dir_reset = 0; + int conf_reset = 0; + + if (notify_fd == -1) + return; + + conf = get_multipath_config(); + /* instead of repeatedly try to reset the inotify watch if + * the config directory or multipath.conf isn't there, just + * do it once per reconfigure */ + if (*sequence_nr != conf->sequence_nr) { + *sequence_nr = conf->sequence_nr; + if (wds->conf_wd == -1) + conf_reset = 1; + if (!watch_config_dir || !conf->config_dir || + strcmp(watch_config_dir, conf->config_dir)) { + dir_reset = 1; + if (watch_config_dir) + free(watch_config_dir); + if (conf->config_dir) + watch_config_dir = strdup(conf->config_dir); + else + watch_config_dir = NULL; + } else if (wds->dir_wd == -1) + dir_reset = 1; + } + put_multipath_config(conf); + + if (dir_reset) { + if (wds->dir_wd != -1) { + inotify_rm_watch(notify_fd, wds->dir_wd); + wds->dir_wd = -1; + } + if (watch_config_dir) { + wds->dir_wd = inotify_add_watch(notify_fd, + watch_config_dir, + IN_CLOSE_WRITE | + IN_DELETE | IN_ONLYDIR); + if (wds->dir_wd == -1) + condlog(3, "didn't set up notifications on %s: %m", watch_config_dir); + } + } + if (conf_reset) { + wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, + IN_CLOSE_WRITE); + if (wds->conf_wd == -1) + condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); + } + return; +} + +static void handle_inotify(int fd, struct watch_descriptors *wds) +{ + char buff[1024] + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *event; + ssize_t len; + char *ptr; + int got_notify = 0; + + for (;;) { + len = read(fd, buff, sizeof(buff)); + if (len <= 0) { + if (len < 0 && errno != EAGAIN) { + condlog(3, "error reading from inotify_fd"); + if (wds->conf_wd != -1) + inotify_rm_watch(fd, wds->conf_wd); + if (wds->dir_wd != -1) + inotify_rm_watch(fd, wds->dir_wd); + wds->conf_wd = wds->dir_wd = -1; + } + break; + } + + got_notify = 1; + for (ptr = buff; ptr < buff + len; + ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *) ptr; + + if (event->mask & IN_IGNORED) { + /* multipathd.conf may have been overwritten. + * Try once to reset the notification */ + if (wds->conf_wd == event->wd) + wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); + else if (wds->dir_wd == event->wd) + wds->dir_wd = -1; + } + } + } + if (got_notify) + condlog(1, "Multipath configuration updated.\nReload multipathd for changes to take effect"); +} + /* * entry point */ @@ -173,13 +282,19 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, char *reply; sigset_t mask; int old_clients = MIN_POLLS; + /* conf->sequence_nr will be 1 when uxsock_listen is first called */ + unsigned int sequence_nr = 0; + struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1 }; condlog(3, "uxsock: startup listener"); - polls = (struct pollfd *)MALLOC((MIN_POLLS + 1) * sizeof(struct pollfd)); + polls = (struct pollfd *)MALLOC((MIN_POLLS + 2) * sizeof(struct pollfd)); if (!polls) { condlog(0, "uxsock: failed to allocate poll fds"); exit_daemon(); } + notify_fd = inotify_init1(IN_NONBLOCK); + if (notify_fd == -1) /* it's fine if notifications fail */ + condlog(3, "failed to start up configuration notifications"); sigfillset(&mask); sigdelset(&mask, SIGINT); sigdelset(&mask, SIGTERM); @@ -198,18 +313,18 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, if (num_clients != old_clients) { struct pollfd *new; if (num_clients <= MIN_POLLS && old_clients > MIN_POLLS) { - new = REALLOC(polls, (1 + MIN_POLLS) * + new = REALLOC(polls, (2 + MIN_POLLS) * sizeof(struct pollfd)); } else if (num_clients <= MIN_POLLS && old_clients <= MIN_POLLS) { new = polls; } else { - new = REALLOC(polls, (1+num_clients) * + new = REALLOC(polls, (2 + num_clients) * sizeof(struct pollfd)); } if (!new) { pthread_mutex_unlock(&client_lock); condlog(0, "%s: failed to realloc %d poll fds", - "uxsock", 1 + num_clients); + "uxsock", 2 + num_clients); sched_yield(); continue; } @@ -219,8 +334,15 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, polls[0].fd = ux_sock; polls[0].events = POLLIN; + reset_watch(notify_fd, &wds, &sequence_nr); + if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1)) + polls[1].fd = -1; + else + polls[1].fd = notify_fd; + polls[1].events = POLLIN; + /* setup the clients */ - i = 1; + i = 2; list_for_each_entry(c, &clients, node) { polls[i].fd = c->fd; polls[i].events = POLLIN; @@ -262,7 +384,7 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, } /* see if a client wants to speak to us */ - for (i = 1; i < num_clients + 1; i++) { + for (i = 2; i < num_clients + 2; i++) { if (polls[i].revents & POLLIN) { struct timespec start_time; @@ -321,6 +443,10 @@ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, if (polls[0].revents & POLLIN) { new_client(ux_sock); } + + /* handle inotify events on config files */ + if (polls[1].revents & POLLIN) + handle_inotify(notify_fd, &wds); } return NULL; diff --git a/multipathd/waiter.c b/multipathd/waiter.c index eb8d699..e645766 100644 --- a/multipathd/waiter.c +++ b/multipathd/waiter.c @@ -49,7 +49,7 @@ static void free_waiter (void *data) FREE(wp); } -void stop_waiter_thread (struct multipath *mpp, struct vectors *vecs) +void stop_waiter_thread (struct multipath *mpp) { pthread_t thread; diff --git a/multipathd/waiter.h b/multipathd/waiter.h index 0cfae46..28e0f6d 100644 --- a/multipathd/waiter.h +++ b/multipathd/waiter.h @@ -11,7 +11,7 @@ struct event_thread { struct vectors *vecs; }; -void stop_waiter_thread (struct multipath *mpp, struct vectors *vecs); +void stop_waiter_thread (struct multipath *mpp); int start_waiter_thread (struct multipath *mpp, struct vectors *vecs); #endif /* _WAITER_H */ diff --git a/tests/Makefile b/tests/Makefile index a5cdf39..77ff324 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,17 +1,37 @@ include ../Makefile.inc -CFLAGS += $(BIN_CFLAGS) -I$(multipathdir) -I$(mpathcmddir) +# Test special behavior of gcc 4.8 with nested initializers +# gcc 4.8 compiles blacklist.c only with -Wno-missing-field-initializers +TEST_MISSING_INITIALIZERS = $(shell \ + echo 'struct A {int a, b;}; struct B {struct A a; int b;} b = {.a.a=1};' | \ + $(CC) -c -Werror -Wmissing-field-initializers -o /dev/null -xc - >/dev/null 2>&1 \ + || echo -Wno-missing-field-initializers) +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 -TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy +TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ + alias directio .SILENT: $(TESTS:%=%.o) .PRECIOUS: $(TESTS:%=%-test) all: $(TESTS:%=%.out) +# test-specific compiler flags +# XYZ-test_FLAGS: Additional compiler flags for this test + +ifneq ($(wildcard directio_test_dev),) +DIO_TEST_DEV = $(shell sed -n -e 's/^[[:space:]]*DIO_TEST_DEV[[:space:]]*=[[:space:]]*\([^[:space:]\#]\+\).*/\1/p' < directio_test_dev) +endif +ifneq ($(DIO_TEST_DEV),) +directio-test_FLAGS := -DDIO_TEST_DEV=\"$(DIO_TEST_DEV)\" +endif + # test-specific linker flags -# XYZ-test-TESTDEPS: test libraries containing __wrap_xyz functions +# XYZ-test_TESTDEPS: test libraries containing __wrap_xyz functions # XYZ-test_OBJDEPS: object files from libraries to link in explicitly # That may be necessary if functions called from the object file are wrapped # (wrapping works only for symbols which are undefined after processing a @@ -23,10 +43,19 @@ hwtable-test_TESTDEPS := test-lib.o hwtable-test_OBJDEPS := ../libmultipath/discovery.o ../libmultipath/blacklist.o \ ../libmultipath/prio.o ../libmultipath/callout.o ../libmultipath/structs.o hwtable-test_LIBDEPS := -ludev -lpthread -ldl +blacklist-test_TESTDEPS := test-log.o blacklist-test_OBJDEPS := ../libmultipath/blacklist.o blacklist-test_LIBDEPS := -ludev vpd-test_OBJDEPS := ../libmultipath/discovery.o vpd-test_LIBDEPS := -ludev -lpthread -ldl +alias-test_TESTDEPS := test-log.o +alias-test_LIBDEPS := -lpthread -ldl +ifneq ($(DIO_TEST_DEV),) +directio-test_LIBDEPS := -laio +endif + +%.o: %.c + $(CC) $(CFLAGS) $($*-test_FLAGS) -c -o $@ $< lib/libchecktur.so: mkdir lib @@ -38,8 +67,11 @@ lib/libchecktur.so: OBJS = $(TESTS:%=%.o) test-lib.o -clean: dep_clean - $(RM) $(TESTS:%=%-test) $(TESTS:%=%.out) $(OBJS) +test_clean: + $(RM) $(TESTS:%=%.out) + +clean: test_clean dep_clean + $(RM) $(TESTS:%=%-test) $(OBJS) *.o.wrap $(RM) -rf lib .SECONDARY: $(OBJS) @@ -53,6 +85,7 @@ dep_clean: @sed -n 's/^.*__wrap_\([a-zA-Z0-9_]*\).*$$/-Wl,--wrap=\1/p' $< | \ sort -u | tr '\n' ' ' >$@ + # COLON will get expanded during second expansion below COLON:=: .SECONDEXPANSION: diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6438a82 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,72 @@ +# multipath-tools unit tests + +Unit tests are built and run by running `make test` in the top directory, +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. + +## Notes on individual tests + +### Tests that require root permissions + +The following tests must be run as root, otherwise some test items will be +skipped because of missing permissions, or the test will fail outright: + + * `dmevents` + * `directio` (if `DIO_TEST_DEV` is set, see below) + +To run these tests, after building the tests as non-root user, change to the +`tests` directory and run `make test-clean`; then run `make` again as root. + +### directio test + +This test includes test items that require a access to a block device. The +device will be opened in read-only mode; you don't need to worry about data +loss. However, the user needs to specify a device to be used. Set the +environment variable `DIO_TEST_DEV` to the path of the device. +Alternatively, create a file `directio_test_dev` under +the `tests` directory containting a single line that sets this environment +variable in Bourne Shell syntax, like this: + + DIO_TEST_DEV=/dev/sdc3 + +After that, run `make directio.out` as root in the `tests` directory to +perform the test. + +## Adding tests + +The unit tests are based on the [cmocka test framework](https://cmocka.org/), +and make use of cmocka's "mock objects" feature to simulate how the code behaves +for different input values. cmocka achieves this by modifying the symbol +lookup at link time, substituting "wrapper functions" for the originally +called function. The Makefile contains code to make sure that `__wrap_xyz()` +wrapper functions are automatically passed to the linker with matching +`-Wl,--wrap` command line arguments, so that tests are correctly rebuilt if +wrapper functions are added or removed. + +### Making sure symbol wrapping works: OBJDEPS + +Special care must be taken to wrap function calls inside a library. Suppose you want +to wrap a function which is both defined in libmultipath and called from other +functions in libmultipath, such as `checker_check()`. When `libmultipath.so` is +created, the linker resolves calls to `checker_check()` inside the `.so` +file. When later the test executable is built by linking the test object file with +`libmultipath.so`, these calls can't be wrapped any more, because they've +already been resolved, and wrapping works only for *unresolved* symbols. +Therefore, object files from libraries that contain calls to functions +which need to be wrapped must be explicitly listed on the linker command line +in order to make the wrapping work. To enforce this, add these object files to +the `xyz-test_OBJDEPS` variable in the Makefile. + +### Using wrapper function libraries: TESTDEPS + +Some wrapper functions are useful in multiple tests. These are maintained in +separate input files, such as `test-lib.c` or `test-log.c`. List these files +in the `xyz-test_TESTDEPS` variable for your test program if you need these +wrappers. + +### Specifying library dependencies: LIBDEPS + +In order to keep the tests lean, not all libraries that libmultipath +normally pulls in are used for every test. Add libraries you need (such as +`-lpthread`) to the `xyz-test_LIBDEPS` variable. diff --git a/tests/alias.c b/tests/alias.c new file mode 100644 index 0000000..30414db --- /dev/null +++ b/tests/alias.c @@ -0,0 +1,744 @@ +#include +#include +#include +#include +#include "util.h" +#include "alias.h" +#include "test-log.h" +#include + +#include "globals.c" +#include "../libmultipath/alias.c" + +#if INT_MAX == 0x7fffffff +/* user_friendly_name for map #INT_MAX */ +#define MPATH_ID_INT_MAX "fxshrxw" +/* ... and one less */ +#define MPATH_ID_INT_MAX_m1 "fxshrxv" +/* ... and one more */ +#define MPATH_ID_INT_MAX_p1 "fxshrxx" +#endif + +void __wrap_rewind(FILE *stream) +{} + +char *__wrap_fgets(char *buf, int n, FILE *stream) +{ + char *val = mock_ptr_type(char *); + if (!val) + return NULL; + strlcpy(buf, val, n); + return buf; +} + +static int __set_errno(int err) +{ + if (err >= 0) { + errno = 0; + return err; + } else { + errno = -err; + return -1; + } +} + +off_t __wrap_lseek(int fd, off_t offset, int whence) +{ + return __set_errno(mock_type(int)); + +} + +ssize_t __wrap_write(int fd, const void *buf, size_t count) +{ + check_expected(count); + check_expected(buf); + return __set_errno(mock_type(int)); +} + +int __wrap_ftruncate(int fd, off_t length) +{ + check_expected(length); + return __set_errno(mock_type(int)); +} + +static void fd_mpatha(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 1, sizeof(buf), "FOO"); + assert_int_equal(rc, 4); + assert_string_equal(buf, "FOOa"); +} + +static void fd_mpathz(void **state) +{ + /* This also tests a "short" buffer, see fd_mpath_short1 */ + char buf[5]; + int rc; + + rc = format_devname(buf, 26, sizeof(buf), "FOO"); + assert_int_equal(rc, 4); + assert_string_equal(buf, "FOOz"); +} + +static void fd_mpathaa(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26 + 1, sizeof(buf), "FOO"); + assert_int_equal(rc, 5); + assert_string_equal(buf, "FOOaa"); +} + +static void fd_mpathzz(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26*26 + 26, sizeof(buf), "FOO"); + assert_int_equal(rc, 5); + assert_string_equal(buf, "FOOzz"); +} + +static void fd_mpathaaa(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26*26 + 27, sizeof(buf), "FOO"); + assert_int_equal(rc, 6); + assert_string_equal(buf, "FOOaaa"); +} + +static void fd_mpathzzz(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26*26*26 + 26*26 + 26, sizeof(buf), "FOO"); + assert_int_equal(rc, 6); + assert_string_equal(buf, "FOOzzz"); +} + +static void fd_mpathaaaa(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26*26*26 + 26*26 + 27, sizeof(buf), "FOO"); + assert_int_equal(rc, 7); + assert_string_equal(buf, "FOOaaaa"); +} + +static void fd_mpathzzzz(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, 26*26*26*26 + 26*26*26 + 26*26 + 26, + sizeof(buf), "FOO"); + assert_int_equal(rc, 7); + assert_string_equal(buf, "FOOzzzz"); +} + +#ifdef MPATH_ID_INT_MAX +static void fd_mpath_max(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, INT_MAX, sizeof(buf), ""); + assert_int_equal(rc, strlen(MPATH_ID_INT_MAX)); + assert_string_equal(buf, MPATH_ID_INT_MAX); +} +#endif + +static void fd_mpath_max1(void **state) +{ + char buf[32]; + int rc; + + rc = format_devname(buf, INT_MIN, sizeof(buf), ""); + assert_int_equal(rc, -1); +} + +static void fd_mpath_short(void **state) +{ + char buf[4]; + int rc; + + rc = format_devname(buf, 1, sizeof(buf), "FOO"); + assert_int_equal(rc, -1); +} + +static void fd_mpath_short1(void **state) +{ + char buf[5]; + int rc; + + rc = format_devname(buf, 27, sizeof(buf), "FOO"); + assert_int_equal(rc, -1); +} + +static int test_format_devname(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(fd_mpatha), + cmocka_unit_test(fd_mpathz), + cmocka_unit_test(fd_mpathaa), + cmocka_unit_test(fd_mpathzz), + cmocka_unit_test(fd_mpathaaa), + cmocka_unit_test(fd_mpathzzz), + cmocka_unit_test(fd_mpathaaaa), + cmocka_unit_test(fd_mpathzzzz), +#ifdef MPATH_ID_INT_MAX + cmocka_unit_test(fd_mpath_max), +#endif + cmocka_unit_test(fd_mpath_max1), + cmocka_unit_test(fd_mpath_short), + cmocka_unit_test(fd_mpath_short1), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void sd_mpatha(void **state) +{ + int rc = scan_devname("MPATHa", "MPATH"); + + assert_int_equal(rc, 1); +} + +/* + * Text after whitespace is ignored. But an overlong input + * errors out, even if it's just whitespace. + * It's kind of strange that scan_devname() treats whitespace + * like this. But I'm not sure if some corner case depends + * on this behavior. + */ +static void sd_mpatha_spc(void **state) +{ + int rc = scan_devname("MPATHa 00", "MPATH"); + + assert_int_equal(rc, 1); +} + +static void sd_mpatha_tab(void **state) +{ + int rc = scan_devname("MPATHa\t00", "MPATH"); + + assert_int_equal(rc, 1); +} + +static void sd_overlong(void **state) +{ + int rc = scan_devname("MPATHa ", "MPATH"); + + assert_int_equal(rc, -1); +} + +static void sd_overlong1(void **state) +{ + int rc = scan_devname("MPATHabcdefgh", "MPATH"); + + assert_int_equal(rc, -1); +} + +static void sd_noprefix(void **state) +{ + int rc = scan_devname("MPATHa", NULL); + + assert_int_equal(rc, -1); +} + +static void sd_nomatchprefix(void **state) +{ + int rc = scan_devname("MPATHa", "mpath"); + + assert_int_equal(rc, -1); +} + +static void sd_eq_prefix(void **state) +{ + int rc = scan_devname("MPATH", "MPATH"); + + assert_int_equal(rc, -1); +} + +static void sd_bad_1(void **state) +{ + int rc = scan_devname("MPATH0", "MPATH"); + + assert_int_equal(rc, -1); +} + +static void sd_bad_2(void **state) +{ + int rc = scan_devname("MPATHa0c", "MPATH"); + + assert_int_equal(rc, -1); +} + +#ifdef MPATH_ID_INT_MAX +static void sd_max(void **state) +{ + int rc = scan_devname("MPATH" MPATH_ID_INT_MAX, "MPATH"); + + assert_int_equal(rc, INT_MAX); +} + +static void sd_max_p1(void **state) +{ + int rc = scan_devname("MPATH" MPATH_ID_INT_MAX_p1, "MPATH"); + + assert_int_equal(rc, -1); +} +#endif + +static void sd_fd_many(void **state) +{ + char buf[32]; + int rc, i; + + for (i = 1; i < 5000; i++) { + rc = format_devname(buf, i, sizeof(buf), "MPATH"); + assert_in_range(rc, 6, 8); + rc = scan_devname(buf, "MPATH"); + assert_int_equal(rc, i); + } +} + +static void sd_fd_random(void **state) +{ + char buf[32]; + int rc, i, n; + + srandom(1); + for (i = 1; i < 1000; i++) { + n = random() & 0xffff; + rc = format_devname(buf, n, sizeof(buf), "MPATH"); + assert_in_range(rc, 6, 9); + rc = scan_devname(buf, "MPATH"); + assert_int_equal(rc, n); + } +} + +static int test_scan_devname(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(sd_mpatha), + cmocka_unit_test(sd_mpatha_spc), + cmocka_unit_test(sd_mpatha_tab), + cmocka_unit_test(sd_overlong), + cmocka_unit_test(sd_overlong1), + cmocka_unit_test(sd_noprefix), + cmocka_unit_test(sd_nomatchprefix), + cmocka_unit_test(sd_eq_prefix), + cmocka_unit_test(sd_bad_1), + cmocka_unit_test(sd_bad_2), +#ifdef MPATH_ID_INT_MAX + cmocka_unit_test(sd_max), + cmocka_unit_test(sd_max_p1), +#endif + cmocka_unit_test(sd_fd_many), + cmocka_unit_test(sd_fd_random), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void lb_empty(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID0", &alias, NULL); + assert_int_equal(rc, 1); + assert_ptr_equal(alias, NULL); +} + +static void lb_match_a(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + expect_condlog(3, "Found matching wwid [WWID0] in bindings file." + " Setting alias to MPATHa\n"); + rc = lookup_binding(NULL, "WWID0", &alias, "MPATH"); + assert_int_equal(rc, 0); + assert_ptr_not_equal(alias, NULL); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void lb_nomatch_a(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID1", &alias, "MPATH"); + assert_int_equal(rc, 2); + assert_ptr_equal(alias, NULL); +} + +static void lb_match_c(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, "MPATHc WWID1\n"); + expect_condlog(3, "Found matching wwid [WWID1] in bindings file." + " Setting alias to MPATHc\n"); + rc = lookup_binding(NULL, "WWID1", &alias, "MPATH"); + assert_int_equal(rc, 0); + assert_ptr_not_equal(alias, NULL); + assert_string_equal(alias, "MPATHc"); + free(alias); +} + +static void lb_nomatch_a_c(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, "MPATHc WWID1\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, 2); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_c_a(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHc WWID1\n"); + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, 2); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_a_b(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, "MPATHz WWID26\n"); + will_return(__wrap_fgets, "MPATHb WWID1\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, 3); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_a_b_bad(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, "MPATHz WWID26\n"); + will_return(__wrap_fgets, "MPATHb\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, 3); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_b_a(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHb WWID1\n"); + will_return(__wrap_fgets, "MPATHz WWID26\n"); + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, 27); + assert_ptr_equal(alias, NULL); +} + +#ifdef MPATH_ID_INT_MAX +static void lb_nomatch_int_max(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHb WWID1\n"); + will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(0, "no more available user_friendly_names\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, -1); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_int_max_m1(void **state) +{ + int rc; + char *alias; + + will_return(__wrap_fgets, "MPATHb WWID1\n"); + will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH"); + assert_int_equal(rc, INT_MAX); + assert_ptr_equal(alias, NULL); +} +#endif + +static int test_lookup_binding(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(lb_empty), + cmocka_unit_test(lb_match_a), + cmocka_unit_test(lb_nomatch_a), + cmocka_unit_test(lb_match_c), + cmocka_unit_test(lb_nomatch_a_c), + cmocka_unit_test(lb_nomatch_c_a), + cmocka_unit_test(lb_nomatch_a_b), + cmocka_unit_test(lb_nomatch_a_b_bad), + cmocka_unit_test(lb_nomatch_b_a), +#ifdef MPATH_ID_INT_MAX + cmocka_unit_test(lb_nomatch_int_max), + cmocka_unit_test(lb_nomatch_int_max_m1), +#endif + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void rl_empty(void **state) +{ + int rc; + char buf[WWID_SIZE]; + + buf[0] = '\0'; + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + rc = rlookup_binding(NULL, buf, "MPATHa"); + assert_int_equal(rc, -1); + assert_string_equal(buf, ""); +} + +static void rl_match_a(void **state) +{ + int rc; + char buf[WWID_SIZE]; + + buf[0] = '\0'; + will_return(__wrap_fgets, "MPATHa WWID0\n"); + expect_condlog(3, "Found matching alias [MPATHa] in bindings file.\n" + "Setting wwid to WWID0\n"); + rc = rlookup_binding(NULL, buf, "MPATHa"); + assert_int_equal(rc, 0); + assert_string_equal(buf, "WWID0"); +} + +static void rl_nomatch_a(void **state) +{ + int rc; + char buf[WWID_SIZE]; + + buf[0] = '\0'; + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "No matching alias [MPATHb] in bindings file.\n"); + rc = rlookup_binding(NULL, buf, "MPATHb"); + assert_int_equal(rc, -1); + assert_string_equal(buf, ""); +} + +static void rl_malformed_a(void **state) +{ + int rc; + char buf[WWID_SIZE]; + + buf[0] = '\0'; + will_return(__wrap_fgets, "MPATHa \n"); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "Ignoring malformed line 1 in bindings file\n"); + expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + rc = rlookup_binding(NULL, buf, "MPATHa"); + assert_int_equal(rc, -1); + assert_string_equal(buf, ""); +} + +static void rl_overlong_a(void **state) +{ + int rc; + char buf[WWID_SIZE]; + char line[WWID_SIZE + 10]; + + snprintf(line, sizeof(line), "MPATHa "); + memset(line + strlen(line), 'W', sizeof(line) - 2 - strlen(line)); + snprintf(line + sizeof(line) - 2, 2, "\n"); + + buf[0] = '\0'; + will_return(__wrap_fgets, line); + will_return(__wrap_fgets, NULL); + expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); + expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + rc = rlookup_binding(NULL, buf, "MPATHa"); + assert_int_equal(rc, -1); + assert_string_equal(buf, ""); +} + +static void rl_match_b(void **state) +{ + int rc; + char buf[WWID_SIZE]; + + buf[0] = '\0'; + will_return(__wrap_fgets, "MPATHa WWID0\n"); + will_return(__wrap_fgets, "MPATHz WWID26\n"); + will_return(__wrap_fgets, "MPATHb WWID2\n"); + expect_condlog(3, "Found matching alias [MPATHb] in bindings file.\n" + "Setting wwid to WWID2\n"); + rc = rlookup_binding(NULL, buf, "MPATHb"); + assert_int_equal(rc, 0); + assert_string_equal(buf, "WWID2"); +} + +static int test_rlookup_binding(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(rl_empty), + cmocka_unit_test(rl_match_a), + cmocka_unit_test(rl_nomatch_a), + cmocka_unit_test(rl_malformed_a), + cmocka_unit_test(rl_overlong_a), + cmocka_unit_test(rl_match_b), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void al_a(void **state) +{ + static const char ln[] = "MPATHa WWIDa\n"; + char *alias; + + will_return(__wrap_lseek, 0); + expect_value(__wrap_write, count, strlen(ln)); + expect_string(__wrap_write, buf, ln); + will_return(__wrap_write, strlen(ln)); + expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); + + alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + assert_ptr_not_equal(alias, NULL); + assert_string_equal(alias, "MPATHa"); +} + +static void al_zz(void **state) +{ + static const char ln[] = "MPATHzz WWIDzz\n"; + char *alias; + + will_return(__wrap_lseek, 0); + expect_value(__wrap_write, count, strlen(ln)); + expect_string(__wrap_write, buf, ln); + will_return(__wrap_write, strlen(ln)); + expect_condlog(3, "Created new binding [MPATHzz] for WWID [WWIDzz]\n"); + + alias = allocate_binding(0, "WWIDzz", 26*26 + 26, "MPATH"); + assert_ptr_not_equal(alias, NULL); + assert_string_equal(alias, "MPATHzz"); +} + +static void al_0(void **state) +{ + char *alias; + + expect_condlog(0, "allocate_binding: cannot allocate new binding for id 0\n"); + alias = allocate_binding(0, "WWIDa", 0, "MPATH"); + assert_ptr_equal(alias, NULL); +} + +static void al_m2(void **state) +{ + char *alias; + + expect_condlog(0, "allocate_binding: cannot allocate new binding for id -2\n"); + alias = allocate_binding(0, "WWIDa", -2, "MPATH"); + assert_ptr_equal(alias, NULL); +} + +static void al_lseek_err(void **state) +{ + char *alias; + + will_return(__wrap_lseek, -ENODEV); + expect_condlog(0, "Cannot seek to end of bindings file : No such device\n"); + alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + assert_ptr_equal(alias, NULL); +} + +static void al_write_err(void **state) +{ + static const char ln[] = "MPATHa WWIDa\n"; + const int offset = 20; + char *alias; + + will_return(__wrap_lseek, offset); + expect_value(__wrap_write, count, strlen(ln)); + expect_string(__wrap_write, buf, ln); + will_return(__wrap_write, strlen(ln) - 1); + expect_value(__wrap_ftruncate, length, offset); + will_return(__wrap_ftruncate, 0); + expect_condlog(0, "Cannot write binding to bindings file : Success\n"); + + alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + assert_ptr_equal(alias, NULL); +} + +static int test_allocate_binding(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(al_a), + cmocka_unit_test(al_zz), + cmocka_unit_test(al_0), + cmocka_unit_test(al_m2), + cmocka_unit_test(al_lseek_err), + cmocka_unit_test(al_write_err), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + +int main(void) +{ + int ret = 0; + + ret += test_format_devname(); + ret += test_scan_devname(); + ret += test_lookup_binding(); + ret += test_rlookup_binding(); + ret += test_allocate_binding(); + + return ret; +} diff --git a/tests/blacklist.c b/tests/blacklist.c index 362c44d..6e7c186 100644 --- a/tests/blacklist.c +++ b/tests/blacklist.c @@ -21,7 +21,7 @@ #include #include "globals.c" #include "blacklist.h" -#include "log.h" +#include "test-log.h" struct udev_device { const char *sysname; @@ -40,8 +40,6 @@ struct udev_list_entry * __wrap_udev_device_get_properties_list_entry(struct udev_device *udev_device) { assert_non_null(udev_device); - if (!udev_device->property_list) - return NULL; if (!*udev_device->property_list) return NULL; return (struct udev_list_entry *)udev_device->property_list; @@ -62,24 +60,6 @@ __wrap_udev_list_entry_get_name(struct udev_list_entry *list_entry) return *(const char **)list_entry; } -void __wrap_dlog (int sink, int prio, const char * fmt, ...) -{ - char buff[MAX_MSG_SIZE]; - va_list ap; - - assert_int_equal(prio, mock_type(int)); - va_start(ap, fmt); - vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); - va_end(ap); - assert_string_equal(buff, mock_ptr_type(char *)); -} - -void expect_condlog(int prio, char *string) -{ - will_return(__wrap_dlog, prio); - will_return(__wrap_dlog, string); -} - vector blist_devnode_sdb; vector blist_all; vector blist_device_foo_bar; diff --git a/tests/directio.c b/tests/directio.c new file mode 100644 index 0000000..3cd7a52 --- /dev/null +++ b/tests/directio.c @@ -0,0 +1,776 @@ +/* + * Copyright (c) 2018 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 "globals.c" +#include "../libmultipath/checkers/directio.c" + +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 full_timeout = { .tv_sec = -1 }; + +int __real_ioctl(int fd, unsigned long request, void *argp); + +int __wrap_ioctl(int fd, unsigned long request, void *argp) +{ +#ifdef DIO_TEST_DEV + mock_type(int); + return __real_ioctl(fd, request, argp); +#else + int *blocksize = (int *)argp; + + assert_int_equal(fd, test_fd); + assert_int_equal(request, BLKBSZGET); + assert_non_null(blocksize); + *blocksize = mock_type(int); + return 0; +#endif +} + +int __real_fcntl(int fd, int cmd, long arg); + +int __wrap_fcntl(int fd, int cmd, long arg) +{ +#ifdef DIO_TEST_DEV + return __real_fcntl(fd, cmd, arg); +#else + assert_int_equal(fd, test_fd); + assert_int_equal(cmd, F_GETFL); + return O_DIRECT; +#endif +} + +int __real___fxstat(int ver, int fd, struct stat *statbuf); + +int __wrap___fxstat(int ver, int fd, struct stat *statbuf) +{ +#ifdef DIO_TEST_DEV + return __real___fxstat(ver, fd, statbuf); +#else + assert_int_equal(fd, test_fd); + assert_non_null(statbuf); + memset(statbuf, 0, sizeof(struct stat)); + return 0; +#endif +} + +int __real_io_setup(int maxevents, io_context_t *ctxp); + +int __wrap_io_setup(int maxevents, io_context_t *ctxp) +{ + ioctx_count++; +#ifdef DIO_TEST_DEV + int ret = mock_type(int); + assert_int_equal(ret, __real_io_setup(maxevents, ctxp)); + return ret; +#else + return mock_type(int); +#endif +} + +int __real_io_destroy(io_context_t ctx); + +int __wrap_io_destroy(io_context_t ctx) +{ + ioctx_count--; +#ifdef DIO_TEST_DEV + int ret = mock_type(int); + assert_int_equal(ret, __real_io_destroy(ctx)); + return ret; +#else + return mock_type(int); +#endif +} + +int __real_io_submit(io_context_t ctx, long nr, struct iocb *ios[]); + +int __wrap_io_submit(io_context_t ctx, long nr, struct iocb *ios[]) +{ +#ifdef DIO_TEST_DEV + struct timespec dev_delay = { .tv_nsec = 100000 }; + int ret = mock_type(int); + assert_int_equal(ret, __real_io_submit(ctx, nr, ios)); + nanosleep(&dev_delay, NULL); + return ret; +#else + return mock_type(int); +#endif +} + +int __real_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); + +int __wrap_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt) +{ +#ifdef DIO_TEST_DEV + mock_type(int); + return __real_io_cancel(ctx, iocb, evt); +#else + return mock_type(int); +#endif +} + +int __real_io_getevents(io_context_t ctx, long min_nr, long nr, + struct io_event *events, struct timespec *timeout); + +int __wrap_io_getevents(io_context_t ctx, long min_nr, long nr, + struct io_event *events, struct timespec *timeout) +{ + int nr_evs; +#ifndef DIO_TEST_DEV + struct timespec *sleep_tmo; + int i; + struct io_event *evs; +#endif + + assert_non_null(timeout); + nr_evs = mock_type(int); + assert_true(nr_evs <= nr); + if (!nr_evs) + return 0; +#ifdef DIO_TEST_DEV + mock_ptr_type(struct timespec *); + mock_ptr_type(struct io_event *); + assert_int_equal(nr_evs, __real_io_getevents(ctx, min_nr, nr_evs, + events, timeout)); +#else + sleep_tmo = mock_ptr_type(struct timespec *); + if (sleep_tmo) { + if (sleep_tmo->tv_sec < 0) + nanosleep(timeout, NULL); + else + nanosleep(sleep_tmo, NULL); + } + if (nr_evs < 0) { + errno = -nr_evs; + return -1; + } + evs = mock_ptr_type(struct io_event *); + for (i = 0; i < nr_evs; i++) + events[i] = evs[i]; +#endif + ev_off -= nr_evs; + return nr_evs; +} + +static void return_io_getevents_none(void) +{ + will_return(__wrap_io_getevents, 0); +} + +static void return_io_getevents_nr(struct timespec *ts, int nr, + struct async_req **reqs, int *res) +{ + int i, off = 0; + + for(i = 0; i < nr; i++) { + mock_events[i + ev_off].obj = &reqs[i]->io; + if (res[i] == 0) + mock_events[i + ev_off].res = reqs[i]->blksize; + } + while (nr > 0) { + will_return(__wrap_io_getevents, (nr > 128)? 128 : nr); + will_return(__wrap_io_getevents, ts); + will_return(__wrap_io_getevents, &mock_events[off + ev_off]); + ts = NULL; + off += 128; + nr -= 128; + } + if (nr == 0) + will_return(__wrap_io_getevents, 0); + ev_off += i; +} + +void do_check_state(struct checker *c, int sync, int timeout, int chk_state) +{ + struct directio_context * ct = (struct directio_context *)c->context; + + if (!ct->running) + will_return(__wrap_io_submit, 1); + assert_int_equal(check_state(test_fd, ct, sync, timeout), chk_state); + assert_int_equal(ev_off, 0); + memset(mock_events, 0, sizeof(mock_events)); +} + +void do_libcheck_reset(int nr_aio_grps) +{ + int count = 0; + struct aio_group *aio_grp; + + list_for_each_entry(aio_grp, &aio_grp_list, node) + count++; + assert_int_equal(count, nr_aio_grps); + for (count = 0; count < nr_aio_grps; count++) + will_return(__wrap_io_destroy, 0); + libcheck_reset(); + assert_true(list_empty(&aio_grp_list)); + assert_int_equal(ioctx_count, 0); +} + +static void do_libcheck_init(struct checker *c, int blocksize, + struct async_req **req) +{ + struct directio_context * ct; + + c->fd = test_fd; + will_return(__wrap_ioctl, blocksize); + assert_int_equal(libcheck_init(c), 0); + ct = (struct directio_context *)c->context; + assert_non_null(ct); + assert_non_null(ct->aio_grp); + assert_non_null(ct->req); + if (req) + *req = ct->req; +#ifndef DIO_TEST_DEV + /* don't check fake blocksize on real devices */ + assert_int_equal(ct->req->blksize, blocksize); +#endif +} + +static int is_checker_running(struct checker *c) +{ + struct directio_context * ct = (struct directio_context *)c->context; + return ct->running; +} + +static struct aio_group *get_aio_grp(struct checker *c) +{ + struct directio_context * ct = (struct directio_context *)c->context; + + assert_non_null(ct); + return ct->aio_grp; +} + +static void check_aio_grp(struct aio_group *aio_grp, int holders, + int orphans) +{ + int count = 0; + struct list_head *item; + + list_for_each(item, &aio_grp->orphans) + count++; + assert_int_equal(holders, aio_grp->holders); + assert_int_equal(orphans, count); +} + +/* simple resetting test */ +static void test_reset(void **state) +{ + assert_true(list_empty(&aio_grp_list)); + do_libcheck_reset(0); +} + +/* tests initializing, then resetting, and then initializing again */ +static void test_init_reset_init(void **state) +{ + struct checker c = {0}; + struct aio_group *aio_grp, *tmp_grp; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, NULL); + aio_grp = get_aio_grp(&c); + check_aio_grp(aio_grp, 1, 0); + list_for_each_entry(tmp_grp, &aio_grp_list, node) + assert_ptr_equal(aio_grp, tmp_grp); + libcheck_free(&c); + check_aio_grp(aio_grp, 0, 0); + do_libcheck_reset(1); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, NULL); + aio_grp = get_aio_grp(&c); + check_aio_grp(aio_grp, 1, 0); + list_for_each_entry(tmp_grp, &aio_grp_list, node) + assert_ptr_equal(aio_grp, tmp_grp); + libcheck_free(&c); + check_aio_grp(aio_grp, 0, 0); + do_libcheck_reset(1); +} + +/* test initializing and then freeing 4096 checkers */ +static void test_init_free(void **state) +{ + int i, count = 0; + struct checker c[4096] = {0}; + struct aio_group *aio_grp; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 4096; i++) { + struct directio_context * ct; + + if (i % 3 == 0) + do_libcheck_init(&c[i], 512, NULL); + else if (i % 3 == 1) + do_libcheck_init(&c[i], 1024, NULL); + else + do_libcheck_init(&c[i], 4096, NULL); + ct = (struct directio_context *)c[i].context; + assert_non_null(ct->aio_grp); + if ((i & 1023) == 0) + aio_grp = ct->aio_grp; + else { + assert_ptr_equal(ct->aio_grp, aio_grp); + assert_int_equal(aio_grp->holders, (i & 1023) + 1); + } + } + count = 0; + list_for_each_entry(aio_grp, &aio_grp_list, node) + count++; + assert_int_equal(count, 4); + for (i = 0; i < 4096; i++) { + struct directio_context * ct = (struct directio_context *)c[i].context; + + aio_grp = ct->aio_grp; + libcheck_free(&c[i]); + assert_int_equal(aio_grp->holders, 1023 - (i & 1023)); + } + list_for_each_entry(aio_grp, &aio_grp_list, node) + assert_int_equal(aio_grp->holders, 0); + do_libcheck_reset(4); +} + +/* check mixed initializing and freeing 4096 checkers */ +static void test_multi_init_free(void **state) +{ + int i, count; + struct checker c[4096] = {0}; + struct aio_group *aio_grp; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + will_return(__wrap_io_setup, 0); + for (count = 0, i = 0; i < 4096; count++) { + /* usually init, but occasionally free checkers */ + if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { + do_libcheck_init(&c[i], 4096, NULL); + i++; + } else { + i--; + libcheck_free(&c[i]); + } + } + count = 0; + list_for_each_entry(aio_grp, &aio_grp_list, node) { + assert_int_equal(aio_grp->holders, 1024); + count++; + } + assert_int_equal(count, 4); + for (count = 0, i = 4096; i > 0; count++) { + /* usually free, but occasionally init checkers */ + if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { + i--; + libcheck_free(&c[i]); + } else { + do_libcheck_init(&c[i], 4096, NULL); + i++; + } + } + do_libcheck_reset(4); +} + +/* simple single checker sync test */ +static void test_check_state_simple(void **state) +{ + struct checker c = {0}; + struct async_req *req; + int res = 0; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, &req); + return_io_getevents_nr(NULL, 1, &req, &res); + do_check_state(&c, 1, 30, PATH_UP); + libcheck_free(&c); + do_libcheck_reset(1); +} + +/* test sync timeout */ +static void test_check_state_timeout(void **state) +{ + struct checker c = {0}; + struct aio_group *aio_grp; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, NULL); + aio_grp = get_aio_grp(&c); + return_io_getevents_none(); + will_return(__wrap_io_cancel, 0); + do_check_state(&c, 1, 30, PATH_DOWN); + check_aio_grp(aio_grp, 1, 0); +#ifdef DIO_TEST_DEV + /* io_cancel will return negative value on timeout, so it happens again + * when freeing the checker */ + will_return(__wrap_io_cancel, 0); +#endif + libcheck_free(&c); + do_libcheck_reset(1); +} + +/* test async timeout */ +static void test_check_state_async_timeout(void **state) +{ + struct checker c = {0}; + struct aio_group *aio_grp; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, NULL); + aio_grp = get_aio_grp(&c); + return_io_getevents_none(); + do_check_state(&c, 0, 3, PATH_PENDING); + return_io_getevents_none(); + do_check_state(&c, 0, 3, PATH_PENDING); + return_io_getevents_none(); + do_check_state(&c, 0, 3, PATH_PENDING); + return_io_getevents_none(); + will_return(__wrap_io_cancel, 0); + do_check_state(&c, 0, 3, PATH_DOWN); + check_aio_grp(aio_grp, 1, 0); +#ifdef DIO_TEST_DEV + will_return(__wrap_io_cancel, 0); +#endif + libcheck_free(&c); + do_libcheck_reset(1); +} + +/* test freeing checkers with outstanding requests */ +static void test_free_with_pending(void **state) +{ + struct checker c[2] = {0}; + struct aio_group *aio_grp; + struct async_req *req; + int res = 0; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c[0], 4096, &req); + do_libcheck_init(&c[1], 4096, NULL); + aio_grp = get_aio_grp(c); + return_io_getevents_none(); + do_check_state(&c[0], 0, 30, PATH_PENDING); + return_io_getevents_nr(NULL, 1, &req, &res); + return_io_getevents_none(); + do_check_state(&c[1], 0, 30, PATH_PENDING); + assert_true(is_checker_running(&c[0])); + assert_true(is_checker_running(&c[1])); + check_aio_grp(aio_grp, 2, 0); + libcheck_free(&c[0]); + check_aio_grp(aio_grp, 1, 0); + will_return(__wrap_io_cancel, 0); + libcheck_free(&c[1]); +#ifdef DIO_TEST_DEV + check_aio_grp(aio_grp, 1, 1); /* real cancel doesn't remove request */ +#else + check_aio_grp(aio_grp, 0, 0); +#endif + do_libcheck_reset(1); +} + +/* test removing orpahed aio_group on free */ +static void test_orphaned_aio_group(void **state) +{ + struct checker c[AIO_GROUP_SIZE] = {0}; + struct aio_group *aio_grp, *tmp_grp; + int i; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < AIO_GROUP_SIZE; i++) { + do_libcheck_init(&c[i], 4096, NULL); + return_io_getevents_none(); + do_check_state(&c[i], 0, 30, PATH_PENDING); + } + aio_grp = get_aio_grp(c); + check_aio_grp(aio_grp, AIO_GROUP_SIZE, 0); + i = 0; + list_for_each_entry(tmp_grp, &aio_grp_list, node) + i++; + assert_int_equal(i, 1); + for (i = 0; i < AIO_GROUP_SIZE; i++) { + assert_true(is_checker_running(&c[i])); + will_return(__wrap_io_cancel, -1); + if (i == AIO_GROUP_SIZE - 1) { + /* remove the orphaned group and create a new one */ + will_return(__wrap_io_destroy, 0); + } + libcheck_free(&c[i]); + } + do_libcheck_reset(0); +} + +/* test sync timeout with failed cancel and cleanup by another + * checker */ +static void test_timeout_cancel_failed(void **state) +{ + struct checker c[2] = {0}; + struct aio_group *aio_grp; + struct async_req *reqs[2]; + int res[] = {0,0}; + int i; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 2; i++) + do_libcheck_init(&c[i], 4096, &reqs[i]); + aio_grp = get_aio_grp(c); + return_io_getevents_none(); + will_return(__wrap_io_cancel, -1); + do_check_state(&c[0], 1, 30, PATH_DOWN); + assert_true(is_checker_running(&c[0])); + check_aio_grp(aio_grp, 2, 0); + return_io_getevents_none(); + will_return(__wrap_io_cancel, -1); + do_check_state(&c[0], 1, 30, PATH_DOWN); + assert_true(is_checker_running(&c[0])); + return_io_getevents_nr(NULL, 1, &reqs[0], &res[0]); + return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); + do_check_state(&c[1], 1, 30, PATH_UP); + do_check_state(&c[0], 1, 30, PATH_UP); + for (i = 0; i < 2; i++) { + assert_false(is_checker_running(&c[i])); + libcheck_free(&c[i]); + } + do_libcheck_reset(1); +} + +/* test async timeout with failed cancel and cleanup by another + * checker */ +static void test_async_timeout_cancel_failed(void **state) +{ + struct checker c[2] = {0}; + struct async_req *reqs[2]; + int res[] = {0,0}; + int i; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 2; i++) + do_libcheck_init(&c[i], 4096, &reqs[i]); + return_io_getevents_none(); + do_check_state(&c[0], 0, 2, PATH_PENDING); + return_io_getevents_none(); + do_check_state(&c[1], 0, 2, PATH_PENDING); + return_io_getevents_none(); + do_check_state(&c[0], 0, 2, PATH_PENDING); + return_io_getevents_none(); + do_check_state(&c[1], 0, 2, PATH_PENDING); + return_io_getevents_none(); + will_return(__wrap_io_cancel, -1); + do_check_state(&c[0], 0, 2, PATH_DOWN); +#ifndef DIO_TEST_DEV + /* can't pick which even gets returned on real devices */ + return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); + do_check_state(&c[1], 0, 2, PATH_UP); +#endif + return_io_getevents_none(); + will_return(__wrap_io_cancel, -1); + do_check_state(&c[0], 0, 2, PATH_DOWN); + assert_true(is_checker_running(&c[0])); + return_io_getevents_nr(NULL, 2, reqs, res); + do_check_state(&c[1], 0, 2, PATH_UP); + do_check_state(&c[0], 0, 2, PATH_UP); + for (i = 0; i < 2; i++) { + assert_false(is_checker_running(&c[i])); + libcheck_free(&c[i]); + } + do_libcheck_reset(1); +} + +/* 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 async_req *reqs[2]; + int res[] = {0,0}; + struct aio_group *aio_grp; + int i; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 2; i++) + do_libcheck_init(&c[i], 4096, &reqs[i]); + aio_grp = get_aio_grp(c); + return_io_getevents_none(); + do_check_state(&c[0], 0, 30, PATH_PENDING); + will_return(__wrap_io_cancel, -1); + check_aio_grp(aio_grp, 2, 0); + libcheck_free(&c[0]); + check_aio_grp(aio_grp, 2, 1); + return_io_getevents_nr(NULL, 2, reqs, res); + do_check_state(&c[1], 0, 2, PATH_UP); + check_aio_grp(aio_grp, 1, 0); + libcheck_free(&c[1]); + check_aio_grp(aio_grp, 0, 0); + do_libcheck_reset(1); +} + +/* test orphaning a request, and having reset clean it up */ +static void test_orphan_reset_cleanup(void **state) +{ + struct checker c; + struct aio_group *orphan_aio_grp, *tmp_aio_grp; + int found, count; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + do_libcheck_init(&c, 4096, NULL); + orphan_aio_grp = get_aio_grp(&c); + return_io_getevents_none(); + do_check_state(&c, 0, 30, PATH_PENDING); + will_return(__wrap_io_cancel, -1); + check_aio_grp(orphan_aio_grp, 1, 0); + libcheck_free(&c); + check_aio_grp(orphan_aio_grp, 1, 1); + found = count = 0; + list_for_each_entry(tmp_aio_grp, &aio_grp_list, node) { + count++; + if (tmp_aio_grp == orphan_aio_grp) + found = 1; + } + assert_int_equal(count, 1); + assert_int_equal(found, 1); + do_libcheck_reset(1); +} + +/* test checkers with different blocksizes */ +static void test_check_state_blksize(void **state) +{ + int i; + struct checker c[3] = {0}; + int blksize[] = {4096, 1024, 512}; + struct async_req *reqs[3]; + int res[] = {0,1,0}; +#ifdef DIO_TEST_DEV + /* can't pick event return state on real devices */ + int chk_state[] = {PATH_UP, PATH_UP, PATH_UP}; +#else + int chk_state[] = {PATH_UP, PATH_DOWN, PATH_UP}; +#endif + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 3; i++) + do_libcheck_init(&c[i], blksize[i], &reqs[i]); + for (i = 0; i < 3; i++) { + return_io_getevents_nr(NULL, 1, &reqs[i], &res[i]); + do_check_state(&c[i], 1, 30, chk_state[i]); + } + for (i = 0; i < 3; i++) { + assert_false(is_checker_running(&c[i])); + libcheck_free(&c[i]); + } + do_libcheck_reset(1); +} + +/* test async checkers pending and getting resovled by another checker + * as well as the loops for getting multiple events */ +static void test_check_state_async(void **state) +{ + int i; + struct checker c[257] = {0}; + struct async_req *reqs[257]; + int res[257] = {0}; + + assert_true(list_empty(&aio_grp_list)); + will_return(__wrap_io_setup, 0); + for (i = 0; i < 257; i++) + do_libcheck_init(&c[i], 4096, &reqs[i]); + for (i = 0; i < 256; i++) { + return_io_getevents_none(); + do_check_state(&c[i], 0, 30, PATH_PENDING); + assert_true(is_checker_running(&c[i])); + } + return_io_getevents_nr(&full_timeout, 256, reqs, res); + return_io_getevents_nr(NULL, 1, &reqs[256], &res[256]); + do_check_state(&c[256], 0, 30, PATH_UP); + assert_false(is_checker_running(&c[256])); + libcheck_free(&c[256]); + for (i = 0; i < 256; i++) { + do_check_state(&c[i], 0, 30, PATH_UP); + assert_false(is_checker_running(&c[i])); + libcheck_free(&c[i]); + } + do_libcheck_reset(1); +} + +static int setup(void **state) +{ +#ifdef DIO_TEST_DEV + test_fd = open(DIO_TEST_DEV, O_RDONLY); + if (test_fd < 0) + fail_msg("cannot open %s: %m", DIO_TEST_DEV); +#endif + return 0; +} + +static int teardown(void **state) +{ +#ifdef DIO_TEST_DEV + assert_true(test_fd > 0); + assert_int_equal(close(test_fd), 0); +#endif + return 0; +} + +int test_directio(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_reset), + cmocka_unit_test(test_init_reset_init), + cmocka_unit_test(test_init_free), + cmocka_unit_test(test_multi_init_free), + cmocka_unit_test(test_check_state_simple), + cmocka_unit_test(test_check_state_timeout), + cmocka_unit_test(test_check_state_async_timeout), + cmocka_unit_test(test_free_with_pending), + cmocka_unit_test(test_timeout_cancel_failed), + cmocka_unit_test(test_async_timeout_cancel_failed), + cmocka_unit_test(test_orphan_checker_cleanup), + cmocka_unit_test(test_orphan_reset_cleanup), + cmocka_unit_test(test_check_state_blksize), + cmocka_unit_test(test_check_state_async), + cmocka_unit_test(test_orphaned_aio_group), + }; + + return cmocka_run_group_tests(tests, setup, teardown); +} + +int main(void) +{ + int ret = 0; + + conf.verbosity = 2; + ret += test_directio(); + return ret; +} diff --git a/tests/hwtable.c b/tests/hwtable.c index 977a566..473028b 100644 --- a/tests/hwtable.c +++ b/tests/hwtable.c @@ -261,7 +261,7 @@ static void write_defaults(const struct hwt_state *hwt) defaults[0].value = hwt->dirname; defaults[1].value = buf; assert_ptr_not_equal(getcwd(dirbuf, sizeof(dirbuf)), NULL); - strncat(dirbuf, "/lib", sizeof(dirbuf)); + strncat(dirbuf, "/lib", sizeof(dirbuf) - 5); defaults[2].value = dirbuf; write_section(hwt->config_file, "defaults", ARRAY_SIZE(defaults), defaults); diff --git a/tests/test-log.c b/tests/test-log.c new file mode 100644 index 0000000..d685d58 --- /dev/null +++ b/tests/test-log.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include "log.h" +#include "test-log.h" + +__attribute__((format(printf, 3, 0))) +void __wrap_dlog (int sink, int prio, const char * fmt, ...) +{ + char buff[MAX_MSG_SIZE]; + va_list ap; + + assert_int_equal(prio, mock_type(int)); + va_start(ap, fmt); + vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); + va_end(ap); + assert_string_equal(buff, mock_ptr_type(char *)); +} + +void expect_condlog(int prio, char *string) +{ + will_return(__wrap_dlog, prio); + will_return(__wrap_dlog, string); +} + diff --git a/tests/test-log.h b/tests/test-log.h new file mode 100644 index 0000000..2c878c6 --- /dev/null +++ b/tests/test-log.h @@ -0,0 +1,7 @@ +#ifndef _TEST_LOG_H +#define _TEST_LOG_H + +void __wrap_dlog (int sink, int prio, const char * fmt, ...); +void expect_condlog(int prio, char *string); + +#endif diff --git a/tests/util.c b/tests/util.c index 4e04a48..7c486fc 100644 --- a/tests/util.c +++ b/tests/util.c @@ -328,11 +328,12 @@ static void test_strlcpy_5(void **state) { char *tst; int rc; + const int sz = sizeof(src_str); - tst = malloc(sizeof(src_str)); + tst = malloc(sz); memset(tst, 'f', sizeof(src_str)); - rc = strlcpy(tst, src_str, sizeof(src_str)); + rc = strlcpy(tst, src_str, sz); assert_int_equal(rc, strlen(src_str)); assert_string_equal(src_str, tst); @@ -344,15 +345,16 @@ static void test_strlcpy_6(void **state) { char *tst; int rc; + const int sz = sizeof(src_str); - tst = malloc(sizeof(src_str) + 2); - memset(tst, 'f', sizeof(src_str) + 2); + tst = malloc(sz + 2); + memset(tst, 'f', sz + 2); - rc = strlcpy(tst, src_str, sizeof(src_str) + 2); + rc = strlcpy(tst, src_str, sz + 2); assert_int_equal(rc, strlen(src_str)); assert_string_equal(src_str, tst); - assert_int_equal(tst[sizeof(src_str)], 'f'); - assert_int_equal(tst[sizeof(src_str) + 1], 'f'); + assert_int_equal(tst[sz], 'f'); + assert_int_equal(tst[sz + 1], 'f'); free(tst); } diff --git a/tests/vpd.c b/tests/vpd.c index d9f80ea..3cbad81 100644 --- a/tests/vpd.c +++ b/tests/vpd.c @@ -429,7 +429,7 @@ static void test_vpd_vnd_ ## len ## _ ## wlen(void **state) \ free(exp_wwid); \ will_return(__wrap_ioctl, n); \ will_return(__wrap_ioctl, vt->vpdbuf); \ - ret = get_vpd_sgio(10, 0x83, vt->wwid, wlen); \ + ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, wlen); \ assert_correct_wwid("test_vpd_vnd_" #len "_" #wlen, \ exp_len, ret, '1', 0, false, \ exp_subst, vt->wwid); \ @@ -459,7 +459,7 @@ static void test_vpd_str_ ## typ ## _ ## len ## _ ## wlen(void **state) \ exp_len = wlen - 1; \ will_return(__wrap_ioctl, n); \ will_return(__wrap_ioctl, vt->vpdbuf); \ - ret = get_vpd_sgio(10, 0x83, vt->wwid, wlen); \ + ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, wlen); \ assert_correct_wwid("test_vpd_str_" #typ "_" #len "_" #wlen, \ exp_len, ret, byte0[type], 0, \ type != STR_IQN, \ @@ -496,7 +496,7 @@ static void test_vpd_naa_ ## naa ## _ ## wlen(void **state) \ 3, naa, 0); \ will_return(__wrap_ioctl, n); \ will_return(__wrap_ioctl, vt->vpdbuf); \ - ret = get_vpd_sgio(10, 0x83, vt->wwid, wlen); \ + ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, wlen); \ assert_correct_wwid("test_vpd_naa_" #naa "_" #wlen, \ exp_len, ret, '3', '0' + naa, true, \ test_id, vt->wwid); \ @@ -506,9 +506,10 @@ static void test_vpd_naa_ ## naa ## _ ## wlen(void **state) \ * test_vpd_eui_LEN_WLEN() - test code for VPD 83, EUI64 * @LEN: EUI64 length (8, 12, or 16) * @WLEN: WWID buffer size + * @SML: Use small VPD page size */ -#define make_test_vpd_eui(len, wlen) \ -static void test_vpd_eui_ ## len ## _ ## wlen(void **state) \ +#define make_test_vpd_eui(len, wlen, sml) \ +static void test_vpd_eui_ ## len ## _ ## wlen ## _ ## sml(void **state) \ { \ struct vpdtest *vt = *state; \ int n, ret; \ @@ -518,10 +519,17 @@ static void test_vpd_eui_ ## len ## _ ## wlen(void **state) \ \ n = create_vpd83(vt->vpdbuf, sizeof(vt->vpdbuf), test_id, \ 2, 0, len); \ + if (sml) { \ + /* overwrite the page size to DEFAULT_SGIO_LEN + 1 */ \ + put_unaligned_be16(255, vt->vpdbuf + 2); \ + /* this causes get_vpd_sgio to do a second ioctl */ \ + will_return(__wrap_ioctl, n); \ + will_return(__wrap_ioctl, vt->vpdbuf); \ + } \ will_return(__wrap_ioctl, n); \ will_return(__wrap_ioctl, vt->vpdbuf); \ - ret = get_vpd_sgio(10, 0x83, vt->wwid, wlen); \ - assert_correct_wwid("test_vpd_eui_" #len "_" #wlen, \ + ret = get_vpd_sgio(10, 0x83, 0, vt->wwid, wlen); \ + assert_correct_wwid("test_vpd_eui_" #len "_" #wlen "_" #sml, \ exp_len, ret, '2', 0, true, \ test_id, vt->wwid); \ } @@ -547,7 +555,7 @@ static void test_vpd80_ ## size ## _ ## len ## _ ## wlen(void **state) \ size, len); \ will_return(__wrap_ioctl, n); \ will_return(__wrap_ioctl, vt->vpdbuf); \ - ret = get_vpd_sgio(10, 0x80, vt->wwid, wlen); \ + ret = get_vpd_sgio(10, 0x80, 0, vt->wwid, wlen); \ assert_correct_wwid("test_vpd80_" #size "_" #len "_" #wlen, \ exp_len, ret, 0, 0, false, \ input, vt->wwid); \ @@ -603,25 +611,30 @@ make_test_vpd_vnd(20, 10); make_test_vpd_vnd(10, 10); /* EUI64 tests */ +/* small vpd page test */ +make_test_vpd_eui(8, 32, 1); +make_test_vpd_eui(12, 32, 1); +make_test_vpd_eui(16, 40, 1); + /* 64bit, WWID size: 18 */ -make_test_vpd_eui(8, 32); -make_test_vpd_eui(8, 18); -make_test_vpd_eui(8, 17); -make_test_vpd_eui(8, 16); -make_test_vpd_eui(8, 10); +make_test_vpd_eui(8, 32, 0); +make_test_vpd_eui(8, 18, 0); +make_test_vpd_eui(8, 17, 0); +make_test_vpd_eui(8, 16, 0); +make_test_vpd_eui(8, 10, 0); /* 96 bit, WWID size: 26 */ -make_test_vpd_eui(12, 32); -make_test_vpd_eui(12, 26); -make_test_vpd_eui(12, 25); -make_test_vpd_eui(12, 20); -make_test_vpd_eui(12, 10); +make_test_vpd_eui(12, 32, 0); +make_test_vpd_eui(12, 26, 0); +make_test_vpd_eui(12, 25, 0); +make_test_vpd_eui(12, 20, 0); +make_test_vpd_eui(12, 10, 0); /* 128 bit, WWID size: 34 */ -make_test_vpd_eui(16, 40); -make_test_vpd_eui(16, 34); -make_test_vpd_eui(16, 33); -make_test_vpd_eui(16, 20); +make_test_vpd_eui(16, 40, 0); +make_test_vpd_eui(16, 34, 0); +make_test_vpd_eui(16, 33, 0); +make_test_vpd_eui(16, 20, 0); /* NAA IEEE registered extended (36), WWID size: 34 */ make_test_vpd_naa(6, 40); @@ -722,20 +735,23 @@ static int test_vpd(void) cmocka_unit_test(test_vpd_vnd_19_20), cmocka_unit_test(test_vpd_vnd_20_10), cmocka_unit_test(test_vpd_vnd_10_10), - cmocka_unit_test(test_vpd_eui_8_32), - cmocka_unit_test(test_vpd_eui_8_18), - cmocka_unit_test(test_vpd_eui_8_17), - cmocka_unit_test(test_vpd_eui_8_16), - cmocka_unit_test(test_vpd_eui_8_10), - cmocka_unit_test(test_vpd_eui_12_32), - cmocka_unit_test(test_vpd_eui_12_26), - cmocka_unit_test(test_vpd_eui_12_25), - cmocka_unit_test(test_vpd_eui_12_20), - cmocka_unit_test(test_vpd_eui_12_10), - cmocka_unit_test(test_vpd_eui_16_40), - cmocka_unit_test(test_vpd_eui_16_34), - cmocka_unit_test(test_vpd_eui_16_33), - cmocka_unit_test(test_vpd_eui_16_20), + cmocka_unit_test(test_vpd_eui_8_32_1), + cmocka_unit_test(test_vpd_eui_12_32_1), + cmocka_unit_test(test_vpd_eui_16_40_1), + cmocka_unit_test(test_vpd_eui_8_32_0), + cmocka_unit_test(test_vpd_eui_8_18_0), + cmocka_unit_test(test_vpd_eui_8_17_0), + cmocka_unit_test(test_vpd_eui_8_16_0), + cmocka_unit_test(test_vpd_eui_8_10_0), + cmocka_unit_test(test_vpd_eui_12_32_0), + cmocka_unit_test(test_vpd_eui_12_26_0), + cmocka_unit_test(test_vpd_eui_12_25_0), + cmocka_unit_test(test_vpd_eui_12_20_0), + cmocka_unit_test(test_vpd_eui_12_10_0), + cmocka_unit_test(test_vpd_eui_16_40_0), + cmocka_unit_test(test_vpd_eui_16_34_0), + cmocka_unit_test(test_vpd_eui_16_33_0), + cmocka_unit_test(test_vpd_eui_16_20_0), cmocka_unit_test(test_vpd_naa_6_40), cmocka_unit_test(test_vpd_naa_6_34), cmocka_unit_test(test_vpd_naa_6_33), -- 2.7.4