diff options
author | JinWang An <jinwang.an@samsung.com> | 2023-01-13 15:40:26 +0900 |
---|---|---|
committer | JinWang An <jinwang.an@samsung.com> | 2023-01-13 15:40:26 +0900 |
commit | 04a3d5d40c15f411ed71c6c7d28b5aa133bff4e8 (patch) | |
tree | 770dfdd842044ad918b6330061d6f4c2f0d31909 | |
parent | 8f0f489d4cfb845f4a5ef00f84d20ddcee0fb228 (diff) | |
download | multipath-tools-04a3d5d40c15f411ed71c6c7d28b5aa133bff4e8.tar.gz multipath-tools-04a3d5d40c15f411ed71c6c7d28b5aa133bff4e8.tar.bz2 multipath-tools-04a3d5d40c15f411ed71c6c7d28b5aa133bff4e8.zip |
Imported Upstream version 0.9.2upstream/0.9.2
36 files changed, 1515 insertions, 522 deletions
@@ -12,6 +12,8 @@ cscope.files cscope.out kpartx/kpartx multipath/multipath +multipath/multipath.rules +multipath/tmpfiles.conf multipathd/multipathd multipathd/multipathc mpathpersist/mpathpersist diff --git a/Makefile.inc b/Makefile.inc index 6399beb..5602506 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -83,9 +83,11 @@ exec_prefix = $(prefix) usr_prefix = $(prefix) bindir = $(exec_prefix)/sbin libudevdir = $(prefix)/$(SYSTEMDPATH)/udev +tmpfilesdir = $(prefix)/$(SYSTEMDPATH)/tmpfiles.d udevrulesdir = $(libudevdir)/rules.d modulesloaddir = $(prefix)/$(SYSTEMDPATH)/modules-load.d multipathdir = $(TOPDIR)/libmultipath +daemondir = $(TOPDIR)/multipathd mpathutildir = $(TOPDIR)/libmpathutil man8dir = $(prefix)/usr/share/man/man8 man5dir = $(prefix)/usr/share/man/man5 @@ -104,6 +106,7 @@ includedir = $(prefix)/usr/include pkgconfdir = $(usrlibdir)/pkgconfig plugindir := $(prefix)/$(LIB)/multipath configdir := $(prefix)/etc/multipath/conf.d +runtimedir := /$(RUN) GZIP_PROG = gzip -9 -c RM = rm -f @@ -148,6 +151,7 @@ WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implici $(WNOCLOBBERED) -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) CPPFLAGS := $(FORTIFY_OPT) \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(plugindir)\" -DRUN_DIR=\"${RUN}\" \ + -DRUNTIME_DIR=\"$(runtimedir)\" \ -DCONFIG_DIR=\"$(configdir)\" -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe BIN_CFLAGS = -fPIE -DPIE @@ -52,10 +52,17 @@ To get latest devel code: Building multipath-tools ======================== -Prerequisites: development packages of for `libdevmapper`, `libreadline`, -`libaio`, `libudev`, `libjson-c`, `liburcu`, and `libsystemd`. +Prerequisites: development packages of for `libdevmapper`, `libaio`, `libudev`, +`libjson-c`, `liburcu`, and `libsystemd`. -To build multipath-tools, type: +To enable commandline history and TAB completion in the interactive mode *(which +is entered with `multipathd -k` or `multipathc`)* you might also set `READLINE` +make variable to `libedit` or `libreadline`, like `make READLINE=libreadline`. +That requires a development package for the library you chose. Note that using +libreadline may [make binary indistributable due to license +incompatibility](https://github.com/opensvc/multipath-tools/issues/36). + +Then, build and install multipath-tools with: make make DESTDIR="/my/target/dir" install @@ -165,7 +172,7 @@ To enable ALUA, the following options should be changed: "LUN Affinity" and "ALUA" should be changed to "Enable", "Redundancy Type" must be "Active-Active". -- LSI/Engenio/NetApp RDAC class, as NetApp SANtricity E/EF Series and OEM arrays: +- LSI/Engenio/NetApp RDAC class, as NetApp SANtricity E/EF Series and rebranded arrays: "Select operating system:" should be changed to "Linux DM-MP (Kernel 3.10 or later)". - NetApp ONTAP: diff --git a/kpartx/kpartx.8 b/kpartx/kpartx.8 index 08bb349..2b144a7 100644 --- a/kpartx/kpartx.8 +++ b/kpartx/kpartx.8 @@ -5,7 +5,7 @@ .\" .\" ---------------------------------------------------------------------------- . -.TH KPARTX 8 2016-10-28 "Linux" +.TH KPARTX 8 2019-04-27 "Linux" . . .\" ---------------------------------------------------------------------------- diff --git a/kpartx/kpartx.c b/kpartx/kpartx.c index 3c49999..1d568c9 100644 --- a/kpartx/kpartx.c +++ b/kpartx/kpartx.c @@ -441,12 +441,7 @@ main(int argc, char **argv){ if (n >= 0) printf("%s: %d slices\n", ptp->type, n); #endif - - if (n > 0) { - close(fd); - fd = -1; - } - else + if (n <= 0) continue; switch(what) { @@ -666,9 +661,9 @@ main(int argc, char **argv){ if (n > 0) break; } + if (fd != -1) + close(fd); if (what == LIST && loopcreated) { - if (fd != -1) - close(fd); if (del_loop(device)) { if (verbose) fprintf(stderr, "can't del loop : %s\n", diff --git a/libmpathpersist/mpath_persistent_reserve_in.3 b/libmpathpersist/mpath_persistent_reserve_in.3 index 4691bde..c168cae 100644 --- a/libmpathpersist/mpath_persistent_reserve_in.3 +++ b/libmpathpersist/mpath_persistent_reserve_in.3 @@ -5,7 +5,7 @@ .\" .\" ---------------------------------------------------------------------------- . -.TH MPATH_PERSISTENT_RESERVE_IN 3 2016-11-01 "Linux" +.TH MPATH_PERSISTENT_RESERVE_IN 3 2018-06-15 "Linux" . . .\" ---------------------------------------------------------------------------- diff --git a/libmpathpersist/mpath_persistent_reserve_out.3 b/libmpathpersist/mpath_persistent_reserve_out.3 index 55b00b0..f20be31 100644 --- a/libmpathpersist/mpath_persistent_reserve_out.3 +++ b/libmpathpersist/mpath_persistent_reserve_out.3 @@ -5,7 +5,7 @@ .\" .\" ---------------------------------------------------------------------------- . -.TH MPATH_PERSISTENT_RESERVE_OUT 3 2016-11-01 "Linux" +.TH MPATH_PERSISTENT_RESERVE_OUT 3 2018-06-15 "Linux" . . .\" ---------------------------------------------------------------------------- diff --git a/libmpathutil/parser.c b/libmpathutil/parser.c index 014d9b8..8d3ac53 100644 --- a/libmpathutil/parser.c +++ b/libmpathutil/parser.c @@ -152,7 +152,7 @@ int snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw, const void *data) { - int r; + int r = 0; char *f; struct config *conf; STRBUF_ON_STACK(sbuf); @@ -190,8 +190,10 @@ snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw, } } while (*fmt++); out: - return __append_strbuf_str(buff, get_strbuf_str(&sbuf), - get_strbuf_len(&sbuf)); + if (r >= 0) + r = __append_strbuf_str(buff, get_strbuf_str(&sbuf), + get_strbuf_len(&sbuf)); + return r; } static const char quote_marker[] = { '\0', '"', '\0' }; diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 8af7cd7..e5249fc 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -218,10 +218,11 @@ int rr_optimize_path_order(struct pathgroup *pgp) total_paths = VECTOR_SIZE(pgp->paths); vector_foreach_slot(pgp->paths, pp, i) { - if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP && - pp->sg_id.proto_id != SCSI_PROTOCOL_SAS && - pp->sg_id.proto_id != SCSI_PROTOCOL_ISCSI && - pp->sg_id.proto_id != SCSI_PROTOCOL_SRP) { + if (pp->bus != SYSFS_BUS_SCSI || + (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP && + pp->sg_id.proto_id != SCSI_PROTOCOL_SAS && + pp->sg_id.proto_id != SCSI_PROTOCOL_ISCSI && + pp->sg_id.proto_id != SCSI_PROTOCOL_SRP)) { /* return success as default path order * is maintained in path group */ @@ -259,6 +260,7 @@ int rr_optimize_path_order(struct pathgroup *pgp) int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) { struct pathgroup * pgp; + struct path *pp; struct config *conf; int i, marginal_pathgroups; char *save_attr; @@ -274,6 +276,14 @@ int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) if (mpp->disable_queueing && VECTOR_SIZE(mpp->paths) != 0) mpp->disable_queueing = 0; + /* Force QUEUE_MODE_BIO for maps with nvme:tcp paths */ + vector_foreach_slot(mpp->paths, pp, i) { + if (pp->bus == SYSFS_BUS_NVME && + pp->sg_id.proto_id == NVME_PROTOCOL_TCP) { + mpp->queue_mode = QUEUE_MODE_BIO; + break; + } + } /* * If this map was created with add_map_without_path(), * mpp->hwe might not be set yet. @@ -1075,6 +1085,7 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, struct config *conf = NULL; int allow_queueing; struct bitfield *size_mismatch_seen; + struct multipath * cmpp; /* ignore refwwid if it's empty */ if (refwwid && !strlen(refwwid)) @@ -1146,6 +1157,13 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, continue; } + cmpp = find_mp_by_wwid(curmp, pp1->wwid); + if (cmpp && cmpp->queue_mode == QUEUE_MODE_RQ && + pp1->bus == SYSFS_BUS_NVME && pp1->sg_id.proto_id == + NVME_PROTOCOL_TCP) { + orphan_path(pp1, "nvme:tcp path not allowed with request queue_mode multipath device"); + continue; + } /* * at this point, we know we really got a new mp */ @@ -1184,6 +1202,8 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, } verify_paths(mpp); + if (cmpp) + mpp->queue_mode = cmpp->queue_mode; if (setup_map(mpp, ¶ms, vecs)) { remove_map(mpp, vecs->pathvec, NULL); continue; diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index 7979f20..3d552b3 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -68,7 +68,7 @@ #define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings" #define DEFAULT_WWIDS_FILE "/etc/multipath/wwids" #define DEFAULT_PRKEYS_FILE "/etc/multipath/prkeys" -#define MULTIPATH_SHM_BASE "/dev/shm/multipath/" +#define MULTIPATH_SHM_BASE RUNTIME_DIR "/multipath/" static inline char *set_default(char *str) diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index 15560f8..f3fcced 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -504,10 +504,11 @@ int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name) proto_id = pp->sg_id.proto_id; - if (proto_id != SCSI_PROTOCOL_FCP && - proto_id != SCSI_PROTOCOL_SAS && - proto_id != SCSI_PROTOCOL_ISCSI && - proto_id != SCSI_PROTOCOL_SRP) { + if (pp->bus != SYSFS_BUS_SCSI || + (proto_id != SCSI_PROTOCOL_FCP && + proto_id != SCSI_PROTOCOL_SAS && + proto_id != SCSI_PROTOCOL_ISCSI && + proto_id != SCSI_PROTOCOL_SRP)) { return 1; } /* iscsi doesn't have adapter info in sysfs @@ -1538,6 +1539,7 @@ nvme_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) struct udev_device *parent; const char *attr_path = NULL; const char *attr; + int i; if (pp->udev) attr_path = udev_device_get_sysname(pp->udev); @@ -1560,6 +1562,18 @@ nvme_sysfs_pathinfo (struct path *pp, const struct _vector *hwtable) attr = udev_device_get_sysattr_value(parent, "cntlid"); pp->sg_id.channel = attr ? atoi(attr) : 0; + attr = udev_device_get_sysattr_value(parent, "transport"); + if (attr) { + for (i = 0; i < NVME_PROTOCOL_UNSPEC; i++){ + if (protocol_name[SYSFS_BUS_NVME + i] && + !strcmp(attr, + protocol_name[SYSFS_BUS_NVME + i] + 5)) { + pp->sg_id.proto_id = i; + break; + } + } + } + snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME"); snprintf(pp->product_id, PATH_PRODUCT_SIZE, "%s", udev_device_get_sysattr_value(parent, "model")); @@ -1810,11 +1824,14 @@ sysfs_pathinfo(struct path *pp, const struct _vector *hwtable) pp->bus = SYSFS_BUS_CCISS; if (!strncmp(pp->dev,"dasd", 4)) pp->bus = SYSFS_BUS_CCW; - if (!strncmp(pp->dev,"sd", 2)) + if (!strncmp(pp->dev,"sd", 2)) { pp->bus = SYSFS_BUS_SCSI; - if (!strncmp(pp->dev,"nvme", 4)) + pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; + } + if (!strncmp(pp->dev,"nvme", 4)) { pp->bus = SYSFS_BUS_NVME; - + pp->sg_id.proto_id = NVME_PROTOCOL_UNSPEC; + } switch (pp->bus) { case SYSFS_BUS_SCSI: return scsi_sysfs_pathinfo(pp, hwtable); diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c index 50d13c0..3b37a92 100644 --- a/libmultipath/dmparser.c +++ b/libmultipath/dmparser.c @@ -151,6 +151,8 @@ int disassemble_map(const struct _vector *pathvec, free(word); } + mpp->queue_mode = strstr(mpp->features, "queue_mode bio") ? + QUEUE_MODE_BIO : QUEUE_MODE_RQ; /* * hwhandler diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 8a447f7..3d86ecb 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -31,7 +31,7 @@ * The new version inherits the previous ones. */ -LIBMULTIPATH_16.0.0 { +LIBMULTIPATH_17.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; diff --git a/libmultipath/print.c b/libmultipath/print.c index 68a793e..d7d522c 100644 --- a/libmultipath/print.c +++ b/libmultipath/print.c @@ -650,7 +650,8 @@ snprint_host_attr (struct strbuf *buff, const struct path * pp, char *attr) const char *value = NULL; int ret; - if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) + if (pp->bus != SYSFS_BUS_SCSI || + pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) return append_strbuf_str(buff, "[undef]"); sprintf(host_id, "host%d", pp->sg_id.host_no); host_dev = udev_device_new_from_subsystem_sysname(udev, "fc_host", @@ -689,7 +690,8 @@ snprint_tgt_wwpn (struct strbuf *buff, const struct path * pp) const char *value = NULL; int ret; - if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) + if (pp->bus != SYSFS_BUS_SCSI || + pp->sg_id.proto_id != SCSI_PROTOCOL_FCP) return append_strbuf_str(buff, "[undef]"); sprintf(rport_id, "rport-%d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 98e3aad..d4f1989 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -26,6 +26,7 @@ #include "strbuf.h" #include <inttypes.h> #include <libudev.h> +#include <ctype.h> pgpolicyfn *pgpolicies[] = { NULL, @@ -413,6 +414,59 @@ void reconcile_features_with_options(const char *id, char **features, int* no_pa } } +static void reconcile_features_with_queue_mode(struct multipath *mp) +{ + char *space = NULL, *val = NULL, *mode_str = NULL, *feat; + int features_mode = QUEUE_MODE_UNDEF; + + if (!mp->features) + return; + + pthread_cleanup_push(cleanup_free_ptr, &space); + pthread_cleanup_push(cleanup_free_ptr, &val); + pthread_cleanup_push(cleanup_free_ptr, &mode_str); + + if (!(feat = strstr(mp->features, "queue_mode")) || + feat == mp->features || !isspace(*(feat - 1)) || + sscanf(feat, "queue_mode%m[ \f\n\r\t\v]%ms", &space, &val) != 2) + goto sync_mode; + if (asprintf(&mode_str, "queue_mode%s%s", space, val) < 0) { + condlog(1, "failed to allocate space for queue_mode feature string"); + mode_str = NULL; /* value undefined on failure */ + goto exit; + } + + if (!strcmp(val, "rq") || !strcmp(val, "mq")) + features_mode = QUEUE_MODE_RQ; + else if (!strcmp(val, "bio")) + features_mode = QUEUE_MODE_BIO; + if (features_mode == QUEUE_MODE_UNDEF) { + condlog(2, "%s: ignoring invalid feature '%s'", + mp->alias, mode_str); + goto sync_mode; + } + + if (mp->queue_mode == QUEUE_MODE_UNDEF) + mp->queue_mode = features_mode; + if (mp->queue_mode == features_mode) + goto exit; + + condlog(2, + "%s: ignoring feature '%s' because queue_mode is set to '%s'", + mp->alias, mode_str, + (mp->queue_mode == QUEUE_MODE_RQ)? "rq" : "bio"); + +sync_mode: + if (mode_str) + remove_feature(&mp->features, mode_str); + if (mp->queue_mode == QUEUE_MODE_BIO) + add_feature(&mp->features, "queue_mode bio"); +exit: + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); +} + int select_features(struct config *conf, struct multipath *mp) { const char *origin; @@ -428,6 +482,7 @@ out: reconcile_features_with_options(mp->alias, &mp->features, &mp->no_path_retry, &mp->retain_hwhandler); + reconcile_features_with_queue_mode(mp); condlog(3, "%s: features = \"%s\" %s", mp->alias, mp->features, origin); return 0; } diff --git a/libmultipath/structs.c b/libmultipath/structs.c index 49621cb..7a2ff58 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -6,6 +6,7 @@ #include <unistd.h> #include <libdevmapper.h> #include <libudev.h> +#include <ctype.h> #include "checkers.h" #include "vector.h" @@ -24,7 +25,6 @@ const char * const protocol_name[LAST_BUS_PROTOCOL_ID + 1] = { [SYSFS_BUS_UNDEF] = "undef", [SYSFS_BUS_CCW] = "ccw", [SYSFS_BUS_CCISS] = "cciss", - [SYSFS_BUS_NVME] = "nvme", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_FCP] = "scsi:fcp", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SPI] = "scsi:spi", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_SSA] = "scsi:ssa", @@ -36,6 +36,13 @@ const char * const protocol_name[LAST_BUS_PROTOCOL_ID + 1] = { [SYSFS_BUS_SCSI + SCSI_PROTOCOL_ATA] = "scsi:ata", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_USB] = "scsi:usb", [SYSFS_BUS_SCSI + SCSI_PROTOCOL_UNSPEC] = "scsi:unspec", + [SYSFS_BUS_NVME + NVME_PROTOCOL_PCIE] = "nvme:pcie", + [SYSFS_BUS_NVME + NVME_PROTOCOL_RDMA] = "nvme:rdma", + [SYSFS_BUS_NVME + NVME_PROTOCOL_FC] = "nvme:fc", + [SYSFS_BUS_NVME + NVME_PROTOCOL_TCP] = "nvme:tcp", + [SYSFS_BUS_NVME + NVME_PROTOCOL_LOOP] = "nvme:loop", + [SYSFS_BUS_NVME + NVME_PROTOCOL_APPLE_NVME] = "nvme:apple-nvme", + [SYSFS_BUS_NVME + NVME_PROTOCOL_UNSPEC] = "nvme:unspec", }; struct adapter_group * @@ -115,7 +122,7 @@ alloc_path (void) pp->sg_id.channel = -1; pp->sg_id.scsi_id = -1; pp->sg_id.lun = SCSI_INVALID_LUN; - pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; + pp->sg_id.proto_id = PROTOCOL_UNSET; pp->fd = -1; pp->tpgs = TPGS_UNDEF; pp->priority = PRIO_UNDEF; @@ -601,23 +608,33 @@ int add_feature(char **f, const char *n) { int c = 0, d, l; char *e, *t; + const char *p; if (!f) return 1; /* Nothing to do */ - if (!n || *n == '0') + if (!n || *n == '\0') return 0; - if (strchr(n, ' ') != NULL) { - condlog(0, "internal error: feature \"%s\" contains spaces", n); + l = strlen(n); + if (isspace(*n) || isspace(*(n + l - 1))) { + condlog(0, "internal error: feature \"%s\" has leading or trailing spaces", n); return 1; } + p = n; + d = 1; + while (*p != '\0') { + if (isspace(*p) && !isspace(*(p + 1)) && *(p + 1) != '\0') + d++; + p++; + } + /* default feature is null */ if(!*f) { - l = asprintf(&t, "1 %s", n); + l = asprintf(&t, "%0d %s", d, n); if(l == -1) return 1; @@ -626,35 +643,24 @@ int add_feature(char **f, const char *n) } /* Check if feature is already present */ - if (strstr(*f, n)) - return 0; + e = *f; + while ((e = strstr(e, n)) != NULL) { + if (isspace(*(e - 1)) && + (isspace(*(e + l)) || *(e + l) == '\0')) + return 0; + e += l; + } /* Get feature count */ c = strtoul(*f, &e, 10); - if (*f == e || (*e != ' ' && *e != '\0')) { + if (*f == e || (!isspace(*e) && *e != '\0')) { condlog(0, "parse error in feature string \"%s\"", *f); return 1; } - - /* Add 1 digit and 1 space */ - l = strlen(e) + strlen(n) + 2; - - c++; - /* Check if we need more digits for feature count */ - for (d = c; d >= 10; d /= 10) - l++; - - t = calloc(1, l + 1); - if (!t) + c += d; + if (asprintf(&t, "%0d%s %s", c, e, n) < 0) return 1; - /* e: old feature string with leading space, or "" */ - if (*e == ' ') - while (*(e + 1) == ' ') - e++; - - snprintf(t, l + 1, "%0d%s %s", c, e, n); - free(*f); *f = t; @@ -663,7 +669,7 @@ int add_feature(char **f, const char *n) int remove_feature(char **f, const char *o) { - int c = 0, d, l; + int c = 0, d; char *e, *p, *n; const char *q; @@ -674,33 +680,35 @@ int remove_feature(char **f, const char *o) if (!o || *o == '\0') return 0; - /* Check if not present */ - if (!strstr(*f, o)) + d = strlen(o); + if (isspace(*o) || isspace(*(o + d - 1))) { + condlog(0, "internal error: feature \"%s\" has leading or trailing spaces", o); + return 1; + } + + /* Check if present and not part of a larger feature token*/ + p = *f + 1; /* the size must be at the start of the features string */ + while ((p = strstr(p, o)) != NULL) { + if (isspace(*(p - 1)) && + (isspace(*(p + d)) || *(p + d) == '\0')) + break; + p += d; + } + if (!p) return 0; /* Get feature count */ c = strtoul(*f, &e, 10); - if (*f == e) - /* parse error */ + if (*f == e || !isspace(*e)) { + condlog(0, "parse error in feature string \"%s\"", *f); return 1; - - /* Normalize features */ - while (*o == ' ') { - o++; } - /* Just spaces, return */ - if (*o == '\0') - return 0; - q = o + strlen(o); - while (*q == ' ') - q--; - d = (int)(q - o); /* Update feature count */ c--; q = o; - while (q[0] != '\0') { - if (q[0] == ' ' && q[1] != ' ' && q[1] != '\0') + while (*q != '\0') { + if (isspace(*q) && !isspace(*(q + 1)) && *(q + 1) != '\0') c--; q++; } @@ -714,15 +722,8 @@ int remove_feature(char **f, const char *o) goto out; } - /* Search feature to be removed */ - e = strstr(*f, o); - if (!e) - /* Not found, return */ - return 0; - /* Update feature count space */ - l = strlen(*f) - d; - n = malloc(l + 1); + n = malloc(strlen(*f) - d + 1); if (!n) return 1; @@ -732,36 +733,16 @@ int remove_feature(char **f, const char *o) * Copy existing features up to the feature * about to be removed */ - p = strchr(*f, ' '); - if (!p) { - /* Internal error, feature string inconsistent */ - free(n); - return 1; - } - while (*p == ' ') - p++; - p--; - if (e != p) { - do { - e--; - d++; - } while (*e == ' '); - e++; d--; - strncat(n, p, (size_t)(e - p)); - p += (size_t)(e - p); - } + strncat(n, e, (size_t)(p - e)); /* Skip feature to be removed */ p += d; - /* Copy remaining features */ - if (strlen(p)) { - while (*p == ' ') - p++; - if (strlen(p)) { - p--; - strcat(n, p); - } - } + while (isspace(*p)) + p++; + if (*p != '\0') + strcat(n, p); + else + strchop(n); out: free(*f); @@ -771,11 +752,17 @@ out: } unsigned int bus_protocol_id(const struct path *pp) { - if (!pp || pp->bus < 0 || pp->bus > SYSFS_BUS_SCSI) + if (!pp || pp->bus < 0 || pp->bus > SYSFS_BUS_NVME) return SYSFS_BUS_UNDEF; - if (pp->bus != SYSFS_BUS_SCSI) + if (pp->bus != SYSFS_BUS_SCSI && pp->bus != SYSFS_BUS_NVME) return pp->bus; - if ((int)pp->sg_id.proto_id < 0 || pp->sg_id.proto_id > SCSI_PROTOCOL_UNSPEC) + if (pp->sg_id.proto_id < 0) + return SYSFS_BUS_UNDEF; + if (pp->bus == SYSFS_BUS_SCSI && + pp->sg_id.proto_id > SCSI_PROTOCOL_UNSPEC) + return SYSFS_BUS_UNDEF; + if (pp->bus == SYSFS_BUS_NVME && + pp->sg_id.proto_id > NVME_PROTOCOL_UNSPEC) return SYSFS_BUS_UNDEF; - return SYSFS_BUS_SCSI + pp->sg_id.proto_id; + return pp->bus + pp->sg_id.proto_id; } diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 5a713d4..9e2c1ab 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -56,15 +56,6 @@ enum failback_mode { FAILBACK_FOLLOWOVER }; -/* SYSFS_BUS_SCSI should be last, see bus_protocol_id() */ -enum sysfs_buses { - SYSFS_BUS_UNDEF, - SYSFS_BUS_CCW, - SYSFS_BUS_CCISS, - SYSFS_BUS_NVME, - SYSFS_BUS_SCSI, -}; - enum pathstates { PSTATE_UNDEF, PSTATE_FAILED, @@ -170,6 +161,14 @@ enum max_sectors_kb_states { MAX_SECTORS_KB_MIN = 4, /* can't be smaller than page size */ }; +enum queue_mode_states { + QUEUE_MODE_UNDEF = 0, + QUEUE_MODE_BIO, + QUEUE_MODE_RQ, +}; + +#define PROTOCOL_UNSET -1 + enum scsi_protocol { SCSI_PROTOCOL_FCP = 0, /* Fibre Channel */ SCSI_PROTOCOL_SPI = 1, /* parallel SCSI */ @@ -182,14 +181,32 @@ enum scsi_protocol { SCSI_PROTOCOL_ATA = 8, SCSI_PROTOCOL_USB = 9, /* USB Attached SCSI (UAS), and others */ SCSI_PROTOCOL_UNSPEC = 0xa, /* No specific protocol */ + SCSI_PROTOCOL_END = 0xb, /* offset of the next sysfs_buses entry */ +}; + +/* values from /sys/class/nvme/nvmeX */ +enum nvme_protocol { + NVME_PROTOCOL_PCIE = 0, + NVME_PROTOCOL_RDMA = 1, + NVME_PROTOCOL_FC = 2, + NVME_PROTOCOL_TCP = 3, + NVME_PROTOCOL_LOOP = 4, + NVME_PROTOCOL_APPLE_NVME = 5, + NVME_PROTOCOL_UNSPEC = 6, /* unknown protocol */ +}; + +enum sysfs_buses { + SYSFS_BUS_UNDEF, + SYSFS_BUS_CCW, + SYSFS_BUS_CCISS, + SYSFS_BUS_SCSI, + SYSFS_BUS_NVME = SYSFS_BUS_SCSI + SCSI_PROTOCOL_END, }; /* * Linear ordering of bus/protocol - * This assumes that SYSFS_BUS_SCSI is last in enum sysfs_buses - * SCSI is the only bus type for which we distinguish protocols. */ -#define LAST_BUS_PROTOCOL_ID (SYSFS_BUS_SCSI + SCSI_PROTOCOL_UNSPEC) +#define LAST_BUS_PROTOCOL_ID (SYSFS_BUS_NVME + NVME_PROTOCOL_UNSPEC) unsigned int bus_protocol_id(const struct path *pp); extern const char * const protocol_name[]; @@ -285,7 +302,7 @@ struct sg_id { uint64_t lun; short h_cmd_per_lun; short d_queue_depth; - enum scsi_protocol proto_id; + int proto_id; int transport_id; }; @@ -396,6 +413,7 @@ struct multipath { int needs_paths_uevent; int ghost_delay; int ghost_delay_tick; + int queue_mode; uid_t uid; gid_t gid; mode_t mode; diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c index 645896c..5a61876 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -264,6 +264,13 @@ int adopt_paths(vector pathvec, struct multipath *mpp) } if (pp->initialized == INIT_REMOVED) continue; + if (mpp->queue_mode == QUEUE_MODE_RQ && + pp->bus == SYSFS_BUS_NVME && + pp->sg_id.proto_id == NVME_PROTOCOL_TCP) { + condlog(2, "%s: mulitpath device %s created with request queue_mode. Unable to add nvme:tcp paths", + pp->dev, mpp->alias); + continue; + } if (!mpp->paths && !(mpp->paths = vector_alloc())) goto err; diff --git a/libmultipath/version.h b/libmultipath/version.h index fcc36d7..b5df67b 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,9 +20,9 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION_CODE 0x000901 +#define VERSION_CODE 0x000902 /* MMDDYY, in hex */ -#define DATE_CODE 0x090716 +#define DATE_CODE 0x0A1816 #define PROG "multipath-tools" diff --git a/mpathpersist/main.c b/mpathpersist/main.c index 894e8c9..b661790 100644 --- a/mpathpersist/main.c +++ b/mpathpersist/main.c @@ -178,7 +178,6 @@ static int handle_args(int argc, char * argv[], int nline) const char *device_name = NULL; int num_prin_sa = 0; int num_prout_sa = 0; - int num_prout_param = 0; int prin_flag = 0; int prout_flag = 0; int ret = 0; @@ -263,11 +262,9 @@ static int handle_args(int argc, char * argv[], int nline) case 'Y': param_alltgpt = 1; - ++num_prout_param; break; case 'Z': param_aptpl = 1; - ++num_prout_param; break; case 'K': if (1 != sscanf (optarg, "%" SCNx64 "", ¶m_rk)) @@ -276,7 +273,6 @@ static int handle_args(int argc, char * argv[], int nline) ret = MPATH_PR_SYNTAX_ERROR; goto out; } - ++num_prout_param; break; case 'S': @@ -286,7 +282,6 @@ static int handle_args(int argc, char * argv[], int nline) ret = MPATH_PR_SYNTAX_ERROR; goto out; } - ++num_prout_param; break; case 'P': @@ -306,7 +301,6 @@ static int handle_args(int argc, char * argv[], int nline) ret = MPATH_PR_SYNTAX_ERROR; goto out; } - ++num_prout_param; break; case 's': diff --git a/mpathpersist/mpathpersist.8 b/mpathpersist/mpathpersist.8 index 7b57459..d594422 100644 --- a/mpathpersist/mpathpersist.8 +++ b/mpathpersist/mpathpersist.8 @@ -5,7 +5,7 @@ .\" .\" ---------------------------------------------------------------------------- . -.TH MPATHPERSIST 8 2019-05-27 "Linux" +.TH MPATHPERSIST 8 2021-11-12 "Linux" . . .\" ---------------------------------------------------------------------------- diff --git a/multipath/Makefile b/multipath/Makefile index 46b7553..116348e 100644 --- a/multipath/Makefile +++ b/multipath/Makefile @@ -13,7 +13,7 @@ EXEC = multipath OBJS = main.o -all: $(EXEC) +all: $(EXEC) multipath.rules tmpfiles.conf $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so $(CC) $(CFLAGS) $(OBJS) -o $(EXEC) $(LDFLAGS) $(LIBDEPS) @@ -23,9 +23,11 @@ install: $(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir)/ $(INSTALL_PROGRAM) -d $(DESTDIR)$(udevrulesdir) $(INSTALL_PROGRAM) -m 644 11-dm-mpath.rules $(DESTDIR)$(udevrulesdir) - $(INSTALL_PROGRAM) -m 644 $(EXEC).rules $(DESTDIR)$(udevrulesdir)/56-multipath.rules + $(INSTALL_PROGRAM) -m 644 multipath.rules $(DESTDIR)$(udevrulesdir)/56-multipath.rules $(INSTALL_PROGRAM) -d $(DESTDIR)$(modulesloaddir) $(INSTALL_PROGRAM) -m 644 modules-load.conf $(DESTDIR)$(modulesloaddir)/multipath.conf + $(INSTALL_PROGRAM) -d $(DESTDIR)$(tmpfilesdir) + $(INSTALL_PROGRAM) -m 644 tmpfiles.conf $(DESTDIR)$(tmpfilesdir)/multipath.conf $(INSTALL_PROGRAM) -d $(DESTDIR)$(man8dir) $(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(man8dir) $(INSTALL_PROGRAM) -d $(DESTDIR)$(man5dir) @@ -46,9 +48,12 @@ uninstall: $(RM) $(DESTDIR)$(man5dir)/$(EXEC).conf.5 clean: dep_clean - $(RM) core *.o $(EXEC) + $(RM) core *.o $(EXEC) multipath.rules tmpfiles.conf include $(wildcard $(OBJS:.o=.d)) dep_clean: $(RM) $(OBJS:.o=.d) + +%: %.in + sed 's,@RUNTIME_DIR@,$(runtimedir),' $< >$@ diff --git a/multipath/multipath.8 b/multipath/multipath.8 index 4c7e988..88149d5 100644 --- a/multipath/multipath.8 +++ b/multipath/multipath.8 @@ -5,7 +5,7 @@ .\" .\" ---------------------------------------------------------------------------- . -.TH MULTIPATH 8 2018-10-10 "Linux" +.TH MULTIPATH 8 2021-11-12 "Linux" . . .\" ---------------------------------------------------------------------------- diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 index acdd1ae..1fea9d5 100644 --- a/multipath/multipath.conf.5 +++ b/multipath/multipath.conf.5 @@ -6,7 +6,7 @@ .\" Update the date below if you make any significant change. .\" ---------------------------------------------------------------------------- . -.TH MULTIPATH.CONF 5 2021-09-08 Linux +.TH MULTIPATH.CONF 5 2022-09-09 Linux . . .\" ---------------------------------------------------------------------------- @@ -320,7 +320,7 @@ Generate the path priority for NetApp ONTAP class, and rebranded arrays. .I rdac (Hardware-dependent) Generate the path priority for LSI/Engenio/NetApp RDAC class as NetApp SANtricity -E/EF Series, and rebranded arrays. +E/EF Series and rebranded arrays, with "Linux DM-MP (Kernel 3.9 or earlier)" option. .TP .I hp_sw (Hardware-dependent) @@ -459,8 +459,13 @@ precedence. See KNOWN ISSUES. <mode> can be \fIbio\fR, \fIrq\fR or \fImq\fR, which corresponds to bio-based, request-based, and block-multiqueue (blk-mq) request-based, respectively. -The default depends on the kernel parameter \fBdm_mod.use_blk_mq\fR. It is -\fImq\fR if the latter is set, and \fIrq\fR otherwise. +Before kernel 4.20 The default depends on the kernel parameter +\fBdm_mod.use_blk_mq\fR. It is \fImq\fR if the latter is set, and \fIrq\fR +otherwise. Since kernel 4.20, \fIrq\fR and \fImq\fR both correspond to +block-multiqueue. Once a multipath device has been created, its queue_mode +cannot be changed. \fInvme:tcp\fR paths are only supported in multipath +devices with queue_mode set to \fIbio\fR. multipath will automatically +set this when creating a device with \fInvme:tcp\fR paths. .TP The default is: \fB<unset>\fR .RE @@ -1374,7 +1379,9 @@ Regular expression for the protocol of a device to be excluded/included. The protocol strings that multipath recognizes are \fIscsi:fcp\fR, \fIscsi:spi\fR, \fIscsi:ssa\fR, \fIscsi:sbp\fR, \fIscsi:srp\fR, \fIscsi:iscsi\fR, \fIscsi:sas\fR, \fIscsi:adt\fR, \fIscsi:ata\fR, -\fIscsi:unspec\fR, \fIccw\fR, \fIcciss\fR, \fInvme\fR, and \fIundef\fR. +\fIscsi:unspec\fR, \fInvme:pcie\fR, \fInvme:rdma\fR, \fInvme:fc\fR, +\fInvme:tcp\fR, \fInvme:loop\fR, \fInvme:apple-nvme\fR, \fInvme:unspec\fR, +\fIccw\fR, \fIcciss\fR, and \fIundef\fR. The protocol that a path is using can be viewed by running \fBmultipathd show paths format "%d %P"\fR .RE @@ -1568,7 +1575,7 @@ with Failover Mode 1 (Passive Not Ready(PNR)). .I 1 rdac (Hardware-dependent) Hardware handler for LSI/Engenio/NetApp RDAC class as NetApp SANtricity E/EF -Series, and rebranded arrays. +Series and rebranded arrays, with "Linux DM-MP (Kernel 3.9 or earlier)" option. .TP .I 1 hp_sw (Hardware-dependent) @@ -1770,7 +1777,9 @@ The protocol subsection recognizes the following mandatory attribute: The protocol string of the path device. The possible values are \fIscsi:fcp\fR, \fIscsi:spi\fR, \fIscsi:ssa\fR, \fIscsi:sbp\fR, \fIscsi:srp\fR, \fIscsi:iscsi\fR, \fIscsi:sas\fR, \fIscsi:adt\fR, \fIscsi:ata\fR, -\fIscsi:unspec\fR, \fIccw\fR, \fIcciss\fR, \fInvme\fR, and \fIundef\fR. This is +\fIscsi:unspec\fR, \fInvme:pcie\fR, \fInvme:rdma\fR, \fInvme:fc\fR, +\fInvme:tcp\fR, \fInvme:loop\fR, \fInvme:apple-nvme\fR, \fInvme:unspec\fR, +\fIccw\fR, \fIcciss\fR, and \fIundef\fR. This is \fBnot\fR a regular expression. the path device protocol string must match exactly. The protocol that a path is using can be viewed by running \fBmultipathd show paths format "%d %P"\fR diff --git a/multipath/multipath.rules b/multipath/multipath.rules.in index f993d99..8d3cf33 100644 --- a/multipath/multipath.rules +++ b/multipath/multipath.rules.in @@ -1,8 +1,8 @@ # Set DM_MULTIPATH_DEVICE_PATH if the device should be handled by multipath SUBSYSTEM!="block", GOTO="end_mpath" KERNEL!="sd*|dasd*|nvme*", GOTO="end_mpath" -ACTION=="remove", TEST=="/dev/shm/multipath/find_multipaths/$major:$minor", \ - RUN+="/usr/bin/rm -f /dev/shm/multipath/find_multipaths/$major:$minor" +ACTION=="remove", TEST=="@RUNTIME_DIR@/multipath/find_multipaths/$major:$minor", \ + RUN+="/usr/bin/rm -f @RUNTIME_DIR@/multipath/find_multipaths/$major:$minor" ACTION!="add|change", GOTO="end_mpath" IMPORT{cmdline}="nompath" diff --git a/multipath/tmpfiles.conf.in b/multipath/tmpfiles.conf.in new file mode 100644 index 0000000..21be438 --- /dev/null +++ b/multipath/tmpfiles.conf.in @@ -0,0 +1 @@ +d @RUNTIME_DIR@/multipath 0700 root root - diff --git a/multipathd/callbacks.c b/multipathd/callbacks.c index 0bd76b7..fb87b28 100644 --- a/multipathd/callbacks.c +++ b/multipathd/callbacks.c @@ -1,60 +1,72 @@ void init_handler_callbacks(void) { - set_handler_callback(LIST+PATHS, HANDLER(cli_list_paths)); - set_handler_callback(LIST+PATHS+FMT, HANDLER(cli_list_paths_fmt)); - set_handler_callback(LIST+PATHS+RAW+FMT, HANDLER(cli_list_paths_raw)); - set_handler_callback(LIST+PATH, HANDLER(cli_list_path)); - set_handler_callback(LIST+MAPS, HANDLER(cli_list_maps)); - set_handler_callback(LIST+STATUS, HANDLER(cli_list_status)); - set_unlocked_handler_callback(LIST+DAEMON, HANDLER(cli_list_daemon)); - set_handler_callback(LIST+MAPS+STATUS, HANDLER(cli_list_maps_status)); - set_handler_callback(LIST+MAPS+STATS, HANDLER(cli_list_maps_stats)); - set_handler_callback(LIST+MAPS+FMT, HANDLER(cli_list_maps_fmt)); - set_handler_callback(LIST+MAPS+RAW+FMT, HANDLER(cli_list_maps_raw)); - set_handler_callback(LIST+MAPS+TOPOLOGY, HANDLER(cli_list_maps_topology)); - set_handler_callback(LIST+TOPOLOGY, HANDLER(cli_list_maps_topology)); - set_handler_callback(LIST+MAPS+JSON, HANDLER(cli_list_maps_json)); - set_handler_callback(LIST+MAP+TOPOLOGY, HANDLER(cli_list_map_topology)); - set_handler_callback(LIST+MAP+FMT, HANDLER(cli_list_map_fmt)); - set_handler_callback(LIST+MAP+RAW+FMT, HANDLER(cli_list_map_fmt)); - set_handler_callback(LIST+MAP+JSON, HANDLER(cli_list_map_json)); - set_handler_callback(LIST+CONFIG+LOCAL, HANDLER(cli_list_config_local)); - set_handler_callback(LIST+CONFIG, HANDLER(cli_list_config)); - set_handler_callback(LIST+BLACKLIST, HANDLER(cli_list_blacklist)); - set_handler_callback(LIST+DEVICES, HANDLER(cli_list_devices)); - set_handler_callback(LIST+WILDCARDS, HANDLER(cli_list_wildcards)); - set_handler_callback(RESET+MAPS+STATS, HANDLER(cli_reset_maps_stats)); - set_handler_callback(RESET+MAP+STATS, HANDLER(cli_reset_map_stats)); - set_handler_callback(ADD+PATH, HANDLER(cli_add_path)); - set_handler_callback(DEL+PATH, HANDLER(cli_del_path)); - set_handler_callback(ADD+MAP, HANDLER(cli_add_map)); - set_handler_callback(DEL+MAP, HANDLER(cli_del_map)); - set_handler_callback(DEL+MAPS, HANDLER(cli_del_maps)); - set_handler_callback(SWITCH+MAP+GROUP, HANDLER(cli_switch_group)); - set_unlocked_handler_callback(RECONFIGURE, HANDLER(cli_reconfigure)); - set_unlocked_handler_callback(RECONFIGURE+ALL, HANDLER(cli_reconfigure_all)); - set_handler_callback(SUSPEND+MAP, HANDLER(cli_suspend)); - set_handler_callback(RESUME+MAP, HANDLER(cli_resume)); - set_handler_callback(RESIZE+MAP, HANDLER(cli_resize)); - set_handler_callback(RELOAD+MAP, HANDLER(cli_reload)); - set_handler_callback(RESET+MAP, HANDLER(cli_reassign)); - set_handler_callback(REINSTATE+PATH, HANDLER(cli_reinstate)); - set_handler_callback(FAIL+PATH, HANDLER(cli_fail)); - set_handler_callback(DISABLEQ+MAP, HANDLER(cli_disable_queueing)); - set_handler_callback(RESTOREQ+MAP, HANDLER(cli_restore_queueing)); - set_handler_callback(DISABLEQ+MAPS, HANDLER(cli_disable_all_queueing)); - set_handler_callback(RESTOREQ+MAPS, HANDLER(cli_restore_all_queueing)); - set_unlocked_handler_callback(QUIT, HANDLER(cli_quit)); - set_unlocked_handler_callback(SHUTDOWN, HANDLER(cli_shutdown)); - set_handler_callback(GETPRSTATUS+MAP, HANDLER(cli_getprstatus)); - set_handler_callback(SETPRSTATUS+MAP, HANDLER(cli_setprstatus)); - set_handler_callback(UNSETPRSTATUS+MAP, HANDLER(cli_unsetprstatus)); - set_handler_callback(FORCEQ+DAEMON, HANDLER(cli_force_no_daemon_q)); - set_handler_callback(RESTOREQ+DAEMON, HANDLER(cli_restore_no_daemon_q)); - set_handler_callback(GETPRKEY+MAP, HANDLER(cli_getprkey)); - set_handler_callback(SETPRKEY+MAP+KEY, HANDLER(cli_setprkey)); - set_handler_callback(UNSETPRKEY+MAP, HANDLER(cli_unsetprkey)); - set_handler_callback(SETMARGINAL+PATH, HANDLER(cli_set_marginal)); - set_handler_callback(UNSETMARGINAL+PATH, HANDLER(cli_unset_marginal)); - set_handler_callback(UNSETMARGINAL+MAP, HANDLER(cli_unset_all_marginal)); + set_handler_callback(VRB_LIST | Q1_PATHS, HANDLER(cli_list_paths)); + set_handler_callback(VRB_LIST | Q1_PATHS | Q2_FMT, HANDLER(cli_list_paths_fmt)); + set_handler_callback(VRB_LIST | Q1_PATHS | Q2_RAW | Q3_FMT, + HANDLER(cli_list_paths_raw)); + set_handler_callback(VRB_LIST | Q1_PATH, HANDLER(cli_list_path)); + set_handler_callback(VRB_LIST | Q1_MAPS, HANDLER(cli_list_maps)); + set_handler_callback(VRB_LIST | Q1_STATUS, HANDLER(cli_list_status)); + set_unlocked_handler_callback(VRB_LIST | Q1_DAEMON, HANDLER(cli_list_daemon)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_STATUS, + HANDLER(cli_list_maps_status)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_STATS, + HANDLER(cli_list_maps_stats)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_FMT, HANDLER(cli_list_maps_fmt)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_RAW | Q3_FMT, + HANDLER(cli_list_maps_raw)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_TOPOLOGY, + HANDLER(cli_list_maps_topology)); + set_handler_callback(VRB_LIST | Q1_TOPOLOGY, HANDLER(cli_list_maps_topology)); + set_handler_callback(VRB_LIST | Q1_MAPS | Q2_JSON, HANDLER(cli_list_maps_json)); + set_handler_callback(VRB_LIST | Q1_MAP | Q2_TOPOLOGY, + HANDLER(cli_list_map_topology)); + set_handler_callback(VRB_LIST | Q1_MAP | Q2_FMT, HANDLER(cli_list_map_fmt)); + set_handler_callback(VRB_LIST | Q1_MAP | Q2_RAW | Q3_FMT, + HANDLER(cli_list_map_fmt)); + set_handler_callback(VRB_LIST | Q1_MAP | Q2_JSON, HANDLER(cli_list_map_json)); + set_handler_callback(VRB_LIST | Q1_CONFIG | Q2_LOCAL, + HANDLER(cli_list_config_local)); + set_handler_callback(VRB_LIST | Q1_CONFIG, HANDLER(cli_list_config)); + set_handler_callback(VRB_LIST | Q1_BLACKLIST, HANDLER(cli_list_blacklist)); + set_handler_callback(VRB_LIST | Q1_DEVICES, HANDLER(cli_list_devices)); + set_handler_callback(VRB_LIST | Q1_WILDCARDS, HANDLER(cli_list_wildcards)); + set_handler_callback(VRB_RESET | Q1_MAPS | Q2_STATS, + HANDLER(cli_reset_maps_stats)); + set_handler_callback(VRB_RESET | Q1_MAP | Q2_STATS, + HANDLER(cli_reset_map_stats)); + set_handler_callback(VRB_ADD | Q1_PATH, HANDLER(cli_add_path)); + set_handler_callback(VRB_DEL | Q1_PATH, HANDLER(cli_del_path)); + set_handler_callback(VRB_ADD | Q1_MAP, HANDLER(cli_add_map)); + set_handler_callback(VRB_DEL | Q1_MAP, HANDLER(cli_del_map)); + set_handler_callback(VRB_DEL | Q1_MAPS, HANDLER(cli_del_maps)); + set_handler_callback(VRB_SWITCH | Q1_MAP | Q2_GROUP, HANDLER(cli_switch_group)); + set_unlocked_handler_callback(VRB_RECONFIGURE, HANDLER(cli_reconfigure)); + set_unlocked_handler_callback(VRB_RECONFIGURE | Q1_ALL, + HANDLER(cli_reconfigure_all)); + set_handler_callback(VRB_SUSPEND | Q1_MAP, HANDLER(cli_suspend)); + set_handler_callback(VRB_RESUME | Q1_MAP, HANDLER(cli_resume)); + set_handler_callback(VRB_RESIZE | Q1_MAP, HANDLER(cli_resize)); + set_handler_callback(VRB_RELOAD | Q1_MAP, HANDLER(cli_reload)); + set_handler_callback(VRB_RESET | Q1_MAP, HANDLER(cli_reassign)); + set_handler_callback(VRB_REINSTATE | Q1_PATH, HANDLER(cli_reinstate)); + set_handler_callback(VRB_FAIL | Q1_PATH, HANDLER(cli_fail)); + set_handler_callback(VRB_DISABLEQ | Q1_MAP, HANDLER(cli_disable_queueing)); + set_handler_callback(VRB_RESTOREQ | Q1_MAP, HANDLER(cli_restore_queueing)); + set_handler_callback(VRB_DISABLEQ | Q1_MAPS, HANDLER(cli_disable_all_queueing)); + set_handler_callback(VRB_RESTOREQ | Q1_MAPS, HANDLER(cli_restore_all_queueing)); + set_unlocked_handler_callback(VRB_QUIT, HANDLER(cli_quit)); + set_unlocked_handler_callback(VRB_SHUTDOWN, HANDLER(cli_shutdown)); + set_handler_callback(VRB_GETPRSTATUS | Q1_MAP, HANDLER(cli_getprstatus)); + set_handler_callback(VRB_SETPRSTATUS | Q1_MAP, HANDLER(cli_setprstatus)); + set_handler_callback(VRB_UNSETPRSTATUS | Q1_MAP, HANDLER(cli_unsetprstatus)); + set_handler_callback(VRB_FORCEQ | Q1_DAEMON, HANDLER(cli_force_no_daemon_q)); + set_handler_callback(VRB_RESTOREQ | Q1_DAEMON, HANDLER(cli_restore_no_daemon_q)); + set_handler_callback(VRB_GETPRKEY | Q1_MAP, HANDLER(cli_getprkey)); + set_handler_callback(VRB_SETPRKEY | Q1_MAP | Q2_KEY, HANDLER(cli_setprkey)); + set_handler_callback(VRB_UNSETPRKEY | Q1_MAP, HANDLER(cli_unsetprkey)); + set_handler_callback(VRB_SETMARGINAL | Q1_PATH, HANDLER(cli_set_marginal)); + set_handler_callback(VRB_UNSETMARGINAL | Q1_PATH, HANDLER(cli_unset_marginal)); + set_handler_callback(VRB_UNSETMARGINAL | Q1_MAP, + HANDLER(cli_unset_all_marginal)); } diff --git a/multipathd/cli.c b/multipathd/cli.c index 5d25ddb..0c89b7c 100644 --- a/multipathd/cli.c +++ b/multipathd/cli.c @@ -30,6 +30,8 @@ vector get_handlers(void) { return handlers; } +/* See KEY_INVALID in cli.h */ +#define INVALID_FINGERPRINT ((uint32_t)(0)) static struct key * alloc_key (void) @@ -44,7 +46,7 @@ alloc_handler (void) } static int -add_key (vector vec, char * str, uint64_t code, int has_param) +add_key (vector vec, char * str, uint8_t code, int has_param) { struct key * kw; @@ -74,7 +76,7 @@ out: return 1; } -static struct handler *add_handler(uint64_t fp, cli_handler *fn, bool locked) +static struct handler *add_handler(uint32_t fp, cli_handler *fn, bool locked) { struct handler * h; @@ -97,11 +99,13 @@ static struct handler *add_handler(uint64_t fp, cli_handler *fn, bool locked) } static struct handler * -find_handler (uint64_t fp) +find_handler (uint32_t fp) { int i; struct handler *h; + if (fp == INVALID_FINGERPRINT) + return NULL; vector_foreach_slot (handlers, h, i) if (h->fingerprint == fp) return h; @@ -110,22 +114,22 @@ find_handler (uint64_t fp) } int -__set_handler_callback (uint64_t fp, cli_handler *fn, bool locked) +__set_handler_callback (uint32_t fp, cli_handler *fn, bool locked) { struct handler *h; + assert(fp != INVALID_FINGERPRINT); assert(find_handler(fp) == NULL); h = add_handler(fp, fn, locked); if (!h) { - condlog(0, "%s: failed to set handler for code %"PRIu64, + condlog(0, "%s: failed to set handler for code %"PRIu32, __func__, fp); return 1; } return 0; } -static void -free_key (struct key * kw) +void free_key (struct key * kw) { if (kw->str) free(kw->str); @@ -170,56 +174,56 @@ load_keys (void) if (!keys) return 1; - r += add_key(keys, "list", LIST, 0); - r += add_key(keys, "show", LIST, 0); - r += add_key(keys, "add", ADD, 0); - r += add_key(keys, "remove", DEL, 0); - r += add_key(keys, "del", DEL, 0); - r += add_key(keys, "switch", SWITCH, 0); - r += add_key(keys, "switchgroup", SWITCH, 0); - r += add_key(keys, "suspend", SUSPEND, 0); - r += add_key(keys, "resume", RESUME, 0); - r += add_key(keys, "reinstate", REINSTATE, 0); - r += add_key(keys, "fail", FAIL, 0); - r += add_key(keys, "resize", RESIZE, 0); - r += add_key(keys, "reset", RESET, 0); - r += add_key(keys, "reload", RELOAD, 0); - r += add_key(keys, "forcequeueing", FORCEQ, 0); - r += add_key(keys, "disablequeueing", DISABLEQ, 0); - r += add_key(keys, "restorequeueing", RESTOREQ, 0); - r += add_key(keys, "paths", PATHS, 0); - r += add_key(keys, "maps", MAPS, 0); - r += add_key(keys, "multipaths", MAPS, 0); - r += add_key(keys, "path", PATH, 1); - r += add_key(keys, "map", MAP, 1); - r += add_key(keys, "multipath", MAP, 1); - r += add_key(keys, "group", GROUP, 1); - r += add_key(keys, "reconfigure", RECONFIGURE, 0); - r += add_key(keys, "daemon", DAEMON, 0); - r += add_key(keys, "status", STATUS, 0); - r += add_key(keys, "stats", STATS, 0); - r += add_key(keys, "topology", TOPOLOGY, 0); - r += add_key(keys, "config", CONFIG, 0); - r += add_key(keys, "blacklist", BLACKLIST, 0); - r += add_key(keys, "devices", DEVICES, 0); - r += add_key(keys, "raw", RAW, 0); - r += add_key(keys, "wildcards", WILDCARDS, 0); - r += add_key(keys, "quit", QUIT, 0); - r += add_key(keys, "exit", QUIT, 0); - r += add_key(keys, "shutdown", SHUTDOWN, 0); - r += add_key(keys, "getprstatus", GETPRSTATUS, 0); - r += add_key(keys, "setprstatus", SETPRSTATUS, 0); - r += add_key(keys, "unsetprstatus", UNSETPRSTATUS, 0); - r += add_key(keys, "format", FMT, 1); - r += add_key(keys, "json", JSON, 0); - r += add_key(keys, "getprkey", GETPRKEY, 0); - r += add_key(keys, "setprkey", SETPRKEY, 0); - r += add_key(keys, "unsetprkey", UNSETPRKEY, 0); - r += add_key(keys, "key", KEY, 1); - r += add_key(keys, "local", LOCAL, 0); - r += add_key(keys, "setmarginal", SETMARGINAL, 0); - r += add_key(keys, "unsetmarginal", UNSETMARGINAL, 0); - r += add_key(keys, "all", ALL, 0); + r += add_key(keys, "list", VRB_LIST, 0); + r += add_key(keys, "show", VRB_LIST, 0); + r += add_key(keys, "add", VRB_ADD, 0); + r += add_key(keys, "remove", VRB_DEL, 0); + r += add_key(keys, "del", VRB_DEL, 0); + r += add_key(keys, "switch", VRB_SWITCH, 0); + r += add_key(keys, "switchgroup", VRB_SWITCH, 0); + r += add_key(keys, "suspend", VRB_SUSPEND, 0); + r += add_key(keys, "resume", VRB_RESUME, 0); + r += add_key(keys, "reinstate", VRB_REINSTATE, 0); + r += add_key(keys, "fail", VRB_FAIL, 0); + r += add_key(keys, "resize", VRB_RESIZE, 0); + r += add_key(keys, "reset", VRB_RESET, 0); + r += add_key(keys, "reload", VRB_RELOAD, 0); + r += add_key(keys, "forcequeueing", VRB_FORCEQ, 0); + r += add_key(keys, "disablequeueing", VRB_DISABLEQ, 0); + r += add_key(keys, "restorequeueing", VRB_RESTOREQ, 0); + r += add_key(keys, "paths", KEY_PATHS, 0); + r += add_key(keys, "maps", KEY_MAPS, 0); + r += add_key(keys, "multipaths", KEY_MAPS, 0); + r += add_key(keys, "path", KEY_PATH, 1); + r += add_key(keys, "map", KEY_MAP, 1); + r += add_key(keys, "multipath", KEY_MAP, 1); + r += add_key(keys, "group", KEY_GROUP, 1); + r += add_key(keys, "reconfigure", VRB_RECONFIGURE, 0); + r += add_key(keys, "daemon", KEY_DAEMON, 0); + r += add_key(keys, "status", KEY_STATUS, 0); + r += add_key(keys, "stats", KEY_STATS, 0); + r += add_key(keys, "topology", KEY_TOPOLOGY, 0); + r += add_key(keys, "config", KEY_CONFIG, 0); + r += add_key(keys, "blacklist", KEY_BLACKLIST, 0); + r += add_key(keys, "devices", KEY_DEVICES, 0); + r += add_key(keys, "raw", KEY_RAW, 0); + r += add_key(keys, "wildcards", KEY_WILDCARDS, 0); + r += add_key(keys, "quit", VRB_QUIT, 0); + r += add_key(keys, "exit", VRB_QUIT, 0); + r += add_key(keys, "shutdown", VRB_SHUTDOWN, 0); + r += add_key(keys, "getprstatus", VRB_GETPRSTATUS, 0); + r += add_key(keys, "setprstatus", VRB_SETPRSTATUS, 0); + r += add_key(keys, "unsetprstatus", VRB_UNSETPRSTATUS, 0); + r += add_key(keys, "format", KEY_FMT, 1); + r += add_key(keys, "json", KEY_JSON, 0); + r += add_key(keys, "getprkey", VRB_GETPRKEY, 0); + r += add_key(keys, "setprkey", VRB_SETPRKEY, 0); + r += add_key(keys, "unsetprkey", VRB_UNSETPRKEY, 0); + r += add_key(keys, "key", KEY_KEY, 1); + r += add_key(keys, "local", KEY_LOCAL, 0); + r += add_key(keys, "setmarginal", VRB_SETMARGINAL, 0); + r += add_key(keys, "unsetmarginal", VRB_UNSETMARGINAL, 0); + r += add_key(keys, "all", KEY_ALL, 0); if (r) { @@ -255,15 +259,29 @@ struct key *find_key (const char * str) return foundkw; } +static void cleanup_strvec(vector *arg) +{ + free_strvec(*arg); +} + +static void cleanup_keys(vector *arg) +{ + free_keys(*arg); +} + /* - * get_cmdvec + * get_cmdvec() - parse input + * + * @cmd: a command string to be parsed + * @v: a vector of keywords with parameters * * returns: * ENOMEM: not enough memory to allocate command - * ESRCH: command not found + * ESRCH: keyword not found at end of input + * ENOENT: keyword not found somewhere else * EINVAL: argument missing for command */ -int get_cmdvec (char *cmd, vector *v) +int get_cmdvec (char *cmd, vector *v, bool allow_incomplete) { int i; int r = 0; @@ -271,18 +289,11 @@ int get_cmdvec (char *cmd, vector *v) char * buff; struct key * kw = NULL; struct key * cmdkw = NULL; - vector cmdvec, strvec; - - strvec = alloc_strvec(cmd); - if (!strvec) - return ENOMEM; - - cmdvec = vector_alloc(); + vector cmdvec __attribute__((cleanup(cleanup_keys))) = vector_alloc(); + vector strvec __attribute__((cleanup(cleanup_strvec))) = alloc_strvec(cmd); - if (!cmdvec) { - free_strvec(strvec); + if (!strvec || !cmdvec) return ENOMEM; - } vector_foreach_slot(strvec, buff, i) { if (is_quote(buff)) @@ -294,18 +305,18 @@ int get_cmdvec (char *cmd, vector *v) } kw = find_key(buff); if (!kw) { - r = ESRCH; - goto out; + r = i == VECTOR_SIZE(strvec) - 1 ? ESRCH : ENOENT; + break; } cmdkw = alloc_key(); if (!cmdkw) { r = ENOMEM; - goto out; + break; } if (!vector_alloc_slot(cmdvec)) { free(cmdkw); r = ENOMEM; - goto out; + break; } vector_set_slot(cmdvec, cmdkw); cmdkw->code = kw->code; @@ -313,32 +324,31 @@ int get_cmdvec (char *cmd, vector *v) if (kw->has_param) get_param = 1; } - if (get_param) { + if (get_param) r = EINVAL; - goto out; - } - *v = cmdvec; - free_strvec(strvec); - return 0; -out: - free_strvec(strvec); - free_keys(cmdvec); + if (r && !allow_incomplete) + return r; + + *v = cmdvec; + cmdvec = NULL; return r; } -uint64_t fingerprint(const struct _vector *vec) +uint32_t fingerprint(const struct _vector *vec) { int i; - uint64_t fp = 0; + uint32_t fp = 0; struct key * kw; - if (!vec) - return 0; - - vector_foreach_slot(vec, kw, i) - fp += kw->code; + if (!vec || VECTOR_SIZE(vec) > 4) + return INVALID_FINGERPRINT; + vector_foreach_slot(vec, kw, i) { + if (i >= 4) + break; + fp |= (uint32_t)kw->code << (8 * i); + } return fp; } @@ -377,8 +387,8 @@ genhelp_sprint_aliases (struct strbuf *reply, vector keys, static int do_genhelp(struct strbuf *reply, const char *cmd, int error) { - int i, j; - uint64_t fp; + int i, j, k; + uint32_t fp; struct handler * h; struct key * kw; int rc = 0; @@ -404,17 +414,24 @@ do_genhelp(struct strbuf *reply, const char *cmd, int error) { vector_foreach_slot (handlers, h, i) { fp = h->fingerprint; - vector_foreach_slot (keys, kw, j) { - if ((kw->code & fp)) { - fp -= kw->code; - if (print_strbuf(reply, " %s", kw->str) < 0 || - genhelp_sprint_aliases(reply, keys, kw) < 0) - return -1; - - if (kw->has_param) { - if (print_strbuf(reply, " $%s", - kw->str) < 0) + for (k = 0; k < 4; k++, fp >>= 8) { + uint32_t code = fp & 0xff; + + if (!code) + break; + + vector_foreach_slot (keys, kw, j) { + if ((uint32_t)kw->code == code) { + if (print_strbuf(reply, " %s", kw->str) < 0 || + genhelp_sprint_aliases(reply, keys, kw) < 0) return -1; + + if (kw->has_param) { + if (print_strbuf(reply, " $%s", + kw->str) < 0) + return -1; + } + break; } } } @@ -432,7 +449,7 @@ void genhelp_handler(const char *cmd, int error, struct strbuf *reply) } char * -get_keyparam (vector v, uint64_t code) +get_keyparam (vector v, uint8_t code) { struct key * kw; int i; diff --git a/multipathd/cli.h b/multipathd/cli.h index cb5bbe2..c6b79c9 100644 --- a/multipathd/cli.h +++ b/multipathd/cli.h @@ -3,98 +3,98 @@ #include <stdint.h> +/* + * CLI commands consist of 4 bytes, a verb (byte 0) and up to + * 3 qualifiers (byte 1 - 3). + */ + enum { - __LIST, /* 0 */ - __ADD, - __DEL, - __SWITCH, - __SUSPEND, - __RESUME, /* 5 */ - __REINSTATE, - __FAIL, - __RESIZE, - __RESET, - __RELOAD, /* 10 */ - __FORCEQ, - __DISABLEQ, - __RESTOREQ, - __PATHS, - __MAPS, /* 15 */ - __PATH, - __MAP, - __GROUP, - __RECONFIGURE, - __DAEMON, /* 20 */ - __STATUS, - __STATS, - __TOPOLOGY, - __CONFIG, - __BLACKLIST, /* 25 */ - __DEVICES, - __RAW, - __WILDCARDS, - __QUIT, - __SHUTDOWN, /* 30 */ - __GETPRSTATUS, - __SETPRSTATUS, - __UNSETPRSTATUS, - __FMT, - __JSON, /* 35 */ - __GETPRKEY, - __SETPRKEY, - __UNSETPRKEY, - __KEY, - __LOCAL, /* 40 */ - __SETMARGINAL, - __UNSETMARGINAL, - __ALL, + /* See INVALID_FINGERPRINT in cli.c */ + KEY_INVALID = 0, + + /* Verbs */ + VRB_LIST = 1, + VRB_ADD = 2, + VRB_DEL = 3, + VRB_RESET = 4, + VRB_SWITCH = 5, + VRB_RECONFIGURE = 6, + VRB_SUSPEND = 7, + VRB_RESUME = 8, + VRB_RESIZE = 9, + VRB_RELOAD = 10, + VRB_FAIL = 11, + VRB_REINSTATE = 12, + VRB_DISABLEQ = 13, + VRB_RESTOREQ = 14, + VRB_FORCEQ = 15, + VRB_GETPRSTATUS = 16, + VRB_SETPRSTATUS = 17, + VRB_UNSETPRSTATUS = 18, + VRB_GETPRKEY = 19, + VRB_SETPRKEY = 20, + VRB_UNSETPRKEY = 21, + VRB_SETMARGINAL = 22, + VRB_UNSETMARGINAL = 23, + VRB_SHUTDOWN = 24, + VRB_QUIT = 25, + + /* Qualifiers, values must be different from verbs */ + KEY_PATH = 65, + KEY_PATHS = 66, + KEY_MAP = 67, + KEY_MAPS = 68, + KEY_TOPOLOGY = 69, + KEY_CONFIG = 70, + KEY_BLACKLIST = 71, + KEY_DEVICES = 72, + KEY_WILDCARDS = 73, + KEY_ALL = 74, + KEY_DAEMON = 75, + KEY_FMT = 76, + KEY_RAW = 77, + KEY_STATUS = 78, + KEY_STATS = 79, + KEY_JSON = 80, + KEY_LOCAL = 81, + KEY_GROUP = 82, + KEY_KEY = 83, }; -#define LIST (1ULL << __LIST) -#define ADD (1ULL << __ADD) -#define DEL (1ULL << __DEL) -#define SWITCH (1ULL << __SWITCH) -#define SUSPEND (1ULL << __SUSPEND) -#define RESUME (1ULL << __RESUME) -#define REINSTATE (1ULL << __REINSTATE) -#define FAIL (1ULL << __FAIL) -#define RESIZE (1ULL << __RESIZE) -#define RESET (1ULL << __RESET) -#define RELOAD (1ULL << __RELOAD) -#define FORCEQ (1ULL << __FORCEQ) -#define DISABLEQ (1ULL << __DISABLEQ) -#define RESTOREQ (1ULL << __RESTOREQ) -#define PATHS (1ULL << __PATHS) -#define MAPS (1ULL << __MAPS) -#define PATH (1ULL << __PATH) -#define MAP (1ULL << __MAP) -#define GROUP (1ULL << __GROUP) -#define RECONFIGURE (1ULL << __RECONFIGURE) -#define DAEMON (1ULL << __DAEMON) -#define STATUS (1ULL << __STATUS) -#define STATS (1ULL << __STATS) -#define TOPOLOGY (1ULL << __TOPOLOGY) -#define CONFIG (1ULL << __CONFIG) -#define BLACKLIST (1ULL << __BLACKLIST) -#define DEVICES (1ULL << __DEVICES) -#define RAW (1ULL << __RAW) -#define COUNT (1ULL << __COUNT) -#define WILDCARDS (1ULL << __WILDCARDS) -#define QUIT (1ULL << __QUIT) -#define SHUTDOWN (1ULL << __SHUTDOWN) -#define GETPRSTATUS (1ULL << __GETPRSTATUS) -#define SETPRSTATUS (1ULL << __SETPRSTATUS) -#define UNSETPRSTATUS (1ULL << __UNSETPRSTATUS) -#define FMT (1ULL << __FMT) -#define JSON (1ULL << __JSON) -#define GETPRKEY (1ULL << __GETPRKEY) -#define SETPRKEY (1ULL << __SETPRKEY) -#define UNSETPRKEY (1ULL << __UNSETPRKEY) -#define KEY (1ULL << __KEY) -#define LOCAL (1ULL << __LOCAL) -#define SETMARGINAL (1ULL << __SETMARGINAL) -#define UNSETMARGINAL (1ULL << __UNSETMARGINAL) -#define ALL (1ULL << __ALL) +/* + * The shifted qualifiers determine valid positions of the + * keywords in the known commands. E.g. the only qualifier + * that's valid in position 3 is "fmt", e.g. "list maps raw fmt". + */ +enum { + /* byte 1: qualifier 1 */ + Q1_PATH = KEY_PATH << 8, + Q1_PATHS = KEY_PATHS << 8, + Q1_MAP = KEY_MAP << 8, + Q1_MAPS = KEY_MAPS << 8, + Q1_TOPOLOGY = KEY_TOPOLOGY << 8, + Q1_CONFIG = KEY_CONFIG << 8, + Q1_BLACKLIST = KEY_BLACKLIST << 8, + Q1_DEVICES = KEY_DEVICES << 8, + Q1_WILDCARDS = KEY_WILDCARDS << 8, + Q1_ALL = KEY_ALL << 8, + Q1_DAEMON = KEY_DAEMON << 8, + Q1_STATUS = KEY_STATUS << 8, + + /* byte 2: qualifier 2 */ + Q2_FMT = KEY_FMT << 16, + Q2_RAW = KEY_RAW << 16, + Q2_STATUS = KEY_STATUS << 16, + Q2_STATS = KEY_STATS << 16, + Q2_TOPOLOGY = KEY_TOPOLOGY << 16, + Q2_JSON = KEY_JSON << 16, + Q2_LOCAL = KEY_LOCAL << 16, + Q2_GROUP = KEY_GROUP << 16, + Q2_KEY = KEY_KEY << 16, + + /* byte 3: qualifier 3 */ + Q3_FMT = KEY_FMT << 24, +}; #define INITIAL_REPLY_LEN 1200 @@ -122,7 +122,7 @@ enum { struct key { char * str; char * param; - uint64_t code; + uint8_t code; int has_param; }; @@ -131,27 +131,28 @@ struct strbuf; typedef int (cli_handler)(void *keywords, struct strbuf *reply, void *data); struct handler { - uint64_t fingerprint; + uint32_t fingerprint; int locked; cli_handler *fn; }; int alloc_handlers (void); -int __set_handler_callback (uint64_t fp, cli_handler *fn, bool locked); +int __set_handler_callback (uint32_t fp, cli_handler *fn, bool locked); #define set_handler_callback(fp, fn) __set_handler_callback(fp, fn, true) #define set_unlocked_handler_callback(fp, fn) __set_handler_callback(fp, fn, false) -int get_cmdvec (char *cmd, vector *v); +int get_cmdvec (char *cmd, vector *v, bool allow_incomplete); struct handler *find_handler_for_cmdvec(const struct _vector *v); void genhelp_handler (const char *cmd, int error, struct strbuf *reply); int load_keys (void); -char * get_keyparam (vector v, uint64_t code); +char * get_keyparam (vector v, uint8_t code); +void free_key (struct key * kw); void free_keys (vector vec); void free_handlers (void); int cli_init (void); void cli_exit(void); -uint64_t fingerprint(const struct _vector *vec); +uint32_t fingerprint(const struct _vector *vec); vector get_keys(void); vector get_handlers(void); struct key *find_key (const char * str); diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index 5b8f647..5f0dd04 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -217,7 +217,7 @@ static int cli_list_paths_fmt (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * fmt = get_keyparam(v, FMT); + char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list paths (operator)"); @@ -228,7 +228,7 @@ static int cli_list_paths_raw (void *v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * fmt = get_keyparam(v, FMT); + char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list paths (operator)"); @@ -239,7 +239,7 @@ static int cli_list_path (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path *pp; param = convert_dev(param, 1); @@ -257,7 +257,7 @@ cli_list_map_topology (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); fieldwidth_t *p_width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((p_width = alloc_path_layout()) == NULL) @@ -289,7 +289,7 @@ cli_list_map_json (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); @@ -391,7 +391,7 @@ static int cli_list_maps_fmt (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * fmt = get_keyparam(v, FMT); + char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list maps (operator)"); @@ -402,7 +402,7 @@ static int cli_list_maps_raw (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * fmt = get_keyparam(v, FMT); + char * fmt = get_keyparam(v, KEY_FMT); condlog(3, "list maps (operator)"); @@ -414,8 +414,8 @@ cli_list_map_fmt (void *v, struct strbuf *reply, void *data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); - char * fmt = get_keyparam(v, FMT); + char * param = get_keyparam(v, KEY_MAP); + char * fmt = get_keyparam(v, KEY_FMT); fieldwidth_t *width __attribute__((cleanup(cleanup_ucharp))) = NULL; if ((width = alloc_multipath_layout()) == NULL) @@ -499,7 +499,7 @@ cli_reset_map_stats (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; struct multipath * mpp; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); @@ -543,7 +543,7 @@ static int cli_add_path (void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path *pp; int r; struct config *conf; @@ -663,7 +663,7 @@ static int cli_del_path (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path *pp; int ret; @@ -686,7 +686,7 @@ static int cli_add_map (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); int major = -1, minor = -1; char dev_path[FILE_NAME_SIZE]; char *refwwid, *alias = NULL; @@ -746,7 +746,7 @@ static int cli_del_map (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); int major, minor; char *alias; int rc; @@ -794,7 +794,7 @@ static int cli_reload(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * mapname = get_keyparam(v, MAP); + char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; @@ -847,7 +847,7 @@ static int cli_resize(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * mapname = get_keyparam(v, MAP); + char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; unsigned long long size; @@ -938,7 +938,7 @@ static int cli_restore_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * mapname = get_keyparam(v, MAP); + char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; struct config *conf; @@ -1001,7 +1001,7 @@ static int cli_disable_queueing(void *v, struct strbuf *reply, void *data) { struct vectors * vecs = (struct vectors *)data; - char * mapname = get_keyparam(v, MAP); + char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; @@ -1048,8 +1048,8 @@ cli_disable_all_queueing(void *v, struct strbuf *reply, void *data) static int cli_switch_group(void * v, struct strbuf *reply, void * data) { - char * mapname = get_keyparam(v, MAP); - int groupnum = atoi(get_keyparam(v, GROUP)); + char * mapname = get_keyparam(v, KEY_MAP); + int groupnum = atoi(get_keyparam(v, KEY_GROUP)); mapname = convert_dev(mapname, 0); condlog(2, "%s: switch to path group #%i (operator)", mapname, groupnum); @@ -1079,7 +1079,7 @@ static int cli_suspend(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); int r; struct multipath * mpp; @@ -1109,7 +1109,7 @@ static int cli_resume(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); int r; struct multipath * mpp; uint16_t udev_flags; @@ -1141,7 +1141,7 @@ static int cli_reinstate(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); @@ -1164,7 +1164,7 @@ static int cli_reassign (void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); struct multipath *mpp; param = convert_dev(param, 0); @@ -1188,7 +1188,7 @@ static int cli_fail(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path * pp; int r; @@ -1284,7 +1284,7 @@ cli_getprstatus (void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); @@ -1307,7 +1307,7 @@ cli_setprstatus(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); @@ -1329,7 +1329,7 @@ cli_unsetprstatus(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, MAP); + char * param = get_keyparam(v, KEY_MAP); param = convert_dev(param, 0); mpp = find_mp_by_str(vecs->mpvec, param); @@ -1350,7 +1350,7 @@ cli_getprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char *mapname = get_keyparam(v, MAP); + char *mapname = get_keyparam(v, KEY_MAP); uint64_t key; mapname = convert_dev(mapname, 0); @@ -1377,7 +1377,7 @@ cli_unsetprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char *mapname = get_keyparam(v, MAP); + char *mapname = get_keyparam(v, KEY_MAP); int ret; struct config *conf; @@ -1401,8 +1401,8 @@ cli_setprkey(void * v, struct strbuf *reply, void * data) { struct multipath * mpp; struct vectors * vecs = (struct vectors *)data; - char *mapname = get_keyparam(v, MAP); - char *keyparam = get_keyparam(v, KEY); + char *mapname = get_keyparam(v, KEY_MAP); + char *keyparam = get_keyparam(v, KEY_KEY); uint64_t prkey; uint8_t flags; int ret; @@ -1431,7 +1431,7 @@ cli_setprkey(void * v, struct strbuf *reply, void * data) static int cli_set_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); @@ -1458,7 +1458,7 @@ static int cli_set_marginal(void * v, struct strbuf *reply, void * data) static int cli_unset_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * param = get_keyparam(v, PATH); + char * param = get_keyparam(v, KEY_PATH); struct path * pp; param = convert_dev(param, 1); @@ -1485,7 +1485,7 @@ static int cli_unset_marginal(void * v, struct strbuf *reply, void * data) static int cli_unset_all_marginal(void * v, struct strbuf *reply, void * data) { struct vectors * vecs = (struct vectors *)data; - char * mapname = get_keyparam(v, MAP); + char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; struct pathgroup * pgp; struct path * pp; diff --git a/multipathd/fpin_handlers.c b/multipathd/fpin_handlers.c index 03b2b9a..a2de301 100644 --- a/multipathd/fpin_handlers.c +++ b/multipathd/fpin_handlers.c @@ -227,7 +227,7 @@ static int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *ve vector_foreach_slot(vecs->pathvec, pp, k) { /* Checks the host number and also for the SCSI FCP */ - if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP || host_num != pp->sg_id.host_no) + if (pp->bus != SYSFS_BUS_SCSI || pp->sg_id.proto_id != SCSI_PROTOCOL_FCP || host_num != pp->sg_id.host_no) continue; sprintf(rport_id, "rport-%d:%d-%d", pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); diff --git a/multipathd/multipathc.c b/multipathd/multipathc.c index b3f7db0..38f2d6a 100644 --- a/multipathd/multipathc.c +++ b/multipathd/multipathc.c @@ -15,6 +15,7 @@ #include "uxsock.h" #include "util.h" #include "cli.h" +#include "debug.h" #ifdef USE_LIBEDIT #include <editline/readline.h> @@ -39,44 +40,68 @@ #endif #if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT) -static int -key_match_fingerprint (struct key * kw, uint64_t fp) -{ - if (!fp) - return 0; - - return ((fp & kw->code) == kw->code); -} - /* * This is the readline completion handler */ char * key_generator (const char * str, int state) { - static int index, len, has_param; - static uint64_t rlfp; - struct key * kw; - int i; - struct handler *h; - vector v = NULL; - const vector keys = get_keys(); - const vector handlers = get_handlers(); + static vector completions; + static int index; + char *word; if (!state) { + uint32_t rlfp = 0, mask = 0; + int len = strlen(str), vlen = 0, i, j; + struct key * kw; + struct handler *h; + vector handlers = get_handlers(); + vector keys = get_keys(); + vector v = NULL; + int r = get_cmdvec(rl_line_buffer, &v, true); + index = 0; - has_param = 0; - rlfp = 0; - len = strlen(str); - int r = get_cmdvec(rl_line_buffer, &v); + if (completions) + vector_free(completions); + + completions = vector_alloc(); + + if (!completions || r == ENOMEM) { + if (v) + vector_free(v); + return NULL; + } + + /* + * Special case: get_cmdvec() ignores trailing whitespace, + * readline doesn't. get_cmdvec() will return "[show]" and + * ESRCH for both "show bogus\t" and "show bogus \t". + * The former case will fail below. In the latter case, + * We shouldn't offer completions. + */ + if (r == ESRCH && !len) + r = ENOENT; + /* * If a word completion is in progress, we don't want * to take an exact keyword match in the fingerprint. * For ex "show map[tab]" would validate "map" and discard * "maps" as a valid candidate. */ - if (v && len) - vector_del_slot(v, VECTOR_SIZE(v) - 1); + if (r != ESRCH && VECTOR_SIZE(v) && len) { + kw = VECTOR_SLOT(v, VECTOR_SIZE(v) - 1); + /* + * If kw->param is set, we were already parsing a + * parameter, not the keyword. Don't delete it. + */ + if (!kw->param) { + free_key(kw); + vector_del_slot(v, VECTOR_SIZE(v) - 1); + if (r == EINVAL) + r = 0; + } + } + /* * Clean up the mess if we dropped the last slot of a 1-slot * vector @@ -85,66 +110,86 @@ key_generator (const char * str, int state) vector_free(v); v = NULL; } - /* - * If last keyword takes a param, don't even try to guess - */ - if (r == EINVAL) { - has_param = 1; - return (strdup("(value)")); - } + /* * Compute a command fingerprint to find out possible completions. * Once done, the vector is useless. Free it. */ if (v) { rlfp = fingerprint(v); + vlen = VECTOR_SIZE(v); + if (vlen >= 4) + mask = ~0; + else + mask = (uint32_t)(1U << (8 * vlen)) - 1; free_keys(v); } - } - /* - * No more completions for parameter placeholder. - * Brave souls might try to add parameter completion by walking paths and - * multipaths vectors. - */ - if (has_param) - return ((char *)NULL); - /* - * Loop through keywords for completion candidates - */ - vector_foreach_slot_after (keys, kw, index) { - if (!strncmp(kw->str, str, len)) { - /* - * Discard keywords already in the command line - */ - if (key_match_fingerprint(kw, rlfp)) { - struct key * curkw = find_key(str); - if (!curkw || (curkw != kw)) + condlog(4, "%s: line=\"%s\" str=\"%s\" r=%d fp=%08x mask=%08x", + __func__, rl_line_buffer, str, r, rlfp, mask); + + /* + * If last keyword takes a param, don't even try to guess + * Brave souls might try to add parameter completion by walking + * paths and multipaths vectors. + */ + if (r == EINVAL) { + if (len == 0 && vector_alloc_slot(completions)) + vector_set_slot(completions, + strdup("VALUE")); + + goto init_done; + } + + if (r == ENOENT) + goto init_done; + + vector_foreach_slot(handlers, h, i) { + uint8_t code; + + if (rlfp != (h->fingerprint & mask)) + continue; + + if (vlen >= 4) + /* + * => mask == ~0 => rlfp == h->fingerprint + * Complete command. This must be the only match. + */ + goto init_done; + else if (rlfp == h->fingerprint && r != ESRCH && + !strcmp(str, "") && + vector_alloc_slot(completions)) + /* just completed */ + vector_set_slot(completions, strdup("")); + else { + /* vlen must be 1, 2, or 3 */ + code = (h->fingerprint >> vlen * 8); + + if (code == KEY_INVALID) continue; - } - /* - * Discard keywords making syntax errors. - * - * nfp is the candidate fingerprint we try to - * validate against all known command fingerprints. - */ - uint64_t nfp = rlfp | kw->code; - vector_foreach_slot(handlers, h, i) { - if (!rlfp || ((h->fingerprint & nfp) == nfp)) { - /* - * At least one full command is - * possible with this keyword : - * Consider it validated - */ - index++; - return (strdup(kw->str)); + + vector_foreach_slot(keys, kw, j) { + if (kw->code != code || + strncmp(kw->str, str, len)) + continue; + if (vector_alloc_slot(completions)) + vector_set_slot(completions, + strdup(kw->str)); } } + } + vector_foreach_slot(completions, word, i) + condlog(4, "%s: %d -> \"%s\"", __func__, i, word); + } - /* - * No more candidates - */ - return ((char *)NULL); + +init_done: + vector_foreach_slot_after(completions, word, index) { + index++; + return word; + } + + return NULL; } #endif diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index 23cb123..02e89fb 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -332,7 +332,7 @@ static int parse_cmd(struct client *c) { int r; - r = get_cmdvec(c->cmd, &c->cmdvec); + r = get_cmdvec(c->cmd, &c->cmdvec, false); if (r) return -r; @@ -412,6 +412,11 @@ static void set_client_state(struct client *c, int state) c->expires = ts_zero; /* reuse these fields for next data transfer */ c->len = c->cmd_len = 0; + /* cmdvec isn't needed any more */ + if (c->cmdvec) { + free_keys(c->cmdvec); + c->cmdvec = NULL; + } break; default: break; @@ -484,7 +489,7 @@ static int client_state_machine(struct client *c, struct vectors *vecs, /* Permission check */ struct key *kw = VECTOR_SLOT(c->cmdvec, 0); - if (!c->is_root && kw->code != LIST) { + if (!c->is_root && kw->code != VRB_LIST) { c->error = -EPERM; condlog(0, "%s: cli[%d]: unauthorized cmd \"%s\"", __func__, c->fd, c->cmd); @@ -506,8 +511,6 @@ static int client_state_machine(struct client *c, struct vectors *vecs, check_for_locked_work(c); pthread_cleanup_pop(1); condlog(4, "%s: cli[%d] grabbed lock", __func__, c->fd); - free_keys(c->cmdvec); - c->cmdvec = NULL; set_client_state(c, CLT_SEND); /* Wait for POLLOUT */ return STM_BREAK; @@ -518,8 +521,6 @@ static int client_state_machine(struct client *c, struct vectors *vecs, case CLT_WORK: c->error = execute_handler(c, vecs); - free_keys(c->cmdvec); - c->cmdvec = NULL; set_client_state(c, CLT_SEND); /* Wait for POLLOUT */ return STM_BREAK; @@ -541,7 +542,7 @@ static int client_state_machine(struct client *c, struct vectors *vecs, if (c->len < c->cmd_len) { const char *buf = get_strbuf_str(&c->reply); - n = send(c->fd, buf + c->len, c->cmd_len, MSG_NOSIGNAL); + n = send(c->fd, buf + c->len, c->cmd_len - c->len, MSG_NOSIGNAL); if (n == -1) { if (!(errno == EAGAIN || errno == EINTR)) c->error = -ECONNRESET; diff --git a/tests/Makefile b/tests/Makefile index 109ea75..3a5b161 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -11,12 +11,13 @@ TEST_MISSING_INITIALIZERS = $(shell \ || echo -Wno-missing-field-initializers) W_MISSING_INITIALIZERS := $(call TEST_MISSING_INITIALIZERS) -CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) -DTESTCONFDIR=\"$(TESTDIR)/conf.d\" +CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) -I$(daemondir) \ + -DTESTCONFDIR=\"$(TESTDIR)/conf.d\" CFLAGS += $(BIN_CFLAGS) -Wno-unused-parameter $(W_MISSING_INITIALIZERS) LIBDEPS += -L. -L $(mpathutildir) -L$(mpathcmddir) -lmultipath -lmpathutil -lmpathcmd -lcmocka TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \ - alias directio valid devt mpathvalid strbuf sysfs + alias directio valid devt mpathvalid strbuf sysfs features cli HELPERS := test-lib.o test-log.o .SILENT: $(TESTS:%=%.o) @@ -36,6 +37,7 @@ ifneq ($(DIO_TEST_DEV),) directio-test_FLAGS := -DDIO_TEST_DEV=\"$(DIO_TEST_DEV)\" endif mpathvalid-test_FLAGS := -I$(mpathvaliddir) +features-test_FLAGS := -I$(multipathdir)/nvme # test-specific linker flags # XYZ-test_TESTDEPS: test libraries containing __wrap_xyz functions @@ -73,6 +75,8 @@ strbuf-test_OBJDEPS := $(mpathutildir)/strbuf.o sysfs-test_TESTDEPS := test-log.o sysfs-test_OBJDEPS := $(multipathdir)/sysfs.o $(mpathutildir)/util.o sysfs-test_LIBDEPS := -ludev -lpthread -ldl +features-test_LIBDEPS := -ludev -lpthread +cli-test_OBJDEPS := $(daemondir)/cli.o %.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) $($*-test_FLAGS) -c -o $@ $< diff --git a/tests/cli.c b/tests/cli.c new file mode 100644 index 0000000..9e2ad3c --- /dev/null +++ b/tests/cli.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2020 Martin Wilck, SUSE + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include <stdbool.h> +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <stdlib.h> +#include <cmocka.h> + +#include <errno.h> + +#include "vector.h" +#include "cli.h" + +#include "globals.c" +#define HANDLER(x) NULL +#include "callbacks.c" + +/* See cli.c */ +#define INVALID_FINGERPRINT ((uint32_t)(0)) + +static int setup(void **state) +{ + return cli_init(); +} + +static int teardown(void **state) +{ + cli_exit(); + return 0; +} + +/* + * @NAME: test name + * @CMD: test command + * @R: retcode of get_cmdvec() + * @FPR: fingerprint (only if R==0) + * @GOOD: expect to find handler (only if R==0) + */ +#define client_test(NAME, CMD, R, FPR, GOOD) \ +static void client_test_##NAME(void **state) \ +{ \ + vector v = NULL; \ + char cmd[] = CMD; \ + \ + assert_int_equal(get_cmdvec(cmd, &v, false), R); \ + if (R == 0) { \ + assert_ptr_not_equal(v, NULL); \ + assert_int_equal(fingerprint(v), FPR); \ + if (GOOD) \ + assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ + else \ + assert_ptr_equal(find_handler_for_cmdvec(v), NULL); \ + free_keys(v); \ + } else \ + assert_ptr_equal(v, NULL); \ +} + +/* + * @NAME: test name + * @CMD: test command + * @FPR: fingerprint + * @PAR: value of parameter for keyword 1 + */ +#define client_param(NAME, CMD, FPR, PAR) \ +static void client_param_##NAME(void **state) \ +{ \ + vector v = NULL; \ + char cmd[] = CMD; \ + \ + assert_int_equal(get_cmdvec(cmd, &v, false), 0); \ + assert_ptr_not_equal(v, NULL); \ + assert_int_equal(fingerprint(v), FPR); \ + assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ + assert_string_equal(((struct key *)VECTOR_SLOT(v, 1))->param, PAR); \ + free_keys(v); \ +} + +/* + * @NAME: test name + * @CMD: test command + * @FPR: fingerprint + * @PAR1: value of parameter for keyword 1 + * @N: index of 2nd parameter keyword + * @PARN: value of parameter for keyword N + */ +#define client_2param(NAME, CMD, FPR, PAR1, N, PARN) \ +static void client_2param_##NAME(void **state) \ +{ \ + vector v = NULL; \ + char cmd[] = CMD; \ + \ + assert_int_equal(get_cmdvec(cmd, &v, false), 0); \ + assert_ptr_not_equal(v, NULL); \ + assert_int_equal(fingerprint(v), FPR); \ + assert_ptr_not_equal(find_handler_for_cmdvec(v), NULL); \ + assert_string_equal(((struct key *)VECTOR_SLOT(v, 1))->param, PAR1); \ + assert_string_equal(((struct key *)VECTOR_SLOT(v, N))->param, PARN); \ + free_keys(v); \ +} + +static void client_test_null(void **state) +{ + vector v = NULL; + + /* alloc_strvec() returns ENOMEM for NULL cmd */ + assert_int_equal(get_cmdvec(NULL, &v, false), ENOMEM); + assert_ptr_equal(v, NULL); +} + +/* alloc_strvec() returns ENOMEM for empty string */ +client_test(empty, "", ENOMEM, 0, false); +client_test(bogus, "bogus", ESRCH, 0, false); +client_test(list, "list", 0, VRB_LIST, false); +client_test(show, "show", 0, VRB_LIST, false); +client_test(s, "s", ESRCH, 0, false); +/* partial match works if it's unique */ +client_test(sh, "sh", ESRCH, 0, false); +client_test(sho, "sho", 0, VRB_LIST, false); +client_test(add, "add", 0, VRB_ADD, false); +client_test(resume, "resume", 0, VRB_RESUME, false); +client_test(disablequeueing, "disablequeueing", 0, VRB_DISABLEQ, false); +/* "disable" -> disablequeueing, "queueing" -> not found */ +client_test(disable_queueing, "disable queueing", ESRCH, 0, false); +/* ENOENT because the not-found keyword is not last pos */ +client_test(queueing_disable, "queueing disable", ENOENT, 0, false); +client_test(disable, "disable", 0, VRB_DISABLEQ, false); +client_test(setprkey, "setprkey", 0, VRB_SETPRKEY, false); +client_test(quit, "quit", 0, VRB_QUIT, true); +client_test(exit, "exit", 0, VRB_QUIT, true); +client_test(show_maps, "show maps", 0, VRB_LIST|Q1_MAPS, true); +client_test(sh_maps, "sh maps", ENOENT, 0, 0); +client_test(sho_maps, "sho maps", 0, VRB_LIST|Q1_MAPS, true); +client_test(sho_multipaths, "sho multipaths", 0, VRB_LIST|Q1_MAPS, true); +/* Needs a parameter */ +client_test(show_map, "show map", EINVAL, 0, 0); +client_test(show_ma, "show ma", ESRCH, 0, 0); +client_test(show_list_maps, "show list maps", 0, + VRB_LIST|(VRB_LIST<<8)|(KEY_MAPS<<16), false); +client_test(show_maps_list, "show maps list", 0, + VRB_LIST|(VRB_LIST<<16)|(KEY_MAPS<<8), false); +client_test(maps_show, "maps show ", 0, (VRB_LIST<<8)|KEY_MAPS, false); +client_test(show_maps_list_json, "show maps list json", 0, + VRB_LIST|(VRB_LIST<<16)|(KEY_MAPS<<8)|(KEY_JSON<<24), false); +/* More than 4 keywords */ +client_test(show_maps_list_json_raw, "show maps list json raw", 0, + INVALID_FINGERPRINT, false); +client_test(show_list_show_list, "show list show list", 0, + VRB_LIST|(VRB_LIST<<8)|(VRB_LIST<<16)|(VRB_LIST<<24), false); +client_test(show_list_show_list_show, "show list show list show", 0, + INVALID_FINGERPRINT, false); +client_test(q_q_q_q, "q q q q", 0, + VRB_QUIT|(VRB_QUIT<<8)|(VRB_QUIT<<16)|(VRB_QUIT<<24), false); +client_test(show_path_xy, "show path xy", 0, VRB_LIST|Q1_PATH, true); +client_param(show_path_xy, "show path xy", VRB_LIST|Q1_PATH, "xy"); +client_param(show_path_xy_2, "show path \"xy\"", VRB_LIST|Q1_PATH, "xy"); +client_param(show_path_x_y, "show path \"x y\"", VRB_LIST|Q1_PATH, "x y"); +client_param(show_path_2inch, "show path \"2\"\"\"", VRB_LIST|Q1_PATH, "2\""); +/* missing closing quote */ +client_param(show_path_2inch_1, "show path \"2\"\"", VRB_LIST|Q1_PATH, "2\""); +client_test(show_map_xy, "show map xy", 0, VRB_LIST|Q1_MAP, false); +client_test(show_map_xy_bogus, "show map xy bogus", ESRCH, 0, false); +client_2param(show_map_xy_format_h, "show map xy form %h", + VRB_LIST|Q1_MAP|Q2_FMT, "xy", 2, "%h"); +client_2param(show_map_xy_raw_format_h, "show map xy raw form %h", + VRB_LIST|Q1_MAP|Q2_RAW|Q3_FMT, "xy", 3, "%h"); +client_test(show_map_xy_format_h_raw, "show map xy form %h raw", 0, + VRB_LIST|(KEY_MAP<<8)|(KEY_FMT<<16)|(KEY_RAW<<24), false); +client_param(list_path_sda, "list path sda", VRB_LIST|Q1_PATH, "sda"); +client_param(add_path_sda, "add path sda", VRB_ADD|Q1_PATH, "sda"); +client_test(list_list_path_sda, "list list path sda", 0, + VRB_LIST|(VRB_LIST<<8)|(KEY_PATH<<16), false); + +static int client_tests(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(client_test_null), + cmocka_unit_test(client_test_empty), + cmocka_unit_test(client_test_bogus), + cmocka_unit_test(client_test_list), + cmocka_unit_test(client_test_show), + cmocka_unit_test(client_test_s), + cmocka_unit_test(client_test_sh), + cmocka_unit_test(client_test_sho), + cmocka_unit_test(client_test_add), + cmocka_unit_test(client_test_resume), + cmocka_unit_test(client_test_disablequeueing), + cmocka_unit_test(client_test_disable_queueing), + cmocka_unit_test(client_test_queueing_disable), + cmocka_unit_test(client_test_disable), + cmocka_unit_test(client_test_setprkey), + cmocka_unit_test(client_test_quit), + cmocka_unit_test(client_test_exit), + cmocka_unit_test(client_test_show_maps), + cmocka_unit_test(client_test_sh_maps), + cmocka_unit_test(client_test_sho_maps), + cmocka_unit_test(client_test_sho_multipaths), + cmocka_unit_test(client_test_show_map), + cmocka_unit_test(client_test_show_ma), + cmocka_unit_test(client_test_maps_show), + cmocka_unit_test(client_test_show_list_maps), + cmocka_unit_test(client_test_show_maps_list), + cmocka_unit_test(client_test_show_maps_list_json), + cmocka_unit_test(client_test_show_maps_list_json_raw), + cmocka_unit_test(client_test_show_list_show_list), + cmocka_unit_test(client_test_show_list_show_list_show), + cmocka_unit_test(client_test_q_q_q_q), + cmocka_unit_test(client_test_show_map_xy), + cmocka_unit_test(client_test_show_map_xy_bogus), + cmocka_unit_test(client_test_show_path_xy), + cmocka_unit_test(client_param_show_path_xy), + cmocka_unit_test(client_param_show_path_xy_2), + cmocka_unit_test(client_param_show_path_x_y), + cmocka_unit_test(client_param_show_path_2inch), + cmocka_unit_test(client_param_show_path_2inch_1), + cmocka_unit_test(client_2param_show_map_xy_format_h), + cmocka_unit_test(client_2param_show_map_xy_raw_format_h), + cmocka_unit_test(client_test_show_map_xy_format_h_raw), + cmocka_unit_test(client_param_list_path_sda), + cmocka_unit_test(client_param_add_path_sda), + cmocka_unit_test(client_test_list_list_path_sda), + }; + + return cmocka_run_group_tests(tests, setup, teardown); +} + +int main(void) +{ + int ret = 0; + + init_test_verbosity(-1); + ret += client_tests(); + return ret; +} diff --git a/tests/features.c b/tests/features.c new file mode 100644 index 0000000..31f978f --- /dev/null +++ b/tests/features.c @@ -0,0 +1,549 @@ +#define _GNU_SOURCE +#include <stddef.h> +#include <stdarg.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "../libmultipath/propsel.c" +#include "globals.c" + +static void test_af_null_features_ptr(void **state) +{ + assert_int_equal(add_feature(NULL, "test"), 1); +} + +static void af_helper(const char *features_start, const char *addition, + const char *features_end, int result) +{ + char *f = NULL, *orig = NULL; + + if (features_start) { + f = strdup(features_start); + assert_non_null(f); + orig = f; + } + assert_int_equal(add_feature(&f, addition), result); + if (result != 0 || features_end == NULL) + assert_ptr_equal(orig, f); + else + assert_string_equal(f, features_end); + free(f); +} + +static void test_af_null_addition1(void **state) +{ + af_helper("0", NULL, NULL, 0); +} + +static void test_af_null_addition2(void **state) +{ + af_helper("1 queue_if_no_path", NULL, NULL, 0); +} + +static void test_af_empty_addition(void **state) +{ + af_helper("2 pg_init_retries 5", "", NULL, 0); +} + +static void test_af_invalid_addition1(void **state) +{ + af_helper("2 pg_init_retries 5", " ", NULL, 1); +} + +static void test_af_invalid_addition2(void **state) +{ + af_helper("2 pg_init_retries 5", "\tbad", NULL, 1); +} + +static void test_af_invalid_addition3(void **state) +{ + af_helper("2 pg_init_retries 5", "bad ", NULL, 1); +} + +static void test_af_invalid_addition4(void **state) +{ + af_helper("2 pg_init_retries 5", " bad ", NULL, 1); +} + +static void test_af_null_features1(void **state) +{ + af_helper(NULL, "test", "1 test", 0); +} + +static void test_af_null_features2(void **state) +{ + af_helper(NULL, "test\t more", "2 test\t more", 0); +} + +static void test_af_null_features3(void **state) +{ + af_helper(NULL, "test\neven\tmore", "3 test\neven\tmore", 0); +} + +static void test_af_already_exists1(void **state) +{ + af_helper("4 this is a test", "test", NULL, 0); +} + +static void test_af_already_exists2(void **state) +{ + af_helper("5 contest testy intestine test retest", "test", NULL, 0); +} + +static void test_af_almost_exists(void **state) +{ + af_helper("3 contest testy intestine", "test", + "4 contest testy intestine test", 0); +} + +static void test_af_bad_features1(void **state) +{ + af_helper("bad", "test", NULL, 1); +} + +static void test_af_bad_features2(void **state) +{ + af_helper("1bad", "test", NULL, 1); +} + +static void test_af_add1(void **state) +{ + af_helper("0", "test", "1 test", 0); +} + +static void test_af_add2(void **state) +{ + af_helper("0", "this is a test", "4 this is a test", 0); +} + +static void test_af_add3(void **state) +{ + af_helper("1 features", "more values", "3 features more values", 0); +} + +static void test_af_add4(void **state) +{ + af_helper("2 one\ttwo", "three\t four", "4 one\ttwo three\t four", 0); +} + +static int test_add_features(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_af_null_features_ptr), + cmocka_unit_test(test_af_null_addition1), + cmocka_unit_test(test_af_null_addition2), + cmocka_unit_test(test_af_empty_addition), + cmocka_unit_test(test_af_invalid_addition1), + cmocka_unit_test(test_af_invalid_addition2), + cmocka_unit_test(test_af_invalid_addition3), + cmocka_unit_test(test_af_invalid_addition4), + cmocka_unit_test(test_af_null_features1), + cmocka_unit_test(test_af_null_features2), + cmocka_unit_test(test_af_null_features3), + cmocka_unit_test(test_af_already_exists1), + cmocka_unit_test(test_af_already_exists2), + cmocka_unit_test(test_af_almost_exists), + cmocka_unit_test(test_af_bad_features1), + cmocka_unit_test(test_af_bad_features2), + cmocka_unit_test(test_af_add1), + cmocka_unit_test(test_af_add2), + cmocka_unit_test(test_af_add3), + cmocka_unit_test(test_af_add4), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void test_rf_null_features_ptr(void **state) +{ + assert_int_equal(remove_feature(NULL, "test"), 1); +} + +static void test_rf_null_features(void **state) +{ + char *f = NULL; + assert_int_equal(remove_feature(&f, "test"), 1); +} + +static void rf_helper(const char *features_start, const char *removal, + const char *features_end, int result) +{ + char *f = strdup(features_start); + char *orig = f; + + assert_non_null(f); + assert_int_equal(remove_feature(&f, removal), result); + if (result != 0 || features_end == NULL) + assert_ptr_equal(orig, f); + else + assert_string_equal(f, features_end); + free(f); +} + +static void test_rf_null_removal(void **state) +{ + rf_helper("1 feature", NULL, NULL, 0); +} + +static void test_rf_empty_removal(void **state) +{ + rf_helper("1 feature", "", NULL, 0); +} + +static void test_rf_invalid_removal1(void **state) +{ + rf_helper("1 feature", " ", NULL, 1); +} + +static void test_rf_invalid_removal2(void **state) +{ + rf_helper("1 feature", " bad", NULL, 1); +} + +static void test_rf_invalid_removal3(void **state) +{ + rf_helper("1 feature", "bad\n", NULL, 1); +} + +static void test_rf_invalid_removal4(void **state) +{ + rf_helper("1 feature", "\tbad \n", NULL, 1); +} + +static void test_rf_bad_features1(void **state) +{ + rf_helper("invalid feature test string", "test", NULL, 1); +} + +static void test_rf_bad_features2(void **state) +{ + rf_helper("2no space test", "test", NULL, 1); +} + +static void test_rf_missing_removal1(void **state) +{ + rf_helper("0", "test", NULL, 0); +} + +static void test_rf_missing_removal2(void **state) +{ + rf_helper("1 detest", "test", NULL, 0); +} + +static void test_rf_missing_removal3(void **state) +{ + rf_helper("4 testing one two three", "test", NULL, 0); +} + +static void test_rf_missing_removal4(void **state) +{ + rf_helper("1 contestant", "test", NULL, 0); +} + +static void test_rf_missing_removal5(void **state) +{ + rf_helper("3 testament protest detestable", "test", NULL, 0); +} + +static void test_rf_remove_all_features1(void **state) +{ + rf_helper("1 test", "test", "0", 0); +} + +static void test_rf_remove_all_features2(void **state) +{ + rf_helper("2 another\t test", "another\t test", "0", 0); +} + +static void test_rf_remove1(void **state) +{ + rf_helper("2 feature1 feature2", "feature2", "1 feature1", 0); +} + +static void test_rf_remove2(void **state) +{ + rf_helper("2 feature1 feature2", "feature1", "1 feature2", 0); +} + +static void test_rf_remove3(void **state) +{ + rf_helper("3 test1 test\ttest2", "test", "2 test1 test2", 0); +} + +static void test_rf_remove4(void **state) +{ + rf_helper("4 this\t is a test", "is a", "2 this\t test", 0); +} + +static void test_rf_remove5(void **state) +{ + rf_helper("3 one more test", "more test", "1 one", 0); +} + +static int test_remove_features(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_rf_null_features_ptr), + cmocka_unit_test(test_rf_null_features), + cmocka_unit_test(test_rf_null_removal), + cmocka_unit_test(test_rf_empty_removal), + cmocka_unit_test(test_rf_invalid_removal1), + cmocka_unit_test(test_rf_invalid_removal2), + cmocka_unit_test(test_rf_invalid_removal3), + cmocka_unit_test(test_rf_invalid_removal4), + cmocka_unit_test(test_rf_bad_features1), + cmocka_unit_test(test_rf_bad_features2), + cmocka_unit_test(test_rf_missing_removal1), + cmocka_unit_test(test_rf_missing_removal2), + cmocka_unit_test(test_rf_missing_removal3), + cmocka_unit_test(test_rf_missing_removal4), + cmocka_unit_test(test_rf_missing_removal5), + cmocka_unit_test(test_rf_remove_all_features1), + cmocka_unit_test(test_rf_remove_all_features2), + cmocka_unit_test(test_rf_remove1), + cmocka_unit_test(test_rf_remove2), + cmocka_unit_test(test_rf_remove3), + cmocka_unit_test(test_rf_remove4), + cmocka_unit_test(test_rf_remove5), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +static void test_cf_null_features(void **state) +{ + struct multipath mp = { + .alias = "test", + }; + reconcile_features_with_queue_mode(&mp); + assert_null(mp.features); +} + +static void cf_helper(const char *features_start, const char *features_end, + int queue_mode_start, int queue_mode_end) +{ + struct multipath mp = { + .alias = "test", + .features = strdup(features_start), + .queue_mode = queue_mode_start, + }; + char *orig = mp.features; + + assert_non_null(orig); + reconcile_features_with_queue_mode(&mp); + if (!features_end) + assert_ptr_equal(orig, mp.features); + else + assert_string_equal(mp.features, features_end); + free(mp.features); + assert_int_equal(mp.queue_mode, queue_mode_end); +} + +static void test_cf_unset_unset1(void **state) +{ + cf_helper("0", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); +} + +static void test_cf_unset_unset2(void **state) +{ + cf_helper("1 queue_mode", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); +} + +static void test_cf_unset_unset3(void **state) +{ + cf_helper("queue_mode", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); +} + +static void test_cf_unset_unset4(void **state) +{ + cf_helper("2 queue_model bio", NULL, QUEUE_MODE_UNDEF, + QUEUE_MODE_UNDEF); +} + +static void test_cf_unset_unset5(void **state) +{ + cf_helper("1 queue_if_no_path", NULL, QUEUE_MODE_UNDEF, + QUEUE_MODE_UNDEF); +} + +static void test_cf_invalid_unset1(void **state) +{ + cf_helper("2 queue_mode biop", "0", QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); +} + +static void test_cf_invalid_unset2(void **state) +{ + cf_helper("3 queue_mode rqs queue_if_no_path", "1 queue_if_no_path", + QUEUE_MODE_UNDEF, QUEUE_MODE_UNDEF); +} + +static void test_cf_rq_unset1(void **state) +{ + cf_helper("2 queue_mode rq", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_RQ); +} + +static void test_cf_rq_unset2(void **state) +{ + cf_helper("2 queue_mode mq", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_RQ); +} + +static void test_cf_bio_unset(void **state) +{ + cf_helper("2 queue_mode bio", NULL, QUEUE_MODE_UNDEF, QUEUE_MODE_BIO); +} + +static void test_cf_unset_bio1(void **state) +{ + cf_helper("1 queue_if_no_path", "3 queue_if_no_path queue_mode bio", + QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_unset_bio2(void **state) +{ + cf_helper("0", "2 queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_unset_bio3(void **state) +{ + cf_helper("2 pg_init_retries 50", "4 pg_init_retries 50 queue_mode bio", + QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_invalid_bio1(void **state) +{ + cf_helper("2 queue_mode bad", "2 queue_mode bio", + QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_invalid_bio2(void **state) +{ + cf_helper("3 queue_if_no_path queue_mode\tbad", "3 queue_if_no_path queue_mode bio", + QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_bio_bio1(void **state) +{ + cf_helper("2 queue_mode bio", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_bio_bio2(void **state) +{ + cf_helper("3 queue_if_no_path queue_mode bio", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_bio_bio3(void **state) +{ + cf_helper("3 queue_mode\nbio queue_if_no_path", NULL, QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_bio_rq1(void **state) +{ + cf_helper("2\nqueue_mode\tbio", "0", QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_bio_rq2(void **state) +{ + cf_helper("3 queue_if_no_path\nqueue_mode bio", "1 queue_if_no_path", + QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_bio_rq3(void **state) +{ + cf_helper("4 queue_mode bio pg_init_retries 20", "2 pg_init_retries 20", + QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_unset_rq1(void **state) +{ + cf_helper("0", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_unset_rq2(void **state) +{ + cf_helper("2 pg_init_retries 15", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_invalid_rq1(void **state) +{ + cf_helper("2 queue_mode bionic", "0", QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_invalid_rq2(void **state) +{ + cf_helper("3 queue_mode b\nqueue_if_no_path", "1 queue_if_no_path", + QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_rq_rq1(void **state) +{ + cf_helper("2 queue_mode rq", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_rq_rq2(void **state) +{ + cf_helper("3 queue_mode\t \trq\nqueue_if_no_path", NULL, QUEUE_MODE_RQ, QUEUE_MODE_RQ); +} + +static void test_cf_rq_bio1(void **state) +{ + cf_helper("2 queue_mode rq", "2 queue_mode bio", QUEUE_MODE_BIO, + QUEUE_MODE_BIO); +} + +static void test_cf_rq_bio2(void **state) +{ + cf_helper("3 queue_if_no_path\nqueue_mode rq", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static void test_cf_rq_bio3(void **state) +{ + cf_helper("3 queue_mode rq\nqueue_if_no_path", "3 queue_if_no_path queue_mode bio", QUEUE_MODE_BIO, QUEUE_MODE_BIO); +} + +static int test_reconcile_features(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_cf_null_features), + cmocka_unit_test(test_cf_unset_unset1), + cmocka_unit_test(test_cf_unset_unset2), + cmocka_unit_test(test_cf_unset_unset3), + cmocka_unit_test(test_cf_unset_unset4), + cmocka_unit_test(test_cf_unset_unset5), + cmocka_unit_test(test_cf_invalid_unset1), + cmocka_unit_test(test_cf_invalid_unset2), + cmocka_unit_test(test_cf_rq_unset1), + cmocka_unit_test(test_cf_rq_unset2), + cmocka_unit_test(test_cf_bio_unset), + cmocka_unit_test(test_cf_unset_bio1), + cmocka_unit_test(test_cf_unset_bio2), + cmocka_unit_test(test_cf_unset_bio3), + cmocka_unit_test(test_cf_invalid_bio1), + cmocka_unit_test(test_cf_invalid_bio2), + cmocka_unit_test(test_cf_bio_bio1), + cmocka_unit_test(test_cf_bio_bio2), + cmocka_unit_test(test_cf_bio_bio3), + cmocka_unit_test(test_cf_bio_rq1), + cmocka_unit_test(test_cf_bio_rq2), + cmocka_unit_test(test_cf_bio_rq3), + cmocka_unit_test(test_cf_unset_rq1), + cmocka_unit_test(test_cf_unset_rq2), + cmocka_unit_test(test_cf_invalid_rq1), + cmocka_unit_test(test_cf_invalid_rq2), + cmocka_unit_test(test_cf_rq_rq1), + cmocka_unit_test(test_cf_rq_rq2), + cmocka_unit_test(test_cf_rq_bio1), + cmocka_unit_test(test_cf_rq_bio2), + cmocka_unit_test(test_cf_rq_bio3), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +int main(void) +{ + int ret = 0; + + init_test_verbosity(-1); + ret += test_add_features(); + ret += test_remove_features(); + ret += test_reconcile_features(); + + return ret; +} |