diff --git a/0136-RHBZ-1304687-wait-for-map-add.patch b/0136-RHBZ-1304687-wait-for-map-add.patch index cd9cdee..dbada98 100644 --- a/0136-RHBZ-1304687-wait-for-map-add.patch +++ b/0136-RHBZ-1304687-wait-for-map-add.patch @@ -3,14 +3,14 @@ libmultipath/config.h | 2 libmultipath/configure.c | 4 + libmultipath/defaults.h | 1 - libmultipath/dict.c | 25 +++++++++ + libmultipath/dict.c | 25 ++++++++ libmultipath/structs.h | 2 multipath.conf.defaults | 1 - multipath/multipath.conf.5 | 8 +++ - multipathd/cli_handlers.c | 65 ++++++++++++++++++++---- - multipathd/main.c | 119 +++++++++++++++++++++++++++++++++++++++++++-- + multipath/multipath.conf.5 | 8 ++ + multipathd/cli_handlers.c | 65 ++++++++++++++++++---- + multipathd/main.c | 132 +++++++++++++++++++++++++++++++++++++++++++-- multipathd/main.h | 1 - 11 files changed, 216 insertions(+), 13 deletions(-) + 11 files changed, 229 insertions(+), 13 deletions(-) Index: multipath-tools-130222/libmultipath/configure.c =================================================================== @@ -22,7 +22,7 @@ Index: multipath-tools-130222/libmultipath/configure.c mpp->action = ACT_NOTHING; + else { + mpp->wait_for_udev = 1; -+ mpp->uev_msg_tick = conf->uev_msg_delay; ++ mpp->uev_wait_tick = conf->uev_wait_timeout; + } } dm_setgeometry(mpp); @@ -36,7 +36,7 @@ Index: multipath-tools-130222/libmultipath/structs.h int queuedio; int action; + int wait_for_udev; -+ int uev_msg_tick; ++ int uev_wait_tick; int pgfailback; int failback_tick; int rr_weight; @@ -175,7 +175,7 @@ Index: multipath-tools-130222/libmultipath/config.h int retrigger_delay; int new_bindings_in_boot; + int delayed_reconfig; -+ int uev_msg_delay; ++ int uev_wait_timeout; unsigned int version[3]; char * dev; @@ -293,28 +293,41 @@ Index: multipath-tools-130222/multipathd/main.c retval = reload_map(vecs, pp->mpp, 0); condlog(2, "%s: map %s reloaded (retval %d)", -@@ -1063,6 +1136,20 @@ followover_should_failback(struct path * +@@ -1063,6 +1136,33 @@ followover_should_failback(struct path * } static void -+missing_uev_message_tick(vector mpvec) ++missing_uev_wait_tick(struct vectors *vecs) +{ + struct multipath * mpp; + unsigned int i; ++ int timed_out = 0; + -+ vector_foreach_slot (mpvec, mpp, i) { -+ if (mpp->wait_for_udev && --mpp->uev_msg_tick <= 0) { -+ condlog(0, "%s: startup incomplete. Still waiting on udev", mpp->alias); -+ mpp->uev_msg_tick = conf->uev_msg_delay; ++ vector_foreach_slot (vecs->mpvec, mpp, i) { ++ if (mpp->wait_for_udev && --mpp->uev_wait_tick <= 0) { ++ timed_out = 1; ++ condlog(0, "%s: timeout waiting on creation uevent. enabling reloads", mpp->alias); ++ if (mpp->wait_for_udev > 1 && update_map(mpp, vecs)) { ++ /* update_map removed map */ ++ i--; ++ continue; ++ } ++ mpp->wait_for_udev = 0; + } + } ++ ++ if (timed_out && conf->delayed_reconfig && ++ !need_to_delay_reconfig(vecs)) { ++ condlog(2, "reconfigure (delayed)"); ++ reconfigure(vecs); ++ } +} + +static void defered_failback_tick (vector mpvec) { struct multipath * mpp; -@@ -1316,6 +1403,9 @@ check_path (struct vectors * vecs, struc +@@ -1316,6 +1416,9 @@ check_path (struct vectors * vecs, struc pp->state = newstate; @@ -324,15 +337,15 @@ Index: multipath-tools-130222/multipathd/main.c /* * path prio refreshing */ -@@ -1369,6 +1459,7 @@ checkerloop (void *ap) +@@ -1369,6 +1472,7 @@ checkerloop (void *ap) if (vecs->mpvec) { defered_failback_tick(vecs->mpvec); retry_count_tick(vecs->mpvec); -+ missing_uev_message_tick(vecs->mpvec); ++ missing_uev_wait_tick(vecs); } if (count) count--; -@@ -1465,6 +1556,22 @@ configure (struct vectors * vecs, int st +@@ -1465,6 +1569,22 @@ configure (struct vectors * vecs, int st } int @@ -355,7 +368,7 @@ Index: multipath-tools-130222/multipathd/main.c reconfigure (struct vectors * vecs) { struct config * old = conf; -@@ -1544,12 +1651,18 @@ void +@@ -1544,12 +1664,18 @@ void handle_signals(void) { if (reconfig_sig && running_state == DAEMON_RUNNING) { @@ -396,7 +409,7 @@ Index: multipath-tools-130222/libmultipath/config.c conf->retrigger_tries = DEFAULT_RETRIGGER_TRIES; conf->retrigger_delay = DEFAULT_RETRIGGER_DELAY; conf->new_bindings_in_boot = 0; -+ conf->uev_msg_delay = DEFAULT_UEV_MSG_DELAY; ++ conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; /* * preload default hwtable @@ -408,7 +421,7 @@ Index: multipath-tools-130222/libmultipath/defaults.h #define DEFAULT_DELAY_CHECKS DELAY_CHECKS_OFF #define DEFAULT_RETRIGGER_DELAY 10 #define DEFAULT_RETRIGGER_TRIES 3 -+#define DEFAULT_UEV_MSG_DELAY 30 ++#define DEFAULT_UEV_WAIT_TIMEOUT 30 #define DEFAULT_CHECKINT 5 #define MAX_CHECKINT(a) (a << 2) @@ -420,7 +433,7 @@ Index: multipath-tools-130222/libmultipath/dict.c } static int -+def_uev_msg_delay_handler(vector strvec) ++def_uev_wait_timeout_handler(vector strvec) +{ + char *buff; + @@ -429,9 +442,9 @@ Index: multipath-tools-130222/libmultipath/dict.c + if (!buff) + return 1; + -+ conf->uev_msg_delay = atoi(buff); -+ if (conf->uev_msg_delay <= 0) -+ conf->uev_msg_delay = DEFAULT_UEV_MSG_DELAY; ++ conf->uev_wait_timeout = atoi(buff); ++ if (conf->uev_wait_timeout <= 0) ++ conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; + FREE(buff); + + return 0; @@ -445,9 +458,9 @@ Index: multipath-tools-130222/libmultipath/dict.c } static int -+snprint_def_uev_msg_delay (char * buff, int len, void * data) ++snprint_def_uev_wait_timeout (char * buff, int len, void * data) +{ -+ return snprintf(buff, len, "%i", conf->uev_msg_delay); ++ return snprintf(buff, len, "%i", conf->uev_wait_timeout); +} + +static int @@ -458,7 +471,7 @@ Index: multipath-tools-130222/libmultipath/dict.c install_keyword("delay_wait_checks", &def_delay_wait_checks_handler, &snprint_def_delay_wait_checks); install_keyword("retrigger_tries", &def_retrigger_tries_handler, &snprint_def_retrigger_tries); install_keyword("retrigger_delay", &def_retrigger_delay_handler, &snprint_def_retrigger_delay); -+ install_keyword("missing_uev_msg_delay", &def_uev_msg_delay_handler, &snprint_def_uev_msg_delay); ++ install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout); install_keyword("new_bindings_in_boot", &def_new_bindings_in_boot_handler, &snprint_def_new_bindings_in_boot); __deprecated install_keyword("default_selector", &def_selector_handler, NULL); __deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL); @@ -470,7 +483,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # config_dir "/etc/multipath/conf.d" # delay_watch_checks no # delay_wait_checks no -+# missing_uev_msg_delay 30 ++# missing_uev_wait_timeout 30 #} #blacklist { # devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" @@ -483,12 +496,12 @@ Index: multipath-tools-130222/multipath/multipath.conf.5 checks. Default is .I no +.TP -+.B missing_uev_msg_delay -+Controls how long multipathd will wait, after a new multipath device is created, -+to receive a change event from udev for the device, before printing a warning -+message. This warning message will print every -+.I missing_uev_msg_delay -+seconds until the uevent is received. the default is ++.B missing_uev_wait_timeout ++Controls how many seconds multipathd will wait, after a new multipath device ++is created, to receive a change event from udev for the device, before ++automatically enabling device reloads. Usually multipathd will delay reloads ++on a device until it receives a change uevent from the initial table load. The ++default is +.I 30 . .SH "blacklist section" diff --git a/0137-RHBZ-1280524-clear-chkr-msg.patch b/0137-RHBZ-1280524-clear-chkr-msg.patch index b7cd118..c05e28e 100644 --- a/0137-RHBZ-1280524-clear-chkr-msg.patch +++ b/0137-RHBZ-1280524-clear-chkr-msg.patch @@ -6,7 +6,7 @@ Index: multipath-tools-130222/multipathd/main.c =================================================================== --- multipath-tools-130222.orig/multipathd/main.c +++ multipath-tools-130222/multipathd/main.c -@@ -1257,6 +1257,8 @@ check_path (struct vectors * vecs, struc +@@ -1270,6 +1270,8 @@ check_path (struct vectors * vecs, struc newstate = path_offline(pp); if (newstate == PATH_UP) newstate = get_state(pp, 1); diff --git a/0139-RHBZ-1273173-queue-no-daemon-doc.patch b/0139-RHBZ-1273173-queue-no-daemon-doc.patch index 4531c1d..1488b14 100644 --- a/0139-RHBZ-1273173-queue-no-daemon-doc.patch +++ b/0139-RHBZ-1273173-queue-no-daemon-doc.patch @@ -1,7 +1,7 @@ --- - multipath.conf.defaults | 57 +++++++++++++++++++++++++++++++++++++-------- + multipath.conf.defaults | 56 +++++++++++++++++++++++++++++++++++++-------- multipath/multipath.conf.5 | 2 - - 2 files changed, 48 insertions(+), 11 deletions(-) + 2 files changed, 48 insertions(+), 10 deletions(-) Index: multipath-tools-130222/multipath/multipath.conf.5 =================================================================== @@ -34,7 +34,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # path_selector "service-time 0" # path_grouping_policy "failover" # uid_attribute "ID_SERIAL" -@@ -12,28 +15,35 @@ +@@ -12,28 +15,36 @@ # features "0" # path_checker "directio" # alias_prefix "mpath" @@ -62,9 +62,9 @@ Index: multipath-tools-130222/multipath.conf.defaults # config_dir "/etc/multipath/conf.d" # delay_watch_checks no # delay_wait_checks no --# missing_uev_msg_delay 30 +# retrigger_tries 3 +# retrigger_delay 10 + # missing_uev_wait_timeout 30 +# new_bindings_in_boot no #} #blacklist { @@ -74,7 +74,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # devnode "^dcssblk[0-9]*" # device { # vendor "DGC" -@@ -68,7 +78,7 @@ +@@ -68,7 +79,7 @@ # product "Universal Xport" # } # device { @@ -83,7 +83,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # product "Universal Xport" # } #} -@@ -666,7 +676,7 @@ +@@ -666,7 +677,7 @@ # features "2 pg_init_retries 50" # hardware_handler "1 rdac" # prio "rdac" @@ -92,7 +92,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # rr_weight "uniform" # no_path_retry 15 # } -@@ -679,7 +689,7 @@ +@@ -679,7 +690,7 @@ # features "2 pg_init_retries 50" # hardware_handler "1 rdac" # prio "rdac" @@ -101,7 +101,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # rr_weight "uniform" # no_path_retry 15 # } -@@ -696,6 +706,7 @@ +@@ -696,6 +707,7 @@ # rr_min_io 128 # flush_on_last_del "yes" # dev_loss_tmo "infinity" @@ -109,7 +109,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # retain_attached_hw_handler yes # detect_prio yes # } -@@ -876,7 +887,7 @@ +@@ -876,7 +888,7 @@ # rr_min_io_rq 1 # } # device { @@ -118,7 +118,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # product "INF-01-00" # product_blacklist "Universal Xport" # path_grouping_policy "group_by_prio" -@@ -886,7 +897,9 @@ +@@ -886,7 +898,9 @@ # prio "rdac" # failback immediate # rr_weight "uniform" @@ -129,7 +129,7 @@ Index: multipath-tools-130222/multipath.conf.defaults # } # device { # vendor "STK" -@@ -925,6 +938,30 @@ +@@ -925,6 +939,30 @@ # rr_weight "uniform" # no_path_retry "queue" # } diff --git a/0150-RHBZ-1253913-fix-startup-msg.patch b/0150-RHBZ-1253913-fix-startup-msg.patch index 7dc5125..8a020ce 100644 --- a/0150-RHBZ-1253913-fix-startup-msg.patch +++ b/0150-RHBZ-1253913-fix-startup-msg.patch @@ -14,7 +14,7 @@ Index: multipath-tools-130222/multipathd/main.c static sem_t exit_sem; /* -@@ -1705,6 +1706,12 @@ sigusr2 (int sig) +@@ -1718,6 +1719,12 @@ sigusr2 (int sig) } static void @@ -27,7 +27,7 @@ Index: multipath-tools-130222/multipathd/main.c signal_init(void) { sigset_t set; -@@ -1807,6 +1814,9 @@ child (void * param) +@@ -1820,6 +1827,9 @@ child (void * param) } running_state = DAEMON_START; @@ -37,7 +37,7 @@ Index: multipath-tools-130222/multipathd/main.c condlog(2, "--------start up--------"); condlog(2, "read " DEFAULT_CONFIGFILE); -@@ -1898,8 +1908,6 @@ child (void * param) +@@ -1911,8 +1921,6 @@ child (void * param) } pthread_attr_destroy(&misc_attr); @@ -46,7 +46,7 @@ Index: multipath-tools-130222/multipathd/main.c update_timestamp(1); /* Ignore errors, we can live without */ -@@ -1979,7 +1987,10 @@ daemonize(void) +@@ -1992,7 +2000,10 @@ daemonize(void) { int pid; int dev_null_fd; @@ -57,7 +57,7 @@ Index: multipath-tools-130222/multipathd/main.c if( (pid = fork()) < 0){ fprintf(stderr, "Failed first fork : %s\n", strerror(errno)); return -1; -@@ -1987,10 +1998,13 @@ daemonize(void) +@@ -2000,10 +2011,13 @@ daemonize(void) else if (pid != 0) return pid; @@ -72,7 +72,7 @@ Index: multipath-tools-130222/multipathd/main.c else if (pid != 0) _exit(0); -@@ -2001,30 +2015,34 @@ daemonize(void) +@@ -2014,30 +2028,34 @@ daemonize(void) if (dev_null_fd < 0){ fprintf(stderr, "cannot open /dev/null for input & output : %s\n", strerror(errno)); @@ -111,7 +111,7 @@ Index: multipath-tools-130222/multipathd/main.c } int -@@ -2103,10 +2121,12 @@ main (int argc, char *argv[]) +@@ -2116,10 +2134,12 @@ main (int argc, char *argv[]) if (err < 0) /* error */ exit(1); diff --git a/0154-UPBZ-1291406-disable-reinstate.patch b/0154-UPBZ-1291406-disable-reinstate.patch index a652e5c..2da4e3f 100644 --- a/0154-UPBZ-1291406-disable-reinstate.patch +++ b/0154-UPBZ-1291406-disable-reinstate.patch @@ -88,7 +88,7 @@ Index: multipath-tools-130222/multipathd/main.c /* * libcheckers -@@ -1235,6 +1236,7 @@ check_path (struct vectors * vecs, struc +@@ -1248,6 +1249,7 @@ check_path (struct vectors * vecs, struc int newstate; int new_path_up = 0; int chkr_new_path_up = 0; @@ -96,7 +96,7 @@ Index: multipath-tools-130222/multipathd/main.c int oldchkrstate = pp->chkrstate; if (!pp->mpp && (pp->missing_udev_info != INFO_MISSING || -@@ -1299,6 +1301,16 @@ check_path (struct vectors * vecs, struc +@@ -1312,6 +1314,16 @@ check_path (struct vectors * vecs, struc pp->wait_checks = 0; } @@ -113,7 +113,7 @@ Index: multipath-tools-130222/multipathd/main.c pp->chkrstate = newstate; if (newstate != pp->state) { int oldstate = pp->state; -@@ -1354,15 +1366,17 @@ check_path (struct vectors * vecs, struc +@@ -1367,15 +1379,17 @@ check_path (struct vectors * vecs, struc /* * reinstate this path */ @@ -140,7 +140,7 @@ Index: multipath-tools-130222/multipathd/main.c } new_path_up = 1; -@@ -1377,8 +1391,9 @@ check_path (struct vectors * vecs, struc +@@ -1390,8 +1404,9 @@ check_path (struct vectors * vecs, struc enable_group(pp); } else if (newstate == PATH_UP || newstate == PATH_GHOST) { diff --git a/0156-UPBZ-1313324-dont-fail-discovery.patch b/0156-UPBZ-1313324-dont-fail-discovery.patch index 36262f7..7e33cfe 100644 --- a/0156-UPBZ-1313324-dont-fail-discovery.patch +++ b/0156-UPBZ-1313324-dont-fail-discovery.patch @@ -214,7 +214,7 @@ Index: multipath-tools-130222/multipathd/main.c =================================================================== --- multipath-tools-130222.orig/multipathd/main.c +++ multipath-tools-130222/multipathd/main.c -@@ -1502,7 +1502,7 @@ configure (struct vectors * vecs, int st +@@ -1515,7 +1515,7 @@ configure (struct vectors * vecs, int st struct multipath * mpp; struct path * pp; vector mpvec; @@ -223,7 +223,7 @@ Index: multipath-tools-130222/multipathd/main.c if (!vecs->pathvec && !(vecs->pathvec = vector_alloc())) return 1; -@@ -1516,7 +1516,9 @@ configure (struct vectors * vecs, int st +@@ -1529,7 +1529,9 @@ configure (struct vectors * vecs, int st /* * probe for current path (from sysfs) and map (from dm) sets */ diff --git a/0161-RHBZ-1311659-no-kpartx.patch b/0161-RHBZ-1311659-no-kpartx.patch index 0bce7f6..8656548 100644 --- a/0161-RHBZ-1311659-no-kpartx.patch +++ b/0161-RHBZ-1311659-no-kpartx.patch @@ -37,7 +37,7 @@ Index: multipath-tools-130222/libmultipath/config.c @@ -677,6 +679,7 @@ load_config (char * file, struct udev *u conf->retrigger_delay = DEFAULT_RETRIGGER_DELAY; conf->new_bindings_in_boot = 0; - conf->uev_msg_delay = DEFAULT_UEV_MSG_DELAY; + conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; + conf->skip_kpartx = DEFAULT_SKIP_KPARTX; /* @@ -65,7 +65,7 @@ Index: multipath-tools-130222/libmultipath/config.h @@ -143,6 +145,7 @@ struct config { int new_bindings_in_boot; int delayed_reconfig; - int uev_msg_delay; + int uev_wait_timeout; + int skip_kpartx; unsigned int version[3]; @@ -138,7 +138,7 @@ Index: multipath-tools-130222/libmultipath/defaults.h @@ -24,6 +24,7 @@ #define DEFAULT_RETRIGGER_DELAY 10 #define DEFAULT_RETRIGGER_TRIES 3 - #define DEFAULT_UEV_MSG_DELAY 30 + #define DEFAULT_UEV_WAIT_TIMEOUT 30 +#define DEFAULT_SKIP_KPARTX SKIP_KPARTX_OFF #define DEFAULT_CHECKINT 5 @@ -585,9 +585,9 @@ Index: multipath-tools-130222/multipath/multipath.conf.5 =================================================================== --- multipath-tools-130222.orig/multipath/multipath.conf.5 +++ multipath-tools-130222/multipath/multipath.conf.5 -@@ -505,6 +505,12 @@ message. This warning message will print - .I missing_uev_msg_delay - seconds until the uevent is received. the default is +@@ -505,6 +505,12 @@ automatically enabling device reloads. U + on a device until it receives a change uevent from the initial table load. The + default is .I 30 +.TP +.B skip_kpartx diff --git a/0167-RHBZ-1335176-fix-show-cmds.patch b/0167-RHBZ-1335176-fix-show-cmds.patch index 55154b6..cbc5817 100644 --- a/0167-RHBZ-1335176-fix-show-cmds.patch +++ b/0167-RHBZ-1335176-fix-show-cmds.patch @@ -128,7 +128,7 @@ Index: multipath-tools-130222/multipathd/main.c dm_queue_if_no_path(mpp->alias, 0); } if (!flush_map(mpp, vecs, 1)) { -@@ -1184,6 +1185,7 @@ retry_count_tick(vector mpvec) +@@ -1197,6 +1198,7 @@ retry_count_tick(vector mpvec) mpp->stat_total_queueing_time++; condlog(4, "%s: Retrying.. No active path", mpp->alias); if(--mpp->retry_tick == 0) { diff --git a/0173-RH-update-man-page.patch b/0173-RH-update-man-page.patch new file mode 100644 index 0000000..80c6dbf --- /dev/null +++ b/0173-RH-update-man-page.patch @@ -0,0 +1,99 @@ +--- + multipath/multipath.conf.5 | 56 ++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 51 insertions(+), 5 deletions(-) + +Index: multipath-tools-130222/multipath/multipath.conf.5 +=================================================================== +--- multipath-tools-130222.orig/multipath/multipath.conf.5 ++++ multipath-tools-130222/multipath/multipath.conf.5 +@@ -240,18 +240,21 @@ Specify any device-mapper features to be + .I num list + where + .I num +-is the number of features in ++is the number, between 0 and 6, of features in + .I list. +-Possible values for the feature list are ++Possible values for the feature list are: + .RS + .TP 12 +-.B queue_if_no_path ++.I queue_if_no_path + Queue IO if no path is active; identical to the + .I no_path_retry + keyword. + .TP +-.B no_partitions +-Disable automatic partitions generation via kpartx. ++.I pg_init_retries ++Number of times to retry pg_init, it must be between 1 and 50. ++.TP ++.I pg_init_delay_msecs ++Number of msecs before pg_init retry, it must be between 0 and 60000. + .RE + .TP + .B path_checker +@@ -511,6 +514,45 @@ If set to + .I yes + , kpartx will not automatically create partitions on the device. The default is + .I no ++.TP ++.B ignore_new_boot_devs ++If set to ++.I yes ++, multipath will never attempt to create a multipath device whose wwid is not ++listed in /etc/multipath/wwids, while running in the initramfs. This keeps ++multipath from adding new devices during the initramfs portion of bootup. The ++default is ++.I no ++.TP ++.B retrigger_tries ++This sets how many times multipathd will reissue change uevents on block ++devices that are not blacklisted, but have no wwid set by udev. Multipath ++assumes that any devices that should not report a wwid are blacklisted. This ++means that if a non-blacklisted device has no wwid, it is likely that udev ++timed out while processing it. Multipathd will wait for a while, and then ++reissue a change uevent to give udev another chance to set the wwid. The ++default is ++.I 3 ++.TP ++.B retrigger_delay ++This sets how long multipathd should wait, after receiving a uevent for a ++non-blacklisted device without a wwid set by udev, before reissuing a ++change uevent. The goal of this delay is to give udev a chance to finish ++processing its current batch of uevents before sending more, to hopefully ++avoid it timing out. The default is ++.I 10 ++.TP ++.B new_bindings_in_boot ++If set to ++.I yes ++, multipath will allow new user_friendly_names bindings to be created while ++running in the initramfs. Otherwise, multipath will not create ++user_friendly_names bindings while running in the initramfs. Instead, it will ++use the WWID for the name of a device that was configured to use ++user_friendly_names. When multipathd is restarted later in boot on the ++regular filesystem, the device will be renamed to a user_friendly_name. The ++default is ++.I no + . + .SH "blacklist section" + The +@@ -603,6 +645,8 @@ section: + .TP + .B flush_on_last_del + .TP ++.B user_friendly_names ++.TP + .B no_path_retry + .TP + .B rr_min_io +@@ -697,6 +741,8 @@ section: + .TP + .B no_path_retry + .TP ++.B user_friendly_names ++.TP + .B rr_min_io + .TP + .B rr_min_io_rq diff --git a/0173-RHBZ-1239173-dont-set-flag.patch b/0173-RHBZ-1239173-dont-set-flag.patch new file mode 100644 index 0000000..269edc8 --- /dev/null +++ b/0173-RHBZ-1239173-dont-set-flag.patch @@ -0,0 +1,38 @@ +--- + libmultipath/configure.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -257,7 +257,7 @@ extern int + setup_map (struct multipath * mpp, char * params, int params_size) + { + struct pathgroup * pgp; +- int i; ++ int i, old_nr_active; + + /* + * don't bother if devmap size is unknown +@@ -311,8 +311,12 @@ setup_map (struct multipath * mpp, char + if (mpp->pgpolicyfn && mpp->pgpolicyfn(mpp)) + return 1; + ++ old_nr_active = mpp->nr_active; + mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST); + ++ if (mpp->nr_active && !old_nr_active) ++ mpp->force_udev_reload = 1; ++ + /* + * ponders each path group and determine highest prio pg + * to switch over (default to first) +@@ -445,7 +449,6 @@ select_action (struct multipath * mpp, v + mpp->alias); + return; + } +- mpp->force_udev_reload = !pathcount(mpp, PATH_WILD); + if (cmpp->size != mpp->size) { + mpp->force_udev_reload = 1; + mpp->action = ACT_RESIZE; diff --git a/0174-RHBZ-1362396-modprobe.patch b/0174-RHBZ-1362396-modprobe.patch new file mode 100644 index 0000000..509510c --- /dev/null +++ b/0174-RHBZ-1362396-modprobe.patch @@ -0,0 +1,16 @@ +--- + multipathd/multipathd.init.redhat | 1 + + 1 file changed, 1 insertion(+) + +Index: multipath-tools-130222/multipathd/multipathd.init.redhat +=================================================================== +--- multipath-tools-130222.orig/multipathd/multipathd.init.redhat ++++ multipath-tools-130222/multipathd/multipathd.init.redhat +@@ -67,6 +67,7 @@ popd > /dev/null + start() { + test -x $DAEMON || exit 5 + echo -n $"Starting $prog daemon: " ++ modprobe dm-multipath >/dev/null 2>&1 + daemon $DAEMON + RETVAL=$? + [ $RETVAL -eq 0 ] && touch $lockdir/$prog diff --git a/0175-RHBZ-1357382-ordering.patch b/0175-RHBZ-1357382-ordering.patch new file mode 100644 index 0000000..c778596 --- /dev/null +++ b/0175-RHBZ-1357382-ordering.patch @@ -0,0 +1,17 @@ +--- + multipathd/multipathd.service | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: multipath-tools-130222/multipathd/multipathd.service +=================================================================== +--- multipath-tools-130222.orig/multipathd/multipathd.service ++++ multipath-tools-130222/multipathd/multipathd.service +@@ -2,7 +2,7 @@ + Description=Device-Mapper Multipath Device Controller + Wants=blk-availability.service + Before=iscsi.service iscsid.service lvm2-activation-early.service +-After=syslog.target ++After=syslog.target systemd-udev-trigger.service + ConditionPathExists=/etc/multipath.conf + ConditionKernelCommandLine=!nompath + DefaultDependencies=no diff --git a/0176-RHBZ-1363830-fix-rename.patch b/0176-RHBZ-1363830-fix-rename.patch new file mode 100644 index 0000000..428b2f1 --- /dev/null +++ b/0176-RHBZ-1363830-fix-rename.patch @@ -0,0 +1,17 @@ +--- + libmultipath/devmapper.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/devmapper.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/devmapper.c ++++ multipath-tools-130222/libmultipath/devmapper.c +@@ -1387,7 +1387,7 @@ dm_rename (const char * old, char * new, + { + int r = 0; + struct dm_task *dmt; +- uint32_t cookie; ++ uint32_t cookie = 0; + uint16_t udev_flags = ((conf->daemon)? DM_UDEV_DISABLE_LIBRARY_FALLBACK : 0) | ((skip_kpartx == SKIP_KPARTX_ON)? MPATH_UDEV_NO_KPARTX_FLAG : 0); + + if (dm_rename_partmaps(old, new)) diff --git a/0177-libmultipath-correctly-initialize-pp-sg_id.patch b/0177-libmultipath-correctly-initialize-pp-sg_id.patch new file mode 100644 index 0000000..1c927be --- /dev/null +++ b/0177-libmultipath-correctly-initialize-pp-sg_id.patch @@ -0,0 +1,38 @@ +From e2b87038125c79089e0bd4c6fd905667c5108740 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 9 Aug 2016 13:36:04 -0500 +Subject: [PATCH 01/11] libmultipath: correctly initialize pp->sg_id + +For BZ 1348372 from upstream: + +commit b4d9ca8dc8bbfbd3782bf4cf2cb1a440685ccd07 +Author: Hannes Reinecke +Date: Wed Nov 11 13:38:57 2015 +0100 + + libmultipath: correctly initialize pp->sg_id + + The default SCSI protocol is 'SCSI_PROTOCOL_UNSPEC'; + '0' is SCSI_PROTOCOL_FCP. + + Signed-off-by: Hannes Reinecke + +Signed-off-by: Mike Christie +--- + libmultipath/structs.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libmultipath/structs.c b/libmultipath/structs.c +index 30d247d..26a6a3b 100644 +--- a/libmultipath/structs.c ++++ b/libmultipath/structs.c +@@ -94,6 +94,7 @@ alloc_path (void) + pp->sg_id.channel = -1; + pp->sg_id.scsi_id = -1; + pp->sg_id.lun = -1; ++ pp->sg_id.proto_id = SCSI_PROTOCOL_UNSPEC; + pp->fd = -1; + pp->priority = PRIO_UNDEF; + } +-- +1.8.3.1 + diff --git a/0178-libmultipath-add-rbd-discovery.patch b/0178-libmultipath-add-rbd-discovery.patch new file mode 100644 index 0000000..6930e46 --- /dev/null +++ b/0178-libmultipath-add-rbd-discovery.patch @@ -0,0 +1,219 @@ +From 2fc494b81157059e0be66022f6a2110f1ce179c3 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 9 Aug 2016 13:44:10 -0500 +Subject: [PATCH 02/11] libmultipath: add rbd discovery + +For BZ 1348372 from upstream commit: + +Commit 152f3f803ee922075e8b25027eb9dc5699f1aefa +Author: Mike Christie +Date: Mon Aug 8 07:01:47 2016 -0500 + + libmultipath: add rbd discovery + + rbd is a block device interface for Ceph. It does not support + any SCSI commands, so this patch adds bus detection and virtual + vendor/product pathinfo. + +-------- + +Porting notes: + +get_uid() chunk does not match upstream due to rhel not having +the get uid callout code and sysfs uid detection code. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers.h | 1 + + libmultipath/discovery.c | 116 ++++++++++++++++++++++++++++++++++++++++------- + libmultipath/structs.h | 1 + + 3 files changed, 101 insertions(+), 17 deletions(-) + +diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h +index f6fe326..735bb25 100644 +--- a/libmultipath/checkers.h ++++ b/libmultipath/checkers.h +@@ -75,6 +75,7 @@ enum path_check_state { + #define EMC_CLARIION "emc_clariion" + #define READSECTOR0 "readsector0" + #define CCISS_TUR "cciss_tur" ++#define RBD "rbd" + + #define DEFAULT_CHECKER DIRECTIO + +diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c +index 7a8282b..1b9f390 100644 +--- a/libmultipath/discovery.c ++++ b/libmultipath/discovery.c +@@ -781,6 +781,21 @@ scsi_sysfs_pathinfo (struct path * pp) + } + + static int ++rbd_sysfs_pathinfo (struct path * pp) ++{ ++ sprintf(pp->vendor_id, "Ceph"); ++ sprintf(pp->product_id, "RBD"); ++ ++ condlog(3, "%s: vendor = %s product = %s", pp->dev, pp->vendor_id, ++ pp->product_id); ++ /* ++ * set the hwe configlet pointer ++ */ ++ pp->hwe = find_hwe(conf->hwtable, pp->vendor_id, pp->product_id, NULL); ++ return 0; ++} ++ ++static int + ccw_sysfs_pathinfo (struct path * pp) + { + struct udev_device *parent; +@@ -974,6 +989,8 @@ sysfs_pathinfo(struct path * pp) + pp->bus = SYSFS_BUS_CCW; + if (!strncmp(pp->dev,"sd", 2)) + pp->bus = SYSFS_BUS_SCSI; ++ if (!strncmp(pp->dev,"rbd", 3)) ++ pp->bus = SYSFS_BUS_RBD; + + if (pp->bus == SYSFS_BUS_UNDEF) + return 0; +@@ -986,6 +1003,9 @@ sysfs_pathinfo(struct path * pp) + } else if (pp->bus == SYSFS_BUS_CCISS) { + if (cciss_sysfs_pathinfo(pp)) + return 1; ++ } else if (pp->bus == SYSFS_BUS_RBD) { ++ if (rbd_sysfs_pathinfo(pp)) ++ return 1; + } + return 0; + } +@@ -1087,10 +1107,60 @@ get_prio (struct path * pp) + } + + static int ++get_rbd_uid(struct path * pp) ++{ ++ struct udev_device *rbd_bus_dev; ++ int ret, rbd_bus_id; ++ const char *pool, *image, *snap; ++ char sysfs_path[PATH_SIZE]; ++ uint64_t snap_id, max_snap_id = -3; ++ ++ ret = sscanf(pp->dev, "rbd%d", &rbd_bus_id); ++ if (ret != 1) ++ return -EINVAL; ++ ++ snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/rbd/devices/%d", ++ rbd_bus_id); ++ rbd_bus_dev = udev_device_new_from_syspath(conf->udev, sysfs_path); ++ if (!rbd_bus_dev) ++ return -ENODEV; ++ ++ ret = -EINVAL; ++ pool = udev_device_get_sysattr_value(rbd_bus_dev, "pool_id"); ++ if (!pool) ++ goto free_dev; ++ ++ image = udev_device_get_sysattr_value(rbd_bus_dev, "image_id"); ++ if (!image) ++ goto free_dev; ++ ++ snap = udev_device_get_sysattr_value(rbd_bus_dev, "snap_id"); ++ if (!snap) ++ goto free_dev; ++ snap_id = strtoull(snap, NULL, 19); ++ if (snap_id >= max_snap_id) ++ ret = snprintf(pp->wwid, WWID_SIZE, "%s-%s", pool, image); ++ else ++ ret = snprintf(pp->wwid, WWID_SIZE, "%s-%s-%s", pool, ++ image, snap); ++ if (ret < WWID_SIZE) { ++ ret = 0; ++ } else { ++ condlog(0, "%s: wwid overflow", pp->dev); ++ ret = -EOVERFLOW; ++ } ++ ++free_dev: ++ udev_device_unref(rbd_bus_dev); ++ return ret; ++} ++ ++static int + get_uid (struct path * pp) + { + char *c; + const char *value; ++ int ret; + + if (!pp->uid_attribute) + select_getuid(pp); +@@ -1101,25 +1171,37 @@ get_uid (struct path * pp) + } + + memset(pp->wwid, 0, WWID_SIZE); +- value = udev_device_get_property_value(pp->udev, pp->uid_attribute); +- if ((!value || strlen(value) == 0) && conf->cmd == CMD_VALID_PATH) +- value = getenv(pp->uid_attribute); +- if (value && strlen(value)) { +- size_t len = WWID_SIZE; +- +- if (strlen(value) + 1 > WWID_SIZE) { +- condlog(0, "%s: wwid overflow", pp->dev); +- } else { +- len = strlen(value); ++ if (pp->bus == SYSFS_BUS_RBD) { ++ ret = get_rbd_uid(pp); ++ if (ret) { ++ condlog(1, "%s: failed to get sysfs uid: %s", ++ pp->dev, strerror(-ret)); ++ pp->missing_udev_info = INFO_MISSING; ++ pp->tick = conf->retrigger_delay; + } +- strncpy(pp->wwid, value, len); +- pp->missing_udev_info = INFO_OK; +- pp->tick = 0; + } else { +- condlog(3, "%s: no %s attribute", pp->dev, +- pp->uid_attribute); +- pp->missing_udev_info = INFO_MISSING; +- pp->tick = conf->retrigger_delay; ++ value = udev_device_get_property_value(pp->udev, ++ pp->uid_attribute); ++ if ((!value || strlen(value) == 0) && ++ conf->cmd == CMD_VALID_PATH) ++ value = getenv(pp->uid_attribute); ++ if (value && strlen(value)) { ++ size_t len = WWID_SIZE; ++ ++ if (strlen(value) + 1 > WWID_SIZE) { ++ condlog(0, "%s: wwid overflow", pp->dev); ++ } else { ++ len = strlen(value); ++ } ++ strncpy(pp->wwid, value, len); ++ pp->missing_udev_info = INFO_OK; ++ pp->tick = 0; ++ } else { ++ condlog(3, "%s: no %s attribute", pp->dev, ++ pp->uid_attribute); ++ pp->missing_udev_info = INFO_MISSING; ++ pp->tick = conf->retrigger_delay; ++ } + } + + /* Strip any trailing blanks */ +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index b5b4567..e566462 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -52,6 +52,7 @@ enum sysfs_buses { + SYSFS_BUS_IDE, + SYSFS_BUS_CCW, + SYSFS_BUS_CCISS, ++ SYSFS_BUS_RBD, + }; + + enum pathstates { +-- +1.8.3.1 + diff --git a/0179-multipath-tools-add-checker-callout-to-repair-path.patch b/0179-multipath-tools-add-checker-callout-to-repair-path.patch new file mode 100644 index 0000000..8b48f7d --- /dev/null +++ b/0179-multipath-tools-add-checker-callout-to-repair-path.patch @@ -0,0 +1,250 @@ +From 1073621a7a63ca4e9a00baedd8edc51e5381eb95 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 9 Aug 2016 13:46:11 -0500 +Subject: [PATCH 03/11] multipath-tools: add checker callout to repair path + +For BZ 1348372 from upstream commit: + +commit 015f87b16a7797a17afd514aec46e65c2a1a2f73 +Author: Mike Christie +Date: Mon Aug 8 07:01:48 2016 -0500 + + multipath-tools: add checker callout to repair path + + This patch adds a callback which can be used to repair a path + if check() has determined it is in the PATH_DOWN state. + + The next patch that adds rbd checker support which will use this to + handle the case where a rbd device is blacklisted. + +-------- + +Porting notes: +checkerloop difference due to different path tracking. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers.c | 23 +++++++++++++++++++++++ + libmultipath/checkers.h | 4 ++++ + libmultipath/checkers/cciss_tur.c | 5 +++++ + libmultipath/checkers/directio.c | 5 +++++ + libmultipath/checkers/emc_clariion.c | 5 +++++ + libmultipath/checkers/hp_sw.c | 5 +++++ + libmultipath/checkers/rdac.c | 5 +++++ + libmultipath/checkers/readsector0.c | 5 +++++ + libmultipath/checkers/tur.c | 5 +++++ + multipathd/main.c | 9 +++++++++ + 10 files changed, 71 insertions(+) + +diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c +index 7f9db2d..fa7d8b7 100644 +--- a/libmultipath/checkers.c ++++ b/libmultipath/checkers.c +@@ -137,6 +137,14 @@ struct checker * add_checker (char * name) + if (!c->free) + goto out; + ++ c->repair = (void (*)(struct checker *)) dlsym(c->handle, ++ "libcheck_repair"); ++ errstr = dlerror(); ++ if (errstr != NULL) ++ condlog(0, "A dynamic linking error occurred: (%s)", errstr); ++ if (!c->repair) ++ goto out; ++ + c->fd = 0; + c->sync = 1; + list_add(&c->node, &checkers); +@@ -202,6 +210,20 @@ void checker_put (struct checker * dst) + free_checker(src); + } + ++void checker_repair (struct checker * c) ++{ ++ if (!c) ++ return; ++ ++ c->message[0] = '\0'; ++ if (c->disable) { ++ MSG(c, "checker disabled"); ++ return; ++ } ++ ++ c->repair(c); ++} ++ + int checker_check (struct checker * c) + { + int r; +@@ -266,6 +288,7 @@ void checker_get (struct checker * dst, char * name) + dst->sync = src->sync; + strncpy(dst->name, src->name, CHECKER_NAME_LEN); + strncpy(dst->message, src->message, CHECKER_MSG_LEN); ++ dst->repair = src->repair; + dst->check = src->check; + dst->init = src->init; + dst->free = src->free; +diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h +index 735bb25..ad3b9e4 100644 +--- a/libmultipath/checkers.h ++++ b/libmultipath/checkers.h +@@ -106,6 +106,9 @@ struct checker { + multipath-wide. Use MALLOC if + you want to stuff data in. */ + int (*check)(struct checker *); ++ void (*repair)(struct checker *); /* called if check returns ++ PATH_DOWN to bring path into ++ usable state */ + int (*init)(struct checker *); /* to allocate the context */ + void (*free)(struct checker *); /* to free the context */ + }; +@@ -125,6 +128,7 @@ void checker_set_async (struct checker *); + void checker_set_fd (struct checker *, int); + void checker_enable (struct checker *); + void checker_disable (struct checker *); ++void checker_repair (struct checker *); + int checker_check (struct checker *); + int checker_selected (struct checker *); + char * checker_name (struct checker *); +diff --git a/libmultipath/checkers/cciss_tur.c b/libmultipath/checkers/cciss_tur.c +index 4c26901..7e4eb81 100644 +--- a/libmultipath/checkers/cciss_tur.c ++++ b/libmultipath/checkers/cciss_tur.c +@@ -63,6 +63,11 @@ void libcheck_free (struct checker * c) + return; + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + extern int + libcheck_check (struct checker * c) + { +diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c +index 46fe6a7..1a997ed 100644 +--- a/libmultipath/checkers/directio.c ++++ b/libmultipath/checkers/directio.c +@@ -116,6 +116,11 @@ void libcheck_free (struct checker * c) + free(ct); + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + static int + check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) + { +diff --git a/libmultipath/checkers/emc_clariion.c b/libmultipath/checkers/emc_clariion.c +index b42d267..43b5025 100644 +--- a/libmultipath/checkers/emc_clariion.c ++++ b/libmultipath/checkers/emc_clariion.c +@@ -90,6 +90,11 @@ void libcheck_free (struct checker * c) + free(c->context); + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + int libcheck_check (struct checker * c) + { + unsigned char sense_buffer[128] = { 0, }; +diff --git a/libmultipath/checkers/hp_sw.c b/libmultipath/checkers/hp_sw.c +index b50ac0c..857ac5e 100644 +--- a/libmultipath/checkers/hp_sw.c ++++ b/libmultipath/checkers/hp_sw.c +@@ -44,6 +44,11 @@ void libcheck_free (struct checker * c) + return; + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + static int + do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, + void *resp, int mx_resp_len, int noisy, unsigned int timeout) +diff --git a/libmultipath/checkers/rdac.c b/libmultipath/checkers/rdac.c +index f0e0af3..5469e61 100644 +--- a/libmultipath/checkers/rdac.c ++++ b/libmultipath/checkers/rdac.c +@@ -139,6 +139,11 @@ void libcheck_free (struct checker * c) + return; + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + static int + do_inq(int sg_fd, unsigned int pg_op, void *resp, int mx_resp_len, + unsigned int timeout) +diff --git a/libmultipath/checkers/readsector0.c b/libmultipath/checkers/readsector0.c +index 0550fb6..b3ed1f3 100644 +--- a/libmultipath/checkers/readsector0.c ++++ b/libmultipath/checkers/readsector0.c +@@ -23,6 +23,11 @@ void libcheck_free (struct checker * c) + return; + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + int libcheck_check (struct checker * c) + { + unsigned char buf[4096]; +diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c +index 1e5b039..91f1458 100644 +--- a/libmultipath/checkers/tur.c ++++ b/libmultipath/checkers/tur.c +@@ -187,6 +187,11 @@ void libcheck_free (struct checker * c) + return; + } + ++void libcheck_repair (struct checker * c) ++{ ++ return; ++} ++ + #define TUR_MSG(msg, fmt, args...) snprintf(msg, CHECKER_MSG_LEN, fmt, ##args); + + int +diff --git a/multipathd/main.c b/multipathd/main.c +index 8808c88..d26fd22 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -1455,6 +1455,14 @@ check_path (struct vectors * vecs, struct path * pp) + } + } + ++void repair_path(struct vectors * vecs, struct path * pp) ++{ ++ if (pp->state != PATH_DOWN) ++ return; ++ ++ checker_repair(&pp->checker); ++} ++ + static void * + checkerloop (void *ap) + { +@@ -1483,6 +1491,7 @@ checkerloop (void *ap) + if (vecs->pathvec) { + vector_foreach_slot (vecs->pathvec, pp, i) { + check_path(vecs, pp); ++ repair_path(vecs, pp); + } + } + if (vecs->mpvec) { +-- +1.8.3.1 + diff --git a/0180-multipath-tools-Add-rbd-checker.patch b/0180-multipath-tools-Add-rbd-checker.patch new file mode 100644 index 0000000..cf8a752 --- /dev/null +++ b/0180-multipath-tools-Add-rbd-checker.patch @@ -0,0 +1,746 @@ +From e28c340ed961409700d46a1cb9a820a8b7a4d016 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Thu, 11 Aug 2016 02:12:12 -0500 +Subject: [PATCH 04/11] multipath-tools: Add rbd checker. + +For BZ 1348372 from upstream commit: + +commit d1cad5649b6fcf9027d43ca0405c900080133e32 +Author: Mike Christie +Date: Mon Aug 8 07:01:49 2016 -0500 + + multipath-tools: Add rbd checker. + + This checker currently only handles the case where a path is failed + due to it being blacklisted by the ceph cluster. The specific use + case for me is when LIO exports rbd images through multiple LIO + instances. + + The problem it handles is when rbd instance1 has the exclusive lock, + but becomes unreachable another host in the cluster will take over + and blacklist the instance1. This prevents it from sending stale IO + and corrupting data. + + Later, when the host is reachable, we will want to failback to it. + To this, the checker will detect we were blacklisted, unmap the old + image which will make sure old IO is failed, and then remap the +image + and unblacklist the host. multipathd will then handle this like a + path being removed and re-added. + +-------- + +Porting notes: +Added rbd to multipath.conf.annotated. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers/Makefile | 7 + libmultipath/checkers/rbd.c | 639 +++++++++++++++++++++++++++++++++++++++++ + multipath.conf.annotated | 4 + multipath/multipath.conf.5 | 3 + 4 files changed, 651 insertions(+), 2 deletions(-) + create mode 100644 libmultipath/checkers/rbd.c + +Index: multipath-tools-130222/libmultipath/checkers/Makefile +=================================================================== +--- multipath-tools-130222.orig/libmultipath/checkers/Makefile ++++ multipath-tools-130222/libmultipath/checkers/Makefile +@@ -14,10 +14,17 @@ LIBS= \ + libcheckhp_sw.so \ + libcheckrdac.so + ++ifeq ($(shell test -r /usr/include/rados/librados.h && echo 1),1) ++LIBS += libcheckrbd.so ++endif ++ + CFLAGS += -fPIC -I.. + + all: $(LIBS) + ++libcheckrbd.so: rbd.o ++ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -lrados -ludev ++ + libcheckdirectio.so: libsg.o directio.o + $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ -laio + +Index: multipath-tools-130222/libmultipath/checkers/rbd.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libmultipath/checkers/rbd.c +@@ -0,0 +1,639 @@ ++/* ++ * Copyright (c) 2016 Red Hat ++ * Copyright (c) 2004 Christophe Varoqui ++ * ++ * Code based off of tur.c and ceph's krbd.cc ++ */ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rados/librados.h" ++ ++#include "structs.h" ++#include "checkers.h" ++ ++#include "../libmultipath/debug.h" ++#include "../libmultipath/uevent.h" ++ ++struct rbd_checker_context; ++typedef int (thread_fn)(struct rbd_checker_context *ct, char *msg); ++ ++#define RBD_MSG(msg, fmt, args...) snprintf(msg, CHECKER_MSG_LEN, fmt, ##args); ++ ++struct rbd_checker_context { ++ int rbd_bus_id; ++ char *client_addr; ++ char *config_info; ++ char *snap; ++ char *pool; ++ char *image; ++ char *username; ++ int remapped; ++ int blacklisted; ++ ++ rados_t cluster; ++ ++ int state; ++ int running; ++ time_t time; ++ thread_fn *fn; ++ pthread_t thread; ++ pthread_mutex_t lock; ++ pthread_cond_t active; ++ pthread_spinlock_t hldr_lock; ++ int holders; ++ char message[CHECKER_MSG_LEN]; ++}; ++ ++int libcheck_init(struct checker * c) ++{ ++ struct rbd_checker_context *ct; ++ struct udev_device *block_dev; ++ struct udev_device *bus_dev; ++ struct udev *udev; ++ struct stat sb; ++ const char *block_name, *addr, *config_info; ++ const char *image, *pool, *snap, *username; ++ char sysfs_path[PATH_SIZE]; ++ int ret; ++ ++ ct = malloc(sizeof(struct rbd_checker_context)); ++ if (!ct) ++ return 1; ++ memset(ct, 0, sizeof(struct rbd_checker_context)); ++ ct->holders = 1; ++ pthread_cond_init(&ct->active, NULL); ++ pthread_mutex_init(&ct->lock, NULL); ++ pthread_spin_init(&ct->hldr_lock, PTHREAD_PROCESS_PRIVATE); ++ c->context = ct; ++ ++ /* ++ * The rbd block layer sysfs device is not linked to the rbd bus ++ * device that we interact with, so figure that out now. ++ */ ++ if (fstat(c->fd, &sb) != 0) ++ goto free_ct; ++ ++ udev = udev_new(); ++ if (!udev) ++ goto free_ct; ++ ++ block_dev = udev_device_new_from_devnum(udev, 'b', sb.st_rdev); ++ if (!block_dev) ++ goto free_udev; ++ ++ block_name = udev_device_get_sysname(block_dev); ++ ret = sscanf(block_name, "rbd%d", &ct->rbd_bus_id); ++ ++ udev_device_unref(block_dev); ++ if (ret != 1) ++ goto free_udev; ++ ++ snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/rbd/devices/%d", ++ ct->rbd_bus_id); ++ bus_dev = udev_device_new_from_syspath(udev, sysfs_path); ++ if (!bus_dev) ++ goto free_udev; ++ ++ addr = udev_device_get_sysattr_value(bus_dev, "client_addr"); ++ if (!addr) { ++ condlog(0, "Could not find client_addr in rbd sysfs. Try " ++ "updating kernel"); ++ goto free_dev; ++ } ++ ++ ct->client_addr = strdup(addr); ++ if (!ct->client_addr) ++ goto free_dev; ++ ++ config_info = udev_device_get_sysattr_value(bus_dev, "config_info"); ++ if (!config_info) ++ goto free_addr; ++ ++ ct->config_info = strdup(config_info); ++ if (!ct->config_info) ++ goto free_addr; ++ ++ username = strstr(config_info, "name="); ++ if (username) { ++ char *end; ++ int len; ++ ++ username += 5; ++ end = strchr(username, ','); ++ if (!end) ++ goto free_info; ++ len = end - username; ++ ++ ct->username = malloc(len + 1); ++ if (!ct->username) ++ goto free_info; ++ strncpy(ct->username, username, len); ++ ct->username[len] = '\0'; ++ } ++ ++ image = udev_device_get_sysattr_value(bus_dev, "name"); ++ if (!image) ++ goto free_username; ++ ++ ct->image = strdup(image); ++ if (!ct->image) ++ goto free_info; ++ ++ pool = udev_device_get_sysattr_value(bus_dev, "pool"); ++ if (!pool) ++ goto free_image; ++ ++ ct->pool = strdup(pool); ++ if (!ct->pool) ++ goto free_image; ++ ++ snap = udev_device_get_sysattr_value(bus_dev, "current_snap"); ++ if (!snap) ++ goto free_pool; ++ ++ if (strcmp("-", snap)) { ++ ct->snap = strdup(snap); ++ if (!ct->snap) ++ goto free_pool; ++ } ++ ++ if (rados_create(&ct->cluster, NULL) < 0) { ++ condlog(0, "Could not create rados cluster"); ++ goto free_snap; ++ } ++ ++ if (rados_conf_read_file(ct->cluster, NULL) < 0) { ++ condlog(0, "Could not read rados conf"); ++ goto shutdown_rados; ++ } ++ ++ ret = rados_connect(ct->cluster); ++ if (ret < 0) { ++ condlog(0, "Could not connect to rados cluster"); ++ goto shutdown_rados; ++ } ++ ++ udev_device_unref(bus_dev); ++ udev_unref(udev); ++ ++ condlog(3, "rbd%d checker init %s %s/%s@%s %s", ct->rbd_bus_id, ++ ct->client_addr, ct->pool, ct->image, ct->snap ? ct->snap : "-", ++ ct->username ? ct->username : "none"); ++ return 0; ++ ++shutdown_rados: ++ rados_shutdown(ct->cluster); ++free_snap: ++ if (ct->snap) ++ free(ct->snap); ++free_pool: ++ free(ct->pool); ++free_image: ++ free(ct->image); ++free_username: ++ if (ct->username) ++ free(ct->username); ++free_info: ++ free(ct->config_info); ++free_addr: ++ free(ct->client_addr); ++free_dev: ++ udev_device_unref(bus_dev); ++free_udev: ++ udev_unref(udev); ++free_ct: ++ free(ct); ++ return 1; ++} ++ ++void cleanup_context(struct rbd_checker_context *ct) ++{ ++ pthread_mutex_destroy(&ct->lock); ++ pthread_cond_destroy(&ct->active); ++ pthread_spin_destroy(&ct->hldr_lock); ++ ++ rados_shutdown(ct->cluster); ++ ++ if (ct->username) ++ free(ct->username); ++ if (ct->snap) ++ free(ct->snap); ++ free(ct->pool); ++ free(ct->image); ++ free(ct->config_info); ++ free(ct->client_addr); ++ free(ct); ++} ++ ++void libcheck_free(struct checker * c) ++{ ++ if (c->context) { ++ struct rbd_checker_context *ct = c->context; ++ int holders; ++ pthread_t thread; ++ ++ pthread_spin_lock(&ct->hldr_lock); ++ ct->holders--; ++ holders = ct->holders; ++ thread = ct->thread; ++ pthread_spin_unlock(&ct->hldr_lock); ++ if (holders) ++ pthread_cancel(thread); ++ else ++ cleanup_context(ct); ++ c->context = NULL; ++ } ++} ++ ++static int rbd_is_blacklisted(struct rbd_checker_context *ct, char *msg) ++{ ++ char *addr_tok, *start, *save; ++ char *cmd[2]; ++ char *blklist, *stat; ++ size_t blklist_len, stat_len; ++ int ret; ++ char *end; ++ ++ cmd[0] = "{\"prefix\": \"osd blacklist ls\"}"; ++ cmd[1] = NULL; ++ ++ ret = rados_mon_command(ct->cluster, (const char **)cmd, 1, "", 0, ++ &blklist, &blklist_len, &stat, &stat_len); ++ if (ret < 0) { ++ RBD_MSG(msg, "rbd checker failed: mon command failed %d", ++ ret); ++ return ret; ++ } ++ ++ if (!blklist || !blklist_len) ++ goto free_bufs; ++ ++ /* ++ * parse list of addrs with the format ++ * ipv4:port/nonce date time\n ++ * or ++ * [ipv6]:port/nonce date time\n ++ */ ++ ret = 0; ++ for (start = blklist; ; start = NULL) { ++ addr_tok = strtok_r(start, "\n", &save); ++ if (!addr_tok || !strlen(addr_tok)) ++ break; ++ ++ end = strchr(addr_tok, ' '); ++ if (!end) { ++ RBD_MSG(msg, "rbd%d checker failed: invalid blacklist %s", ++ ct->rbd_bus_id, addr_tok); ++ break; ++ } ++ *end = '\0'; ++ ++ if (!strcmp(addr_tok, ct->client_addr)) { ++ ct->blacklisted = 1; ++ RBD_MSG(msg, "rbd%d checker: %s is blacklisted", ++ ct->rbd_bus_id, ct->client_addr); ++ ret = 1; ++ break; ++ } ++ } ++ ++free_bufs: ++ rados_buffer_free(blklist); ++ rados_buffer_free(stat); ++ return ret; ++} ++ ++int rbd_check(struct rbd_checker_context *ct, char *msg) ++{ ++ if (ct->blacklisted || rbd_is_blacklisted(ct, msg) == 1) ++ return PATH_DOWN; ++ ++ RBD_MSG(msg, "rbd checker reports path is up"); ++ /* ++ * Path may have issues, but the ceph cluster is at least ++ * accepting IO, so we can attempt to do IO. ++ * ++ * TODO: in future versions, we can run other tests to ++ * verify OSDs and networks. ++ */ ++ return PATH_UP; ++} ++ ++int safe_write(int fd, const void *buf, size_t count) ++{ ++ while (count > 0) { ++ ssize_t r = write(fd, buf, count); ++ if (r < 0) { ++ if (errno == EINTR) ++ continue; ++ return -errno; ++ } ++ count -= r; ++ buf = (char *)buf + r; ++ } ++ return 0; ++} ++ ++static int sysfs_write_rbd_bus(const char *which, const char *buf, ++ size_t buf_len) ++{ ++ char sysfs_path[PATH_SIZE]; ++ int fd; ++ int r; ++ ++ /* we require newer kernels so single_major should alwayws be there */ ++ snprintf(sysfs_path, sizeof(sysfs_path), ++ "/sys/bus/rbd/%s_single_major", which); ++ fd = open(sysfs_path, O_WRONLY); ++ if (fd < 0) ++ return -errno; ++ ++ r = safe_write(fd, buf, buf_len); ++ close(fd); ++ return r; ++} ++ ++static int rbd_remap(struct rbd_checker_context *ct) ++{ ++ char *argv[11]; ++ pid_t pid; ++ int ret = 0, i = 0; ++ int status; ++ ++ pid = fork(); ++ switch (pid) { ++ case 0: ++ argv[i++] = "rbd"; ++ argv[i++] = "map"; ++ argv[i++] = "-o noshare"; ++ if (ct->username) { ++ argv[i++] = "--id"; ++ argv[i++] = ct->username; ++ } ++ argv[i++] = "--pool"; ++ argv[i++] = ct->pool; ++ if (ct->snap) { ++ argv[i++] = "--snap"; ++ argv[i++] = ct->snap; ++ } ++ argv[i++] = ct->image; ++ argv[i] = NULL; ++ ++ ret = execvp(argv[0], argv); ++ condlog(0, "Error executing rbd: %s", strerror(errno)); ++ exit(-1); ++ case -1: ++ condlog(0, "fork failed: %s", strerror(errno)); ++ return -1; ++ default: ++ ret = -1; ++ wait(&status); ++ if (WIFEXITED(status)) { ++ status = WEXITSTATUS(status); ++ if (status == 0) ++ ret = 0; ++ else ++ condlog(0, "rbd failed with %d", status); ++ } ++ } ++ ++ return ret; ++} ++ ++static int sysfs_write_rbd_remove(const char *buf, int buf_len) ++{ ++ return sysfs_write_rbd_bus("remove", buf, buf_len); ++} ++ ++static int rbd_rm_blacklist(struct rbd_checker_context *ct) ++{ ++ char *cmd[2]; ++ char *stat, *cmd_str; ++ size_t stat_len; ++ int ret; ++ ++ ret = asprintf(&cmd_str, "{\"prefix\": \"osd blacklist\", \"blacklistop\": \"rm\", \"addr\": \"%s\"}", ++ ct->client_addr); ++ if (ret == -1) ++ return -ENOMEM; ++ ++ cmd[0] = cmd_str; ++ cmd[1] = NULL; ++ ++ ret = rados_mon_command(ct->cluster, (const char **)cmd, 1, "", 0, ++ NULL, 0, &stat, &stat_len); ++ if (ret < 0) { ++ condlog(1, "rbd%d repair failed to remove blacklist for %s %d", ++ ct->rbd_bus_id, ct->client_addr, ret); ++ goto free_cmd; ++ } ++ ++ condlog(1, "rbd%d repair rm blacklist for %s", ++ ct->rbd_bus_id, ct->client_addr); ++ free(stat); ++free_cmd: ++ free(cmd_str); ++ return ret; ++} ++ ++static int rbd_repair(struct rbd_checker_context *ct, char *msg) ++{ ++ char del[17]; ++ int ret; ++ ++ if (!ct->blacklisted) ++ return PATH_UP; ++ ++ if (!ct->remapped) { ++ ret = rbd_remap(ct); ++ if (ret) { ++ RBD_MSG(msg, "rbd%d repair failed to remap. Err %d", ++ ct->rbd_bus_id, ret); ++ return PATH_DOWN; ++ } ++ } ++ ct->remapped = 1; ++ ++ snprintf(del, sizeof(del), "%d force", ct->rbd_bus_id); ++ ret = sysfs_write_rbd_remove(del, strlen(del) + 1); ++ if (ret) { ++ RBD_MSG(msg, "rbd%d repair failed to clean up. Err %d", ++ ct->rbd_bus_id, ret); ++ return PATH_DOWN; ++ } ++ ++ ret = rbd_rm_blacklist(ct); ++ if (ret) { ++ RBD_MSG(msg, "rbd%d repair could not remove blacklist entry. Err %d", ++ ct->rbd_bus_id, ret); ++ return PATH_DOWN; ++ } ++ ++ ct->remapped = 0; ++ ct->blacklisted = 0; ++ ++ RBD_MSG(msg, "rbd%d has been repaired", ct->rbd_bus_id); ++ return PATH_UP; ++} ++ ++#define rbd_thread_cleanup_push(ct) pthread_cleanup_push(cleanup_func, ct) ++#define rbd_thread_cleanup_pop(ct) pthread_cleanup_pop(1) ++ ++void cleanup_func(void *data) ++{ ++ int holders; ++ struct rbd_checker_context *ct = data; ++ pthread_spin_lock(&ct->hldr_lock); ++ ct->holders--; ++ holders = ct->holders; ++ ct->thread = 0; ++ pthread_spin_unlock(&ct->hldr_lock); ++ if (!holders) ++ cleanup_context(ct); ++} ++ ++void *rbd_thread(void *ctx) ++{ ++ struct rbd_checker_context *ct = ctx; ++ int state; ++ ++ condlog(3, "rbd%d thread starting up", ct->rbd_bus_id); ++ ++ ct->message[0] = '\0'; ++ /* This thread can be canceled, so setup clean up */ ++ rbd_thread_cleanup_push(ct) ++ ++ /* checker start up */ ++ pthread_mutex_lock(&ct->lock); ++ ct->state = PATH_PENDING; ++ pthread_mutex_unlock(&ct->lock); ++ ++ state = ct->fn(ct, ct->message); ++ ++ /* checker done */ ++ pthread_mutex_lock(&ct->lock); ++ ct->state = state; ++ pthread_mutex_unlock(&ct->lock); ++ pthread_cond_signal(&ct->active); ++ ++ condlog(3, "rbd%d thead finished, state %s", ct->rbd_bus_id, ++ checker_state_name(state)); ++ rbd_thread_cleanup_pop(ct); ++ return ((void *)0); ++} ++ ++static void rbd_timeout(struct timespec *tsp) ++{ ++ struct timeval now; ++ ++ gettimeofday(&now, NULL); ++ tsp->tv_sec = now.tv_sec; ++ tsp->tv_nsec = now.tv_usec * 1000; ++ tsp->tv_nsec += 1000000; /* 1 millisecond */ ++} ++ ++static int rbd_exec_fn(struct checker *c, thread_fn *fn) ++{ ++ struct rbd_checker_context *ct = c->context; ++ struct timespec tsp; ++ pthread_attr_t attr; ++ int rbd_status, r; ++ ++ if (c->sync) ++ return rbd_check(ct, c->message); ++ /* ++ * Async mode ++ */ ++ r = pthread_mutex_lock(&ct->lock); ++ if (r != 0) { ++ condlog(2, "rbd%d mutex lock failed with %d", ct->rbd_bus_id, ++ r); ++ MSG(c, "rbd%d thread failed to initialize", ct->rbd_bus_id); ++ return PATH_WILD; ++ } ++ ++ if (ct->running) { ++ /* Check if checker is still running */ ++ if (ct->thread) { ++ condlog(3, "rbd%d thread not finished", ct->rbd_bus_id); ++ rbd_status = PATH_PENDING; ++ } else { ++ /* checker done */ ++ ct->running = 0; ++ rbd_status = ct->state; ++ strncpy(c->message, ct->message, CHECKER_MSG_LEN); ++ c->message[CHECKER_MSG_LEN - 1] = '\0'; ++ } ++ pthread_mutex_unlock(&ct->lock); ++ } else { ++ /* Start new checker */ ++ ct->state = PATH_UNCHECKED; ++ ct->fn = fn; ++ pthread_spin_lock(&ct->hldr_lock); ++ ct->holders++; ++ pthread_spin_unlock(&ct->hldr_lock); ++ setup_thread_attr(&attr, 32 * 1024, 1); ++ r = pthread_create(&ct->thread, &attr, rbd_thread, ct); ++ if (r) { ++ pthread_mutex_unlock(&ct->lock); ++ ct->thread = 0; ++ ct->holders--; ++ condlog(3, "rbd%d failed to start rbd thread, using sync mode", ++ ct->rbd_bus_id); ++ return fn(ct, c->message); ++ } ++ pthread_attr_destroy(&attr); ++ rbd_timeout(&tsp); ++ r = pthread_cond_timedwait(&ct->active, &ct->lock, &tsp); ++ rbd_status = ct->state; ++ strncpy(c->message, ct->message,CHECKER_MSG_LEN); ++ c->message[CHECKER_MSG_LEN -1] = '\0'; ++ pthread_mutex_unlock(&ct->lock); ++ ++ if (ct->thread && ++ (rbd_status == PATH_PENDING || rbd_status == PATH_UNCHECKED)) { ++ condlog(3, "rbd%d thread still running", ++ ct->rbd_bus_id); ++ ct->running = 1; ++ rbd_status = PATH_PENDING; ++ } ++ } ++ ++ return rbd_status; ++} ++ ++void libcheck_repair(struct checker * c) ++{ ++ struct rbd_checker_context *ct = c->context; ++ ++ if (!ct || !ct->blacklisted) ++ return; ++ rbd_exec_fn(c, rbd_repair); ++} ++ ++int libcheck_check(struct checker * c) ++{ ++ struct rbd_checker_context *ct = c->context; ++ ++ if (!ct) ++ return PATH_UNCHECKED; ++ ++ if (ct->blacklisted) ++ return PATH_DOWN; ++ ++ return rbd_exec_fn(c, rbd_check); ++} +Index: multipath-tools-130222/multipath.conf.annotated +=================================================================== +--- multipath-tools-130222.orig/multipath.conf.annotated ++++ multipath-tools-130222/multipath.conf.annotated +@@ -97,7 +97,7 @@ + # # scope : multipath & multipathd + # # desc : the default method used to determine the paths' state + # # values : readsector0|tur|emc_clariion|hp_sw|directio|rdac| +-# cciss_tur|hp_tur ++# cciss_tur|hp_tur|rbd + # # default : directio + # # + # path_checker directio +@@ -493,7 +493,7 @@ + # # scope : multipathd & multipathd + # # desc : path checking algorithm to use to check path state + # # values : readsector0|tur|emc_clariion|hp_sw|directio|rdac| +-# # cciss_tur|hp_tur ++# # cciss_tur|hp_tur|rbd + # # + # path_checker directio + # +Index: multipath-tools-130222/multipath/multipath.conf.5 +=================================================================== +--- multipath-tools-130222.orig/multipath/multipath.conf.5 ++++ multipath-tools-130222/multipath/multipath.conf.5 +@@ -284,6 +284,9 @@ Check the path state for LSI/Engenio/Net + .B directio + Read the first sector with direct I/O. + .TP ++.B rbd ++Check if the path is in the Ceph blacklist. ++.TP + Default value is \fIdirectio\fR. + .RE + .TP diff --git a/0181-multipath-tools-Add-rbd-to-the-hwtable.patch b/0181-multipath-tools-Add-rbd-to-the-hwtable.patch new file mode 100644 index 0000000..fd9d6ff --- /dev/null +++ b/0181-multipath-tools-Add-rbd-to-the-hwtable.patch @@ -0,0 +1,51 @@ +From b25186a60347e2a0f2e53a10c05d9ad52a88c890 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 9 Aug 2016 13:53:21 -0500 +Subject: [PATCH 05/11] multipath-tools: Add rbd to the hwtable + +For BZ 1348372 from upstream commit: + +commit 61fe9e521965ff70db6a65370b394d769077d54c +Author: Mike Christie +Date: Mon Aug 8 07:01:50 2016 -0500 + + multipath-tools: Add rbd to the hwtable + + Add rbd to hwtable. These defaults are for the HA type of setup + supported by the checker. We do no support features like multibus + at the dm-multipath level yet + +Signed-off-by: Mike Christie +--- + libmultipath/hwtable.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c +index 61d1033..d278c04 100644 +--- a/libmultipath/hwtable.c ++++ b/libmultipath/hwtable.c +@@ -1206,6 +1206,21 @@ static struct hwentry default_hw[] = { + .pgfailback = -FAILBACK_IMMEDIATE, + .checker_name = TUR, + }, ++ { ++ /* ++ * Red Hat ++ * ++ * Maintainer: Mike Christie ++ * Mail: mchristi@redhat.com ++ */ ++ .vendor = "Ceph", ++ .product = "RBD", ++ .pgpolicy = FAILOVER, ++ .no_path_retry = NO_PATH_RETRY_FAIL, ++ .checker_name = RBD, ++ .deferred_remove = DEFERRED_REMOVE_ON, ++ }, ++ + /* + * EOL + */ +-- +1.8.3.1 + diff --git a/0182-multipath-tools-check-for-initialized-checker-before.patch b/0182-multipath-tools-check-for-initialized-checker-before.patch new file mode 100644 index 0000000..26b8f28 --- /dev/null +++ b/0182-multipath-tools-check-for-initialized-checker-before.patch @@ -0,0 +1,49 @@ +From 7592f62383e6143a54d89885e505834c4977c4a6 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 16 Aug 2016 11:44:27 -0500 +Subject: [PATCH 06/11] multipath-tools: check for initialized checker before + +For bz from upstream commit: + +commit b5773d46a4550c3c222bb415197e0bc5f09c1169 +Author: Mike Christie +Date: Mon Aug 15 12:13:45 2016 -0500 + + multipath-tools: check for initialized checker before + + This fixes a regression added with: + 015f87b16a7797a17afd514aec46e65c2a1a2f73 + + We can hit a race where when pathinfo is setting up a path, the path + could have gone down already. In the DI_CHECKER chunk we then do not +run + get_state and attach a checker. Later when check_path is run + path_offline could still return PATH_DOWN or PATH_REMOVED and + get_state is again not run so we do not get to attach a checker. I + was then running repair_path since the state was PATH_DOWN, and we +then + hit a segfault. + + This has us test if a checker is selected before running repair. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c +index fa7d8b7..6cd8d34 100644 +--- a/libmultipath/checkers.c ++++ b/libmultipath/checkers.c +@@ -212,7 +212,7 @@ void checker_put (struct checker * dst) + + void checker_repair (struct checker * c) + { +- if (!c) ++ if (!c || !checker_selected(c)) + return; + + c->message[0] = '\0'; +-- +1.8.3.1 + diff --git a/0183-multipathd-Don-t-call-repair-on-blacklisted-path.patch b/0183-multipathd-Don-t-call-repair-on-blacklisted-path.patch new file mode 100644 index 0000000..93323c1 --- /dev/null +++ b/0183-multipathd-Don-t-call-repair-on-blacklisted-path.patch @@ -0,0 +1,91 @@ +From 2926316c8492a1d18c7bbdac0fac75c38ce16152 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Tue, 16 Aug 2016 11:47:16 -0500 +Subject: [PATCH 07/11] multipathd: Don't call repair on blacklisted path + +For BZ 1348372 from upstream commit: + +Author: Mike Christie +Date: Mon Aug 15 12:13:46 2016 -0500 + + multipathd: Don't call repair on blacklisted paths + + This fixes a regression added in + 015f87b16a7797a17afd514aec46e65c2a1a2f73 + + If a path is blacklisted the checkerloop will free the path so + don't call repair on it. + + This moves the repair call down into check_path, because I think + we also do not need to call it for other cases where we cannot get + the uuid info or being orphaned. + +Signed-off-by: Mike Christie +--- + multipathd/main.c | 22 ++++++++++++---------- + 1 file changed, 12 insertions(+), 10 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index d26fd22..4638c8b 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -1238,6 +1238,16 @@ int update_path_groups(struct multipath *mpp, struct vectors *vecs, int refresh) + return 0; + } + ++void repair_path(struct path * pp) ++{ ++ if (pp->state != PATH_DOWN) ++ return; ++ ++ checker_repair(&pp->checker); ++ if (strlen(checker_message(&pp->checker))) ++ LOG_MSG(1, checker_message(&pp->checker)); ++} ++ + void + check_path (struct vectors * vecs, struct path * pp) + { +@@ -1352,6 +1362,7 @@ check_path (struct vectors * vecs, struct path * pp) + pp->mpp->failback_tick = 0; + + pp->mpp->stat_path_failures++; ++ repair_path(pp); + return; + } + +@@ -1431,7 +1442,7 @@ check_path (struct vectors * vecs, struct path * pp) + } + + pp->state = newstate; +- ++ repair_path(pp); + + if (pp->mpp->wait_for_udev) + return; +@@ -1455,14 +1466,6 @@ check_path (struct vectors * vecs, struct path * pp) + } + } + +-void repair_path(struct vectors * vecs, struct path * pp) +-{ +- if (pp->state != PATH_DOWN) +- return; +- +- checker_repair(&pp->checker); +-} +- + static void * + checkerloop (void *ap) + { +@@ -1491,7 +1494,6 @@ checkerloop (void *ap) + if (vecs->pathvec) { + vector_foreach_slot (vecs->pathvec, pp, i) { + check_path(vecs, pp); +- repair_path(vecs, pp); + } + } + if (vecs->mpvec) { +-- +1.8.3.1 + diff --git a/0184-rbd-fix-sync-repair-support.patch b/0184-rbd-fix-sync-repair-support.patch new file mode 100644 index 0000000..6ae6303 --- /dev/null +++ b/0184-rbd-fix-sync-repair-support.patch @@ -0,0 +1,29 @@ +From d1bda720153b4978121fbae40f82d2f4b9aff997 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Thu, 25 Aug 2016 01:34:11 -0500 +Subject: [PATCH 08/11] rbd: fix sync repair support + +If sync was set we were calling check instead +of function passed in. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers/rbd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libmultipath/checkers/rbd.c b/libmultipath/checkers/rbd.c +index 6f1b53a..76f4005 100644 +--- a/libmultipath/checkers/rbd.c ++++ b/libmultipath/checkers/rbd.c +@@ -554,7 +554,7 @@ static int rbd_exec_fn(struct checker *c, thread_fn *fn) + int rbd_status, r; + + if (c->sync) +- return rbd_check(ct, c->message); ++ return fn(ct, c->message); + /* + * Async mode + */ +-- +1.8.3.1 + diff --git a/0185-rbd-check-for-nonshared-clients.patch b/0185-rbd-check-for-nonshared-clients.patch new file mode 100644 index 0000000..c209d73 --- /dev/null +++ b/0185-rbd-check-for-nonshared-clients.patch @@ -0,0 +1,32 @@ +From c9a788f437f2729f943cd03c43e84b65d74eb015 Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Wed, 31 Aug 2016 15:22:09 -0500 +Subject: [PATCH 09/11] rbd: check for nonshared clients + +The rbd checker only supports nonshared clients so add a check +during init time. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers/rbd.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/libmultipath/checkers/rbd.c b/libmultipath/checkers/rbd.c +index 76f4005..a6f3405 100644 +--- a/libmultipath/checkers/rbd.c ++++ b/libmultipath/checkers/rbd.c +@@ -123,6 +123,11 @@ int libcheck_init(struct checker * c) + if (!config_info) + goto free_addr; + ++ if (!strstr(config_info, "noshare")) { ++ condlog(3, "Only nonshared clients supported."); ++ goto free_addr; ++ } ++ + ct->config_info = strdup(config_info); + if (!ct->config_info) + goto free_addr; +-- +1.8.3.1 + diff --git a/0186-rbd-check-for-exclusive-lock-enabled.patch b/0186-rbd-check-for-exclusive-lock-enabled.patch new file mode 100644 index 0000000..290850b --- /dev/null +++ b/0186-rbd-check-for-exclusive-lock-enabled.patch @@ -0,0 +1,56 @@ +From 513d210cdbccfdaadb0cf7f09ba97d563aac52bb Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Wed, 31 Aug 2016 15:40:16 -0500 +Subject: [PATCH 10/11] rbd: check for exclusive lock enabled + +Only attach the checker if the rbd image has the exclusive lock +enabled. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers/rbd.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/libmultipath/checkers/rbd.c b/libmultipath/checkers/rbd.c +index a6f3405..e34bf53 100644 +--- a/libmultipath/checkers/rbd.c ++++ b/libmultipath/checkers/rbd.c +@@ -33,6 +33,8 @@ typedef int (thread_fn)(struct rbd_checker_context *ct, char *msg); + + #define RBD_MSG(msg, fmt, args...) snprintf(msg, CHECKER_MSG_LEN, fmt, ##args); + ++#define RBD_FEATURE_EXCLUSIVE_LOCK (1 << 2) ++ + struct rbd_checker_context { + int rbd_bus_id; + char *client_addr; +@@ -65,8 +67,9 @@ int libcheck_init(struct checker * c) + struct udev_device *bus_dev; + struct udev *udev; + struct stat sb; +- const char *block_name, *addr, *config_info; ++ const char *block_name, *addr, *config_info, *features_str; + const char *image, *pool, *snap, *username; ++ uint64_t features = 0; + char sysfs_path[PATH_SIZE]; + int ret; + +@@ -119,6 +122,15 @@ int libcheck_init(struct checker * c) + if (!ct->client_addr) + goto free_dev; + ++ features_str = udev_device_get_sysattr_value(bus_dev, "features"); ++ if (!features_str) ++ goto free_addr; ++ features = strtoll(features_str, NULL, 16); ++ if (!(features & RBD_FEATURE_EXCLUSIVE_LOCK)) { ++ condlog(3, "Exclusive lock not set."); ++ goto free_addr; ++ } ++ + config_info = udev_device_get_sysattr_value(bus_dev, "config_info"); + if (!config_info) + goto free_addr; +-- +1.8.3.1 + diff --git a/0187-rbd-fixup-log-messages.patch b/0187-rbd-fixup-log-messages.patch new file mode 100644 index 0000000..c4ed6db --- /dev/null +++ b/0187-rbd-fixup-log-messages.patch @@ -0,0 +1,238 @@ +From 3ed9a923904887e41c774c71232ae2a1ff6fc3fb Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Wed, 31 Aug 2016 15:59:53 -0500 +Subject: [PATCH 11/11] rbd: fixup log messages + +Add rbd device prefix to condlog messages that was missing it, and drop +it in RBD_MSG because it is already added by caller. + +Signed-off-by: Mike Christie +--- + libmultipath/checkers/rbd.c | 67 +++++++++++++++++++++++---------------------- + 1 file changed, 35 insertions(+), 32 deletions(-) + +diff --git a/libmultipath/checkers/rbd.c b/libmultipath/checkers/rbd.c +index e34bf53..8e6cd3c 100644 +--- a/libmultipath/checkers/rbd.c ++++ b/libmultipath/checkers/rbd.c +@@ -113,8 +113,8 @@ int libcheck_init(struct checker * c) + + addr = udev_device_get_sysattr_value(bus_dev, "client_addr"); + if (!addr) { +- condlog(0, "Could not find client_addr in rbd sysfs. Try " +- "updating kernel"); ++ condlog(0, "rbd%d: Could not find client_addr in rbd sysfs. " ++ "Try updating kernel", ct->rbd_bus_id); + goto free_dev; + } + +@@ -127,7 +127,7 @@ int libcheck_init(struct checker * c) + goto free_addr; + features = strtoll(features_str, NULL, 16); + if (!(features & RBD_FEATURE_EXCLUSIVE_LOCK)) { +- condlog(3, "Exclusive lock not set."); ++ condlog(3, "rbd%d: Exclusive lock not set.", ct->rbd_bus_id); + goto free_addr; + } + +@@ -136,7 +136,8 @@ int libcheck_init(struct checker * c) + goto free_addr; + + if (!strstr(config_info, "noshare")) { +- condlog(3, "Only nonshared clients supported."); ++ condlog(3, "rbd%d: Only nonshared clients supported.", ++ ct->rbd_bus_id); + goto free_addr; + } + +@@ -189,18 +190,20 @@ int libcheck_init(struct checker * c) + } + + if (rados_create(&ct->cluster, NULL) < 0) { +- condlog(0, "Could not create rados cluster"); ++ condlog(0, "rbd%d: Could not create rados cluster", ++ ct->rbd_bus_id); + goto free_snap; + } + + if (rados_conf_read_file(ct->cluster, NULL) < 0) { +- condlog(0, "Could not read rados conf"); ++ condlog(0, "rbd%d: Could not read rados conf", ct->rbd_bus_id); + goto shutdown_rados; + } + + ret = rados_connect(ct->cluster); + if (ret < 0) { +- condlog(0, "Could not connect to rados cluster"); ++ condlog(0, "rbd%d: Could not connect to rados cluster", ++ ct->rbd_bus_id); + goto shutdown_rados; + } + +@@ -291,8 +294,7 @@ static int rbd_is_blacklisted(struct rbd_checker_context *ct, char *msg) + ret = rados_mon_command(ct->cluster, (const char **)cmd, 1, "", 0, + &blklist, &blklist_len, &stat, &stat_len); + if (ret < 0) { +- RBD_MSG(msg, "rbd checker failed: mon command failed %d", +- ret); ++ RBD_MSG(msg, "checker failed: mon command failed %d", ret); + return ret; + } + +@@ -313,16 +315,15 @@ static int rbd_is_blacklisted(struct rbd_checker_context *ct, char *msg) + + end = strchr(addr_tok, ' '); + if (!end) { +- RBD_MSG(msg, "rbd%d checker failed: invalid blacklist %s", +- ct->rbd_bus_id, addr_tok); ++ RBD_MSG(msg, "checker failed: invalid blacklist %s", ++ addr_tok); + break; + } + *end = '\0'; + + if (!strcmp(addr_tok, ct->client_addr)) { + ct->blacklisted = 1; +- RBD_MSG(msg, "rbd%d checker: %s is blacklisted", +- ct->rbd_bus_id, ct->client_addr); ++ RBD_MSG(msg, "%s is blacklisted", ct->client_addr); + ret = 1; + break; + } +@@ -339,7 +340,7 @@ int rbd_check(struct rbd_checker_context *ct, char *msg) + if (ct->blacklisted || rbd_is_blacklisted(ct, msg) == 1) + return PATH_DOWN; + +- RBD_MSG(msg, "rbd checker reports path is up"); ++ RBD_MSG(msg, "checker reports path is up"); + /* + * Path may have issues, but the ceph cluster is at least + * accepting IO, so we can attempt to do IO. +@@ -411,10 +412,12 @@ static int rbd_remap(struct rbd_checker_context *ct) + argv[i] = NULL; + + ret = execvp(argv[0], argv); +- condlog(0, "Error executing rbd: %s", strerror(errno)); ++ condlog(0, "rbd%d: Error executing rbd: %s", ct->rbd_bus_id, ++ strerror(errno)); + exit(-1); + case -1: +- condlog(0, "fork failed: %s", strerror(errno)); ++ condlog(0, "rbd%d: fork failed: %s", ct->rbd_bus_id, ++ strerror(errno)); + return -1; + default: + ret = -1; +@@ -424,7 +427,8 @@ static int rbd_remap(struct rbd_checker_context *ct) + if (status == 0) + ret = 0; + else +- condlog(0, "rbd failed with %d", status); ++ condlog(0, "rbd%d: failed with %d", ++ ct->rbd_bus_id, status); + } + } + +@@ -454,12 +458,12 @@ static int rbd_rm_blacklist(struct rbd_checker_context *ct) + ret = rados_mon_command(ct->cluster, (const char **)cmd, 1, "", 0, + NULL, 0, &stat, &stat_len); + if (ret < 0) { +- condlog(1, "rbd%d repair failed to remove blacklist for %s %d", ++ condlog(1, "rbd%d: repair failed to remove blacklist for %s %d", + ct->rbd_bus_id, ct->client_addr, ret); + goto free_cmd; + } + +- condlog(1, "rbd%d repair rm blacklist for %s", ++ condlog(1, "rbd%d: repair rm blacklist for %s", + ct->rbd_bus_id, ct->client_addr); + free(stat); + free_cmd: +@@ -478,8 +482,7 @@ static int rbd_repair(struct rbd_checker_context *ct, char *msg) + if (!ct->remapped) { + ret = rbd_remap(ct); + if (ret) { +- RBD_MSG(msg, "rbd%d repair failed to remap. Err %d", +- ct->rbd_bus_id, ret); ++ RBD_MSG(msg, "repair failed to remap. Err %d", ret); + return PATH_DOWN; + } + } +@@ -488,22 +491,21 @@ static int rbd_repair(struct rbd_checker_context *ct, char *msg) + snprintf(del, sizeof(del), "%d force", ct->rbd_bus_id); + ret = sysfs_write_rbd_remove(del, strlen(del) + 1); + if (ret) { +- RBD_MSG(msg, "rbd%d repair failed to clean up. Err %d", +- ct->rbd_bus_id, ret); ++ RBD_MSG(msg, "repair failed to clean up. Err %d", ret); + return PATH_DOWN; + } + + ret = rbd_rm_blacklist(ct); + if (ret) { +- RBD_MSG(msg, "rbd%d repair could not remove blacklist entry. Err %d", +- ct->rbd_bus_id, ret); ++ RBD_MSG(msg, "repair could not remove blacklist entry. Err %d", ++ ret); + return PATH_DOWN; + } + + ct->remapped = 0; + ct->blacklisted = 0; + +- RBD_MSG(msg, "rbd%d has been repaired", ct->rbd_bus_id); ++ RBD_MSG(msg, "has been repaired"); + return PATH_UP; + } + +@@ -528,7 +530,7 @@ void *rbd_thread(void *ctx) + struct rbd_checker_context *ct = ctx; + int state; + +- condlog(3, "rbd%d thread starting up", ct->rbd_bus_id); ++ condlog(3, "rbd%d: thread starting up", ct->rbd_bus_id); + + ct->message[0] = '\0'; + /* This thread can be canceled, so setup clean up */ +@@ -547,7 +549,7 @@ void *rbd_thread(void *ctx) + pthread_mutex_unlock(&ct->lock); + pthread_cond_signal(&ct->active); + +- condlog(3, "rbd%d thead finished, state %s", ct->rbd_bus_id, ++ condlog(3, "rbd%d: thead finished, state %s", ct->rbd_bus_id, + checker_state_name(state)); + rbd_thread_cleanup_pop(ct); + return ((void *)0); +@@ -577,16 +579,17 @@ static int rbd_exec_fn(struct checker *c, thread_fn *fn) + */ + r = pthread_mutex_lock(&ct->lock); + if (r != 0) { +- condlog(2, "rbd%d mutex lock failed with %d", ct->rbd_bus_id, ++ condlog(2, "rbd%d: mutex lock failed with %d", ct->rbd_bus_id, + r); +- MSG(c, "rbd%d thread failed to initialize", ct->rbd_bus_id); ++ MSG(c, "rbd%d: thread failed to initialize", ct->rbd_bus_id); + return PATH_WILD; + } + + if (ct->running) { + /* Check if checker is still running */ + if (ct->thread) { +- condlog(3, "rbd%d thread not finished", ct->rbd_bus_id); ++ condlog(3, "rbd%d: thread not finished", ++ ct->rbd_bus_id); + rbd_status = PATH_PENDING; + } else { + /* checker done */ +@@ -623,7 +626,7 @@ static int rbd_exec_fn(struct checker *c, thread_fn *fn) + + if (ct->thread && + (rbd_status == PATH_PENDING || rbd_status == PATH_UNCHECKED)) { +- condlog(3, "rbd%d thread still running", ++ condlog(3, "rbd%d: thread still running", + ct->rbd_bus_id); + ct->running = 1; + rbd_status = PATH_PENDING; +-- +1.8.3.1 + diff --git a/0188-RHBZ-1368501-dont-exit.patch b/0188-RHBZ-1368501-dont-exit.patch new file mode 100644 index 0000000..4783991 --- /dev/null +++ b/0188-RHBZ-1368501-dont-exit.patch @@ -0,0 +1,208 @@ +--- + libmultipath/configure.c | 8 +++-- + multipathd/main.c | 68 +++++++++++++++++++++++++++++++++++++---------- + 2 files changed, 59 insertions(+), 17 deletions(-) + +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -829,8 +829,10 @@ coalesce_paths (struct vectors * vecs, v + * at this point, we know we really got a new mp + */ + mpp = add_map_with_path(vecs, pp1, 0); +- if (!mpp) +- return 1; ++ if (!mpp) { ++ orphan_path(pp1); ++ continue; ++ } + + if (pp1->priority == PRIO_UNDEF) + mpp->action = ACT_REJECT; +@@ -879,7 +881,7 @@ coalesce_paths (struct vectors * vecs, v + condlog(3, "%s: domap (%u) failure " + "for create/reload map", + mpp->alias, r); +- if (r == DOMAP_FAIL) { ++ if (r == DOMAP_FAIL || conf->daemon) { + condlog(2, "%s: %s map", + mpp->alias, (mpp->action == ACT_CREATE)? + "ignoring" : "removing"); +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -821,7 +821,7 @@ map_discovery (struct vectors * vecs) + + vector_foreach_slot (vecs->mpvec, mpp, i) + if (setup_multipath(vecs, mpp)) +- return 1; ++ i--; + + return 0; + } +@@ -1523,21 +1523,29 @@ configure (struct vectors * vecs, int st + vector mpvec; + int i, ret; + +- if (!vecs->pathvec && !(vecs->pathvec = vector_alloc())) ++ if (!vecs->pathvec && !(vecs->pathvec = vector_alloc())) { ++ condlog(0, "couldn't allocate path vec in configure"); + return 1; ++ } + +- if (!vecs->mpvec && !(vecs->mpvec = vector_alloc())) ++ if (!vecs->mpvec && !(vecs->mpvec = vector_alloc())) { ++ condlog(0, "couldn't allocate multipath vec in configure"); + return 1; ++ } + +- if (!(mpvec = vector_alloc())) ++ if (!(mpvec = vector_alloc())) { ++ condlog(0, "couldn't allocate new maps vec in configure"); + return 1; ++ } + + /* + * probe for current path (from sysfs) and map (from dm) sets + */ + ret = path_discovery(vecs->pathvec, conf, DI_ALL); +- if (ret < 0) ++ if (ret < 0) { ++ condlog(0, "configure failed at path discovery"); + return 1; ++ } + + vector_foreach_slot (vecs->pathvec, pp, i){ + if (filter_path(conf, pp) > 0){ +@@ -1548,21 +1556,27 @@ configure (struct vectors * vecs, int st + else + pp->checkint = conf->checkint; + } +- if (map_discovery(vecs)) ++ if (map_discovery(vecs)) { ++ condlog(0, "configure failed at map discovery"); + return 1; ++ } + + /* + * create new set of maps & push changed ones into dm + */ +- if (coalesce_paths(vecs, mpvec, NULL, 1)) ++ if (coalesce_paths(vecs, mpvec, NULL, 1)) { ++ condlog(0, "configure failed while coalescing paths"); + return 1; ++ } + + /* + * may need to remove some maps which are no longer relevant + * e.g., due to blacklist changes in conf file + */ +- if (coalesce_maps(vecs, mpvec)) ++ if (coalesce_maps(vecs, mpvec)) { ++ condlog(0, "configure failed while coalescing maps"); + return 1; ++ } + + dm_lib_release(); + +@@ -1588,11 +1602,15 @@ configure (struct vectors * vecs, int st + * start dm event waiter threads for these new maps + */ + vector_foreach_slot(vecs->mpvec, mpp, i) { +- if (setup_multipath(vecs, mpp)) +- return 1; ++ if (setup_multipath(vecs, mpp)) { ++ i--; ++ continue; ++ } + if (start_waiters) +- if (start_waiter_thread(mpp, vecs)) +- return 1; ++ if (start_waiter_thread(mpp, vecs)) { ++ remove_map(mpp, vecs, 1); ++ i--; ++ } + } + return 0; + } +@@ -1857,15 +1875,23 @@ child (void * param) + condlog(2, "--------start up--------"); + condlog(2, "read " DEFAULT_CONFIGFILE); + +- if (load_config(DEFAULT_CONFIGFILE, udev)) ++ if (load_config(DEFAULT_CONFIGFILE, udev)) { ++ condlog(0, "failed to load config"); ++ if (logsink) ++ log_thread_stop(); + exit(1); ++ } + + if (init_checkers()) { + condlog(0, "failed to initialize checkers"); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + if (init_prio()) { + condlog(0, "failed to initialize prioritizers"); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + +@@ -1898,8 +1924,12 @@ child (void * param) + } + + vecs = gvecs = init_vecs(); +- if (!vecs) ++ if (!vecs) { ++ condlog(0, "failed to create vecs"); ++ if (logsink) ++ log_thread_stop(); + exit(1); ++ } + + setscheduler(); + set_oom_adj(); +@@ -1911,11 +1941,15 @@ child (void * param) + */ + if ((rc = pthread_create(&uevent_thr, &uevent_attr, ueventloop, udev))) { + condlog(0, "failed to create uevent thread: %d", rc); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + pthread_attr_destroy(&uevent_attr); + if ((rc = pthread_create(&uxlsnr_thr, &misc_attr, uxlsnrloop, vecs))) { + condlog(0, "failed to create cli listener: %d", rc); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + /* +@@ -1927,6 +1961,8 @@ child (void * param) + if (configure(vecs, 1)) { + unlock(vecs->lock); + condlog(0, "failure during configuration"); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + unlock(vecs->lock); +@@ -1936,10 +1972,14 @@ child (void * param) + */ + if ((rc = pthread_create(&check_thr, &misc_attr, checkerloop, vecs))) { + condlog(0,"failed to create checker loop thread: %d", rc); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + if ((rc = pthread_create(&uevq_thr, &misc_attr, uevqloop, vecs))) { + condlog(0, "failed to create uevent dispatcher: %d", rc); ++ if (logsink) ++ log_thread_stop(); + exit(1); + } + pthread_attr_destroy(&misc_attr); diff --git a/0189-RHBZ-1368211-remove-retries.patch b/0189-RHBZ-1368211-remove-retries.patch new file mode 100644 index 0000000..253d572 --- /dev/null +++ b/0189-RHBZ-1368211-remove-retries.patch @@ -0,0 +1,168 @@ +--- + libmultipath/config.c | 1 + + libmultipath/config.h | 1 + + libmultipath/devmapper.c | 35 +++++++++++++++++++---------------- + libmultipath/dict.c | 25 +++++++++++++++++++++++++ + multipath.conf.defaults | 1 + + multipath/multipath.conf.5 | 5 +++++ + 6 files changed, 52 insertions(+), 16 deletions(-) + +Index: multipath-tools-130222/libmultipath/devmapper.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/devmapper.c ++++ multipath-tools-130222/libmultipath/devmapper.c +@@ -803,10 +803,11 @@ dm_flush_map_nopaths(const char * mapnam + extern int + dm_suspend_and_flush_map (const char * mapname) + { +- int s = 0, queue_if_no_path = 0; ++ int need_reset = 0, queue_if_no_path = 0; + unsigned long long mapsize; + char params[PARAMS_SIZE] = {0}; + int udev_flags = 0; ++ int retries = conf->remove_retries; + + if (!dm_is_mpath(mapname)) + return 0; /* nothing to do */ +@@ -821,22 +822,24 @@ dm_suspend_and_flush_map (const char * m + queue_if_no_path = 1; + } + +- if (queue_if_no_path) +- s = dm_queue_if_no_path((char *)mapname, 0); +- /* Leave queue_if_no_path alone if unset failed */ +- if (s) +- queue_if_no_path = 0; +- else +- s = dm_simplecmd_flush(DM_DEVICE_SUSPEND, mapname, 0, 0); +- +- if (!dm_flush_map(mapname)) { +- condlog(4, "multipath map %s removed", mapname); +- return 0; +- } ++ if (queue_if_no_path && dm_queue_if_no_path((char *)mapname, 0) == 0) ++ need_reset = 1; ++ ++ do { ++ if (!queue_if_no_path || need_reset) ++ dm_simplecmd_flush(DM_DEVICE_SUSPEND, mapname, 0, 0); ++ ++ if (!dm_flush_map(mapname)) { ++ condlog(4, "multipath map %s removed", mapname); ++ return 0; ++ } ++ dm_simplecmd_noflush(DM_DEVICE_RESUME, mapname, udev_flags); ++ if (retries) ++ sleep(1); ++ } while (retries-- > 0); + condlog(2, "failed to remove multipath map %s", mapname); +- dm_simplecmd_noflush(DM_DEVICE_RESUME, mapname, udev_flags); +- if (queue_if_no_path) +- s = dm_queue_if_no_path((char *)mapname, 1); ++ if (need_reset) ++ dm_queue_if_no_path((char *)mapname, 1); + return 1; + } + +Index: multipath-tools-130222/libmultipath/config.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.c ++++ multipath-tools-130222/libmultipath/config.c +@@ -680,6 +680,7 @@ load_config (char * file, struct udev *u + conf->new_bindings_in_boot = 0; + conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; + conf->skip_kpartx = DEFAULT_SKIP_KPARTX; ++ conf->remove_retries = 0; + + /* + * preload default hwtable +Index: multipath-tools-130222/libmultipath/config.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.h ++++ multipath-tools-130222/libmultipath/config.h +@@ -146,6 +146,7 @@ struct config { + int delayed_reconfig; + int uev_wait_timeout; + int skip_kpartx; ++ int remove_retries; + unsigned int version[3]; + + char * dev; +Index: multipath-tools-130222/libmultipath/dict.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/dict.c ++++ multipath-tools-130222/libmultipath/dict.c +@@ -935,6 +935,24 @@ def_new_bindings_in_boot_handler(vector + return 0; + } + ++static int ++def_remove_retries_handler(vector strvec) ++{ ++ char *buff; ++ ++ buff = set_value(strvec); ++ ++ if (!buff) ++ return 1; ++ ++ conf->remove_retries = atoi(buff); ++ if (conf->remove_retries < 0) ++ conf->remove_retries = 0; ++ FREE(buff); ++ ++ return 0; ++} ++ + /* + * blacklist block handlers + */ +@@ -3405,6 +3423,12 @@ snprint_def_new_bindings_in_boot(char * + } + + static int ++snprint_def_remove_retries (char * buff, int len, void * data) ++{ ++ return snprintf(buff, len, "%i", conf->remove_retries); ++} ++ ++static int + snprint_ble_simple (char * buff, int len, void * data) + { + struct blentry * ble = (struct blentry *)data; +@@ -3483,6 +3507,7 @@ init_keywords(void) + install_keyword("retrigger_delay", &def_retrigger_delay_handler, &snprint_def_retrigger_delay); + install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout); + install_keyword("new_bindings_in_boot", &def_new_bindings_in_boot_handler, &snprint_def_new_bindings_in_boot); ++ install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); + __deprecated install_keyword("default_selector", &def_selector_handler, NULL); + __deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL); + __deprecated install_keyword("default_uid_attribute", &def_uid_attribute_handler, NULL); +Index: multipath-tools-130222/multipath.conf.defaults +=================================================================== +--- multipath-tools-130222.orig/multipath.conf.defaults ++++ multipath-tools-130222/multipath.conf.defaults +@@ -41,6 +41,7 @@ + # retrigger_delay 10 + # missing_uev_wait_timeout 30 + # new_bindings_in_boot no ++# remove_retries 0 + #} + #blacklist { + # devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" +Index: multipath-tools-130222/multipath/multipath.conf.5 +=================================================================== +--- multipath-tools-130222.orig/multipath/multipath.conf.5 ++++ multipath-tools-130222/multipath/multipath.conf.5 +@@ -556,6 +556,11 @@ user_friendly_names. When multipathd is + regular filesystem, the device will be renamed to a user_friendly_name. The + default is + .I no ++.TP ++.B remove_retries ++This sets how may times multipath will retry removing a device that is in-use. ++Between each attempt, multipath will sleep 1 second. The default is ++.I 0 + . + .SH "blacklist section" + The diff --git a/0190-RHBZ-1380602-rbd-lock-on-read.patch b/0190-RHBZ-1380602-rbd-lock-on-read.patch new file mode 100644 index 0000000..584d37f --- /dev/null +++ b/0190-RHBZ-1380602-rbd-lock-on-read.patch @@ -0,0 +1,38 @@ +--- + libmultipath/checkers/rbd.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/checkers/rbd.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/checkers/rbd.c ++++ multipath-tools-130222/libmultipath/checkers/rbd.c +@@ -45,6 +45,7 @@ struct rbd_checker_context { + char *username; + int remapped; + int blacklisted; ++ int lock_on_read:1; + + rados_t cluster; + +@@ -141,6 +142,9 @@ int libcheck_init(struct checker * c) + goto free_addr; + } + ++ if (strstr(config_info, "lock_on_read")) ++ ct->lock_on_read = 1; ++ + ct->config_info = strdup(config_info); + if (!ct->config_info) + goto free_addr; +@@ -397,7 +401,10 @@ static int rbd_remap(struct rbd_checker_ + case 0: + argv[i++] = "rbd"; + argv[i++] = "map"; +- argv[i++] = "-o noshare"; ++ if (ct->lock_on_read) ++ argv[i++] = "-o noshare,lock_on_read"; ++ else ++ argv[i++] = "-o noshare"; + if (ct->username) { + argv[i++] = "--id"; + argv[i++] = ct->username; diff --git a/0191-RHBZ-1169168-disable-changed-paths.patch b/0191-RHBZ-1169168-disable-changed-paths.patch new file mode 100644 index 0000000..e7e9286 --- /dev/null +++ b/0191-RHBZ-1169168-disable-changed-paths.patch @@ -0,0 +1,210 @@ +--- + libmultipath/config.c | 1 + + libmultipath/config.h | 1 + + libmultipath/dict.c | 33 +++++++++++++++++++++++++++++++++ + libmultipath/discovery.c | 11 ++++++----- + libmultipath/discovery.h | 1 + + libmultipath/structs.h | 1 + + multipathd/main.c | 26 ++++++++++++++++++++++++++ + 7 files changed, 69 insertions(+), 5 deletions(-) + +Index: multipath-tools-130222/libmultipath/discovery.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.c ++++ multipath-tools-130222/libmultipath/discovery.c +@@ -1155,8 +1155,8 @@ free_dev: + return ret; + } + +-static int +-get_uid (struct path * pp) ++int ++get_uid (struct path * pp, struct udev_device *udev) + { + char *c; + const char *value; +@@ -1165,7 +1165,7 @@ get_uid (struct path * pp) + if (!pp->uid_attribute) + select_getuid(pp); + +- if (!pp->udev) { ++ if (!udev) { + condlog(1, "%s: no udev information", pp->dev); + return 1; + } +@@ -1180,7 +1180,7 @@ get_uid (struct path * pp) + pp->tick = conf->retrigger_delay; + } + } else { +- value = udev_device_get_property_value(pp->udev, ++ value = udev_device_get_property_value(udev, + pp->uid_attribute); + if ((!value || strlen(value) == 0) && + conf->cmd == CMD_VALID_PATH) +@@ -1194,6 +1194,7 @@ get_uid (struct path * pp) + len = strlen(value); + } + strncpy(pp->wwid, value, len); ++ condlog(4, "%s: got wwid of '%s'", pp->dev, pp->wwid); + pp->missing_udev_info = INFO_OK; + pp->tick = 0; + } else { +@@ -1282,7 +1283,7 @@ pathinfo (struct path *pp, vector hwtabl + } + + if ((mask & DI_WWID) && !strlen(pp->wwid)) +- get_uid(pp); ++ get_uid(pp, pp->udev); + if (mask & DI_BLACKLIST && mask & DI_WWID) { + if (filter_wwid(conf->blist_wwid, conf->elist_wwid, + pp->wwid) > 0) { +Index: multipath-tools-130222/libmultipath/discovery.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.h ++++ multipath-tools-130222/libmultipath/discovery.h +@@ -44,6 +44,7 @@ int sysfs_set_scsi_tmo (struct multipath + int sysfs_get_timeout(struct path *pp, unsigned int *timeout); + int sysfs_get_host_pci_name(struct path *pp, char *pci_name); + int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address); ++int get_uid (struct path * pp, struct udev_device *udev); + + /* + * discovery bitmask +Index: multipath-tools-130222/libmultipath/structs.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/structs.h ++++ multipath-tools-130222/libmultipath/structs.h +@@ -209,6 +209,7 @@ struct path { + int fd; + int missing_udev_info; + int retriggers; ++ int wwid_changed; + + /* configlet pointers */ + struct hwentry * hwe; +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -784,6 +784,26 @@ uev_update_path (struct uevent *uev, str + if (pp->missing_udev_info == INFO_REQUESTED) + return uev_add_path(uev, vecs); + ++ if (conf->disable_changed_wwids && ++ (strlen(pp->wwid) || pp->wwid_changed)) { ++ char wwid[WWID_SIZE]; ++ ++ strcpy(wwid, pp->wwid); ++ get_uid(pp, uev->udev); ++ if (strcmp(wwid, pp->wwid) != 0) { ++ condlog(0, "%s: path wwid changed from '%s' to '%s'. disallowing", uev->kernel, wwid, pp->wwid); ++ strcpy(pp->wwid, wwid); ++ if (!pp->wwid_changed) { ++ pp->wwid_changed = 1; ++ pp->tick = 1; ++ dm_fail_path(pp->mpp->alias, pp->dev_t); ++ } ++ } ++ else { ++ pp->wwid_changed = 0; ++ } ++ } ++ + /* reinit the prio values on change event, in case something is + * different */ + prio_init(&pp->prio); +@@ -1284,6 +1304,12 @@ check_path (struct vectors * vecs, struc + else + checker_clear_message(&pp->checker); + ++ if (pp->wwid_changed) { ++ condlog(2, "%s: path wwid has changed. Refusing to use", ++ pp->dev); ++ newstate = PATH_DOWN; ++ } ++ + if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) { + condlog(2, "%s: unusable path", pp->dev); + pathinfo(pp, conf->hwtable, 0); +Index: multipath-tools-130222/libmultipath/config.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.c ++++ multipath-tools-130222/libmultipath/config.c +@@ -681,6 +681,7 @@ load_config (char * file, struct udev *u + conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; + conf->skip_kpartx = DEFAULT_SKIP_KPARTX; + conf->remove_retries = 0; ++ conf->disable_changed_wwids = 0; + + /* + * preload default hwtable +Index: multipath-tools-130222/libmultipath/config.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.h ++++ multipath-tools-130222/libmultipath/config.h +@@ -147,6 +147,7 @@ struct config { + int uev_wait_timeout; + int skip_kpartx; + int remove_retries; ++ int disable_changed_wwids; + unsigned int version[3]; + + char * dev; +Index: multipath-tools-130222/libmultipath/dict.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/dict.c ++++ multipath-tools-130222/libmultipath/dict.c +@@ -953,6 +953,29 @@ def_remove_retries_handler(vector strvec + return 0; + } + ++static int ++def_disable_changed_wwids_handler(vector strvec) ++{ ++ char * buff; ++ ++ buff = set_value(strvec); ++ ++ if (!buff) ++ return 1; ++ ++ if ((strlen(buff) == 2 && !strcmp(buff, "no")) || ++ (strlen(buff) == 1 && !strcmp(buff, "0"))) ++ conf->disable_changed_wwids = 0; ++ else if ((strlen(buff) == 3 && !strcmp(buff, "yes")) || ++ (strlen(buff) == 1 && !strcmp(buff, "1"))) ++ conf->disable_changed_wwids = 1; ++ else ++ conf->disable_changed_wwids = 0; ++ ++ FREE(buff); ++ return 0; ++} ++ + /* + * blacklist block handlers + */ +@@ -3429,6 +3452,15 @@ snprint_def_remove_retries (char * buff, + } + + static int ++snprint_def_disable_changed_wwids(char * buff, int len, void * data) ++{ ++ if (conf->disable_changed_wwids == 1) ++ return snprintf(buff, len, "yes"); ++ else ++ return snprintf(buff, len, "no"); ++} ++ ++static int + snprint_ble_simple (char * buff, int len, void * data) + { + struct blentry * ble = (struct blentry *)data; +@@ -3508,6 +3540,7 @@ init_keywords(void) + install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout); + install_keyword("new_bindings_in_boot", &def_new_bindings_in_boot_handler, &snprint_def_new_bindings_in_boot); + install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); ++ install_keyword("disable_changed_wwids", &def_disable_changed_wwids_handler, &snprint_def_disable_changed_wwids); + __deprecated install_keyword("default_selector", &def_selector_handler, NULL); + __deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL); + __deprecated install_keyword("default_uid_attribute", &def_uid_attribute_handler, NULL); diff --git a/0192-RHBZ-1362409-infinibox-config.patch b/0192-RHBZ-1362409-infinibox-config.patch new file mode 100644 index 0000000..dd56f8b --- /dev/null +++ b/0192-RHBZ-1362409-infinibox-config.patch @@ -0,0 +1,34 @@ +--- + libmultipath/hwtable.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +Index: multipath-tools-130222/libmultipath/hwtable.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/hwtable.c ++++ multipath-tools-130222/libmultipath/hwtable.c +@@ -1168,6 +1168,25 @@ static struct hwentry default_hw[] = { + .prio_name = PRIO_ALUA, + .prio_args = NULL, + }, ++ /* ++ * Infinidat ++ */ ++ { ++ .vendor = "NFINIDAT", ++ .product = "InfiniBox.*", ++ .features = DEFAULT_FEATURES, ++ .hwhandler = DEFAULT_HWHANDLER, ++ .pgpolicy = GROUP_BY_PRIO, ++ .pgfailback = 30, ++ .rr_weight = RR_WEIGHT_PRIO, ++ .no_path_retry = NO_PATH_RETRY_FAIL, ++ .checker_name = TUR, ++ .prio_name = PRIO_ALUA, ++ .prio_args = NULL, ++ .selector = "round-robin 0", ++ .flush_on_last_del = FLUSH_ENABLED, ++ .dev_loss = 30, ++ }, + { + .vendor = "XtremIO", + .product = "XtremApp", diff --git a/0194-RHBZ-1351964-kpartx-recurse.patch b/0194-RHBZ-1351964-kpartx-recurse.patch new file mode 100644 index 0000000..be5b22e --- /dev/null +++ b/0194-RHBZ-1351964-kpartx-recurse.patch @@ -0,0 +1,17 @@ +--- + kpartx/dos.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: multipath-tools-130222/kpartx/dos.c +=================================================================== +--- multipath-tools-130222.orig/kpartx/dos.c ++++ multipath-tools-130222/kpartx/dos.c +@@ -46,7 +46,7 @@ read_extended_partition(int fd, struct p + for (i=0; i<2; i++) { + memcpy(&p, bp + 0x1be + i * sizeof (p), sizeof (p)); + if (is_extended(p.sys_type)) { +- if (p.nr_sects && !moretodo) { ++ if (p.start_sect && p.nr_sects && !moretodo) { + next = start + sector_size_mul * le32_to_cpu(p.start_sect); + moretodo = 1; + } diff --git a/0195-RHBZ-1359510-no-daemon-msg.patch b/0195-RHBZ-1359510-no-daemon-msg.patch new file mode 100644 index 0000000..667f10d --- /dev/null +++ b/0195-RHBZ-1359510-no-daemon-msg.patch @@ -0,0 +1,111 @@ +--- + libmultipath/configure.c | 21 ++++++++++++++++++++- + libmultipath/configure.h | 1 + + multipath/main.c | 21 +++++++++++++++++++++ + 3 files changed, 42 insertions(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -743,7 +743,8 @@ deadmap (struct multipath * mpp) + return 1; /* dead */ + } + +-int check_daemon(void) ++extern int ++check_daemon(void) + { + int fd; + char *reply; +@@ -776,6 +777,7 @@ coalesce_paths (struct vectors * vecs, v + { + int r = 1; + int k, i; ++ int map_processed = 0; + char empty_buff[WWID_SIZE]; + char params[PARAMS_SIZE]; + struct multipath * mpp; +@@ -936,6 +938,13 @@ coalesce_paths (struct vectors * vecs, v + else + remove_map(mpp, vecs, 0); + } ++ ++ /* By now at least one multipath device map is processed, ++ * so set map_processed = 1 ++ */ ++ if (!map_processed) ++ map_processed = 1; ++ + } + /* + * Flush maps with only dead paths (ie not in sysfs) +@@ -963,6 +972,16 @@ coalesce_paths (struct vectors * vecs, v + condlog(2, "%s: remove (dead)", alias); + } + } ++ ++ /* If there is at least one multipath device map processed then ++ * check if 'multipathd' service is running or not? ++ */ ++ if (map_processed) { ++ if (!conf->daemon && !check_daemon()) ++ condlog(0, "'multipathd' service is currently not " ++ "running, IO failover/failback will not work"); ++ } ++ + return 0; + } + +Index: multipath-tools-130222/libmultipath/configure.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.h ++++ multipath-tools-130222/libmultipath/configure.h +@@ -27,6 +27,7 @@ enum actions { + int setup_map (struct multipath * mpp, char * params, int params_size ); + int domap (struct multipath * mpp, char * params); + int reinstate_paths (struct multipath *mpp); ++int check_daemon(void); + int coalesce_paths (struct vectors *vecs, vector curmp, char * refwwid, int force_reload); + int get_refwwid (char * dev, enum devtypes dev_type, vector pathvec, char **wwid); + int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh); +Index: multipath-tools-130222/multipath/main.c +=================================================================== +--- multipath-tools-130222.orig/multipath/main.c ++++ multipath-tools-130222/multipath/main.c +@@ -178,6 +178,7 @@ static int + get_dm_mpvec (vector curmp, vector pathvec, char * refwwid) + { + int i; ++ int maps_present = 0; + struct multipath * mpp; + char params[PARAMS_SIZE], status[PARAMS_SIZE]; + +@@ -226,7 +227,27 @@ get_dm_mpvec (vector curmp, vector pathv + + if (conf->cmd == CMD_CREATE) + reinstate_paths(mpp); ++ ++ /* At this place we have found at least one multipath ++ * device map, so set maps_present = 1 ++ */ ++ if (!maps_present) ++ maps_present = 1; ++ + } ++ ++ /* If there is at least one multipath device map present then ++ * check if 'multipathd' service is running or not? ++ */ ++ if (maps_present) { ++ if (!conf->daemon && !check_daemon()) { ++ condlog(0, "multipath device maps are present, but " ++ "'multipathd' service is not running"); ++ condlog(0, "IO failover/failback will not work without " ++ "'multipathd' service running"); ++ } ++ } ++ + return 0; + } + diff --git a/0196-RHBZ-1239173-dont-set-flag.patch b/0196-RHBZ-1239173-dont-set-flag.patch new file mode 100644 index 0000000..269edc8 --- /dev/null +++ b/0196-RHBZ-1239173-dont-set-flag.patch @@ -0,0 +1,38 @@ +--- + libmultipath/configure.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -257,7 +257,7 @@ extern int + setup_map (struct multipath * mpp, char * params, int params_size) + { + struct pathgroup * pgp; +- int i; ++ int i, old_nr_active; + + /* + * don't bother if devmap size is unknown +@@ -311,8 +311,12 @@ setup_map (struct multipath * mpp, char + if (mpp->pgpolicyfn && mpp->pgpolicyfn(mpp)) + return 1; + ++ old_nr_active = mpp->nr_active; + mpp->nr_active = pathcount(mpp, PATH_UP) + pathcount(mpp, PATH_GHOST); + ++ if (mpp->nr_active && !old_nr_active) ++ mpp->force_udev_reload = 1; ++ + /* + * ponders each path group and determine highest prio pg + * to switch over (default to first) +@@ -445,7 +449,6 @@ select_action (struct multipath * mpp, v + mpp->alias); + return; + } +- mpp->force_udev_reload = !pathcount(mpp, PATH_WILD); + if (cmpp->size != mpp->size) { + mpp->force_udev_reload = 1; + mpp->action = ACT_RESIZE; diff --git a/0197-RHBZ-1394059-max-sectors-kb.patch b/0197-RHBZ-1394059-max-sectors-kb.patch new file mode 100644 index 0000000..7ed778d --- /dev/null +++ b/0197-RHBZ-1394059-max-sectors-kb.patch @@ -0,0 +1,474 @@ +--- + libmultipath/config.c | 3 + + libmultipath/config.h | 3 + + libmultipath/configure.c | 1 + libmultipath/defaults.h | 1 + libmultipath/devmapper.c | 4 +- + libmultipath/dict.c | 87 +++++++++++++++++++++++++++++++++++++++++++++ + libmultipath/discovery.c | 60 +++++++++++++++++++++++++++++++ + libmultipath/discovery.h | 1 + libmultipath/propsel.c | 25 ++++++++++++ + libmultipath/propsel.h | 1 + libmultipath/structs.h | 7 +++ + multipath/multipath.conf.5 | 8 ++++ + 12 files changed, 200 insertions(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/config.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.c ++++ multipath-tools-130222/libmultipath/config.c +@@ -344,6 +344,7 @@ merge_hwe (struct hwentry * dst, struct + merge_num(delay_watch_checks); + merge_num(delay_wait_checks); + merge_num(skip_kpartx); ++ merge_num(max_sectors_kb); + + /* + * Make sure features is consistent with +@@ -405,6 +406,7 @@ overwrite_hwe (struct hwentry * dst, str + overwrite_num(delay_watch_checks); + overwrite_num(delay_wait_checks); + overwrite_num(skip_kpartx); ++ overwrite_num(max_sectors_kb); + + /* + * Make sure features is consistent with +@@ -682,6 +684,7 @@ load_config (char * file, struct udev *u + conf->skip_kpartx = DEFAULT_SKIP_KPARTX; + conf->remove_retries = 0; + conf->disable_changed_wwids = 0; ++ conf->max_sectors_kb = DEFAULT_MAX_SECTORS_KB; + + /* + * preload default hwtable +Index: multipath-tools-130222/libmultipath/config.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.h ++++ multipath-tools-130222/libmultipath/config.h +@@ -65,6 +65,7 @@ struct hwentry { + int delay_watch_checks; + int delay_wait_checks; + int skip_kpartx; ++ int max_sectors_kb; + char * bl_product; + }; + +@@ -92,6 +93,7 @@ struct mpentry { + int delay_watch_checks; + int delay_wait_checks; + int skip_kpartx; ++ int max_sectors_kb; + uid_t uid; + gid_t gid; + mode_t mode; +@@ -148,6 +150,7 @@ struct config { + int skip_kpartx; + int remove_retries; + int disable_changed_wwids; ++ int max_sectors_kb; + unsigned int version[3]; + + char * dev; +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -295,6 +295,7 @@ setup_map (struct multipath * mpp, char + select_delay_watch_checks(mpp); + select_delay_wait_checks(mpp); + select_skip_kpartx(mpp); ++ select_max_sectors_kb(mpp); + + sysfs_set_scsi_tmo(mpp); + /* +Index: multipath-tools-130222/libmultipath/defaults.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/defaults.h ++++ multipath-tools-130222/libmultipath/defaults.h +@@ -25,6 +25,7 @@ + #define DEFAULT_RETRIGGER_TRIES 3 + #define DEFAULT_UEV_WAIT_TIMEOUT 30 + #define DEFAULT_SKIP_KPARTX SKIP_KPARTX_OFF ++#define DEFAULT_MAX_SECTORS_KB MAX_SECTORS_KB_UNDEF + + #define DEFAULT_CHECKINT 5 + #define MAX_CHECKINT(a) (a << 2) +Index: multipath-tools-130222/libmultipath/dict.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/dict.c ++++ multipath-tools-130222/libmultipath/dict.c +@@ -976,6 +976,22 @@ def_disable_changed_wwids_handler(vector + return 0; + } + ++static int ++def_max_sectors_kb_handler(vector strvec) ++{ ++ char * buff; ++ ++ buff = set_value(strvec); ++ if (!buff) ++ return 1; ++ ++ if ((conf->max_sectors_kb = atoi(buff)) < MAX_SECTORS_KB_MIN) ++ conf->max_sectors_kb = MAX_SECTORS_KB_UNDEF; ++ ++ FREE(buff); ++ return 0; ++} ++ + /* + * blacklist block handlers + */ +@@ -1765,6 +1781,26 @@ hw_delay_wait_checks_handler(vector strv + return 0; + } + ++static int ++hw_max_sectors_kb_handler(vector strvec) ++{ ++ struct hwentry *hwe = VECTOR_LAST_SLOT(conf->hwtable); ++ char * buff; ++ ++ if (!hwe) ++ return 1; ++ ++ buff = set_value(strvec); ++ if (!buff) ++ return 1; ++ ++ if ((hwe->max_sectors_kb = atoi(buff)) < MAX_SECTORS_KB_MIN) ++ hwe->max_sectors_kb = MAX_SECTORS_KB_UNDEF; ++ ++ FREE(buff); ++ return 0; ++} ++ + /* + * multipaths block handlers + */ +@@ -2316,6 +2352,26 @@ mp_delay_wait_checks_handler(vector strv + return 0; + } + ++static int ++mp_max_sectors_kb_handler(vector strvec) ++{ ++ struct mpentry *mpe = VECTOR_LAST_SLOT(conf->mptable); ++ char * buff; ++ ++ if (!mpe) ++ return 1; ++ ++ buff = set_value(strvec); ++ if (!buff) ++ return 1; ++ ++ if ((mpe->max_sectors_kb = atoi(buff)) < MAX_SECTORS_KB_MIN) ++ mpe->max_sectors_kb = MAX_SECTORS_KB_UNDEF; ++ ++ FREE(buff); ++ return 0; ++} ++ + /* + * config file keywords printing + */ +@@ -2615,6 +2671,16 @@ snprint_mp_delay_wait_checks(char * buff + } + + static int ++snprint_mp_max_sectors_kb(char * buff, int len, void * data) ++{ ++ struct mpentry * mpe = (struct mpentry *)data; ++ ++ if (mpe->max_sectors_kb == MAX_SECTORS_KB_UNDEF) ++ return 0; ++ return snprintf(buff, len, "%d", mpe->max_sectors_kb); ++} ++ ++static int + snprint_hw_fast_io_fail(char * buff, int len, void * data) + { + struct hwentry * hwe = (struct hwentry *)data; +@@ -2993,6 +3059,16 @@ snprint_detect_prio(char * buff, int len + } + + static int ++snprint_hw_max_sectors_kb(char * buff, int len, void * data) ++{ ++ struct hwentry * hwe = (struct hwentry *)data; ++ ++ if (hwe->max_sectors_kb == MAX_SECTORS_KB_UNDEF) ++ return 0; ++ return snprintf(buff, len, "%d", hwe->max_sectors_kb); ++} ++ ++static int + snprint_def_polling_interval (char * buff, int len, void * data) + { + return snprintf(buff, len, "%i", conf->checkint); +@@ -3461,6 +3537,14 @@ snprint_def_disable_changed_wwids(char * + } + + static int ++snprint_def_max_sectors_kb(char * buff, int len, void * data) ++{ ++ if (conf->max_sectors_kb == MAX_SECTORS_KB_UNDEF) ++ return 0; ++ return snprintf(buff, len, "%d", conf->max_sectors_kb); ++} ++ ++static int + snprint_ble_simple (char * buff, int len, void * data) + { + struct blentry * ble = (struct blentry *)data; +@@ -3541,6 +3625,7 @@ init_keywords(void) + install_keyword("new_bindings_in_boot", &def_new_bindings_in_boot_handler, &snprint_def_new_bindings_in_boot); + install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); + install_keyword("disable_changed_wwids", &def_disable_changed_wwids_handler, &snprint_def_disable_changed_wwids); ++ install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb); + __deprecated install_keyword("default_selector", &def_selector_handler, NULL); + __deprecated install_keyword("default_path_grouping_policy", &def_pgpolicy_handler, NULL); + __deprecated install_keyword("default_uid_attribute", &def_uid_attribute_handler, NULL); +@@ -3609,6 +3694,7 @@ init_keywords(void) + install_keyword("delay_watch_checks", &hw_delay_watch_checks_handler, &snprint_hw_delay_watch_checks); + install_keyword("delay_wait_checks", &hw_delay_wait_checks_handler, &snprint_hw_delay_wait_checks); + install_keyword("skip_kpartx", &hw_skip_kpartx_handler, &snprint_hw_skip_kpartx); ++ install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb); + install_sublevel_end(); + + install_keyword_root("multipaths", &multipaths_handler); +@@ -3637,5 +3723,6 @@ init_keywords(void) + install_keyword("delay_watch_checks", &mp_delay_watch_checks_handler, &snprint_mp_delay_watch_checks); + install_keyword("delay_wait_checks", &mp_delay_wait_checks_handler, &snprint_mp_delay_wait_checks); + install_keyword("skip_kpartx", &mp_skip_kpartx_handler, &snprint_mp_skip_kpartx); ++ install_keyword("max_sectors_kb", &mp_max_sectors_kb_handler, &snprint_mp_max_sectors_kb); + install_sublevel_end(); + } +Index: multipath-tools-130222/libmultipath/discovery.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.c ++++ multipath-tools-130222/libmultipath/discovery.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "checkers.h" + #include "vector.h" +@@ -27,6 +28,7 @@ + #include "discovery.h" + #include "prio.h" + #include "defaults.h" ++#include "devmapper.h" + + int + store_pathinfo (vector pathvec, vector hwtable, struct udev_device *udevice, +@@ -166,6 +168,64 @@ declare_sysfs_get_str(rev); + declare_sysfs_get_str(dev); + + int ++sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload) ++{ ++ struct pathgroup * pgp; ++ struct path *pp; ++ char buff[11]; ++ struct udev_device *udevice = NULL; ++ int i, j, len, ret; ++ int max_sectors_kb; ++ ++ if (mpp->max_sectors_kb == MAX_SECTORS_KB_UNDEF) ++ return 0; ++ max_sectors_kb = mpp->max_sectors_kb; ++ if (is_reload) { ++ if (!mpp->dmi && dm_get_info(mpp->alias, &mpp->dmi) != 0) { ++ condlog(0, "failed to get dm info on %s to set max_sectors_kb", mpp->alias); ++ return 1; ++ } ++ udevice = udev_device_new_from_devnum(conf->udev, 'b', ++ makedev(mpp->dmi->major, ++ mpp->dmi->minor)); ++ if (!udevice) { ++ condlog(0, "failed to get udev device to set max_sectors_kb for %s", mpp->alias); ++ return 1; ++ } ++ if (sysfs_attr_get_value(udevice, "queue/max_sectors_kb", ++ buff, sizeof(buff)) <= 0) { ++ condlog(0, "failed to get current max_sectors_kb from %s", mpp->alias); ++ goto fail_reload; ++ } ++ if (sscanf(buff, "%u\n", &max_sectors_kb) != 1) { ++ condlog(0, "can't parse current max_sectors_kb from %s", ++ mpp->alias); ++ goto fail_reload; ++ } ++ udev_device_unref(udevice); ++ } ++ snprintf(buff, 11, "%d", max_sectors_kb); ++ len = strlen(buff); ++ ++ vector_foreach_slot (mpp->pg, pgp, i) { ++ vector_foreach_slot (pgp->paths, pp, j) { ++ ret = sysfs_attr_set_value(pp->udev, ++ "queue/max_sectors_kb", ++ buff, len); ++ if (ret < 0) { ++ condlog(0, "failed setting max_sectors_kb on %s : %s", pp->dev, strerror(-ret)); ++ return 1; ++ } ++ } ++ } ++ return 0; ++ ++fail_reload: ++ udev_device_unref(udevice); ++ return 1; ++} ++ ++int + sysfs_get_timeout(struct path *pp, unsigned int *timeout) + { + const char *attr = NULL; +Index: multipath-tools-130222/libmultipath/discovery.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.h ++++ multipath-tools-130222/libmultipath/discovery.h +@@ -41,6 +41,7 @@ int store_pathinfo (vector pathvec, vect + struct udev_device *udevice, int flag, + struct path **pp_ptr); + int sysfs_set_scsi_tmo (struct multipath *mpp); ++int sysfs_set_max_sectors_kb(struct multipath *mpp, int is_reload); + int sysfs_get_timeout(struct path *pp, unsigned int *timeout); + int sysfs_get_host_pci_name(struct path *pp, char *pci_name); + int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address); +Index: multipath-tools-130222/libmultipath/propsel.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/propsel.c ++++ multipath-tools-130222/libmultipath/propsel.c +@@ -880,3 +880,28 @@ select_skip_kpartx (struct multipath * m + condlog(3, "skip_kpartx = DISABLED (internal default)"); + return 0; + } ++ ++extern int ++select_max_sectors_kb (struct multipath * mp) ++{ ++ if (mp->mpe && mp->mpe->max_sectors_kb != MAX_SECTORS_KB_UNDEF) { ++ mp->max_sectors_kb = mp->mpe->max_sectors_kb; ++ condlog(3, "max_sectors_kb = %i (multipath setting)", ++ mp->max_sectors_kb); ++ return 0; ++ } ++ if (mp->hwe && mp->hwe->max_sectors_kb != MAX_SECTORS_KB_UNDEF) { ++ mp->max_sectors_kb = mp->hwe->max_sectors_kb; ++ condlog(3, "max_sectors_kb = %i (controler setting)", ++ mp->max_sectors_kb); ++ return 0; ++ } ++ if (conf->max_sectors_kb != MAX_SECTORS_KB_UNDEF) { ++ mp->max_sectors_kb = conf->max_sectors_kb; ++ condlog(3, "max_sectors_kb = %i (config file default)", ++ mp->max_sectors_kb); ++ return 0; ++ } ++ mp->max_sectors_kb = MAX_SECTORS_KB_UNDEF; ++ return 0; ++} +Index: multipath-tools-130222/libmultipath/propsel.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/propsel.h ++++ multipath-tools-130222/libmultipath/propsel.h +@@ -24,3 +24,4 @@ int select_deferred_remove(struct multip + int select_delay_watch_checks (struct multipath * mp); + int select_delay_wait_checks (struct multipath * mp); + int select_skip_kpartx (struct multipath * mp); ++int select_max_sectors_kb (struct multipath * mp); +Index: multipath-tools-130222/libmultipath/structs.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/structs.h ++++ multipath-tools-130222/libmultipath/structs.h +@@ -128,6 +128,12 @@ enum skip_kpartx_states { + SKIP_KPARTX_ON, + }; + ++ ++enum max_sectors_kb_states { ++ MAX_SECTORS_KB_UNDEF = 0, ++ MAX_SECTORS_KB_MIN = 4, /* can't be smaller than page size */ ++}; ++ + enum scsi_protocol { + SCSI_PROTOCOL_FCP = 0, /* Fibre Channel */ + SCSI_PROTOCOL_SPI = 1, /* parallel SCSI */ +@@ -245,6 +251,7 @@ struct multipath { + int delay_wait_checks; + int force_udev_reload; + int skip_kpartx; ++ int max_sectors_kb; + unsigned int dev_loss; + uid_t uid; + gid_t gid; +Index: multipath-tools-130222/multipath/multipath.conf.5 +=================================================================== +--- multipath-tools-130222.orig/multipath/multipath.conf.5 ++++ multipath-tools-130222/multipath/multipath.conf.5 +@@ -561,6 +561,10 @@ default is + This sets how may times multipath will retry removing a device that is in-use. + Between each attempt, multipath will sleep 1 second. The default is + .I 0 ++.TP ++.B max_sectors_kb ++Sets the max_sectors_kb device parameter on all path devices and the multipath ++device to the specified value. Default is device dependent. + . + .SH "blacklist section" + The +@@ -672,6 +676,8 @@ section: + .B delay_wait_checks + .TP + .B skip_kpartx ++.TP ++.B max_sectors_kb + .RE + .PD + .LP +@@ -772,6 +778,8 @@ section: + .B delay_wait_checks + .TP + .B skip_kpartx ++.TP ++.B max_sectors_kb + .RE + .PD + .LP +Index: multipath-tools-130222/libmultipath/devmapper.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/devmapper.c ++++ multipath-tools-130222/libmultipath/devmapper.c +@@ -21,7 +21,7 @@ + #include "devmapper.h" + #include "config.h" + #include "sysfs.h" +- ++#include "discovery.h" + #include "log_pthread.h" + #include + #include +@@ -330,6 +330,7 @@ extern int + dm_addmap_create (struct multipath *mpp, char * params) { + int ro; + ++ sysfs_set_max_sectors_kb(mpp, 0); + for (ro = 0; ro <= 1; ro++) { + int err; + +@@ -356,6 +357,7 @@ dm_addmap_create (struct multipath *mpp, + + extern int + dm_addmap_reload (struct multipath *mpp, char *params) { ++ sysfs_set_max_sectors_kb(mpp, 1); + if (dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, 0, ADDMAP_RW, SKIP_KPARTX_OFF)) + return 1; + if (errno != EROFS) diff --git a/0198-RHBZ-1372032-detect-path-checker.patch b/0198-RHBZ-1372032-detect-path-checker.patch new file mode 100644 index 0000000..48fcfce --- /dev/null +++ b/0198-RHBZ-1372032-detect-path-checker.patch @@ -0,0 +1,377 @@ +--- + libmultipath/config.c | 4 ++ + libmultipath/config.h | 2 + + libmultipath/defaults.h | 1 + libmultipath/dict.c | 74 +++++++++++++++++++++++++++++++++++++++++++++ + libmultipath/discovery.c | 1 + libmultipath/hwtable.c | 1 + libmultipath/propsel.c | 65 +++++++++++++++++++++++++++++++-------- + libmultipath/propsel.h | 1 + libmultipath/structs.h | 7 ++++ + multipath/multipath.conf.5 | 9 +++++ + 10 files changed, 152 insertions(+), 13 deletions(-) + +Index: multipath-tools-130222/libmultipath/config.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.c ++++ multipath-tools-130222/libmultipath/config.c +@@ -340,6 +340,7 @@ merge_hwe (struct hwentry * dst, struct + merge_num(user_friendly_names); + merge_num(retain_hwhandler); + merge_num(detect_prio); ++ merge_num(detect_checker); + merge_num(deferred_remove); + merge_num(delay_watch_checks); + merge_num(delay_wait_checks); +@@ -402,6 +403,7 @@ overwrite_hwe (struct hwentry * dst, str + overwrite_num(user_friendly_names); + overwrite_num(retain_hwhandler); + overwrite_num(detect_prio); ++ overwrite_num(detect_checker); + overwrite_num(deferred_remove); + overwrite_num(delay_watch_checks); + overwrite_num(delay_wait_checks); +@@ -476,6 +478,7 @@ store_hwe (vector hwtable, struct hwentr + hwe->user_friendly_names = dhwe->user_friendly_names; + hwe->retain_hwhandler = dhwe->retain_hwhandler; + hwe->detect_prio = dhwe->detect_prio; ++ hwe->detect_checker = dhwe->detect_checker; + + if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product))) + goto out; +@@ -672,6 +675,7 @@ load_config (char * file, struct udev *u + conf->fast_io_fail = DEFAULT_FAST_IO_FAIL; + conf->retain_hwhandler = DEFAULT_RETAIN_HWHANDLER; + conf->detect_prio = DEFAULT_DETECT_PRIO; ++ conf->detect_checker = DEFAULT_DETECT_CHECKER; + conf->deferred_remove = DEFAULT_DEFERRED_REMOVE; + conf->hw_strmatch = 0; + conf->force_sync = 0; +Index: multipath-tools-130222/libmultipath/config.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.h ++++ multipath-tools-130222/libmultipath/config.h +@@ -61,6 +61,7 @@ struct hwentry { + int user_friendly_names; + int retain_hwhandler; + int detect_prio; ++ int detect_checker; + int deferred_remove; + int delay_watch_checks; + int delay_wait_checks; +@@ -136,6 +137,7 @@ struct config { + int reassign_maps; + int retain_hwhandler; + int detect_prio; ++ int detect_checker; + int force_sync; + int deferred_remove; + int ignore_new_boot_devs; +Index: multipath-tools-130222/libmultipath/defaults.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/defaults.h ++++ multipath-tools-130222/libmultipath/defaults.h +@@ -19,6 +19,7 @@ + #define DEFAULT_FAST_IO_FAIL 5 + #define DEFAULT_RETAIN_HWHANDLER RETAIN_HWHANDLER_OFF + #define DEFAULT_DETECT_PRIO DETECT_PRIO_OFF ++#define DEFAULT_DETECT_CHECKER DETECT_CHECKER_OFF + #define DEFAULT_DEFERRED_REMOVE DEFERRED_REMOVE_OFF + #define DEFAULT_DELAY_CHECKS DELAY_CHECKS_OFF + #define DEFAULT_RETRIGGER_DELAY 10 +Index: multipath-tools-130222/libmultipath/dict.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/dict.c ++++ multipath-tools-130222/libmultipath/dict.c +@@ -714,6 +714,29 @@ def_detect_prio_handler(vector strvec) + } + + static int ++def_detect_checker_handler(vector strvec) ++{ ++ char * buff; ++ ++ buff = set_value(strvec); ++ ++ if (!buff) ++ return 1; ++ ++ if ((strlen(buff) == 2 && !strcmp(buff, "no")) || ++ (strlen(buff) == 1 && !strcmp(buff, "0"))) ++ conf->detect_checker = DETECT_CHECKER_OFF; ++ else if ((strlen(buff) == 3 && !strcmp(buff, "yes")) || ++ (strlen(buff) == 1 && !strcmp(buff, "1"))) ++ conf->detect_checker = DETECT_CHECKER_ON; ++ else ++ conf->detect_checker = DETECT_CHECKER_UNDEF; ++ ++ FREE(buff); ++ return 0; ++} ++ ++static int + def_hw_strmatch_handler(vector strvec) + { + char *buff; +@@ -1682,6 +1705,33 @@ hw_detect_prio_handler(vector strvec) + } + + static int ++hw_detect_checker_handler(vector strvec) ++{ ++ struct hwentry *hwe = VECTOR_LAST_SLOT(conf->hwtable); ++ char * buff; ++ ++ if (!hwe) ++ return 1; ++ ++ buff = set_value(strvec); ++ ++ if (!buff) ++ return 1; ++ ++ if ((strlen(buff) == 2 && !strcmp(buff, "no")) || ++ (strlen(buff) == 1 && !strcmp(buff, "0"))) ++ hwe->detect_checker = DETECT_CHECKER_OFF; ++ else if ((strlen(buff) == 3 && !strcmp(buff, "yes")) || ++ (strlen(buff) == 1 && !strcmp(buff, "1"))) ++ hwe->detect_checker = DETECT_CHECKER_ON; ++ else ++ hwe->detect_checker = DETECT_CHECKER_UNDEF; ++ ++ FREE(buff); ++ return 0; ++} ++ ++static int + hw_deferred_remove_handler(vector strvec) + { + struct hwentry *hwe = VECTOR_LAST_SLOT(conf->hwtable); +@@ -3059,6 +3109,19 @@ snprint_detect_prio(char * buff, int len + } + + static int ++snprint_detect_checker(char * buff, int len, void * data) ++{ ++ struct hwentry * hwe = (struct hwentry *)data; ++ ++ if (hwe->detect_checker == DETECT_CHECKER_ON) ++ return snprintf(buff, len, "yes"); ++ else if (hwe->detect_checker == DETECT_CHECKER_OFF) ++ return snprintf(buff, len, "no"); ++ else ++ return 0; ++} ++ ++static int + snprint_hw_max_sectors_kb(char * buff, int len, void * data) + { + struct hwentry * hwe = (struct hwentry *)data; +@@ -3424,6 +3487,15 @@ snprint_def_detect_prio(char * buff, int + } + + static int ++snprint_def_detect_checker(char * buff, int len, void * data) ++{ ++ if (conf->detect_checker == DETECT_PRIO_ON) ++ return snprintf(buff, len, "yes"); ++ else ++ return snprintf(buff, len, "no"); ++} ++ ++static int + snprint_def_hw_strmatch(char * buff, int len, void * data) + { + if (conf->hw_strmatch) +@@ -3611,6 +3683,7 @@ init_keywords(void) + install_keyword("find_multipaths", &def_find_multipaths_handler, &snprint_def_find_multipaths); + install_keyword("retain_attached_hw_handler", &def_retain_hwhandler_handler, &snprint_def_retain_hwhandler_handler); + install_keyword("detect_prio", &def_detect_prio_handler, &snprint_def_detect_prio); ++ install_keyword("detect_path_checker", &def_detect_checker_handler, &snprint_def_detect_checker); + install_keyword("hw_str_match", &def_hw_strmatch_handler, &snprint_def_hw_strmatch); + install_keyword("force_sync", &def_force_sync_handler, &snprint_def_force_sync); + install_keyword("deferred_remove", &def_deferred_remove_handler, &snprint_def_deferred_remove); +@@ -3690,6 +3763,7 @@ init_keywords(void) + install_keyword("user_friendly_names", &hw_names_handler, &snprint_hw_user_friendly_names); + install_keyword("retain_attached_hw_handler", &hw_retain_hwhandler_handler, &snprint_hw_retain_hwhandler_handler); + install_keyword("detect_prio", &hw_detect_prio_handler, &snprint_detect_prio); ++ install_keyword("detect_path_checker", &hw_detect_checker_handler, &snprint_detect_checker); + install_keyword("deferred_remove", &hw_deferred_remove_handler, &snprint_hw_deferred_remove); + install_keyword("delay_watch_checks", &hw_delay_watch_checks_handler, &snprint_hw_delay_watch_checks); + install_keyword("delay_wait_checks", &hw_delay_wait_checks_handler, &snprint_hw_delay_wait_checks); +Index: multipath-tools-130222/libmultipath/discovery.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.c ++++ multipath-tools-130222/libmultipath/discovery.c +@@ -1107,6 +1107,7 @@ get_state (struct path * pp, int daemon) + return PATH_UNCHECKED; + } + } ++ select_detect_checker(pp); + select_checker(pp); + if (!checker_selected(c)) { + condlog(3, "%s: No checker selected", pp->dev); +Index: multipath-tools-130222/libmultipath/hwtable.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/hwtable.c ++++ multipath-tools-130222/libmultipath/hwtable.c +@@ -289,6 +289,7 @@ static struct hwentry default_hw[] = { + .prio_args = NULL, + .retain_hwhandler = RETAIN_HWHANDLER_ON, + .detect_prio = DETECT_PRIO_ON, ++ .detect_checker = DETECT_CHECKER_ON, + }, + { + .vendor = "EMC", +Index: multipath-tools-130222/libmultipath/propsel.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/propsel.c ++++ multipath-tools-130222/libmultipath/propsel.c +@@ -335,11 +335,43 @@ select_hwhandler (struct multipath * mp) + return 0; + } + ++int ++detect_alua(struct path * pp) ++{ ++ int ret; ++ int tpgs = 0; ++ ++ if ((tpgs = get_target_port_group_support(pp->fd)) <= 0) ++ return 0; ++ pp->tpgs = tpgs; ++ ret = get_target_port_group(pp->fd, NULL); ++ if (ret < 0) ++ return 0; ++ if (get_asymmetric_access_state(pp->fd, ret, NULL) < 0) ++ return 0; ++ return 1; ++} ++ ++void ++detect_checker(struct path * pp) ++{ ++ if (detect_alua(pp)) ++ checker_get(&pp->checker, TUR); ++} ++ + extern int + select_checker(struct path *pp) + { + struct checker * c = &pp->checker; + ++ if (pp->detect_checker == DETECT_CHECKER_ON) { ++ detect_checker(pp); ++ if (checker_selected(c)) { ++ condlog(3, "%s: path checker = %s (detected setting)", ++ pp->dev, checker_name(c)); ++ goto out; ++ } ++ } + if (pp->hwe && pp->hwe->checker_name) { + checker_get(c, pp->hwe->checker_name); + condlog(3, "%s: path checker = %s (controller setting)", +@@ -396,19 +428,8 @@ select_getuid (struct path * pp) + void + detect_prio(struct path * pp) + { +- int ret; +- struct prio *p = &pp->prio; +- int tpgs = 0; +- +- if ((tpgs = get_target_port_group_support(pp->fd)) <= 0) +- return; +- pp->tpgs = tpgs; +- ret = get_target_port_group(pp->fd, NULL); +- if (ret < 0) +- return; +- if (get_asymmetric_access_state(pp->fd, ret, NULL) < 0) +- return; +- prio_get(p, PRIO_ALUA, DEFAULT_PRIO_ARGS); ++ if (detect_alua(pp)) ++ prio_get(&pp->prio, PRIO_ALUA, DEFAULT_PRIO_ARGS); + } + + extern int +@@ -803,6 +824,24 @@ select_detect_prio (struct path * pp) + return 0; + } + ++extern int ++select_detect_checker (struct path * pp) ++{ ++ if (pp->hwe && pp->hwe->detect_checker) { ++ pp->detect_checker = pp->hwe->detect_checker; ++ condlog(3, "%s: detect_checker = %d (controller default)", pp->dev, pp->detect_checker); ++ return 0; ++ } ++ if (conf->detect_checker) { ++ pp->detect_checker = conf->detect_checker; ++ condlog(3, "%s: detect_checker = %d (config file default)", pp->dev, pp->detect_checker); ++ return 0; ++ } ++ pp->detect_checker = DEFAULT_DETECT_CHECKER; ++ condlog(3, "%s: detect_checker = %d (compiled in default)", pp->dev, pp->detect_checker); ++ return 0; ++} ++ + extern int + select_delay_watch_checks (struct multipath * mp) + { +Index: multipath-tools-130222/libmultipath/propsel.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/propsel.h ++++ multipath-tools-130222/libmultipath/propsel.h +@@ -20,6 +20,7 @@ int select_dev_loss(struct multipath *mp + int select_reservation_key(struct multipath *mp); + int select_retain_hwhandler (struct multipath * mp); + int select_detect_prio(struct path * pp); ++int select_detect_checker(struct path * pp); + int select_deferred_remove(struct multipath *mp); + int select_delay_watch_checks (struct multipath * mp); + int select_delay_wait_checks (struct multipath * mp); +Index: multipath-tools-130222/libmultipath/structs.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/structs.h ++++ multipath-tools-130222/libmultipath/structs.h +@@ -115,6 +115,12 @@ enum detect_prio_states { + DETECT_PRIO_ON, + }; + ++enum detect_checker_states { ++ DETECT_CHECKER_UNDEF, ++ DETECT_CHECKER_OFF, ++ DETECT_CHECKER_ON, ++}; ++ + enum deferred_remove_states { + DEFERRED_REMOVE_UNDEF, + DEFERRED_REMOVE_OFF, +@@ -204,6 +210,7 @@ struct path { + int priority; + int pgindex; + int detect_prio; ++ int detect_checker; + int watch_checks; + int wait_checks; + int tpgs; +Index: multipath-tools-130222/multipath/multipath.conf.5 +=================================================================== +--- multipath-tools-130222.orig/multipath/multipath.conf.5 ++++ multipath-tools-130222/multipath/multipath.conf.5 +@@ -448,6 +448,15 @@ will automatically use the + prioritizer. If not, the prioritizer will be selected as usual. Default is + .I no + .TP ++.B detect_checker ++If set to ++.I yes ++, multipath will try to detect if the device supports ALUA. If so, the device ++will automatically use the ++.I tur ++checker. If not, the prioritizer will be selected as ususal. Default is ++.I no ++.TP + .B hw_str_match + If set to + .I yes diff --git a/0199-RHBZ-1279355-3pardata-config.patch b/0199-RHBZ-1279355-3pardata-config.patch new file mode 100644 index 0000000..037de3d --- /dev/null +++ b/0199-RHBZ-1279355-3pardata-config.patch @@ -0,0 +1,17 @@ +--- + libmultipath/hwtable.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/hwtable.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/hwtable.c ++++ multipath-tools-130222/libmultipath/hwtable.c +@@ -69,7 +69,7 @@ static struct hwentry default_hw[] = { + .pgpolicy = MULTIBUS, + .pgfailback = FAILBACK_UNDEF, + .rr_weight = RR_WEIGHT_NONE, +- .no_path_retry = NO_PATH_RETRY_UNDEF, ++ .no_path_retry = 12, + .checker_name = DEFAULT_CHECKER, + .prio_name = DEFAULT_PRIO, + .prio_args = NULL, diff --git a/0200-RHBZ-1402092-orphan-status.patch b/0200-RHBZ-1402092-orphan-status.patch new file mode 100644 index 0000000..67830ca --- /dev/null +++ b/0200-RHBZ-1402092-orphan-status.patch @@ -0,0 +1,29 @@ +--- + libmultipath/print.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +Index: multipath-tools-130222/libmultipath/print.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/print.c ++++ multipath-tools-130222/libmultipath/print.c +@@ -386,7 +386,9 @@ snprint_dev_t (char * buff, size_t len, + static int + snprint_offline (char * buff, size_t len, struct path * pp) + { +- if (pp->offline) ++ if (!pp || !pp->mpp) ++ return snprintf(buff, len, "unknown"); ++ else if (pp->offline) + return snprintf(buff, len, "offline"); + else + return snprintf(buff, len, "running"); +@@ -395,6 +397,9 @@ snprint_offline (char * buff, size_t len + static int + snprint_chk_state (char * buff, size_t len, struct path * pp) + { ++ if (!pp || !pp->mpp) ++ return snprintf(buff, len, "undef"); ++ + switch (pp->state) { + case PATH_UP: + return snprintf(buff, len, "ready"); diff --git a/0201-RHBZ-1403552-silence-warning.patch b/0201-RHBZ-1403552-silence-warning.patch new file mode 100644 index 0000000..c816ea0 --- /dev/null +++ b/0201-RHBZ-1403552-silence-warning.patch @@ -0,0 +1,59 @@ +--- + libmultipath/discovery.c | 15 +++++++++++---- + multipathd/main.c | 10 ++++++++++ + 2 files changed, 21 insertions(+), 4 deletions(-) + +Index: multipath-tools-130222/libmultipath/discovery.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/discovery.c ++++ multipath-tools-130222/libmultipath/discovery.c +@@ -84,10 +84,6 @@ path_discover (vector pathvec, struct co + if (!devname) + return PATHINFO_FAILED; + +- if (filter_devnode(conf->blist_devnode, conf->elist_devnode, +- (char *)devname) > 0) +- return PATHINFO_SKIPPED; +- + pp = find_path_by_dev(pathvec, (char *)devname); + if (!pp) { + return store_pathinfo(pathvec, conf->hwtable, +@@ -1286,6 +1282,17 @@ pathinfo (struct path *pp, vector hwtabl + if (!pp) + return PATHINFO_FAILED; + ++ /* ++ * For behavior backward-compatibility with multipathd, ++ * the blacklisting by filter_devnode() is not ++ * limited by DI_BLACKLIST and occurs before this debug ++ * message with the mask value. ++ */ ++ if (filter_devnode(conf->blist_devnode, ++ conf->elist_devnode, ++ pp->dev) > 0) ++ return PATHINFO_SKIPPED; ++ + condlog(3, "%s: mask = 0x%x", pp->dev, mask); + + /* +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -776,6 +776,16 @@ uev_update_path (struct uevent *uev, str + + pp = find_path_by_dev(vecs->pathvec, uev->kernel); + if (!pp) { ++ /* If the path is blacklisted, print a debug/non-default verbosity message. */ ++ if (uev->udev) { ++ int flag = DI_SYSFS | DI_WWID; ++ ++ if (store_pathinfo(NULL, conf->hwtable, uev->udev, flag, NULL) == PATHINFO_SKIPPED) { ++ condlog(3, "%s: spurious uevent, path is blacklisted", uev->kernel); ++ return 0; ++ } ++ } ++ + condlog(0, "%s: spurious uevent, path not found", + uev->kernel); + return 1; diff --git a/0202-RHBZ-1362120-skip-prio.patch b/0202-RHBZ-1362120-skip-prio.patch new file mode 100644 index 0000000..8a187ad --- /dev/null +++ b/0202-RHBZ-1362120-skip-prio.patch @@ -0,0 +1,18 @@ +--- + multipathd/main.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -1248,7 +1248,8 @@ int update_prio(struct path *pp, int ref + return changed; + } + oldpriority = pp->priority; +- pathinfo(pp, conf->hwtable, DI_PRIO); ++ if (pp->state != PATH_DOWN) ++ pathinfo(pp, conf->hwtable, DI_PRIO); + + if (pp->priority == oldpriority) + return 0; diff --git a/0203-RHBZ-1363718-add-msgs.patch b/0203-RHBZ-1363718-add-msgs.patch new file mode 100644 index 0000000..9eaadc0 --- /dev/null +++ b/0203-RHBZ-1363718-add-msgs.patch @@ -0,0 +1,24 @@ +--- + multipathd/main.c | 2 ++ + 1 file changed, 2 insertions(+) + +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -337,6 +337,7 @@ ev_add_map (char * dev, char * alias, st + + if (mpp) { + if (mpp->wait_for_udev > 1) { ++ condlog(2, "%s: performing delayed actions", mpp->alias); + if (update_map(mpp, vecs)) + /* setup multipathd removed the map */ + return 1; +@@ -535,6 +536,7 @@ ev_add_path (struct path * pp, struct ve + pp->tpgs == TPGS_IMPLICIT)) + mpp->force_udev_reload = 1; + else { ++ condlog(2, "%s : delaying path addition until %s is fully initialized", pp->dev, mpp->alias); + mpp->wait_for_udev = 2; + orphan_path(pp); + return 0; diff --git a/0204-RHBZ-1406226-nimble-config.patch b/0204-RHBZ-1406226-nimble-config.patch new file mode 100644 index 0000000..c3f2e90 --- /dev/null +++ b/0204-RHBZ-1406226-nimble-config.patch @@ -0,0 +1,28 @@ +--- + libmultipath/hwtable.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +Index: multipath-tools-130222/libmultipath/hwtable.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/hwtable.c ++++ multipath-tools-130222/libmultipath/hwtable.c +@@ -1189,6 +1189,19 @@ static struct hwentry default_hw[] = { + .dev_loss = 30, + }, + { ++ .vendor = "Nimble", ++ .product = "Server", ++ .features = "1 queue_if_no_path", ++ .hwhandler = "1 alua", ++ .pgpolicy = GROUP_BY_PRIO, ++ .prio_name = PRIO_ALUA, ++ .prio_args = NULL, ++ .pgfailback = -FAILBACK_IMMEDIATE, ++ .selector = "round-robin 0", ++ .dev_loss = MAX_DEV_LOSS_TMO, ++ .fast_io_fail = 1, ++ }, ++ { + .vendor = "XtremIO", + .product = "XtremApp", + .features = DEFAULT_FEATURES, diff --git a/0205-RHBZ-1416569-reset-stats.patch b/0205-RHBZ-1416569-reset-stats.patch new file mode 100644 index 0000000..bb414ba --- /dev/null +++ b/0205-RHBZ-1416569-reset-stats.patch @@ -0,0 +1,108 @@ +--- + multipathd/cli.c | 2 ++ + multipathd/cli_handlers.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ + multipathd/cli_handlers.h | 2 ++ + multipathd/main.c | 2 ++ + 4 files changed, 50 insertions(+) + +Index: multipath-tools-130222/multipathd/cli.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/cli.c ++++ multipath-tools-130222/multipathd/cli.c +@@ -482,6 +482,8 @@ cli_init (void) { + add_handler(LIST+BLACKLIST, NULL); + add_handler(LIST+DEVICES, NULL); + add_handler(LIST+WILDCARDS, NULL); ++ add_handler(RESET+MAPS+STATS, NULL); ++ add_handler(RESET+MAP+STATS, NULL); + add_handler(ADD+PATH, NULL); + add_handler(DEL+PATH, NULL); + add_handler(ADD+MAP, NULL); +Index: multipath-tools-130222/multipathd/cli_handlers.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/cli_handlers.c ++++ multipath-tools-130222/multipathd/cli_handlers.c +@@ -233,6 +233,17 @@ show_config (char ** r, int * len) + return 0; + } + ++void ++reset_stats(struct multipath * mpp) ++{ ++ mpp->stat_switchgroup = 0; ++ mpp->stat_path_failures = 0; ++ mpp->stat_map_loads = 0; ++ mpp->stat_total_queueing_time = 0; ++ mpp->stat_queueing_timeouts = 0; ++ mpp->stat_map_failures = 0; ++} ++ + int + cli_list_config (void * v, char ** reply, int * len, void * data) + { +@@ -501,6 +512,39 @@ cli_list_daemon (void * v, char ** reply + } + + int ++cli_reset_maps_stats (void * v, char ** reply, int * len, void * data) ++{ ++ struct vectors * vecs = (struct vectors *)data; ++ int i; ++ struct multipath * mpp; ++ ++ condlog(3, "reset multipaths stats (operator)"); ++ ++ vector_foreach_slot(vecs->mpvec, mpp, i) { ++ reset_stats(mpp); ++ } ++ return 0; ++} ++ ++int ++cli_reset_map_stats (void * v, char ** reply, int * len, void * data) ++{ ++ struct vectors * vecs = (struct vectors *)data; ++ struct multipath * mpp; ++ char * param = get_keyparam(v, MAP); ++ ++ param = convert_dev(param, 0); ++ mpp = find_mp_by_str(vecs->mpvec, param); ++ ++ if (!mpp) ++ return 1; ++ ++ condlog(3, "reset multipath %s stats (operator)", param); ++ reset_stats(mpp); ++ return 0; ++} ++ ++int + cli_add_path (void * v, char ** reply, int * len, void * data) + { + struct vectors * vecs = (struct vectors *)data; +Index: multipath-tools-130222/multipathd/cli_handlers.h +=================================================================== +--- multipath-tools-130222.orig/multipathd/cli_handlers.h ++++ multipath-tools-130222/multipathd/cli_handlers.h +@@ -16,6 +16,8 @@ int cli_list_config (void * v, char ** r + int cli_list_blacklist (void * v, char ** reply, int * len, void * data); + int cli_list_devices (void * v, char ** reply, int * len, void * data); + int cli_list_wildcards (void * v, char ** reply, int * len, void * data); ++int cli_reset_maps_stats (void * v, char ** reply, int * len, void * data); ++int cli_reset_map_stats (void * v, char ** reply, int * len, void * data); + int cli_add_path (void * v, char ** reply, int * len, void * data); + int cli_del_path (void * v, char ** reply, int * len, void * data); + int cli_add_map (void * v, char ** reply, int * len, void * data); +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -1011,6 +1011,8 @@ uxlsnrloop (void * ap) + set_handler_callback(LIST+BLACKLIST, cli_list_blacklist); + set_handler_callback(LIST+DEVICES, cli_list_devices); + set_handler_callback(LIST+WILDCARDS, cli_list_wildcards); ++ set_handler_callback(RESET+MAPS+STATS, cli_reset_maps_stats); ++ set_handler_callback(RESET+MAP+STATS, cli_reset_map_stats); + set_handler_callback(ADD+PATH, cli_add_path); + set_handler_callback(DEL+PATH, cli_del_path); + set_handler_callback(ADD+MAP, cli_add_map); diff --git a/0206-RHBZ-1239173-pt2-no-paths.patch b/0206-RHBZ-1239173-pt2-no-paths.patch new file mode 100644 index 0000000..c38be56 --- /dev/null +++ b/0206-RHBZ-1239173-pt2-no-paths.patch @@ -0,0 +1,121 @@ +--- + libmultipath/configure.c | 2 - + libmultipath/devmapper.h | 6 ++++ + multipath/11-dm-mpath.rules | 61 +++++++++++++++++++++++++++++++++----------- + 3 files changed, 54 insertions(+), 15 deletions(-) + +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -615,7 +615,7 @@ extern int + domap (struct multipath * mpp, char * params) + { + int r = 0; +- uint16_t udev_flags = ((mpp->force_udev_reload)? 0 : MPATH_UDEV_RELOAD_FLAG) | ((mpp->skip_kpartx == SKIP_KPARTX_ON)? MPATH_UDEV_NO_KPARTX_FLAG : 0); ++ uint16_t udev_flags = ((mpp->force_udev_reload)? 0 : MPATH_UDEV_RELOAD_FLAG) | ((mpp->skip_kpartx == SKIP_KPARTX_ON)? MPATH_UDEV_NO_KPARTX_FLAG : 0) | ((mpp->nr_active)? 0 : MPATH_UDEV_NO_PATHS_FLAG); + + /* + * last chance to quit before touching the devmaps +Index: multipath-tools-130222/libmultipath/devmapper.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/devmapper.h ++++ multipath-tools-130222/libmultipath/devmapper.h +@@ -18,6 +18,12 @@ + #define MPATH_UDEV_NO_KPARTX_FLAG 0 + #endif + ++#ifdef DM_SUBSYSTEM_UDEV_FLAG2 ++#define MPATH_UDEV_NO_PATHS_FLAG DM_SUBSYSTEM_UDEV_FLAG2 ++#else ++#define MPATH_UDEV_NO_PATHS_FLAG 0 ++#endif ++ + void dm_init(void); + int dm_prereq (void); + int dm_drv_version (unsigned int * version, char * str); +Index: multipath-tools-130222/multipath/11-dm-mpath.rules +=================================================================== +--- multipath-tools-130222.orig/multipath/11-dm-mpath.rules ++++ multipath-tools-130222/multipath/11-dm-mpath.rules +@@ -2,33 +2,66 @@ ACTION!="add|change", GOTO="mpath_end" + ENV{DM_UDEV_RULES_VSN}!="?*", GOTO="mpath_end" + ENV{DM_UUID}!="mpath-?*", GOTO="mpath_end" + ++IMPORT{db}="DM_DISABLE_OTHER_RULES_FLAG_OLD" ++IMPORT{db}="MPATH_DEVICE_READY" ++ ++# If this uevent didn't come from dm, don't try to update the ++# device state ++ENV{DM_COOKIE}!="?*", ENV{DM_ACTION}!="PATH_*", IMPORT{db}="DM_UDEV_DISABLE_OTHER_RULES_FLAG", IMPORT{db}="DM_NOSCAN", GOTO="scan_import" ++ ++ENV{.MPATH_DEVICE_READY_OLD}="$env{MPATH_DEVICE_READY}" ++ ++# multipath sets DM_SUBSYSTEM_UDEV_FLAG2 when it reloads a ++# table with no active devices. If this happens, mark the ++# device not ready ++ENV{DM_SUBSYSTEM_UDEV_FLAG2}=="1", ENV{MPATH_DEVICE_READY}="0",\ ++ GOTO="mpath_action" ++ ++# If the last path has failed mark the device not ready ++ENV{DM_ACTION}=="PATH_FAILED", ENV{DM_NR_VALID_PATHS}=="0",\ ++ ENV{MPATH_DEVICE_READY}="0", GOTO="mpath_action" ++ ++# Don't mark a device ready on a PATH_FAILED event. even if ++# DM_NR_VALID_PATHS is greater than 0. Just keep the existing ++# value ++ENV{DM_ACTION}=="PATH_FAILED", GOTO="mpath_action" ++ ++# This event is either a PATH_REINSTATED or a table reload where ++# there are active paths. Mark the device ready ++ENV{MPATH_DEVICE_READY}="" ++ ++LABEL="mpath_action" ++# DM_SUBSYSTEM_UDEV_FLAG0 is the "RELOAD" flag for multipath subsystem. ++# Drop the DM_ACTIVATION flag here as mpath reloads tables if any of its ++# paths are lost/recovered. For any stack above the mpath device, this is not ++# something that should be reacted upon since it would be useless extra work. ++# It's exactly mpath's job to provide *seamless* device access to any of the ++# paths that are available underneath. ++ENV{DM_SUBSYSTEM_UDEV_FLAG0}=="1", ENV{DM_ACTIVATION}="0" ++ + # Do not initiate scanning if no path is available, + # otherwise there would be a hang or IO error on access. + # We'd like to avoid this, especially within udev processing. +-ENV{DM_NR_VALID_PATHS}!="?*", IMPORT{db}="DM_NR_VALID_PATHS" +-ENV{DM_NR_VALID_PATHS}=="0", ENV{DM_NOSCAN}="1" ++ENV{MPATH_DEVICE_READY}=="0", ENV{DM_NOSCAN}="1" + + # Also skip all foreign rules if no path is available. + # Remember the original value of DM_DISABLE_OTHER_RULES_FLAG + # and restore it back once we have at least one path available. +-IMPORT{db}="DM_DISABLE_OTHER_RULES_FLAG_OLD" +-ENV{DM_ACTION}=="PATH_FAILED",\ +- ENV{DM_NR_VALID_PATHS}=="0",\ ++ENV{MPATH_DEVICE_READY}=="0", ENV{.MPATH_DEVICE_READY_OLD}!="0",\ + ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}=="",\ + ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="$env{DM_UDEV_DISABLE_OTHER_RULES_FLAG}",\ + ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" +-ENV{DM_ACTION}=="PATH_REINSTATED",\ +- ENV{DM_NR_VALID_PATHS}=="1",\ ++ENV{MPATH_DEVICE_READY}!="0", ENV{.MPATH_DEVICE_READY_OLD}=="0",\ + ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="$env{DM_DISABLE_OTHER_RULES_FLAG_OLD}",\ + ENV{DM_DISABLE_OTHER_RULES_FLAG_OLD}="",\ + ENV{DM_ACTIVATION}="1" + +-# DM_SUBSYSTEM_UDEV_FLAG0 is the "RELOAD" flag for multipath subsystem. +-# Drop the DM_ACTIVATION flag here as mpath reloads tables if any of its +-# paths are lost/recovered. For any stack above the mpath device, this is not +-# something that should be reacted upon since it would be useless extra work. +-# It's exactly mpath's job to provide *seamless* device access to any of the +-# paths that are available underneath. +-ENV{DM_SUBSYSTEM_UDEV_FLAG0}=="1", ENV{DM_ACTIVATION}="0" ++LABEL="scan_import" ++ENV{DM_NOSCAN}!="1", GOTO="mpath_end" ++ENV{ID_FS_TYPE}!="?*", IMPORT{db}="ID_FS_TYPE" ++ENV{ID_FS_USAGE}!="?*", IMPORT{db}="ID_FS_USAGE" ++ENV{ID_FS_UUID}!="?*", IMPORT{db}="ID_FS_UUID" ++ENV{ID_FS_ENC}!="?*", IMPORT{db}="ID_FS_UUID_ENC" ++ENV{ID_FS_VERSION}!="?*", IMPORT{db}="ID_FS_VERSION" + + LABEL="mpath_end" diff --git a/0207-UP-add-libmpathcmd.patch b/0207-UP-add-libmpathcmd.patch new file mode 100644 index 0000000..ee19d65 --- /dev/null +++ b/0207-UP-add-libmpathcmd.patch @@ -0,0 +1,840 @@ +From c146b5840bbd7ad89c8a8de6192590ad0595a977 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 7 Apr 2016 18:19:58 -0500 +Subject: [PATCH] Add libmpathcmd library and use it internally + +Other programs would like to communicate with multipathd to issue +command or check status. Instead of having them exec multipathd, +I've pulled the code that sends commands and receives replies from +multipathd into its own library. I've made the multipath tools use +this library internally as well. + +Signed-off-by: Benjamin Marzinski +--- + Makefile | 1 + Makefile.inc | 2 + libmpathcmd/Makefile | 32 +++++++ + libmpathcmd/mpath_cmd.c | 178 +++++++++++++++++++++++++++++++++++++++ + libmpathcmd/mpath_cmd.h | 125 +++++++++++++++++++++++++++ + libmpathpersist/Makefile | 5 - + libmpathpersist/mpath_updatepr.c | 30 +++--- + libmultipath/Makefile | 4 + libmultipath/config.c | 1 + libmultipath/configure.c | 10 +- + libmultipath/uxsock.c | 88 +++---------------- + libmultipath/uxsock.h | 6 - + mpathpersist/Makefile | 2 + multipath/Makefile | 5 - + multipathd/Makefile | 4 + multipathd/uxclnt.c | 13 +- + multipathd/uxlsnr.c | 9 - + 17 files changed, 401 insertions(+), 114 deletions(-) + create mode 100644 libmpathcmd/Makefile + create mode 100644 libmpathcmd/mpath_cmd.c + create mode 100644 libmpathcmd/mpath_cmd.h + +Index: multipath-tools-130222/Makefile +=================================================================== +--- multipath-tools-130222.orig/Makefile ++++ multipath-tools-130222/Makefile +@@ -20,6 +20,7 @@ export KRNLSRC + export KRNLOBJ + + BUILDDIRS = \ ++ libmpathcmd \ + libmultipath \ + libmultipath/prioritizers \ + libmultipath/checkers \ +Index: multipath-tools-130222/Makefile.inc +=================================================================== +--- multipath-tools-130222.orig/Makefile.inc ++++ multipath-tools-130222/Makefile.inc +@@ -34,6 +34,8 @@ syslibdir = $(prefix)/usr/$(LIB) + libdir = $(prefix)/usr/$(LIB)/multipath + unitdir = $(prefix)/lib/systemd/system + mpathpersistdir = $(TOPDIR)/libmpathpersist ++includedir = $(prefix)/usr/include ++mpathcmddir = $(TOPDIR)/libmpathcmd + + GZIP = /bin/gzip -9 -c + INSTALL_PROGRAM = install +Index: multipath-tools-130222/libmpathcmd/Makefile +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libmpathcmd/Makefile +@@ -0,0 +1,32 @@ ++# Makefile ++# ++include ../Makefile.inc ++ ++SONAME=0 ++DEVLIB = libmpathcmd.so ++LIBS = $(DEVLIB).$(SONAME) ++ ++CFLAGS += -fPIC ++ ++OBJS = mpath_cmd.o ++ ++all: $(LIBS) ++ ++$(LIBS): $(OBJS) ++ $(CC) $(LDFLAGS) $(SHARED_FLAGS) -Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS) ++ ln -sf $@ $(DEVLIB) ++ ++install: $(LIBS) ++ $(INSTALL_PROGRAM) -d $(DESTDIR)$(syslibdir) ++ $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) ++ ln -sf $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) ++ $(INSTALL_PROGRAM) -d $(DESTDIR)$(includedir) ++ $(INSTALL_PROGRAM) -m 644 mpath_cmd.h $(DESTDIR)$(includedir) ++ ++uninstall: ++ rm -f $(DESTDIR)$(syslibdir)/$(LIBS) ++ rm -f $(DESTDIR)$(syslibdir)/$(DEVLIB) ++ rm -f $(DESTDIR)$(includedir)/mpath_cmd.h ++ ++clean: ++ rm -f core *.a *.o *.gz *.so *.so.* +Index: multipath-tools-130222/libmpathcmd/mpath_cmd.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libmpathcmd/mpath_cmd.c +@@ -0,0 +1,178 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mpath_cmd.h" ++ ++/* ++ * keep reading until its all read ++ */ ++static ssize_t read_all(int fd, void *buf, size_t len, unsigned int timeout) ++{ ++ size_t total = 0; ++ ssize_t n; ++ int ret; ++ struct pollfd pfd; ++ ++ while (len) { ++ pfd.fd = fd; ++ pfd.events = POLLIN; ++ ret = poll(&pfd, 1, timeout); ++ if (!ret) { ++ errno = ETIMEDOUT; ++ return -1; ++ } else if (ret < 0) { ++ if (errno == EINTR) ++ continue; ++ return -1; ++ } else if (!(pfd.revents & POLLIN)) ++ continue; ++ n = read(fd, buf, len); ++ if (n < 0) { ++ if ((errno == EINTR) || (errno == EAGAIN)) ++ continue; ++ return -1; ++ } ++ if (!n) ++ return total; ++ buf = n + (char *)buf; ++ len -= n; ++ total += n; ++ } ++ return total; ++} ++ ++/* ++ * keep writing until it's all sent ++ */ ++static size_t write_all(int fd, const void *buf, size_t len) ++{ ++ size_t total = 0; ++ ++ while (len) { ++ ssize_t n = write(fd, buf, len); ++ if (n < 0) { ++ if ((errno == EINTR) || (errno == EAGAIN)) ++ continue; ++ return total; ++ } ++ if (!n) ++ return total; ++ buf = n + (char *)buf; ++ len -= n; ++ total += n; ++ } ++ return total; ++} ++ ++/* ++ * connect to a unix domain socket ++ */ ++int mpath_connect(void) ++{ ++ int fd, len; ++ struct sockaddr_un addr; ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.sun_family = AF_LOCAL; ++ addr.sun_path[0] = '\0'; ++ len = strlen(DEFAULT_SOCKET) + 1 + sizeof(sa_family_t); ++ strncpy(&addr.sun_path[1], DEFAULT_SOCKET, len); ++ ++ fd = socket(AF_LOCAL, SOCK_STREAM, 0); ++ if (fd == -1) ++ return -1; ++ ++ if (connect(fd, (struct sockaddr *)&addr, len) == -1) { ++ close(fd); ++ return -1; ++ } ++ ++ return fd; ++} ++ ++int mpath_disconnect(int fd) ++{ ++ return close(fd); ++} ++ ++ssize_t mpath_recv_reply_len(int fd, unsigned int timeout) ++{ ++ size_t len; ++ ssize_t ret; ++ ++ ret = read_all(fd, &len, sizeof(len), timeout); ++ if (ret < 0) ++ return ret; ++ if (ret != sizeof(len)) { ++ errno = EIO; ++ return -1; ++ } ++ return len; ++} ++ ++int mpath_recv_reply_data(int fd, char *reply, size_t len, ++ unsigned int timeout) ++{ ++ ssize_t ret; ++ ++ ret = read_all(fd, reply, len, timeout); ++ if (ret < 0) ++ return ret; ++ if (ret != len) { ++ errno = EIO; ++ return -1; ++ } ++ reply[len - 1] = '\0'; ++ return 0; ++} ++ ++int mpath_recv_reply(int fd, char **reply, unsigned int timeout) ++{ ++ int err; ++ ssize_t len; ++ ++ *reply = NULL; ++ len = mpath_recv_reply_len(fd, timeout); ++ if (len <= 0) ++ return -1; ++ *reply = malloc(len); ++ if (!*reply) ++ return -1; ++ err = mpath_recv_reply_data(fd, *reply, len, timeout); ++ if (err) { ++ free(*reply); ++ *reply = NULL; ++ return -1; ++ } ++ return 0; ++} ++ ++int mpath_send_cmd(int fd, const char *cmd) ++{ ++ size_t len; ++ ++ if (cmd != NULL) ++ len = strlen(cmd) + 1; ++ else ++ len = 0; ++ if (write_all(fd, &len, sizeof(len)) != sizeof(len)) ++ return -1; ++ if (len && write_all(fd, cmd, len) != len) ++ return -1; ++ return 0; ++} ++ ++int mpath_process_cmd(int fd, const char *cmd, char **reply, ++ unsigned int timeout) ++{ ++ if (mpath_send_cmd(fd, cmd) != 0) ++ return -1; ++ return mpath_recv_reply(fd, reply, timeout); ++} +Index: multipath-tools-130222/libmpathcmd/mpath_cmd.h +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libmpathcmd/mpath_cmd.h +@@ -0,0 +1,125 @@ ++/* ++ * Copyright (C) 2015 Red Hat, Inc. ++ * ++ * This file is part of the device-mapper multipath userspace tools. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, ++ * USA. ++ */ ++ ++#ifndef LIB_MPATH_CMD_H ++#define LIB_MPATH_CMD_H ++ ++#ifdef __cpluscplus ++extern "C" { ++#endif ++ ++#define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" ++#define DEFAULT_REPLY_TIMEOUT 10000 ++ ++ ++/* ++ * DESCRIPTION: ++ * Connect to the running multipathd daemon. On systems with the ++ * multipathd.socket systemd unit file installed, this command will ++ * start multipathd if it is not already running. This function ++ * must be run before any of the others in this library ++ * ++ * RETURNS: ++ * A file descriptor on success. -1 on failure (with errno set). ++ */ ++int mpath_connect(void); ++ ++ ++/* ++ * DESCRIPTION: ++ * Disconnect from the multipathd daemon. This function must be ++ * run after after processing all the multipath commands. ++ * ++ * RETURNS: ++ * 0 on success. -1 on failure (with errno set). ++ */ ++int mpath_disconnect(int fd); ++ ++ ++/* ++ * DESCRIPTION ++ * Send multipathd a command and return the reply. This function ++ * does the same as calling mpath_send_cmd() and then ++ * mpath_recv_reply() ++ * ++ * RETURNS: ++ * 0 on successs, and reply will either be NULL (if there was no ++ * reply data), or point to the reply string, which must be freed by ++ * the caller. -1 on failure (with errno set). ++ */ ++int mpath_process_cmd(int fd, const char *cmd, char **reply, ++ unsigned int timeout); ++ ++ ++/* ++ * DESCRIPTION: ++ * Send a command to multipathd ++ * ++ * RETURNS: ++ * 0 on success. -1 on failure (with errno set) ++ */ ++int mpath_send_cmd(int fd, const char *cmd); ++ ++ ++/* ++ * DESCRIPTION: ++ * Return a reply from multipathd for a previously sent command. ++ * This is equivalent to calling mpath_recv_reply_len(), allocating ++ * a buffer of the appropriate size, and then calling ++ * mpath_recv_reply_data() with that buffer. ++ * ++ * RETURNS: ++ * 0 on success, and reply will either be NULL (if there was no ++ * reply data), or point to the reply string, which must be freed by ++ * the caller, -1 on failure (with errno set). ++ */ ++int mpath_recv_reply(int fd, char **reply, unsigned int timeout); ++ ++ ++/* ++ * DESCRIPTION: ++ * Return the size of the upcoming reply data from the sent multipath ++ * command. This must be called before calling mpath_recv_reply_data(). ++ * ++ * RETURNS: ++ * The required size of the reply data buffer on success. -1 on ++ * failure (with errno set). ++ */ ++ssize_t mpath_recv_reply_len(int fd, unsigned int timeout); ++ ++ ++/* ++ * DESCRIPTION: ++ * Return the reply data from the sent multipath command. ++ * mpath_recv_reply_len must be called first. reply must point to a ++ * buffer of len size. ++ * ++ * RETURNS: ++ * 0 on success, and reply will contain the reply data string. -1 ++ * on failure (with errno set). ++ */ ++int mpath_recv_reply_data(int fd, char *reply, size_t len, ++ unsigned int timeout); ++ ++#ifdef __cplusplus ++} ++#endif ++#endif /* LIB_MPATH_CMD_H */ +Index: multipath-tools-130222/libmpathpersist/Makefile +=================================================================== +--- multipath-tools-130222.orig/libmpathpersist/Makefile ++++ multipath-tools-130222/libmpathpersist/Makefile +@@ -10,8 +10,9 @@ DEVLIB = libmpathpersist.so + LIBS = $(DEVLIB).$(SONAME) + + +-CFLAGS += -fPIC -I$(multipathdir) -I$(mpathpersistdir) +-LIBDEPS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath ++CFLAGS += -fPIC -I$(multipathdir) -I$(mpathpersistdir) -I$(mpathcmddir) ++LIBDEPS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath \ ++ -L$(mpathcmddir) -lmpathcmd + + OBJS = mpath_persist.o mpath_updatepr.o mpath_pr_ioctl.o + +Index: multipath-tools-130222/libmpathpersist/mpath_updatepr.c +=================================================================== +--- multipath-tools-130222.orig/libmpathpersist/mpath_updatepr.c ++++ multipath-tools-130222/libmpathpersist/mpath_updatepr.c +@@ -12,9 +12,9 @@ + #include + #include + #include ++#include ++#include + #include "memory.h" +-#include "../libmultipath/uxsock.h" +-#include "../libmultipath/defaults.h" + + unsigned long mem_allocated; /* Total memory used in Bytes */ + +@@ -23,10 +23,9 @@ int update_prflag(char * arg1, char * ar + int fd; + char str[64]; + char *reply; +- size_t len; + int ret = 0; + +- fd = ux_socket_connect(DEFAULT_SOCKET); ++ fd = mpath_connect(); + if (fd == -1) { + condlog (0, "ux socket connect error"); + return 1 ; +@@ -34,18 +33,23 @@ int update_prflag(char * arg1, char * ar + + snprintf(str,sizeof(str),"map %s %s", arg1, arg2); + condlog (2, "%s: pr flag message=%s", arg1, str); +- send_packet(fd, str, strlen(str) + 1); +- recv_packet(fd, &reply, &len); +- +- condlog (2, "%s: message=%s reply=%s", arg1, str, reply); +- if (!reply || strncmp(reply,"ok", 2) == 0) +- ret = -1; +- else if (strncmp(reply, "fail", 4) == 0) ++ send_packet(fd, str); ++ ret = recv_packet(fd, &reply); ++ if (ret < 0) { ++ condlog(2, "%s: message=%s recv error=%d", arg1, str, errno); + ret = -2; +- else{ +- ret = atoi(reply); ++ } else { ++ condlog (2, "%s: message=%s reply=%s", arg1, str, reply); ++ if (!reply || strncmp(reply,"ok", 2) == 0) ++ ret = -1; ++ else if (strncmp(reply, "fail", 4) == 0) ++ ret = -2; ++ else{ ++ ret = atoi(reply); ++ } + } + + free(reply); ++ mpath_disconnect(fd); + return ret; + } +Index: multipath-tools-130222/libmultipath/Makefile +=================================================================== +--- multipath-tools-130222.orig/libmultipath/Makefile ++++ multipath-tools-130222/libmultipath/Makefile +@@ -7,8 +7,8 @@ include ../Makefile.inc + SONAME=0 + DEVLIB = libmultipath.so + LIBS = $(DEVLIB).$(SONAME) +-LIBDEPS = -lpthread -ldl -ldevmapper -ludev +-CFLAGS += -fPIC ++LIBDEPS = -lpthread -ldl -ldevmapper -ludev -L$(mpathcmddir) -lmpathcmd ++CFLAGS += -fPIC -I$(mpathcmddir) + + OBJS = memory.o parser.o vector.o devmapper.o \ + hwtable.o blacklist.o util.o dmparser.o config.o \ +Index: multipath-tools-130222/libmultipath/config.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/config.c ++++ multipath-tools-130222/libmultipath/config.c +@@ -25,6 +25,7 @@ + #include "prio.h" + #include "devmapper.h" + #include "version.h" ++#include "mpath_cmd.h" + + static int + hwe_strmatch (struct hwentry *hwe1, struct hwentry *hwe2) +Index: multipath-tools-130222/libmultipath/configure.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/configure.c ++++ multipath-tools-130222/libmultipath/configure.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "checkers.h" + #include "vector.h" +@@ -752,16 +753,15 @@ check_daemon(void) + { + int fd; + char *reply; +- size_t len; + int ret = 0; + +- fd = ux_socket_connect(DEFAULT_SOCKET); ++ fd = mpath_connect(); + if (fd == -1) + return 0; + +- if (send_packet(fd, "show daemon", 12) != 0) ++ if (send_packet(fd, "show daemon") != 0) + goto out; +- if (recv_packet(fd, &reply, &len) != 0) ++ if (recv_packet(fd, &reply) != 0) + goto out; + + if (strstr(reply, "shutdown")) +@@ -772,7 +772,7 @@ check_daemon(void) + out_free: + FREE(reply); + out: +- close(fd); ++ mpath_disconnect(fd); + return ret; + } + +Index: multipath-tools-130222/libmultipath/uxsock.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/uxsock.c ++++ multipath-tools-130222/libmultipath/uxsock.c +@@ -16,37 +16,10 @@ + #include + #include + #include ++#include + + #include "memory.h" + #include "uxsock.h" +- +-/* +- * connect to a unix domain socket +- */ +-int ux_socket_connect(const char *name) +-{ +- int fd, len; +- struct sockaddr_un addr; +- +- memset(&addr, 0, sizeof(addr)); +- addr.sun_family = AF_LOCAL; +- addr.sun_path[0] = '\0'; +- len = strlen(name) + 1 + sizeof(sa_family_t); +- strncpy(&addr.sun_path[1], name, len); +- +- fd = socket(AF_LOCAL, SOCK_STREAM, 0); +- if (fd == -1) { +- return -1; +- } +- +- if (connect(fd, (struct sockaddr *)&addr, len) == -1) { +- close(fd); +- return -1; +- } +- +- return fd; +-} +- + /* + * create a unix domain socket and start listening on it + * return a file descriptor open on the socket +@@ -102,32 +75,9 @@ size_t write_all(int fd, const void *buf + } + + /* +- * keep reading until its all read +- */ +-size_t read_all(int fd, void *buf, size_t len) +-{ +- size_t total = 0; +- +- while (len) { +- ssize_t n = read(fd, buf, len); +- if (n < 0) { +- if ((errno == EINTR) || (errno == EAGAIN)) +- continue; +- return total; +- } +- if (!n) +- return total; +- buf = n + (char *)buf; +- len -= n; +- total += n; +- } +- return total; +-} +- +-/* + * send a packet in length prefix format + */ +-int send_packet(int fd, const char *buf, size_t len) ++int send_packet(int fd, const char *buf) + { + int ret = 0; + sigset_t set, old; +@@ -137,10 +87,7 @@ int send_packet(int fd, const char *buf, + sigaddset(&set, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &set, &old); + +- if (write_all(fd, &len, sizeof(len)) != sizeof(len)) +- ret = -1; +- if (!ret && write_all(fd, buf, len) != len) +- ret = -1; ++ ret = mpath_send_cmd(fd, buf); + + /* And unblock it again */ + pthread_sigmask(SIG_SETMASK, &old, NULL); +@@ -151,25 +98,24 @@ int send_packet(int fd, const char *buf, + /* + * receive a packet in length prefix format + */ +-int recv_packet(int fd, char **buf, size_t *len) ++int recv_packet(int fd, char **buf) + { +- if (read_all(fd, len, sizeof(*len)) != sizeof(*len)) { +- (*buf) = NULL; +- *len = 0; +- return -1; +- } +- if (len == 0) { +- (*buf) = NULL; +- return 0; +- } +- (*buf) = MALLOC(*len); ++ int err; ++ ssize_t len; ++ unsigned int timeout = DEFAULT_REPLY_TIMEOUT; ++ ++ *buf = NULL; ++ len = mpath_recv_reply_len(fd, timeout); ++ if (len <= 0) ++ return len; ++ (*buf) = MALLOC(len); + if (!*buf) +- return -1; +- if (read_all(fd, *buf, *len) != *len) { ++ return -ENOMEM; ++ err = mpath_recv_reply_data(fd, *buf, len, timeout); ++ if (err) { + FREE(*buf); + (*buf) = NULL; +- *len = 0; +- return -1; ++ return err; + } + return 0; + } +Index: multipath-tools-130222/libmultipath/uxsock.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/uxsock.h ++++ multipath-tools-130222/libmultipath/uxsock.h +@@ -1,7 +1,5 @@ + /* some prototypes */ +-int ux_socket_connect(const char *name); + int ux_socket_listen(const char *name); +-int send_packet(int fd, const char *buf, size_t len); +-int recv_packet(int fd, char **buf, size_t *len); ++int send_packet(int fd, const char *buf); ++int recv_packet(int fd, char **buf); + size_t write_all(int fd, const void *buf, size_t len); +-size_t read_all(int fd, void *buf, size_t len); +Index: multipath-tools-130222/mpathpersist/Makefile +=================================================================== +--- multipath-tools-130222.orig/mpathpersist/Makefile ++++ multipath-tools-130222/mpathpersist/Makefile +@@ -5,7 +5,7 @@ include ../Makefile.inc + OBJS = main.o + + CFLAGS += -I$(multipathdir) -I$(mpathpersistdir) +-LDFLAGS += -lpthread -ldevmapper -L$(mpathpersistdir) -lmpathpersist -L$(multipathdir) -lmultipath -ludev ++LDFLAGS += -lpthread -ldevmapper -L$(mpathpersistdir) -lmpathpersist -L$(multipathdir) -L$(mpathcmddir) -lmpathcmd -lmultipath -ludev + + EXEC = mpathpersist + +Index: multipath-tools-130222/multipath/Makefile +=================================================================== +--- multipath-tools-130222.orig/multipath/Makefile ++++ multipath-tools-130222/multipath/Makefile +@@ -6,8 +6,9 @@ include ../Makefile.inc + + OBJS = main.o + +-CFLAGS += -fPIC -I$(multipathdir) +-LDFLAGS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath -ludev ++CFLAGS += -I$(multipathdir) -I$(mpathcmddir) ++LDFLAGS += -lpthread -ldevmapper -ldl -L$(multipathdir) -lmultipath -ludev \ ++ -L$(mpathcmddir) -lmpathcmd + + EXEC = multipath + +Index: multipath-tools-130222/multipathd/Makefile +=================================================================== +--- multipath-tools-130222.orig/multipathd/Makefile ++++ multipath-tools-130222/multipathd/Makefile +@@ -5,10 +5,10 @@ include ../Makefile.inc + # + # basic flags setting + # +-CFLAGS += -fPIE -DPIE -I$(multipathdir) -I$(mpathpersistdir) ++CFLAGS += -fPIE -DPIE -I$(multipathdir) -I$(mpathpersistdir) -I$(mpathcmddir) + LDFLAGS += -lpthread -ldevmapper -lreadline -ludev -ldl \ + -L$(multipathdir) -lmultipath -L$(mpathpersistdir) -lmpathpersist \ +- -Wl,-z,now -pie ++ -L$(mpathcmddir) -lmpathcmd -Wl,-z,now -pie + + # + # debuging stuff +Index: multipath-tools-130222/multipathd/uxclnt.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/uxclnt.c ++++ multipath-tools-130222/multipathd/uxclnt.c +@@ -17,6 +17,7 @@ + #include + #include + ++#include + #include + #include + #include +@@ -49,7 +50,6 @@ static void process(int fd) + rl_readline_name = "multipathd"; + rl_completion_entry_function = key_generator; + while ((line = readline("multipathd> "))) { +- size_t len; + size_t llen = strlen(line); + + if (!llen) { +@@ -61,8 +61,8 @@ static void process(int fd) + if (!strncmp(line, "quit", 4) && llen == 4) + break; + +- if (send_packet(fd, line, llen + 1) != 0) break; +- if (recv_packet(fd, &reply, &len) != 0) break; ++ if (send_packet(fd, line) != 0) break; ++ if (recv_packet(fd, &reply) != 0) break; + + print_reply(reply); + +@@ -77,13 +77,12 @@ static void process(int fd) + static void process_req(int fd, char * inbuf) + { + char *reply; +- size_t len; + +- if (send_packet(fd, inbuf, strlen(inbuf) + 1) != 0) { ++ if (send_packet(fd, inbuf) != 0) { + printf("cannot send packet\n"); + return; + } +- if (recv_packet(fd, &reply, &len) != 0) ++ if (recv_packet(fd, &reply) != 0) + printf("error receiving packet\n"); + else { + printf("%s", reply); +@@ -98,7 +97,7 @@ int uxclnt(char * inbuf) + { + int fd; + +- fd = ux_socket_connect(DEFAULT_SOCKET); ++ fd = mpath_connect(); + if (fd == -1) { + perror("ux_socket_connect"); + exit(1); +Index: multipath-tools-130222/multipathd/uxlsnr.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/uxlsnr.c ++++ multipath-tools-130222/multipathd/uxlsnr.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + + #include "main.h" + #include "cli.h" +@@ -108,7 +109,6 @@ void * uxsock_listen(int (*uxsock_trigge + void * trigger_data) + { + int ux_sock; +- size_t len; + int rlen; + char *inbuf; + char *reply; +@@ -171,16 +171,15 @@ void * uxsock_listen(int (*uxsock_trigge + struct client *next = c->next; + + if (polls[i].revents & POLLIN) { +- if (recv_packet(c->fd, &inbuf, &len) != 0) { ++ if (recv_packet(c->fd, &inbuf) != 0) { + dead_client(c); + } else { +- inbuf[len - 1] = 0; + condlog(4, "Got request [%s]", inbuf); + uxsock_trigger(inbuf, &reply, &rlen, + trigger_data); + if (reply) { +- if (send_packet(c->fd, reply, +- rlen) != 0) { ++ if (send_packet(c->fd, ++ reply) != 0) { + dead_client(c); + } + condlog(4, "Reply [%d bytes]", diff --git a/0208-UPBZ-1430097-multipathd-IPC-changes.patch b/0208-UPBZ-1430097-multipathd-IPC-changes.patch new file mode 100644 index 0000000..cddf641 --- /dev/null +++ b/0208-UPBZ-1430097-multipathd-IPC-changes.patch @@ -0,0 +1,280 @@ +[PATCH] Multipath: Remove duplicated memset() for multipathd show command. +[PATCH] multipath-tools: New way to limit the IPC command length. +[PATCH] multipath-tools: Perform socket client uid check on IPC commands. + +Signed-off-by: Gris Ge +--- + libmultipath/print.c | 10 ---------- + libmultipath/uxsock.c | 38 +++++++++++++++++++++++++++++--------- + libmultipath/uxsock.h | 9 +++++++++ + multipathd/main.c | 15 +++++++++++++-- + multipathd/uxlsnr.c | 31 ++++++++++++++++++++++++++----- + multipathd/uxlsnr.h | 8 +++++--- + 6 files changed, 82 insertions(+), 29 deletions(-) + +Index: multipath-tools-130222/libmultipath/print.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/print.c ++++ multipath-tools-130222/libmultipath/print.c +@@ -771,8 +771,6 @@ snprint_multipath_header (char * line, i + int fwd; + struct multipath_data * data; + +- memset(line, 0, len); +- + do { + if (!TAIL) + break; +@@ -806,8 +804,6 @@ snprint_multipath (char * line, int len, + struct multipath_data * data; + char buff[MAX_FIELD_LEN] = {}; + +- memset(line, 0, len); +- + do { + if (!TAIL) + break; +@@ -842,8 +838,6 @@ snprint_path_header (char * line, int le + int fwd; + struct path_data * data; + +- memset(line, 0, len); +- + do { + if (!TAIL) + break; +@@ -877,8 +871,6 @@ snprint_path (char * line, int len, char + struct path_data * data; + char buff[MAX_FIELD_LEN]; + +- memset(line, 0, len); +- + do { + if (!TAIL) + break; +@@ -914,8 +906,6 @@ snprint_pathgroup (char * line, int len, + struct pathgroup_data * data; + char buff[MAX_FIELD_LEN]; + +- memset(line, 0, len); +- + do { + if (!TAIL) + break; +Index: multipath-tools-130222/libmultipath/uxsock.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/uxsock.c ++++ multipath-tools-130222/libmultipath/uxsock.c +@@ -20,6 +20,15 @@ + + #include "memory.h" + #include "uxsock.h" ++ ++/* ++ * Code is similar with mpath_recv_reply() with data size limitation ++ * and debug-able malloc. ++ * When limit == 0, it means no limit on data size, used for socket client ++ * to receiving data from multipathd. ++ */ ++static int _recv_packet(int fd, char **buf, ssize_t limit); ++ + /* + * create a unix domain socket and start listening on it + * return a file descriptor open on the socket +@@ -95,27 +104,38 @@ int send_packet(int fd, const char *buf) + return ret; + } + +-/* +- * receive a packet in length prefix format +- */ +-int recv_packet(int fd, char **buf) ++static int _recv_packet(int fd, char **buf, ssize_t limit) + { +- int err; +- ssize_t len; ++ int err = 0; ++ ssize_t len = 0; + unsigned int timeout = DEFAULT_REPLY_TIMEOUT; + + *buf = NULL; + len = mpath_recv_reply_len(fd, timeout); + if (len <= 0) + return len; ++ if ((limit > 0) && (len > limit)) ++ return -EINVAL; + (*buf) = MALLOC(len); + if (!*buf) + return -ENOMEM; + err = mpath_recv_reply_data(fd, *buf, len, timeout); +- if (err) { ++ if (err != 0) { + FREE(*buf); + (*buf) = NULL; +- return err; + } +- return 0; ++ return err; ++} ++ ++/* ++ * receive a packet in length prefix format ++ */ ++int recv_packet(int fd, char **buf) ++{ ++ return _recv_packet(fd, buf, 0 /* no limit */); ++} ++ ++int recv_packet_from_client(int fd, char **buf) ++{ ++ return _recv_packet(fd, buf, _MAX_CMD_LEN); + } +Index: multipath-tools-130222/libmultipath/uxsock.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/uxsock.h ++++ multipath-tools-130222/libmultipath/uxsock.h +@@ -3,3 +3,12 @@ int ux_socket_listen(const char *name); + int send_packet(int fd, const char *buf); + int recv_packet(int fd, char **buf); + size_t write_all(int fd, const void *buf, size_t len); ++ ++#define _MAX_CMD_LEN 512 ++ ++/* ++ * Used for receiving socket command from untrusted socket client where data ++ * size is restricted to 512(_MAX_CMD_LEN) at most. ++ * Return -EINVAL if data length requested by client exceeded the _MAX_CMD_LEN. ++ */ ++int recv_packet_from_client(int fd, char **buf); +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include "prioritizers/alua_rtpg.h" + +@@ -859,7 +860,8 @@ map_discovery (struct vectors * vecs) + } + + int +-uxsock_trigger (char * str, char ** reply, int * len, void * trigger_data) ++uxsock_trigger (char * str, char ** reply, int * len, bool is_root, ++ void * trigger_data) + { + struct vectors * vecs; + int r; +@@ -872,6 +874,15 @@ uxsock_trigger (char * str, char ** repl + lock(vecs->lock); + pthread_testcancel(); + ++ if ((str != NULL) && (is_root == false) && ++ (strncmp(str, "list", strlen("list")) != 0) && ++ (strncmp(str, "show", strlen("show")) != 0)) { ++ *reply = STRDUP("permission deny: need to be root"); ++ *len = strlen(*reply) + 1; ++ r = 1; ++ goto out; ++ } ++ + r = parse_cmd(str, reply, len, vecs); + + if (r > 0) { +@@ -885,7 +896,7 @@ uxsock_trigger (char * str, char ** repl + r = 0; + } + /* else if (r < 0) leave *reply alone */ +- ++out: + lock_cleanup_pop(vecs->lock); + return r; + } +Index: multipath-tools-130222/multipathd/uxlsnr.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/uxlsnr.c ++++ multipath-tools-130222/multipathd/uxlsnr.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -48,6 +49,23 @@ struct pollfd *polls; + volatile sig_atomic_t reconfig_sig = 0; + volatile sig_atomic_t log_reset_sig = 0; + ++static bool _socket_client_is_root(int fd); ++ ++static bool _socket_client_is_root(int fd) ++{ ++ socklen_t len = 0; ++ struct ucred uc; ++ ++ len = sizeof(struct ucred); ++ if ((fd >= 0) && ++ (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) == 0) && ++ (uc.uid == 0)) ++ return true; ++ ++ /* Treat error as not root client */ ++ return false; ++} ++ + /* + * handle a new client joining + */ +@@ -105,8 +123,7 @@ void uxsock_cleanup(void *arg) + /* + * entry point + */ +-void * uxsock_listen(int (*uxsock_trigger)(char *, char **, int *, void *), +- void * trigger_data) ++void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, void * trigger_data) + { + int ux_sock; + int rlen; +@@ -171,12 +188,16 @@ void * uxsock_listen(int (*uxsock_trigge + struct client *next = c->next; + + if (polls[i].revents & POLLIN) { +- if (recv_packet(c->fd, &inbuf) != 0) { ++ if (recv_packet_from_client(c->fd, ++ &inbuf) != 0) { + dead_client(c); ++ } else if (!inbuf) { ++ condlog(4, "recv_packet_from_client " ++ "get null request"); ++ continue; + } else { + condlog(4, "Got request [%s]", inbuf); +- uxsock_trigger(inbuf, &reply, &rlen, +- trigger_data); ++ uxsock_trigger(inbuf, &reply, &rlen, _socket_client_is_root(c->fd), trigger_data); + if (reply) { + if (send_packet(c->fd, + reply) != 0) { +Index: multipath-tools-130222/multipathd/uxlsnr.h +=================================================================== +--- multipath-tools-130222.orig/multipathd/uxlsnr.h ++++ multipath-tools-130222/multipathd/uxlsnr.h +@@ -1,9 +1,11 @@ + #ifndef _UXLSNR_H + #define _UXLSNR_H + +-void * uxsock_listen(int (*uxsock_trigger) +- (char *, char **, int *, void *), +- void * trigger_data); ++#include ++ ++typedef int (uxsock_trigger_fn)(char *, char **, int *, bool, void *); ++ ++void *uxsock_listen(uxsock_trigger_fn uxsock_trigger, void * trigger_data); + + extern volatile sig_atomic_t reconfig_sig; + extern volatile sig_atomic_t log_reset_sig; diff --git a/0209-UPBZ-1430097-multipath-C-API.patch b/0209-UPBZ-1430097-multipath-C-API.patch new file mode 100644 index 0000000..08a6f1d --- /dev/null +++ b/0209-UPBZ-1430097-multipath-C-API.patch @@ -0,0 +1,5632 @@ +From 4335abb36f33f12eadc943729901fac31f3dc012 Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Fri, 24 Feb 2017 20:50:26 +0800 +Subject: [PATCH] multipath-tools: Introducing multipath C API + +Features: + + * Use mpath_cmd.h for IPC connection and use output of 'show maps json'. + * Library user guide will be 'man 3 libdmmp.h'. + * Every public function has its own manpage in section 3 which is + generated by linux 'kernel-doc' tool. + +Usage: + + make -j5 + sudo make install \ + bindir=/usr/sbin/ \ + syslibdir=/usr/lib64/ \ + libdir=/usr/lib64/multipath \ + rcdir=/etc/rc.d/init.d \ + unitdir=/usr/lib/systemd/system \ + includedir=/usr/include + make -C libdmmp check + make -C libdmmp speed_test + + man libdmmp.h + man dmmp_mpath_array_get + man + +Performance: + + * 10k scsi_debug sdX with 2 disks per mpath (i7-6820HQ 16GiB RAM): + $ make -C libdmmp speed_test + Got 5000 mpath + real 3.22 + user 0.15 + sys 0.01 + +Misc: + * Developer note is libdmmp/DEV_NOTES. + +Changes since V4: + + * Add new function dmmp_mpath_kdev_name_get() to query the '/dev/dm-01' for + mpath. + * Updated manpages. + * Rebased to current master ea4367159d32444e48a409a4f1c4f18324b737a9. + +Signed-off-by: Gris Ge +--- + .gitignore | 8 + Makefile | 1 + Makefile.inc | 6 + libdmmp/DEV_NOTES | 41 + libdmmp/Makefile | 84 + + libdmmp/docs/doc-preclean.pl | 28 + libdmmp/docs/kernel-doc | 3156 ++++++++++++++++++++++++++++++++++++++ + libdmmp/docs/libdmmp.h.3 | 113 + + libdmmp/docs/split-man.pl | 40 + libdmmp/libdmmp.c | 285 +++ + libdmmp/libdmmp.pc.in | 9 + libdmmp/libdmmp/libdmmp.h | 653 +++++++ + libdmmp/libdmmp_misc.c | 87 + + libdmmp/libdmmp_mp.c | 159 + + libdmmp/libdmmp_path.c | 115 + + libdmmp/libdmmp_pg.c | 208 ++ + libdmmp/libdmmp_private.h | 208 ++ + libdmmp/test/Makefile | 30 + libdmmp/test/libdmmp_speed_test.c | 49 + libdmmp/test/libdmmp_test.c | 147 + + 20 files changed, 5426 insertions(+), 1 deletion(-) + create mode 100644 libdmmp/DEV_NOTES + create mode 100644 libdmmp/Makefile + create mode 100644 libdmmp/docs/doc-preclean.pl + create mode 100644 libdmmp/docs/kernel-doc + create mode 100644 libdmmp/docs/libdmmp.h.3 + create mode 100644 libdmmp/docs/split-man.pl + create mode 100644 libdmmp/libdmmp.c + create mode 100644 libdmmp/libdmmp.pc.in + create mode 100644 libdmmp/libdmmp/libdmmp.h + create mode 100644 libdmmp/libdmmp_misc.c + create mode 100644 libdmmp/libdmmp_mp.c + create mode 100644 libdmmp/libdmmp_path.c + create mode 100644 libdmmp/libdmmp_pg.c + create mode 100644 libdmmp/libdmmp_private.h + create mode 100644 libdmmp/test/Makefile + create mode 100644 libdmmp/test/libdmmp_speed_test.c + create mode 100644 libdmmp/test/libdmmp_test.c + +Index: multipath-tools-130222/.gitignore +=================================================================== +--- multipath-tools-130222.orig/.gitignore ++++ multipath-tools-130222/.gitignore +@@ -10,3 +10,11 @@ multipath/multipath + multipathd/multipathd + mpathpersist/mpathpersist + .nfs* ++*.swp ++*.patch ++*.rej ++*.orig ++libdmmp/docs/man/*.3.gz ++libdmmp/*.so.* ++libdmmp/test/libdmmp_test ++libdmmp/test/libdmmp_speed_test +Index: multipath-tools-130222/Makefile +=================================================================== +--- multipath-tools-130222.orig/Makefile ++++ multipath-tools-130222/Makefile +@@ -25,6 +25,7 @@ BUILDDIRS = \ + libmultipath/prioritizers \ + libmultipath/checkers \ + libmpathpersist \ ++ libdmmp \ + multipath \ + multipathd \ + mpathpersist \ +Index: multipath-tools-130222/Makefile.inc +=================================================================== +--- multipath-tools-130222.orig/Makefile.inc ++++ multipath-tools-130222/Makefile.inc +@@ -36,8 +36,12 @@ unitdir = $(prefix)/lib/systemd/syst + mpathpersistdir = $(TOPDIR)/libmpathpersist + includedir = $(prefix)/usr/include + mpathcmddir = $(TOPDIR)/libmpathcmd ++libdmmpdir = $(TOPDIR)/libdmmp ++pkgconfdir = $(prefix)/usr/$(LIB)/pkgconfig + +-GZIP = /bin/gzip -9 -c ++GZIP = /bin/gzip -9 -c ++RM = rm -f ++LN = ln -sf + INSTALL_PROGRAM = install + + ifndef RPM_OPT_FLAGS +Index: multipath-tools-130222/libdmmp/DEV_NOTES +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/DEV_NOTES +@@ -0,0 +1,41 @@ ++== Planed features == ++ * Expose all properties used by /usr/bin/multipath ++ ++== Code style == ++ * Keep things as simple as possible. ++ * Linux Kernel code style. ++ * Don't use typedef. ++ * Don't use enum. ++ * We are not smarter than API user, so don't create wrapping function like: ++ ++ ``` ++ dmmp_mpath_search_by_id(struct dmmp_context *ctx, ++ struct dmmp_mpath **dmmp_mp, ++ uint32_t dmmp_mp_count, const char *id) ++ ++ dmmp_path_group_id_search(struct dmmp_mpath *dmmp_mp, ++ const char *blk_name) ++ ``` ++ * The performance is the same for query single mpath and query all mpaths, ++ so no `dmmp_mpath_of_wwid(struct dmmp_context *ctx, const char *wwid)` yet. ++ ++== Naming scheme == ++ * Public constants should be named as `DMMP_XXX_YYY`. ++ * Public functions should be named as `dmmp__`. ++ * Private constants should be named as `_DMMP_XXX_YYY`. ++ * Private functions should be named as `_dmmp__`. ++ ++== Code Layout == ++ * libdmmp_private.h ++ Internal functions or macros. ++ * libdmmp.c ++ Handling multipathd IPC and generate dmmp_context and ++ dmmp_mpath_array_get(). ++ * libdmmp_mp.c ++ For `struct dmmp_mpath` ++ * libdmmp_pg.c ++ For `struct dmmp_path_group` ++ * libdmmp_path.c ++ For `struct dmmp_path` ++ * libdmmp_misc.c ++ Misc functions. +Index: multipath-tools-130222/libdmmp/Makefile +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/Makefile +@@ -0,0 +1,84 @@ ++# Makefile ++# ++# Copyright (C) 2015 - 2016 Red Hat, Inc. ++# Gris Ge ++# ++include ../Makefile.inc ++ ++LIBDMMP_VERSION=0.1.0 ++SONAME=$(LIBDMMP_VERSION) ++DEVLIB = libdmmp.so ++LIBS = $(DEVLIB).$(SONAME) ++LIBDEPS = -pthread ++PKGFILE = libdmmp.pc ++EXTRA_MAN_FILES = libdmmp.h.3 ++HEADERS = libdmmp/libdmmp.h ++OBJS = libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o ++ ++CFLAGS += -fPIC -fvisibility=hidden -I$(libdmmpdir) -I$(mpathcmddir) \ ++ $(shell pkg-config --cflags json-c) ++LDFLAGS += $(shell pkg-config --libs json-c) -L$(mpathcmddir) -lmpathcmd ++ ++all: $(LIBS) doc ++ ++$(LIBS): $(OBJS) ++ $(CC) $(LDFLAGS) $(SHARED_FLAGS) \ ++ -Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS) ++ $(LN) $@ $(DEVLIB) ++ ++install: ++ $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS) ++ $(INSTALL_PROGRAM) -m 644 -D \ ++ $(HEADERS) $(DESTDIR)$(includedir)/$(HEADERS) ++ $(LN) $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) ++ $(INSTALL_PROGRAM) -m 644 -D \ ++ $(PKGFILE).in $(DESTDIR)$(pkgconfdir)/$(PKGFILE) ++ perl -i -pe 's|__VERSION__|$(LIBDMMP_VERSION)|g' \ ++ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) ++ perl -i -pe 's|__LIBDIR__|$(syslibdir)|g' \ ++ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) ++ perl -i -pe 's|__INCLUDEDIR__|$(includedir)|g' \ ++ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) ++ @for file in docs/man/*.3.gz; do \ ++ $(INSTALL_PROGRAM) -m 644 -D \ ++ $$file \ ++ $(DESTDIR)$(man3dir)/ || exit $?; \ ++ done ++ ++uninstall: ++ $(RM) $(DESTDIR)$(syslibdir)/$(LIBS) ++ $(RM) $(DESTDIR)$(includedir)/$(HEADERS) ++ $(RM) $(DESTDIR)$(syslibdir)/$(DEVLIB) ++ @for file in $(DESTDIR)$(man3dir)/dmmp_*; do \ ++ $(RM) $$file; \ ++ done ++ $(RM) $(DESTDIR)$(man3dir)/libdmmp.h* ++ ++clean: ++ $(RM) core *.a *.o *.gz *.so *.so.* ++ $(RM) docs/man/*.3.gz ++ $(MAKE) -C test clean ++ ++check: all ++ $(MAKE) -C test check ++ ++speed_test: all ++ $(MAKE) -C test speed_test ++ ++doc: docs/man/$(EXTRA_MAN_FILES).gz ++ ++TEMPFILE := $(shell mktemp) ++ ++docs/man/$(EXTRA_MAN_FILES).gz: $(HEADERS) ++ @for file in $(EXTRA_MAN_FILES); do \ ++ $(INSTALL_PROGRAM) -v -m 644 -D docs/$$file docs/man/$$file; \ ++ done ++ cat $(HEADERS) | \ ++ perl docs/doc-preclean.pl > $(TEMPFILE) ++ perl docs/kernel-doc -man $(TEMPFILE) | \ ++ perl docs/split-man.pl docs/man ++ -rm -f $(TEMPFILE) ++ @for file in docs/man/*.3; do \ ++ gzip -f $$file; \ ++ done ++ find docs/man -type f -name \*[0-9].gz +Index: multipath-tools-130222/libdmmp/docs/doc-preclean.pl +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/docs/doc-preclean.pl +@@ -0,0 +1,28 @@ ++#!/usr/bin/perl ++# Copyright (C) 2016 Red Hat, Inc. ++# ++# This program is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program. If not, see . ++# ++# Author: Gris Ge ++ ++use strict; ++ ++my @REMOVE_KEY_LIST=("DMMP_DLL_EXPORT"); ++ ++while (<>) { ++ for my $key (@REMOVE_KEY_LIST) { ++ (s/$key//g); ++ } ++ print; ++} +Index: multipath-tools-130222/libdmmp/docs/kernel-doc +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/docs/kernel-doc +@@ -0,0 +1,3156 @@ ++#!/usr/bin/perl -w ++ ++use strict; ++ ++## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## ++## Copyright (C) 2000, 1 Tim Waugh ## ++## Copyright (C) 2001 Simon Huggins ## ++## Copyright (C) 2005-2012 Randy Dunlap ## ++## Copyright (C) 2012 Dan Luedtke ## ++## ## ++## #define enhancements by Armin Kuster ## ++## Copyright (c) 2000 MontaVista Software, Inc. ## ++## ## ++## This software falls under the GNU General Public License. ## ++## Please read the COPYING file for more information ## ++ ++# 18/01/2001 - Cleanups ++# Functions prototyped as foo(void) same as foo() ++# Stop eval'ing where we don't need to. ++# -- huggie@earth.li ++ ++# 27/06/2001 - Allowed whitespace after initial "/**" and ++# allowed comments before function declarations. ++# -- Christian Kreibich ++ ++# Still to do: ++# - add perldoc documentation ++# - Look more closely at some of the scarier bits :) ++ ++# 26/05/2001 - Support for separate source and object trees. ++# Return error code. ++# Keith Owens ++ ++# 23/09/2001 - Added support for typedefs, structs, enums and unions ++# Support for Context section; can be terminated using empty line ++# Small fixes (like spaces vs. \s in regex) ++# -- Tim Jansen ++ ++# 25/07/2012 - Added support for HTML5 ++# -- Dan Luedtke ++ ++sub usage { ++ my $message = <<"EOF"; ++Usage: $0 [OPTION ...] FILE ... ++ ++Read C language source or header FILEs, extract embedded documentation comments, ++and print formatted documentation to standard output. ++ ++The documentation comments are identified by "/**" opening comment mark. See ++Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax. ++ ++Output format selection (mutually exclusive): ++ -docbook Output DocBook format. ++ -html Output HTML format. ++ -html5 Output HTML5 format. ++ -list Output symbol list format. This is for use by docproc. ++ -man Output troff manual page format. This is the default. ++ -rst Output reStructuredText format. ++ -text Output plain text format. ++ ++Output selection (mutually exclusive): ++ -export Only output documentation for symbols that have been ++ exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() ++ in any input FILE or -export-file FILE. ++ -internal Only output documentation for symbols that have NOT been ++ exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() ++ in any input FILE or -export-file FILE. ++ -function NAME Only output documentation for the given function(s) ++ or DOC: section title(s). All other functions and DOC: ++ sections are ignored. May be specified multiple times. ++ -nofunction NAME Do NOT output documentation for the given function(s); ++ only output documentation for the other functions and ++ DOC: sections. May be specified multiple times. ++ ++Output selection modifiers: ++ -no-doc-sections Do not output DOC: sections. ++ -enable-lineno Enable output of #define LINENO lines. Only works with ++ reStructuredText format. ++ -export-file FILE Specify an additional FILE in which to look for ++ EXPORT_SYMBOL() and EXPORT_SYMBOL_GPL(). To be used with ++ -export or -internal. May be specified multiple times. ++ ++Other parameters: ++ -v Verbose output, more warnings and other information. ++ -h Print this help. ++ ++EOF ++ print $message; ++ exit 1; ++} ++ ++# ++# format of comments. ++# In the following table, (...)? signifies optional structure. ++# (...)* signifies 0 or more structure elements ++# /** ++# * function_name(:)? (- short description)? ++# (* @parameterx: (description of parameter x)?)* ++# (* a blank line)? ++# * (Description:)? (Description of function)? ++# * (section header: (section description)? )* ++# (*)?*/ ++# ++# So .. the trivial example would be: ++# ++# /** ++# * my_function ++# */ ++# ++# If the Description: header tag is omitted, then there must be a blank line ++# after the last parameter specification. ++# e.g. ++# /** ++# * my_function - does my stuff ++# * @my_arg: its mine damnit ++# * ++# * Does my stuff explained. ++# */ ++# ++# or, could also use: ++# /** ++# * my_function - does my stuff ++# * @my_arg: its mine damnit ++# * Description: Does my stuff explained. ++# */ ++# etc. ++# ++# Besides functions you can also write documentation for structs, unions, ++# enums and typedefs. Instead of the function name you must write the name ++# of the declaration; the struct/union/enum/typedef must always precede ++# the name. Nesting of declarations is not supported. ++# Use the argument mechanism to document members or constants. ++# e.g. ++# /** ++# * struct my_struct - short description ++# * @a: first member ++# * @b: second member ++# * ++# * Longer description ++# */ ++# struct my_struct { ++# int a; ++# int b; ++# /* private: */ ++# int c; ++# }; ++# ++# All descriptions can be multiline, except the short function description. ++# ++# For really longs structs, you can also describe arguments inside the ++# body of the struct. ++# eg. ++# /** ++# * struct my_struct - short description ++# * @a: first member ++# * @b: second member ++# * ++# * Longer description ++# */ ++# struct my_struct { ++# int a; ++# int b; ++# /** ++# * @c: This is longer description of C ++# * ++# * You can use paragraphs to describe arguments ++# * using this method. ++# */ ++# int c; ++# }; ++# ++# This should be use only for struct/enum members. ++# ++# You can also add additional sections. When documenting kernel functions you ++# should document the "Context:" of the function, e.g. whether the functions ++# can be called form interrupts. Unlike other sections you can end it with an ++# empty line. ++# A non-void function should have a "Return:" section describing the return ++# value(s). ++# Example-sections should contain the string EXAMPLE so that they are marked ++# appropriately in DocBook. ++# ++# Example: ++# /** ++# * user_function - function that can only be called in user context ++# * @a: some argument ++# * Context: !in_interrupt() ++# * ++# * Some description ++# * Example: ++# * user_function(22); ++# */ ++# ... ++# ++# ++# All descriptive text is further processed, scanning for the following special ++# patterns, which are highlighted appropriately. ++# ++# 'funcname()' - function ++# '$ENVVAR' - environmental variable ++# '&struct_name' - name of a structure (up to two words including 'struct') ++# '@parameter' - name of a parameter ++# '%CONST' - name of a constant. ++ ++## init lots of data ++ ++ ++my $errors = 0; ++my $warnings = 0; ++my $anon_struct_union = 0; ++ ++# match expressions used to find embedded type information ++my $type_constant = '\%([-_\w]+)'; ++my $type_func = '(\w+)\(\)'; ++my $type_param = '\@(\w+(\.\.\.)?)'; ++my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params ++my $type_struct = '\&((struct\s*)*[_\w]+)'; ++my $type_struct_xml = '\\&((struct\s*)*[_\w]+)'; ++my $type_env = '(\$\w+)'; ++my $type_enum_full = '\&(enum)\s*([_\w]+)'; ++my $type_struct_full = '\&(struct)\s*([_\w]+)'; ++my $type_typedef_full = '\&(typedef)\s*([_\w]+)'; ++my $type_union_full = '\&(union)\s*([_\w]+)'; ++my $type_member = '\&([_\w]+)((\.|->)[_\w]+)'; ++my $type_member_func = $type_member . '\(\)'; ++ ++# Output conversion substitutions. ++# One for each output format ++ ++# these work fairly well ++my @highlights_html = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\$1"], ++ [$type_struct_xml, "\$1"], ++ [$type_env, "\$1"], ++ [$type_param, "\$1"] ++ ); ++my $local_lt = "\\\\\\\\lt:"; ++my $local_gt = "\\\\\\\\gt:"; ++my $blankline_html = $local_lt . "p" . $local_gt; # was "

" ++ ++# html version 5 ++my @highlights_html5 = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\$1"], ++ [$type_struct_xml, "\$1"], ++ [$type_env, "\$1"], ++ [$type_param, "\$1]"] ++ ); ++my $blankline_html5 = $local_lt . "br /" . $local_gt; ++ ++# XML, docbook format ++my @highlights_xml = ( ++ ["([^=])\\\"([^\\\"<]+)\\\"", "\$1\$2"], ++ [$type_constant, "\$1"], ++ [$type_struct_xml, "\$1"], ++ [$type_param, "\$1"], ++ [$type_func, "\$1"], ++ [$type_env, "\$1"] ++ ); ++my $blankline_xml = $local_lt . "/para" . $local_gt . $local_lt . "para" . $local_gt . "\n"; ++ ++# gnome, docbook format ++my @highlights_gnome = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\$1"], ++ [$type_struct, "\$1"], ++ [$type_env, "\$1"], ++ [$type_param, "\$1" ] ++ ); ++my $blankline_gnome = "\n"; ++ ++# these are pretty rough ++my @highlights_man = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\\\\fB\$1\\\\fP"], ++ [$type_struct, "\\\\fI\$1\\\\fP"], ++ [$type_param, "\\\\fI\$1\\\\fP"] ++ ); ++my $blankline_man = ""; ++ ++# text-mode ++my @highlights_text = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\$1"], ++ [$type_struct, "\$1"], ++ [$type_param, "\$1"] ++ ); ++my $blankline_text = ""; ++ ++# rst-mode ++my @highlights_rst = ( ++ [$type_constant, "``\$1``"], ++ # Note: need to escape () to avoid func matching later ++ [$type_member_func, "\\:c\\:type\\:`\$1\$2\\\\(\\\\) <\$1>`"], ++ [$type_member, "\\:c\\:type\\:`\$1\$2 <\$1>`"], ++ [$type_fp_param, "**\$1\\\\(\\\\)**"], ++ [$type_func, "\\:c\\:func\\:`\$1()`"], ++ [$type_struct_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"], ++ [$type_enum_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"], ++ [$type_typedef_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"], ++ [$type_union_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"], ++ # in rst this can refer to any type ++ [$type_struct, "\\:c\\:type\\:`\$1`"], ++ [$type_param, "**\$1**"] ++ ); ++my $blankline_rst = "\n"; ++ ++# list mode ++my @highlights_list = ( ++ [$type_constant, "\$1"], ++ [$type_func, "\$1"], ++ [$type_struct, "\$1"], ++ [$type_param, "\$1"] ++ ); ++my $blankline_list = ""; ++ ++# read arguments ++if ($#ARGV == -1) { ++ usage(); ++} ++ ++my $kernelversion; ++my $dohighlight = ""; ++ ++my $verbose = 0; ++my $output_mode = "man"; ++my $output_preformatted = 0; ++my $no_doc_sections = 0; ++my $enable_lineno = 0; ++my @highlights = @highlights_man; ++my $blankline = $blankline_man; ++my $modulename = "Kernel API"; ++ ++use constant { ++ OUTPUT_ALL => 0, # output all symbols and doc sections ++ OUTPUT_INCLUDE => 1, # output only specified symbols ++ OUTPUT_EXCLUDE => 2, # output everything except specified symbols ++ OUTPUT_EXPORTED => 3, # output exported symbols ++ OUTPUT_INTERNAL => 4, # output non-exported symbols ++}; ++my $output_selection = OUTPUT_ALL; ++my $show_not_found = 0; ++ ++my @export_file_list; ++ ++my @build_time; ++if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && ++ (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { ++ @build_time = gmtime($seconds); ++} else { ++ @build_time = localtime; ++} ++ ++my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', ++ 'July', 'August', 'September', 'October', ++ 'November', 'December')[$build_time[4]] . ++ " " . ($build_time[5]+1900); ++ ++# Essentially these are globals. ++# They probably want to be tidied up, made more localised or something. ++# CAVEAT EMPTOR! Some of the others I localised may not want to be, which ++# could cause "use of undefined value" or other bugs. ++my ($function, %function_table, %parametertypes, $declaration_purpose); ++my $declaration_start_line; ++my ($type, $declaration_name, $return_type); ++my ($newsection, $newcontents, $prototype, $brcount, %source_map); ++ ++if (defined($ENV{'KBUILD_VERBOSE'})) { ++ $verbose = "$ENV{'KBUILD_VERBOSE'}"; ++} ++ ++# Generated docbook code is inserted in a template at a point where ++# docbook v3.1 requires a non-zero sequence of RefEntry's; see: ++# http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html ++# We keep track of number of generated entries and generate a dummy ++# if needs be to ensure the expanded template can be postprocessed ++# into html. ++my $section_counter = 0; ++ ++my $lineprefix=""; ++ ++# Parser states ++use constant { ++ STATE_NORMAL => 0, # normal code ++ STATE_NAME => 1, # looking for function name ++ STATE_FIELD => 2, # scanning field start ++ STATE_PROTO => 3, # scanning prototype ++ STATE_DOCBLOCK => 4, # documentation block ++ STATE_INLINE => 5, # gathering documentation outside main block ++}; ++my $state; ++my $in_doc_sect; ++ ++# Inline documentation state ++use constant { ++ STATE_INLINE_NA => 0, # not applicable ($state != STATE_INLINE) ++ STATE_INLINE_NAME => 1, # looking for member name (@foo:) ++ STATE_INLINE_TEXT => 2, # looking for member documentation ++ STATE_INLINE_END => 3, # done ++ STATE_INLINE_ERROR => 4, # error - Comment without header was found. ++ # Spit a warning as it's not ++ # proper kernel-doc and ignore the rest. ++}; ++my $inline_doc_state; ++ ++#declaration types: can be ++# 'function', 'struct', 'union', 'enum', 'typedef' ++my $decl_type; ++ ++my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. ++my $doc_end = '\*/'; ++my $doc_com = '\s*\*\s*'; ++my $doc_com_body = '\s*\* ?'; ++my $doc_decl = $doc_com . '(\w+)'; ++# @params and a strictly limited set of supported section names ++my $doc_sect = $doc_com . ++ '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:(.*)'; ++my $doc_content = $doc_com_body . '(.*)'; ++my $doc_block = $doc_com . 'DOC:\s*(.*)?'; ++my $doc_inline_start = '^\s*/\*\*\s*$'; ++my $doc_inline_sect = '\s*\*\s*(@[\w\s]+):(.*)'; ++my $doc_inline_end = '^\s*\*/\s*$'; ++my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$'; ++my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;'; ++ ++my %parameterdescs; ++my %parameterdesc_start_lines; ++my @parameterlist; ++my %sections; ++my @sectionlist; ++my %section_start_lines; ++my $sectcheck; ++my $struct_actual; ++ ++my $contents = ""; ++my $new_start_line = 0; ++ ++# the canonical section names. see also $doc_sect above. ++my $section_default = "Description"; # default section ++my $section_intro = "Introduction"; ++my $section = $section_default; ++my $section_context = "Context"; ++my $section_return = "Return"; ++ ++my $undescribed = "-- undescribed --"; ++ ++reset_state(); ++ ++while ($ARGV[0] =~ m/^-(.*)/) { ++ my $cmd = shift @ARGV; ++ if ($cmd eq "-html") { ++ $output_mode = "html"; ++ @highlights = @highlights_html; ++ $blankline = $blankline_html; ++ } elsif ($cmd eq "-html5") { ++ $output_mode = "html5"; ++ @highlights = @highlights_html5; ++ $blankline = $blankline_html5; ++ } elsif ($cmd eq "-man") { ++ $output_mode = "man"; ++ @highlights = @highlights_man; ++ $blankline = $blankline_man; ++ } elsif ($cmd eq "-text") { ++ $output_mode = "text"; ++ @highlights = @highlights_text; ++ $blankline = $blankline_text; ++ } elsif ($cmd eq "-rst") { ++ $output_mode = "rst"; ++ @highlights = @highlights_rst; ++ $blankline = $blankline_rst; ++ } elsif ($cmd eq "-docbook") { ++ $output_mode = "xml"; ++ @highlights = @highlights_xml; ++ $blankline = $blankline_xml; ++ } elsif ($cmd eq "-list") { ++ $output_mode = "list"; ++ @highlights = @highlights_list; ++ $blankline = $blankline_list; ++ } elsif ($cmd eq "-gnome") { ++ $output_mode = "gnome"; ++ @highlights = @highlights_gnome; ++ $blankline = $blankline_gnome; ++ } elsif ($cmd eq "-module") { # not needed for XML, inherits from calling document ++ $modulename = shift @ARGV; ++ } elsif ($cmd eq "-function") { # to only output specific functions ++ $output_selection = OUTPUT_INCLUDE; ++ $function = shift @ARGV; ++ $function_table{$function} = 1; ++ } elsif ($cmd eq "-nofunction") { # output all except specific functions ++ $output_selection = OUTPUT_EXCLUDE; ++ $function = shift @ARGV; ++ $function_table{$function} = 1; ++ } elsif ($cmd eq "-export") { # only exported symbols ++ $output_selection = OUTPUT_EXPORTED; ++ %function_table = (); ++ } elsif ($cmd eq "-internal") { # only non-exported symbols ++ $output_selection = OUTPUT_INTERNAL; ++ %function_table = (); ++ } elsif ($cmd eq "-export-file") { ++ my $file = shift @ARGV; ++ push(@export_file_list, $file); ++ } elsif ($cmd eq "-v") { ++ $verbose = 1; ++ } elsif (($cmd eq "-h") || ($cmd eq "--help")) { ++ usage(); ++ } elsif ($cmd eq '-no-doc-sections') { ++ $no_doc_sections = 1; ++ } elsif ($cmd eq '-enable-lineno') { ++ $enable_lineno = 1; ++ } elsif ($cmd eq '-show-not-found') { ++ $show_not_found = 1; ++ } ++} ++ ++# continue execution near EOF; ++ ++# get kernel version from env ++sub get_kernel_version() { ++ my $version = 'unknown kernel version'; ++ ++ if (defined($ENV{'KERNELVERSION'})) { ++ $version = $ENV{'KERNELVERSION'}; ++ } ++ return $version; ++} ++ ++# ++sub print_lineno { ++ my $lineno = shift; ++ if ($enable_lineno && defined($lineno)) { ++ print "#define LINENO " . $lineno . "\n"; ++ } ++} ++## ++# dumps section contents to arrays/hashes intended for that purpose. ++# ++sub dump_section { ++ my $file = shift; ++ my $name = shift; ++ my $contents = join "\n", @_; ++ ++ if ($name =~ m/$type_param/) { ++ $name = $1; ++ $parameterdescs{$name} = $contents; ++ $sectcheck = $sectcheck . $name . " "; ++ $parameterdesc_start_lines{$name} = $new_start_line; ++ $new_start_line = 0; ++ } elsif ($name eq "@\.\.\.") { ++ $name = "..."; ++ $parameterdescs{$name} = $contents; ++ $sectcheck = $sectcheck . $name . " "; ++ $parameterdesc_start_lines{$name} = $new_start_line; ++ $new_start_line = 0; ++ } else { ++ if (defined($sections{$name}) && ($sections{$name} ne "")) { ++ # Only warn on user specified duplicate section names. ++ if ($name ne $section_default) { ++ print STDERR "${file}:$.: warning: duplicate section name '$name'\n"; ++ ++$warnings; ++ } ++ $sections{$name} .= $contents; ++ } else { ++ $sections{$name} = $contents; ++ push @sectionlist, $name; ++ $section_start_lines{$name} = $new_start_line; ++ $new_start_line = 0; ++ } ++ } ++} ++ ++## ++# dump DOC: section after checking that it should go out ++# ++sub dump_doc_section { ++ my $file = shift; ++ my $name = shift; ++ my $contents = join "\n", @_; ++ ++ if ($no_doc_sections) { ++ return; ++ } ++ ++ if (($output_selection == OUTPUT_ALL) || ++ ($output_selection == OUTPUT_INCLUDE && ++ defined($function_table{$name})) || ++ ($output_selection == OUTPUT_EXCLUDE && ++ !defined($function_table{$name}))) ++ { ++ dump_section($file, $name, $contents); ++ output_blockhead({'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'module' => $modulename, ++ 'content-only' => ($output_selection != OUTPUT_ALL), }); ++ } ++} ++ ++## ++# output function ++# ++# parameterdescs, a hash. ++# function => "function name" ++# parameterlist => @list of parameters ++# parameterdescs => %parameter descriptions ++# sectionlist => @list of sections ++# sections => %section descriptions ++# ++ ++sub output_highlight { ++ my $contents = join "\n",@_; ++ my $line; ++ ++# DEBUG ++# if (!defined $contents) { ++# use Carp; ++# confess "output_highlight got called with no args?\n"; ++# } ++ ++ if ($output_mode eq "html" || $output_mode eq "html5" || ++ $output_mode eq "xml") { ++ $contents = local_unescape($contents); ++ # convert data read & converted thru xml_escape() into &xyz; format: ++ $contents =~ s/\\\\\\/\&/g; ++ } ++# print STDERR "contents b4:$contents\n"; ++ eval $dohighlight; ++ die $@ if $@; ++# print STDERR "contents af:$contents\n"; ++ ++# strip whitespaces when generating html5 ++ if ($output_mode eq "html5") { ++ $contents =~ s/^\s+//; ++ $contents =~ s/\s+$//; ++ } ++ foreach $line (split "\n", $contents) { ++ if (! $output_preformatted) { ++ $line =~ s/^\s*//; ++ } ++ if ($line eq ""){ ++ if (! $output_preformatted) { ++ print $lineprefix, local_unescape($blankline); ++ } ++ } else { ++ $line =~ s/\\\\\\/\&/g; ++ if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { ++ print "\\&$line"; ++ } else { ++ print $lineprefix, $line; ++ } ++ } ++ print "\n"; ++ } ++} ++ ++# output sections in html ++sub output_section_html(%) { ++ my %args = %{$_[0]}; ++ my $section; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "

$section

\n"; ++ print "
\n"; ++ output_highlight($args{'sections'}{$section}); ++ print "
\n"; ++ } ++} ++ ++# output enum in html ++sub output_enum_html(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ print "

enum " . $args{'enum'} . "

\n"; ++ ++ print "enum " . $args{'enum'} . " {
\n"; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print " " . $parameter . ""; ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ",\n"; ++ } ++ print "
"; ++ } ++ print "};
\n"; ++ ++ print "

Constants

\n"; ++ print "
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "
" . $parameter . "\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter}); ++ } ++ print "
\n"; ++ output_section_html(@_); ++ print "
\n"; ++} ++ ++# output typedef in html ++sub output_typedef_html(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ print "

typedef " . $args{'typedef'} . "

\n"; ++ ++ print "typedef " . $args{'typedef'} . "\n"; ++ output_section_html(@_); ++ print "
\n"; ++} ++ ++# output struct in html ++sub output_struct_html(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ ++ print "

" . $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "

\n"; ++ print "" . $args{'type'} . " " . $args{'struct'} . " {
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ if ($parameter =~ /^#/) { ++ print "$parameter
\n"; ++ next; ++ } ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print "    $1$parameter) ($2);
\n"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print "    $1 $parameter$2;
\n"; ++ } else { ++ print "    $type $parameter;
\n"; ++ } ++ } ++ print "};
\n"; ++ ++ print "

Members

\n"; ++ print "
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print "
" . $parameter . "\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ } ++ print "
\n"; ++ output_section_html(@_); ++ print "
\n"; ++} ++ ++# output function in html ++sub output_function_html(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ print "

" . $args{'function'} . " - " . $args{'purpose'} . "

\n"; ++ print "" . $args{'functiontype'} . "\n"; ++ print "" . $args{'function'} . "\n"; ++ print "("; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print "$1$parameter) ($2)"; ++ } else { ++ print "" . $type . " " . $parameter . ""; ++ } ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ",\n"; ++ } ++ } ++ print ")\n"; ++ ++ print "

Arguments

\n"; ++ print "
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print "
" . $parameter . "\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ } ++ print "
\n"; ++ output_section_html(@_); ++ print "
\n"; ++} ++ ++# output DOC: block header in html ++sub output_blockhead_html(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "

$section

\n"; ++ print "
    \n"; ++ output_highlight($args{'sections'}{$section}); ++ print "
\n"; ++ } ++ print "
\n"; ++} ++ ++# output sections in html5 ++sub output_section_html5(%) { ++ my %args = %{$_[0]}; ++ my $section; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "
\n"; ++ print "

$section

\n"; ++ print "

\n"; ++ output_highlight($args{'sections'}{$section}); ++ print "

\n"; ++ print "
\n"; ++ } ++} ++ ++# output enum in html5 ++sub output_enum_html5(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ my $html5id; ++ ++ $html5id = $args{'enum'}; ++ $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; ++ print "
"; ++ print "

enum " . $args{'enum'} . "

\n"; ++ print "
    \n"; ++ print "
  1. "; ++ print "enum "; ++ print "" . $args{'enum'} . " {"; ++ print "
  2. \n"; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "
  3. "; ++ print "" . $parameter . ""; ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ","; ++ } ++ print "
  4. \n"; ++ } ++ print "
  5. };
  6. \n"; ++ print "
\n"; ++ ++ print "
\n"; ++ print "

Constants

\n"; ++ print "
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "
" . $parameter . "
\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter}); ++ print "
\n"; ++ } ++ print "
\n"; ++ print "
\n"; ++ output_section_html5(@_); ++ print "
\n"; ++} ++ ++# output typedef in html5 ++sub output_typedef_html5(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ my $html5id; ++ ++ $html5id = $args{'typedef'}; ++ $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; ++ print "
\n"; ++ print "

typedef " . $args{'typedef'} . "

\n"; ++ ++ print "
    \n"; ++ print "
  1. "; ++ print "typedef "; ++ print "" . $args{'typedef'} . ""; ++ print "
  2. \n"; ++ print "
\n"; ++ output_section_html5(@_); ++ print "
\n"; ++} ++ ++# output struct in html5 ++sub output_struct_html5(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $html5id; ++ ++ $html5id = $args{'struct'}; ++ $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; ++ print "
\n"; ++ print "
\n"; ++ print "

" . $args{'type'} . " " . $args{'struct'} . "

"; ++ print "

". $args{'purpose'} . "

\n"; ++ print "
\n"; ++ print "
    \n"; ++ print "
  1. "; ++ print "" . $args{'type'} . " "; ++ print "" . $args{'struct'} . " {"; ++ print "
  2. \n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "
  3. "; ++ if ($parameter =~ /^#/) { ++ print "" . $parameter ."\n"; ++ print "
  4. \n"; ++ next; ++ } ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print "$1 "; ++ print "$parameter"; ++ print ") "; ++ print "($2);"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print "$1 "; ++ print "$parameter"; ++ print "$2;"; ++ } else { ++ print "$type "; ++ print "$parameter;"; ++ } ++ print "\n"; ++ } ++ print "
  5. };
  6. \n"; ++ print "
\n"; ++ ++ print "
\n"; ++ print "

Members

\n"; ++ print "
\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print "
" . $parameter . "
\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print "
\n"; ++ } ++ print "
\n"; ++ print "
\n"; ++ output_section_html5(@_); ++ print "
\n"; ++} ++ ++# output function in html5 ++sub output_function_html5(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ my $html5id; ++ ++ $html5id = $args{'function'}; ++ $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; ++ print "
\n"; ++ print "
\n"; ++ print "

" . $args{'function'} . "

"; ++ print "

" . $args{'purpose'} . "

\n"; ++ print "
\n"; ++ print "
    \n"; ++ print "
  1. "; ++ print "" . $args{'functiontype'} . " "; ++ print "" . $args{'function'} . " ("; ++ print "
  2. "; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "
  3. "; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print "$1 "; ++ print "$parameter"; ++ print ") "; ++ print "($2)"; ++ } else { ++ print "$type "; ++ print "$parameter"; ++ } ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ","; ++ } ++ print "
  4. \n"; ++ } ++ print "
  5. )
  6. \n"; ++ print "
\n"; ++ ++ print "
\n"; ++ print "

Arguments

\n"; ++ print "

\n"; ++ print "

\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print "
" . $parameter . "
\n"; ++ print "
"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print "
\n"; ++ } ++ print "
\n"; ++ print "
\n"; ++ output_section_html5(@_); ++ print "
\n"; ++} ++ ++# output DOC: block header in html5 ++sub output_blockhead_html5(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ my $html5id; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ $html5id = $section; ++ $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; ++ print "
\n"; ++ print "

$section

\n"; ++ print "

\n"; ++ output_highlight($args{'sections'}{$section}); ++ print "

\n"; ++ } ++ print "
\n"; ++} ++ ++sub output_section_xml(%) { ++ my %args = %{$_[0]}; ++ my $section; ++ # print out each section ++ $lineprefix=" "; ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "\n"; ++ print "$section\n"; ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ $output_preformatted = 1; ++ } else { ++ print "\n"; ++ } ++ output_highlight($args{'sections'}{$section}); ++ $output_preformatted = 0; ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ } else { ++ print "\n"; ++ } ++ print "\n"; ++ } ++} ++ ++# output function in XML DocBook ++sub output_function_xml(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ my $id; ++ ++ $id = "API-" . $args{'function'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ print "\n"; ++ print "\n"; ++ print " LINUX\n"; ++ print " Kernel Hackers Manual\n"; ++ print " $man_date\n"; ++ print "\n"; ++ print "\n"; ++ print " " . $args{'function'} . "\n"; ++ print " 9\n"; ++ print " " . $kernelversion . "\n"; ++ print "\n"; ++ print "\n"; ++ print " " . $args{'function'} . "\n"; ++ print " \n"; ++ print " "; ++ output_highlight ($args{'purpose'}); ++ print " \n"; ++ print "\n"; ++ ++ print "\n"; ++ print " Synopsis\n"; ++ print " \n"; ++ print " " . $args{'functiontype'} . " "; ++ print "" . $args{'function'} . " \n"; ++ ++ $count = 0; ++ if ($#{$args{'parameterlist'}} >= 0) { ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print " $1$parameter)\n"; ++ print " $2\n"; ++ } else { ++ print " " . $type; ++ print " $parameter\n"; ++ } ++ } ++ } else { ++ print " \n"; ++ } ++ print " \n"; ++ print "\n"; ++ ++ # print parameters ++ print "\n Arguments\n"; ++ if ($#{$args{'parameterlist'}} >= 0) { ++ print " \n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print " \n $parameter\n"; ++ print " \n \n"; ++ $lineprefix=" "; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print " \n \n \n"; ++ } ++ print " \n"; ++ } else { ++ print " \n None\n \n"; ++ } ++ print "\n"; ++ ++ output_section_xml(@_); ++ print "\n\n"; ++} ++ ++# output struct in XML DocBook ++sub output_struct_xml(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $id; ++ ++ $id = "API-struct-" . $args{'struct'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ print "\n"; ++ print "\n"; ++ print " LINUX\n"; ++ print " Kernel Hackers Manual\n"; ++ print " $man_date\n"; ++ print "\n"; ++ print "\n"; ++ print " " . $args{'type'} . " " . $args{'struct'} . "\n"; ++ print " 9\n"; ++ print " " . $kernelversion . "\n"; ++ print "\n"; ++ print "\n"; ++ print " " . $args{'type'} . " " . $args{'struct'} . "\n"; ++ print " \n"; ++ print " "; ++ output_highlight ($args{'purpose'}); ++ print " \n"; ++ print "\n"; ++ ++ print "\n"; ++ print " Synopsis\n"; ++ print " \n"; ++ print $args{'type'} . " " . $args{'struct'} . " {\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ if ($parameter =~ /^#/) { ++ my $prm = $parameter; ++ # convert data read & converted thru xml_escape() into &xyz; format: ++ # This allows us to have #define macros interspersed in a struct. ++ $prm =~ s/\\\\\\/\&/g; ++ print "$prm\n"; ++ next; ++ } ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ defined($args{'parameterdescs'}{$parameter_name}) || next; ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print " $1 $parameter) ($2);\n"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print " $1 $parameter$2;\n"; ++ } else { ++ print " " . $type . " " . $parameter . ";\n"; ++ } ++ } ++ print "};"; ++ print " \n"; ++ print "\n"; ++ ++ print " \n"; ++ print " Members\n"; ++ ++ if ($#{$args{'parameterlist'}} >= 0) { ++ print " \n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ defined($args{'parameterdescs'}{$parameter_name}) || next; ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print " "; ++ print " $parameter\n"; ++ print " \n"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print " \n"; ++ print " \n"; ++ } ++ print " \n"; ++ } else { ++ print " \n None\n \n"; ++ } ++ print " \n"; ++ ++ output_section_xml(@_); ++ ++ print "\n\n"; ++} ++ ++# output enum in XML DocBook ++sub output_enum_xml(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ my $id; ++ ++ $id = "API-enum-" . $args{'enum'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ print "\n"; ++ print "\n"; ++ print " LINUX\n"; ++ print " Kernel Hackers Manual\n"; ++ print " $man_date\n"; ++ print "\n"; ++ print "\n"; ++ print " enum " . $args{'enum'} . "\n"; ++ print " 9\n"; ++ print " " . $kernelversion . "\n"; ++ print "\n"; ++ print "\n"; ++ print " enum " . $args{'enum'} . "\n"; ++ print " \n"; ++ print " "; ++ output_highlight ($args{'purpose'}); ++ print " \n"; ++ print "\n"; ++ ++ print "\n"; ++ print " Synopsis\n"; ++ print " \n"; ++ print "enum " . $args{'enum'} . " {\n"; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print " $parameter"; ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ","; ++ } ++ print "\n"; ++ } ++ print "};"; ++ print " \n"; ++ print "\n"; ++ ++ print "\n"; ++ print " Constants\n"; ++ print " \n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print " "; ++ print " $parameter\n"; ++ print " \n"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print " \n"; ++ print " \n"; ++ } ++ print " \n"; ++ print "\n"; ++ ++ output_section_xml(@_); ++ ++ print "\n\n"; ++} ++ ++# output typedef in XML DocBook ++sub output_typedef_xml(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $id; ++ ++ $id = "API-typedef-" . $args{'typedef'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ print "\n"; ++ print "\n"; ++ print " LINUX\n"; ++ print " Kernel Hackers Manual\n"; ++ print " $man_date\n"; ++ print "\n"; ++ print "\n"; ++ print " typedef " . $args{'typedef'} . "\n"; ++ print " 9\n"; ++ print "\n"; ++ print "\n"; ++ print " typedef " . $args{'typedef'} . "\n"; ++ print " \n"; ++ print " "; ++ output_highlight ($args{'purpose'}); ++ print " \n"; ++ print "\n"; ++ ++ print "\n"; ++ print " Synopsis\n"; ++ print " typedef " . $args{'typedef'} . ";\n"; ++ print "\n"; ++ ++ output_section_xml(@_); ++ ++ print "\n\n"; ++} ++ ++# output in XML DocBook ++sub output_blockhead_xml(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ my $id = $args{'module'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ # print out each section ++ $lineprefix=" "; ++ foreach $section (@{$args{'sectionlist'}}) { ++ if (!$args{'content-only'}) { ++ print "\n $section\n"; ++ } ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ $output_preformatted = 1; ++ } else { ++ print "\n"; ++ } ++ output_highlight($args{'sections'}{$section}); ++ $output_preformatted = 0; ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ } else { ++ print ""; ++ } ++ if (!$args{'content-only'}) { ++ print "\n\n"; ++ } ++ } ++ ++ print "\n\n"; ++} ++ ++# output in XML DocBook ++sub output_function_gnome { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ my $id; ++ ++ $id = $args{'module'} . "-" . $args{'function'}; ++ $id =~ s/[^A-Za-z0-9]/-/g; ++ ++ print "\n"; ++ print " " . $args{'function'} . "\n"; ++ ++ print " \n"; ++ print " " . $args{'functiontype'} . " "; ++ print "" . $args{'function'} . " "; ++ print "\n"; ++ ++ $count = 0; ++ if ($#{$args{'parameterlist'}} >= 0) { ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print " $1 $parameter)\n"; ++ print " $2\n"; ++ } else { ++ print " " . $type; ++ print " $parameter\n"; ++ } ++ } ++ } else { ++ print " \n"; ++ } ++ print " \n"; ++ if ($#{$args{'parameterlist'}} >= 0) { ++ print " \n"; ++ print "\n"; ++ print "\n"; ++ print "\n"; ++ print "\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print " $parameter\n"; ++ print " \n"; ++ $lineprefix=" "; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ print " \n"; ++ } ++ print " \n"; ++ } else { ++ print " \n None\n \n"; ++ } ++ ++ # print out each section ++ $lineprefix=" "; ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "\n $section\n"; ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ $output_preformatted = 1; ++ } else { ++ } ++ print "\n"; ++ output_highlight($args{'sections'}{$section}); ++ $output_preformatted = 0; ++ print "\n"; ++ if ($section =~ m/EXAMPLE/i) { ++ print "\n"; ++ } else { ++ } ++ print " \n"; ++ } ++ ++ print "\n\n"; ++} ++ ++## ++# output function in man ++sub output_function_man(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; ++ ++ print ".SH NAME\n"; ++ print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; ++ ++ print ".SH SYNOPSIS\n"; ++ if ($args{'functiontype'} ne "") { ++ print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; ++ } else { ++ print ".B \"" . $args{'function'} . "\n"; ++ } ++ $count = 0; ++ my $parenth = "("; ++ my $post = ","; ++ foreach my $parameter (@{$args{'parameterlist'}}) { ++ if ($count == $#{$args{'parameterlist'}}) { ++ $post = ");"; ++ } ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n"; ++ } else { ++ $type =~ s/([^\*])$/$1 /; ++ print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n"; ++ } ++ $count++; ++ $parenth = ""; ++ } ++ ++ print ".SH ARGUMENTS\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print ".IP \"" . $parameter . "\" 12\n"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ } ++ foreach $section (@{$args{'sectionlist'}}) { ++ print ".SH \"", uc $section, "\"\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++## ++# output enum in man ++sub output_enum_man(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; ++ ++ print ".SH NAME\n"; ++ print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; ++ ++ print ".SH SYNOPSIS\n"; ++ print "enum " . $args{'enum'} . " {\n"; ++ $count = 0; ++ foreach my $parameter (@{$args{'parameterlist'}}) { ++ print ".br\n.BI \" $parameter\"\n"; ++ if ($count == $#{$args{'parameterlist'}}) { ++ print "\n};\n"; ++ last; ++ } ++ else { ++ print ", \n.br\n"; ++ } ++ $count++; ++ } ++ ++ print ".SH Constants\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print ".IP \"" . $parameter . "\" 12\n"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ } ++ foreach $section (@{$args{'sectionlist'}}) { ++ print ".SH \"$section\"\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++## ++# output struct in man ++sub output_struct_man(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ ++ print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; ++ ++ print ".SH NAME\n"; ++ print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; ++ ++ print ".SH SYNOPSIS\n"; ++ print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; ++ ++ foreach my $parameter (@{$args{'parameterlist'}}) { ++ if ($parameter =~ /^#/) { ++ print ".BI \"$parameter\"\n.br\n"; ++ next; ++ } ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print ".BI \" " . $1 . "\" " . $parameter . " \") (" . $2 . ")" . "\"\n;\n"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print ".BI \" " . $1 . "\ \" " . $parameter . $2 . " \"" . "\"\n;\n"; ++ } else { ++ $type =~ s/([^\*])$/$1 /; ++ print ".BI \" " . $type . "\" " . $parameter . " \"" . "\"\n;\n"; ++ } ++ print "\n.br\n"; ++ } ++ print "};\n.br\n"; ++ ++ print ".SH Members\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print ".IP \"" . $parameter . "\" 12\n"; ++ output_highlight($args{'parameterdescs'}{$parameter_name}); ++ } ++ foreach $section (@{$args{'sectionlist'}}) { ++ print ".SH \"$section\"\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++## ++# output typedef in man ++sub output_typedef_man(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ ++ print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; ++ ++ print ".SH NAME\n"; ++ print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print ".SH \"$section\"\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++sub output_blockhead_man(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $count; ++ ++ print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print ".SH \"$section\"\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++## ++# output in text ++sub output_function_text(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $start; ++ ++ print "Name:\n\n"; ++ print $args{'function'} . " - " . $args{'purpose'} . "\n"; ++ ++ print "\nSynopsis:\n\n"; ++ if ($args{'functiontype'} ne "") { ++ $start = $args{'functiontype'} . " " . $args{'function'} . " ("; ++ } else { ++ $start = $args{'function'} . " ("; ++ } ++ print $start; ++ ++ my $count = 0; ++ foreach my $parameter (@{$args{'parameterlist'}}) { ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print $1 . $parameter . ") (" . $2; ++ } else { ++ print $type . " " . $parameter; ++ } ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ",\n"; ++ print " " x length($start); ++ } else { ++ print ");\n\n"; ++ } ++ } ++ ++ print "Arguments:\n\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ print $parameter . "\n\t" . $args{'parameterdescs'}{$parameter_name} . "\n"; ++ } ++ output_section_text(@_); ++} ++ ++#output sections in text ++sub output_section_text(%) { ++ my %args = %{$_[0]}; ++ my $section; ++ ++ print "\n"; ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "$section:\n\n"; ++ output_highlight($args{'sections'}{$section}); ++ } ++ print "\n\n"; ++} ++ ++# output enum in text ++sub output_enum_text(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ print "Enum:\n\n"; ++ ++ print "enum " . $args{'enum'} . " - " . $args{'purpose'} . "\n\n"; ++ print "enum " . $args{'enum'} . " {\n"; ++ $count = 0; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "\t$parameter"; ++ if ($count != $#{$args{'parameterlist'}}) { ++ $count++; ++ print ","; ++ } ++ print "\n"; ++ } ++ print "};\n\n"; ++ ++ print "Constants:\n\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "$parameter\n\t"; ++ print $args{'parameterdescs'}{$parameter} . "\n"; ++ } ++ ++ output_section_text(@_); ++} ++ ++# output typedef in text ++sub output_typedef_text(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $count; ++ print "Typedef:\n\n"; ++ ++ print "typedef " . $args{'typedef'} . " - " . $args{'purpose'} . "\n"; ++ output_section_text(@_); ++} ++ ++# output struct as text ++sub output_struct_text(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ ++ print $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "\n\n"; ++ print $args{'type'} . " " . $args{'struct'} . " {\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ if ($parameter =~ /^#/) { ++ print "$parameter\n"; ++ next; ++ } ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print "\t$1 $parameter) ($2);\n"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print "\t$1 $parameter$2;\n"; ++ } else { ++ print "\t" . $type . " " . $parameter . ";\n"; ++ } ++ } ++ print "};\n\n"; ++ ++ print "Members:\n\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ print "$parameter\n\t"; ++ print $args{'parameterdescs'}{$parameter_name} . "\n"; ++ } ++ print "\n"; ++ output_section_text(@_); ++} ++ ++sub output_blockhead_text(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print " $section:\n"; ++ print " -> "; ++ output_highlight($args{'sections'}{$section}); ++ } ++} ++ ++## ++# output in restructured text ++# ++ ++# ++# This could use some work; it's used to output the DOC: sections, and ++# starts by putting out the name of the doc section itself, but that tends ++# to duplicate a header already in the template file. ++# ++sub output_blockhead_rst(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ if ($output_selection != OUTPUT_INCLUDE) { ++ print "**$section**\n\n"; ++ } ++ print_lineno($section_start_lines{$section}); ++ output_highlight_rst($args{'sections'}{$section}); ++ print "\n"; ++ } ++} ++ ++sub output_highlight_rst { ++ my $contents = join "\n",@_; ++ my $line; ++ ++ # undo the evil effects of xml_escape() earlier ++ $contents = xml_unescape($contents); ++ ++ eval $dohighlight; ++ die $@ if $@; ++ ++ foreach $line (split "\n", $contents) { ++ print $lineprefix . $line . "\n"; ++ } ++} ++ ++sub output_function_rst(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ my $oldprefix = $lineprefix; ++ my $start = ""; ++ ++ if ($args{'typedef'}) { ++ print ".. c:type:: ". $args{'function'} . "\n\n"; ++ print_lineno($declaration_start_line); ++ print " **Typedef**: "; ++ $lineprefix = ""; ++ output_highlight_rst($args{'purpose'}); ++ $start = "\n\n**Syntax**\n\n ``"; ++ } else { ++ print ".. c:function:: "; ++ } ++ if ($args{'functiontype'} ne "") { ++ $start .= $args{'functiontype'} . " " . $args{'function'} . " ("; ++ } else { ++ $start .= $args{'function'} . " ("; ++ } ++ print $start; ++ ++ my $count = 0; ++ foreach my $parameter (@{$args{'parameterlist'}}) { ++ if ($count ne 0) { ++ print ", "; ++ } ++ $count++; ++ $type = $args{'parametertypes'}{$parameter}; ++ ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print $1 . $parameter . ") (" . $2; ++ } else { ++ print $type . " " . $parameter; ++ } ++ } ++ if ($args{'typedef'}) { ++ print ");``\n\n"; ++ } else { ++ print ")\n\n"; ++ print_lineno($declaration_start_line); ++ $lineprefix = " "; ++ output_highlight_rst($args{'purpose'}); ++ print "\n"; ++ } ++ ++ print "**Parameters**\n\n"; ++ $lineprefix = " "; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ my $parameter_name = $parameter; ++ #$parameter_name =~ s/\[.*//; ++ $type = $args{'parametertypes'}{$parameter}; ++ ++ if ($type ne "") { ++ print "``$type $parameter``\n"; ++ } else { ++ print "``$parameter``\n"; ++ } ++ ++ print_lineno($parameterdesc_start_lines{$parameter_name}); ++ ++ if (defined($args{'parameterdescs'}{$parameter_name}) && ++ $args{'parameterdescs'}{$parameter_name} ne $undescribed) { ++ output_highlight_rst($args{'parameterdescs'}{$parameter_name}); ++ } else { ++ print " *undescribed*\n"; ++ } ++ print "\n"; ++ } ++ ++ $lineprefix = $oldprefix; ++ output_section_rst(@_); ++} ++ ++sub output_section_rst(%) { ++ my %args = %{$_[0]}; ++ my $section; ++ my $oldprefix = $lineprefix; ++ $lineprefix = ""; ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "**$section**\n\n"; ++ print_lineno($section_start_lines{$section}); ++ output_highlight_rst($args{'sections'}{$section}); ++ print "\n"; ++ } ++ print "\n"; ++ $lineprefix = $oldprefix; ++} ++ ++sub output_enum_rst(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $oldprefix = $lineprefix; ++ my $count; ++ my $name = "enum " . $args{'enum'}; ++ ++ print "\n\n.. c:type:: " . $name . "\n\n"; ++ print_lineno($declaration_start_line); ++ $lineprefix = " "; ++ output_highlight_rst($args{'purpose'}); ++ print "\n"; ++ ++ print "**Constants**\n\n"; ++ $lineprefix = " "; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ print "``$parameter``\n"; ++ if ($args{'parameterdescs'}{$parameter} ne $undescribed) { ++ output_highlight_rst($args{'parameterdescs'}{$parameter}); ++ } else { ++ print " *undescribed*\n"; ++ } ++ print "\n"; ++ } ++ ++ $lineprefix = $oldprefix; ++ output_section_rst(@_); ++} ++ ++sub output_typedef_rst(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $oldprefix = $lineprefix; ++ my $name = "typedef " . $args{'typedef'}; ++ ++ print "\n\n.. c:type:: " . $name . "\n\n"; ++ print_lineno($declaration_start_line); ++ $lineprefix = " "; ++ output_highlight_rst($args{'purpose'}); ++ print "\n"; ++ ++ $lineprefix = $oldprefix; ++ output_section_rst(@_); ++} ++ ++sub output_struct_rst(%) { ++ my %args = %{$_[0]}; ++ my ($parameter); ++ my $oldprefix = $lineprefix; ++ my $name = $args{'type'} . " " . $args{'struct'}; ++ ++ print "\n\n.. c:type:: " . $name . "\n\n"; ++ print_lineno($declaration_start_line); ++ $lineprefix = " "; ++ output_highlight_rst($args{'purpose'}); ++ print "\n"; ++ ++ print "**Definition**\n\n"; ++ print "::\n\n"; ++ print " " . $args{'type'} . " " . $args{'struct'} . " {\n"; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ if ($parameter =~ /^#/) { ++ print " " . "$parameter\n"; ++ next; ++ } ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { ++ # pointer-to-function ++ print " $1 $parameter) ($2);\n"; ++ } elsif ($type =~ m/^(.*?)\s*(:.*)/) { ++ # bitfield ++ print " $1 $parameter$2;\n"; ++ } else { ++ print " " . $type . " " . $parameter . ";\n"; ++ } ++ } ++ print " };\n\n"; ++ ++ print "**Members**\n\n"; ++ $lineprefix = " "; ++ foreach $parameter (@{$args{'parameterlist'}}) { ++ ($parameter =~ /^#/) && next; ++ ++ my $parameter_name = $parameter; ++ $parameter_name =~ s/\[.*//; ++ ++ ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; ++ $type = $args{'parametertypes'}{$parameter}; ++ print_lineno($parameterdesc_start_lines{$parameter_name}); ++ print "``" . $parameter . "``\n"; ++ output_highlight_rst($args{'parameterdescs'}{$parameter_name}); ++ print "\n"; ++ } ++ print "\n"; ++ ++ $lineprefix = $oldprefix; ++ output_section_rst(@_); ++} ++ ++ ++## list mode output functions ++ ++sub output_function_list(%) { ++ my %args = %{$_[0]}; ++ ++ print $args{'function'} . "\n"; ++} ++ ++# output enum in list ++sub output_enum_list(%) { ++ my %args = %{$_[0]}; ++ print $args{'enum'} . "\n"; ++} ++ ++# output typedef in list ++sub output_typedef_list(%) { ++ my %args = %{$_[0]}; ++ print $args{'typedef'} . "\n"; ++} ++ ++# output struct as list ++sub output_struct_list(%) { ++ my %args = %{$_[0]}; ++ ++ print $args{'struct'} . "\n"; ++} ++ ++sub output_blockhead_list(%) { ++ my %args = %{$_[0]}; ++ my ($parameter, $section); ++ ++ foreach $section (@{$args{'sectionlist'}}) { ++ print "DOC: $section\n"; ++ } ++} ++ ++## ++# generic output function for all types (function, struct/union, typedef, enum); ++# calls the generated, variable output_ function name based on ++# functype and output_mode ++sub output_declaration { ++ no strict 'refs'; ++ my $name = shift; ++ my $functype = shift; ++ my $func = "output_${functype}_$output_mode"; ++ if (($output_selection == OUTPUT_ALL) || ++ (($output_selection == OUTPUT_INCLUDE || ++ $output_selection == OUTPUT_EXPORTED) && ++ defined($function_table{$name})) || ++ (($output_selection == OUTPUT_EXCLUDE || ++ $output_selection == OUTPUT_INTERNAL) && ++ !($functype eq "function" && defined($function_table{$name})))) ++ { ++ &$func(@_); ++ $section_counter++; ++ } ++} ++ ++## ++# generic output function - calls the right one based on current output mode. ++sub output_blockhead { ++ no strict 'refs'; ++ my $func = "output_blockhead_" . $output_mode; ++ &$func(@_); ++ $section_counter++; ++} ++ ++## ++# takes a declaration (struct, union, enum, typedef) and ++# invokes the right handler. NOT called for functions. ++sub dump_declaration($$) { ++ no strict 'refs'; ++ my ($prototype, $file) = @_; ++ my $func = "dump_" . $decl_type; ++ &$func(@_); ++} ++ ++sub dump_union($$) { ++ dump_struct(@_); ++} ++ ++sub dump_struct($$) { ++ my $x = shift; ++ my $file = shift; ++ my $nested; ++ ++ if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) { ++ #my $decl_type = $1; ++ $declaration_name = $2; ++ my $members = $3; ++ ++ # ignore embedded structs or unions ++ $members =~ s/({.*})//g; ++ $nested = $1; ++ ++ # ignore members marked private: ++ $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; ++ $members =~ s/\/\*\s*private:.*//gosi; ++ # strip comments: ++ $members =~ s/\/\*.*?\*\///gos; ++ $nested =~ s/\/\*.*?\*\///gos; ++ # strip kmemcheck_bitfield_{begin,end}.*; ++ $members =~ s/kmemcheck_bitfield_.*?;//gos; ++ # strip attributes ++ $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; ++ $members =~ s/__aligned\s*\([^;]*\)//gos; ++ $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos; ++ # replace DECLARE_BITMAP ++ $members =~ s/DECLARE_BITMAP\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; ++ ++ create_parameterlist($members, ';', $file); ++ check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested); ++ ++ output_declaration($declaration_name, ++ 'struct', ++ {'struct' => $declaration_name, ++ 'module' => $modulename, ++ 'parameterlist' => \@parameterlist, ++ 'parameterdescs' => \%parameterdescs, ++ 'parametertypes' => \%parametertypes, ++ 'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'purpose' => $declaration_purpose, ++ 'type' => $decl_type ++ }); ++ } ++ else { ++ print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; ++ ++$errors; ++ } ++} ++ ++sub dump_enum($$) { ++ my $x = shift; ++ my $file = shift; ++ ++ $x =~ s@/\*.*?\*/@@gos; # strip comments. ++ # strip #define macros inside enums ++ $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos; ++ ++ if ($x =~ /enum\s+(\w+)\s*{(.*)}/) { ++ $declaration_name = $1; ++ my $members = $2; ++ ++ foreach my $arg (split ',', $members) { ++ $arg =~ s/^\s*(\w+).*/$1/; ++ push @parameterlist, $arg; ++ if (!$parameterdescs{$arg}) { ++ $parameterdescs{$arg} = $undescribed; ++ print STDERR "${file}:$.: warning: Enum value '$arg' ". ++ "not described in enum '$declaration_name'\n"; ++ } ++ ++ } ++ ++ output_declaration($declaration_name, ++ 'enum', ++ {'enum' => $declaration_name, ++ 'module' => $modulename, ++ 'parameterlist' => \@parameterlist, ++ 'parameterdescs' => \%parameterdescs, ++ 'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'purpose' => $declaration_purpose ++ }); ++ } ++ else { ++ print STDERR "${file}:$.: error: Cannot parse enum!\n"; ++ ++$errors; ++ } ++} ++ ++sub dump_typedef($$) { ++ my $x = shift; ++ my $file = shift; ++ ++ $x =~ s@/\*.*?\*/@@gos; # strip comments. ++ ++ # Parse function prototypes ++ if ($x =~ /typedef\s+(\w+)\s*\(\*\s*(\w\S+)\s*\)\s*\((.*)\);/ || ++ $x =~ /typedef\s+(\w+)\s*(\w\S+)\s*\s*\((.*)\);/) { ++ ++ # Function typedefs ++ $return_type = $1; ++ $declaration_name = $2; ++ my $args = $3; ++ ++ create_parameterlist($args, ',', $file); ++ ++ output_declaration($declaration_name, ++ 'function', ++ {'function' => $declaration_name, ++ 'typedef' => 1, ++ 'module' => $modulename, ++ 'functiontype' => $return_type, ++ 'parameterlist' => \@parameterlist, ++ 'parameterdescs' => \%parameterdescs, ++ 'parametertypes' => \%parametertypes, ++ 'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'purpose' => $declaration_purpose ++ }); ++ return; ++ } ++ ++ while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { ++ $x =~ s/\(*.\)\s*;$/;/; ++ $x =~ s/\[*.\]\s*;$/;/; ++ } ++ ++ if ($x =~ /typedef.*\s+(\w+)\s*;/) { ++ $declaration_name = $1; ++ ++ output_declaration($declaration_name, ++ 'typedef', ++ {'typedef' => $declaration_name, ++ 'module' => $modulename, ++ 'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'purpose' => $declaration_purpose ++ }); ++ } ++ else { ++ print STDERR "${file}:$.: error: Cannot parse typedef!\n"; ++ ++$errors; ++ } ++} ++ ++sub save_struct_actual($) { ++ my $actual = shift; ++ ++ # strip all spaces from the actual param so that it looks like one string item ++ $actual =~ s/\s*//g; ++ $struct_actual = $struct_actual . $actual . " "; ++} ++ ++sub create_parameterlist($$$) { ++ my $args = shift; ++ my $splitter = shift; ++ my $file = shift; ++ my $type; ++ my $param; ++ ++ # temporarily replace commas inside function pointer definition ++ while ($args =~ /(\([^\),]+),/) { ++ $args =~ s/(\([^\),]+),/$1#/g; ++ } ++ ++ foreach my $arg (split($splitter, $args)) { ++ # strip comments ++ $arg =~ s/\/\*.*\*\///; ++ # strip leading/trailing spaces ++ $arg =~ s/^\s*//; ++ $arg =~ s/\s*$//; ++ $arg =~ s/\s+/ /; ++ ++ if ($arg =~ /^#/) { ++ # Treat preprocessor directive as a typeless variable just to fill ++ # corresponding data structures "correctly". Catch it later in ++ # output_* subs. ++ push_parameter($arg, "", $file); ++ } elsif ($arg =~ m/\(.+\)\s*\(/) { ++ # pointer-to-function ++ $arg =~ tr/#/,/; ++ $arg =~ m/[^\(]+\(\*?\s*(\w*)\s*\)/; ++ $param = $1; ++ $type = $arg; ++ $type =~ s/([^\(]+\(\*?)\s*$param/$1/; ++ save_struct_actual($param); ++ push_parameter($param, $type, $file); ++ } elsif ($arg) { ++ $arg =~ s/\s*:\s*/:/g; ++ $arg =~ s/\s*\[/\[/g; ++ ++ my @args = split('\s*,\s*', $arg); ++ if ($args[0] =~ m/\*/) { ++ $args[0] =~ s/(\*+)\s*/ $1/; ++ } ++ ++ my @first_arg; ++ if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { ++ shift @args; ++ push(@first_arg, split('\s+', $1)); ++ push(@first_arg, $2); ++ } else { ++ @first_arg = split('\s+', shift @args); ++ } ++ ++ unshift(@args, pop @first_arg); ++ $type = join " ", @first_arg; ++ ++ foreach $param (@args) { ++ if ($param =~ m/^(\*+)\s*(.*)/) { ++ save_struct_actual($2); ++ push_parameter($2, "$type $1", $file); ++ } ++ elsif ($param =~ m/(.*?):(\d+)/) { ++ if ($type ne "") { # skip unnamed bit-fields ++ save_struct_actual($1); ++ push_parameter($1, "$type:$2", $file) ++ } ++ } ++ else { ++ save_struct_actual($param); ++ push_parameter($param, $type, $file); ++ } ++ } ++ } ++ } ++} ++ ++sub push_parameter($$$) { ++ my $param = shift; ++ my $type = shift; ++ my $file = shift; ++ ++ if (($anon_struct_union == 1) && ($type eq "") && ++ ($param eq "}")) { ++ return; # ignore the ending }; from anon. struct/union ++ } ++ ++ $anon_struct_union = 0; ++ my $param_name = $param; ++ $param_name =~ s/\[.*//; ++ ++ if ($type eq "" && $param =~ /\.\.\.$/) ++ { ++ if (!$param =~ /\w\.\.\.$/) { ++ # handles unnamed variable parameters ++ $param = "..."; ++ } ++ if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { ++ $parameterdescs{$param} = "variable arguments"; ++ } ++ } ++ elsif ($type eq "" && ($param eq "" or $param eq "void")) ++ { ++ $param="void"; ++ $parameterdescs{void} = "no arguments"; ++ } ++ elsif ($type eq "" && ($param eq "struct" or $param eq "union")) ++ # handle unnamed (anonymous) union or struct: ++ { ++ $type = $param; ++ $param = "{unnamed_" . $param . "}"; ++ $parameterdescs{$param} = "anonymous\n"; ++ $anon_struct_union = 1; ++ } ++ ++ # warn if parameter has no description ++ # (but ignore ones starting with # as these are not parameters ++ # but inline preprocessor statements); ++ # also ignore unnamed structs/unions; ++ if (!$anon_struct_union) { ++ if (!defined $parameterdescs{$param_name} && $param_name !~ /^#/) { ++ ++ $parameterdescs{$param_name} = $undescribed; ++ ++ if (($type eq 'function') || ($type eq 'enum')) { ++ print STDERR "${file}:$.: warning: Function parameter ". ++ "or member '$param' not " . ++ "described in '$declaration_name'\n"; ++ } ++ print STDERR "${file}:$.: warning:" . ++ " No description found for parameter '$param'\n"; ++ ++$warnings; ++ } ++ } ++ ++ $param = xml_escape($param); ++ ++ # strip spaces from $param so that it is one continuous string ++ # on @parameterlist; ++ # this fixes a problem where check_sections() cannot find ++ # a parameter like "addr[6 + 2]" because it actually appears ++ # as "addr[6", "+", "2]" on the parameter list; ++ # but it's better to maintain the param string unchanged for output, ++ # so just weaken the string compare in check_sections() to ignore ++ # "[blah" in a parameter string; ++ ###$param =~ s/\s*//g; ++ push @parameterlist, $param; ++ $parametertypes{$param} = $type; ++} ++ ++sub check_sections($$$$$$) { ++ my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_; ++ my @sects = split ' ', $sectcheck; ++ my @prms = split ' ', $prmscheck; ++ my $err; ++ my ($px, $sx); ++ my $prm_clean; # strip trailing "[array size]" and/or beginning "*" ++ ++ foreach $sx (0 .. $#sects) { ++ $err = 1; ++ foreach $px (0 .. $#prms) { ++ $prm_clean = $prms[$px]; ++ $prm_clean =~ s/\[.*\]//; ++ $prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; ++ # ignore array size in a parameter string; ++ # however, the original param string may contain ++ # spaces, e.g.: addr[6 + 2] ++ # and this appears in @prms as "addr[6" since the ++ # parameter list is split at spaces; ++ # hence just ignore "[..." for the sections check; ++ $prm_clean =~ s/\[.*//; ++ ++ ##$prm_clean =~ s/^\**//; ++ if ($prm_clean eq $sects[$sx]) { ++ $err = 0; ++ last; ++ } ++ } ++ if ($err) { ++ if ($decl_type eq "function") { ++ print STDERR "${file}:$.: warning: " . ++ "Excess function parameter " . ++ "'$sects[$sx]' " . ++ "description in '$decl_name'\n"; ++ ++$warnings; ++ } else { ++ if ($nested !~ m/\Q$sects[$sx]\E/) { ++ print STDERR "${file}:$.: warning: " . ++ "Excess struct/union/enum/typedef member " . ++ "'$sects[$sx]' " . ++ "description in '$decl_name'\n"; ++ ++$warnings; ++ } ++ } ++ } ++ } ++} ++ ++## ++# Checks the section describing the return value of a function. ++sub check_return_section { ++ my $file = shift; ++ my $declaration_name = shift; ++ my $return_type = shift; ++ ++ # Ignore an empty return type (It's a macro) ++ # Ignore functions with a "void" return type. (But don't ignore "void *") ++ if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { ++ return; ++ } ++ ++ if (!defined($sections{$section_return}) || ++ $sections{$section_return} eq "") { ++ print STDERR "${file}:$.: warning: " . ++ "No description found for return value of " . ++ "'$declaration_name'\n"; ++ ++$warnings; ++ } ++} ++ ++## ++# takes a function prototype and the name of the current file being ++# processed and spits out all the details stored in the global ++# arrays/hashes. ++sub dump_function($$) { ++ my $prototype = shift; ++ my $file = shift; ++ my $noret = 0; ++ ++ $prototype =~ s/^static +//; ++ $prototype =~ s/^extern +//; ++ $prototype =~ s/^asmlinkage +//; ++ $prototype =~ s/^inline +//; ++ $prototype =~ s/^__inline__ +//; ++ $prototype =~ s/^__inline +//; ++ $prototype =~ s/^__always_inline +//; ++ $prototype =~ s/^noinline +//; ++ $prototype =~ s/__init +//; ++ $prototype =~ s/__init_or_module +//; ++ $prototype =~ s/__meminit +//; ++ $prototype =~ s/__must_check +//; ++ $prototype =~ s/__weak +//; ++ my $define = $prototype =~ s/^#\s*define\s+//; #ak added ++ $prototype =~ s/__attribute__\s*\(\([a-z,]*\)\)//; ++ ++ # Yes, this truly is vile. We are looking for: ++ # 1. Return type (may be nothing if we're looking at a macro) ++ # 2. Function name ++ # 3. Function parameters. ++ # ++ # All the while we have to watch out for function pointer parameters ++ # (which IIRC is what the two sections are for), C types (these ++ # regexps don't even start to express all the possibilities), and ++ # so on. ++ # ++ # If you mess with these regexps, it's a good idea to check that ++ # the following functions' documentation still comes out right: ++ # - parport_register_device (function pointer parameters) ++ # - atomic_set (macro) ++ # - pci_match_device, __copy_to_user (long return type) ++ ++ if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) { ++ # This is an object-like macro, it has no return type and no parameter ++ # list. ++ # Function-like macros are not allowed to have spaces between ++ # declaration_name and opening parenthesis (notice the \s+). ++ $return_type = $1; ++ $declaration_name = $2; ++ $noret = 1; ++ } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || ++ $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || ++ $prototype =~ m/^(\w+\s+\w+\s*\*\s*\w+\s*\*\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/) { ++ $return_type = $1; ++ $declaration_name = $2; ++ my $args = $3; ++ ++ create_parameterlist($args, ',', $file); ++ } else { ++ print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n"; ++ return; ++ } ++ ++ my $prms = join " ", @parameterlist; ++ check_sections($file, $declaration_name, "function", $sectcheck, $prms, ""); ++ ++ # This check emits a lot of warnings at the moment, because many ++ # functions don't have a 'Return' doc section. So until the number ++ # of warnings goes sufficiently down, the check is only performed in ++ # verbose mode. ++ # TODO: always perform the check. ++ if ($verbose && !$noret) { ++ check_return_section($file, $declaration_name, $return_type); ++ } ++ ++ output_declaration($declaration_name, ++ 'function', ++ {'function' => $declaration_name, ++ 'module' => $modulename, ++ 'functiontype' => $return_type, ++ 'parameterlist' => \@parameterlist, ++ 'parameterdescs' => \%parameterdescs, ++ 'parametertypes' => \%parametertypes, ++ 'sectionlist' => \@sectionlist, ++ 'sections' => \%sections, ++ 'purpose' => $declaration_purpose ++ }); ++} ++ ++sub reset_state { ++ $function = ""; ++ %parameterdescs = (); ++ %parametertypes = (); ++ @parameterlist = (); ++ %sections = (); ++ @sectionlist = (); ++ $sectcheck = ""; ++ $struct_actual = ""; ++ $prototype = ""; ++ ++ $state = STATE_NORMAL; ++ $inline_doc_state = STATE_INLINE_NA; ++} ++ ++sub tracepoint_munge($) { ++ my $file = shift; ++ my $tracepointname = 0; ++ my $tracepointargs = 0; ++ ++ if ($prototype =~ m/TRACE_EVENT\((.*?),/) { ++ $tracepointname = $1; ++ } ++ if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { ++ $tracepointname = $1; ++ } ++ if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { ++ $tracepointname = $2; ++ } ++ $tracepointname =~ s/^\s+//; #strip leading whitespace ++ if ($prototype =~ m/TP_PROTO\((.*?)\)/) { ++ $tracepointargs = $1; ++ } ++ if (($tracepointname eq 0) || ($tracepointargs eq 0)) { ++ print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n". ++ "$prototype\n"; ++ } else { ++ $prototype = "static inline void trace_$tracepointname($tracepointargs)"; ++ } ++} ++ ++sub syscall_munge() { ++ my $void = 0; ++ ++ $prototype =~ s@[\r\n\t]+@ @gos; # strip newlines/CR's/tabs ++## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { ++ if ($prototype =~ m/SYSCALL_DEFINE0/) { ++ $void = 1; ++## $prototype = "long sys_$1(void)"; ++ } ++ ++ $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name ++ if ($prototype =~ m/long (sys_.*?),/) { ++ $prototype =~ s/,/\(/; ++ } elsif ($void) { ++ $prototype =~ s/\)/\(void\)/; ++ } ++ ++ # now delete all of the odd-number commas in $prototype ++ # so that arg types & arg names don't have a comma between them ++ my $count = 0; ++ my $len = length($prototype); ++ if ($void) { ++ $len = 0; # skip the for-loop ++ } ++ for (my $ix = 0; $ix < $len; $ix++) { ++ if (substr($prototype, $ix, 1) eq ',') { ++ $count++; ++ if ($count % 2 == 1) { ++ substr($prototype, $ix, 1) = ' '; ++ } ++ } ++ } ++} ++ ++sub process_proto_function($$) { ++ my $x = shift; ++ my $file = shift; ++ ++ $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line ++ ++ if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) { ++ # do nothing ++ } ++ elsif ($x =~ /([^\{]*)/) { ++ $prototype .= $1; ++ } ++ ++ if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { ++ $prototype =~ s@/\*.*?\*/@@gos; # strip comments. ++ $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. ++ $prototype =~ s@^\s+@@gos; # strip leading spaces ++ if ($prototype =~ /SYSCALL_DEFINE/) { ++ syscall_munge(); ++ } ++ if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || ++ $prototype =~ /DEFINE_SINGLE_EVENT/) ++ { ++ tracepoint_munge($file); ++ } ++ dump_function($prototype, $file); ++ reset_state(); ++ } ++} ++ ++sub process_proto_type($$) { ++ my $x = shift; ++ my $file = shift; ++ ++ $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. ++ $x =~ s@^\s+@@gos; # strip leading spaces ++ $x =~ s@\s+$@@gos; # strip trailing spaces ++ $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line ++ ++ if ($x =~ /^#/) { ++ # To distinguish preprocessor directive from regular declaration later. ++ $x .= ";"; ++ } ++ ++ while (1) { ++ if ( $x =~ /([^{};]*)([{};])(.*)/ ) { ++ $prototype .= $1 . $2; ++ ($2 eq '{') && $brcount++; ++ ($2 eq '}') && $brcount--; ++ if (($2 eq ';') && ($brcount == 0)) { ++ dump_declaration($prototype, $file); ++ reset_state(); ++ last; ++ } ++ $x = $3; ++ } else { ++ $prototype .= $x; ++ last; ++ } ++ } ++} ++ ++# xml_escape: replace <, >, and & in the text stream; ++# ++# however, formatting controls that are generated internally/locally in the ++# kernel-doc script are not escaped here; instead, they begin life like ++# $blankline_html (4 of '\' followed by a mnemonic + ':'), then these strings ++# are converted to their mnemonic-expected output, without the 4 * '\' & ':', ++# just before actual output; (this is done by local_unescape()) ++sub xml_escape($) { ++ my $text = shift; ++ if (($output_mode eq "text") || ($output_mode eq "man")) { ++ return $text; ++ } ++ $text =~ s/\&/\\\\\\amp;/g; ++ $text =~ s/\/\\\\\\gt;/g; ++ return $text; ++} ++ ++# xml_unescape: reverse the effects of xml_escape ++sub xml_unescape($) { ++ my $text = shift; ++ if (($output_mode eq "text") || ($output_mode eq "man")) { ++ return $text; ++ } ++ $text =~ s/\\\\\\amp;/\&/g; ++ $text =~ s/\\\\\\lt;//g; ++ return $text; ++} ++ ++# convert local escape strings to html ++# local escape strings look like: '\\\\menmonic:' (that's 4 backslashes) ++sub local_unescape($) { ++ my $text = shift; ++ if (($output_mode eq "text") || ($output_mode eq "man")) { ++ return $text; ++ } ++ $text =~ s/\\\\\\\\lt://g; ++ return $text; ++} ++ ++sub map_filename($) { ++ my $file; ++ my ($orig_file) = @_; ++ ++ if (defined($ENV{'SRCTREE'})) { ++ $file = "$ENV{'SRCTREE'}" . "/" . $orig_file; ++ } else { ++ $file = $orig_file; ++ } ++ ++ if (defined($source_map{$file})) { ++ $file = $source_map{$file}; ++ } ++ ++ return $file; ++} ++ ++sub process_export_file($) { ++ my ($orig_file) = @_; ++ my $file = map_filename($orig_file); ++ ++ if (!open(IN,"<$file")) { ++ print STDERR "Error: Cannot open file $file\n"; ++ ++$errors; ++ return; ++ } ++ ++ while () { ++ if (/$export_symbol/) { ++ $function_table{$2} = 1; ++ } ++ } ++ ++ close(IN); ++} ++ ++sub process_file($) { ++ my $file; ++ my $identifier; ++ my $func; ++ my $descr; ++ my $in_purpose = 0; ++ my $initial_section_counter = $section_counter; ++ my ($orig_file) = @_; ++ my $leading_space; ++ ++ $file = map_filename($orig_file); ++ ++ if (!open(IN,"<$file")) { ++ print STDERR "Error: Cannot open file $file\n"; ++ ++$errors; ++ return; ++ } ++ ++ $. = 1; ++ ++ $section_counter = 0; ++ while () { ++ while (s/\\\s*$//) { ++ $_ .= ; ++ } ++ if ($state == STATE_NORMAL) { ++ if (/$doc_start/o) { ++ $state = STATE_NAME; # next line is always the function name ++ $in_doc_sect = 0; ++ $declaration_start_line = $. + 1; ++ } ++ } elsif ($state == STATE_NAME) {# this line is the function name (always) ++ if (/$doc_block/o) { ++ $state = STATE_DOCBLOCK; ++ $contents = ""; ++ $new_start_line = $. + 1; ++ ++ if ( $1 eq "" ) { ++ $section = $section_intro; ++ } else { ++ $section = $1; ++ } ++ } ++ elsif (/$doc_decl/o) { ++ $identifier = $1; ++ if (/\s*([\w\s]+?)\s*-/) { ++ $identifier = $1; ++ } ++ ++ $state = STATE_FIELD; ++ # if there's no @param blocks need to set up default section ++ # here ++ $contents = ""; ++ $section = $section_default; ++ $new_start_line = $. + 1; ++ if (/-(.*)/) { ++ # strip leading/trailing/multiple spaces ++ $descr= $1; ++ $descr =~ s/^\s*//; ++ $descr =~ s/\s*$//; ++ $descr =~ s/\s+/ /g; ++ $declaration_purpose = xml_escape($descr); ++ $in_purpose = 1; ++ } else { ++ $declaration_purpose = ""; ++ } ++ ++ if (($declaration_purpose eq "") && $verbose) { ++ print STDERR "${file}:$.: warning: missing initial short description on line:\n"; ++ print STDERR $_; ++ ++$warnings; ++ } ++ ++ if ($identifier =~ m/^struct/) { ++ $decl_type = 'struct'; ++ } elsif ($identifier =~ m/^union/) { ++ $decl_type = 'union'; ++ } elsif ($identifier =~ m/^enum/) { ++ $decl_type = 'enum'; ++ } elsif ($identifier =~ m/^typedef/) { ++ $decl_type = 'typedef'; ++ } else { ++ $decl_type = 'function'; ++ } ++ ++ if ($verbose) { ++ print STDERR "${file}:$.: info: Scanning doc for $identifier\n"; ++ } ++ } else { ++ print STDERR "${file}:$.: warning: Cannot understand $_ on line $.", ++ " - I thought it was a doc line\n"; ++ ++$warnings; ++ $state = STATE_NORMAL; ++ } ++ } elsif ($state == STATE_FIELD) { # look for head: lines, and include content ++ if (/$doc_sect/i) { # case insensitive for supported section names ++ $newsection = $1; ++ $newcontents = $2; ++ ++ # map the supported section names to the canonical names ++ if ($newsection =~ m/^description$/i) { ++ $newsection = $section_default; ++ } elsif ($newsection =~ m/^context$/i) { ++ $newsection = $section_context; ++ } elsif ($newsection =~ m/^returns?$/i) { ++ $newsection = $section_return; ++ } elsif ($newsection =~ m/^\@return$/) { ++ # special: @return is a section, not a param description ++ $newsection = $section_return; ++ } ++ ++ if (($contents ne "") && ($contents ne "\n")) { ++ if (!$in_doc_sect && $verbose) { ++ print STDERR "${file}:$.: warning: contents before sections\n"; ++ ++$warnings; ++ } ++ dump_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ } ++ ++ $in_doc_sect = 1; ++ $in_purpose = 0; ++ $contents = $newcontents; ++ $new_start_line = $.; ++ while ((substr($contents, 0, 1) eq " ") || ++ substr($contents, 0, 1) eq "\t") { ++ $contents = substr($contents, 1); ++ } ++ if ($contents ne "") { ++ $contents .= "\n"; ++ } ++ $section = $newsection; ++ $leading_space = undef; ++ } elsif (/$doc_end/) { ++ if (($contents ne "") && ($contents ne "\n")) { ++ dump_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ $contents = ""; ++ } ++ # look for doc_com + + doc_end: ++ if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { ++ print STDERR "${file}:$.: warning: suspicious ending line: $_"; ++ ++$warnings; ++ } ++ ++ $prototype = ""; ++ $state = STATE_PROTO; ++ $brcount = 0; ++# print STDERR "end of doc comment, looking for prototype\n"; ++ } elsif (/$doc_content/) { ++ # miguel-style comment kludge, look for blank lines after ++ # @parameter line to signify start of description ++ if ($1 eq "") { ++ if ($section =~ m/^@/ || $section eq $section_context) { ++ dump_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ $contents = ""; ++ $new_start_line = $.; ++ } else { ++ $contents .= "\n"; ++ } ++ $in_purpose = 0; ++ } elsif ($in_purpose == 1) { ++ # Continued declaration purpose ++ chomp($declaration_purpose); ++ $declaration_purpose .= " " . xml_escape($1); ++ $declaration_purpose =~ s/\s+/ /g; ++ } else { ++ my $cont = $1; ++ if ($section =~ m/^@/ || $section eq $section_context) { ++ if (!defined $leading_space) { ++ if ($cont =~ m/^(\s+)/) { ++ $leading_space = $1; ++ } else { ++ $leading_space = ""; ++ } ++ } ++ ++ $cont =~ s/^$leading_space//; ++ } ++ $contents .= $cont . "\n"; ++ } ++ } else { ++ # i dont know - bad line? ignore. ++ print STDERR "${file}:$.: warning: bad line: $_"; ++ ++$warnings; ++ } ++ } elsif ($state == STATE_INLINE) { # scanning for inline parameters ++ # First line (state 1) needs to be a @parameter ++ if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) { ++ $section = $1; ++ $contents = $2; ++ $new_start_line = $.; ++ if ($contents ne "") { ++ while ((substr($contents, 0, 1) eq " ") || ++ substr($contents, 0, 1) eq "\t") { ++ $contents = substr($contents, 1); ++ } ++ $contents .= "\n"; ++ } ++ $inline_doc_state = STATE_INLINE_TEXT; ++ # Documentation block end */ ++ } elsif (/$doc_inline_end/) { ++ if (($contents ne "") && ($contents ne "\n")) { ++ dump_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ $contents = ""; ++ } ++ $state = STATE_PROTO; ++ $inline_doc_state = STATE_INLINE_NA; ++ # Regular text ++ } elsif (/$doc_content/) { ++ if ($inline_doc_state == STATE_INLINE_TEXT) { ++ $contents .= $1 . "\n"; ++ # nuke leading blank lines ++ if ($contents =~ /^\s*$/) { ++ $contents = ""; ++ } ++ } elsif ($inline_doc_state == STATE_INLINE_NAME) { ++ $inline_doc_state = STATE_INLINE_ERROR; ++ print STDERR "${file}:$.: warning: "; ++ print STDERR "Incorrect use of kernel-doc format: $_"; ++ ++$warnings; ++ } ++ } ++ } elsif ($state == STATE_PROTO) { # scanning for function '{' (end of prototype) ++ if (/$doc_inline_oneline/) { ++ $section = $1; ++ $contents = $2; ++ if ($contents ne "") { ++ $contents .= "\n"; ++ dump_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ $contents = ""; ++ } ++ } elsif (/$doc_inline_start/) { ++ $state = STATE_INLINE; ++ $inline_doc_state = STATE_INLINE_NAME; ++ } elsif ($decl_type eq 'function') { ++ process_proto_function($_, $file); ++ } else { ++ process_proto_type($_, $file); ++ } ++ } elsif ($state == STATE_DOCBLOCK) { ++ if (/$doc_end/) ++ { ++ dump_doc_section($file, $section, xml_escape($contents)); ++ $section = $section_default; ++ $contents = ""; ++ $function = ""; ++ %parameterdescs = (); ++ %parametertypes = (); ++ @parameterlist = (); ++ %sections = (); ++ @sectionlist = (); ++ $prototype = ""; ++ $state = STATE_NORMAL; ++ } ++ elsif (/$doc_content/) ++ { ++ if ( $1 eq "" ) ++ { ++ $contents .= $blankline; ++ } ++ else ++ { ++ $contents .= $1 . "\n"; ++ } ++ } ++ } ++ } ++ if ($initial_section_counter == $section_counter) { ++ print STDERR "${file}:1: warning: no structured comments found\n"; ++ if (($output_selection == OUTPUT_INCLUDE) && ($show_not_found == 1)) { ++ print STDERR " Was looking for '$_'.\n" for keys %function_table; ++ } ++ if ($output_mode eq "xml") { ++ # The template wants at least one RefEntry here; make one. ++ print "\n"; ++ print " \n"; ++ print " \n"; ++ print " ${orig_file}\n"; ++ print " \n"; ++ print " \n"; ++ print " Document generation inconsistency\n"; ++ print " \n"; ++ print " \n"; ++ print " \n"; ++ print " \n"; ++ print " Oops\n"; ++ print " \n"; ++ print " \n"; ++ print " \n"; ++ print " The template for this document tried to insert\n"; ++ print " the structured comment from the file\n"; ++ print " ${orig_file} at this point,\n"; ++ print " but none was found.\n"; ++ print " This dummy section is inserted to allow\n"; ++ print " generation to continue.\n"; ++ print " \n"; ++ print " \n"; ++ print " \n"; ++ print "\n"; ++ } ++ } ++} ++ ++ ++$kernelversion = get_kernel_version(); ++ ++# generate a sequence of code that will splice in highlighting information ++# using the s// operator. ++for (my $k = 0; $k < @highlights; $k++) { ++ my $pattern = $highlights[$k][0]; ++ my $result = $highlights[$k][1]; ++# print STDERR "scanning pattern:$pattern, highlight:($result)\n"; ++ $dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n"; ++} ++ ++# Read the file that maps relative names to absolute names for ++# separate source and object directories and for shadow trees. ++if (open(SOURCE_MAP, "<.tmp_filelist.txt")) { ++ my ($relname, $absname); ++ while() { ++ chop(); ++ ($relname, $absname) = (split())[0..1]; ++ $relname =~ s:^/+::; ++ $source_map{$relname} = $absname; ++ } ++ close(SOURCE_MAP); ++} ++ ++if ($output_selection == OUTPUT_EXPORTED || ++ $output_selection == OUTPUT_INTERNAL) { ++ ++ push(@export_file_list, @ARGV); ++ ++ foreach (@export_file_list) { ++ chomp; ++ process_export_file($_); ++ } ++} ++ ++foreach (@ARGV) { ++ chomp; ++ process_file($_); ++} ++if ($verbose && $errors) { ++ print STDERR "$errors errors\n"; ++} ++if ($verbose && $warnings) { ++ print STDERR "$warnings warnings\n"; ++} ++ ++exit($errors); +Index: multipath-tools-130222/libdmmp/docs/libdmmp.h.3 +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/docs/libdmmp.h.3 +@@ -0,0 +1,113 @@ ++.TH "libdmmp.h" 3 "January 2016" "Device Mapper Multipath API - libdmmp Manual" ++ ++.SH NAME ++libdmmp.h \- Device Mapper Multipath API. ++ ++.SH SYNOPSIS ++#include ++ ++.SH "DESCRIPTION" ++ ++All the libdmmp public functions ships its own man pages. ++Use 'man 3 ' to check the detail usage. ++ ++.SH "USAGE" ++ ++To use libdmmp in your project, we suggest to use the 'pkg-config' way: ++ ++ * Add this line into your configure.ac: ++ ++ PKG_CHECK_MODULES([LIBDMMP], [libdmmp]) ++ ++ * Add these lines into your Makefile.am: ++ ++ foo_LDFLAGS += $(LIBDMMP_LIBS) ++ foo_CFLAGS += $(LIBDMMP_CFLAGS) ++ ++.SH LOG HANDLING ++ ++The log handler function could be set via 'dmmp_context_log_func_set()'. ++The log priority could be set via 'dmmp_context_log_priority_set()'. ++ ++By default, the log priorities is 'DMMP_LOG_PRIORITY_WARNING'. ++By default, the log handler is print log to STDERR, and its code is listed ++below in case you want to create your own log handler. ++ ++ static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80; ++ ++ static void _log_stderr(struct dmmp_context *ctx, ++ enum dmmp_log_priority priority, ++ const char *file, int line, ++ const char *func_name, ++ const char *format, va_list args) ++ { ++ int printed_bytes = 0; ++ ++ printed_bytes += fprintf(stderr, "libdmmp %s: ", ++ dmmp_log_priority_str(priority)); ++ printed_bytes += vfprintf(stderr, format, args); ++ userdata = dmmp_context_userdata_get(ctx); ++ if (userdata != NULL) ++ fprintf(stderr, "(with user data at memory address %p)", ++ userdata); ++ ++ if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) { ++ fprintf(stderr, "%*s # %s:%s():%d\n", ++ _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, ++ func_name, line); ++ } else { ++ fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); ++ } ++ } ++ ++ ++.SH "SAMPLE CODE" ++ ++ #include ++ ++ int main(int argc, char *argv[]) { ++ struct dmmp_context *ctx = NULL; ++ struct dmmp_mpath **dmmp_mps = NULL; ++ struct dmmp_path_group **dmmp_pgs = NULL; ++ struct dmmp_path **dmmp_ps = NULL; ++ uint32_t dmmp_mp_count = 0; ++ uint32_t dmmp_pg_count = 0; ++ uint32_t dmmp_p_count = 0; ++ const char *name = NULL; ++ const char *wwid = NULL; ++ uint32_t i = 0; ++ int rc = DMMP_OK; ++ ++ ctx = dmmp_context_new(); ++ dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); ++ // By default, log will be printed to STDERR, you could ++ // change that via dmmp_context_log_func_set() ++ rc = dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count); ++ if (rc != DMMP_OK) { ++ printf("dmmp_mpath_array_get() failed with %d: %s", rc, ++ dmmp_strerror(rc)); ++ goto out; ++ } ++ for (i = 0; i < dmmp_mp_count; ++i) { ++ name = dmmp_mpath_name_get(dmmp_mps[i]); ++ wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); ++ printf("dmmp_mpath_array_get(): Got mpath: %s %s\n", name, ++ wwid); ++ // You could use dmmp_path_group_array_get() to retrieve ++ // path group information and then invoke dmmp_path_array_get() ++ // for path information. ++ } ++ ++ out: ++ dmmp_context_free(ctx); ++ dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); ++ if (rc != DMMP_OK) ++ exit(1); ++ exit(0); ++ } ++ ++.SH "LICENSE" ++GPLv2+ ++ ++.SH "BUG" ++Please report bug to +Index: multipath-tools-130222/libdmmp/docs/split-man.pl +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/docs/split-man.pl +@@ -0,0 +1,40 @@ ++#!/usr/bin/perl ++# Originally From: ++# https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt ++# ++# Changes: ++# * Create manpage section 3 instead of 9. ++# * Replace 'Kernel Hackers Manual' to ++# 'Device Mapper Multipath API - libdmmp Manual' ++# * Remove LINUX from header. ++# * Remove DMMP_DLL_EXPORT. ++$man_sec_num = 3; ++$title = 'Device Mapper Multipath API - libdmmp Manual'; ++ ++if ( $#ARGV < 0 ) { ++ die "where do I put the results?\n"; ++} ++ ++mkdir $ARGV[0], 0777; ++$state = 0; ++while () { ++ if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) { ++ if ( $state == 1 ) { close OUT } ++ $state = 1; ++ $fn = "$ARGV[0]/$1.$man_sec_num"; ++ print STDERR "Creating $fn\n"; ++ open OUT, ">$fn" or die "can't open $fn: $!\n"; ++ ++ # Change man page code from 9 to $man_sec_num; ++ s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/; ++ s/Kernel Hacker's Manual/$title/g; ++ s/LINUX//g; ++ ++ print OUT $_; ++ } ++ elsif ( $state != 0 ) { ++ print OUT $_; ++ } ++} ++ ++close OUT; +Index: multipath-tools-130222/libdmmp/libdmmp.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp.c +@@ -0,0 +1,285 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++#include "libdmmp_private.h" ++ ++#define _DEFAULT_UXSOCK_TIMEOUT 60000 ++/* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get() ++ * only take 3.5 seconds, so this default value should be OK for most users. ++ */ ++ ++#define _DMMP_IPC_SHOW_JSON_CMD "show maps json" ++#define _DMMP_JSON_MAJOR_KEY "major_version" ++#define _DMMP_JSON_MAJOR_VERSION 0 ++#define _DMMP_JSON_MAPS_KEY "maps" ++#define _ERRNO_STR_BUFF_SIZE 256 ++ ++struct dmmp_context { ++ void (*log_func)(struct dmmp_context *ctx, int priority, ++ const char *file, int line, const char *func_name, ++ const char *format, va_list args); ++ int log_priority; ++ void *userdata; ++ unsigned int tmo; ++}; ++ ++_dmmp_getter_func_gen(dmmp_context_log_priority_get, ++ struct dmmp_context, ctx, log_priority, ++ int); ++ ++_dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx, ++ userdata, void *); ++ ++_dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo, ++ unsigned int); ++ ++_dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath, ++ _dmmp_mpath_free); ++ ++void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file, ++ int line, const char *func_name, const char *format, ...) ++{ ++ va_list args; ++ ++ va_start(args, format); ++ ctx->log_func(ctx, priority, file, line, func_name, format, args); ++ va_end(args); ++} ++ ++struct dmmp_context *dmmp_context_new(void) ++{ ++ struct dmmp_context *ctx = NULL; ++ ++ ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context)); ++ ++ if (ctx == NULL) ++ return NULL; ++ ++ ctx->log_func = _dmmp_log_stderr; ++ ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT; ++ ctx->userdata = NULL; ++ ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT; ++ ++ return ctx; ++} ++ ++void dmmp_context_free(struct dmmp_context *ctx) ++{ ++ free(ctx); ++} ++ ++void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority) ++{ ++ assert(ctx != NULL); ++ ctx->log_priority = priority; ++} ++ ++void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo) ++{ ++ assert(ctx != NULL); ++ ctx->tmo = tmo; ++} ++ ++void dmmp_context_log_func_set ++ (struct dmmp_context *ctx, ++ void (*log_func)(struct dmmp_context *ctx, int priority, ++ const char *file, int line, const char *func_name, ++ const char *format, va_list args)) ++{ ++ assert(ctx != NULL); ++ ctx->log_func = log_func; ++} ++ ++void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata) ++{ ++ assert(ctx != NULL); ++ ctx->userdata = userdata; ++} ++ ++int dmmp_mpath_array_get(struct dmmp_context *ctx, ++ struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count) ++{ ++ struct dmmp_mpath *dmmp_mp = NULL; ++ int rc = DMMP_OK; ++ char *j_str = NULL; ++ json_object *j_obj = NULL; ++ json_object *j_obj_map = NULL; ++ enum json_tokener_error j_err = json_tokener_success; ++ json_tokener *j_token = NULL; ++ struct array_list *ar_maps = NULL; ++ uint32_t i = 0; ++ int cur_json_major_version = -1; ++ int ar_maps_len = -1; ++ int socket_fd = -1; ++ int errno_save = 0; ++ char errno_str_buff[_ERRNO_STR_BUFF_SIZE]; ++ ++ assert(ctx != NULL); ++ assert(dmmp_mps != NULL); ++ assert(dmmp_mp_count != NULL); ++ ++ *dmmp_mps = NULL; ++ *dmmp_mp_count = 0; ++ ++ socket_fd = mpath_connect(); ++ if (socket_fd == -1) { ++ errno_save = errno; ++ memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE); ++ strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE); ++ if (errno_save == ECONNREFUSED) { ++ rc = DMMP_ERR_NO_DAEMON; ++ _error(ctx, "Socket connection refuse. " ++ "Maybe multipathd daemon is not running"); ++ } else { ++ _error(ctx, "IPC failed with error %d(%s)", errno_save, ++ errno_str_buff); ++ rc = DMMP_ERR_IPC_ERROR; ++ } ++ goto out; ++ } ++ ++ if (mpath_process_cmd(socket_fd, _DMMP_IPC_SHOW_JSON_CMD, ++ &j_str, ctx->tmo) != 0) { ++ errno_save = errno; ++ memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE); ++ strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE); ++ if (errno_save == ETIMEDOUT) { ++ rc = DMMP_ERR_IPC_TIMEOUT; ++ _error(ctx, "IPC communication timeout, try to " ++ "increase it via dmmp_context_timeout_set()"); ++ goto out; ++ } ++ _error(ctx, "IPC failed when process command '%s' with " ++ "error %d(%s)", _DMMP_IPC_SHOW_JSON_CMD, errno_save, ++ errno_str_buff); ++ rc = DMMP_ERR_IPC_ERROR; ++ goto out; ++ } ++ ++ if ((j_str == NULL) || (strlen(j_str) == 0)) { ++ _error(ctx, "IPC return empty reply for command %s", ++ _DMMP_IPC_SHOW_JSON_CMD); ++ rc = DMMP_ERR_IPC_ERROR; ++ goto out; ++ } ++ ++ _debug(ctx, "Got json output from multipathd: '%s'", j_str); ++ j_token = json_tokener_new(); ++ if (j_token == NULL) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: json_tokener_new() retuned NULL"); ++ goto out; ++ } ++ j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1); ++ ++ if (j_obj == NULL) { ++ rc = DMMP_ERR_IPC_ERROR; ++ j_err = json_tokener_get_error(j_token); ++ _error(ctx, "Failed to parse JSON output from multipathd IPC: " ++ "%s", json_tokener_error_desc(j_err)); ++ goto out; ++ } ++ ++ _json_obj_get_value(ctx, j_obj, cur_json_major_version, ++ _DMMP_JSON_MAJOR_KEY, json_type_int, ++ json_object_get_int, rc, out); ++ ++ if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) { ++ rc = DMMP_ERR_INCOMPATIBLE; ++ _error(ctx, "Incompatible multipathd JSON major version %d, " ++ "should be %d", cur_json_major_version, ++ _DMMP_JSON_MAJOR_VERSION); ++ goto out; ++ } ++ _debug(ctx, "multipathd JSON major version(%d) check pass", ++ _DMMP_JSON_MAJOR_VERSION); ++ ++ _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY, ++ json_type_array, json_object_get_array, rc, out); ++ ++ if (ar_maps == NULL) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: Got NULL map array from " ++ "_json_obj_get_value()"); ++ goto out; ++ } ++ ++ ar_maps_len = array_list_length(ar_maps); ++ if (ar_maps_len < 0) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: Got negative length for ar_maps"); ++ goto out; ++ } ++ else if (ar_maps_len == 0) ++ goto out; ++ else ++ *dmmp_mp_count = ar_maps_len & UINT32_MAX; ++ ++ *dmmp_mps = (struct dmmp_mpath **) ++ malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count)); ++ _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out); ++ for (; i < *dmmp_mp_count; ++i) ++ (*dmmp_mps)[i] = NULL; ++ ++ for (i = 0; i < *dmmp_mp_count; ++i) { ++ j_obj_map = array_list_get_idx(ar_maps, i); ++ if (j_obj_map == NULL) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: array_list_get_idx() return NULL"); ++ goto out; ++ } ++ ++ dmmp_mp = _dmmp_mpath_new(); ++ _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out); ++ (*dmmp_mps)[i] = dmmp_mp; ++ _good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out); ++ } ++ ++out: ++ if (socket_fd >= 0) ++ mpath_disconnect(socket_fd); ++ free(j_str); ++ if (j_token != NULL) ++ json_tokener_free(j_token); ++ if (j_obj != NULL) ++ json_object_put(j_obj); ++ ++ if (rc != DMMP_OK) { ++ dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count); ++ *dmmp_mps = NULL; ++ *dmmp_mp_count = 0; ++ } ++ ++ return rc; ++} +Index: multipath-tools-130222/libdmmp/libdmmp.pc.in +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp.pc.in +@@ -0,0 +1,9 @@ ++includedir=__INCLUDEDIR__ ++libdir=__LIBDIR__ ++ ++Name: libdmmp ++Version: __VERSION__ ++Description: Device mapper multipath management library ++Requires: ++Libs: -L${libdir} -ldmmp ++Cflags: -I${includedir} +Index: multipath-tools-130222/libdmmp/libdmmp/libdmmp.h +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp/libdmmp.h +@@ -0,0 +1,653 @@ ++/* ++ * Copyright (C) 2015 - 2017 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++ ++#ifndef _LIB_DMMP_H_ ++#define _LIB_DMMP_H_ ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define DMMP_DLL_EXPORT __attribute__ ((visibility ("default"))) ++#define DMMP_DLL_LOCAL __attribute__ ((visibility ("hidden"))) ++ ++#define DMMP_OK 0 ++#define DMMP_ERR_BUG 1 ++#define DMMP_ERR_NO_MEMORY 2 ++#define DMMP_ERR_IPC_TIMEOUT 3 ++#define DMMP_ERR_IPC_ERROR 4 ++#define DMMP_ERR_NO_DAEMON 5 ++#define DMMP_ERR_INCOMPATIBLE 6 ++ ++/* ++ * Use the syslog severity level as log priority ++ */ ++#define DMMP_LOG_PRIORITY_ERROR 3 ++#define DMMP_LOG_PRIORITY_WARNING 4 ++#define DMMP_LOG_PRIORITY_INFO 6 ++#define DMMP_LOG_PRIORITY_DEBUG 7 ++ ++#define DMMP_LOG_PRIORITY_DEFAULT DMMP_LOG_PRIORITY_WARNING ++ ++/** ++ * dmmp_log_priority_str() - Convert log priority to string. ++ * ++ * Convert log priority to string (const char *). ++ * ++ * @priority: ++ * int. Log priority. ++ * ++ * Return: ++ * const char *. Valid string are: ++ * ++ * * "ERROR" for DMMP_LOG_PRIORITY_ERROR ++ * ++ * * "WARN " for DMMP_LOG_PRIORITY_WARNING ++ * ++ * * "INFO " for DMMP_LOG_PRIORITY_INFO ++ * ++ * * "DEBUG" for DMMP_LOG_PRIORITY_DEBUG ++ * ++ * * "Invalid argument" for invalid log priority. ++ */ ++DMMP_DLL_EXPORT const char *dmmp_log_priority_str(int priority); ++ ++DMMP_DLL_EXPORT struct dmmp_context; ++ ++DMMP_DLL_EXPORT struct dmmp_mpath; ++ ++DMMP_DLL_EXPORT struct dmmp_path_group; ++ ++#define DMMP_PATH_GROUP_STATUS_UNKNOWN 0 ++#define DMMP_PATH_GROUP_STATUS_ENABLED 1 ++#define DMMP_PATH_GROUP_STATUS_DISABLED 2 ++#define DMMP_PATH_GROUP_STATUS_ACTIVE 3 ++ ++DMMP_DLL_EXPORT struct dmmp_path; ++ ++#define DMMP_PATH_STATUS_UNKNOWN 0 ++//#define DMMP_PATH_STATUS_UNCHECKED 1 ++// ^ print.h does not expose this. ++#define DMMP_PATH_STATUS_DOWN 2 ++#define DMMP_PATH_STATUS_UP 3 ++#define DMMP_PATH_STATUS_SHAKY 4 ++#define DMMP_PATH_STATUS_GHOST 5 ++#define DMMP_PATH_STATUS_PENDING 6 ++#define DMMP_PATH_STATUS_TIMEOUT 7 ++//#define DMMP_PATH_STATUS_REMOVED 8 ++// ^ print.h does not expose this. ++#define DMMP_PATH_STATUS_DELAYED 9 ++ ++/** ++ * dmmp_strerror() - Convert error code to string. ++ * ++ * Convert error code (int) to string (const char *): ++ * ++ * * DMMP_OK -- "OK" ++ * ++ * * DMMP_ERR_BUG -- "BUG of libdmmp library" ++ * ++ * * DMMP_ERR_NO_MEMORY -- "Out of memory" ++ * ++ * * DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd, ++ * try to set bigger timeout value via dmmp_context_timeout_set ()" ++ * ++ * * DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon" ++ * ++ * * DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started" ++ * ++ * * DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not ++ * compatible with current library" ++ * ++ * * Other invalid error number -- "Invalid argument" ++ * ++ * @rc: ++ * int. Return code by libdmmp functions. When provided error code is not a ++ * valid error code, return "Invalid argument". ++ * ++ * Return: ++ * const char *. The meaning of provided error code. ++ * ++ */ ++DMMP_DLL_EXPORT const char *dmmp_strerror(int rc); ++ ++/** ++ * dmmp_context_new() - Create struct dmmp_context. ++ * ++ * The default logging level (DMMP_LOG_PRIORITY_DEFAULT) is ++ * DMMP_LOG_PRIORITY_WARNING which means only warning and error message will be ++ * forward to log handler function. The default log handler function will print ++ * log message to STDERR, to change so, please use dmmp_context_log_func_set() ++ * to set your own log handler, check manpage libdmmp.h(3) for detail. ++ * ++ * Return: ++ * Pointer of 'struct dmmp_context'. Should be freed by ++ * dmmp_context_free(). ++ */ ++DMMP_DLL_EXPORT struct dmmp_context *dmmp_context_new(void); ++ ++/** ++ * dmmp_context_free() - Release the memory of struct dmmp_context. ++ * ++ * Release the memory of struct dmmp_context, but the userdata memory defined ++ * via dmmp_context_userdata_set() will not be touched. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_context_free(struct dmmp_context *ctx); ++ ++/** ++ * dmmp_context_timeout_set() - Set IPC timeout. ++ * ++ * By default, the IPC to multipathd daemon will timeout after 60 seconds. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * @tmo: ++ * Timeout in milliseconds(1 seconds equal 1000 milliseconds). ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_context_timeout_set(struct dmmp_context *ctx, ++ unsigned int tmo); ++ ++/** ++ * dmmp_context_timeout_get() - Get IPC timeout. ++ * ++ * Retrieve timeout value of IPC connection to multipathd daemon. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * unsigned int. Timeout in milliseconds. ++ */ ++DMMP_DLL_EXPORT unsigned int dmmp_context_timeout_get(struct dmmp_context *ctx); ++ ++/** ++ * dmmp_context_log_priority_set() - Set log priority. ++ * ++ * ++ * When library generates log message, only equal or more important(less value) ++ * message will be forwarded to log handler function. Valid log priority values ++ * are: ++ * ++ * * DMMP_LOG_PRIORITY_ERROR -- 3 ++ * ++ * * DMMP_LOG_PRIORITY_WARNING -- 4 ++ * ++ * * DMMP_LOG_PRIORITY_INFO -- 5 ++ * ++ * * DMMP_LOG_PRIORITY_DEBUG -- 7 ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * @priority: ++ * int, log priority. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_context_log_priority_set(struct dmmp_context *ctx, ++ int priority); ++ ++/** ++ * dmmp_context_log_priority_get() - Get log priority. ++ * ++ * Retrieve current log priority. Valid log priority values are: ++ * ++ * * DMMP_LOG_PRIORITY_ERROR -- 3 ++ * ++ * * DMMP_LOG_PRIORITY_WARNING -- 4 ++ * ++ * * DMMP_LOG_PRIORITY_INFO -- 5 ++ * ++ * * DMMP_LOG_PRIORITY_DEBUG -- 7 ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * int, log priority. ++ */ ++DMMP_DLL_EXPORT int dmmp_context_log_priority_get(struct dmmp_context *ctx); ++ ++/** ++ * dmmp_context_log_func_set() - Set log handler function. ++ * ++ * Set custom log handler. The log handler will be invoked when log message ++ * is equal or more important(less value) than log priority setting. ++ * Please check manpage libdmmp.h(3) for detail usage. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @log_func: ++ * Pointer of log handler function. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_context_log_func_set ++ (struct dmmp_context *ctx, ++ void (*log_func) ++ (struct dmmp_context *ctx, int priority, ++ const char *file, int line, const char *func_name, ++ const char *format, va_list args)); ++ ++/** ++ * dmmp_context_userdata_set() - Set user data pointer. ++ * ++ * Store user data pointer into 'struct dmmp_context'. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @userdata: ++ * Pointer of user defined data. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_context_userdata_set(struct dmmp_context *ctx, ++ void *userdata); ++ ++/** ++ * dmmp_context_userdata_get() - Get user data pointer. ++ * ++ * Retrieve user data pointer from 'struct dmmp_context'. ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * void *. Pointer of user defined data. ++ */ ++DMMP_DLL_EXPORT void *dmmp_context_userdata_get(struct dmmp_context *ctx); ++ ++/** ++ * dmmp_mpath_array_get() - Query all existing multipath devices. ++ * ++ * Query all existing multipath devices and store them into a pointer array. ++ * The memory of 'dmmp_mps' should be freed via dmmp_mpath_array_free(). ++ * ++ * @ctx: ++ * Pointer of 'struct dmmp_context'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_mps: ++ * Output pointer array of 'struct dmmp_mpath'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_mp_count: ++ * Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * int. Valid error codes are: ++ * ++ * * DMMP_OK ++ * ++ * * DMMP_ERR_BUG ++ * ++ * * DMMP_ERR_NO_MEMORY ++ * ++ * * DMMP_ERR_NO_DAEMON ++ * ++ * * DMMP_ERR_INCONSISTENT_DATA ++ * ++ * Error number could be converted to string by dmmp_strerror(). ++ */ ++DMMP_DLL_EXPORT int dmmp_mpath_array_get(struct dmmp_context *ctx, ++ struct dmmp_mpath ***dmmp_mps, ++ uint32_t *dmmp_mp_count); ++ ++/** ++ * dmmp_mpath_array_free() - Free 'struct dmmp_mpath' pointer array. ++ * ++ * Free the 'dmmp_mps' pointer array generated by dmmp_mpath_array_get(). ++ * If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing. ++ * ++ * @dmmp_mps: ++ * Pointer of 'struct dmmp_mpath' array. ++ * @dmmp_mp_count: ++ * uint32_t, the size of 'dmmp_mps' pointer array. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_mpath_array_free(struct dmmp_mpath **dmmp_mps, ++ uint32_t dmmp_mp_count); ++ ++/** ++ * dmmp_mpath_wwid_get() - Retrieve WWID of certain mpath. ++ * ++ * @dmmp_mp: ++ * Pointer of 'struct dmmp_mpath'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * const char *. No need to free this memory, the resources will get ++ * freed when dmmp_mpath_array_free(). ++ */ ++DMMP_DLL_EXPORT const char *dmmp_mpath_wwid_get(struct dmmp_mpath *dmmp_mp); ++ ++/** ++ * dmmp_mpath_name_get() - Retrieve name(alias) of certain mpath. ++ * ++ * Retrieve the name (also known as alias) of certain mpath. ++ * When the config 'user_friendly_names' been set 'no', the name will be ++ * identical to WWID retrieved by dmmp_mpath_wwid_get(). ++ * ++ * @dmmp_mp: ++ * Pointer of 'struct dmmp_mpath'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * const char *. No need to free this memory, the resources will get ++ * freed when dmmp_mpath_array_free(). ++ */ ++DMMP_DLL_EXPORT const char *dmmp_mpath_name_get(struct dmmp_mpath *dmmp_mp); ++ ++/** ++ * dmmp_mpath_kdev_name_get() - Retrieve kernel DEVNAME of certain mpath. ++ * ++ * Retrieve DEVNAME name used by kernel uevent of specified mpath. ++ * Example: 'dm-1'. ++ * ++ * @dmmp_mp: ++ * Pointer of 'struct dmmp_mpath'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * const char *. No need to free this memory, the resources will get ++ * freed when dmmp_mpath_array_free(). ++ */ ++DMMP_DLL_EXPORT const char *dmmp_mpath_kdev_name_get ++ (struct dmmp_mpath *dmmp_mp); ++ ++/** ++ * dmmp_path_group_array_get() - Retrieve path groups pointer array. ++ * ++ * Retrieve the path groups of certain mpath. ++ * ++ * The memory of output pointer array is hold by 'struct dmmp_mpath', no ++ * need to free this memory, the resources will got freed when ++ * dmmp_mpath_array_free(). ++ * ++ * @dmmp_mp: ++ * Pointer of 'struct dmmp_mpath'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_pgs: ++ * Output pointer of 'struct dmmp_path_group' pointer array. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_pg_count: ++ * Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_path_group_array_get ++ (struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs, ++ uint32_t *dmmp_pg_count); ++ ++/** ++ * dmmp_path_group_id_get() - Retrieve path group ID. ++ * ++ * Retrieve the path group ID which could be used to switch active path group ++ * via command: ++ * ++ * multipathd -k'switch multipath mpathb group $id' ++ * ++ * @dmmp_pg: ++ * Pointer of 'struct dmmp_path_group'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * uint32_t. ++ */ ++DMMP_DLL_EXPORT uint32_t dmmp_path_group_id_get ++ (struct dmmp_path_group *dmmp_pg); ++ ++/** ++ * dmmp_path_group_priority_get() - Retrieve path group priority. ++ * ++ * The enabled path group with highest priority will be next active path group ++ * if active path group down. ++ * ++ * @dmmp_pg: ++ * Pointer of 'struct dmmp_path_group'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * uint32_t. ++ */ ++DMMP_DLL_EXPORT uint32_t dmmp_path_group_priority_get ++ (struct dmmp_path_group *dmmp_pg); ++ ++/** ++ * dmmp_path_group_status_get() - Retrieve path group status. ++ * ++ * The valid path group statuses are: ++ * ++ * * DMMP_PATH_GROUP_STATUS_UNKNOWN ++ * ++ * * DMMP_PATH_GROUP_STATUS_ENABLED -- standby to be active ++ * ++ * * DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down ++ * ++ * * DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O ++ * ++ * @dmmp_pg: ++ * Pointer of 'struct dmmp_path_group'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * uint32_t. ++ */ ++DMMP_DLL_EXPORT uint32_t dmmp_path_group_status_get ++ (struct dmmp_path_group *dmmp_pg); ++ ++/** ++ * dmmp_path_group_status_str() - Convert path group status to string. ++ * ++ * Convert path group status uint32_t to string (const char *). ++ * ++ * @pg_status: ++ * uint32_t. Path group status. ++ * When provided value is not a valid path group status, return "Invalid ++ * argument". ++ * ++ * Return: ++ * const char *. Valid string are: ++ * ++ * * "Invalid argument" ++ * ++ * * "undef" ++ * ++ * * "enabled" ++ * ++ * * "disabled" ++ * ++ * * "active" ++ */ ++DMMP_DLL_EXPORT const char *dmmp_path_group_status_str(uint32_t pg_status); ++ ++/** ++ * dmmp_path_group_selector_get() - Retrieve path group selector. ++ * ++ * Path group selector determine which path in active path group will be ++ * use to next I/O. ++ * ++ * @dmmp_pg: ++ * Pointer of 'struct dmmp_path_group'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * const char *. ++ */ ++DMMP_DLL_EXPORT const char *dmmp_path_group_selector_get ++ (struct dmmp_path_group *dmmp_pg); ++ ++/** ++ * dmmp_path_array_get() - Retrieve path pointer array. ++ * ++ * The memory of output pointer array is hold by 'struct dmmp_mpath', no ++ * need to free this memory, the resources will got freed when ++ * dmmp_mpath_array_free(). ++ * ++ * @dmmp_pg: ++ * Pointer of 'struct dmmp_path_group'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_ps: ++ * Output pointer of 'struct dmmp_path' pointer array. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * @dmmp_p_count: ++ * Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * void ++ */ ++DMMP_DLL_EXPORT void dmmp_path_array_get(struct dmmp_path_group *dmmp_pg, ++ struct dmmp_path ***dmmp_ps, ++ uint32_t *dmmp_p_count); ++ ++/** ++ * dmmp_path_blk_name_get() - Retrieve block name. ++ * ++ * Retrieve block name of certain path. The example of block names are 'sda', ++ * 'nvme0n1'. ++ * ++ * @dmmp_p: ++ * Pointer of 'struct dmmp_path'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * const char *. No need to free this memory, the resources will get ++ * freed when dmmp_mpath_array_free(). ++ */ ++DMMP_DLL_EXPORT const char *dmmp_path_blk_name_get(struct dmmp_path *dmmp_p); ++ ++/** ++ * dmmp_path_status_get() - Retrieve the path status. ++ * ++ * The valid path statuses are: ++ * ++ * * DMMP_PATH_STATUS_UNKNOWN ++ * ++ * * DMMP_PATH_STATUS_DOWN ++ * ++ * Path is down and you shouldn't try to send commands to it. ++ * ++ * * DMMP_PATH_STATUS_UP ++ * ++ * Path is up and I/O can be sent to it. ++ * ++ * * DMMP_PATH_STATUS_SHAKY ++ * ++ * Only emc_clariion checker when path not available for "normal" ++ * operations. ++ * ++ * * DMMP_PATH_STATUS_GHOST ++ * ++ * Only hp_sw and rdac checkers. Indicates a "passive/standby" ++ * path on active/passive HP arrays. These paths will return valid ++ * answers to certain SCSI commands (tur, read_capacity, inquiry, ++ * start_stop), but will fail I/O commands. The path needs an ++ * initialization command to be sent to it in order for I/Os to ++ * succeed. ++ * ++ * * DMMP_PATH_STATUS_PENDING ++ * ++ * Available for all async checkers when a check IO is in flight. ++ * ++ * * DMMP_PATH_STATUS_TIMEOUT ++ * ++ * Only tur checker when command timed out. ++ * ++ * * DMMP_PATH_STATUS_DELAYED ++ * ++ * If a path fails after being up for less than delay_watch_checks checks, ++ * when it comes back up again, it will not be marked as up until it has ++ * been up for delay_wait_checks checks. During this time, it is marked as ++ * "delayed". ++ * ++ * @dmmp_p: ++ * Pointer of 'struct dmmp_path'. ++ * If this pointer is NULL, your program will be terminated by assert. ++ * ++ * Return: ++ * uint32_t. ++ */ ++DMMP_DLL_EXPORT uint32_t dmmp_path_status_get(struct dmmp_path *dmmp_p); ++ ++/** ++ * dmmp_path_status_str() - Convert path status to string. ++ * ++ * Convert path status uint32_t to string (const char *): ++ * ++ * * DMMP_PATH_STATUS_UNKNOWN -- "undef" ++ * ++ * * DMMP_PATH_STATUS_DOWN -- "faulty" ++ * ++ * * DMMP_PATH_STATUS_UP -- "ready" ++ * ++ * * DMMP_PATH_STATUS_SHAKY -- "shaky" ++ * ++ * * DMMP_PATH_STATUS_GHOST -- "ghost" ++ * ++ * * DMMP_PATH_STATUS_PENDING -- "pending" ++ * ++ * * DMMP_PATH_STATUS_TIMEOUT -- "timeout" ++ * ++ * * DMMP_PATH_STATUS_REMOVED -- "removed" ++ * ++ * * DMMP_PATH_STATUS_DELAYED -- "delayed" ++ * ++ * @path_status: ++ * uint32_t. Path status. ++ * When provided value is not a valid path status, return ++ * "Invalid argument". ++ * ++ * Return: ++ * const char *. The meaning of status value. ++ */ ++DMMP_DLL_EXPORT const char *dmmp_path_status_str(uint32_t path_status); ++ ++#ifdef __cplusplus ++} /* End of extern "C" */ ++#endif ++ ++#endif /* End of _LIB_DMMP_H_ */ +Index: multipath-tools-130222/libdmmp/libdmmp_misc.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp_misc.c +@@ -0,0 +1,87 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++#include "libdmmp_private.h" ++ ++#define _DMMP_LOG_STRERR_ALIGN_WIDTH 80 ++/* ^ Only used in _dmmp_log_stderr() for pretty log output. ++ * When provided log message is less than 80 bytes, fill it with space, then ++ * print code file name, function name, line after the 80th bytes. ++ */ ++ ++static const struct _num_str_conv _DMMP_RC_MSG_CONV[] = { ++ {DMMP_OK, "OK"}, ++ {DMMP_ERR_NO_MEMORY, "Out of memory"}, ++ {DMMP_ERR_BUG, "BUG of libdmmp library"}, ++ {DMMP_ERR_IPC_TIMEOUT, "Timeout when communicate with multipathd, " ++ "try to increase it via " ++ "dmmp_context_timeout_set()"}, ++ {DMMP_ERR_IPC_ERROR, "Error when communicate with multipathd daemon"}, ++ {DMMP_ERR_NO_DAEMON, "The multipathd daemon not started"}, ++ {DMMP_ERR_INCOMPATIBLE, "Incompatible multipathd daemon version"}, ++}; ++ ++_dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG_CONV); ++ ++static const struct _num_str_conv _DMMP_PRI_CONV[] = { ++ {DMMP_LOG_PRIORITY_DEBUG, "DEBUG"}, ++ {DMMP_LOG_PRIORITY_INFO, "INFO"}, ++ {DMMP_LOG_PRIORITY_WARNING, "WARNING"}, ++ {DMMP_LOG_PRIORITY_ERROR, "ERROR"}, ++}; ++_dmmp_str_func_gen(dmmp_log_priority_str, int, priority, _DMMP_PRI_CONV); ++ ++void _dmmp_log_stderr(struct dmmp_context *ctx, int priority, ++ const char *file, int line, const char *func_name, ++ const char *format, va_list args) ++{ ++ int printed_bytes = 0; ++ void *userdata = NULL; ++ ++ printed_bytes += fprintf(stderr, "libdmmp %s: ", ++ dmmp_log_priority_str(priority)); ++ printed_bytes += vfprintf(stderr, format, args); ++ ++ userdata = dmmp_context_userdata_get(ctx); ++ if (userdata != NULL) ++ fprintf(stderr, "(userdata address: %p)", ++ userdata); ++ /* ^ Just demonstrate how userdata could be used and ++ * bypass clang static analyzer about unused ctx argument warning ++ */ ++ ++ if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) { ++ fprintf(stderr, "%*s # %s:%s():%d\n", ++ _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, ++ func_name, line); ++ } else { ++ fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); ++ } ++} +Index: multipath-tools-130222/libdmmp/libdmmp_mp.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp_mp.c +@@ -0,0 +1,159 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++#include "libdmmp_private.h" ++ ++struct dmmp_mpath { ++ char *wwid; ++ char *alias; ++ uint32_t dmmp_pg_count; ++ struct dmmp_path_group **dmmp_pgs; ++ char *kdev_name; ++}; ++ ++_dmmp_getter_func_gen(dmmp_mpath_name_get, struct dmmp_mpath, dmmp_mp, ++ alias, const char *); ++_dmmp_getter_func_gen(dmmp_mpath_wwid_get, struct dmmp_mpath, dmmp_mp, ++ wwid, const char *); ++_dmmp_getter_func_gen(dmmp_mpath_kdev_name_get, struct dmmp_mpath, dmmp_mp, ++ kdev_name, const char *); ++ ++struct dmmp_mpath *_dmmp_mpath_new(void) ++{ ++ struct dmmp_mpath *dmmp_mp = NULL; ++ ++ dmmp_mp = (struct dmmp_mpath *) malloc(sizeof(struct dmmp_mpath)); ++ ++ if (dmmp_mp != NULL) { ++ dmmp_mp->wwid = NULL; ++ dmmp_mp->alias = NULL; ++ dmmp_mp->dmmp_pg_count = 0; ++ dmmp_mp->dmmp_pgs = NULL; ++ } ++ return dmmp_mp; ++} ++ ++int _dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp, ++ json_object *j_obj_map) ++{ ++ int rc = DMMP_OK; ++ const char *wwid = NULL; ++ const char *alias = NULL; ++ struct array_list *ar_pgs = NULL; ++ int ar_pgs_len = -1; ++ uint32_t i = 0; ++ struct dmmp_path_group *dmmp_pg = NULL; ++ const char *kdev_name = NULL; ++ ++ assert(ctx != NULL); ++ assert(dmmp_mp != NULL); ++ assert(j_obj_map != NULL); ++ ++ _json_obj_get_value(ctx, j_obj_map, wwid, "uuid", json_type_string, ++ json_object_get_string, rc, out); ++ _json_obj_get_value(ctx, j_obj_map, alias, "name", json_type_string, ++ json_object_get_string, rc, out); ++ _json_obj_get_value(ctx, j_obj_map, kdev_name, "sysfs", ++ json_type_string, json_object_get_string, rc, out); ++ ++ _dmmp_null_or_empty_str_check(ctx, wwid, rc, out); ++ _dmmp_null_or_empty_str_check(ctx, alias, rc, out); ++ ++ dmmp_mp->wwid = strdup(wwid); ++ _dmmp_alloc_null_check(ctx, dmmp_mp->wwid, rc, out); ++ dmmp_mp->alias = strdup(alias); ++ _dmmp_alloc_null_check(ctx, dmmp_mp->alias, rc, out); ++ dmmp_mp->kdev_name = strdup(kdev_name); ++ _dmmp_alloc_null_check(ctx, dmmp_mp->kdev_name, rc, out); ++ ++ _json_obj_get_value(ctx, j_obj_map, ar_pgs, "path_groups", ++ json_type_array, json_object_get_array, rc, out); ++ ar_pgs_len = array_list_length(ar_pgs); ++ if (ar_pgs_len < 0) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: Got negative length for ar_pgs"); ++ goto out; ++ } ++ else if (ar_pgs_len == 0) ++ goto out; ++ else ++ dmmp_mp->dmmp_pg_count = ar_pgs_len & UINT32_MAX; ++ ++ dmmp_mp->dmmp_pgs = (struct dmmp_path_group **) ++ malloc(sizeof(struct dmmp_path_group *) * ++ dmmp_mp->dmmp_pg_count); ++ _dmmp_alloc_null_check(ctx, dmmp_mp->dmmp_pgs, rc, out); ++ for (; i < dmmp_mp->dmmp_pg_count; ++i) ++ dmmp_mp->dmmp_pgs[i] = NULL; ++ ++ for (i = 0; i < dmmp_mp->dmmp_pg_count; ++i) { ++ dmmp_pg = _dmmp_path_group_new(); ++ _dmmp_alloc_null_check(ctx, dmmp_pg, rc, out); ++ dmmp_mp->dmmp_pgs[i] = dmmp_pg; ++ _good(_dmmp_path_group_update(ctx, dmmp_pg, ++ array_list_get_idx(ar_pgs, i)), ++ rc, out); ++ } ++ ++ _debug(ctx, "Got mpath wwid: '%s', alias: '%s'", dmmp_mp->wwid, ++ dmmp_mp->alias); ++ ++out: ++ if (rc != DMMP_OK) ++ _dmmp_mpath_free(dmmp_mp); ++ return rc; ++} ++ ++void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp) ++{ ++ if (dmmp_mp == NULL) ++ return ; ++ ++ free((char *) dmmp_mp->alias); ++ free((char *) dmmp_mp->wwid); ++ free((char *) dmmp_mp->kdev_name); ++ ++ if (dmmp_mp->dmmp_pgs != NULL) ++ _dmmp_path_group_array_free(dmmp_mp->dmmp_pgs, ++ dmmp_mp->dmmp_pg_count); ++ ++ free(dmmp_mp); ++} ++ ++void dmmp_path_group_array_get(struct dmmp_mpath *dmmp_mp, ++ struct dmmp_path_group ***dmmp_pgs, ++ uint32_t *dmmp_pg_count) ++{ ++ assert(dmmp_mp != NULL); ++ assert(dmmp_pgs != NULL); ++ assert(dmmp_pg_count != NULL); ++ ++ *dmmp_pgs = dmmp_mp->dmmp_pgs; ++ *dmmp_pg_count = dmmp_mp->dmmp_pg_count; ++} +Index: multipath-tools-130222/libdmmp/libdmmp_path.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp_path.c +@@ -0,0 +1,115 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++#include "libdmmp_private.h" ++ ++#define _DMMP_SHOW_PS_INDEX_BLK_NAME 0 ++#define _DMMP_SHOW_PS_INDEX_SATAUS 1 ++#define _DMMP_SHOW_PS_INDEX_WWID 2 ++#define _DMMP_SHOW_PS_INDEX_PGID 3 ++ ++struct dmmp_path { ++ char *blk_name; ++ uint32_t status; ++}; ++ ++static const struct _num_str_conv _DMMP_PATH_STATUS_CONV[] = { ++ {DMMP_PATH_STATUS_UNKNOWN, "undef"}, ++ {DMMP_PATH_STATUS_UP, "ready"}, ++ {DMMP_PATH_STATUS_DOWN, "faulty"}, ++ {DMMP_PATH_STATUS_SHAKY, "shaky"}, ++ {DMMP_PATH_STATUS_GHOST, "ghost"}, ++ {DMMP_PATH_STATUS_PENDING, "i/o pending"}, ++ {DMMP_PATH_STATUS_TIMEOUT, "i/o timeout"}, ++ {DMMP_PATH_STATUS_DELAYED, "delayed"}, ++}; ++ ++_dmmp_str_func_gen(dmmp_path_status_str, uint32_t, path_status, ++ _DMMP_PATH_STATUS_CONV); ++_dmmp_str_conv_func_gen(_dmmp_path_status_str_conv, ctx, path_status_str, ++ uint32_t, DMMP_PATH_STATUS_UNKNOWN, ++ _DMMP_PATH_STATUS_CONV); ++ ++_dmmp_getter_func_gen(dmmp_path_blk_name_get, struct dmmp_path, dmmp_p, ++ blk_name, const char *); ++_dmmp_getter_func_gen(dmmp_path_status_get, struct dmmp_path, dmmp_p, ++ status, uint32_t); ++ ++struct dmmp_path *_dmmp_path_new(void) ++{ ++ struct dmmp_path *dmmp_p = NULL; ++ ++ dmmp_p = (struct dmmp_path *) malloc(sizeof(struct dmmp_path)); ++ ++ if (dmmp_p != NULL) { ++ dmmp_p->blk_name = NULL; ++ dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN; ++ } ++ return dmmp_p; ++} ++ ++int _dmmp_path_update(struct dmmp_context *ctx, struct dmmp_path *dmmp_p, ++ json_object *j_obj_p) ++{ ++ int rc = DMMP_OK; ++ const char *blk_name = NULL; ++ const char *status_str = NULL; ++ ++ assert(ctx != NULL); ++ assert(dmmp_p != NULL); ++ assert(j_obj_p != NULL); ++ ++ _json_obj_get_value(ctx, j_obj_p, blk_name, "dev", ++ json_type_string, json_object_get_string, rc, out); ++ _json_obj_get_value(ctx, j_obj_p, status_str, "chk_st", ++ json_type_string, json_object_get_string, rc, out); ++ ++ _dmmp_null_or_empty_str_check(ctx, blk_name, rc, out); ++ _dmmp_null_or_empty_str_check(ctx, status_str, rc, out); ++ ++ dmmp_p->blk_name = strdup(blk_name); ++ _dmmp_alloc_null_check(ctx, dmmp_p->blk_name, rc, out); ++ ++ dmmp_p->status = _dmmp_path_status_str_conv(ctx, status_str); ++ ++ _debug(ctx, "Got path blk_name: '%s'", dmmp_p->blk_name); ++ _debug(ctx, "Got path status: %s(%" PRIu32 ")", ++ dmmp_path_status_str(dmmp_p->status), dmmp_p->status); ++ ++out: ++ if (rc != DMMP_OK) ++ _dmmp_path_free(dmmp_p); ++ return rc; ++} ++ ++void _dmmp_path_free(struct dmmp_path *dmmp_p) ++{ ++ if (dmmp_p == NULL) ++ return; ++ free(dmmp_p->blk_name); ++ free(dmmp_p); ++} +Index: multipath-tools-130222/libdmmp/libdmmp_pg.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp_pg.c +@@ -0,0 +1,208 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++#include "libdmmp_private.h" ++ ++#define _DMMP_SHOW_PGS_CMD "show groups raw format %w|%g|%p|%t|%s" ++#define _DMMP_SHOW_PG_INDEX_WWID 0 ++#define _DMMP_SHOW_PG_INDEX_PG_ID 1 ++#define _DMMP_SHOW_PG_INDEX_PRI 2 ++#define _DMMP_SHOW_PG_INDEX_STATUS 3 ++#define _DMMP_SHOW_PG_INDEX_SELECTOR 4 ++ ++struct dmmp_path_group { ++ uint32_t id; ++ /* ^ pgindex of struct path, will be used for path group switch */ ++ uint32_t status; ++ uint32_t priority; ++ char *selector; ++ uint32_t dmmp_p_count; ++ struct dmmp_path **dmmp_ps; ++}; ++ ++static const struct _num_str_conv _DMMP_PATH_GROUP_STATUS_CONV[] = { ++ {DMMP_PATH_GROUP_STATUS_UNKNOWN, "undef"}, ++ {DMMP_PATH_GROUP_STATUS_ACTIVE, "active"}, ++ {DMMP_PATH_GROUP_STATUS_DISABLED, "disabled"}, ++ {DMMP_PATH_GROUP_STATUS_ENABLED, "enabled"}, ++}; ++ ++_dmmp_str_func_gen(dmmp_path_group_status_str, uint32_t, pg_status, ++ _DMMP_PATH_GROUP_STATUS_CONV); ++_dmmp_str_conv_func_gen(_dmmp_path_group_status_str_conv, ctx, pg_status_str, ++ uint32_t, DMMP_PATH_GROUP_STATUS_UNKNOWN, ++ _DMMP_PATH_GROUP_STATUS_CONV); ++ ++_dmmp_getter_func_gen(dmmp_path_group_id_get, struct dmmp_path_group, dmmp_pg, ++ id, uint32_t); ++_dmmp_getter_func_gen(dmmp_path_group_status_get, struct dmmp_path_group, ++ dmmp_pg, status, uint32_t); ++_dmmp_getter_func_gen(dmmp_path_group_priority_get, struct dmmp_path_group, ++ dmmp_pg, priority, uint32_t); ++_dmmp_getter_func_gen(dmmp_path_group_selector_get, struct dmmp_path_group, ++ dmmp_pg, selector, const char *); ++_dmmp_array_free_func_gen(_dmmp_path_group_array_free, struct dmmp_path_group, ++ _dmmp_path_group_free); ++ ++ ++struct dmmp_path_group *_dmmp_path_group_new(void) ++{ ++ struct dmmp_path_group *dmmp_pg = NULL; ++ ++ dmmp_pg = (struct dmmp_path_group *) ++ malloc(sizeof(struct dmmp_path_group)); ++ ++ if (dmmp_pg != NULL) { ++ dmmp_pg->id = _DMMP_PATH_GROUP_ID_UNKNOWN; ++ dmmp_pg->status = DMMP_PATH_GROUP_STATUS_UNKNOWN; ++ dmmp_pg->priority = 0; ++ dmmp_pg->selector = NULL; ++ dmmp_pg->dmmp_p_count = 0; ++ dmmp_pg->dmmp_ps = NULL; ++ } ++ return dmmp_pg; ++} ++int _dmmp_path_group_update(struct dmmp_context *ctx, ++ struct dmmp_path_group *dmmp_pg, ++ json_object *j_obj_pg) ++{ ++ int rc = DMMP_OK; ++ uint32_t id = 0; ++ int priority_int = -1 ; ++ const char *status_str = NULL; ++ const char *selector = NULL; ++ struct array_list *ar_ps = NULL; ++ int ar_ps_len = -1; ++ uint32_t i = 0; ++ struct dmmp_path *dmmp_p = NULL; ++ ++ assert(ctx != NULL); ++ assert(dmmp_pg != NULL); ++ assert(j_obj_pg != NULL); ++ ++ _json_obj_get_value(ctx, j_obj_pg, status_str, "dm_st", ++ json_type_string, json_object_get_string, rc, out); ++ ++ _json_obj_get_value(ctx, j_obj_pg, selector, "selector", ++ json_type_string, json_object_get_string, rc, out); ++ ++ _json_obj_get_value(ctx, j_obj_pg, priority_int, "pri", ++ json_type_int, json_object_get_int, rc, out); ++ ++ _json_obj_get_value(ctx, j_obj_pg, id, "group", ++ json_type_int, json_object_get_int, rc, out); ++ ++ dmmp_pg->priority = (priority_int <= 0) ? 0 : priority_int & UINT32_MAX; ++ ++ _dmmp_null_or_empty_str_check(ctx, status_str, rc, out); ++ _dmmp_null_or_empty_str_check(ctx, selector, rc, out); ++ ++ dmmp_pg->selector = strdup(selector); ++ _dmmp_alloc_null_check(ctx, dmmp_pg->selector, rc, out); ++ ++ dmmp_pg->id = id; ++ ++ if (dmmp_pg->id == _DMMP_PATH_GROUP_ID_UNKNOWN) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: Got unknown(%d) path group ID", ++ _DMMP_PATH_GROUP_ID_UNKNOWN); ++ goto out; ++ } ++ ++ dmmp_pg->status = _dmmp_path_group_status_str_conv(ctx, status_str); ++ ++ _json_obj_get_value(ctx, j_obj_pg, ar_ps, "paths", ++ json_type_array, json_object_get_array, rc, out); ++ ++ ar_ps_len = array_list_length(ar_ps); ++ if (ar_ps_len < 0) { ++ rc = DMMP_ERR_BUG; ++ _error(ctx, "BUG: Got negative length for ar_ps"); ++ goto out; ++ } ++ else if (ar_ps_len == 0) ++ goto out; ++ else ++ dmmp_pg->dmmp_p_count = ar_ps_len & UINT32_MAX; ++ ++ dmmp_pg->dmmp_ps = (struct dmmp_path **) ++ malloc(sizeof(struct dmmp_path *) * dmmp_pg->dmmp_p_count); ++ _dmmp_alloc_null_check(ctx, dmmp_pg->dmmp_ps, rc, out); ++ for (; i < dmmp_pg->dmmp_p_count; ++i) ++ dmmp_pg->dmmp_ps[i] = NULL; ++ ++ for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) { ++ dmmp_p = _dmmp_path_new(); ++ _dmmp_alloc_null_check(ctx, dmmp_p, rc, out); ++ dmmp_pg->dmmp_ps[i] = dmmp_p; ++ _good(_dmmp_path_update(ctx, dmmp_p, ++ array_list_get_idx(ar_ps, i)), ++ rc, out); ++ } ++ ++ _debug(ctx, "Got path group id: %" PRIu32 "", dmmp_pg->id); ++ _debug(ctx, "Got path group priority: %" PRIu32 "", dmmp_pg->priority); ++ _debug(ctx, "Got path group status: %s(%" PRIu32 ")", ++ dmmp_path_group_status_str(dmmp_pg->status), dmmp_pg->status); ++ _debug(ctx, "Got path group selector: '%s'", dmmp_pg->selector); ++ ++out: ++ if (rc != DMMP_OK) ++ _dmmp_path_group_free(dmmp_pg); ++ return rc; ++} ++ ++void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg) ++{ ++ uint32_t i = 0; ++ ++ if (dmmp_pg == NULL) ++ return; ++ ++ free((char *) dmmp_pg->selector); ++ ++ if (dmmp_pg->dmmp_ps != NULL) { ++ for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) { ++ _dmmp_path_free(dmmp_pg->dmmp_ps[i]); ++ } ++ free(dmmp_pg->dmmp_ps); ++ } ++ free(dmmp_pg); ++} ++ ++void dmmp_path_array_get(struct dmmp_path_group *mp_pg, ++ struct dmmp_path ***mp_paths, ++ uint32_t *dmmp_p_count) ++{ ++ assert(mp_pg != NULL); ++ assert(mp_paths != NULL); ++ assert(dmmp_p_count != NULL); ++ ++ *mp_paths = mp_pg->dmmp_ps; ++ *dmmp_p_count = mp_pg->dmmp_p_count; ++} +Index: multipath-tools-130222/libdmmp/libdmmp_private.h +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/libdmmp_private.h +@@ -0,0 +1,208 @@ ++/* ++ * Copyright (C) 2015 - 2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ * Todd Gill ++ */ ++ ++#ifndef _LIB_DMMP_PRIVATE_H_ ++#define _LIB_DMMP_PRIVATE_H_ ++ ++/* ++ * Notes: ++ * Internal/Private functions does not check input argument but using ++ * assert() to abort if NULL pointer found in argument. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "libdmmp/libdmmp.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define _good(rc, rc_val, out) \ ++ do { \ ++ rc_val = rc; \ ++ if (rc_val != DMMP_OK) \ ++ goto out; \ ++ } while(0) ++ ++#define _DMMP_PATH_GROUP_ID_UNKNOWN 0 ++ ++DMMP_DLL_LOCAL struct _num_str_conv { ++ const uint32_t value; ++ const char *str; ++}; ++ ++#define _dmmp_str_func_gen(func_name, var_type, var, conv_array) \ ++const char *func_name(var_type var) { \ ++ size_t i = 0; \ ++ uint32_t tmp_var = var & UINT32_MAX; \ ++ /* In the whole libdmmp, we don't have negative value */ \ ++ for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \ ++ if ((conv_array[i].value) == tmp_var) \ ++ return conv_array[i].str; \ ++ } \ ++ return "Invalid argument"; \ ++} ++ ++#define _dmmp_str_conv_func_gen(func_name, ctx, var_name, out_type, \ ++ unknown_value, conv_array) \ ++static out_type func_name(struct dmmp_context *ctx, const char *var_name) { \ ++ size_t i = 0; \ ++ for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \ ++ if (strcmp(conv_array[i].str, var_name) == 0) \ ++ return conv_array[i].value; \ ++ } \ ++ _warn(ctx, "Got unknown " #var_name ": '%s'", var_name); \ ++ return unknown_value; \ ++} ++ ++#define _json_obj_get_value(ctx, j_obj, out_value, key, value_type, \ ++ value_func, rc, out) \ ++do { \ ++ json_type j_type = json_type_null; \ ++ json_object *j_obj_tmp = NULL; \ ++ if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != TRUE) { \ ++ _error(ctx, "Invalid JSON output from multipathd IPC: " \ ++ "key '%s' not found", key); \ ++ rc = DMMP_ERR_IPC_ERROR; \ ++ goto out; \ ++ } \ ++ if (j_obj_tmp == NULL) { \ ++ _error(ctx, "BUG: Got NULL j_obj_tmp from " \ ++ "json_object_object_get_ex() while it return TRUE"); \ ++ rc = DMMP_ERR_BUG; \ ++ goto out; \ ++ } \ ++ j_type = json_object_get_type(j_obj_tmp); \ ++ if (j_type != value_type) { \ ++ _error(ctx, "Invalid value type for key'%s' of JSON output " \ ++ "from multipathd IPC. Should be %s(%d), " \ ++ "but got %s(%d)", key, json_type_to_name(value_type), \ ++ value_type, json_type_to_name(j_type), j_type); \ ++ rc = DMMP_ERR_IPC_ERROR; \ ++ goto out; \ ++ } \ ++ out_value = value_func(j_obj_tmp); \ ++} while(0); ++ ++DMMP_DLL_LOCAL int _dmmp_ipc_exec(struct dmmp_context *ctx, const char *cmd, ++ char **output); ++ ++DMMP_DLL_LOCAL struct dmmp_mpath *_dmmp_mpath_new(void); ++DMMP_DLL_LOCAL struct dmmp_path_group *_dmmp_path_group_new(void); ++DMMP_DLL_LOCAL struct dmmp_path *_dmmp_path_new(void); ++ ++DMMP_DLL_LOCAL int _dmmp_mpath_update(struct dmmp_context *ctx, ++ struct dmmp_mpath *dmmp_mp, ++ json_object *j_obj_map); ++DMMP_DLL_LOCAL int _dmmp_path_group_update(struct dmmp_context *ctx, ++ struct dmmp_path_group *dmmp_pg, ++ json_object *j_obj_pg); ++DMMP_DLL_LOCAL int _dmmp_path_update(struct dmmp_context *ctx, ++ struct dmmp_path *dmmp_p, ++ json_object *j_obj_p); ++ ++DMMP_DLL_LOCAL void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp); ++DMMP_DLL_LOCAL void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg); ++DMMP_DLL_LOCAL void _dmmp_path_group_array_free ++ (struct dmmp_path_group **dmmp_pgs, uint32_t dmmp_pg_count); ++DMMP_DLL_LOCAL void _dmmp_path_free(struct dmmp_path *dmmp_p); ++DMMP_DLL_LOCAL void _dmmp_log(struct dmmp_context *ctx, int priority, ++ const char *file, int line, ++ const char *func_name, ++ const char *format, ...); ++DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc); ++ ++DMMP_DLL_LOCAL void _dmmp_log_stderr(struct dmmp_context *ctx, int priority, ++ const char *file, int line, ++ const char *func_name, const char *format, ++ va_list args); ++ ++ ++#define _dmmp_log_cond(ctx, prio, arg...) \ ++ do { \ ++ if (dmmp_context_log_priority_get(ctx) >= prio) \ ++ _dmmp_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, \ ++ ## arg); \ ++ } while (0) ++ ++#define _debug(ctx, arg...) \ ++ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_DEBUG, ## arg) ++#define _info(ctx, arg...) \ ++ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_INFO, ## arg) ++#define _warn(ctx, arg...) \ ++ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_WARNING, ## arg) ++#define _error(ctx, arg...) \ ++ _dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_ERROR, ## arg) ++ ++/* ++ * Check pointer returned by malloc() or strdup(), if NULL, set ++ * rc as DMMP_ERR_NO_MEMORY, report error and goto goto_out. ++ */ ++#define _dmmp_alloc_null_check(ctx, ptr, rc, goto_out) \ ++ do { \ ++ if (ptr == NULL) { \ ++ rc = DMMP_ERR_NO_MEMORY; \ ++ _error(ctx, dmmp_strerror(rc)); \ ++ goto goto_out; \ ++ } \ ++ } while(0) ++ ++#define _dmmp_null_or_empty_str_check(ctx, var, rc, goto_out) \ ++ do { \ ++ if (var == NULL) { \ ++ rc = DMMP_ERR_BUG; \ ++ _error(ctx, "BUG: Got NULL " #var); \ ++ goto goto_out; \ ++ } \ ++ if (strlen(var) == 0) { \ ++ rc = DMMP_ERR_BUG; \ ++ _error(ctx, "BUG: Got empty " #var); \ ++ goto goto_out; \ ++ } \ ++ } while(0) ++ ++#define _dmmp_getter_func_gen(func_name, struct_name, struct_data, \ ++ prop_name, prop_type) \ ++ prop_type func_name(struct_name *struct_data) \ ++ { \ ++ assert(struct_data != NULL); \ ++ return struct_data->prop_name; \ ++ } ++ ++#define _dmmp_array_free_func_gen(func_name, struct_name, struct_free_func) \ ++ void func_name(struct_name **ptr_array, uint32_t ptr_count) \ ++ { \ ++ uint32_t i = 0; \ ++ if (ptr_array == NULL) \ ++ return; \ ++ for (; i < ptr_count; ++i) \ ++ struct_free_func(ptr_array[i]); \ ++ free(ptr_array); \ ++ } ++ ++#ifdef __cplusplus ++} /* End of extern "C" */ ++#endif ++ ++#endif /* End of _LIB_DMMP_PRIVATE_H_ */ +Index: multipath-tools-130222/libdmmp/test/Makefile +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/test/Makefile +@@ -0,0 +1,30 @@ ++# Makefile ++# ++# Copyright (C) 2015-2016 Gris Ge ++# ++include ../../Makefile.inc ++ ++_libdmmpdir=../$(libdmmpdir) ++_mpathcmddir=../$(mpathcmddir) ++ ++TEST_EXEC = libdmmp_test ++SPD_TEST_EXEC = libdmmp_speed_test ++CFLAGS += -I$(_libdmmpdir) ++LDFLAGS += -L$(_libdmmpdir) -ldmmp ++ ++all: $(TEST_EXEC) $(SPD_TEST_EXEC) ++ ++check: $(TEST_EXEC) $(SPD_TEST_EXEC) ++ sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \ ++ valgrind --quiet --leak-check=full \ ++ --show-reachable=no --show-possibly-lost=no \ ++ --trace-children=yes --error-exitcode=1 \ ++ ./$(TEST_EXEC) ++ $(MAKE) speed_test ++ ++speed_test: $(SPD_TEST_EXEC) ++ sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \ ++ time -p ./$(SPD_TEST_EXEC) ++ ++clean: ++ rm -f $(TEST_EXEC) $(SPD_TEST_EXEC) +Index: multipath-tools-130222/libdmmp/test/libdmmp_speed_test.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/test/libdmmp_speed_test.c +@@ -0,0 +1,49 @@ ++/* ++ * Copyright (C) 2015-2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int main(int argc, char *argv[]) ++{ ++ struct dmmp_context *ctx = NULL; ++ struct dmmp_mpath **dmmp_mps = NULL; ++ uint32_t dmmp_mp_count = 0; ++ int rc = EXIT_SUCCESS; ++ ++ ctx = dmmp_context_new(); ++ dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_WARNING); ++ ++ if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) { ++ printf("FAILED\n"); ++ rc = EXIT_FAILURE; ++ } else { ++ printf("Got %" PRIu32 " mpath\n", dmmp_mp_count); ++ dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); ++ } ++ dmmp_context_free(ctx); ++ exit(rc); ++} +Index: multipath-tools-130222/libdmmp/test/libdmmp_test.c +=================================================================== +--- /dev/null ++++ multipath-tools-130222/libdmmp/test/libdmmp_test.c +@@ -0,0 +1,147 @@ ++/* ++ * Copyright (C) 2015-2016 Red Hat, Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author: Gris Ge ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define FAIL(rc, out, ...) \ ++ do { \ ++ rc = EXIT_FAILURE; \ ++ fprintf(stderr, "FAIL: "__VA_ARGS__ ); \ ++ goto out; \ ++ } while(0) ++#define PASS(...) fprintf(stdout, "PASS: "__VA_ARGS__ ); ++#define FILE_NAME_SIZE 256 ++#define TMO 10000 /* Forcing timeout to 10 seconds */ ++ ++int test_paths(struct dmmp_path_group *mp_pg) ++{ ++ struct dmmp_path **mp_ps = NULL; ++ uint32_t mp_p_count = 0; ++ uint32_t i = 0; ++ const char *blk_name = NULL; ++ int rc = EXIT_SUCCESS; ++ ++ dmmp_path_array_get(mp_pg, &mp_ps, &mp_p_count); ++ if (mp_p_count == 0) ++ FAIL(rc, out, "dmmp_path_array_get(): Got no path\n"); ++ for (i = 0; i < mp_p_count; ++i) { ++ blk_name = dmmp_path_blk_name_get(mp_ps[i]); ++ if (blk_name == NULL) ++ FAIL(rc, out, "dmmp_path_blk_name_get(): Got NULL\n"); ++ PASS("dmmp_path_blk_name_get(): %s\n", blk_name); ++ PASS("dmmp_path_status_get(): %" PRIu32 " -- %s\n", ++ dmmp_path_status_get(mp_ps[i]), ++ dmmp_path_status_str(dmmp_path_status_get(mp_ps[i]))); ++ } ++out: ++ return rc; ++} ++ ++int test_path_groups(struct dmmp_mpath *dmmp_mp) ++{ ++ struct dmmp_path_group **dmmp_pgs = NULL; ++ uint32_t dmmp_pg_count = 0; ++ uint32_t i = 0; ++ int rc = EXIT_SUCCESS; ++ ++ dmmp_path_group_array_get(dmmp_mp, &dmmp_pgs, &dmmp_pg_count); ++ if ((dmmp_pg_count == 0) && (dmmp_pgs != NULL)) ++ FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is not NULL " ++ "but mp_pg_count is 0\n"); ++ if ((dmmp_pg_count != 0) && (dmmp_pgs == NULL)) ++ FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is NULL " ++ "but mp_pg_count is not 0\n"); ++ if (dmmp_pg_count == 0) ++ FAIL(rc, out, "dmmp_path_group_array_get(): " ++ "Got 0 path group\n"); ++ ++ PASS("dmmp_path_group_array_get(): Got %" PRIu32 " path groups\n", ++ dmmp_pg_count); ++ ++ for (i = 0; i < dmmp_pg_count; ++i) { ++ PASS("dmmp_path_group_id_get(): %" PRIu32 "\n", ++ dmmp_path_group_id_get(dmmp_pgs[i])); ++ PASS("dmmp_path_group_priority_get(): %" PRIu32 "\n", ++ dmmp_path_group_priority_get(dmmp_pgs[i])); ++ PASS("dmmp_path_group_status_get(): %" PRIu32 " -- %s\n", ++ dmmp_path_group_status_get(dmmp_pgs[i]), ++ dmmp_path_group_status_str ++ (dmmp_path_group_status_get(dmmp_pgs[i]))); ++ PASS("dmmp_path_group_selector_get(): %s\n", ++ dmmp_path_group_selector_get(dmmp_pgs[i])); ++ rc = test_paths(dmmp_pgs[i]); ++ if (rc != 0) ++ goto out; ++ } ++out: ++ return rc; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ struct dmmp_context *ctx = NULL; ++ struct dmmp_mpath **dmmp_mps = NULL; ++ uint32_t dmmp_mp_count = 0; ++ const char *name = NULL; ++ const char *wwid = NULL; ++ const char *kdev = NULL; ++ uint32_t i = 0; ++ int rc = EXIT_SUCCESS; ++ ++ ctx = dmmp_context_new(); ++ dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG); ++ dmmp_context_userdata_set(ctx, ctx); ++ dmmp_context_userdata_set(ctx, NULL); ++ dmmp_context_timeout_set(ctx, TMO); ++ if (dmmp_context_timeout_get(ctx) != TMO) ++ FAIL(rc, out, "dmmp_context_timeout_set(): Failed to set " ++ "timeout to %u", TMO); ++ ++ if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) ++ FAIL(rc, out, "dmmp_mpath_array_get(): rc != 0\n"); ++ if (dmmp_mp_count == 0) ++ FAIL(rc, out, "dmmp_mpath_array_get(): " ++ "Got no multipath devices\n"); ++ PASS("dmmp_mpath_array_get(): Got %" PRIu32 " mpath\n", dmmp_mp_count); ++ for (i = 0; i < dmmp_mp_count; ++i) { ++ name = dmmp_mpath_name_get(dmmp_mps[i]); ++ wwid = dmmp_mpath_wwid_get(dmmp_mps[i]); ++ kdev = dmmp_mpath_kdev_name_get(dmmp_mps[i]); ++ if ((name == NULL) ||(wwid == NULL) || (kdev == NULL)) ++ FAIL(rc, out, ++ "dmmp_mpath_array_get(): Got NULL name or wwid"); ++ PASS("dmmp_mpath_array_get(): Got mpath(%s): %s %s\n", ++ kdev, name, wwid); ++ rc = test_path_groups(dmmp_mps[i]); ++ if (rc != 0) ++ goto out; ++ } ++ dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count); ++out: ++ dmmp_context_free(ctx); ++ exit(rc); ++} diff --git a/0210-RH-fix-uninstall.patch b/0210-RH-fix-uninstall.patch new file mode 100644 index 0000000..7083ded --- /dev/null +++ b/0210-RH-fix-uninstall.patch @@ -0,0 +1,25 @@ +--- + libmpathpersist/Makefile | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +Index: multipath-tools-130222/libmpathpersist/Makefile +=================================================================== +--- multipath-tools-130222.orig/libmpathpersist/Makefile ++++ multipath-tools-130222/libmpathpersist/Makefile +@@ -33,12 +33,14 @@ install: $(LIBS) + ln -sf $(LIBS) $(DESTDIR)$(syslibdir)/$(DEVLIB) + install -m 644 mpath_persistent_reserve_in.3.gz $(DESTDIR)$(man3dir) + install -m 644 mpath_persistent_reserve_out.3.gz $(DESTDIR)$(man3dir) ++ $(INSTALL_PROGRAM) -m 644 mpath_persist.h $(DESTDIR)$(includedir) + + uninstall: + rm -f $(DESTDIR)$(syslibdir)/$(LIBS) + rm -f $(DESTDIR)$(syslibdir)/$(DEVLIB) +- rm $(DESTDIR)$(mandir)/mpath_persistent_reserve_in.3.gz +- rm $(DESTDIR)$(mandir)/mpath_persistent_reserve_out.3.gz ++ rm $(DESTDIR)$(man3dir)/mpath_persistent_reserve_in.3.gz ++ rm $(DESTDIR)$(man3dir)/mpath_persistent_reserve_out.3.gz ++ rm -f $(DESTDIR)$(includedir)/mpath_persist.h + + clean: + rm -f core *.a *.o diff --git a/0211-RH-strlen-fix.patch b/0211-RH-strlen-fix.patch new file mode 100644 index 0000000..2c12fe8 --- /dev/null +++ b/0211-RH-strlen-fix.patch @@ -0,0 +1,35 @@ +--- + multipathd/main.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -878,7 +878,8 @@ uxsock_trigger (char * str, char ** repl + (strncmp(str, "list", strlen("list")) != 0) && + (strncmp(str, "show", strlen("show")) != 0)) { + *reply = STRDUP("permission deny: need to be root"); +- *len = strlen(*reply) + 1; ++ if (*reply) ++ *len = strlen(*reply) + 1; + r = 1; + goto out; + } +@@ -887,12 +888,14 @@ uxsock_trigger (char * str, char ** repl + + if (r > 0) { + *reply = STRDUP("fail\n"); +- *len = strlen(*reply) + 1; ++ if (*reply) ++ *len = strlen(*reply) + 1; + r = 1; + } + else if (!r && *len == 0) { + *reply = STRDUP("ok\n"); +- *len = strlen(*reply) + 1; ++ if (*reply) ++ *len = strlen(*reply) + 1; + r = 0; + } + /* else if (r < 0) leave *reply alone */ diff --git a/0212-RHBZ-1431562-for-read-only.patch b/0212-RHBZ-1431562-for-read-only.patch new file mode 100644 index 0000000..b398100 --- /dev/null +++ b/0212-RHBZ-1431562-for-read-only.patch @@ -0,0 +1,56 @@ +--- + libmultipath/devmapper.c | 10 ++++++---- + libmultipath/structs.h | 1 + + multipathd/main.c | 5 +++-- + 3 files changed, 10 insertions(+), 6 deletions(-) + +Index: multipath-tools-130222/libmultipath/devmapper.c +=================================================================== +--- multipath-tools-130222.orig/libmultipath/devmapper.c ++++ multipath-tools-130222/libmultipath/devmapper.c +@@ -358,10 +358,12 @@ dm_addmap_create (struct multipath *mpp, + extern int + dm_addmap_reload (struct multipath *mpp, char *params) { + sysfs_set_max_sectors_kb(mpp, 1); +- if (dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, 0, ADDMAP_RW, SKIP_KPARTX_OFF)) +- return 1; +- if (errno != EROFS) +- return 0; ++ if (!mpp->force_readonly) { ++ if (dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, 0, ADDMAP_RW, SKIP_KPARTX_OFF)) ++ return 1; ++ if (errno != EROFS) ++ return 0; ++ } + return dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, 0, ADDMAP_RO, SKIP_KPARTX_OFF); + } + +Index: multipath-tools-130222/libmultipath/structs.h +=================================================================== +--- multipath-tools-130222.orig/libmultipath/structs.h ++++ multipath-tools-130222/libmultipath/structs.h +@@ -259,6 +259,7 @@ struct multipath { + int force_udev_reload; + int skip_kpartx; + int max_sectors_kb; ++ int force_readonly; + unsigned int dev_loss; + uid_t uid; + gid_t gid; +Index: multipath-tools-130222/multipathd/main.c +=================================================================== +--- multipath-tools-130222.orig/multipathd/main.c ++++ multipath-tools-130222/multipathd/main.c +@@ -831,9 +831,10 @@ uev_update_path (struct uevent *uev, str + pp->mpp->wait_for_udev = 2; + return 0; + } +- ++ if (ro == 1) ++ pp->mpp->force_readonly = 1; + retval = reload_map(vecs, pp->mpp, 0); +- ++ pp->mpp->force_readonly = 0; + condlog(2, "%s: map %s reloaded (retval %d)", + uev->kernel, pp->mpp->alias, retval); + } diff --git a/device-mapper-multipath.spec b/device-mapper-multipath.spec index 2453b12..a0374dd 100644 --- a/device-mapper-multipath.spec +++ b/device-mapper-multipath.spec @@ -1,7 +1,7 @@ Summary: Tools to manage multipath devices using device-mapper Name: device-mapper-multipath Version: 0.4.9 -Release: 85%{?dist} +Release: 86%{?dist} License: GPL+ Group: System Environment/Base URL: http://christophe.varoqui.free.fr/ @@ -180,21 +180,64 @@ Patch0169: 0169-UPBZ-1353357-json-output.patch Patch0170: 0170-UPBZ-1352925-fix-typo.patch Patch0171: 0171-UPBZ-1356651-allow-zero-size.patch Patch0172: 0172-RHBZ-1350931-no-active-add.patch +Patch0173: 0173-RH-update-man-page.patch +Patch0174: 0174-RHBZ-1362396-modprobe.patch +Patch0175: 0175-RHBZ-1357382-ordering.patch +Patch0176: 0176-RHBZ-1363830-fix-rename.patch +Patch0177: 0177-libmultipath-correctly-initialize-pp-sg_id.patch +Patch0178: 0178-libmultipath-add-rbd-discovery.patch +Patch0179: 0179-multipath-tools-add-checker-callout-to-repair-path.patch +Patch0180: 0180-multipath-tools-Add-rbd-checker.patch +Patch0181: 0181-multipath-tools-Add-rbd-to-the-hwtable.patch +Patch0182: 0182-multipath-tools-check-for-initialized-checker-before.patch +Patch0183: 0183-multipathd-Don-t-call-repair-on-blacklisted-path.patch +Patch0184: 0184-rbd-fix-sync-repair-support.patch +Patch0185: 0185-rbd-check-for-nonshared-clients.patch +Patch0186: 0186-rbd-check-for-exclusive-lock-enabled.patch +Patch0187: 0187-rbd-fixup-log-messages.patch +Patch0188: 0188-RHBZ-1368501-dont-exit.patch +Patch0189: 0189-RHBZ-1368211-remove-retries.patch +Patch0190: 0190-RHBZ-1380602-rbd-lock-on-read.patch +Patch0191: 0191-RHBZ-1169168-disable-changed-paths.patch +Patch0192: 0192-RHBZ-1362409-infinibox-config.patch +Patch0194: 0194-RHBZ-1351964-kpartx-recurse.patch +Patch0195: 0195-RHBZ-1359510-no-daemon-msg.patch +Patch0196: 0196-RHBZ-1239173-dont-set-flag.patch +Patch0197: 0197-RHBZ-1394059-max-sectors-kb.patch +Patch0198: 0198-RHBZ-1372032-detect-path-checker.patch +Patch0199: 0199-RHBZ-1279355-3pardata-config.patch +Patch0200: 0200-RHBZ-1402092-orphan-status.patch +Patch0201: 0201-RHBZ-1403552-silence-warning.patch +Patch0202: 0202-RHBZ-1362120-skip-prio.patch +Patch0203: 0203-RHBZ-1363718-add-msgs.patch +Patch0204: 0204-RHBZ-1406226-nimble-config.patch +Patch0205: 0205-RHBZ-1416569-reset-stats.patch +Patch0206: 0206-RHBZ-1239173-pt2-no-paths.patch +Patch0207: 0207-UP-add-libmpathcmd.patch +Patch0208: 0208-UPBZ-1430097-multipathd-IPC-changes.patch +Patch0209: 0209-UPBZ-1430097-multipath-C-API.patch +Patch0210: 0210-RH-fix-uninstall.patch +Patch0211: 0211-RH-strlen-fix.patch +Patch0212: 0212-RHBZ-1431562-for-read-only.patch # runtime Requires: %{name}-libs = %{version}-%{release} Requires: kpartx = %{version}-%{release} -Requires: device-mapper >= 1.02.96 +Requires: device-mapper >= 7:1.02.96 Requires: initscripts Requires(post): systemd-units systemd-sysv chkconfig Requires(preun): systemd-units Requires(postun): systemd-units # build/setup -BuildRequires: libaio-devel, device-mapper-devel >= 1.02.82-2 +BuildRequires: libaio-devel, device-mapper-devel >= 1.02.89 BuildRequires: libselinux-devel, libsepol-devel BuildRequires: readline-devel, ncurses-devel BuildRequires: systemd-units, systemd-devel +BuildRequires: json-c-devel, perl, pkgconfig +%ifarch x86_64 +BuildRequires: librados2-devel +%endif BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) @@ -212,9 +255,20 @@ Group: System Environment/Libraries %description libs The %{name}-libs provides the path checker -and prioritizer modules. It also contains the multipath shared library, +and prioritizer modules. It also contains the libmpathpersist and +libmpathcmd shared libraries, as well as multipath's internal library, libmultipath. +%package devel +Summary: Development libraries and headers for %{name} +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} +Requires: %{name}-libs = %{version}-%{release} + +%description devel +This package contains the files need to develop applications that use +device-mapper-multipath's lbmpathpersist and libmpathcmd libraries. + %if 0%{?fedora} < 23 %package sysvinit Summary: SysV init script for device-mapper-multipath @@ -232,6 +286,27 @@ Group: System Environment/Base %description -n kpartx kpartx manages partition creation and removal for device-mapper devices. +%package -n libdmmp +Summary: device-mapper-multipath C API library +Group: System Environment/Libraries +Requires: json-c +Requires: %{name} = %{version}-%{release} +Requires: %{name}-libs = %{version}-%{release} + +%description -n libdmmp +This package contains the shared library for the device-mapper-multipath +C API library. + +%package -n libdmmp-devel +Summary: device-mapper-multipath C API library headers +Group: Development/Libraries +Requires: pkgconfig +Requires: libdmmp = %{version}-%{release} + +%description -n libdmmp-devel +This package contains the files needed to develop applications that use +device-mapper-multipath's libdmmp C API library + %prep %setup -q -n multipath-tools-130222 %patch0001 -p1 @@ -405,12 +480,52 @@ kpartx manages partition creation and removal for device-mapper devices. %patch0170 -p1 %patch0171 -p1 %patch0172 -p1 +%patch0173 -p1 +%patch0174 -p1 +%patch0175 -p1 +%patch0176 -p1 +%patch0177 -p1 +%patch0178 -p1 +%patch0179 -p1 +%patch0180 -p1 +%patch0181 -p1 +%patch0182 -p1 +%patch0183 -p1 +%patch0184 -p1 +%patch0185 -p1 +%patch0186 -p1 +%patch0187 -p1 +%patch0188 -p1 +%patch0189 -p1 +%patch0190 -p1 +%patch0191 -p1 +%patch0192 -p1 +%patch0194 -p1 +%patch0195 -p1 +%patch0196 -p1 +%patch0197 -p1 +%patch0198 -p1 +%patch0199 -p1 +%patch0200 -p1 +%patch0201 -p1 +%patch0202 -p1 +%patch0203 -p1 +%patch0204 -p1 +%patch0205 -p1 +%patch0206 -p1 +%patch0207 -p1 +%patch0208 -p1 +%patch0209 -p1 +%patch0210 -p1 +%patch0211 -p1 +%patch0212 -p1 cp %{SOURCE1} . %build %define _sbindir /usr/sbin %define _libdir /usr/%{_lib} %define _libmpathdir %{_libdir}/multipath +%define _pkgconfdir %{_libdir}/pkgconfig make %{?_smp_mflags} LIB=%{_lib} %install @@ -422,7 +537,9 @@ make install \ syslibdir=%{_libdir} \ libdir=%{_libmpathdir} \ rcdir=%{_initrddir} \ - unitdir=%{_unitdir} + unitdir=%{_unitdir} \ + includedir=%{_includedir} \ + pkgconfdir=%{_pkgconfdir} # tree fix up install -d %{buildroot}/etc/multipath @@ -464,8 +581,6 @@ fi %{_sbindir}/mpathconf %{_sbindir}/mpathpersist %{_unitdir}/multipathd.service -%{_mandir}/man3/mpath_persistent_reserve_in.3.gz -%{_mandir}/man3/mpath_persistent_reserve_out.3.gz %{_mandir}/man5/multipath.conf.5.gz %{_mandir}/man8/multipath.8.gz %{_mandir}/man8/multipathd.8.gz @@ -486,8 +601,8 @@ fi %license COPYING %{_libdir}/libmultipath.so %{_libdir}/libmultipath.so.* -%{_libdir}/libmpathpersist.so %{_libdir}/libmpathpersist.so.* +%{_libdir}/libmpathcmd.so.* %dir %{_libmpathdir} %{_libmpathdir}/* @@ -495,6 +610,16 @@ fi %postun libs -p /sbin/ldconfig +%files devel +%defattr(-,root,root,-) +%doc AUTHOR COPYING +%{_libdir}/libmpathpersist.so +%{_libdir}/libmpathcmd.so +%{_includedir}/mpath_cmd.h +%{_includedir}/mpath_persist.h +%{_mandir}/man3/mpath_persistent_reserve_in.3.gz +%{_mandir}/man3/mpath_persistent_reserve_out.3.gz + %if 0%{?fedora} < 23 %files sysvinit %{_initrddir}/multipathd @@ -505,7 +630,114 @@ fi %{_sbindir}/kpartx %{_mandir}/man8/kpartx.8.gz +%files -n libdmmp +%defattr(-,root,root,-) +%doc AUTHOR COPYING +%{_libdir}/libdmmp.so.* + +%post -n libdmmp -p /sbin/ldconfig + +%postun -n libdmmp -p /sbin/ldconfig + +%files -n libdmmp-devel +%defattr(-,root,root,-) +%doc AUTHOR COPYING +%{_libdir}/libdmmp.so +%dir %{_includedir}/libdmmp +%{_includedir}/libdmmp/* +%{_mandir}/man3/dmmp_* +%{_mandir}/man3/libdmmp.h.3.gz +%{_pkgconfdir}/libdmmp.pc + %changelog +* Fri Apr 7 2017 Benjamin Marzinski 0.4.9-86 +- Modify 0136-RHBZ-1304687-wait-for-map-add.patch + * switch to missing_uev_wait_timeout to stop waiting for uev +- Refresh 0137-RHBZ-1280524-clear-chkr-msg.patch +- Refresh 0150-RHBZ-1253913-fix-startup-msg.patch +- Refresh 0154-UPBZ-1291406-disable-reinstate.patch +- Refresh 0156-UPBZ-1313324-dont-fail-discovery.patch +- Refresh 0161-RHBZ-1311659-no-kpartx.patch +- Refresh 0167-RHBZ-1335176-fix-show-cmds.patch +- Add 0173-RH-update-man-page.patch +- Add 0174-RHBZ-1362396-modprobe.patch + * make starting the multipathd service modprobe dm-multipath in the + sysvinit scripts +- Add 0175-RHBZ-1357382-ordering.patch + * force multipathd.service to start after systemd-udev-trigger.service +- Add 0176-RHBZ-1363830-fix-rename.patch + * initialized a variable to make dm_rename not fail randomly +- Add 0177-libmultipath-correctly-initialize-pp-sg_id.patch + * This and all the following patches add the rbd patch checker +- Add 0178-libmultipath-add-rbd-discovery.patch +- Add 0179-multipath-tools-add-checker-callout-to-repair-path.patch +- Add 0180-multipath-tools-Add-rbd-checker.patch +- Add 0181-multipath-tools-Add-rbd-to-the-hwtable.patch +- Add 0182-multipath-tools-check-for-initialized-checker-before.patch +- Add 0183-multipathd-Don-t-call-repair-on-blacklisted-path.patch +- Add 0184-rbd-fix-sync-repair-support.patch +- Add 0185-rbd-check-for-nonshared-clients.patch +- Add 0186-rbd-check-for-exclusive-lock-enabled.patch +- Add 0187-rbd-fixup-log-messages.patch +- Add 0188-RHBZ-1368501-dont-exit.patch + * make multipathd not exit if it encounters recoverable errors on startup +- Add 0189-RHBZ-1368211-remove-retries.patch + * add "remove_retries" multipath.conf parameter to make multiple attempts + to remove a multipath device if it is busy. +- Add 0190-RHBZ-1380602-rbd-lock-on-read.patch + * pass lock_on_read when remapping image +- Add 0191-RHBZ-1169168-disable-changed-paths.patch + * add "disabled_changed_wwids" multipath.conf parameter to disable + paths whose wwid changes +- Add 0192-RHBZ-1362409-infinibox-config.patch +- Add 0194-RHBZ-1351964-kpartx-recurse.patch + * fix recursion on corrupt dos partitions +- Add 0195-RHBZ-1359510-no-daemon-msg.patch + * print a messages when multipathd isn't running +- Add 0196-RHBZ-1239173-dont-set-flag.patch + * don't set reload flag on reloads when you gain your first + valid path +- Add 0197-RHBZ-1394059-max-sectors-kb.patch + * add "max_sectors_kb" multipath.conf parameter to set max_sectors_kb + on a multipath device and all its path devices +- Add 0198-RHBZ-1372032-detect-path-checker.patch + * add "detect_checker" multipath.conf parameter to detect ALUA arrays + and set the path checker to TUR +- Add 0199-RHBZ-1279355-3pardata-config.patch +- Add 0200-RHBZ-1402092-orphan-status.patch + * clear status on orphan paths +- Add 0201-RHBZ-1403552-silence-warning.patch +- Add 0202-RHBZ-1362120-skip-prio.patch + * don't run prio on failed paths +- Add 0203-RHBZ-1363718-add-msgs.patch +- Add 0204-RHBZ-1406226-nimble-config.patch +- Add 0205-RHBZ-1416569-reset-stats.patch + * add "reset maps stats" and "reset map stats" multipathd + interactive commands to reset the stats tracked by multipathd +- Add 0206-RHBZ-1239173-pt2-no-paths.patch + * make multipath correctly disable scanning and rules running when + it gets a uevent and there are not valid paths. +- Add 0207-UP-add-libmpathcmd.patch + * New shared library, libmpathcmd, that sends and receives messages from + multipathd. device-mapper-multipath now uses this library internally. +- Add 0208-UPBZ-1430097-multipathd-IPC-changes.patch + * validation that modifying commands are coming from root. +- Add 0209-UPBZ-1430097-multipath-C-API.patch + * New shared library. libdmmp, that presents the information from multipathd + in a structured manner to make it easier for callers to use +- Add 0210-RH-fix-uninstall.patch + * Minor compilation fixes +- Add 0211-RH-strlen-fix.patch + * checks that variables are not NULL before passing them to strlen +- Add 0212-RHBZ-1431562-for-read-only.patch +- Make 3 new subpackages + * device-mapper-multipath-devel, libdmmp, and libdmmp-devel. libmpathcmd + and libmpathprio are in device-mapper-multipath-libs and + device-mapper-multipath-devel. libdmmp is in its own subpackages +- Move libmpathprio devel files to device-mapper-multipath-devel +- Added BuildRequires on librados2-devel + + * Fri Feb 10 2017 Fedora Release Engineering - 0.4.9-85 - Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild