Imported Upstream version 0.9.2 upstream/0.9.2
authorJinWang An <jinwang.an@samsung.com>
Fri, 13 Jan 2023 06:40:26 +0000 (15:40 +0900)
committerJinWang An <jinwang.an@samsung.com>
Fri, 13 Jan 2023 06:40:26 +0000 (15:40 +0900)
37 files changed:
.gitignore
Makefile.inc
README.md
kpartx/kpartx.8
kpartx/kpartx.c
libmpathpersist/mpath_persistent_reserve_in.3
libmpathpersist/mpath_persistent_reserve_out.3
libmpathutil/parser.c
libmultipath/configure.c
libmultipath/defaults.h
libmultipath/discovery.c
libmultipath/dmparser.c
libmultipath/libmultipath.version
libmultipath/print.c
libmultipath/propsel.c
libmultipath/structs.c
libmultipath/structs.h
libmultipath/structs_vec.c
libmultipath/version.h
mpathpersist/main.c
mpathpersist/mpathpersist.8
multipath/Makefile
multipath/multipath.8
multipath/multipath.conf.5
multipath/multipath.rules [deleted file]
multipath/multipath.rules.in [new file with mode: 0644]
multipath/tmpfiles.conf.in [new file with mode: 0644]
multipathd/callbacks.c
multipathd/cli.c
multipathd/cli.h
multipathd/cli_handlers.c
multipathd/fpin_handlers.c
multipathd/multipathc.c
multipathd/uxlsnr.c
tests/Makefile
tests/cli.c [new file with mode: 0644]
tests/features.c [new file with mode: 0644]

index 821c3e6721dd02d49a347e5a2f75d2e4adabf803..83f8a5523f78e2de9b9b9c4c61b0d9386bd23452 100644 (file)
@@ -12,6 +12,8 @@ cscope.files
 cscope.out
 kpartx/kpartx
 multipath/multipath
+multipath/multipath.rules
+multipath/tmpfiles.conf
 multipathd/multipathd
 multipathd/multipathc
 mpathpersist/mpathpersist
index 6399beb2bb440a230292346b77c086703e7a8178..5602506cff151dfaf3dcf1da0f78e0f33f61d386 100644 (file)
@@ -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
index b05b1332069c7de48e3be9c11a891f6185e2eede..d003e1dbd5a202c1cdf9d6a38482367e7db6bff0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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:
index 08bb349be0645f2784b8f232f1bab46f63a9eae6..2b144a7fa11af7994c58def1cc1d3f366e1c9aeb 100644 (file)
@@ -5,7 +5,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH KPARTX 8 2016-10-28 "Linux"
+.TH KPARTX 8 2019-04-27 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
index 3c4999959e889462f006e986b6137b5bc4a74171..1d568c9e9a5c5a3953b80b44b5303a9512240e8c 100644 (file)
@@ -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",
index 4691bdeab9b12c2b9834d0201cc8be0fcbf78a3b..c168cae855faa0531024ec154b411a1901544474 100644 (file)
@@ -5,7 +5,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH MPATH_PERSISTENT_RESERVE_IN 3 2016-11-01 "Linux"
+.TH MPATH_PERSISTENT_RESERVE_IN 3 2018-06-15 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
index 55b00b001bc8171b0bc065916051570e9094545a..f20be31314d5b179192ddf566e0264c2a43e2f1f 100644 (file)
@@ -5,7 +5,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH MPATH_PERSISTENT_RESERVE_OUT 3 2016-11-01 "Linux"
+.TH MPATH_PERSISTENT_RESERVE_OUT 3 2018-06-15 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
index 014d9b8360610251c3a1f1289d4326b44150139c..8d3ac53a9065dbf120d4fdf05ee392fcdfea2ad8 100644 (file)
@@ -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' };
index 8af7cd790f930da4145c086ac4bb4b9738aaf2fb..e5249fc1b91e9117a3c894e96d828c4c919a7530 100644 (file)
@@ -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, &params, vecs)) {
                        remove_map(mpp, vecs->pathvec, NULL);
                        continue;
index 7979f208b3d325abe8507685050694d713f35b8d..3d552b33d07066982f60276d204b2ac73a7cb5e6 100644 (file)
@@ -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)
index 15560f8cfc0421810c8e2cf31a204be5f3f203e8..f3fccedd64b2e5f0121fe1ffd8c729b5806ef190 100644 (file)
@@ -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);
index 50d13c08d23b041c7fa16146aa2f3ff76547001e..3b37a92615b4f7f7267f60018e765761af428edd 100644 (file)
@@ -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
index 8a447f7f99a3fc3704a4a382ae1f40660e401de2..3d86ecb3263abae3fcf271d08f5d8d3a0e088b7a 100644 (file)
@@ -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;
index 68a793e7b05349ec84248e2e1b201a14d2172f1f..d7d522c84e9a9ec87d8606a3ade407859fcdb507 100644 (file)
@@ -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);
index 98e3aad15c8031a6a5630e496868f79289748bd2..d4f198977b077b3b865ff8b344c12da39383027b 100644 (file)
@@ -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;
 }
index 49621cb32776aec2efb43fc0cebfc14d99abf76b..7a2ff5899c91aa59fdc75ec627177e5d1e8fef49 100644 (file)
@@ -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;
 }
index 5a713d464912f9a5f135f4c944d7583c5474be30..9e2c1ab0debae66942d3897a244ae04452c4d15a 100644 (file)
@@ -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;
index 645896c6dc8316d40fc4166ffd3bc75e428ec786..5a618767e8dce2a8e509fe6b0aec90e1659ed06a 100644 (file)
@@ -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;
 
index fcc36d78d9f157681d8026f3d2e2b0bb2d81ba92..b5df67bced8fe9dc6e3d9bbf59e5b47ab65a3dcb 100644 (file)
@@ -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"
 
index 894e8c943ca496289263c483db6298464405bda3..b6617902f20ea49ff169bfc0d6f1286358681973 100644 (file)
@@ -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 "", &param_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':
index 7b574592d228d142bc6645e8b778ea2833c42d31..d594422e534534d321871994805299c733fc229c 100644 (file)
@@ -5,7 +5,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH MPATHPERSIST 8 2019-05-27 "Linux"
+.TH MPATHPERSIST 8 2021-11-12 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
index 46b7553f60e7fc4c1c4545565862f9b0ff904d65..116348e2357c820bb9483ad33d703aad7886edcd 100644 (file)
@@ -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),' $< >$@
index 4c7e988563834e10a0b0cdc4bec4ca94e6ee65e5..88149d53b471278f6728de0ed45f703632ce16b7 100644 (file)
@@ -5,7 +5,7 @@
 .\"
 .\" ----------------------------------------------------------------------------
 .
-.TH MULTIPATH 8 2018-10-10 "Linux"
+.TH MULTIPATH 8 2021-11-12 "Linux"
 .
 .
 .\" ----------------------------------------------------------------------------
index acdd1ae6d8c74fca80db2b54bd85bca18d237d90..1fea9d5a2600fc9d344a43380e4174c205e886cc 100644 (file)
@@ -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
deleted file mode 100644 (file)
index f993d99..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-# 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!="add|change", GOTO="end_mpath"
-
-IMPORT{cmdline}="nompath"
-ENV{nompath}=="?*", GOTO="end_mpath"
-IMPORT{cmdline}="multipath"
-ENV{multipath}=="off", GOTO="end_mpath"
-
-ENV{DEVTYPE}!="partition", GOTO="test_dev"
-IMPORT{parent}="DM_MULTIPATH_DEVICE_PATH"
-ENV{DM_MULTIPATH_DEVICE_PATH}=="1", ENV{ID_FS_TYPE}="none", \
-       ENV{SYSTEMD_READY}="0"
-GOTO="end_mpath"
-
-LABEL="test_dev"
-
-ENV{MPATH_SBIN_PATH}="/sbin"
-TEST!="$env{MPATH_SBIN_PATH}/multipath", ENV{MPATH_SBIN_PATH}="/usr/sbin"
-
-# FIND_MULTIPATHS_WAIT_UNTIL is the timeout (in seconds after the
-# epoch).
-IMPORT{db}="FIND_MULTIPATHS_WAIT_UNTIL"
-ENV{.SAVED_FM_WAIT_UNTIL}="$env{FIND_MULTIPATHS_WAIT_UNTIL}"
-
-# multipath -u needs to know if this device has ever been exported
-IMPORT{db}="DM_MULTIPATH_DEVICE_PATH"
-
-# multipath -u sets DM_MULTIPATH_DEVICE_PATH and,
-# if "find_multipaths smart", also FIND_MULTIPATHS_WAIT_UNTIL.
-IMPORT{program}="$env{MPATH_SBIN_PATH}/multipath -u %k"
-
-# case 1: this is definitely multipath
-ENV{DM_MULTIPATH_DEVICE_PATH}=="1", \
-       ENV{ID_FS_TYPE}="mpath_member", ENV{SYSTEMD_READY}="0", \
-       GOTO="stop_wait"
-
-# case 2: this is definitely not multipath, or timeout has expired
-ENV{DM_MULTIPATH_DEVICE_PATH}!="2", \
-       GOTO="stop_wait"
-
-# Code below here is only run in "smart" mode.
-# multipath -u has indicated this is "maybe" multipath.
-
-# Note that DM_MULTIPATH_DEVICE_PATH has the value 2 at this point.
-# This value will never propagate to other rules files, because
-# it will be reset to 1 in the "pretend_multipath" section below.
-
-# This shouldn't happen, just in case.
-ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="?*", GOTO="end_mpath"
-
-# Be careful not to start the timer twice.
-ACTION!="add", GOTO="pretend_mpath"
-ENV{.SAVED_FM_WAIT_UNTIL}=="?*", GOTO="pretend_mpath"
-
-# At this point, we are seeing this path for the first time, and it's "maybe" multipath.
-
-# The actual start command for the timer.
-#
-# The purpose of this command is only to make sure we will receive another
-# uevent eventually. *Any* uevent may cause waiting to finish if it either ends
-# in case 1-3 above, or if it arrives after FIND_MULTIPATHS_WAIT_UNTIL.
-#
-# Note that this will try to activate multipathd if it isn't running yet.
-# If that fails, the unit starts and expires nonetheless. If multipathd
-# startup needs to wait for other services, this wait time will add up with
-# the --on-active timeout.
-#
-# We must trigger an "add" event because LVM2 will only act on those.
-
-RUN+="/usr/bin/systemd-run --unit=cancel-multipath-wait-$kernel --description 'cancel waiting for multipath siblings of $kernel' --no-block --timer-property DefaultDependencies=no --timer-property Conflicts=shutdown.target --timer-property Before=shutdown.target --timer-property Conflicts=initrd-cleanup.service --timer-property Before=initrd-cleanup.service --timer-property AccuracySec=500ms --property DefaultDependencies=no --property Conflicts=shutdown.target --property Before=shutdown.target --property Conflicts=initrd-cleanup.service --property Before=initrd-cleanup.service --on-active=$env{FIND_MULTIPATHS_WAIT_UNTIL} /usr/bin/udevadm trigger --action=add $sys$devpath"
-
-LABEL="pretend_mpath"
-ENV{DM_MULTIPATH_DEVICE_PATH}="1"
-ENV{SYSTEMD_READY}="0"
-GOTO="end_mpath"
-
-LABEL="stop_wait"
-# If timeout hasn't expired but we're not in "maybe" state any more, stop timer
-# Do this only once, and only if the timer has been started before
-IMPORT{db}="FIND_MULTIPATHS_WAIT_CANCELLED"
-ENV{FIND_MULTIPATHS_WAIT_CANCELLED}!="?*", \
-       ENV{FIND_MULTIPATHS_WAIT_UNTIL}=="?*", \
-       ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="0", \
-       ENV{FIND_MULTIPATHS_WAIT_CANCELLED}="1", \
-       RUN+="/usr/bin/systemctl stop cancel-multipath-wait-$kernel.timer"
-
-LABEL="end_mpath"
diff --git a/multipath/multipath.rules.in b/multipath/multipath.rules.in
new file mode 100644 (file)
index 0000000..8d3cf33
--- /dev/null
@@ -0,0 +1,91 @@
+# 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=="@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"
+ENV{nompath}=="?*", GOTO="end_mpath"
+IMPORT{cmdline}="multipath"
+ENV{multipath}=="off", GOTO="end_mpath"
+
+ENV{DEVTYPE}!="partition", GOTO="test_dev"
+IMPORT{parent}="DM_MULTIPATH_DEVICE_PATH"
+ENV{DM_MULTIPATH_DEVICE_PATH}=="1", ENV{ID_FS_TYPE}="none", \
+       ENV{SYSTEMD_READY}="0"
+GOTO="end_mpath"
+
+LABEL="test_dev"
+
+ENV{MPATH_SBIN_PATH}="/sbin"
+TEST!="$env{MPATH_SBIN_PATH}/multipath", ENV{MPATH_SBIN_PATH}="/usr/sbin"
+
+# FIND_MULTIPATHS_WAIT_UNTIL is the timeout (in seconds after the
+# epoch).
+IMPORT{db}="FIND_MULTIPATHS_WAIT_UNTIL"
+ENV{.SAVED_FM_WAIT_UNTIL}="$env{FIND_MULTIPATHS_WAIT_UNTIL}"
+
+# multipath -u needs to know if this device has ever been exported
+IMPORT{db}="DM_MULTIPATH_DEVICE_PATH"
+
+# multipath -u sets DM_MULTIPATH_DEVICE_PATH and,
+# if "find_multipaths smart", also FIND_MULTIPATHS_WAIT_UNTIL.
+IMPORT{program}="$env{MPATH_SBIN_PATH}/multipath -u %k"
+
+# case 1: this is definitely multipath
+ENV{DM_MULTIPATH_DEVICE_PATH}=="1", \
+       ENV{ID_FS_TYPE}="mpath_member", ENV{SYSTEMD_READY}="0", \
+       GOTO="stop_wait"
+
+# case 2: this is definitely not multipath, or timeout has expired
+ENV{DM_MULTIPATH_DEVICE_PATH}!="2", \
+       GOTO="stop_wait"
+
+# Code below here is only run in "smart" mode.
+# multipath -u has indicated this is "maybe" multipath.
+
+# Note that DM_MULTIPATH_DEVICE_PATH has the value 2 at this point.
+# This value will never propagate to other rules files, because
+# it will be reset to 1 in the "pretend_multipath" section below.
+
+# This shouldn't happen, just in case.
+ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="?*", GOTO="end_mpath"
+
+# Be careful not to start the timer twice.
+ACTION!="add", GOTO="pretend_mpath"
+ENV{.SAVED_FM_WAIT_UNTIL}=="?*", GOTO="pretend_mpath"
+
+# At this point, we are seeing this path for the first time, and it's "maybe" multipath.
+
+# The actual start command for the timer.
+#
+# The purpose of this command is only to make sure we will receive another
+# uevent eventually. *Any* uevent may cause waiting to finish if it either ends
+# in case 1-3 above, or if it arrives after FIND_MULTIPATHS_WAIT_UNTIL.
+#
+# Note that this will try to activate multipathd if it isn't running yet.
+# If that fails, the unit starts and expires nonetheless. If multipathd
+# startup needs to wait for other services, this wait time will add up with
+# the --on-active timeout.
+#
+# We must trigger an "add" event because LVM2 will only act on those.
+
+RUN+="/usr/bin/systemd-run --unit=cancel-multipath-wait-$kernel --description 'cancel waiting for multipath siblings of $kernel' --no-block --timer-property DefaultDependencies=no --timer-property Conflicts=shutdown.target --timer-property Before=shutdown.target --timer-property Conflicts=initrd-cleanup.service --timer-property Before=initrd-cleanup.service --timer-property AccuracySec=500ms --property DefaultDependencies=no --property Conflicts=shutdown.target --property Before=shutdown.target --property Conflicts=initrd-cleanup.service --property Before=initrd-cleanup.service --on-active=$env{FIND_MULTIPATHS_WAIT_UNTIL} /usr/bin/udevadm trigger --action=add $sys$devpath"
+
+LABEL="pretend_mpath"
+ENV{DM_MULTIPATH_DEVICE_PATH}="1"
+ENV{SYSTEMD_READY}="0"
+GOTO="end_mpath"
+
+LABEL="stop_wait"
+# If timeout hasn't expired but we're not in "maybe" state any more, stop timer
+# Do this only once, and only if the timer has been started before
+IMPORT{db}="FIND_MULTIPATHS_WAIT_CANCELLED"
+ENV{FIND_MULTIPATHS_WAIT_CANCELLED}!="?*", \
+       ENV{FIND_MULTIPATHS_WAIT_UNTIL}=="?*", \
+       ENV{FIND_MULTIPATHS_WAIT_UNTIL}!="0", \
+       ENV{FIND_MULTIPATHS_WAIT_CANCELLED}="1", \
+       RUN+="/usr/bin/systemctl stop cancel-multipath-wait-$kernel.timer"
+
+LABEL="end_mpath"
diff --git a/multipath/tmpfiles.conf.in b/multipath/tmpfiles.conf.in
new file mode 100644 (file)
index 0000000..21be438
--- /dev/null
@@ -0,0 +1 @@
+d @RUNTIME_DIR@/multipath 0700 root root -
index 0bd76b7c3d4e330ce8d516c7a7547e54867ad7a3..fb87b2800c0041ff61f47cc57f5ebd10463f23fa 100644 (file)
@@ -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));
 }
index 5d25ddb94fa6ba16170d22bde2922f65792b5c79..0c89b7cd976c14518cad963097c8c84580f435fa 100644 (file)
@@ -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;
index cb5bbe2000dfafdfb7e5c09e79e7f5914b69e7ef..c6b79c9d8870024c8b33e92d1ee9bad76be83295 100644 (file)
@@ -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);
index 5b8f647bd49c4ec1db744477a2dbbae46a57fcfb..5f0dd04e0657995c30bba0f4f1f12aa8b0d15fe5 100644 (file)
@@ -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;
index 03b2b9adb81e9e4a36cc48776b50786c82a187cb..a2de301107fa54240b04dd1f489d18434d64c644 100644 (file)
@@ -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);
index b3f7db0da01ce329014fee58ec7a651a7cafa97b..38f2d6a33f0b4cd3a6d71555765acb54d3fda565 100644 (file)
@@ -15,6 +15,7 @@
 #include "uxsock.h"
 #include "util.h"
 #include "cli.h"
+#include "debug.h"
 
 #ifdef USE_LIBEDIT
 #include <editline/readline.h>
 #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
 
index 23cb123d3cd1be07e8f09bfbeb5784a77c7ce00d..02e89fb489e768ca88950d0d9bad77c2861aac94 100644 (file)
@@ -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;
index 109ea75bb3212566cb7ba981bc06a066485d61ee..3a5b161c2daaa70efd4f28476473b1e1caaa460f 100644 (file)
@@ -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 (file)
index 0000000..9e2ad3c
--- /dev/null
@@ -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 (file)
index 0000000..31f978f
--- /dev/null
@@ -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;
+}