cscope.out
kpartx/kpartx
multipath/multipath
+multipath/multipath.rules
+multipath/tmpfiles.conf
multipathd/multipathd
multipathd/multipathc
mpathpersist/mpathpersist
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
pkgconfdir = $(usrlibdir)/pkgconfig
plugindir := $(prefix)/$(LIB)/multipath
configdir := $(prefix)/etc/multipath/conf.d
+runtimedir := /$(RUN)
GZIP_PROG = gzip -9 -c
RM = rm -f
$(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
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
"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:
.\"
.\" ----------------------------------------------------------------------------
.
-.TH KPARTX 8 2016-10-28 "Linux"
+.TH KPARTX 8 2019-04-27 "Linux"
.
.
.\" ----------------------------------------------------------------------------
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) {
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",
.\"
.\" ----------------------------------------------------------------------------
.
-.TH MPATH_PERSISTENT_RESERVE_IN 3 2016-11-01 "Linux"
+.TH MPATH_PERSISTENT_RESERVE_IN 3 2018-06-15 "Linux"
.
.
.\" ----------------------------------------------------------------------------
.\"
.\" ----------------------------------------------------------------------------
.
-.TH MPATH_PERSISTENT_RESERVE_OUT 3 2016-11-01 "Linux"
+.TH MPATH_PERSISTENT_RESERVE_OUT 3 2018-06-15 "Linux"
.
.
.\" ----------------------------------------------------------------------------
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);
}
} 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' };
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
*/
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;
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.
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))
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
*/
}
verify_paths(mpp);
+ if (cmpp)
+ mpp->queue_mode = cmpp->queue_mode;
if (setup_map(mpp, ¶ms, vecs)) {
remove_map(mpp, vecs->pathvec, NULL);
continue;
#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)
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
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);
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"));
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);
free(word);
}
+ mpp->queue_mode = strstr(mpp->features, "queue_mode bio") ?
+ QUEUE_MODE_BIO : QUEUE_MODE_RQ;
/*
* hwhandler
* The new version inherits the previous ones.
*/
-LIBMULTIPATH_16.0.0 {
+LIBMULTIPATH_17.0.0 {
global:
/* symbols referenced by multipath and multipathd */
add_foreign;
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",
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);
#include "strbuf.h"
#include <inttypes.h>
#include <libudev.h>
+#include <ctype.h>
pgpolicyfn *pgpolicies[] = {
NULL,
}
}
+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;
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;
}
#include <unistd.h>
#include <libdevmapper.h>
#include <libudev.h>
+#include <ctype.h>
#include "checkers.h"
#include "vector.h"
[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",
[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 *
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;
{
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;
}
/* 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;
int remove_feature(char **f, const char *o)
{
- int c = 0, d, l;
+ int c = 0, d;
char *e, *p, *n;
const char *q;
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++;
}
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;
* 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);
}
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;
}
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,
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 */
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[];
uint64_t lun;
short h_cmd_per_lun;
short d_queue_depth;
- enum scsi_protocol proto_id;
+ int proto_id;
int transport_id;
};
int needs_paths_uevent;
int ghost_delay;
int ghost_delay_tick;
+ int queue_mode;
uid_t uid;
gid_t gid;
mode_t mode;
}
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;
#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"
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;
case 'Y':
param_alltgpt = 1;
- ++num_prout_param;
break;
case 'Z':
param_aptpl = 1;
- ++num_prout_param;
break;
case 'K':
if (1 != sscanf (optarg, "%" SCNx64 "", ¶m_rk))
ret = MPATH_PR_SYNTAX_ERROR;
goto out;
}
- ++num_prout_param;
break;
case 'S':
ret = MPATH_PR_SYNTAX_ERROR;
goto out;
}
- ++num_prout_param;
break;
case 'P':
ret = MPATH_PR_SYNTAX_ERROR;
goto out;
}
- ++num_prout_param;
break;
case 's':
.\"
.\" ----------------------------------------------------------------------------
.
-.TH MPATHPERSIST 8 2019-05-27 "Linux"
+.TH MPATHPERSIST 8 2021-11-12 "Linux"
.
.
.\" ----------------------------------------------------------------------------
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)
$(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)
$(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),' $< >$@
.\"
.\" ----------------------------------------------------------------------------
.
-.TH MULTIPATH 8 2018-10-10 "Linux"
+.TH MULTIPATH 8 2021-11-12 "Linux"
.
.
.\" ----------------------------------------------------------------------------
.\" 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
.
.
.\" ----------------------------------------------------------------------------
.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)
<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
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
.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)
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
+++ /dev/null
-# 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"
--- /dev/null
+# 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"
--- /dev/null
+d @RUNTIME_DIR@/multipath 0700 root root -
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));
}
{
return handlers;
}
+/* See KEY_INVALID in cli.h */
+#define INVALID_FINGERPRINT ((uint32_t)(0))
static struct key *
alloc_key (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;
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;
}
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;
}
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);
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) {
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;
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))
}
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;
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;
}
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;
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;
}
}
}
}
char *
-get_keyparam (vector v, uint64_t code)
+get_keyparam (vector v, uint8_t code)
{
struct key * kw;
int i;
#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
struct key {
char * str;
char * param;
- uint64_t code;
+ uint8_t code;
int has_param;
};
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);
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)");
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)");
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);
{
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)
{
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);
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)");
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)");
{
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)
{
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);
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;
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;
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;
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;
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;
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;
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;
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;
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);
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;
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;
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);
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);
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;
{
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);
{
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);
{
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);
{
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);
{
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;
{
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;
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);
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);
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;
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);
#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
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
{
int r;
- r = get_cmdvec(c->cmd, &c->cmdvec);
+ r = get_cmdvec(c->cmd, &c->cmdvec, false);
if (r)
return -r;
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;
/* 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);
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;
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;
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;
|| 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)
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
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 $@ $<
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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;
+}