diff --git a/SOURCES/0150-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch b/SOURCES/0150-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch new file mode 100644 index 0000000..207a743 --- /dev/null +++ b/SOURCES/0150-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 3 Jun 2025 23:35:03 -0400 +Subject: [PATCH] libmpathpersist: retry commands on other paths in + mpath_prout_common + +mpath_prout_common() will only try sending the prout command to one +path. If that fails, it will give up. There are a number of cases where +it is reasonable to assume that sending the command down another path +could succeed. Keep trying other available paths in these cases. + +Do do this, this patch adds a new error code, MPATH_PR_RETRYABLE_ERROR. +libmpathpersist will not return this error to users. It will change it +to MPATH_PR_OTHER if it fails trying on all the paths. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmpathpersist/mpath_persist.c | 13 +++++++++---- + libmpathpersist/mpath_persist.h | 3 +++ + libmpathpersist/mpath_pr_ioctl.c | 25 ++++++++++++++----------- + 3 files changed, 26 insertions(+), 15 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 29b64937..af5d3070 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -130,7 +130,7 @@ mpath_prin_activepath (struct multipath *mpp, int rq_servact, + } + } + } +- return ret; ++ return (ret == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : ret; + } + + static vector curmp; +@@ -568,7 +568,7 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + } + + pthread_attr_destroy(&attr); +- return (status); ++ return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; + } + + void * mpath_prout_pthread_fn(void *p) +@@ -588,6 +588,7 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + int i,j, ret; + struct pathgroup *pgp = NULL; + struct path *pp = NULL; ++ bool found = false; + + vector_foreach_slot (mpp->pg, pgp, j){ + vector_foreach_slot (pgp->paths, pp, i){ +@@ -598,12 +599,16 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + } + + condlog (3, "%s: sending pr out command to %s", mpp->wwid, pp->dev); ++ found = true; + ret = send_prout_activepath(pp->dev, rq_servact, + rq_scope, rq_type, + paramp, noisy); +- return ret ; ++ if (ret != MPATH_PR_RETRYABLE_ERROR) ++ return ret; + } + } ++ if (found) ++ return MPATH_PR_OTHER; + condlog (0, "%s: no path available", mpp->wwid); + return MPATH_PR_DMMP_ERROR; + } +@@ -839,7 +844,7 @@ out1: + free (pamp); + out: + free (pr_buff); +- return (status); ++ return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; + } + + void * mpath_alloc_prin_response(int prin_sa) +diff --git a/libmpathpersist/mpath_persist.h b/libmpathpersist/mpath_persist.h +index 9e9c0a82..8d441d04 100644 +--- a/libmpathpersist/mpath_persist.h ++++ b/libmpathpersist/mpath_persist.h +@@ -63,6 +63,9 @@ extern "C" { + #define MPATH_PR_THREAD_ERROR 14 /* pthreads error (e.g. unable to create new thread) */ + #define MPATH_PR_OTHER 15 /*other error/warning has occurred(transport + or driver error) */ ++#define MPATH_PR_RETRYABLE_ERROR 16 /* error that might be succeed ++ down another path. Internal ++ only. */ + + /* PR MASK */ + #define MPATH_F_APTPL_MASK 0x01 /* APTPL MASK*/ +diff --git a/libmpathpersist/mpath_pr_ioctl.c b/libmpathpersist/mpath_pr_ioctl.c +index 126601c3..9cd83729 100644 +--- a/libmpathpersist/mpath_pr_ioctl.c ++++ b/libmpathpersist/mpath_pr_ioctl.c +@@ -54,7 +54,7 @@ int prout_do_scsi_ioctl(char * dev, int rq_servact, int rq_scope, + fd = open(devname, O_RDONLY); + if(fd < 0){ + condlog (1, "%s: unable to open device.", dev); +- return MPATH_PR_FILE_ERROR; ++ return MPATH_PR_RETRYABLE_ERROR; + } + + unsigned char cdb[MPATH_PROUT_CMDLEN] = +@@ -125,14 +125,17 @@ retry : + goto retry; + } + +- if (((status == MPATH_PR_SENSE_NOT_READY )&& (Sensedata.ASC == 0x04)&& +- (Sensedata.ASCQ == 0x07))&& (retry > 0)) +- { +- usleep(1000); +- --retry; +- condlog(3, "%s: retrying for sense 02/04/07." +- " Remaining retries = %d", dev, retry); +- goto retry; ++ if (status == MPATH_PR_SENSE_NOT_READY) { ++ if (Sensedata.ASC == 0x04 && Sensedata.ASCQ == 0x07 && retry > 0) { ++ usleep(1000); ++ --retry; ++ condlog(3, ++ "%s: retrying for sense 02/04/07." ++ " Remaining retries = %d", ++ dev, retry); ++ goto retry; ++ } else ++ status = MPATH_PR_RETRYABLE_ERROR; + } + + close(fd); +@@ -344,7 +347,7 @@ int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int + fd = open(devname, O_RDONLY); + if(fd < 0){ + condlog(0, "%s: Unable to open device ", dev); +- return MPATH_PR_FILE_ERROR; ++ return MPATH_PR_RETRYABLE_ERROR; + } + + if (mpath_mx_alloc_len) +@@ -490,7 +493,7 @@ int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, + case DID_OK : + break; + default : +- return MPATH_PR_OTHER; ++ return MPATH_PR_RETRYABLE_ERROR; + } + switch(io_hdr.driver_status) + { diff --git a/SOURCES/0151-libmpathpersist-check-released-key-against-the-reser.patch b/SOURCES/0151-libmpathpersist-check-released-key-against-the-reser.patch new file mode 100644 index 0000000..b1ba65d --- /dev/null +++ b/SOURCES/0151-libmpathpersist-check-released-key-against-the-reser.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 3 Jun 2025 23:35:04 -0400 +Subject: [PATCH] libmpathpersist: check released key against the reservation + key + +According to the SCSI Spec, if a persistent reservation RELEASE is +issued using the correct key for the I_T_L Nexus that it is issued on +but the reservation is held by a different key, the command should +return Success and do nothing. When libmpathpersist tried to release a +reservation that was held by a different key, it ended up using the +failback code designed to release a reservation held by a failed path to +release it anyways. This means that any node could release a scsi +persistent reservation held by another node. Fix this to follow the SCSI +Spec. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmpathpersist/mpath_persist.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index af5d3070..9d42704d 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -745,7 +745,13 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + condlog (2, "%s: Path holding reservation is released.", mpp->wwid); + return MPATH_PR_SUCCESS; + } +- condlog (2, "%s: Path holding reservation is not avialable.", mpp->wwid); ++ if (!get_be64(mpp->reservation_key) || ++ memcmp(&mpp->reservation_key, resp.prin_descriptor.prin_readresv.key, 8)) { ++ condlog(2, "%s: Releasing key not holding reservation.", mpp->wwid); ++ return MPATH_PR_SUCCESS; ++ } ++ ++ condlog (2, "%s: Path holding reservation is not available.", mpp->wwid); + + pr_buff = mpath_alloc_prin_response(MPATH_PRIN_RFSTAT_SA); + if (!pr_buff){ diff --git a/SOURCES/0152-multipathd-remove-thread-from-mpath_pr_event_handle.patch b/SOURCES/0152-multipathd-remove-thread-from-mpath_pr_event_handle.patch new file mode 100644 index 0000000..2492189 --- /dev/null +++ b/SOURCES/0152-multipathd-remove-thread-from-mpath_pr_event_handle.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 16 Jun 2025 19:25:50 -0400 +Subject: [PATCH] multipathd: remove thread from mpath_pr_event_handle + +mpath_pr_event_handle() creates a separate thread to do the persistent +reservation work, but it doesn't take any advantage of the work being +done in another thread. Merge mpath_pr_event_handle() and +mpath_pr_event_handler_fn() into a single function with no separate +thread. + +Signed-off-by: Benjamin Marzinski +--- + multipathd/main.c | 49 ++++++++++++----------------------------------- + multipathd/main.h | 3 --- + 2 files changed, 12 insertions(+), 40 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index 4119ad79..3f4b8ff6 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -90,6 +90,8 @@ + #define CMDSIZE 160 + #define MSG_SIZE 32 + ++static void mpath_pr_event_handle(struct path *pp); ++ + #define LOG_MSG(lvl, pp) \ + do { \ + if (pp->mpp && checker_selected(&pp->checker) && \ +@@ -3722,17 +3724,24 @@ main (int argc, char *argv[]) + return (child(NULL)); + } + +-void * mpath_pr_event_handler_fn (void * pathp ) ++static void mpath_pr_event_handle(struct path *pp) + { + struct multipath * mpp; + unsigned int i; + int ret, isFound; +- struct path * pp = (struct path *)pathp; + struct prout_param_descriptor *param; + struct prin_resp *resp; + +- rcu_register_thread(); + mpp = pp->mpp; ++ if (pp->bus != SYSFS_BUS_SCSI) { ++ mpp->prflag = PRFLAG_UNSET; ++ return; ++ } ++ ++ if (!get_be64(mpp->reservation_key)) { ++ mpp->prflag = PRFLAG_UNSET; ++ return; ++ } + + resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); + if (!resp){ +@@ -3799,38 +3808,4 @@ void * mpath_pr_event_handler_fn (void * pathp ) + out: + if (resp) + free(resp); +- rcu_unregister_thread(); +- return NULL; +-} +- +-int mpath_pr_event_handle(struct path *pp) +-{ +- pthread_t thread; +- int rc; +- pthread_attr_t attr; +- struct multipath * mpp; +- +- if (pp->bus != SYSFS_BUS_SCSI) +- goto no_pr; +- +- mpp = pp->mpp; +- +- if (!get_be64(mpp->reservation_key)) +- goto no_pr; +- +- pthread_attr_init(&attr); +- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); +- +- rc = pthread_create(&thread, NULL , mpath_pr_event_handler_fn, pp); +- if (rc) { +- condlog(0, "%s: ERROR; return code from pthread_create() is %d", pp->dev, rc); +- return -1; +- } +- pthread_attr_destroy(&attr); +- rc = pthread_join(thread, NULL); +- return 0; +- +-no_pr: +- pp->mpp->prflag = PRFLAG_UNSET; +- return 0; + } +diff --git a/multipathd/main.h b/multipathd/main.h +index 4138faa4..897051e0 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -52,10 +52,7 @@ void dumpHex(const char * , int len, int no_ascii); + int prout_do_scsi_ioctl(char * , int rq_servact, int rq_scope, + unsigned int rq_type, + struct prout_param_descriptor *param, int noisy); +-int mpath_pr_event_handle(struct path *pp); +-void * mpath_pr_event_handler_fn (void * ); + int update_map_pr(struct multipath *mpp); +-void * mpath_pr_event_handler_fn (void * pathp ); + void handle_signals(bool); + int __setup_multipath (struct vectors * vecs, struct multipath * mpp, + int reset); diff --git a/SOURCES/0153-libmpathpersist-remove-uneeded-wrapper-function.patch b/SOURCES/0153-libmpathpersist-remove-uneeded-wrapper-function.patch new file mode 100644 index 0000000..304e582 --- /dev/null +++ b/SOURCES/0153-libmpathpersist-remove-uneeded-wrapper-function.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 16 Jun 2025 19:39:04 -0400 +Subject: [PATCH] libmpathpersist: remove uneeded wrapper function. + +mpath_send_prin_activepath() just calls prin_do_scsi_ioctl() with exactly +the same arguments that it is passed, and returns the same return. remove +it. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 15 ++------------- + libmpathpersist/mpathpr.h | 1 - + 2 files changed, 2 insertions(+), 14 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 9d42704d..a7d07963 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -118,8 +118,8 @@ mpath_prin_activepath (struct multipath *mpp, int rq_servact, + + condlog(3, "%s: sending pr in command to %s ", + mpp->wwid, pp->dev); +- ret = mpath_send_prin_activepath(pp->dev, rq_servact, +- resp, noisy); ++ ret = prin_do_scsi_ioctl(pp->dev, rq_servact, resp, ++ noisy); + switch(ret) + { + case MPATH_PR_SUCCESS: +@@ -418,17 +418,6 @@ get_mpvec (vector curmp, vector pathvec, char * refwwid) + return MPATH_PR_SUCCESS ; + } + +-int mpath_send_prin_activepath (char * dev, int rq_servact, +- struct prin_resp * resp, int noisy) +-{ +- +- int rc; +- +- rc = prin_do_scsi_ioctl(dev, rq_servact, resp, noisy); +- +- return (rc); +-} +- + int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 5ea8cd6f..0292bc50 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -31,7 +31,6 @@ int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int + int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); + void * _mpath_pr_update (void *arg); +-int mpath_send_prin_activepath (char * dev, int rq_servact, struct prin_resp * resp, int noisy); + int get_mpvec (vector curmp, vector pathvec, char * refwwid); + void * mpath_prout_pthread_fn(void *p); + void dumpHex(const char* , int len, int no_ascii); diff --git a/SOURCES/0154-libmpathpersist-reduce-log-level-for-persistent-rese.patch b/SOURCES/0154-libmpathpersist-reduce-log-level-for-persistent-rese.patch new file mode 100644 index 0000000..489692e --- /dev/null +++ b/SOURCES/0154-libmpathpersist-reduce-log-level-for-persistent-rese.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 16 Jun 2025 20:08:08 -0400 +Subject: [PATCH] libmpathpersist: reduce log level for persistent reservation + checking + +Move logging of minor expected behavior to INFO level. Modify the log +level of some messages by whether or not mpp->prflag changed values. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 26 ++++++++++++++------------ + 1 file changed, 14 insertions(+), 12 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index a7d07963..1ce43de1 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -109,7 +109,7 @@ mpath_prin_activepath (struct multipath *mpp, int rq_servact, + vector_foreach_slot (pgp->paths, pp, i){ + if (!((pp->state == PATH_UP) || + (pp->state == PATH_GHOST))){ +- condlog(2, "%s: %s not available. Skip.", ++ condlog(3, "%s: %s not available. Skip.", + mpp->wwid, pp->dev); + condlog(3, "%s: status = %d.", + mpp->wwid, pp->state); +@@ -875,13 +875,13 @@ int update_map_pr(struct multipath *mpp) + struct prin_resp *resp; + unsigned int i; + int ret = MPATH_PR_OTHER, isFound; ++ bool was_set = (mpp->prflag == PRFLAG_SET); + + if (!get_be64(mpp->reservation_key)) + { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ + mpp->prflag = PRFLAG_UNSET; +- condlog(4, "%s: reservation_key not set in multipath.conf", +- mpp->alias); ++ condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); + return MPATH_PR_SUCCESS; + } + +@@ -893,7 +893,7 @@ int update_map_pr(struct multipath *mpp) + } + if (count_active_paths(mpp) == 0) + { +- condlog(0,"%s: No available paths to check pr status", ++ condlog(2, "%s: No available paths to check pr status", + mpp->alias); + goto out; + } +@@ -910,22 +910,24 @@ int update_map_pr(struct multipath *mpp) + + if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) + { +- condlog(3,"%s: No key found. Device may not be registered. ", mpp->alias); ++ condlog(was_set ? 1 : 3, "%s: No key found. Device may not be registered. ", mpp->alias); + goto out; + } + +- condlog(2, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, ++ condlog(3, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, + get_be64(mpp->reservation_key)); + + isFound =0; + for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) + { +- condlog(2, "%s: PR IN READKEYS[%d] reservation key:", mpp->alias, i); +- dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i*8], 8 , 1); ++ if (libmp_verbosity >= 3) { ++ condlog(3, "%s: PR IN READKEYS[%d] reservation key:", ++ mpp->alias, i); ++ dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8, 1); ++ } + +- if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i*8], 8)) +- { +- condlog(2, "%s: reservation key found in pr in readkeys response", mpp->alias); ++ if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8)) { ++ condlog(3, "%s: reservation key found in pr in readkeys response", mpp->alias); + isFound =1; + } + } +@@ -933,7 +935,7 @@ int update_map_pr(struct multipath *mpp) + if (isFound) + { + mpp->prflag = PRFLAG_SET; +- condlog(2, "%s: prflag flag set.", mpp->alias ); ++ condlog(was_set ? 3 : 2, "%s: prflag flag set.", mpp->alias ); + } + + out: diff --git a/SOURCES/0155-libmpathpersist-remove-pointless-update_map_pr-ret-v.patch b/SOURCES/0155-libmpathpersist-remove-pointless-update_map_pr-ret-v.patch new file mode 100644 index 0000000..67ba607 --- /dev/null +++ b/SOURCES/0155-libmpathpersist-remove-pointless-update_map_pr-ret-v.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 23 Jun 2025 13:43:45 -0400 +Subject: [PATCH] libmpathpersist: remove pointless update_map_pr ret value + code + +Don't set ret to MPATH_PR_SUCCESS, after the code just made sure that +it was already set to MPATH_PR_SUCCESS. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 1ce43de1..d9439292 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -906,8 +906,6 @@ int update_map_pr(struct multipath *mpp) + goto out; + } + +- ret = MPATH_PR_SUCCESS; +- + if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) + { + condlog(was_set ? 1 : 3, "%s: No key found. Device may not be registered. ", mpp->alias); diff --git a/SOURCES/0156-multipathd-use-update_map_pr-in-mpath_pr_event_handl.patch b/SOURCES/0156-multipathd-use-update_map_pr-in-mpath_pr_event_handl.patch new file mode 100644 index 0000000..5537e05 --- /dev/null +++ b/SOURCES/0156-multipathd-use-update_map_pr-in-mpath_pr_event_handl.patch @@ -0,0 +1,227 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 18 Jun 2025 19:22:07 -0400 +Subject: [PATCH] multipathd: use update_map_pr in mpath_pr_event_handle + +Clean up the duplicate code in mpath_pr_event_handle() and +update_map_pr() by making update_map_pr() take an optional path device +to use for its check, instead of checking all path devices and make +mpath_pr_event_handle() call update_map_pr() to do its checking. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 21 ++++++---- + libmpathpersist/mpathpr.h | 2 +- + multipathd/main.c | 69 +++++---------------------------- + multipathd/main.h | 1 - + 4 files changed, 25 insertions(+), 68 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index d9439292..2c35cb3e 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -869,7 +869,7 @@ void * mpath_alloc_prin_response(int prin_sa) + return ptr; + } + +-int update_map_pr(struct multipath *mpp) ++int update_map_pr(struct multipath *mpp, struct path *pp) + { + int noisy=0; + struct prin_resp *resp; +@@ -882,7 +882,7 @@ int update_map_pr(struct multipath *mpp) + /* Nothing to do. Assuming pr mgmt feature is disabled*/ + mpp->prflag = PRFLAG_UNSET; + condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); +- return MPATH_PR_SUCCESS; ++ return MPATH_PR_SKIP; + } + + resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); +@@ -891,14 +891,18 @@ int update_map_pr(struct multipath *mpp) + condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); + return MPATH_PR_OTHER; + } +- if (count_active_paths(mpp) == 0) +- { ++ if (!pp && count_active_paths(mpp) == 0) { + condlog(2, "%s: No available paths to check pr status", + mpp->alias); + goto out; + } + mpp->prflag = PRFLAG_UNSET; +- ret = mpath_prin_activepath(mpp, MPATH_PRIN_RKEY_SA, resp, noisy); ++ if (pp) ++ ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, ++ noisy); ++ else ++ ret = mpath_prin_activepath(mpp, MPATH_PRIN_RKEY_SA, resp, ++ noisy); + + if (ret != MPATH_PR_SUCCESS ) + { +@@ -933,8 +937,11 @@ int update_map_pr(struct multipath *mpp) + if (isFound) + { + mpp->prflag = PRFLAG_SET; +- condlog(was_set ? 3 : 2, "%s: prflag flag set.", mpp->alias ); +- } ++ condlog(was_set ? 3 : 2, "%s: key found. prflag set.", ++ mpp->alias); ++ } else ++ condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", ++ mpp->alias); + + out: + free(resp); +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 0292bc50..dd84e6d9 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -48,6 +48,6 @@ int update_prflag(char *mapname, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); + #define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) + void * mpath_alloc_prin_response(int prin_sa); +-int update_map_pr(struct multipath *mpp); ++int update_map_pr(struct multipath *mpp, struct path *pp); + + #endif +diff --git a/multipathd/main.c b/multipathd/main.c +index 3f4b8ff6..3b99bbe2 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -68,6 +68,7 @@ + + #include "mpath_cmd.h" + #include "mpath_persist.h" ++#include "mpathpr.h" + + #include "prioritizers/alua_rtpg.h" + +@@ -643,7 +644,7 @@ fail: + sync_map_state(mpp); + + if (mpp->prflag != PRFLAG_SET) +- update_map_pr(mpp); ++ update_map_pr(mpp, NULL); + if (mpp->prflag == PRFLAG_SET) + pr_register_active_paths(mpp); + +@@ -1268,7 +1269,7 @@ rescan: + + if (retries >= 0) { + if (start_waiter) +- update_map_pr(mpp); ++ update_map_pr(mpp, NULL); + if (mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) + pr_register_active_paths(mpp); + condlog(2, "%s [%s]: path added to devmap %s", +@@ -2883,7 +2884,7 @@ configure (struct vectors * vecs) + vector_foreach_slot(mpvec, mpp, i){ + if (remember_wwid(mpp->wwid) == 1) + trigger_paths_udev_change(mpp, true); +- update_map_pr(mpp); ++ update_map_pr(mpp, NULL); + if (mpp->prflag == PRFLAG_SET) + pr_register_active_paths(mpp); + } +@@ -3726,70 +3727,24 @@ main (int argc, char *argv[]) + + static void mpath_pr_event_handle(struct path *pp) + { +- struct multipath * mpp; +- unsigned int i; +- int ret, isFound; ++ struct multipath *mpp = pp->mpp; ++ int ret; + struct prout_param_descriptor *param; +- struct prin_resp *resp; + +- mpp = pp->mpp; + if (pp->bus != SYSFS_BUS_SCSI) { + mpp->prflag = PRFLAG_UNSET; + return; + } + +- if (!get_be64(mpp->reservation_key)) { +- mpp->prflag = PRFLAG_UNSET; ++ if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) + return; +- } +- +- resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); +- if (!resp){ +- condlog(0,"%s Alloc failed for prin response", pp->dev); +- goto out; +- } +- +- mpp->prflag = PRFLAG_UNSET; +- ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, 0); +- if (ret != MPATH_PR_SUCCESS ) +- { +- condlog(0,"%s : pr in read keys service action failed. Error=%d", pp->dev, ret); +- goto out; +- } +- +- condlog(3, " event pr=%d addlen=%d",resp->prin_descriptor.prin_readkeys.prgeneration, +- resp->prin_descriptor.prin_readkeys.additional_length ); +- +- if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) +- { +- condlog(1, "%s: No key found. Device may not be registered.", pp->dev); +- goto out; +- } +- condlog(2, "Multipath reservation_key: 0x%" PRIx64 " ", +- get_be64(mpp->reservation_key)); + +- isFound =0; +- for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) +- { +- condlog(2, "PR IN READKEYS[%d] reservation key:",i); +- dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i*8], 8 , -1); +- if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i*8], 8)) +- { +- condlog(2, "%s: pr key found in prin readkeys response", mpp->alias); +- isFound =1; +- break; +- } +- } +- if (!isFound) +- { +- condlog(0, "%s: Either device not registered or ", pp->dev); +- condlog(0, "host is not authorised for registration. Skip path"); +- goto out; +- } ++ if (mpp->prflag != PRFLAG_SET) ++ return; + + param = (struct prout_param_descriptor *)MALLOC(sizeof(struct prout_param_descriptor)); + if (!param) +- goto out; ++ return; + + param->sa_flags = mpp->sa_flags; + memcpy(param->sa_key, &mpp->reservation_key, 8); +@@ -3802,10 +3757,6 @@ static void mpath_pr_event_handle(struct path *pp) + { + condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret); + } +- mpp->prflag = PRFLAG_SET; + + free(param); +-out: +- if (resp) +- free(resp); + } +diff --git a/multipathd/main.h b/multipathd/main.h +index 897051e0..6d2ae72a 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -52,7 +52,6 @@ void dumpHex(const char * , int len, int no_ascii); + int prout_do_scsi_ioctl(char * , int rq_servact, int rq_scope, + unsigned int rq_type, + struct prout_param_descriptor *param, int noisy); +-int update_map_pr(struct multipath *mpp); + void handle_signals(bool); + int __setup_multipath (struct vectors * vecs, struct multipath * mpp, + int reset); diff --git a/SOURCES/0157-libmpathpersist-limit-changing-prflag-in-update_map_.patch b/SOURCES/0157-libmpathpersist-limit-changing-prflag-in-update_map_.patch new file mode 100644 index 0000000..9201d4b --- /dev/null +++ b/SOURCES/0157-libmpathpersist-limit-changing-prflag-in-update_map_.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 20 Jun 2025 14:31:13 -0400 +Subject: [PATCH] libmpathpersist: limit changing prflag in update_map_pr + +Do not unset mpp->prflag in update_map_pr() unless the device doesn't +support persistent reservations or it successfully reads the registered +keys and discovers that the device's key is not there. Also, nothing in +the libmpathpersist ever returns MPATH_PR_SENSE_INVALID_OP. It instead +returns MPATH_PR_ILLEGAL_REQ if the device doesn't support persistent +reservations, so check for that instead. + +Also, do not even check for the registered keys in update_map_pr() if +mpp->prflag is unset. Otherwise, multipathd will set mpp->prflag if the +key was unregistered (and thus prflag was unset) while a path is down +(and thus could not have its key unregistered) and then that path comes +back up. I should note that the above issue can only occur if multipath +is defining PR keys in /etc/multipath.conf, instead of the prkeys file. + +If a device has no registered keys, it must unset prflag, since that +means it's no longer registered. Possibly its registration was removed +by a CLEAR or PREEMPT action. But if a device has a registered key but +it is supposed to be unregistered, it should remain unregistered, since +that key is likely one that libmpathpersist could not unregister at the +time. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 2c35cb3e..65a81faf 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -123,7 +123,7 @@ mpath_prin_activepath (struct multipath *mpp, int rq_servact, + switch(ret) + { + case MPATH_PR_SUCCESS: +- case MPATH_PR_SENSE_INVALID_OP: ++ case MPATH_PR_ILLEGAL_REQ: + return ret; + default: + continue; +@@ -877,6 +877,10 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + int ret = MPATH_PR_OTHER, isFound; + bool was_set = (mpp->prflag == PRFLAG_SET); + ++ /* If pr is explicitly unset, it must be manually set */ ++ if (mpp->prflag == PRFLAG_UNSET) ++ return MPATH_PR_SKIP; ++ + if (!get_be64(mpp->reservation_key)) + { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ +@@ -896,7 +900,6 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + mpp->alias); + goto out; + } +- mpp->prflag = PRFLAG_UNSET; + if (pp) + ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, + noisy); +@@ -906,9 +909,12 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + + if (ret != MPATH_PR_SUCCESS ) + { ++ if (ret == MPATH_PR_ILLEGAL_REQ) ++ mpp->prflag = PRFLAG_UNSET; + condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); + goto out; + } ++ mpp->prflag = PRFLAG_UNSET; + + if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) + { diff --git a/SOURCES/0158-multipathd-Don-t-call-update_map_pr-unnecessarily.patch b/SOURCES/0158-multipathd-Don-t-call-update_map_pr-unnecessarily.patch new file mode 100644 index 0000000..8ee029d --- /dev/null +++ b/SOURCES/0158-multipathd-Don-t-call-update_map_pr-unnecessarily.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 20 Jun 2025 16:26:01 -0400 +Subject: [PATCH] multipathd: Don't call update_map_pr unnecessarily + +None of the calls to update_map_pr() outside of mpath_pr_event_handle() +add any benefit. When update_map_pr() is called without a path, it tries +to read the pr keys list on each usable path until it succeeds, and then +checks the keys to see if they include the configured key. + +In all cases where update_map_pr() is called outside of +mpath_pr_event_handle(), after it is called, pr_register_active_paths() +is called if a matching key was found. pr_register_active_paths() calls +mpath_pr_event_handle() on each usable path, which calls update_map_pr() +with a path, so it only checks that path. If a matching key is found, it +registers a key on the current path. The result is that after +pr_register_active_paths() is called, update_map_pr() will be called for +each usable path, just like update_map_pr() did. So calling +update_map_pr() first doesn't change the results for multipathd, it just +adds duplicate work, so remove those calls. + +Signed-off-by: Benjamin Marzinski +--- + multipathd/main.c | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index 3b99bbe2..bd87851d 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -555,6 +555,8 @@ pr_register_active_paths(struct multipath *mpp) + + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { ++ if (mpp->prflag == PRFLAG_UNSET) ++ return; + if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) + mpath_pr_event_handle(pp); + } +@@ -643,10 +645,7 @@ fail: + + sync_map_state(mpp); + +- if (mpp->prflag != PRFLAG_SET) +- update_map_pr(mpp, NULL); +- if (mpp->prflag == PRFLAG_SET) +- pr_register_active_paths(mpp); ++ pr_register_active_paths(mpp); + + if (VECTOR_SIZE(offline_paths) != 0) + handle_orphaned_offline_paths(offline_paths); +@@ -1268,10 +1267,9 @@ rescan: + sync_map_state(mpp); + + if (retries >= 0) { +- if (start_waiter) +- update_map_pr(mpp, NULL); +- if (mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) +- pr_register_active_paths(mpp); ++ if ((mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) || ++ start_waiter) ++ pr_register_active_paths(mpp); + condlog(2, "%s [%s]: path added to devmap %s", + pp->dev, pp->dev_t, mpp->alias); + return 0; +@@ -2884,9 +2882,7 @@ configure (struct vectors * vecs) + vector_foreach_slot(mpvec, mpp, i){ + if (remember_wwid(mpp->wwid) == 1) + trigger_paths_udev_change(mpp, true); +- update_map_pr(mpp, NULL); +- if (mpp->prflag == PRFLAG_SET) +- pr_register_active_paths(mpp); ++ pr_register_active_paths(mpp); + } + + /* diff --git a/SOURCES/0159-libmpathpersist-remove-useless-function-send_prout_a.patch b/SOURCES/0159-libmpathpersist-remove-useless-function-send_prout_a.patch new file mode 100644 index 0000000..a34c81d --- /dev/null +++ b/SOURCES/0159-libmpathpersist-remove-useless-function-send_prout_a.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 23 Jun 2025 19:06:12 -0400 +Subject: [PATCH] libmpathpersist: remove useless function + send_prout_activepath + +send_prout_activepath() creates a single separate thread that just calls +prout_do_scsi_ioctl() and it doesn't take any advantage of the work +being done in another thread. Remove the function and call +prout_do_scsi_ioctl() directly. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 38 ++------------------------------- + libmpathpersist/mpathpr.h | 2 -- + 2 files changed, 2 insertions(+), 38 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 65a81faf..346abe71 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -589,9 +589,8 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + + condlog (3, "%s: sending pr out command to %s", mpp->wwid, pp->dev); + found = true; +- ret = send_prout_activepath(pp->dev, rq_servact, +- rq_scope, rq_type, +- paramp, noisy); ++ ret = prout_do_scsi_ioctl(pp->dev, rq_servact, rq_scope, ++ rq_type, paramp, noisy); + if (ret != MPATH_PR_RETRYABLE_ERROR) + return ret; + } +@@ -602,39 +601,6 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + return MPATH_PR_DMMP_ERROR; + } + +-int send_prout_activepath(char * dev, int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) +-{ +- struct prout_param param; +- param.rq_servact = rq_servact; +- param.rq_scope = rq_scope; +- param.rq_type = rq_type; +- param.paramp = paramp; +- param.noisy = noisy; +- param.status = -1; +- +- pthread_t thread; +- pthread_attr_t attr; +- int rc; +- +- memset(&thread, 0, sizeof(thread)); +- strlcpy(param.dev, dev, FILE_NAME_SIZE); +- /* Initialize and set thread joinable attribute */ +- pthread_attr_init(&attr); +- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); +- +- rc = pthread_create(&thread, &attr, mpath_prout_pthread_fn, (void *)(¶m)); +- if (rc){ +- condlog (3, "%s: failed to create thread %d", dev, rc); +- return MPATH_PR_THREAD_ERROR; +- } +- /* Free attribute and wait for the other threads */ +- pthread_attr_destroy(&attr); +- rc = pthread_join(thread, NULL); +- +- return (param.status); +-} +- + int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index dd84e6d9..0d1075e5 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -41,8 +41,6 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); +-int send_prout_activepath(char * dev, int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + + int update_prflag(char *mapname, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); diff --git a/SOURCES/0160-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch b/SOURCES/0160-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch new file mode 100644 index 0000000..ed622f7 --- /dev/null +++ b/SOURCES/0160-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Mon, 5 May 2025 12:47:47 +0200 +Subject: [PATCH] libmpathpersist: fix memory leak in mpath_prout_rel() + +Found by Fedora's static analysis [1]. + +[1] https://openscanhub.fedoraproject.org/task/51915/log/device-mapper-multipath-0.11.1-1.fc43/scan-results.html#def44 + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 346abe71..59159737 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -614,10 +614,10 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + int count = 0; + int status = MPATH_PR_SUCCESS; + struct prin_resp resp; +- struct prout_param_descriptor *pamp; ++ struct prout_param_descriptor *pamp = NULL; + struct prin_resp *pr_buff; + int length; +- struct transportid *pptr; ++ struct transportid *pptr = NULL; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -730,7 +730,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + pamp = (struct prout_param_descriptor *)malloc (length); + if (!pamp){ + condlog (0, "%s: failed to alloc pr out parameter.", mpp->wwid); +- goto out1; ++ goto out; + } + + memset(pamp, 0, length); +@@ -740,6 +740,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + condlog (0, "%s: failed to alloc pr out transportid.", mpp->wwid); + goto out1; + } ++ pptr = pamp->trnptid_list[0]; + + if (get_be64(mpp->reservation_key)){ + memcpy (pamp->key, &mpp->reservation_key, 8); +@@ -751,11 +752,10 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + + if (status) { + condlog(0, "%s: failed to send CLEAR_SA", mpp->wwid); +- goto out1; ++ goto out2; + } + + pamp->num_transportid = 1; +- pptr=pamp->trnptid_list[0]; + + for (i = 0; i < num; i++){ + if (get_be64(mpp->reservation_key) && +@@ -799,7 +799,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + status = mpath_prout_reg(mpp, MPATH_PROUT_REG_SA, rq_scope, rq_type, pamp, noisy); + } + +- ++out2: + free(pptr); + out1: + free (pamp); diff --git a/SOURCES/0161-limpathpersist-redesign-failed-release-workaround.patch b/SOURCES/0161-limpathpersist-redesign-failed-release-workaround.patch new file mode 100644 index 0000000..1c74724 --- /dev/null +++ b/SOURCES/0161-limpathpersist-redesign-failed-release-workaround.patch @@ -0,0 +1,307 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 23 Jun 2025 20:40:48 -0400 +Subject: [PATCH] limpathpersist: redesign failed release workaround + +The workaround for releasing a reservation held by a failed path +(clearing all persistent reservation data and then reregistering all the +keys) has several problems. It requires devices that support the READ +FULL STATUS command and are capable of specifying TransportIDs with +REGISTRATION commands (SIP_C), neither of which are mandatory to support +SCSI Persistent Reservations. Non SIP_C devices will be left with no +registered keys. Also, not all cleared keys are registered, just the +ones going to the Target Port receiving the REGISTRATION command. To +reregister all the keys, the code would have to also use the "All Target +Ports" flag to register keys on different Target Ports, but this could +end up registering keys on paths they aren't supposed to be on (for +instance if one of the registered keys was only there because the path +was down and it couldn't be released). + +The redesign avoids these issues by only using mandatory Persistent +Reservation commands, without extra optional parameters or flags, and +only effects the keys of the multipath device it is being issued on. +The new workaround is: +1. Suspend the multipath device to prevent I/O +2. Preempt the key to move the reservation to an available path. This + also removes the registered keys from every path except the path + issuing the PREEMPT command. Since the device is suspended, not I/O + can go to these unregisted paths and fail. +3. Release the reservation on the path that now holds it. +4. Resume the device (since it no longer matters that most of the paths + no longer have a registered key) +5. Reregister the keys on all the paths. + +If steps 3 or 4 fail, the code will attempt to reregister the keys, and +then attempt (or possibly re-attempt) the resume. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 171 ++++++++++++++---------------- + libmpathpersist/mpathpr.h | 2 - + libmultipath/libmultipath.version | 1 + + 3 files changed, 78 insertions(+), 96 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 59159737..7e60aa47 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -97,6 +97,11 @@ int libmpathpersist_exit(void) + return 0; + } + ++static int mpath_prout_common(struct multipath *mpp,int rq_servact, ++ int rq_scope, unsigned int rq_type, ++ struct prout_param_descriptor* paramp, int noisy, ++ struct path **pptr); ++ + int + mpath_prin_activepath (struct multipath *mpp, int rq_servact, + struct prin_resp * resp, int noisy) +@@ -283,6 +288,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + conf = get_multipath_config(); + select_reservation_key(conf, mpp); + select_all_tg_pt(conf, mpp); ++ select_skip_kpartx(conf, mpp); + put_multipath_config(conf); + + memcpy(&prkey, paramp->sa_key, 8); +@@ -319,7 +325,8 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + case MPATH_PROUT_PREE_SA : + case MPATH_PROUT_PREE_AB_SA : + case MPATH_PROUT_CLEAR_SA: +- ret = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); ++ ret = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, ++ paramp, noisy, NULL); + break; + case MPATH_PROUT_REL_SA: + ret = mpath_prout_rel(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); +@@ -571,8 +578,10 @@ void * mpath_prout_pthread_fn(void *p) + pthread_exit(NULL); + } + +-int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor* paramp, int noisy) ++static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, ++ unsigned int rq_type, ++ struct prout_param_descriptor* paramp, int noisy, ++ struct path **pptr) + { + int i,j, ret; + struct pathgroup *pgp = NULL; +@@ -591,6 +600,8 @@ int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + found = true; + ret = prout_do_scsi_ioctl(pp->dev, rq_servact, rq_scope, + rq_type, paramp, noisy); ++ if (ret == MPATH_PR_SUCCESS && pptr) ++ *pptr = pp; + if (ret != MPATH_PR_RETRYABLE_ERROR) + return ret; + } +@@ -610,14 +621,12 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + struct path *pp = NULL; + int active_pathcount = 0; + pthread_attr_t attr; +- int rc, found = 0; ++ int rc; + int count = 0; + int status = MPATH_PR_SUCCESS; + struct prin_resp resp; +- struct prout_param_descriptor *pamp = NULL; +- struct prin_resp *pr_buff; +- int length; +- struct transportid *pptr = NULL; ++ uint16_t udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; ++ bool did_resume = false; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -707,104 +716,78 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + } + + condlog (2, "%s: Path holding reservation is not available.", mpp->wwid); ++ /* ++ * Cannot free the reservation because the path that is holding it ++ * is not usable. Workaround this by: ++ * 1. Suspending the device ++ * 2. Preempting the reservation to move it to a usable path ++ * (this removes the registered keys on all paths except the ++ * preempting one. Since the device is suspended, no IO can ++ * go to these unregistered paths and fail). ++ * 3. Releasing the reservation on the path that now holds it. ++ * 4. Resuming the device (since it no longer matters that most of ++ * that paths no longer have a registered key) ++ * 5. Reregistering keys on all the paths ++ */ + +- pr_buff = mpath_alloc_prin_response(MPATH_PRIN_RFSTAT_SA); +- if (!pr_buff){ +- condlog (0, "%s: failed to alloc pr in response buffer.", mpp->wwid); ++ if (!dm_simplecmd_noflush(DM_DEVICE_SUSPEND, mpp->alias, 0)) { ++ condlog(0, "%s: release: failed to suspend dm device.", mpp->wwid); + return MPATH_PR_OTHER; + } + +- status = mpath_prin_activepath (mpp, MPATH_PRIN_RFSTAT_SA, pr_buff, noisy); +- +- if (status != MPATH_PR_SUCCESS){ +- condlog (0, "%s: pr in read full status command failed.", mpp->wwid); +- goto out; +- } +- +- num = pr_buff->prin_descriptor.prin_readfd.number_of_descriptor; +- if (0 == num){ +- goto out; +- } +- length = sizeof (struct prout_param_descriptor) + (sizeof (struct transportid *)); +- +- pamp = (struct prout_param_descriptor *)malloc (length); +- if (!pamp){ +- condlog (0, "%s: failed to alloc pr out parameter.", mpp->wwid); +- goto out; +- } +- +- memset(pamp, 0, length); +- +- pamp->trnptid_list[0] = (struct transportid *) malloc (sizeof (struct transportid)); +- if (!pamp->trnptid_list[0]){ +- condlog (0, "%s: failed to alloc pr out transportid.", mpp->wwid); +- goto out1; ++ memset(paramp, 0, sizeof(*paramp)); ++ memcpy(paramp->key, &mpp->reservation_key, 8); ++ memcpy(paramp->sa_key, &mpp->reservation_key, 8); ++ status = mpath_prout_common(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, ++ paramp, noisy, &pp); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog(0, "%s: release: pr preempt command failed.", mpp->wwid); ++ goto fail_resume; + } +- pptr = pamp->trnptid_list[0]; + +- if (get_be64(mpp->reservation_key)){ +- memcpy (pamp->key, &mpp->reservation_key, 8); +- condlog (3, "%s: reservation key set.", mpp->wwid); ++ memset(paramp, 0, sizeof(*paramp)); ++ memcpy(paramp->key, &mpp->reservation_key, 8); ++ status = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REL_SA, rq_scope, ++ rq_type, paramp, noisy); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog(0, "%s: release on alternate path failed.", mpp->wwid); ++ goto out_reregister; + } + +- status = mpath_prout_common (mpp, MPATH_PROUT_CLEAR_SA, +- rq_scope, rq_type, pamp, noisy); +- +- if (status) { +- condlog(0, "%s: failed to send CLEAR_SA", mpp->wwid); +- goto out2; ++ if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { ++ condlog(0, "%s release: failed to resume dm device.", mpp->wwid); ++ /* ++ * leave status set to MPATH_PR_SUCCESS, we will have another ++ * chance to resume the device. ++ */ ++ goto out_reregister; + } ++ did_resume = true; + +- pamp->num_transportid = 1; +- +- for (i = 0; i < num; i++){ +- if (get_be64(mpp->reservation_key) && +- memcmp(pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, +- &mpp->reservation_key, 8)){ +- /*register with tarnsport id*/ +- memset(pamp, 0, length); +- pamp->trnptid_list[0] = pptr; +- memset (pamp->trnptid_list[0], 0, sizeof (struct transportid)); +- memcpy (pamp->sa_key, +- pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, 8); +- pamp->sa_flags = MPATH_F_SPEC_I_PT_MASK; +- pamp->num_transportid = 1; +- +- memcpy (pamp->trnptid_list[0], +- &pr_buff->prin_descriptor.prin_readfd.descriptors[i]->trnptid, +- sizeof (struct transportid)); +- status = mpath_prout_common (mpp, MPATH_PROUT_REG_SA, 0, rq_type, +- pamp, noisy); +- +- pamp->sa_flags = 0; +- memcpy (pamp->key, pr_buff->prin_descriptor.prin_readfd.descriptors[i]->key, 8); +- memset (pamp->sa_key, 0, 8); +- pamp->num_transportid = 0; +- status = mpath_prout_common (mpp, MPATH_PROUT_REG_SA, 0, rq_type, +- pamp, noisy); +- } +- else +- { +- if (get_be64(mpp->reservation_key)) +- found = 1; +- } +- +- +- } ++out_reregister: ++ memset(paramp, 0, sizeof(*paramp)); ++ memcpy(paramp->sa_key, &mpp->reservation_key, 8); ++ rc = mpath_prout_reg(mpp, MPATH_PROUT_REG_IGN_SA, rq_scope, rq_type, ++ paramp, noisy); ++ if (rc != MPATH_PR_SUCCESS) ++ condlog(0, "%s: release: failed to reregister paths.", mpp->wwid); + +- if (found){ +- memset (pamp, 0, length); +- memcpy (pamp->sa_key, &mpp->reservation_key, 8); +- memset (pamp->key, 0, 8); +- status = mpath_prout_reg(mpp, MPATH_PROUT_REG_SA, rq_scope, rq_type, pamp, noisy); ++ /* ++ * If we failed releasing the reservation or resuming earlier ++ * try resuming now. Otherwise, return with the reregistering status ++ * This means we will report failure, even though the resevation ++ * has been released, since the keys were not reregistered. ++ */ ++ if (did_resume) ++ return rc; ++ else if (status == MPATH_PR_SUCCESS) ++ status = rc; ++fail_resume: ++ if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { ++ condlog(0, "%s: release: failed to resume dm device.", mpp->wwid); ++ if (status == MPATH_PR_SUCCESS) ++ status = MPATH_PR_OTHER; + } +- +-out2: +- free(pptr); +-out1: +- free (pamp); +-out: +- free (pr_buff); + return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; + } + +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 0d1075e5..85eac1c7 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -37,8 +37,6 @@ void dumpHex(const char* , int len, int no_ascii); + + int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); +-int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + +diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version +index 46907278..5340957c 100644 +--- a/libmultipath/libmultipath.version ++++ b/libmultipath/libmultipath.version +@@ -301,6 +301,7 @@ global: + LIBMULTIPATH_9.1.2 { + global: + cleanup_mutex; ++ select_skip_kpartx; + } LIBMULTIPATH_9.1.1; + + LIBMULTIPATH_9.1.3 { diff --git a/SOURCES/0162-libmpathpersist-fail-the-release-if-all-threads-fail.patch b/SOURCES/0162-libmpathpersist-fail-the-release-if-all-threads-fail.patch new file mode 100644 index 0000000..82c4fa7 --- /dev/null +++ b/SOURCES/0162-libmpathpersist-fail-the-release-if-all-threads-fail.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 8 Jul 2025 16:23:13 -0400 +Subject: [PATCH] libmpathpersist: fail the release if all threads fail + +If none of the threads succeeds in issuing the release, simply return +failure, instead of trying the workaround. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 7e60aa47..04a08b76 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -627,6 +627,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + struct prin_resp resp; + uint16_t udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; + bool did_resume = false; ++ bool all_threads_failed; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -688,15 +689,22 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + } + } + ++ all_threads_failed = true; + for (i = 0; i < count; i++){ + /* check thread status here and return the status */ + +- if (thread[i].param.status == MPATH_PR_RESERV_CONFLICT) ++ if (thread[i].param.status == MPATH_PR_SUCCESS) ++ all_threads_failed = false; ++ else if (thread[i].param.status == MPATH_PR_RESERV_CONFLICT) + status = MPATH_PR_RESERV_CONFLICT; +- else if (status == MPATH_PR_SUCCESS +- && thread[i].param.status != MPATH_PR_RESERV_CONFLICT) ++ else if (status == MPATH_PR_SUCCESS) + status = thread[i].param.status; + } ++ if (all_threads_failed) { ++ condlog(0, "%s: all threads failed to release reservation.", ++ mpp->wwid); ++ return status; ++ } + + status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); + if (status != MPATH_PR_SUCCESS){ diff --git a/SOURCES/0163-limpathpersist-Handle-changing-key-corner-case.patch b/SOURCES/0163-limpathpersist-Handle-changing-key-corner-case.patch new file mode 100644 index 0000000..51db209 --- /dev/null +++ b/SOURCES/0163-limpathpersist-Handle-changing-key-corner-case.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 27 Jun 2025 18:17:40 -0400 +Subject: [PATCH] limpathpersist: Handle changing key corner case + +When you change the reservation key of a registered multipath device, +some of paths might be down or even deleted since you originally +registered the key. libmpathpersist won't be able to change these +registrations. This can be a problem if the path that is down is holding +the reservation. Other nodes will assume that the reservation is now +under your new key, when it is still under the old one. If they try to +preempt the reservation, they will succeed. But they will just +unregister all the paths with the new key. They won't take over the +reservation, and if the path holding it comes back up, it will still be +able to write to the device. + +To avoid this, after libmpathpersist changes the key of a registered +device, it now checks to see if its old key is still holding the +reservation. If it is, limpathpersist preempts the reservation. +Currently this only works on REGISTER commands, not REGISTER AND IGNORE +ones. A future patch will add that capability. + +I should note that this relies on the fact that libmpathpersist has +never worked correctly if two nodes use the same reservation key for the +same device. If another node used the same key, it could be the one +holding the reservation, and preempting the reservation would deregister +it. However, multipathd has always relied on the fact that two nodes +will not use the same registration key to know if it is safe to register +a key on a path that was just added or just came back up. If there is +already a registered key matching the configured key, multipathd assumes +that the device has not been preempted, and registers the key on the +path. If there are no keys matching the configured key, multipathd +assumes that the device has been preempted, and won't register a key. +Again, if another node shared the same key, multipathd could registered +paths on a node that had been preempted. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 58 ++++++++++++++++++++++++++++++++- + 1 file changed, 57 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 04a08b76..c09fbbe7 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -425,6 +425,60 @@ get_mpvec (vector curmp, vector pathvec, char * refwwid) + return MPATH_PR_SUCCESS ; + } + ++/* ++ * If you are changing the key registered to a device, and that device is ++ * holding the reservation on a path that couldn't get its key updated, ++ * either because it is down or no longer part of the multipath device, ++ * you need to preempt the reservation to a usable path with the new key ++ */ ++void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, ++ int noisy) ++{ ++ uint8_t zero[8] = {0}; ++ struct prin_resp resp = {0}; ++ int rq_scope; ++ unsigned int rq_type; ++ struct prout_param_descriptor paramp = {0}; ++ int status; ++ ++ /* ++ * If you previously didn't have a key registered or you didn't ++ * switch to a different key, there's no need to preempt. Also, you ++ * can't preempt if you no longer have a registered key ++ */ ++ if (memcmp(key, zero, 8) == 0 || memcmp(sa_key, zero, 8) == 0 || ++ memcmp(key, sa_key, 8) == 0) ++ return; ++ ++ status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog(0, "%s: register: pr in read reservation command failed.", mpp->wwid); ++ return; ++ } ++ /* If there is no reservation, there's nothing to preempt */ ++ if (!resp.prin_descriptor.prin_readresv.additional_length) ++ return; ++ /* ++ * If there reservation is not held by the old key, you don't ++ * want to preempt it ++ */ ++ if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) != 0) ++ return; ++ /* Assume this key is being held by an inaccessable path on this ++ * node. libmpathpersist has never worked if multiple nodes share ++ * the same reservation key for a device ++ */ ++ rq_type = resp.prin_descriptor.prin_readresv.scope_type & MPATH_PR_TYPE_MASK; ++ rq_scope = (resp.prin_descriptor.prin_readresv.scope_type & MPATH_PR_SCOPE_MASK) >> 4; ++ memcpy(paramp.key, sa_key, 8); ++ memcpy(paramp.sa_key, key, 8); ++ status = mpath_prout_common(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, ++ ¶mp, noisy, NULL); ++ if (status != MPATH_PR_SUCCESS) ++ condlog(0, "%s: register: pr preempt command failed.", ++ mpp->wwid); ++} ++ + int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { +@@ -564,6 +618,8 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + } + + pthread_attr_destroy(&attr); ++ if (status == MPATH_PR_SUCCESS) ++ preempt_missing_path(mpp, paramp->key, paramp->sa_key, noisy); + return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; + } + +@@ -624,7 +680,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + int rc; + int count = 0; + int status = MPATH_PR_SUCCESS; +- struct prin_resp resp; ++ struct prin_resp resp = {0}; + uint16_t udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; + bool did_resume = false; + bool all_threads_failed; diff --git a/SOURCES/0164-libmpathpersist-fix-command-keyword-ordering.patch b/SOURCES/0164-libmpathpersist-fix-command-keyword-ordering.patch new file mode 100644 index 0000000..d32555b --- /dev/null +++ b/SOURCES/0164-libmpathpersist-fix-command-keyword-ordering.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 29 Nov 2022 22:56:48 -0600 +Subject: [PATCH] libmpathpersist: fix command keyword ordering + +When libmpathpersist was communicating with multipathd, it wasn't using +the correct keyword order for the commands, as specified in the CLI +commands reference. Since commit f812466f, multipathd requires commands +to be ordered correctly. Fix the ordering. + +Fixes: f812466f ("multipathd: more robust command parsing") +Reported-by: miaoguanqin +Cc: lixiaokeng +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmpathpersist/mpath_updatepr.c | 23 ++++++++++++----------- + 1 file changed, 12 insertions(+), 11 deletions(-) + +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index 0aca28eb..af2c5549 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -18,7 +18,7 @@ + #include "mpathpr.h" + + +-static int do_update_pr(char *alias, char *arg) ++static int do_update_pr(char *alias, char *cmd, char *key) + { + int fd; + char str[256]; +@@ -31,7 +31,10 @@ static int do_update_pr(char *alias, char *arg) + return -1; + } + +- snprintf(str,sizeof(str),"map %s %s", alias, arg); ++ if (key) ++ snprintf(str,sizeof(str),"%s map %s key %s", cmd, alias, key); ++ else ++ snprintf(str,sizeof(str),"%s map %s", cmd, alias); + condlog (2, "%s: pr message=%s", alias, str); + if (send_packet(fd, str) != 0) { + condlog(2, "%s: message=%s send error=%d", alias, str, errno); +@@ -56,18 +59,16 @@ static int do_update_pr(char *alias, char *arg) + } + + int update_prflag(char *mapname, int set) { +- return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus"); ++ return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus", ++ NULL); + } + + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags) { + char str[256]; +- char *flagstr = ""; + +- if (sa_flags & MPATH_F_APTPL_MASK) +- flagstr = ":aptpl"; +- if (prkey) +- sprintf(str, "setprkey key %" PRIx64 "%s", prkey, flagstr); +- else +- sprintf(str, "unsetprkey"); +- return do_update_pr(mapname, str); ++ if (!prkey) ++ return do_update_pr(mapname, "unsetprkey", NULL); ++ sprintf(str, "%" PRIx64 "%s", prkey, ++ (sa_flags & MPATH_F_APTPL_MASK) ? ":aptpl" : ""); ++ return do_update_pr(mapname, "setprkey", str); + } diff --git a/SOURCES/0165-libmpathpersist-use-conf-timeout-for-updating-persis.patch b/SOURCES/0165-libmpathpersist-use-conf-timeout-for-updating-persis.patch new file mode 100644 index 0000000..dfdc4b0 --- /dev/null +++ b/SOURCES/0165-libmpathpersist-use-conf-timeout-for-updating-persis.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Mon, 2 Jan 2023 12:39:36 +0100 +Subject: [PATCH] libmpathpersist: use conf->timeout for updating persistent + reservations + +On systems with many LUNs, multipathd may fail to respond within the +default timeout to a "setprkey" command because the vecs lock is held +by the path checker. Honor the globally configured uxsock timeout in +libmpathpersist. + +Reported-by: boposki (github.com/opensvc/multipath-tools/pull/58) +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_updatepr.c | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index af2c5549..39dfa218 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -13,6 +13,8 @@ + #include + #include "debug.h" + #include "mpath_cmd.h" ++#include "vector.h" ++#include "config.h" + #include "uxsock.h" + #include "memory.h" + #include "mpathpr.h" +@@ -24,6 +26,12 @@ static int do_update_pr(char *alias, char *cmd, char *key) + char str[256]; + char *reply; + int ret = 0; ++ int timeout; ++ struct config *conf; ++ ++ conf = get_multipath_config(); ++ timeout = conf->uxsock_timeout; ++ put_multipath_config(conf); + + fd = mpath_connect(); + if (fd == -1) { +@@ -41,7 +49,7 @@ static int do_update_pr(char *alias, char *cmd, char *key) + mpath_disconnect(fd); + return -1; + } +- ret = recv_packet(fd, &reply, DEFAULT_REPLY_TIMEOUT); ++ ret = recv_packet(fd, &reply, timeout); + if (ret < 0) { + condlog(2, "%s: message=%s recv error=%d", alias, str, errno); + ret = -1; diff --git a/SOURCES/0166-libmapthpersist-Handle-REGISTER-AND-IGNORE-changing-.patch b/SOURCES/0166-libmapthpersist-Handle-REGISTER-AND-IGNORE-changing-.patch new file mode 100644 index 0000000..faa9893 --- /dev/null +++ b/SOURCES/0166-libmapthpersist-Handle-REGISTER-AND-IGNORE-changing-.patch @@ -0,0 +1,180 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 30 Jun 2025 20:10:59 -0400 +Subject: [PATCH] libmapthpersist: Handle REGISTER AND IGNORE changing key + corner case + +When you use REGISTER to change the reservation key of a registered +multipath device, where the path holding the reservation is down, +libmpathpersist will PREEMPT the failed path to move the reservation to +a usable path, so the reservation key will be updated. However, when you +use REGISTER AND IGNORE, you don't pass in the old key, so +libmpathpersist can't use it to check against the key holding the +reservation, which is necessary to know if it needs to PREEMPT. + +Since the SCSI spec says that devices must ignore any passed-in key on +REGISTER AND IGNORE, libmpathpersist can still use it to pass in the old +key value. But unlike with REGISTER, the command won't fail if device +isn't actually registered with the old key, so libmpathpersist needs to +check that itself. To do this, it calls update_map_pr() just like +multipathd does to check that the device is registered before +registering new paths. However, libmpathpersist doesn't track the +persistent reservation state like multipathd, so it needs to ask +multipathd if it thinks the device is registered, using the "get +prstatus map " command. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 20 +++++++++++ + libmpathpersist/mpath_updatepr.c | 57 ++++++++++++++++++++++++-------- + libmpathpersist/mpathpr.h | 1 + + 3 files changed, 64 insertions(+), 14 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index c09fbbe7..324d88a2 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -271,6 +271,23 @@ int __mpath_persistent_reserve_in (int fd, int rq_servact, + resp, noisy); + } + ++/* ++ * for MPATH_PROUT_REG_IGN_SA, we use the ignored paramp->key to store the ++ * currently registered key. ++ */ ++static void set_ignored_key(struct multipath *mpp, uint8_t *key) ++{ ++ memset(key, 0, 8); ++ if (!get_be64(mpp->reservation_key)) ++ return; ++ if (get_prflag(mpp->alias) == PRFLAG_UNSET) ++ return; ++ update_map_pr(mpp, NULL); ++ if (mpp->prflag != PRFLAG_SET) ++ return; ++ memcpy(key, &mpp->reservation_key, 8); ++} ++ + static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + int rq_servact, int rq_scope, unsigned int rq_type, + struct prout_param_descriptor *paramp, int noisy) +@@ -291,6 +308,9 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + select_skip_kpartx(conf, mpp); + put_multipath_config(conf); + ++ if (rq_servact == MPATH_PROUT_REG_IGN_SA) ++ set_ignored_key(mpp, paramp->key); ++ + memcpy(&prkey, paramp->sa_key, 8); + if (mpp->prkey_source == PRKEY_SOURCE_FILE && prkey && + (rq_servact == MPATH_PROUT_REG_IGN_SA || +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index 39dfa218..1172ec8a 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -18,14 +18,13 @@ + #include "uxsock.h" + #include "memory.h" + #include "mpathpr.h" ++#include "structs.h" + + +-static int do_update_pr(char *alias, char *cmd, char *key) ++static char *do_pr(char *alias, char *str) + { + int fd; +- char str[256]; + char *reply; +- int ret = 0; + int timeout; + struct config *conf; + +@@ -36,24 +35,35 @@ static int do_update_pr(char *alias, char *cmd, char *key) + fd = mpath_connect(); + if (fd == -1) { + condlog (0, "ux socket connect error"); +- return -1; ++ return NULL; + } + +- if (key) +- snprintf(str,sizeof(str),"%s map %s key %s", cmd, alias, key); +- else +- snprintf(str,sizeof(str),"%s map %s", cmd, alias); + condlog (2, "%s: pr message=%s", alias, str); + if (send_packet(fd, str) != 0) { + condlog(2, "%s: message=%s send error=%d", alias, str, errno); + mpath_disconnect(fd); +- return -1; ++ return NULL; + } +- ret = recv_packet(fd, &reply, timeout); +- if (ret < 0) { ++ if (recv_packet(fd, &reply, timeout) < 0) + condlog(2, "%s: message=%s recv error=%d", alias, str, errno); +- ret = -1; +- } else { ++ ++ mpath_disconnect(fd); ++ return reply; ++} ++ ++static int do_update_pr(char *alias, char *cmd, char *key) ++{ ++ char str[256]; ++ char *reply = NULL; ++ int ret = -1; ++ ++ if (key) ++ snprintf(str,sizeof(str),"%s map %s key %s", cmd, alias, key); ++ else ++ snprintf(str,sizeof(str),"%s map %s", cmd, alias); ++ ++ reply = do_pr(alias, str); ++ if (reply) { + condlog (2, "%s: message=%s reply=%s", alias, str, reply); + if (reply && strncmp(reply,"ok", 2) == 0) + ret = 0; +@@ -62,10 +72,29 @@ static int do_update_pr(char *alias, char *cmd, char *key) + } + + free(reply); +- mpath_disconnect(fd); + return ret; + } + ++int get_prflag(char *mapname) { ++ char str[256]; ++ char *reply; ++ int prflag; ++ ++ snprintf(str, sizeof(str), "getprstatus map %s", mapname); ++ reply = do_pr(mapname, str); ++ if (!reply) ++ prflag = PRFLAG_UNKNOWN; ++ else if (strncmp(reply,"unset", 5) == 0) ++ prflag = PRFLAG_UNSET; ++ else if (strncmp(reply,"set", 3) == 0) ++ prflag = PRFLAG_SET; ++ else ++ prflag = PRFLAG_UNKNOWN; ++ ++ free(reply); ++ return prflag; ++} ++ + int update_prflag(char *mapname, int set) { + return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus", + NULL); +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 85eac1c7..35ef82c8 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -42,6 +42,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + + int update_prflag(char *mapname, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); ++int get_prflag(char *mapname); + #define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) + void * mpath_alloc_prin_response(int prin_sa); + int update_map_pr(struct multipath *mpp, struct path *pp); diff --git a/SOURCES/0167-libmultipath-rename-prflag_value-enums.patch b/SOURCES/0167-libmultipath-rename-prflag_value-enums.patch new file mode 100644 index 0000000..dc3aa53 --- /dev/null +++ b/SOURCES/0167-libmultipath-rename-prflag_value-enums.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 1 Jul 2025 16:45:00 -0400 +Subject: [PATCH] libmultipath: rename prflag_value enums + +These will also be used by another variable, so make their name more +generic. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 16 ++++++++-------- + libmpathpersist/mpath_updatepr.c | 8 ++++---- + libmultipath/structs.h | 8 ++++---- + multipathd/cli_handlers.c | 21 +++++++++++---------- + multipathd/main.c | 16 ++++++++-------- + 5 files changed, 35 insertions(+), 34 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 324d88a2..2935be76 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -280,10 +280,10 @@ static void set_ignored_key(struct multipath *mpp, uint8_t *key) + memset(key, 0, 8); + if (!get_be64(mpp->reservation_key)) + return; +- if (get_prflag(mpp->alias) == PRFLAG_UNSET) ++ if (get_prflag(mpp->alias) == PR_UNSET) + return; + update_map_pr(mpp, NULL); +- if (mpp->prflag != PRFLAG_SET) ++ if (mpp->prflag != PR_SET) + return; + memcpy(key, &mpp->reservation_key, 8); + } +@@ -908,16 +908,16 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + struct prin_resp *resp; + unsigned int i; + int ret = MPATH_PR_OTHER, isFound; +- bool was_set = (mpp->prflag == PRFLAG_SET); ++ bool was_set = (mpp->prflag == PR_SET); + + /* If pr is explicitly unset, it must be manually set */ +- if (mpp->prflag == PRFLAG_UNSET) ++ if (mpp->prflag == PR_UNSET) + return MPATH_PR_SKIP; + + if (!get_be64(mpp->reservation_key)) + { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ +- mpp->prflag = PRFLAG_UNSET; ++ mpp->prflag = PR_UNSET; + condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); + return MPATH_PR_SKIP; + } +@@ -943,11 +943,11 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + if (ret != MPATH_PR_SUCCESS ) + { + if (ret == MPATH_PR_ILLEGAL_REQ) +- mpp->prflag = PRFLAG_UNSET; ++ mpp->prflag = PR_UNSET; + condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); + goto out; + } +- mpp->prflag = PRFLAG_UNSET; ++ mpp->prflag = PR_UNSET; + + if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) + { +@@ -975,7 +975,7 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + + if (isFound) + { +- mpp->prflag = PRFLAG_SET; ++ mpp->prflag = PR_SET; + condlog(was_set ? 3 : 2, "%s: key found. prflag set.", + mpp->alias); + } else +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index 1172ec8a..00748264 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -83,13 +83,13 @@ int get_prflag(char *mapname) { + snprintf(str, sizeof(str), "getprstatus map %s", mapname); + reply = do_pr(mapname, str); + if (!reply) +- prflag = PRFLAG_UNKNOWN; ++ prflag = PR_UNKNOWN; + else if (strncmp(reply,"unset", 5) == 0) +- prflag = PRFLAG_UNSET; ++ prflag = PR_UNSET; + else if (strncmp(reply,"set", 3) == 0) +- prflag = PRFLAG_SET; ++ prflag = PR_SET; + else +- prflag = PRFLAG_UNKNOWN; ++ prflag = PR_UNKNOWN; + + free(reply); + return prflag; +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 0846e833..f47683c1 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -383,10 +383,10 @@ struct path { + typedef int (pgpolicyfn) (struct multipath *, vector); + + +-enum prflag_value { +- PRFLAG_UNKNOWN, +- PRFLAG_UNSET, +- PRFLAG_SET, ++enum pr_value { ++ PR_UNKNOWN, ++ PR_UNSET, ++ PR_SET, + }; + + struct multipath { +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index aca8e2df..1e735aab 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -1279,14 +1279,15 @@ cli_shutdown (void * v, char ** reply, int * len, void * data) + return 0; + } + ++static const char * const pr_str[] = { ++ [PR_UNKNOWN] = "unknown\n", ++ [PR_UNSET] = "unset\n", ++ [PR_SET] = "set\n", ++}; ++ + int + cli_getprstatus (void * v, char ** reply, int * len, void * data) + { +- static const char * const prflag_str[] = { +- [PRFLAG_UNKNOWN] = "unknown\n", +- [PRFLAG_UNSET] = "unset\n", +- [PRFLAG_SET] = "set\n", +- }; + struct multipath * mpp; + struct vectors * vecs = (struct vectors *)data; + char * param = get_keyparam(v, MAP); +@@ -1298,7 +1299,7 @@ cli_getprstatus (void * v, char ** reply, int * len, void * data) + if (!mpp) + return 1; + +- *len = asprintf(reply, "%s", prflag_str[mpp->prflag]); ++ *len = asprintf(reply, "%s", pr_str[mpp->prflag]); + if (*len < 0) + return 1; + +@@ -1321,8 +1322,8 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + if (!mpp) + return 1; + +- if (mpp->prflag != PRFLAG_SET) { +- mpp->prflag = PRFLAG_SET; ++ if (mpp->prflag != PR_SET) { ++ mpp->prflag = PR_SET; + condlog(2, "%s: prflag set", param); + } + +@@ -1344,8 +1345,8 @@ cli_unsetprstatus(void * v, char ** reply, int * len, void * data) + if (!mpp) + return 1; + +- if (mpp->prflag != PRFLAG_UNSET) { +- mpp->prflag = PRFLAG_UNSET; ++ if (mpp->prflag != PR_UNSET) { ++ mpp->prflag = PR_UNSET; + condlog(2, "%s: prflag unset", param); + } + +diff --git a/multipathd/main.c b/multipathd/main.c +index bd87851d..56050a06 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -555,7 +555,7 @@ pr_register_active_paths(struct multipath *mpp) + + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { +- if (mpp->prflag == PRFLAG_UNSET) ++ if (mpp->prflag == PR_UNSET) + return; + if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) + mpath_pr_event_handle(pp); +@@ -1139,7 +1139,7 @@ ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) + int start_waiter = 0; + int ret; + int ro; +- unsigned char prflag = PRFLAG_UNSET; ++ unsigned char prflag = PR_UNSET; + + /* + * need path UID to go any further +@@ -1267,7 +1267,7 @@ rescan: + sync_map_state(mpp); + + if (retries >= 0) { +- if ((mpp->prflag == PRFLAG_SET && prflag != PRFLAG_SET) || ++ if ((mpp->prflag == PR_SET && prflag != PR_SET) || + start_waiter) + pr_register_active_paths(mpp); + condlog(2, "%s [%s]: path added to devmap %s", +@@ -2535,7 +2535,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + } + + if (newstate == PATH_UP || newstate == PATH_GHOST) { +- if (pp->mpp->prflag != PRFLAG_UNSET) { ++ if (pp->mpp->prflag != PR_UNSET) { + int prflag = pp->mpp->prflag; + /* + * Check Persistent Reservation. +@@ -2543,8 +2543,8 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + condlog(2, "%s: checking persistent " + "reservation registration", pp->dev); + mpath_pr_event_handle(pp); +- if (pp->mpp->prflag == PRFLAG_SET && +- prflag != PRFLAG_SET) ++ if (pp->mpp->prflag == PR_SET && ++ prflag != PR_SET) + pr_register_active_paths(pp->mpp); + } + } +@@ -3728,14 +3728,14 @@ static void mpath_pr_event_handle(struct path *pp) + struct prout_param_descriptor *param; + + if (pp->bus != SYSFS_BUS_SCSI) { +- mpp->prflag = PRFLAG_UNSET; ++ mpp->prflag = PR_UNSET; + return; + } + + if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) + return; + +- if (mpp->prflag != PRFLAG_SET) ++ if (mpp->prflag != PR_SET) + return; + + param = (struct prout_param_descriptor *)MALLOC(sizeof(struct prout_param_descriptor)); diff --git a/SOURCES/0168-libmpathpersist-use-a-switch-statement-for-prout-com.patch b/SOURCES/0168-libmpathpersist-use-a-switch-statement-for-prout-com.patch new file mode 100644 index 0000000..77670f6 --- /dev/null +++ b/SOURCES/0168-libmpathpersist-use-a-switch-statement-for-prout-com.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 3 Jul 2025 14:09:08 -0400 +Subject: [PATCH] libmpathpersist: use a switch statement for prout command + finalizing + +Change the code at the end of do_mpath_persistent_reserve_out() to +use a switch statement instead of multiple if statements. A future +patch will add more actions here, and a switch statement looks cleaner. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 2935be76..12888f90 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -356,15 +356,20 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + goto out1; + } + +- if ((ret == MPATH_PR_SUCCESS) && ((rq_servact == MPATH_PROUT_REG_SA) || +- (rq_servact == MPATH_PROUT_REG_IGN_SA))) ++ if (ret != MPATH_PR_SUCCESS) ++ goto out1; ++ ++ switch(rq_servact) + { ++ case MPATH_PROUT_REG_SA: ++ case MPATH_PROUT_REG_IGN_SA: + if (prkey == 0) { + update_prflag(alias, 0); + update_prkey(alias, 0); + } else + update_prflag(alias, 1); +- } else if ((ret == MPATH_PR_SUCCESS) && (rq_servact == MPATH_PROUT_CLEAR_SA)) { ++ break; ++ case MPATH_PROUT_CLEAR_SA: + update_prflag(alias, 0); + update_prkey(alias, 0); + } diff --git a/SOURCES/0169-libmpathpersist-Add-safety-check-for-preempting-on-k.patch b/SOURCES/0169-libmpathpersist-Add-safety-check-for-preempting-on-k.patch new file mode 100644 index 0000000..e24a1d5 --- /dev/null +++ b/SOURCES/0169-libmpathpersist-Add-safety-check-for-preempting-on-k.patch @@ -0,0 +1,477 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 2 Jul 2025 12:39:08 -0400 +Subject: [PATCH] libmpathpersist: Add safety check for preempting on key + change + +When a reservation key is changed from one non-zero value to another, +libmpathpersist checks if the old key is still holding the reservation, +and preempts it if it is. This was only safe if two nodes never share +the same key. If a node uses the same key as another node that is +holding the reservation, and switches keys so that it no longer matches, +it will end up preempting the reservation. This is clearly unexpected +behavior, and it can come about by a simple accident of registering a +device with the wrong key, and then immediately fixing it. + +To handle this, add code to track if a device is the reservation holder +to multipathd. multipathd now has three new commands "getprhold", +"setprhold", and "unsetprhold". These commands work like the equivalent +*prstatus commands. libmpathpersist calls setprhold on RESERVE commands +and PREEMPT commands when the preempted key is holding the reservation +and unsetprhold on RELEASE commands. Also, calling unsetprflag causes +prhold to be unset as well, so CLEAR commands and REGISTER commands with +a 0x0 service action key will also unset prhold. libmpathpersist() will +also unset prhold if it notices that the device cannot be holding a +reservation in preempt_missing_path(). + +When a new multipath device is created, its initial prhold state is +PR_UNKNOWN until it checks the current reservation, just like with +prflag. If multipathd ever finds that a device's registration has been +preempted or cleared in update_map_pr(), it unsets prhold, just like +with prflag. + +Now, before libmpathpersist preempts a reservation when changing keys +it also checks if multipathd thinks that the device is holding +the reservation. If it does not, then libmpathpersist won't preempt +the key. It will assume that another node is holding the reservation +with the same key. + +I should note that this safety check only stops a node not holding the +reservation from preempting the node holding the reservation. If the +node holding the reservation changes its key, but it fails to change the +resevation key, because that path is down or gone, it will still issue +the preempt to move the reservation to a usable path, even if another +node is using the same key. This will remove the registrations for that +other node. It also will not work correctly if multipathd stops tracking +a device for some reason. It's only a best-effort safety check. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 84 +++++++++++++++++++++++++++----- + libmpathpersist/mpath_updatepr.c | 17 ++++++- + libmpathpersist/mpathpr.h | 2 + + libmultipath/structs.h | 1 + + multipathd/cli.c | 7 ++- + multipathd/cli.h | 6 +++ + multipathd/cli_handlers.c | 54 ++++++++++++++++++++ + multipathd/cli_handlers.h | 3 ++ + multipathd/main.c | 36 +++++++++++++- + 9 files changed, 194 insertions(+), 16 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 12888f90..e47e9190 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -271,19 +271,39 @@ int __mpath_persistent_reserve_in (int fd, int rq_servact, + resp, noisy); + } + ++static int reservation_key_matches(struct multipath *mpp, uint8_t *key, ++ int noisy) ++{ ++ struct prin_resp resp = {0}; ++ int status; ++ ++ status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog(0, "%s: pr in read reservation command failed.", ++ mpp->wwid); ++ return YNU_UNDEF; ++ } ++ if (!resp.prin_descriptor.prin_readresv.additional_length) ++ return YNU_NO; ++ if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) == 0) ++ return YNU_YES; ++ return YNU_NO; ++} ++ + /* + * for MPATH_PROUT_REG_IGN_SA, we use the ignored paramp->key to store the +- * currently registered key. ++ * currently registered key for use in preempt_missing_path(), but only if ++ * the key is holding the reservation. + */ + static void set_ignored_key(struct multipath *mpp, uint8_t *key) + { + memset(key, 0, 8); + if (!get_be64(mpp->reservation_key)) + return; +- if (get_prflag(mpp->alias) == PR_UNSET) ++ if (get_prhold(mpp->alias) == PR_UNSET) + return; +- update_map_pr(mpp, NULL); +- if (mpp->prflag != PR_SET) ++ if (reservation_key_matches(mpp, (uint8_t *)&mpp->reservation_key, ++ 0) == YNU_NO) + return; + memcpy(key, &mpp->reservation_key, 8); + } +@@ -297,6 +317,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + int ret; + uint64_t prkey; + struct config *conf; ++ bool preempting_reservation = false; + + ret = mpath_get_map(curmp, pathvec, fd, &alias, &mpp); + if (ret != MPATH_PR_SUCCESS) +@@ -341,9 +362,12 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + case MPATH_PROUT_REG_IGN_SA: + ret= mpath_prout_reg(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); + break; +- case MPATH_PROUT_RES_SA : + case MPATH_PROUT_PREE_SA : + case MPATH_PROUT_PREE_AB_SA : ++ if (reservation_key_matches(mpp, paramp->sa_key, noisy) == YNU_YES) ++ preempting_reservation = true; ++ /* fallthrough */ ++ case MPATH_PROUT_RES_SA : + case MPATH_PROUT_CLEAR_SA: + ret = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, + paramp, noisy, NULL); +@@ -372,6 +396,15 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + case MPATH_PROUT_CLEAR_SA: + update_prflag(alias, 0); + update_prkey(alias, 0); ++ break; ++ case MPATH_PROUT_RES_SA: ++ case MPATH_PROUT_REL_SA: ++ update_prhold(alias, (rq_servact == MPATH_PROUT_RES_SA)); ++ break; ++ case MPATH_PROUT_PREE_SA: ++ case MPATH_PROUT_PREE_AB_SA: ++ if (preempting_reservation) ++ update_prhold(alias, 1); + } + out1: + FREE(alias); +@@ -455,6 +488,10 @@ get_mpvec (vector curmp, vector pathvec, char * refwwid) + * holding the reservation on a path that couldn't get its key updated, + * either because it is down or no longer part of the multipath device, + * you need to preempt the reservation to a usable path with the new key ++ * ++ * Also, it's possible that the reservation was preempted, and the device ++ * is being re-registered. If it appears that is the case, clear ++ * mpp->prhold in multipathd. + */ + void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, + int noisy) +@@ -467,12 +504,19 @@ void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, + int status; + + /* +- * If you previously didn't have a key registered or you didn't +- * switch to a different key, there's no need to preempt. Also, you +- * can't preempt if you no longer have a registered key ++ * If you previously didn't have a key registered, you can't ++ * be holding the reservation. Also, you can't preempt if you ++ * no longer have a registered key + */ +- if (memcmp(key, zero, 8) == 0 || memcmp(sa_key, zero, 8) == 0 || +- memcmp(key, sa_key, 8) == 0) ++ if (memcmp(key, zero, 8) == 0 || memcmp(sa_key, zero, 8) == 0) { ++ update_prhold(mpp->alias, false); ++ return; ++ } ++ /* ++ * If you didn't switch to a different key, there is no need to ++ * preempt. ++ */ ++ if (memcmp(key, sa_key, 8) == 0) + return; + + status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); +@@ -481,13 +525,29 @@ void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, + return; + } + /* If there is no reservation, there's nothing to preempt */ +- if (!resp.prin_descriptor.prin_readresv.additional_length) ++ if (!resp.prin_descriptor.prin_readresv.additional_length) { ++ update_prhold(mpp->alias, false); + return; ++ } + /* + * If there reservation is not held by the old key, you don't + * want to preempt it + */ +- if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) != 0) ++ if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) != 0) { ++ /* ++ * If reseravation key doesn't match either the old or ++ * the new key, then clear prhold. ++ */ ++ if (memcmp(sa_key, resp.prin_descriptor.prin_readresv.key, 8) != 0) ++ update_prhold(mpp->alias, false); ++ return; ++ } ++ ++ /* ++ * If multipathd doesn't think it is holding the reservation, don't ++ * preempt it ++ */ ++ if (get_prhold(mpp->alias) != PR_SET) + return; + /* Assume this key is being held by an inaccessable path on this + * node. libmpathpersist has never worked if multiple nodes share +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index 00748264..bfa6e089 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -75,12 +75,13 @@ static int do_update_pr(char *alias, char *cmd, char *key) + return ret; + } + +-int get_prflag(char *mapname) { ++ ++static int do_get_pr(char *mapname, const char *cmd) { + char str[256]; + char *reply; + int prflag; + +- snprintf(str, sizeof(str), "getprstatus map %s", mapname); ++ snprintf(str, sizeof(str), "%s map %s", cmd, mapname); + reply = do_pr(mapname, str); + if (!reply) + prflag = PR_UNKNOWN; +@@ -95,11 +96,23 @@ int get_prflag(char *mapname) { + return prflag; + } + ++int get_prflag(char *mapname) { ++ return do_get_pr(mapname, "getprstatus"); ++} ++ ++int get_prhold(char *mapname) { ++ return do_get_pr(mapname, "getprhold"); ++} ++ + int update_prflag(char *mapname, int set) { + return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus", + NULL); + } + ++int update_prhold(char *mapname, bool set) { ++ return do_update_pr(mapname, (set)? "setprhold" : "unsetprhold", NULL); ++} ++ + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags) { + char str[256]; + +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 35ef82c8..60fa0b09 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -43,6 +43,8 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + int update_prflag(char *mapname, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); + int get_prflag(char *mapname); ++int get_prhold(char *mapname); ++int update_prhold(char *mapname, bool set); + #define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) + void * mpath_alloc_prin_response(int prin_sa); + int update_map_pr(struct multipath *mpp, struct path *pp); +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index f47683c1..761fc642 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -466,6 +466,7 @@ struct multipath { + int all_tg_pt; + struct gen_multipath generic_mp; + bool fpin_must_reload; ++ int prhold; + }; + + static inline int marginal_path_check_enabled(const struct multipath *mpp) +diff --git a/multipathd/cli.c b/multipathd/cli.c +index 03ad0d64..d33b571d 100644 +--- a/multipathd/cli.c ++++ b/multipathd/cli.c +@@ -223,7 +223,9 @@ load_keys (void) + r += add_key(keys, "local", LOCAL, 0); + r += add_key(keys, "setmarginal", SETMARGINAL, 0); + r += add_key(keys, "unsetmarginal", UNSETMARGINAL, 0); +- ++ r += add_key(keys, "getprhold", GETPRHOLD, 0); ++ r += add_key(keys, "setprhold", SETPRHOLD, 0); ++ r += add_key(keys, "unsetprhold", UNSETPRHOLD, 0); + + if (r) { + free_keys(keys); +@@ -574,6 +576,9 @@ cli_init (void) { + add_handler(GETPRKEY+MAP, NULL); + add_handler(SETPRKEY+MAP+KEY, NULL); + add_handler(UNSETPRKEY+MAP, NULL); ++ add_handler(GETPRHOLD+MAP, NULL); ++ add_handler(SETPRHOLD+MAP, NULL); ++ add_handler(UNSETPRHOLD+MAP, NULL); + add_handler(FORCEQ+DAEMON, NULL); + add_handler(RESTOREQ+DAEMON, NULL); + add_handler(SETMARGINAL+PATH, NULL); +diff --git a/multipathd/cli.h b/multipathd/cli.h +index fdfb9aed..4d12f8fd 100644 +--- a/multipathd/cli.h ++++ b/multipathd/cli.h +@@ -47,6 +47,9 @@ enum { + __LOCAL, + __SETMARGINAL, + __UNSETMARGINAL, ++ __GETPRHOLD, ++ __SETPRHOLD, ++ __UNSETPRHOLD, + }; + + #define LIST (1 << __LIST) +@@ -93,6 +96,9 @@ enum { + #define LOCAL (1ULL << __LOCAL) + #define SETMARGINAL (1ULL << __SETMARGINAL) + #define UNSETMARGINAL (1ULL << __UNSETMARGINAL) ++#define GETPRHOLD (1ULL << __GETPRHOLD) ++#define SETPRHOLD (1ULL << __SETPRHOLD) ++#define UNSETPRHOLD (1ULL << __UNSETPRHOLD) + + #define INITIAL_REPLY_LEN 1200 + +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index 1e735aab..f6353b61 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -1349,6 +1349,10 @@ cli_unsetprstatus(void * v, char ** reply, int * len, void * data) + mpp->prflag = PR_UNSET; + condlog(2, "%s: prflag unset", param); + } ++ if (mpp->prhold != PR_UNSET) { ++ mpp->prhold = PR_UNSET; ++ condlog(2, "%s: prhold unset (by clearing prflag)", param); ++ } + + return 0; + } +@@ -1441,6 +1445,56 @@ cli_setprkey(void * v, char ** reply, int * len, void * data) + return ret; + } + ++static int ++do_prhold(struct vectors *vecs, char *param, int prhold) { ++ struct multipath *mpp = find_mp_by_str(vecs->mpvec, param); ++ ++ if (!mpp) ++ return 1; ++ ++ if (mpp->prhold != prhold) { ++ mpp->prhold = prhold; ++ condlog(2, "%s: prhold %s", param, pr_str[prhold]); ++ } ++ ++ return 0; ++} ++ ++int ++cli_setprhold(void * v, char ** reply, int * len, void * data) ++{ ++ return do_prhold((struct vectors *)data, get_keyparam(v, MAP), ++ PR_SET); ++} ++ ++int ++cli_unsetprhold(void * v, char ** reply, int * len, void * data) ++{ ++ return do_prhold((struct vectors *)data, get_keyparam(v, MAP), ++ PR_UNSET); ++} ++ ++int ++cli_getprhold(void * v, char ** reply, int * len, void * data) ++{ ++ struct multipath *mpp; ++ struct vectors *vecs = (struct vectors *)data; ++ char *param = get_keyparam(v, MAP); ++ ++ param = convert_dev(param, 0); ++ ++ mpp = find_mp_by_str(vecs->mpvec, param); ++ if (!mpp) ++ return 1; ++ ++ *len = asprintf(reply, "%s", pr_str[mpp->prhold]); ++ if (*len < 0) ++ return 1; ++ ++ condlog(3, "%s: reply = %s", param, *reply); ++ return 0; ++} ++ + int cli_set_marginal(void * v, char ** reply, int * len, void * data) + { + struct vectors * vecs = (struct vectors *)data; +diff --git a/multipathd/cli_handlers.h b/multipathd/cli_handlers.h +index 6f57b429..348c8485 100644 +--- a/multipathd/cli_handlers.h ++++ b/multipathd/cli_handlers.h +@@ -53,3 +53,6 @@ int cli_unsetprkey(void * v, char ** reply, int * len, void * data); + int cli_set_marginal(void * v, char ** reply, int * len, void * data); + int cli_unset_marginal(void * v, char ** reply, int * len, void * data); + int cli_unset_all_marginal(void * v, char ** reply, int * len, void * data); ++int cli_getprhold(void * v, char ** reply, int * len, void * data); ++int cli_setprhold(void * v, char ** reply, int * len, void * data); ++int cli_unsetprhold(void * v, char ** reply, int * len, void * data); +diff --git a/multipathd/main.c b/multipathd/main.c +index 56050a06..060d3468 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -1874,6 +1874,9 @@ uxlsnrloop (void * ap) + set_handler_callback(SETMARGINAL|PATH, cli_set_marginal); + set_handler_callback(UNSETMARGINAL|PATH, cli_unset_marginal); + set_handler_callback(UNSETMARGINAL|MAP, cli_unset_all_marginal); ++ set_handler_callback(GETPRHOLD|MAP, cli_getprhold); ++ set_handler_callback(SETPRHOLD|MAP, cli_setprhold); ++ set_handler_callback(UNSETPRHOLD|MAP, cli_unsetprhold); + + umask(077); + uxsock_listen(num, ux_sock, ap); +@@ -3721,6 +3724,32 @@ main (int argc, char *argv[]) + return (child(NULL)); + } + ++static void check_prhold(struct multipath *mpp, struct path *pp) ++{ ++ struct prin_resp resp = {0}; ++ int status; ++ ++ if (mpp->prflag == PR_UNSET) { ++ mpp->prhold = PR_UNSET; ++ return; ++ } ++ if (mpp->prflag != PR_SET || mpp->prhold != PR_UNKNOWN) ++ return; ++ ++ status = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RRES_SA, &resp, 0); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog (0, "%s: pr in read reservation command failed: %d", ++ mpp->wwid, status); ++ return; ++ } ++ mpp->prhold = PR_UNSET; ++ if (!resp.prin_descriptor.prin_readresv.additional_length) ++ return; ++ ++ if (memcmp(&mpp->reservation_key, resp.prin_descriptor.prin_readresv.key, 8) == 0) ++ mpp->prhold = PR_SET; ++} ++ + static void mpath_pr_event_handle(struct path *pp) + { + struct multipath *mpp = pp->mpp; +@@ -3732,8 +3761,13 @@ static void mpath_pr_event_handle(struct path *pp) + return; + } + +- if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) ++ if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) { ++ if (mpp->prflag == PR_UNSET) ++ mpp->prhold = PR_UNSET; + return; ++ } ++ ++ check_prhold(mpp, pp); + + if (mpp->prflag != PR_SET) + return; diff --git a/SOURCES/0170-limpathpersist-remove-update_map_pr-code-for-NULL-pp.patch b/SOURCES/0170-limpathpersist-remove-update_map_pr-code-for-NULL-pp.patch new file mode 100644 index 0000000..93de0f9 --- /dev/null +++ b/SOURCES/0170-limpathpersist-remove-update_map_pr-code-for-NULL-pp.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 14 Jul 2025 20:14:06 -0400 +Subject: [PATCH] limpathpersist: remove update_map_pr code for NULL pp + +Since update_map_pr is always called with pp set now, remove the code +to handle being called with NULL pp. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 12 +----------- + 1 file changed, 1 insertion(+), 11 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index e47e9190..fbf6161f 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -993,18 +993,8 @@ int update_map_pr(struct multipath *mpp, struct path *pp) + condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); + return MPATH_PR_OTHER; + } +- if (!pp && count_active_paths(mpp) == 0) { +- condlog(2, "%s: No available paths to check pr status", +- mpp->alias); +- goto out; +- } +- if (pp) +- ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, +- noisy); +- else +- ret = mpath_prin_activepath(mpp, MPATH_PRIN_RKEY_SA, resp, +- noisy); + ++ ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, noisy); + if (ret != MPATH_PR_SUCCESS ) + { + if (ret == MPATH_PR_ILLEGAL_REQ) diff --git a/SOURCES/0171-libmpathpersist-move-update_map_pr-to-multipathd.patch b/SOURCES/0171-libmpathpersist-move-update_map_pr-to-multipathd.patch new file mode 100644 index 0000000..6ae0623 --- /dev/null +++ b/SOURCES/0171-libmpathpersist-move-update_map_pr-to-multipathd.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 14 Jul 2025 20:20:08 -0400 +Subject: [PATCH] libmpathpersist: move update_map_pr to multipathd + +multipathd is now the only program that calls update_map_pr(), so move +it there, and make it static. There are no other code changes. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/libmpathpersist.version | 1 - + libmpathpersist/mpath_persist.c | 75 ------------------------- + libmpathpersist/mpathpr.h | 1 - + multipathd/main.c | 75 +++++++++++++++++++++++++ + 4 files changed, 75 insertions(+), 77 deletions(-) + +diff --git a/libmpathpersist/libmpathpersist.version b/libmpathpersist/libmpathpersist.version +index e0748138..2758e233 100644 +--- a/libmpathpersist/libmpathpersist.version ++++ b/libmpathpersist/libmpathpersist.version +@@ -26,7 +26,6 @@ global: + mpath_persistent_reserve_free_vecs; + prin_do_scsi_ioctl; + prout_do_scsi_ioctl; +- update_map_pr; + + local: *; + }; +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index fbf6161f..22d4e117 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -966,78 +966,3 @@ void * mpath_alloc_prin_response(int prin_sa) + } + return ptr; + } +- +-int update_map_pr(struct multipath *mpp, struct path *pp) +-{ +- int noisy=0; +- struct prin_resp *resp; +- unsigned int i; +- int ret = MPATH_PR_OTHER, isFound; +- bool was_set = (mpp->prflag == PR_SET); +- +- /* If pr is explicitly unset, it must be manually set */ +- if (mpp->prflag == PR_UNSET) +- return MPATH_PR_SKIP; +- +- if (!get_be64(mpp->reservation_key)) +- { +- /* Nothing to do. Assuming pr mgmt feature is disabled*/ +- mpp->prflag = PR_UNSET; +- condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); +- return MPATH_PR_SKIP; +- } +- +- resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); +- if (!resp) +- { +- condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); +- return MPATH_PR_OTHER; +- } +- +- ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, noisy); +- if (ret != MPATH_PR_SUCCESS ) +- { +- if (ret == MPATH_PR_ILLEGAL_REQ) +- mpp->prflag = PR_UNSET; +- condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); +- goto out; +- } +- mpp->prflag = PR_UNSET; +- +- if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) +- { +- condlog(was_set ? 1 : 3, "%s: No key found. Device may not be registered. ", mpp->alias); +- goto out; +- } +- +- condlog(3, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, +- get_be64(mpp->reservation_key)); +- +- isFound =0; +- for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) +- { +- if (libmp_verbosity >= 3) { +- condlog(3, "%s: PR IN READKEYS[%d] reservation key:", +- mpp->alias, i); +- dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8, 1); +- } +- +- if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8)) { +- condlog(3, "%s: reservation key found in pr in readkeys response", mpp->alias); +- isFound =1; +- } +- } +- +- if (isFound) +- { +- mpp->prflag = PR_SET; +- condlog(was_set ? 3 : 2, "%s: key found. prflag set.", +- mpp->alias); +- } else +- condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", +- mpp->alias); +- +-out: +- free(resp); +- return ret; +-} +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 60fa0b09..052af85d 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -47,6 +47,5 @@ int get_prhold(char *mapname); + int update_prhold(char *mapname, bool set); + #define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) + void * mpath_alloc_prin_response(int prin_sa); +-int update_map_pr(struct multipath *mpp, struct path *pp); + + #endif +diff --git a/multipathd/main.c b/multipathd/main.c +index 060d3468..29c788e3 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3750,6 +3750,81 @@ static void check_prhold(struct multipath *mpp, struct path *pp) + mpp->prhold = PR_SET; + } + ++static int update_map_pr(struct multipath *mpp, struct path *pp) ++{ ++ int noisy=0; ++ struct prin_resp *resp; ++ unsigned int i; ++ int ret = MPATH_PR_OTHER, isFound; ++ bool was_set = (mpp->prflag == PR_SET); ++ ++ /* If pr is explicitly unset, it must be manually set */ ++ if (mpp->prflag == PR_UNSET) ++ return MPATH_PR_SKIP; ++ ++ if (!get_be64(mpp->reservation_key)) ++ { ++ /* Nothing to do. Assuming pr mgmt feature is disabled*/ ++ mpp->prflag = PR_UNSET; ++ condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); ++ return MPATH_PR_SKIP; ++ } ++ ++ resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); ++ if (!resp) ++ { ++ condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); ++ return MPATH_PR_OTHER; ++ } ++ ++ ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, noisy); ++ if (ret != MPATH_PR_SUCCESS ) ++ { ++ if (ret == MPATH_PR_ILLEGAL_REQ) ++ mpp->prflag = PR_UNSET; ++ condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); ++ goto out; ++ } ++ mpp->prflag = PR_UNSET; ++ ++ if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) ++ { ++ condlog(was_set ? 1 : 3, "%s: No key found. Device may not be registered. ", mpp->alias); ++ goto out; ++ } ++ ++ condlog(3, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, ++ get_be64(mpp->reservation_key)); ++ ++ isFound =0; ++ for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) ++ { ++ if (libmp_verbosity >= 3) { ++ condlog(3, "%s: PR IN READKEYS[%d] reservation key:", ++ mpp->alias, i); ++ dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8, 1); ++ } ++ ++ if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8)) { ++ condlog(3, "%s: reservation key found in pr in readkeys response", mpp->alias); ++ isFound =1; ++ } ++ } ++ ++ if (isFound) ++ { ++ mpp->prflag = PR_SET; ++ condlog(was_set ? 3 : 2, "%s: key found. prflag set.", ++ mpp->alias); ++ } else ++ condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", ++ mpp->alias); ++ ++out: ++ free(resp); ++ return ret; ++} ++ + static void mpath_pr_event_handle(struct path *pp) + { + struct multipath *mpp = pp->mpp; diff --git a/SOURCES/0172-multipathd-clean-up-update_map_pr-and-mpath_pr_event.patch b/SOURCES/0172-multipathd-clean-up-update_map_pr-and-mpath_pr_event.patch new file mode 100644 index 0000000..d3dda74 --- /dev/null +++ b/SOURCES/0172-multipathd-clean-up-update_map_pr-and-mpath_pr_event.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 15 Jul 2025 14:14:42 -0400 +Subject: [PATCH] multipathd: clean up update_map_pr and mpath_pr_event_handle + +Store the READ KEYS response and the prout_param_descriptor on the stack +to avoid having to fail these functions for allocation reasons. Don't +explicitly check for additional_length == 0, since the for-loop already +handles that. Also cleanup formatting issues,remove redundant messages, +and reduce the log level of others. + +Signed-off-by: Benjamin Marzinski +--- + multipathd/main.c | 70 ++++++++++++++++------------------------------- + 1 file changed, 24 insertions(+), 46 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index 29c788e3..8466d4d4 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3752,8 +3752,7 @@ static void check_prhold(struct multipath *mpp, struct path *pp) + + static int update_map_pr(struct multipath *mpp, struct path *pp) + { +- int noisy=0; +- struct prin_resp *resp; ++ struct prin_resp resp; + unsigned int i; + int ret = MPATH_PR_OTHER, isFound; + bool was_set = (mpp->prflag == PR_SET); +@@ -3762,57 +3761,42 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + if (mpp->prflag == PR_UNSET) + return MPATH_PR_SKIP; + +- if (!get_be64(mpp->reservation_key)) +- { ++ if (!get_be64(mpp->reservation_key)) { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ + mpp->prflag = PR_UNSET; + condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); + return MPATH_PR_SKIP; + } + +- resp = mpath_alloc_prin_response(MPATH_PRIN_RKEY_SA); +- if (!resp) +- { +- condlog(0,"%s : failed to alloc resp in update_map_pr", mpp->alias); +- return MPATH_PR_OTHER; +- } ++ memset(&resp, 0, sizeof(resp)); + +- ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, resp, noisy); +- if (ret != MPATH_PR_SUCCESS ) +- { ++ ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, &resp, 0); ++ if (ret != MPATH_PR_SUCCESS) { + if (ret == MPATH_PR_ILLEGAL_REQ) + mpp->prflag = PR_UNSET; + condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); +- goto out; ++ return ret; + } + mpp->prflag = PR_UNSET; + +- if (resp->prin_descriptor.prin_readkeys.additional_length == 0 ) +- { +- condlog(was_set ? 1 : 3, "%s: No key found. Device may not be registered. ", mpp->alias); +- goto out; +- } +- +- condlog(3, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, ++ condlog(4, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, + get_be64(mpp->reservation_key)); + +- isFound =0; +- for (i = 0; i < resp->prin_descriptor.prin_readkeys.additional_length/8; i++ ) +- { +- if (libmp_verbosity >= 3) { +- condlog(3, "%s: PR IN READKEYS[%d] reservation key:", ++ isFound = 0; ++ for (i = 0; i < resp.prin_descriptor.prin_readkeys.additional_length / 8; i++) { ++ uint8_t *keyp = &resp.prin_descriptor.prin_readkeys.key_list[i * 8]; ++ ++ if (libmp_verbosity >= 4) { ++ condlog(4, "%s: PR IN READKEYS[%d] reservation key:", + mpp->alias, i); +- dumpHex((char *)&resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8, 1); ++ dumpHex((char *)keyp, 8, 1); + } + +- if (!memcmp(&mpp->reservation_key, &resp->prin_descriptor.prin_readkeys.key_list[i * 8], 8)) { +- condlog(3, "%s: reservation key found in pr in readkeys response", mpp->alias); +- isFound =1; +- } ++ if (!memcmp(&mpp->reservation_key, keyp, 8)) ++ isFound = 1; + } + +- if (isFound) +- { ++ if (isFound) { + mpp->prflag = PR_SET; + condlog(was_set ? 3 : 2, "%s: key found. prflag set.", + mpp->alias); +@@ -3820,16 +3804,14 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", + mpp->alias); + +-out: +- free(resp); +- return ret; ++ return MPATH_PR_SUCCESS; + } + + static void mpath_pr_event_handle(struct path *pp) + { + struct multipath *mpp = pp->mpp; + int ret; +- struct prout_param_descriptor *param; ++ struct prout_param_descriptor param; + + if (pp->bus != SYSFS_BUS_SCSI) { + mpp->prflag = PR_UNSET; +@@ -3847,21 +3829,17 @@ static void mpath_pr_event_handle(struct path *pp) + if (mpp->prflag != PR_SET) + return; + +- param = (struct prout_param_descriptor *)MALLOC(sizeof(struct prout_param_descriptor)); +- if (!param) +- return; ++ memset(¶m, 0, sizeof(param)); + +- param->sa_flags = mpp->sa_flags; +- memcpy(param->sa_key, &mpp->reservation_key, 8); +- param->num_transportid = 0; ++ param.sa_flags = mpp->sa_flags; ++ memcpy(param.sa_key, &mpp->reservation_key, 8); ++ param.num_transportid = 0; + + condlog(3, "device %s:%s", pp->dev, pp->mpp->wwid); + +- ret = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REG_IGN_SA, 0, 0, param, 0); ++ ret = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REG_IGN_SA, 0, 0, ¶m, 0); + if (ret != MPATH_PR_SUCCESS ) + { + condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret); + } +- +- free(param); + } diff --git a/SOURCES/0173-libmpathpersist-clean-up-duplicate-function-declarat.patch b/SOURCES/0173-libmpathpersist-clean-up-duplicate-function-declarat.patch new file mode 100644 index 0000000..9d6b4ff --- /dev/null +++ b/SOURCES/0173-libmpathpersist-clean-up-duplicate-function-declarat.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 15 Jul 2025 14:50:55 -0400 +Subject: [PATCH] libmpathpersist: clean up duplicate function declarations + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_pr_ioctl.c | 10 +++------- + mpathpersist/main.c | 3 +-- + multipathd/main.h | 6 ------ + 3 files changed, 4 insertions(+), 15 deletions(-) + +diff --git a/libmpathpersist/mpath_pr_ioctl.c b/libmpathpersist/mpath_pr_ioctl.c +index 9cd83729..e5b25692 100644 +--- a/libmpathpersist/mpath_pr_ioctl.c ++++ b/libmpathpersist/mpath_pr_ioctl.c +@@ -15,20 +15,16 @@ + #include "mpath_pr_ioctl.h" + #include "mpath_persist.h" + #include "unaligned.h" +- + #include "debug.h" + + #define FILE_NAME_SIZE 256 ++#include "mpathpr.h" + + #define TIMEOUT 2000 + #define MAXRETRY 5 + +-int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp *resp, int noisy); +-int mpath_translate_response (char * dev, struct sg_io_hdr io_hdr, +- SenseData_t *Sensedata); +-void dumpHex(const char* str, int len, int no_ascii); +-int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); ++int mpath_translate_response(char *dev, struct sg_io_hdr io_hdr, ++ SenseData_t *Sensedata); + uint32_t format_transportids(struct prout_param_descriptor *paramp); + void convert_be32_to_cpu(uint32_t *num); + void convert_be16_to_cpu(uint16_t *num); +diff --git a/mpathpersist/main.c b/mpathpersist/main.c +index 14245cc3..26c69b01 100644 +--- a/mpathpersist/main.c ++++ b/mpathpersist/main.c +@@ -17,6 +17,7 @@ + #include + #include + #include "version.h" ++#include "mpathpr.h" + + static const char * pr_type_strs[] = { + "obsolete [0]", +@@ -37,8 +38,6 @@ void mpath_print_buf_readcap(struct prin_resp *pr_buff); + void mpath_print_buf_readfullstat(struct prin_resp *pr_buff); + void mpath_print_buf_readresv(struct prin_resp *pr_buff); + void mpath_print_buf_readkeys(struct prin_resp *pr_buff); +-void dumpHex(const char* str, int len, int no_ascii); +-void * mpath_alloc_prin_response(int prin_sa); + void mpath_print_transport_id(struct prin_fulldescr *fdesc); + int construct_transportid(const char * inp, struct transportid transid[], int num_transportids); + +diff --git a/multipathd/main.h b/multipathd/main.h +index 6d2ae72a..5b5840c0 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -46,12 +46,6 @@ int ev_remove_map (char *, char *, int, struct vectors *); + int flush_map(struct multipath *, struct vectors *); + int set_config_state(enum daemon_status); + void * mpath_alloc_prin_response(int prin_sa); +-int prin_do_scsi_ioctl(char *, int rq_servact, struct prin_resp * resp, +- int noisy); +-void dumpHex(const char * , int len, int no_ascii); +-int prout_do_scsi_ioctl(char * , int rq_servact, int rq_scope, +- unsigned int rq_type, +- struct prout_param_descriptor *param, int noisy); + void handle_signals(bool); + int __setup_multipath (struct vectors * vecs, struct multipath * mpp, + int reset); diff --git a/SOURCES/0174-multipathd-wrap-setting-and-unsetting-prflag.patch b/SOURCES/0174-multipathd-wrap-setting-and-unsetting-prflag.patch new file mode 100644 index 0000000..3b68a8f --- /dev/null +++ b/SOURCES/0174-multipathd-wrap-setting-and-unsetting-prflag.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 15 Jul 2025 17:54:16 -0400 +Subject: [PATCH] multipathd: wrap setting and unsetting prflag + +When prflag is unset, prhold and sa_flags should also be unset. A future +patch will add another variable to be set when prflag is set. Wrap all +these actions in set_pr() and unset_pr(). + +Signed-off-by: Benjamin Marzinski +--- + multipathd/cli_handlers.c | 11 +++++------ + multipathd/main.c | 34 ++++++++++++++++++++-------------- + multipathd/main.h | 2 ++ + 3 files changed, 27 insertions(+), 20 deletions(-) + +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index f6353b61..93768ef3 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -1323,7 +1323,7 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + return 1; + + if (mpp->prflag != PR_SET) { +- mpp->prflag = PR_SET; ++ set_pr(mpp); + condlog(2, "%s: prflag set", param); + } + +@@ -1346,12 +1346,11 @@ cli_unsetprstatus(void * v, char ** reply, int * len, void * data) + return 1; + + if (mpp->prflag != PR_UNSET) { +- mpp->prflag = PR_UNSET; + condlog(2, "%s: prflag unset", param); +- } +- if (mpp->prhold != PR_UNSET) { +- mpp->prhold = PR_UNSET; +- condlog(2, "%s: prhold unset (by clearing prflag)", param); ++ if (mpp->prhold != PR_UNSET) ++ condlog(2, "%s: prhold unset (by clearing prflag)", ++ param); ++ unset_pr(mpp); + } + + return 0; +diff --git a/multipathd/main.c b/multipathd/main.c +index 8466d4d4..bc4cf66e 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3729,10 +3729,6 @@ static void check_prhold(struct multipath *mpp, struct path *pp) + struct prin_resp resp = {0}; + int status; + +- if (mpp->prflag == PR_UNSET) { +- mpp->prhold = PR_UNSET; +- return; +- } + if (mpp->prflag != PR_SET || mpp->prhold != PR_UNKNOWN) + return; + +@@ -3750,6 +3746,18 @@ static void check_prhold(struct multipath *mpp, struct path *pp) + mpp->prhold = PR_SET; + } + ++void set_pr(struct multipath *mpp) ++{ ++ mpp->prflag = PR_SET; ++} ++ ++void unset_pr(struct multipath *mpp) ++{ ++ mpp->prflag = PR_UNSET; ++ mpp->prhold = PR_UNSET; ++ mpp->sa_flags = 0; ++} ++ + static int update_map_pr(struct multipath *mpp, struct path *pp) + { + struct prin_resp resp; +@@ -3763,7 +3771,7 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + + if (!get_be64(mpp->reservation_key)) { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ +- mpp->prflag = PR_UNSET; ++ unset_pr(mpp); + condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); + return MPATH_PR_SKIP; + } +@@ -3773,11 +3781,10 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, &resp, 0); + if (ret != MPATH_PR_SUCCESS) { + if (ret == MPATH_PR_ILLEGAL_REQ) +- mpp->prflag = PR_UNSET; ++ unset_pr(mpp); + condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); + return ret; + } +- mpp->prflag = PR_UNSET; + + condlog(4, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, + get_be64(mpp->reservation_key)); +@@ -3797,12 +3804,14 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + } + + if (isFound) { +- mpp->prflag = PR_SET; ++ set_pr(mpp); + condlog(was_set ? 3 : 2, "%s: key found. prflag set.", + mpp->alias); +- } else ++ } else { ++ unset_pr(mpp); + condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", + mpp->alias); ++ } + + return MPATH_PR_SUCCESS; + } +@@ -3814,15 +3823,12 @@ static void mpath_pr_event_handle(struct path *pp) + struct prout_param_descriptor param; + + if (pp->bus != SYSFS_BUS_SCSI) { +- mpp->prflag = PR_UNSET; ++ unset_pr(mpp); + return; + } + +- if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) { +- if (mpp->prflag == PR_UNSET) +- mpp->prhold = PR_UNSET; ++ if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) + return; +- } + + check_prhold(mpp, pp); + +diff --git a/multipathd/main.h b/multipathd/main.h +index 5b5840c0..77603751 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -58,4 +58,6 @@ void handle_path_wwid_change(struct path *pp, struct vectors *vecs); + bool check_path_wwid_change(struct path *pp); + int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors *vecs); ++void set_pr(struct multipath *mpp); ++void unset_pr(struct multipath *mpp); + #endif /* MAIN_H */ diff --git a/SOURCES/0175-multipathd-unregister-PR-key-when-path-is-restored-i.patch b/SOURCES/0175-multipathd-unregister-PR-key-when-path-is-restored-i.patch new file mode 100644 index 0000000..0f13a0b --- /dev/null +++ b/SOURCES/0175-multipathd-unregister-PR-key-when-path-is-restored-i.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 16 Jul 2025 13:15:05 -0400 +Subject: [PATCH] multipathd: unregister PR key when path is restored if + necessary + +It is possible that a path was unavailable and either the registered PR +key was removed or the registered PR key was changed and then that new +key was preempted. In both of these situations, this path will still +have a registered key (just not one that matches mpp->reservation_key) +but it should not have one. If the path becomes usable again in this +state, it may allow the multipath device to access storage that it +shouldn't be allowed to access. + +To deal with this, track if a multipath device ever had a registered PR +key. If so, and the device no longer has a registered key, explicitly +clear the key when paths get restored. + +Signed-off-by: Benjamin Marzinski +--- + libmultipath/structs.h | 1 + + multipathd/main.c | 46 ++++++++++++++++++++++++++++++++---------- + 2 files changed, 36 insertions(+), 11 deletions(-) + +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 761fc642..a67e767d 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -467,6 +467,7 @@ struct multipath { + struct gen_multipath generic_mp; + bool fpin_must_reload; + int prhold; ++ bool ever_registered_pr; + }; + + static inline int marginal_path_check_enabled(const struct multipath *mpp) +diff --git a/multipathd/main.c b/multipathd/main.c +index bc4cf66e..56d51c48 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -2538,7 +2538,8 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + } + + if (newstate == PATH_UP || newstate == PATH_GHOST) { +- if (pp->mpp->prflag != PR_UNSET) { ++ if (pp->mpp->prflag != PR_UNSET || ++ pp->mpp->ever_registered_pr) { + int prflag = pp->mpp->prflag; + /* + * Check Persistent Reservation. +@@ -3748,6 +3749,7 @@ static void check_prhold(struct multipath *mpp, struct path *pp) + + void set_pr(struct multipath *mpp) + { ++ mpp->ever_registered_pr = true; + mpp->prflag = PR_SET; + } + +@@ -3758,22 +3760,27 @@ void unset_pr(struct multipath *mpp) + mpp->sa_flags = 0; + } + ++/* ++ * Returns MPATH_PR_SUCCESS unless if fails to read the PR keys. If ++ * MPATH_PR_SUCCESS is returned, mpp->prflag will be either PR_SET or ++ * PR_UNSET. ++ */ + static int update_map_pr(struct multipath *mpp, struct path *pp) + { + struct prin_resp resp; + unsigned int i; +- int ret = MPATH_PR_OTHER, isFound; ++ int ret, isFound; + bool was_set = (mpp->prflag == PR_SET); + + /* If pr is explicitly unset, it must be manually set */ + if (mpp->prflag == PR_UNSET) +- return MPATH_PR_SKIP; ++ return MPATH_PR_SUCCESS; + + if (!get_be64(mpp->reservation_key)) { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ + unset_pr(mpp); + condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); +- return MPATH_PR_SKIP; ++ return MPATH_PR_SUCCESS; + } + + memset(&resp, 0, sizeof(resp)); +@@ -3821,6 +3828,7 @@ static void mpath_pr_event_handle(struct path *pp) + struct multipath *mpp = pp->mpp; + int ret; + struct prout_param_descriptor param; ++ bool clear_reg = false; + + if (pp->bus != SYSFS_BUS_SCSI) { + unset_pr(mpp); +@@ -3832,20 +3840,36 @@ static void mpath_pr_event_handle(struct path *pp) + + check_prhold(mpp, pp); + +- if (mpp->prflag != PR_SET) +- return; ++ if (mpp->prflag != PR_SET) { ++ if (!mpp->ever_registered_pr) ++ return; ++ /* ++ * This path may have been unusable and either the ++ * registration was cleared or the registered ++ * key was switched and then that new key was preempted. ++ * In either case, this path should not have a registration ++ * but it might still have one, just with a different ++ * key than mpp->reservation_key is currently set to. ++ * clear it to be sure. ++ */ ++ clear_reg = true; ++ } + + memset(¶m, 0, sizeof(param)); + +- param.sa_flags = mpp->sa_flags; +- memcpy(param.sa_key, &mpp->reservation_key, 8); +- param.num_transportid = 0; ++ if (!clear_reg) { ++ param.sa_flags = mpp->sa_flags; ++ memcpy(param.sa_key, &mpp->reservation_key, 8); ++ param.num_transportid = 0; ++ } + +- condlog(3, "device %s:%s", pp->dev, pp->mpp->wwid); ++ condlog(3, "%s registration for device %s:%s", ++ clear_reg ? "Clearing" : "Setting", pp->dev, pp->mpp->wwid); + + ret = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REG_IGN_SA, 0, 0, ¶m, 0); + if (ret != MPATH_PR_SUCCESS ) + { +- condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret); ++ condlog(0, "%s: %s reservation registration failed. Error: %d", ++ clear_reg ? "Clearing" : "Setting", pp->dev, ret); + } + } diff --git a/SOURCES/0176-libmpathpersist-Fix-up-reservation_key-checking.patch b/SOURCES/0176-libmpathpersist-Fix-up-reservation_key-checking.patch new file mode 100644 index 0000000..e19a4fa --- /dev/null +++ b/SOURCES/0176-libmpathpersist-Fix-up-reservation_key-checking.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 17 Jul 2025 15:32:37 -0400 +Subject: [PATCH] libmpathpersist: Fix-up reservation_key checking + +The reservation key checking in do_mpath_persistent_reserve_out() was +slightly wrong. It allowed invalid keys for preempting. It now correctly +checks the reservation key for the preempt commands. + +It also was a little overly strict in some places. Formerly, it only +allowed registering from any key to the configured key or registering +from the configured key to any key (as long as you use the prkeys file). +Now it also allows unregistering from any key and registering an +unregistered device to any key (as long as you use the prkeys file). + +Also, clarify the code by replacing prkey with a bool tracking if you +are registering or unregistering. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 49 +++++++++++++++++++++++++-------- + 1 file changed, 38 insertions(+), 11 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 22d4e117..98b6997c 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -315,9 +315,9 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + struct multipath *mpp; + char *alias; + int ret; +- uint64_t prkey; ++ uint64_t zerokey = 0; + struct config *conf; +- bool preempting_reservation = false; ++ bool unregistering, preempting_reservation = false; + + ret = mpath_get_map(curmp, pathvec, fd, &alias, &mpp); + if (ret != MPATH_PR_SUCCESS) +@@ -332,11 +332,12 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + if (rq_servact == MPATH_PROUT_REG_IGN_SA) + set_ignored_key(mpp, paramp->key); + +- memcpy(&prkey, paramp->sa_key, 8); +- if (mpp->prkey_source == PRKEY_SOURCE_FILE && prkey && ++ unregistering = (memcmp(&zerokey, paramp->sa_key, 8) == 0); ++ if (mpp->prkey_source == PRKEY_SOURCE_FILE && !unregistering && + (rq_servact == MPATH_PROUT_REG_IGN_SA || + (rq_servact == MPATH_PROUT_REG_SA && + (!get_be64(mpp->reservation_key) || ++ memcmp(paramp->key, &zerokey, 8) == 0 || + memcmp(paramp->key, &mpp->reservation_key, 8) == 0)))) { + memcpy(&mpp->reservation_key, paramp->sa_key, 8); + if (update_prkey_flags(alias, get_be64(mpp->reservation_key), +@@ -348,12 +349,38 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + } + } + +- if (memcmp(paramp->key, &mpp->reservation_key, 8) && +- memcmp(paramp->sa_key, &mpp->reservation_key, 8) && +- (prkey || rq_servact != MPATH_PROUT_REG_IGN_SA)) { +- condlog(0, "%s: configured reservation key doesn't match: 0x%" PRIx64, alias, get_be64(mpp->reservation_key)); +- ret = MPATH_PR_SYNTAX_ERROR; +- goto out1; ++ /* ++ * If you are registering a non-zero key, mpp->reservation_key ++ * must be set and must equal paramp->sa_key. ++ * If you're not registering a key, mpp->reservation_key must be ++ * set, and must equal paramp->key ++ */ ++ if ((rq_servact == MPATH_PROUT_REG_IGN_SA || ++ rq_servact == MPATH_PROUT_REG_SA)) { ++ if (!unregistering && !get_be64(mpp->reservation_key)) { ++ condlog(0, "%s: no configured reservation key", alias); ++ ret = MPATH_PR_SYNTAX_ERROR; ++ goto out1; ++ } ++ if (!unregistering && ++ memcmp(paramp->sa_key, &mpp->reservation_key, 8)) { ++ condlog(0, "%s: configured reservation key doesn't match: 0x%" PRIx64, ++ mpp->alias, get_be64(mpp->reservation_key)); ++ ret = MPATH_PR_SYNTAX_ERROR; ++ goto out1; ++ } ++ } else { ++ if (!get_be64(mpp->reservation_key)) { ++ condlog(0, "%s: no configured reservation key", alias); ++ ret = MPATH_PR_SYNTAX_ERROR; ++ goto out1; ++ } ++ if (memcmp(paramp->key, &mpp->reservation_key, 8)) { ++ condlog(0, "%s: configured reservation key doesn't match: 0x%" PRIx64, ++ mpp->alias, get_be64(mpp->reservation_key)); ++ ret = MPATH_PR_SYNTAX_ERROR; ++ goto out1; ++ } + } + + switch(rq_servact) +@@ -387,7 +414,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + { + case MPATH_PROUT_REG_SA: + case MPATH_PROUT_REG_IGN_SA: +- if (prkey == 0) { ++ if (unregistering) { + update_prflag(alias, 0); + update_prkey(alias, 0); + } else diff --git a/SOURCES/0177-libmpathpersist-change-how-reservation-conflicts-are.patch b/SOURCES/0177-libmpathpersist-change-how-reservation-conflicts-are.patch new file mode 100644 index 0000000..145cd28 --- /dev/null +++ b/SOURCES/0177-libmpathpersist-change-how-reservation-conflicts-are.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 17 Jul 2025 19:12:23 -0400 +Subject: [PATCH] libmpathpersist: change how reservation conflicts are handled + +If registering a key on a path fails with a reservation conflict in +mpath_prout_reg(), libmpathpersist currently tries to roll back the +registration. This code doesn't always make much sense. First, it +updates the configured key, but doesn't fix it if it does a rollback. +Second, it always rolls the key back to 0x0, unregistering paths that +may have been previously registered. These rollback only happen on the +paths where the registration succeeded, meaning that they were in the +expected state when the command was run. The paths where the command +failed, that were in an unexpected state, remain in that state. + +The code no longer attempts to rollback registrations that failed +with a reservation conflict. Instead, it checks that at least one +path was in the expected state and was successfully registered. If +so, then it assumes that the registration command was a resonable one +and retries it on the paths that failed with a reservation conflict. +But instead of using MPATH_PROUT_REG_SA, it uses MPATH_PROUT_REG_IGN_SA +so that it will ignore the current key. This will keep it from +failing with a reservation conflict because the path doesn't have the +expected key registered on it. If path reservations failed for reasons +other than a reservation conflict, the command still returns failure. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 69 +++++++++++++++++++++------------ + 1 file changed, 44 insertions(+), 25 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 98b6997c..af1fdd61 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -598,13 +598,13 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + int i, j, k; + struct pathgroup *pgp = NULL; + struct path *pp = NULL; +- int rollback = 0; ++ bool can_retry = false; ++ bool need_retry = false; + int active_pathcount=0; + int rc; + int count=0; + int status = MPATH_PR_SUCCESS; + int all_tg_pt; +- uint64_t sa_key = 0; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -693,43 +693,62 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + condlog (0, "%s: Thread[%d] failed to join thread %d", mpp->wwid, i, rc); + } + } +- if (!rollback && (thread[i].param.status == MPATH_PR_RESERV_CONFLICT)){ +- rollback = 1; +- sa_key = get_unaligned_be64(¶mp->sa_key[0]); +- status = MPATH_PR_RESERV_CONFLICT ; +- } +- if (!rollback && (status == MPATH_PR_SUCCESS)){ ++ /* ++ * We only retry if there is at least one registration that ++ * returned a reservation conflict (which we need to retry) ++ * and at least one registration the return success, so we ++ * know that the command worked on some of the paths. If ++ * the registation fails on all paths, then it wasn't a ++ * valid request, so there's no need to retry. ++ */ ++ if (thread[i].param.status == MPATH_PR_RESERV_CONFLICT) ++ need_retry = true; ++ else if (thread[i].param.status == MPATH_PR_SUCCESS) ++ can_retry = true; ++ else if (status == MPATH_PR_SUCCESS) + status = thread[i].param.status; +- } + } +- if (rollback && ((rq_servact == MPATH_PROUT_REG_SA) && sa_key != 0 )){ +- condlog (3, "%s: ERROR: initiating pr out rollback", mpp->wwid); +- memcpy(¶mp->key, ¶mp->sa_key, 8); +- memset(¶mp->sa_key, 0, 8); +- for( i=0 ; i < count ; i++){ +- if(thread[i].param.status == MPATH_PR_SUCCESS) { +- rc = pthread_create(&thread[i].id, &attr, mpath_prout_pthread_fn, +- (void *)(&thread[i].param)); +- if (rc){ +- condlog (0, "%s: failed to create thread for rollback. %d", mpp->wwid, rc); +- thread[i].param.status = MPATH_PR_THREAD_ERROR; +- } +- } else ++ if (need_retry && can_retry && rq_servact == MPATH_PROUT_REG_SA && ++ status == MPATH_PR_SUCCESS) { ++ condlog (3, "%s: ERROR: initiating pr out retry", mpp->wwid); ++ for (i = 0; i < count; i++) { ++ if (thread[i].param.status != MPATH_PR_RESERV_CONFLICT) { + thread[i].param.status = MPATH_PR_SKIP; ++ continue; ++ } ++ /* ++ * retry using MPATH_PROUT_REG_IGN_SA to avoid ++ * conflicts. We already know that some paths ++ * succeeded using MPATH_PROUT_REG_SA. ++ */ ++ thread[i].param.rq_servact = MPATH_PROUT_REG_IGN_SA; ++ rc = pthread_create(&thread[i].id, &attr, ++ mpath_prout_pthread_fn, ++ (void *)(&thread[i].param)); ++ if (rc) { ++ condlog(0, "%s: failed to create thread for retry. %d", ++ mpp->wwid, rc); ++ thread[i].param.status = MPATH_PR_THREAD_ERROR; ++ } + } +- for(i=0; i < count ; i++){ ++ for (i = 0; i < count; i++) { + if (thread[i].param.status != MPATH_PR_SKIP && + thread[i].param.status != MPATH_PR_THREAD_ERROR) { + rc = pthread_join(thread[i].id, NULL); +- if (rc){ +- condlog (3, "%s: failed to join thread while rolling back %d", ++ if (rc) { ++ condlog(3, "%s: failed to join thread while retrying %d", + mpp->wwid, i); + } ++ if (status == MPATH_PR_SUCCESS) ++ status = thread[i].param.status; + } + } ++ need_retry = false; + } + + pthread_attr_destroy(&attr); ++ if (need_retry) ++ status = MPATH_PR_RESERV_CONFLICT; + if (status == MPATH_PR_SUCCESS) + preempt_missing_path(mpp, paramp->key, paramp->sa_key, noisy); + return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; diff --git a/SOURCES/0178-libmpathpersist-Clear-prkey-in-multipathd-before-unr.patch b/SOURCES/0178-libmpathpersist-Clear-prkey-in-multipathd-before-unr.patch new file mode 100644 index 0000000..d2088fb --- /dev/null +++ b/SOURCES/0178-libmpathpersist-Clear-prkey-in-multipathd-before-unr.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 21 Jul 2025 19:27:17 -0400 +Subject: [PATCH] libmpathpersist: Clear prkey in multipathd before + unregistering + +When you register or switch keys in libmpathpersist, it updates +mpp->reservation_key in multipathd before doing the registration. This +means that any paths that come online while you are doing the +registration get the new key registered. libmpathpersist didn't do +this when unregistering a key. This could cause the same problem. A +path that got restored while unregistering the device could end up +getting the old key registered on it. Fix this by unsetting the key +before doing the unregister, instead of afterwards. + +There is still a race condition associated with updating +mpp->reservation_key before doing the registration (but not on +unregistration). This will be dealt with by a future patch. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index af1fdd61..ee49c27b 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -333,7 +333,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + set_ignored_key(mpp, paramp->key); + + unregistering = (memcmp(&zerokey, paramp->sa_key, 8) == 0); +- if (mpp->prkey_source == PRKEY_SOURCE_FILE && !unregistering && ++ if (mpp->prkey_source == PRKEY_SOURCE_FILE && + (rq_servact == MPATH_PROUT_REG_IGN_SA || + (rq_servact == MPATH_PROUT_REG_SA && + (!get_be64(mpp->reservation_key) || +@@ -414,10 +414,9 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + { + case MPATH_PROUT_REG_SA: + case MPATH_PROUT_REG_IGN_SA: +- if (unregistering) { ++ if (unregistering) + update_prflag(alias, 0); +- update_prkey(alias, 0); +- } else ++ else + update_prflag(alias, 1); + break; + case MPATH_PROUT_CLEAR_SA: diff --git a/SOURCES/0179-libmpathpersist-only-clear-the-key-if-we-are-using-t.patch b/SOURCES/0179-libmpathpersist-only-clear-the-key-if-we-are-using-t.patch new file mode 100644 index 0000000..440a201 --- /dev/null +++ b/SOURCES/0179-libmpathpersist-only-clear-the-key-if-we-are-using-t.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 23 Jul 2025 16:52:53 -0400 +Subject: [PATCH] libmpathpersist: only clear the key if we are using the + prkeys file + +Otherwise this request will create a useless prkeys file. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index ee49c27b..f585d8e9 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -421,7 +421,8 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + break; + case MPATH_PROUT_CLEAR_SA: + update_prflag(alias, 0); +- update_prkey(alias, 0); ++ if (mpp->prkey_source == PRKEY_SOURCE_FILE) ++ update_prkey(alias, 0); + break; + case MPATH_PROUT_RES_SA: + case MPATH_PROUT_REL_SA: diff --git a/SOURCES/0180-libmpathpersist-Restore-old-reservation-key-on-failu.patch b/SOURCES/0180-libmpathpersist-Restore-old-reservation-key-on-failu.patch new file mode 100644 index 0000000..6eea1d0 --- /dev/null +++ b/SOURCES/0180-libmpathpersist-Restore-old-reservation-key-on-failu.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 23 Jul 2025 17:54:45 -0400 +Subject: [PATCH] libmpathpersist: Restore old reservation key on failure + +If we updated the key and then failed, restore the old key. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index f585d8e9..5b81c67f 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -316,8 +316,10 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + char *alias; + int ret; + uint64_t zerokey = 0; ++ struct be64 oldkey = {0}; + struct config *conf; + bool unregistering, preempting_reservation = false; ++ bool updated_prkey = false; + + ret = mpath_get_map(curmp, pathvec, fd, &alias, &mpp); + if (ret != MPATH_PR_SUCCESS) +@@ -339,6 +341,8 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + (!get_be64(mpp->reservation_key) || + memcmp(paramp->key, &zerokey, 8) == 0 || + memcmp(paramp->key, &mpp->reservation_key, 8) == 0)))) { ++ updated_prkey = true; ++ memcpy(&oldkey, &mpp->reservation_key, 8); + memcpy(&mpp->reservation_key, paramp->sa_key, 8); + if (update_prkey_flags(alias, get_be64(mpp->reservation_key), + paramp->sa_flags)) { +@@ -407,8 +411,12 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + goto out1; + } + +- if (ret != MPATH_PR_SUCCESS) ++ if (ret != MPATH_PR_SUCCESS) { ++ if (updated_prkey) ++ update_prkey_flags(alias, get_be64(oldkey), ++ mpp->sa_flags); + goto out1; ++ } + + switch(rq_servact) + { diff --git a/SOURCES/0181-libmpatpersist-update-reservation-key-before-checkin.patch b/SOURCES/0181-libmpatpersist-update-reservation-key-before-checkin.patch new file mode 100644 index 0000000..cdb5b2d --- /dev/null +++ b/SOURCES/0181-libmpatpersist-update-reservation-key-before-checkin.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 23 Jul 2025 18:28:52 -0400 +Subject: [PATCH] libmpatpersist: update reservation key before checking paths + +There is a race condition when changing reservation keys where a failed +path could come back online after libmpathpersist checks the paths, but +before it updates the reservation key. In this case, the path would come +up and get reregistered with the old key by multipathd, and +libmpathpersist would not update its key, because the path was down +when it checked. + +To fix this, check the paths after updating the key, so any path that +comes up after getting checked will use the updated key. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 83 ++++++++++++++------------------- + libmpathpersist/mpathpr.h | 1 - + 2 files changed, 36 insertions(+), 48 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 5b81c67f..3d4c0c2c 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -187,7 +187,19 @@ int mpath_persistent_reserve_init_vecs(int verbose) + return __mpath_persistent_reserve_init_vecs(&curmp, &pathvec, verbose); + } + +-static int mpath_get_map(vector curmp, vector pathvec, int fd, char **palias, ++static int ++get_path_info(struct multipath *mpp, vector pathvec) ++{ ++ if (update_multipath_table(mpp, pathvec, DI_CHECKER) != DMP_OK || ++ update_mpp_paths(mpp, pathvec)) { ++ condlog(0, "error parsing map %s", mpp->wwid); ++ return MPATH_PR_DMMP_ERROR; ++ } ++ extract_hwe_from_path(mpp); ++ return MPATH_PR_SUCCESS ; ++} ++ ++static int mpath_get_map(vector curmp, int fd, char **palias, + struct multipath **pmpp) + { + int ret = MPATH_PR_DMMP_ERROR; +@@ -223,12 +235,6 @@ static int mpath_get_map(vector curmp, vector pathvec, int fd, char **palias, + goto out; + } + +- /* get info of all paths from the dm device */ +- if (get_mpvec(curmp, pathvec, alias)){ +- condlog(0, "%s: failed to get device info.", alias); +- goto out; +- } +- + mpp = find_mp_by_alias(curmp, alias); + + if (!mpp) { +@@ -254,7 +260,11 @@ static int do_mpath_persistent_reserve_in (vector curmp, vector pathvec, + struct multipath *mpp; + int ret; + +- ret = mpath_get_map(curmp, pathvec, fd, NULL, &mpp); ++ ret = mpath_get_map(curmp, fd, NULL, &mpp); ++ if (ret != MPATH_PR_SUCCESS) ++ return ret; ++ ++ ret = get_path_info(mpp, pathvec); + if (ret != MPATH_PR_SUCCESS) + return ret; + +@@ -321,19 +331,15 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + bool unregistering, preempting_reservation = false; + bool updated_prkey = false; + +- ret = mpath_get_map(curmp, pathvec, fd, &alias, &mpp); ++ ret = mpath_get_map(curmp, fd, &alias, &mpp); + if (ret != MPATH_PR_SUCCESS) + return ret; + + conf = get_multipath_config(); ++ mpp->mpe = find_mpe(conf->mptable, mpp->wwid); + select_reservation_key(conf, mpp); +- select_all_tg_pt(conf, mpp); +- select_skip_kpartx(conf, mpp); + put_multipath_config(conf); + +- if (rq_servact == MPATH_PROUT_REG_IGN_SA) +- set_ignored_key(mpp, paramp->key); +- + unregistering = (memcmp(&zerokey, paramp->sa_key, 8) == 0); + if (mpp->prkey_source == PRKEY_SOURCE_FILE && + (rq_servact == MPATH_PROUT_REG_IGN_SA || +@@ -387,6 +393,22 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + } + } + ++ ret = get_path_info(mpp, pathvec); ++ if (ret != MPATH_PR_SUCCESS) { ++ if (updated_prkey) ++ update_prkey_flags(alias, get_be64(oldkey), ++ mpp->sa_flags); ++ goto out1; ++ } ++ ++ conf = get_multipath_config(); ++ select_all_tg_pt(conf, mpp); ++ select_skip_kpartx(conf, mpp); ++ put_multipath_config(conf); ++ ++ if (rq_servact == MPATH_PROUT_REG_IGN_SA) ++ set_ignored_key(mpp, paramp->key); ++ + switch(rq_servact) + { + case MPATH_PROUT_REG_SA: +@@ -485,39 +507,6 @@ int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, + return ret; + } + +-int +-get_mpvec (vector curmp, vector pathvec, char * refwwid) +-{ +- int i; +- struct multipath *mpp; +- +- vector_foreach_slot (curmp, mpp, i){ +- /* +- * discard out of scope maps +- */ +- if (!mpp->alias) { +- condlog(0, "%s: map with empty alias!", __func__); +- continue; +- } +- +- if (mpp->pg != NULL) +- /* Already seen this one */ +- continue; +- +- if (refwwid && strncmp (mpp->alias, refwwid, WWID_SIZE - 1)) +- continue; +- +- if (update_multipath_table(mpp, pathvec, DI_CHECKER) != DMP_OK || +- update_mpp_paths(mpp, pathvec)) { +- condlog(1, "error parsing map %s", mpp->wwid); +- remove_map(mpp, pathvec, curmp); +- i--; +- } else +- extract_hwe_from_path(mpp); +- } +- return MPATH_PR_SUCCESS ; +-} +- + /* + * If you are changing the key registered to a device, and that device is + * holding the reservation on a path that couldn't get its key updated, +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 052af85d..27df5fdd 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -31,7 +31,6 @@ int prin_do_scsi_ioctl(char * dev, int rq_servact, struct prin_resp * resp, int + int prout_do_scsi_ioctl( char * dev, int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy); + void * _mpath_pr_update (void *arg); +-int get_mpvec (vector curmp, vector pathvec, char * refwwid); + void * mpath_prout_pthread_fn(void *p); + void dumpHex(const char* , int len, int no_ascii); + diff --git a/SOURCES/0182-libmpathpersist-retry-on-conflicts-in-mpath_prout_co.patch b/SOURCES/0182-libmpathpersist-retry-on-conflicts-in-mpath_prout_co.patch new file mode 100644 index 0000000..bd39503 --- /dev/null +++ b/SOURCES/0182-libmpathpersist-retry-on-conflicts-in-mpath_prout_co.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 24 Jul 2025 14:05:38 -0400 +Subject: [PATCH] libmpathpersist: retry on conflicts in mpath_prout_common + +mpath_prout_common() just needs to execute a prout command down one +path. If it uses a path that was down when the key was changed and has +since been restored, but multipathd hasn't noticed yet, that path will +still be using the old key. This was causing mpath_prout_common() to +fail with MPATH_PR_RESERV_CONFLICT, even if there were other paths that +would work. + +Now, if prout command fails with MPATH_PR_RESERV_CONFLICT, +mpath_prout_common() checks if pp->dmstate is PSTATE_FAILED. If it is, +mpath_prout_common() assumes that multipathd has not yet noticed that +the path is back online and it might still have and old key, so it +doesn't immediately return. If it can't successfully send the command +down another path, it will still return MPATH_PR_RESERV_CONFLICT. + +Also, make sure prout_do_scsi_ioctl() always returns a MPATH_PR_* +type error. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 14 +++++++++++++- + libmpathpersist/mpath_pr_ioctl.c | 2 +- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 3d4c0c2c..560c9ae7 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -771,6 +771,7 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + struct pathgroup *pgp = NULL; + struct path *pp = NULL; + bool found = false; ++ bool conflict = false; + + vector_foreach_slot (mpp->pg, pgp, j){ + vector_foreach_slot (pgp->paths, pp, i){ +@@ -786,12 +787,23 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + rq_type, paramp, noisy); + if (ret == MPATH_PR_SUCCESS && pptr) + *pptr = pp; ++ /* ++ * If this path is considered down by the kernel, ++ * it may have just come back up, and multipathd ++ * may not have had time to update the key. Allow ++ * reservation conflicts. ++ */ ++ if (ret == MPATH_PR_RESERV_CONFLICT && ++ pp->dmstate == PSTATE_FAILED) { ++ conflict = true; ++ continue; ++ } + if (ret != MPATH_PR_RETRYABLE_ERROR) + return ret; + } + } + if (found) +- return MPATH_PR_OTHER; ++ return conflict ? MPATH_PR_RESERV_CONFLICT : MPATH_PR_OTHER; + condlog (0, "%s: no path available", mpp->wwid); + return MPATH_PR_DMMP_ERROR; + } +diff --git a/libmpathpersist/mpath_pr_ioctl.c b/libmpathpersist/mpath_pr_ioctl.c +index e5b25692..689578ac 100644 +--- a/libmpathpersist/mpath_pr_ioctl.c ++++ b/libmpathpersist/mpath_pr_ioctl.c +@@ -105,7 +105,7 @@ retry : + { + condlog(0, "%s: ioctl failed %d", dev, ret); + close(fd); +- return ret; ++ return MPATH_PR_OTHER; + } + + condlog(4, "%s: Duration=%u (ms)", dev, io_hdr.duration); diff --git a/SOURCES/0183-libmpathpersist-Don-t-always-fail-registrations-for-.patch b/SOURCES/0183-libmpathpersist-Don-t-always-fail-registrations-for-.patch new file mode 100644 index 0000000..dd04657 --- /dev/null +++ b/SOURCES/0183-libmpathpersist-Don-t-always-fail-registrations-for-.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 25 Jul 2025 15:53:16 -0400 +Subject: [PATCH] libmpathpersist: Don't always fail registrations for + retryable errors + +When libmpathpersist registers a key, it's possible that a path fails +between when it checks the path's status, and when it tries to do the +registrations on the path. In this case, the registration will fail with +a retryable error. If the registration was allowed to succeed, +multipathd would update the now failed path's key when it came back +online, and everything would work correctly. However it is possible for +a registration to fail with a retryable error on a path that is still +usable. + +Libmpathpersist needs to avoid the case where it does not update the +key of a usable path. Otherwise the path might be able to write to +storage it shouldn't be allowed to. Or it could fail writing to storage +that it should be allowed to write to. So if a registration would +succeed except for retryable errors, libmpathpersist now rechecks all +those paths to see if they are still usable. If they are, then it fails +the registration as before. If they are not, then the registration +succeeds. + +Also, rename can_retry to had_success, since it is used for checking +more than retries now. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 70 +++++++++++++++++++++++++++++---- + 1 file changed, 63 insertions(+), 7 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 560c9ae7..54408e91 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -588,6 +588,48 @@ void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, + mpp->wwid); + } + ++/* ++ * If libmpathpersist fails at updating the key on a path with a retryable ++ * error, it has probably failed. But there is a chance that the path is ++ * still usable. To make sure a path isn't active without a key, when it ++ * should have one, or with a key, when it shouldn't have one, check if ++ * the path is still usable. If it is, we must fail the registration. ++ */ ++static int check_failed_paths(struct multipath *mpp, ++ struct threadinfo *thread, int count) ++{ ++ int i, j, k; ++ int ret; ++ struct pathgroup *pgp; ++ struct path *pp; ++ struct config *conf; ++ ++ for (i = 0; i < count; i++) { ++ if (thread[i].param.status != MPATH_PR_RETRYABLE_ERROR) ++ continue; ++ vector_foreach_slot (mpp->pg, pgp, j) { ++ vector_foreach_slot (pgp->paths, pp, k) { ++ if (strncmp(pp->dev, thread[i].param.dev, ++ FILE_NAME_SIZE) == 0) ++ goto match; ++ } ++ } ++ /* no match. This shouldn't ever happen. */ ++ condlog(0, "%s: Error: can't find path %s", mpp->wwid, ++ thread[i].param.dev); ++ continue; ++match: ++ conf = get_multipath_config(); ++ ret = pathinfo(pp, conf, DI_CHECKER); ++ put_multipath_config(conf); ++ /* If pathinfo fails, or if the path is active, return error */ ++ if (ret != PATHINFO_OK || pp->state == PATH_UP || ++ pp->state == PATH_GHOST) ++ return MPATH_PR_OTHER; ++ } ++ return MPATH_PR_SUCCESS; ++} ++ + int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { +@@ -595,8 +637,9 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + int i, j, k; + struct pathgroup *pgp = NULL; + struct path *pp = NULL; +- bool can_retry = false; ++ bool had_success = false; + bool need_retry = false; ++ bool retryable_error = false; + int active_pathcount=0; + int rc; + int count=0; +@@ -700,16 +743,20 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + */ + if (thread[i].param.status == MPATH_PR_RESERV_CONFLICT) + need_retry = true; ++ else if (thread[i].param.status == MPATH_PR_RETRYABLE_ERROR) ++ retryable_error = true; + else if (thread[i].param.status == MPATH_PR_SUCCESS) +- can_retry = true; ++ had_success = true; + else if (status == MPATH_PR_SUCCESS) + status = thread[i].param.status; + } +- if (need_retry && can_retry && rq_servact == MPATH_PROUT_REG_SA && ++ if (need_retry && had_success && rq_servact == MPATH_PROUT_REG_SA && + status == MPATH_PR_SUCCESS) { + condlog (3, "%s: ERROR: initiating pr out retry", mpp->wwid); ++ retryable_error = false; + for (i = 0; i < count; i++) { +- if (thread[i].param.status != MPATH_PR_RESERV_CONFLICT) { ++ /* retry retryable errors and conflicts */ ++ if (thread[i].param.status != MPATH_PR_RESERV_CONFLICT && thread[i].param.status != MPATH_PR_RETRYABLE_ERROR) { + thread[i].param.status = MPATH_PR_SKIP; + continue; + } +@@ -736,7 +783,9 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + condlog(3, "%s: failed to join thread while retrying %d", + mpp->wwid, i); + } +- if (status == MPATH_PR_SUCCESS) ++ if (thread[i].param.status == MPATH_PR_RETRYABLE_ERROR) ++ retryable_error = true; ++ else if (status == MPATH_PR_SUCCESS) + status = thread[i].param.status; + } + } +@@ -745,10 +794,17 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + + pthread_attr_destroy(&attr); + if (need_retry) +- status = MPATH_PR_RESERV_CONFLICT; ++ return MPATH_PR_RESERV_CONFLICT; ++ if (status != MPATH_PR_SUCCESS) ++ return status; ++ /* If you had retryable errors on all paths, fail the registration */ ++ if (!had_success) ++ return MPATH_PR_OTHER; ++ if (retryable_error) ++ status = check_failed_paths(mpp, thread, count); + if (status == MPATH_PR_SUCCESS) + preempt_missing_path(mpp, paramp->key, paramp->sa_key, noisy); +- return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; ++ return status; + } + + void * mpath_prout_pthread_fn(void *p) diff --git a/SOURCES/0184-libmpathpersist-Don-t-try-release-workaround-for-inv.patch b/SOURCES/0184-libmpathpersist-Don-t-try-release-workaround-for-inv.patch new file mode 100644 index 0000000..aa086d0 --- /dev/null +++ b/SOURCES/0184-libmpathpersist-Don-t-try-release-workaround-for-inv.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 25 Aug 2025 14:09:29 -0400 +Subject: [PATCH] libmpathpersist: Don't try release workaround for invalid + type + +When trying to release a reservation, if the user specified the wrong +reservation type, libmpathpersist would try to preempt the reservation, +because the reservation key matched the device key, but it was not +removed. In this case, the preemption would also fail because it also +requires a matching type. + +Check if the reservation type matches, to avoid attempting the +workaround in this case. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 54408e91..ad2e0e80 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -880,6 +880,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + uint16_t udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; + bool did_resume = false; + bool all_threads_failed; ++ unsigned int scope_type; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -975,6 +976,13 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + return MPATH_PR_SUCCESS; + } + ++ scope_type = resp.prin_descriptor.prin_readresv.scope_type; ++ if ((scope_type & MPATH_PR_TYPE_MASK) != rq_type) { ++ condlog(2, "%s: --prout_type %u doesn't match reservation %u", ++ mpp->wwid, rq_type, scope_type & MPATH_PR_TYPE_MASK); ++ return MPATH_PR_RESERV_CONFLICT; ++ } ++ + condlog (2, "%s: Path holding reservation is not available.", mpp->wwid); + /* + * Cannot free the reservation because the path that is holding it diff --git a/SOURCES/0185-libmpathpersist-Don-t-fail-RESERVE-commands-unnecess.patch b/SOURCES/0185-libmpathpersist-Don-t-fail-RESERVE-commands-unnecess.patch new file mode 100644 index 0000000..9d0f770 --- /dev/null +++ b/SOURCES/0185-libmpathpersist-Don-t-fail-RESERVE-commands-unnecess.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 4 Sep 2025 16:33:27 -0400 +Subject: [PATCH] libmpathpersist: Don't fail RESERVE commands unnecessarily + +If you issue a RESERVE to a regular SCSI device that already holds the +reservation, it succeeds (and does nothing). If you issue a RESERVE to a +multipath device that already holds the reservation, it can fail with +a reservation conflict error if you issue the RESERVE to a path that isn't +holding the reservation. Instead, it should try all paths and +succeed if the reservation command succeeds on any of them. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index ad2e0e80..52fbc0aa 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -848,9 +848,20 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + * it may have just come back up, and multipathd + * may not have had time to update the key. Allow + * reservation conflicts. ++ * ++ * If you issue a RESERVE to a regular scsi device ++ * that already holds the reservation, it succeeds ++ * (and does nothing). A multipath device that ++ * holds the reservation should not return a ++ * reservation conflict on a RESERVE command, just ++ * because it issued the RESERVE to a path that ++ * isn't holding the reservation. It should instead ++ * keep trying to see if it succeeds on another ++ * path. + */ + if (ret == MPATH_PR_RESERV_CONFLICT && +- pp->dmstate == PSTATE_FAILED) { ++ (pp->dmstate == PSTATE_FAILED || ++ rq_servact == MPATH_PROUT_RES_SA)) { + conflict = true; + continue; + } diff --git a/SOURCES/0186-libmpathpersist-reregister-keys-when-self-preempting.patch b/SOURCES/0186-libmpathpersist-reregister-keys-when-self-preempting.patch new file mode 100644 index 0000000..e7d5c3f --- /dev/null +++ b/SOURCES/0186-libmpathpersist-reregister-keys-when-self-preempting.patch @@ -0,0 +1,203 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 5 Sep 2025 14:04:58 -0400 +Subject: [PATCH] libmpathpersist: reregister keys when self preempting + +When a SCSI device preempts its own reservation key, it will remove the +registered keys from all other devices with that reservation key, but +retain its registered key (and possibly acquire the reservation). If a +multipath device preempts its own reservation key, it will also remove +the registered keys from all its paths except the one issuing the +reservation. This means that IO to the device can fail if it goes to one +of these unregistered paths. + +To avoid this, whenever a multipath device preempts itself, it must +first suspend, then do the preemption and reregister the removed keys, +and finally resume the device. This is already what libmpathpersist does +if a release fails because the path holding the reservation is currently +unavailable, with the addition of releasing the reservation after +preempting it. This commit refactors that code into a separate function, +makes the release optional, and calls the new function, preempt_self(), +whenever a device preempts itself. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 136 +++++++++++++++++--------------- + 1 file changed, 71 insertions(+), 65 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 52fbc0aa..3d3b62dd 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -37,6 +37,9 @@ + + extern struct udev *udev; + ++static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, ++ unsigned int rq_type, int noisy, bool do_release); ++ + static void adapt_config(struct config *conf) + { + conf->force_sync = 1; +@@ -419,6 +422,12 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + case MPATH_PROUT_PREE_AB_SA : + if (reservation_key_matches(mpp, paramp->sa_key, noisy) == YNU_YES) + preempting_reservation = true; ++ /* if we are preempting ourself */ ++ if (memcmp(paramp->sa_key, paramp->key, 8) == 0) { ++ ret = preempt_self(mpp, rq_servact, rq_scope, rq_type, ++ noisy, false); ++ break; ++ } + /* fallthrough */ + case MPATH_PROUT_RES_SA : + case MPATH_PROUT_CLEAR_SA: +@@ -875,6 +884,65 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + return MPATH_PR_DMMP_ERROR; + } + ++/* ++ * Called to make a multipath device preempt its own reservation (and ++ * optionally release the reservation). Doing this causes the reservation ++ * keys to be removed from all the device paths except that path used to issue ++ * the preempt, so they need to be restored. To avoid the chance that IO ++ * goes to these paths when they don't have a registered key, the device ++ * is suspended before issuing the preemption, and the keys are reregistered ++ * before resuming it. ++ */ ++static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, ++ unsigned int rq_type, int noisy, bool do_release) ++{ ++ int status, rel_status = MPATH_PR_SUCCESS; ++ struct path *pp = NULL; ++ struct prout_param_descriptor paramp = {.sa_flags = 0}; ++ uint16_t udev_flags = (mpp->skip_kpartx) ? MPATH_UDEV_NO_KPARTX_FLAG : 0; ++ ++ if (!dm_simplecmd_noflush(DM_DEVICE_SUSPEND, mpp->alias, 0)) { ++ condlog(0, "%s: self preempt failed to suspend device.", mpp->wwid); ++ return MPATH_PR_OTHER; ++ } ++ ++ memcpy(paramp.key, &mpp->reservation_key, 8); ++ memcpy(paramp.sa_key, &mpp->reservation_key, 8); ++ status = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, ++ ¶mp, noisy, &pp); ++ if (status != MPATH_PR_SUCCESS) { ++ condlog(0, "%s: self preempt command failed.", mpp->wwid); ++ goto fail_resume; ++ } ++ ++ if (do_release) { ++ memset(¶mp, 0, sizeof(paramp)); ++ memcpy(paramp.key, &mpp->reservation_key, 8); ++ rel_status = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REL_SA, ++ rq_scope, rq_type, ¶mp, noisy); ++ if (rel_status != MPATH_PR_SUCCESS) ++ condlog(0, "%s: release on alternate path failed.", ++ mpp->wwid); ++ } ++ ++ memset(¶mp, 0, sizeof(paramp)); ++ memcpy(paramp.sa_key, &mpp->reservation_key, 8); ++ status = mpath_prout_reg(mpp, MPATH_PROUT_REG_IGN_SA, rq_scope, ++ rq_type, ¶mp, noisy); ++ if (status != MPATH_PR_SUCCESS) ++ condlog(0, "%s: self preempt failed to reregister paths.", ++ mpp->wwid); ++ ++fail_resume: ++ if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { ++ condlog(0, "%s: self preempt failed to resume device.", mpp->wwid); ++ if (status == MPATH_PR_SUCCESS) ++ status = MPATH_PR_OTHER; ++ } ++ /* return the first error we encountered */ ++ return (rel_status != MPATH_PR_SUCCESS) ? rel_status : status; ++} ++ + int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { +@@ -888,8 +956,6 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + int count = 0; + int status = MPATH_PR_SUCCESS; + struct prin_resp resp = {0}; +- uint16_t udev_flags = (mpp->skip_kpartx)? MPATH_UDEV_NO_KPARTX_FLAG : 0; +- bool did_resume = false; + bool all_threads_failed; + unsigned int scope_type; + +@@ -1004,70 +1070,10 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + * preempting one. Since the device is suspended, no IO can + * go to these unregistered paths and fail). + * 3. Releasing the reservation on the path that now holds it. +- * 4. Resuming the device (since it no longer matters that most of +- * that paths no longer have a registered key) +- * 5. Reregistering keys on all the paths ++ * 4. Reregistering keys on all the paths ++ * 5. Resuming the device + */ +- +- if (!dm_simplecmd_noflush(DM_DEVICE_SUSPEND, mpp->alias, 0)) { +- condlog(0, "%s: release: failed to suspend dm device.", mpp->wwid); +- return MPATH_PR_OTHER; +- } +- +- memset(paramp, 0, sizeof(*paramp)); +- memcpy(paramp->key, &mpp->reservation_key, 8); +- memcpy(paramp->sa_key, &mpp->reservation_key, 8); +- status = mpath_prout_common(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, +- paramp, noisy, &pp); +- if (status != MPATH_PR_SUCCESS) { +- condlog(0, "%s: release: pr preempt command failed.", mpp->wwid); +- goto fail_resume; +- } +- +- memset(paramp, 0, sizeof(*paramp)); +- memcpy(paramp->key, &mpp->reservation_key, 8); +- status = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REL_SA, rq_scope, +- rq_type, paramp, noisy); +- if (status != MPATH_PR_SUCCESS) { +- condlog(0, "%s: release on alternate path failed.", mpp->wwid); +- goto out_reregister; +- } +- +- if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { +- condlog(0, "%s release: failed to resume dm device.", mpp->wwid); +- /* +- * leave status set to MPATH_PR_SUCCESS, we will have another +- * chance to resume the device. +- */ +- goto out_reregister; +- } +- did_resume = true; +- +-out_reregister: +- memset(paramp, 0, sizeof(*paramp)); +- memcpy(paramp->sa_key, &mpp->reservation_key, 8); +- rc = mpath_prout_reg(mpp, MPATH_PROUT_REG_IGN_SA, rq_scope, rq_type, +- paramp, noisy); +- if (rc != MPATH_PR_SUCCESS) +- condlog(0, "%s: release: failed to reregister paths.", mpp->wwid); +- +- /* +- * If we failed releasing the reservation or resuming earlier +- * try resuming now. Otherwise, return with the reregistering status +- * This means we will report failure, even though the resevation +- * has been released, since the keys were not reregistered. +- */ +- if (did_resume) +- return rc; +- else if (status == MPATH_PR_SUCCESS) +- status = rc; +-fail_resume: +- if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { +- condlog(0, "%s: release: failed to resume dm device.", mpp->wwid); +- if (status == MPATH_PR_SUCCESS) +- status = MPATH_PR_OTHER; +- } +- return (status == MPATH_PR_RETRYABLE_ERROR) ? MPATH_PR_OTHER : status; ++ return preempt_self(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, noisy, true); + } + + void * mpath_alloc_prin_response(int prin_sa) diff --git a/SOURCES/0187-libmpathpersist-handle-updating-key-race-condition.patch b/SOURCES/0187-libmpathpersist-handle-updating-key-race-condition.patch new file mode 100644 index 0000000..e93d1ac --- /dev/null +++ b/SOURCES/0187-libmpathpersist-handle-updating-key-race-condition.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 9 Sep 2025 18:55:49 -0400 +Subject: [PATCH] libmpathpersist: handle updating key race condition + +If a multipath device's registered key is changed, mpathpersist needs +to update the key in multipathd before it registers the new key on the +paths. This means that there is a time when multipathd thinks the paths +should be using the new key, but none of them are. If a path is +restored after mpathpersist checks which paths are usable to set the +key on, but before it sets the key on any path, multipathd will see +that the new key is not registered on any paths, and think that the +key has been preempted or cleared. This will leave the path without +a key, and possibly make multipathd think the device does not hold +the reservation, even if it does. + +To avoid this, multipathd will now remember the old key when registering +a new one. Once the registration is finished, and (un)setprstatus is +called, multipathd will forget the old key. Until then, multipathd will +check for either key when it looks to see if there is an existing key. +If the registration fails and the key get reverted, multipathd will +also forget the old key. + +Signed-off-by: Benjamin Marzinski +--- + libmultipath/prkey.c | 16 ++++++++++++++-- + libmultipath/structs.h | 1 + + multipathd/main.c | 12 +++++++++++- + 3 files changed, 26 insertions(+), 3 deletions(-) + +diff --git a/libmultipath/prkey.c b/libmultipath/prkey.c +index d645f813..ab45f176 100644 +--- a/libmultipath/prkey.c ++++ b/libmultipath/prkey.c +@@ -176,10 +176,22 @@ int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, + } + else + ret = do_prkey(fd, mpp->wwid, NULL, PRKEY_WRITE); +- if (ret == 0) ++ if (ret == 0) { ++ /* ++ * If you are reverting back to the old key, because you ++ * did not successfully set a new key, don't remember the ++ * key you never successfully set. ++ */ ++ if (get_be64(mpp->old_pr_key) == prkey) ++ memset(&mpp->old_pr_key, 0, 8); ++ else ++ memcpy(&mpp->old_pr_key, &mpp->reservation_key, 8); + select_reservation_key(conf, mpp); +- if (get_be64(mpp->reservation_key) != prkey) ++ } ++ if (get_be64(mpp->reservation_key) != prkey) { ++ memset(&mpp->old_pr_key, 0, 8); + ret = 1; ++ } + out_file: + close(fd); + out: +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index a67e767d..2f69e831 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -468,6 +468,7 @@ struct multipath { + bool fpin_must_reload; + int prhold; + bool ever_registered_pr; ++ struct be64 old_pr_key; + }; + + static inline int marginal_path_check_enabled(const struct multipath *mpp) +diff --git a/multipathd/main.c b/multipathd/main.c +index 56d51c48..8a0d9edc 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3751,6 +3751,7 @@ void set_pr(struct multipath *mpp) + { + mpp->ever_registered_pr = true; + mpp->prflag = PR_SET; ++ memset(&mpp->old_pr_key, 0, 8); + } + + void unset_pr(struct multipath *mpp) +@@ -3758,6 +3759,7 @@ void unset_pr(struct multipath *mpp) + mpp->prflag = PR_UNSET; + mpp->prhold = PR_UNSET; + mpp->sa_flags = 0; ++ memset(&mpp->old_pr_key, 0, 8); + } + + /* +@@ -3806,7 +3808,15 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + dumpHex((char *)keyp, 8, 1); + } + +- if (!memcmp(&mpp->reservation_key, keyp, 8)) ++ /* ++ * If you are in the middle of updating a key (old_pr_key ++ * is set) check for either the new key or the old key, ++ * since you might be checking before any paths have ++ * updated their keys. ++ */ ++ if (!memcmp(&mpp->reservation_key, keyp, 8) || ++ (get_be64(mpp->old_pr_key) && ++ !memcmp(&mpp->old_pr_key, keyp, 8))) + isFound = 1; + } + diff --git a/SOURCES/0188-libmpathpersist-handle-preempting-all-registrants-re.patch b/SOURCES/0188-libmpathpersist-handle-preempting-all-registrants-re.patch new file mode 100644 index 0000000..a320788 --- /dev/null +++ b/SOURCES/0188-libmpathpersist-handle-preempting-all-registrants-re.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 10 Sep 2025 17:52:12 -0400 +Subject: [PATCH] libmpathpersist: handle preempting all registrants + reservations + +All Registrants reservations (types 7 and 8) are held by key 0x0. When +preempting one, all registrations are cleared, except for the one on the +path that issued the PREEMPT. libmpathpersist needs to handle this just +like the other cases of self-preemption, so that all the paths of the +preempting multipath device have their registrations restored while the +device is suspended. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 36 +++++++++++++++++++++++++++------ + 1 file changed, 30 insertions(+), 6 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 3d3b62dd..92a0021d 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -37,8 +37,9 @@ + + extern struct udev *udev; + +-static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, +- unsigned int rq_type, int noisy, bool do_release); ++static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, ++ int rq_servact, int rq_scope, unsigned int rq_type, ++ int noisy, bool do_release); + + static void adapt_config(struct config *conf) + { +@@ -321,6 +322,21 @@ static void set_ignored_key(struct multipath *mpp, uint8_t *key) + memcpy(key, &mpp->reservation_key, 8); + } + ++static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, ++ unsigned int rq_type, int noisy, bool do_release) ++{ ++ return do_preempt_self(mpp, mpp->reservation_key, rq_servact, rq_scope, ++ rq_type, noisy, do_release); ++} ++ ++static int preempt_all(struct multipath *mpp, int rq_servact, int rq_scope, ++ unsigned int rq_type, int noisy) ++{ ++ struct be64 zerokey = {0}; ++ return do_preempt_self(mpp, zerokey, rq_servact, rq_scope, rq_type, ++ noisy, false); ++} ++ + static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + int rq_servact, int rq_scope, unsigned int rq_type, + struct prout_param_descriptor *paramp, int noisy) +@@ -420,8 +436,15 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + break; + case MPATH_PROUT_PREE_SA : + case MPATH_PROUT_PREE_AB_SA : +- if (reservation_key_matches(mpp, paramp->sa_key, noisy) == YNU_YES) ++ if (reservation_key_matches(mpp, paramp->sa_key, noisy) == YNU_YES) { + preempting_reservation = true; ++ if (memcmp(paramp->sa_key, &zerokey, 8) == 0) { ++ /* all registrants case */ ++ ret = preempt_all(mpp, rq_servact, rq_scope, ++ rq_type, noisy); ++ break; ++ } ++ } + /* if we are preempting ourself */ + if (memcmp(paramp->sa_key, paramp->key, 8) == 0) { + ret = preempt_self(mpp, rq_servact, rq_scope, rq_type, +@@ -893,8 +916,9 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + * is suspended before issuing the preemption, and the keys are reregistered + * before resuming it. + */ +-static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, +- unsigned int rq_type, int noisy, bool do_release) ++static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, ++ int rq_servact, int rq_scope, unsigned int rq_type, ++ int noisy, bool do_release) + { + int status, rel_status = MPATH_PR_SUCCESS; + struct path *pp = NULL; +@@ -907,7 +931,7 @@ static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, + } + + memcpy(paramp.key, &mpp->reservation_key, 8); +- memcpy(paramp.sa_key, &mpp->reservation_key, 8); ++ memcpy(paramp.sa_key, &sa_key, 8); + status = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, + ¶mp, noisy, &pp); + if (status != MPATH_PR_SUCCESS) { diff --git a/SOURCES/0189-libmpathpersist-Fix-REGISTER-AND-IGNORE-while-holdin.patch b/SOURCES/0189-libmpathpersist-Fix-REGISTER-AND-IGNORE-while-holdin.patch new file mode 100644 index 0000000..98221e5 --- /dev/null +++ b/SOURCES/0189-libmpathpersist-Fix-REGISTER-AND-IGNORE-while-holdin.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 18 Sep 2025 18:12:07 -0400 +Subject: [PATCH] libmpathpersist: Fix REGISTER AND IGNORE while holding a + reservation + +If a device that is holding a reservation changes its registered key, +but the path holding the reservation is unavailable, libmpathpersist +must preempt the old key to update the reservation. If the key is +changed using REGISTER AND IGNORE, set_ignored_key() determines the old +key to preempt. Unfortunately, commit 165427dda broke it, by comparing +the wrong key against the actual reservation key. Then commit cf0eea85 +broke it more, by using mpp->reservation_key after it had been updated, +so it was no longer the old key. Fix this by correctly comparing the old +key against the actual reservation key. + +Fixes: 165427dda ("libmpathpersist: Add safety check for preempting on key change") +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 92a0021d..6a254d2e 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -309,17 +309,17 @@ static int reservation_key_matches(struct multipath *mpp, uint8_t *key, + * currently registered key for use in preempt_missing_path(), but only if + * the key is holding the reservation. + */ +-static void set_ignored_key(struct multipath *mpp, uint8_t *key) ++static void set_ignored_key(struct multipath *mpp, uint8_t *curr_key, ++ uint8_t *key) + { + memset(key, 0, 8); +- if (!get_be64(mpp->reservation_key)) ++ if (memcmp(curr_key, key, 8) == 0) + return; + if (get_prhold(mpp->alias) == PR_UNSET) + return; +- if (reservation_key_matches(mpp, (uint8_t *)&mpp->reservation_key, +- 0) == YNU_NO) ++ if (reservation_key_matches(mpp, curr_key, 0) == YNU_NO) + return; +- memcpy(key, &mpp->reservation_key, 8); ++ memcpy(key, curr_key, 8); + } + + static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, +@@ -359,6 +359,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + select_reservation_key(conf, mpp); + put_multipath_config(conf); + ++ memcpy(&oldkey, &mpp->reservation_key, 8); + unregistering = (memcmp(&zerokey, paramp->sa_key, 8) == 0); + if (mpp->prkey_source == PRKEY_SOURCE_FILE && + (rq_servact == MPATH_PROUT_REG_IGN_SA || +@@ -367,7 +368,6 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + memcmp(paramp->key, &zerokey, 8) == 0 || + memcmp(paramp->key, &mpp->reservation_key, 8) == 0)))) { + updated_prkey = true; +- memcpy(&oldkey, &mpp->reservation_key, 8); + memcpy(&mpp->reservation_key, paramp->sa_key, 8); + if (update_prkey_flags(alias, get_be64(mpp->reservation_key), + paramp->sa_flags)) { +@@ -426,7 +426,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + put_multipath_config(conf); + + if (rq_servact == MPATH_PROUT_REG_IGN_SA) +- set_ignored_key(mpp, paramp->key); ++ set_ignored_key(mpp, (uint8_t *)&oldkey, paramp->key); + + switch(rq_servact) + { diff --git a/SOURCES/0190-libmpathpersist-Handle-RESERVE-with-reservation-held.patch b/SOURCES/0190-libmpathpersist-Handle-RESERVE-with-reservation-held.patch new file mode 100644 index 0000000..e6f4d6c --- /dev/null +++ b/SOURCES/0190-libmpathpersist-Handle-RESERVE-with-reservation-held.patch @@ -0,0 +1,168 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 18 Sep 2025 19:29:08 -0400 +Subject: [PATCH] libmpathpersist: Handle RESERVE with reservation held by + failed path + +Issuing a RESERVE on a device that already holds the reservation should +succeed, as long as the type is the same. But if the path that holds the +reservation is unavailable, mpathpersist fails, since it gets a +reservation conflict on all available paths. To deal with this, if the +multipath device has failed paths, and the key holding the reservation +matches the multipath device's key, and multipathd says that it is +holding the reservation, assume the reservation is held by a failed path +and claim the RESERVE succeeded, even though none of the actual scsi +commands did + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 48 +++++++++++++++++++++++++-------- + 1 file changed, 37 insertions(+), 11 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 6a254d2e..fce28723 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -104,7 +104,7 @@ int libmpathpersist_exit(void) + static int mpath_prout_common(struct multipath *mpp,int rq_servact, + int rq_scope, unsigned int rq_type, + struct prout_param_descriptor* paramp, int noisy, +- struct path **pptr); ++ struct path **pptr, bool *failed_paths); + + int + mpath_prin_activepath (struct multipath *mpp, int rq_servact, +@@ -286,12 +286,12 @@ int __mpath_persistent_reserve_in (int fd, int rq_servact, + } + + static int reservation_key_matches(struct multipath *mpp, uint8_t *key, +- int noisy) ++ unsigned int *type) + { + struct prin_resp resp = {0}; + int status; + +- status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); ++ status = mpath_prin_activepath(mpp, MPATH_PRIN_RRES_SA, &resp, 0); + if (status != MPATH_PR_SUCCESS) { + condlog(0, "%s: pr in read reservation command failed.", + mpp->wwid); +@@ -299,8 +299,12 @@ static int reservation_key_matches(struct multipath *mpp, uint8_t *key, + } + if (!resp.prin_descriptor.prin_readresv.additional_length) + return YNU_NO; +- if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) == 0) ++ if (memcmp(key, resp.prin_descriptor.prin_readresv.key, 8) == 0) { ++ if (type) ++ *type = resp.prin_descriptor.prin_readresv.scope_type & ++ MPATH_PR_TYPE_MASK; + return YNU_YES; ++ } + return YNU_NO; + } + +@@ -317,11 +321,20 @@ static void set_ignored_key(struct multipath *mpp, uint8_t *curr_key, + return; + if (get_prhold(mpp->alias) == PR_UNSET) + return; +- if (reservation_key_matches(mpp, curr_key, 0) == YNU_NO) ++ if (reservation_key_matches(mpp, curr_key, NULL) == YNU_NO) + return; + memcpy(key, curr_key, 8); + } + ++static bool check_holding_reservation(struct multipath *mpp, unsigned int *type) ++{ ++ if (get_be64(mpp->reservation_key) && ++ get_prhold(mpp->alias) == PR_SET && ++ reservation_key_matches(mpp, (uint8_t *)&mpp->reservation_key, type) == YNU_YES) ++ return true; ++ return false; ++} ++ + static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, + unsigned int rq_type, int noisy, bool do_release) + { +@@ -349,6 +362,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + struct config *conf; + bool unregistering, preempting_reservation = false; + bool updated_prkey = false; ++ bool failed_paths = false; + + ret = mpath_get_map(curmp, fd, &alias, &mpp); + if (ret != MPATH_PR_SUCCESS) +@@ -436,7 +450,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + break; + case MPATH_PROUT_PREE_SA : + case MPATH_PROUT_PREE_AB_SA : +- if (reservation_key_matches(mpp, paramp->sa_key, noisy) == YNU_YES) { ++ if (reservation_key_matches(mpp, paramp->sa_key, NULL) == YNU_YES) { + preempting_reservation = true; + if (memcmp(paramp->sa_key, &zerokey, 8) == 0) { + /* all registrants case */ +@@ -453,10 +467,18 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + } + /* fallthrough */ + case MPATH_PROUT_RES_SA : +- case MPATH_PROUT_CLEAR_SA: ++ case MPATH_PROUT_CLEAR_SA: { ++ unsigned int res_type; + ret = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, +- paramp, noisy, NULL); ++ paramp, noisy, NULL, &failed_paths); ++ if (rq_servact == MPATH_PROUT_RES_SA && ++ ret != MPATH_PR_SUCCESS && failed_paths && ++ check_holding_reservation(mpp, &res_type) && ++ res_type == rq_type) ++ /* The reserve failed, but multipathd says we hold it */ ++ ret = MPATH_PR_SUCCESS; + break; ++ } + case MPATH_PROUT_REL_SA: + ret = mpath_prout_rel(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); + break; +@@ -614,7 +636,7 @@ void preempt_missing_path(struct multipath *mpp, uint8_t *key, uint8_t *sa_key, + memcpy(paramp.key, sa_key, 8); + memcpy(paramp.sa_key, key, 8); + status = mpath_prout_common(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, +- ¶mp, noisy, NULL); ++ ¶mp, noisy, NULL, NULL); + if (status != MPATH_PR_SUCCESS) + condlog(0, "%s: register: pr preempt command failed.", + mpp->wwid); +@@ -853,7 +875,7 @@ void * mpath_prout_pthread_fn(void *p) + static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, + struct prout_param_descriptor* paramp, int noisy, +- struct path **pptr) ++ struct path **pptr, bool *failed_paths) + { + int i,j, ret; + struct pathgroup *pgp = NULL; +@@ -866,6 +888,8 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + if (!((pp->state == PATH_UP) || (pp->state == PATH_GHOST))){ + condlog (1, "%s: %s path not up. Skip", + mpp->wwid, pp->dev); ++ if (failed_paths) ++ *failed_paths = true; + continue; + } + +@@ -899,6 +923,8 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + } + if (ret != MPATH_PR_RETRYABLE_ERROR) + return ret; ++ if (failed_paths) ++ *failed_paths = true; + } + } + if (found) +@@ -933,7 +959,7 @@ static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, + memcpy(paramp.key, &mpp->reservation_key, 8); + memcpy(paramp.sa_key, &sa_key, 8); + status = mpath_prout_common(mpp, rq_servact, rq_scope, rq_type, +- ¶mp, noisy, &pp); ++ ¶mp, noisy, &pp, NULL); + if (status != MPATH_PR_SUCCESS) { + condlog(0, "%s: self preempt command failed.", mpp->wwid); + goto fail_resume; diff --git a/SOURCES/0191-libmpathpersist-use-check_holding_reservation-in-mpa.patch b/SOURCES/0191-libmpathpersist-use-check_holding_reservation-in-mpa.patch new file mode 100644 index 0000000..02661cf --- /dev/null +++ b/SOURCES/0191-libmpathpersist-use-check_holding_reservation-in-mpa.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 22 Sep 2025 17:40:00 -0400 +Subject: [PATCH] libmpathpersist: use check_holding_reservation in + mpath_prout_rel + +Instead of open-coding mostly the same work, just make mpath_prout_rel() +call check_holding_reservation(). + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 24 ++++-------------------- + 1 file changed, 4 insertions(+), 20 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index fce28723..15141be4 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -997,7 +997,6 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) + { + int i, j; +- int num = 0; + struct pathgroup *pgp = NULL; + struct path *pp = NULL; + int active_pathcount = 0; +@@ -1005,9 +1004,8 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + int rc; + int count = 0; + int status = MPATH_PR_SUCCESS; +- struct prin_resp resp = {0}; + bool all_threads_failed; +- unsigned int scope_type; ++ unsigned int res_type; + + if (!mpp) + return MPATH_PR_DMMP_ERROR; +@@ -1086,27 +1084,13 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + return status; + } + +- status = mpath_prin_activepath (mpp, MPATH_PRIN_RRES_SA, &resp, noisy); +- if (status != MPATH_PR_SUCCESS){ +- condlog (0, "%s: pr in read reservation command failed.", mpp->wwid); +- return MPATH_PR_OTHER; +- } +- +- num = resp.prin_descriptor.prin_readresv.additional_length / 8; +- if (num == 0){ +- condlog (2, "%s: Path holding reservation is released.", mpp->wwid); +- return MPATH_PR_SUCCESS; +- } +- if (!get_be64(mpp->reservation_key) || +- memcmp(&mpp->reservation_key, resp.prin_descriptor.prin_readresv.key, 8)) { ++ if (!check_holding_reservation(mpp, &res_type)) { + condlog(2, "%s: Releasing key not holding reservation.", mpp->wwid); + return MPATH_PR_SUCCESS; + } +- +- scope_type = resp.prin_descriptor.prin_readresv.scope_type; +- if ((scope_type & MPATH_PR_TYPE_MASK) != rq_type) { ++ if (res_type != rq_type) { + condlog(2, "%s: --prout_type %u doesn't match reservation %u", +- mpp->wwid, rq_type, scope_type & MPATH_PR_TYPE_MASK); ++ mpp->wwid, rq_type, res_type); + return MPATH_PR_RESERV_CONFLICT; + } + diff --git a/SOURCES/0192-libmpathpersist-Fix-unregistering-while-holding-the-.patch b/SOURCES/0192-libmpathpersist-Fix-unregistering-while-holding-the-.patch new file mode 100644 index 0000000..c2ea130 --- /dev/null +++ b/SOURCES/0192-libmpathpersist-Fix-unregistering-while-holding-the-.patch @@ -0,0 +1,287 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Fri, 19 Sep 2025 18:17:20 -0400 +Subject: [PATCH] libmpathpersist: Fix unregistering while holding the + reservation + +There were two problems with how libmpathpersist handled unregistering +a key while holding the reseravation (which should also release the +reservation). +1. If the path holding the reservation is not unregistered first, there + will be unregistered paths, while a reservation is still held, which + would cause IO to those paths to fail, when it shouldn't. +2. If the path that holds the reservation is down, libmpathpersist was + not clearing the reservation, since the there were no registered keys + it could use for the PREEMPT command workaround + +To fix these, libmpathpersist now releases the reservation first when +trying to unregister a key that is holding the reservation. +mpath_prout_rel() has a new option so that if it needs to self preempt +to clear the reservation, it won't re-register the paths when called +as part of unregistering a key. Also, instead of checking if the device +is currently holding a reservation using mpp->reservation_key in +check_holding_reservation() (which will already be set to 0 when called +as part of unregistering a key), pass in the key to check. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 111 +++++++++++++++++++++++--------- + libmpathpersist/mpath_persist.h | 3 + + libmpathpersist/mpathpr.h | 3 +- + 3 files changed, 84 insertions(+), 33 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 15141be4..72be48c1 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -37,9 +37,15 @@ + + extern struct udev *udev; + ++enum preempt_work { ++ PREE_WORK_NONE, ++ PREE_WORK_REL, ++ PREE_WORK_REL_UNREG, ++}; ++ + static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, + int rq_servact, int rq_scope, unsigned int rq_type, +- int noisy, bool do_release); ++ int noisy, enum preempt_work work); + + static void adapt_config(struct config *conf) + { +@@ -326,20 +332,22 @@ static void set_ignored_key(struct multipath *mpp, uint8_t *curr_key, + memcpy(key, curr_key, 8); + } + +-static bool check_holding_reservation(struct multipath *mpp, unsigned int *type) ++static bool check_holding_reservation(struct multipath *mpp, uint8_t *curr_key, ++ unsigned int *type) + { +- if (get_be64(mpp->reservation_key) && ++ uint64_t zerokey = 0; ++ if (memcmp(curr_key, &zerokey, 8) != 0 && + get_prhold(mpp->alias) == PR_SET && +- reservation_key_matches(mpp, (uint8_t *)&mpp->reservation_key, type) == YNU_YES) ++ reservation_key_matches(mpp, curr_key, type) == YNU_YES) + return true; + return false; + } + + static int preempt_self(struct multipath *mpp, int rq_servact, int rq_scope, +- unsigned int rq_type, int noisy, bool do_release) ++ unsigned int rq_type, int noisy, enum preempt_work work) + { + return do_preempt_self(mpp, mpp->reservation_key, rq_servact, rq_scope, +- rq_type, noisy, do_release); ++ rq_type, noisy, work); + } + + static int preempt_all(struct multipath *mpp, int rq_servact, int rq_scope, +@@ -347,7 +355,7 @@ static int preempt_all(struct multipath *mpp, int rq_servact, int rq_scope, + { + struct be64 zerokey = {0}; + return do_preempt_self(mpp, zerokey, rq_servact, rq_scope, rq_type, +- noisy, false); ++ noisy, PREE_WORK_NONE); + } + + static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, +@@ -373,7 +381,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + select_reservation_key(conf, mpp); + put_multipath_config(conf); + +- memcpy(&oldkey, &mpp->reservation_key, 8); ++ oldkey = mpp->reservation_key; + unregistering = (memcmp(&zerokey, paramp->sa_key, 8) == 0); + if (mpp->prkey_source == PRKEY_SOURCE_FILE && + (rq_servact == MPATH_PROUT_REG_IGN_SA || +@@ -446,7 +454,27 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + { + case MPATH_PROUT_REG_SA: + case MPATH_PROUT_REG_IGN_SA: +- ret= mpath_prout_reg(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); ++ if (unregistering && check_holding_reservation(mpp, (uint8_t *)&oldkey, &rq_type)) { ++ struct be64 newkey = mpp->reservation_key; ++ /* temporarily restore reservation key */ ++ mpp->reservation_key = oldkey; ++ ret = mpath_prout_rel(mpp, MPATH_PROUT_REL_SA, rq_scope, ++ rq_type, paramp, noisy, true); ++ mpp->reservation_key = newkey; ++ if (ret == MPATH_PR_SUCCESS) ++ /* ++ * Since unregistering it true, paramp->sa_key ++ * must be zero here. So this command will ++ * unregister the key. ++ */ ++ ret = mpath_prout_reg(mpp, rq_servact, rq_scope, ++ rq_type, paramp, noisy); ++ else if (ret == MPATH_PR_SUCCESS_UNREGISTER) ++ /* We already unregistered the key */ ++ ret = MPATH_PR_SUCCESS; ++ } else ++ ret = mpath_prout_reg(mpp, rq_servact, rq_scope, ++ rq_type, paramp, noisy); + break; + case MPATH_PROUT_PREE_SA : + case MPATH_PROUT_PREE_AB_SA : +@@ -462,7 +490,7 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + /* if we are preempting ourself */ + if (memcmp(paramp->sa_key, paramp->key, 8) == 0) { + ret = preempt_self(mpp, rq_servact, rq_scope, rq_type, +- noisy, false); ++ noisy, PREE_WORK_NONE); + break; + } + /* fallthrough */ +@@ -473,14 +501,14 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + paramp, noisy, NULL, &failed_paths); + if (rq_servact == MPATH_PROUT_RES_SA && + ret != MPATH_PR_SUCCESS && failed_paths && +- check_holding_reservation(mpp, &res_type) && ++ check_holding_reservation(mpp, paramp->key, &res_type) && + res_type == rq_type) + /* The reserve failed, but multipathd says we hold it */ + ret = MPATH_PR_SUCCESS; + break; + } + case MPATH_PROUT_REL_SA: +- ret = mpath_prout_rel(mpp, rq_servact, rq_scope, rq_type, paramp, noisy); ++ ret = mpath_prout_rel(mpp, rq_servact, rq_scope, rq_type, paramp, noisy, false); + break; + default: + ret = MPATH_PR_OTHER; +@@ -935,16 +963,16 @@ static int mpath_prout_common(struct multipath *mpp,int rq_servact, int rq_scope + + /* + * Called to make a multipath device preempt its own reservation (and +- * optionally release the reservation). Doing this causes the reservation +- * keys to be removed from all the device paths except that path used to issue +- * the preempt, so they need to be restored. To avoid the chance that IO +- * goes to these paths when they don't have a registered key, the device +- * is suspended before issuing the preemption, and the keys are reregistered +- * before resuming it. ++ * optional extra work). Doing this causes the reservation keys to be removed ++ * from all the device paths except that path used to issue the preempt, so ++ * they may need to be restored. To avoid the chance that IO goes to these ++ * paths when they don't have a registered key and a reservation exists, the ++ * device is suspended before issuing the preemption, and the keys are ++ * reregistered (or the reservation is released) before resuming it. + */ + static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, + int rq_servact, int rq_scope, unsigned int rq_type, +- int noisy, bool do_release) ++ int noisy, enum preempt_work work) + { + int status, rel_status = MPATH_PR_SUCCESS; + struct path *pp = NULL; +@@ -965,7 +993,7 @@ static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, + goto fail_resume; + } + +- if (do_release) { ++ if (work != PREE_WORK_NONE) { + memset(¶mp, 0, sizeof(paramp)); + memcpy(paramp.key, &mpp->reservation_key, 8); + rel_status = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REL_SA, +@@ -973,15 +1001,26 @@ static int do_preempt_self(struct multipath *mpp, struct be64 sa_key, + if (rel_status != MPATH_PR_SUCCESS) + condlog(0, "%s: release on alternate path failed.", + mpp->wwid); ++ else if (work == PREE_WORK_REL_UNREG) { ++ /* unregister the last path */ ++ rel_status = prout_do_scsi_ioctl(pp->dev, ++ MPATH_PROUT_REG_IGN_SA, ++ rq_scope, rq_type, ++ ¶mp, noisy); ++ if (rel_status != MPATH_PR_SUCCESS) ++ condlog(0, "%s: final self preempt unregister failed,", ++ mpp->wwid); ++ } ++ } ++ if (work != PREE_WORK_REL_UNREG) { ++ memset(¶mp, 0, sizeof(paramp)); ++ memcpy(paramp.sa_key, &mpp->reservation_key, 8); ++ status = mpath_prout_reg(mpp, MPATH_PROUT_REG_IGN_SA, rq_scope, ++ rq_type, ¶mp, noisy); ++ if (status != MPATH_PR_SUCCESS) ++ condlog(0, "%s: self preempt failed to reregister paths.", ++ mpp->wwid); + } +- +- memset(¶mp, 0, sizeof(paramp)); +- memcpy(paramp.sa_key, &mpp->reservation_key, 8); +- status = mpath_prout_reg(mpp, MPATH_PROUT_REG_IGN_SA, rq_scope, +- rq_type, ¶mp, noisy); +- if (status != MPATH_PR_SUCCESS) +- condlog(0, "%s: self preempt failed to reregister paths.", +- mpp->wwid); + + fail_resume: + if (!dm_simplecmd_noflush(DM_DEVICE_RESUME, mpp->alias, udev_flags)) { +@@ -993,8 +1032,9 @@ fail_resume: + return (rel_status != MPATH_PR_SUCCESS) ? rel_status : status; + } + +-int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy) ++int mpath_prout_rel(struct multipath *mpp, int rq_servact, int rq_scope, ++ unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, ++ bool unregister) + { + int i, j; + struct pathgroup *pgp = NULL; +@@ -1084,7 +1124,8 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + return status; + } + +- if (!check_holding_reservation(mpp, &res_type)) { ++ if (!check_holding_reservation(mpp, (uint8_t *)&mpp->reservation_key, ++ &res_type)) { + condlog(2, "%s: Releasing key not holding reservation.", mpp->wwid); + return MPATH_PR_SUCCESS; + } +@@ -1107,7 +1148,13 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + * 4. Reregistering keys on all the paths + * 5. Resuming the device + */ +- return preempt_self(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, noisy, true); ++ status = preempt_self(mpp, MPATH_PROUT_PREE_SA, rq_scope, rq_type, ++ noisy, ++ unregister ? PREE_WORK_REL_UNREG : PREE_WORK_REL); ++ if (status == MPATH_PR_SUCCESS && unregister) ++ return MPATH_PR_SUCCESS_UNREGISTER; ++ return status; ++ + } + + void * mpath_alloc_prin_response(int prin_sa) +diff --git a/libmpathpersist/mpath_persist.h b/libmpathpersist/mpath_persist.h +index 8d441d04..807787b5 100644 +--- a/libmpathpersist/mpath_persist.h ++++ b/libmpathpersist/mpath_persist.h +@@ -66,6 +66,9 @@ extern "C" { + #define MPATH_PR_RETRYABLE_ERROR 16 /* error that might be succeed + down another path. Internal + only. */ ++#define MPATH_PR_SUCCESS_UNREGISTER 17 /* Success, and additionally, all ++ paths were unregistered. ++ Internal only. */ + + /* PR MASK */ + #define MPATH_F_APTPL_MASK 0x01 /* APTPL MASK*/ +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 27df5fdd..69f402f2 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -37,7 +37,8 @@ void dumpHex(const char* , int len, int no_ascii); + int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, +- unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); ++ unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, ++ bool unregister); + + int update_prflag(char *mapname, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); diff --git a/SOURCES/0193-libmpathpersist-Fix-race-between-restoring-a-path-an.patch b/SOURCES/0193-libmpathpersist-Fix-race-between-restoring-a-path-an.patch new file mode 100644 index 0000000..899308d --- /dev/null +++ b/SOURCES/0193-libmpathpersist-Fix-race-between-restoring-a-path-an.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 30 Sep 2025 19:36:36 -0400 +Subject: [PATCH] libmpathpersist: Fix race between restoring a path and + preemption + +If a multipath device gets preempted while a path is being restored, the +path could end up getting the old key registered on it anyways. This +happens by: +1. multipathd calling update_map_pr() in mpath_pr_event_handle() and + seeing that there are matching keys. +2. Those matching keys get preempted. +3. multipathd registering the preempted key on the path. + +Since there is no way to guarantee that the key won't get preempted +after multipathd checks for it, mpath_pr_event_handle() now calls +update_map_pr() to check the registered keys again after registering the +key. There must be at least as many keys registered after registering +the key on the restored path (which may already have a key registered on +it, so it's possible that the number of registered keys doesn't change). + +pr_register_active_paths() calls mpath_pr_event_handle() for all working +paths on a device. In order to avoid calling update_map_pr() twice for +every path, mpath_pr_event_handle() now takes the number of keys to +expect, and skips the initial call to update_map_pr() if it already +knows how many keys are registered before trying to register the new +path. + +Signed-off-by: Benjamin Marzinski +--- + multipathd/main.c | 78 +++++++++++++++++++++++++++++++---------------- + 1 file changed, 52 insertions(+), 26 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index 8a0d9edc..4b0935b5 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -91,7 +91,8 @@ + #define CMDSIZE 160 + #define MSG_SIZE 32 + +-static void mpath_pr_event_handle(struct path *pp); ++static unsigned int ++mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed); + + #define LOG_MSG(lvl, pp) \ + do { \ +@@ -549,7 +550,7 @@ flush_map_nopaths(struct multipath *mpp, struct vectors *vecs) { + static void + pr_register_active_paths(struct multipath *mpp) + { +- unsigned int i, j; ++ unsigned int i, j, nr_keys = 0; + struct path *pp; + struct pathgroup *pgp; + +@@ -558,7 +559,7 @@ pr_register_active_paths(struct multipath *mpp) + if (mpp->prflag == PR_UNSET) + return; + if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) +- mpath_pr_event_handle(pp); ++ nr_keys = mpath_pr_event_handle(pp, nr_keys); + } + } + } +@@ -1184,7 +1185,7 @@ rescan: + verify_paths(mpp); + mpp->action = ACT_RELOAD; + prflag = mpp->prflag; +- mpath_pr_event_handle(pp); ++ mpath_pr_event_handle(pp, 0); + } else { + if (!should_multipath(pp, vecs->pathvec, vecs->mpvec)) { + orphan_path(pp, "only one path"); +@@ -2546,7 +2547,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + */ + condlog(2, "%s: checking persistent " + "reservation registration", pp->dev); +- mpath_pr_event_handle(pp); ++ mpath_pr_event_handle(pp, 0); + if (pp->mpp->prflag == PR_SET && + prflag != PR_SET) + pr_register_active_paths(pp->mpp); +@@ -3766,12 +3767,16 @@ void unset_pr(struct multipath *mpp) + * Returns MPATH_PR_SUCCESS unless if fails to read the PR keys. If + * MPATH_PR_SUCCESS is returned, mpp->prflag will be either PR_SET or + * PR_UNSET. ++ * ++ * The number of found keys must be at least as large as *nr_keys, ++ * and if MPATH_PR_SUCCESS is returned and mpp->prflag is PR_SET after ++ * the call, *nr_keys will be set to the number of found keys. + */ +-static int update_map_pr(struct multipath *mpp, struct path *pp) ++static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *nr_keys) + { + struct prin_resp resp; +- unsigned int i; +- int ret, isFound; ++ unsigned int i, nr_found = 0; ++ int ret; + bool was_set = (mpp->prflag == PR_SET); + + /* If pr is explicitly unset, it must be manually set */ +@@ -3798,7 +3803,6 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + condlog(4, "%s: Multipath reservation_key: 0x%" PRIx64 " ", mpp->alias, + get_be64(mpp->reservation_key)); + +- isFound = 0; + for (i = 0; i < resp.prin_descriptor.prin_readkeys.additional_length / 8; i++) { + uint8_t *keyp = &resp.prin_descriptor.prin_readkeys.key_list[i * 8]; + +@@ -3817,42 +3821,57 @@ static int update_map_pr(struct multipath *mpp, struct path *pp) + if (!memcmp(&mpp->reservation_key, keyp, 8) || + (get_be64(mpp->old_pr_key) && + !memcmp(&mpp->old_pr_key, keyp, 8))) +- isFound = 1; ++ nr_found++; + } + +- if (isFound) { ++ if (nr_found >= *nr_keys) { + set_pr(mpp); +- condlog(was_set ? 3 : 2, "%s: key found. prflag set.", +- mpp->alias); ++ condlog(was_set ? 3 : 2, "%s: %u keys found. prflag set.", ++ mpp->alias, nr_found); ++ *nr_keys = nr_found; + } else { + unset_pr(mpp); +- condlog(was_set ? 1 : 3, "%s: key not found. prflag unset.", +- mpp->alias); ++ condlog(was_set ? 1 : 3, ++ "%s: %u keys found. needed %u. prflag unset.", ++ mpp->alias, nr_found, *nr_keys); + } + + return MPATH_PR_SUCCESS; + } + +-static void mpath_pr_event_handle(struct path *pp) ++/* ++ * This function is called with the number of registered keys that should be ++ * seen for this device to know that the key has not been preempted while the ++ * path was getting registered. If 0 is passed in, update_mpath_pr is called ++ * before registering the key to figure out the number, assuming that at ++ * least one key must exist. ++ * ++ * The function returns the number of keys that are registered or 0 if ++ * it's unknown. ++ */ ++static unsigned int mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed) + { + struct multipath *mpp = pp->mpp; + int ret; +- struct prout_param_descriptor param; ++ struct prout_param_descriptor param = {.sa_flags = 0}; + bool clear_reg = false; + + if (pp->bus != SYSFS_BUS_SCSI) { + unset_pr(mpp); +- return; ++ return 0; + } + +- if (update_map_pr(mpp, pp) != MPATH_PR_SUCCESS) +- return; ++ if (nr_keys_needed == 0) { ++ nr_keys_needed = 1; ++ if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS) ++ return 0; ++ } + + check_prhold(mpp, pp); + + if (mpp->prflag != PR_SET) { + if (!mpp->ever_registered_pr) +- return; ++ return 0; + /* + * This path may have been unusable and either the + * registration was cleared or the registered +@@ -3865,21 +3884,28 @@ static void mpath_pr_event_handle(struct path *pp) + clear_reg = true; + } + +- memset(¶m, 0, sizeof(param)); +- + if (!clear_reg) { + param.sa_flags = mpp->sa_flags; + memcpy(param.sa_key, &mpp->reservation_key, 8); + param.num_transportid = 0; + } +- ++retry: + condlog(3, "%s registration for device %s:%s", + clear_reg ? "Clearing" : "Setting", pp->dev, pp->mpp->wwid); + + ret = prout_do_scsi_ioctl(pp->dev, MPATH_PROUT_REG_IGN_SA, 0, 0, ¶m, 0); +- if (ret != MPATH_PR_SUCCESS ) +- { ++ if (ret != MPATH_PR_SUCCESS) { + condlog(0, "%s: %s reservation registration failed. Error: %d", + clear_reg ? "Clearing" : "Setting", pp->dev, ret); ++ } else if (!clear_reg) { ++ if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS) ++ return 0; ++ if (mpp->prflag != PR_SET) { ++ memset(¶m, 0, sizeof(param)); ++ clear_reg = true; ++ goto retry; ++ } ++ return nr_keys_needed; + } ++ return 0; + } diff --git a/SOURCES/0194-multipathd-Fix-tracking-of-old-PR-key.patch b/SOURCES/0194-multipathd-Fix-tracking-of-old-PR-key.patch new file mode 100644 index 0000000..30a0c84 --- /dev/null +++ b/SOURCES/0194-multipathd-Fix-tracking-of-old-PR-key.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Tue, 30 Sep 2025 23:32:11 -0400 +Subject: [PATCH] multipathd: Fix tracking of old PR key + +When libmpathpersist is in the process of changing a registered key, +multipathd needs to remember both the old and new values of the key to +deal with a race between limpathpersist updating the key and multipathd +restoring a failed path. It was supposed to stop remembering the old +value when libmpathpersist was done changing the key and issued a +"setprstatus" command. However, clearing the old key was done in +set_pr(), which only gets called by cli_setprstatus() when registering a +new device, not when changing the key on a registered device. Also, +set_pr() is called by update_map_pr(). This means that multipathd was +forgetting the key when it shouldn't and not when it should. Fix this +to only forget the key when cli_setprstatus() is called, and always +then. + +Fixes: 1aeffa9e ("libmpathpersist: handle updating key race condition") +Signed-off-by: Benjamin Marzinski +--- + multipathd/cli_handlers.c | 1 + + multipathd/main.c | 1 - + 2 files changed, 1 insertion(+), 1 deletion(-) + +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index 93768ef3..541dd700 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -1326,6 +1326,7 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + set_pr(mpp); + condlog(2, "%s: prflag set", param); + } ++ memset(&mpp->old_pr_key, 0, 8); + + + return 0; +diff --git a/multipathd/main.c b/multipathd/main.c +index 4b0935b5..1811747e 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3752,7 +3752,6 @@ void set_pr(struct multipath *mpp) + { + mpp->ever_registered_pr = true; + mpp->prflag = PR_SET; +- memset(&mpp->old_pr_key, 0, 8); + } + + void unset_pr(struct multipath *mpp) diff --git a/SOURCES/0195-multipathd-Fix-race-while-registering-PR-key.patch b/SOURCES/0195-multipathd-Fix-race-while-registering-PR-key.patch new file mode 100644 index 0000000..8e2a84f --- /dev/null +++ b/SOURCES/0195-multipathd-Fix-race-while-registering-PR-key.patch @@ -0,0 +1,246 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Thu, 6 Nov 2025 20:08:14 -0500 +Subject: [PATCH] multipathd: Fix race while registering PR key + +When libmpathpersist registers a key, It first checks which paths are +active, then registers the key on those paths, and then tells multipathd +that the key has been registered, and it should start tracking it. If +a path comes up after libmpathpersist checks for active paths, but before +it tells multipathd that the key has been registered, multipathd will not +register a key no that path (since it hasn't been told to that the device +has a registered key yet). This can leave the device with a path that is +missing a key. + +To solve this, when multipathd is told that a key has been registered, +it checks if there are the same number of registered keys as active +paths. If there aren't, it registers keys on all the paths (at least +until the number of registered keys and active paths are equal). To +avoid doing a bunch of unnecessary PR work, pr_register_active_paths() +has a new option to track the number of active paths, and +mpath_pr_event_handle() now takes the number of keys to expect. The +first time it's called, when there is an unknown number of keys, if the +number of keys it finds matches the number of active_paths, it and +pr_register_active_paths() exit early, since there is no registering +work to do. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + multipathd/cli_handlers.c | 6 +++- + multipathd/main.c | 67 ++++++++++++++++++++++++++++----------- + multipathd/main.h | 1 + + 3 files changed, 54 insertions(+), 20 deletions(-) + +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index 541dd700..0c63ca9a 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -1324,7 +1324,11 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + + if (mpp->prflag != PR_SET) { + set_pr(mpp); +- condlog(2, "%s: prflag set", param); ++ pr_register_active_paths(mpp, true); ++ if (mpp->prflag == PR_SET) ++ condlog(2, "%s: prflag set", param); ++ else ++ condlog(0, "%s: Failed to set prflag", param); + } + memset(&mpp->old_pr_key, 0, 8); + +diff --git a/multipathd/main.c b/multipathd/main.c +index 1811747e..a85c0db4 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -92,7 +92,8 @@ + #define MSG_SIZE 32 + + static unsigned int +-mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed); ++mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed, ++ unsigned int nr_keys_wanted); + + #define LOG_MSG(lvl, pp) \ + do { \ +@@ -547,19 +548,28 @@ flush_map_nopaths(struct multipath *mpp, struct vectors *vecs) { + return true; + } + +-static void +-pr_register_active_paths(struct multipath *mpp) ++void pr_register_active_paths(struct multipath *mpp, bool check_nr_active) + { + unsigned int i, j, nr_keys = 0; ++ unsigned int nr_active = 0; + struct path *pp; + struct pathgroup *pgp; + ++ if (check_nr_active) { ++ nr_active = count_active_paths(mpp); ++ if (!nr_active) ++ return; ++ } ++ + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { + if (mpp->prflag == PR_UNSET) + return; +- if ((pp->state == PATH_UP) || (pp->state == PATH_GHOST)) +- nr_keys = mpath_pr_event_handle(pp, nr_keys); ++ if (pp->state == PATH_UP || pp->state == PATH_GHOST) { ++ nr_keys = mpath_pr_event_handle(pp, nr_keys, nr_active); ++ if (check_nr_active && nr_keys == nr_active) ++ return; ++ } + } + } + } +@@ -646,7 +656,7 @@ fail: + + sync_map_state(mpp); + +- pr_register_active_paths(mpp); ++ pr_register_active_paths(mpp, false); + + if (VECTOR_SIZE(offline_paths) != 0) + handle_orphaned_offline_paths(offline_paths); +@@ -1185,7 +1195,7 @@ rescan: + verify_paths(mpp); + mpp->action = ACT_RELOAD; + prflag = mpp->prflag; +- mpath_pr_event_handle(pp, 0); ++ mpath_pr_event_handle(pp, 0, 0); + } else { + if (!should_multipath(pp, vecs->pathvec, vecs->mpvec)) { + orphan_path(pp, "only one path"); +@@ -1268,9 +1278,8 @@ rescan: + sync_map_state(mpp); + + if (retries >= 0) { +- if ((mpp->prflag == PR_SET && prflag != PR_SET) || +- start_waiter) +- pr_register_active_paths(mpp); ++ if ((mpp->prflag == PR_SET && prflag != PR_SET) || start_waiter) ++ pr_register_active_paths(mpp, false); + condlog(2, "%s [%s]: path added to devmap %s", + pp->dev, pp->dev_t, mpp->alias); + return 0; +@@ -2547,10 +2556,10 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + */ + condlog(2, "%s: checking persistent " + "reservation registration", pp->dev); +- mpath_pr_event_handle(pp, 0); ++ mpath_pr_event_handle(pp, 0, 0); + if (pp->mpp->prflag == PR_SET && + prflag != PR_SET) +- pr_register_active_paths(pp->mpp); ++ pr_register_active_paths(pp->mpp, false); + } + } + +@@ -2887,7 +2896,7 @@ configure (struct vectors * vecs) + vector_foreach_slot(mpvec, mpp, i){ + if (remember_wwid(mpp->wwid) == 1) + trigger_paths_udev_change(mpp, true); +- pr_register_active_paths(mpp); ++ pr_register_active_paths(mpp, false); + } + + /* +@@ -3769,7 +3778,8 @@ void unset_pr(struct multipath *mpp) + * + * The number of found keys must be at least as large as *nr_keys, + * and if MPATH_PR_SUCCESS is returned and mpp->prflag is PR_SET after +- * the call, *nr_keys will be set to the number of found keys. ++ * the call, *nr_keys will be set to the number of found keys. Otherwise ++ * it will be set to 0. + */ + static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *nr_keys) + { +@@ -3779,13 +3789,18 @@ static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *n + bool was_set = (mpp->prflag == PR_SET); + + /* If pr is explicitly unset, it must be manually set */ +- if (mpp->prflag == PR_UNSET) ++ if (mpp->prflag == PR_UNSET) { ++ *nr_keys = 0; + return MPATH_PR_SUCCESS; ++ } + + if (!get_be64(mpp->reservation_key)) { + /* Nothing to do. Assuming pr mgmt feature is disabled*/ + unset_pr(mpp); +- condlog(was_set ? 2 : 4, "%s: reservation_key not set in multipath.conf", mpp->alias); ++ condlog(was_set ? 2 : 4, ++ "%s: reservation_key not set in multipath.conf", ++ mpp->alias); ++ *nr_keys = 0; + return MPATH_PR_SUCCESS; + } + +@@ -3795,7 +3810,9 @@ static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *n + if (ret != MPATH_PR_SUCCESS) { + if (ret == MPATH_PR_ILLEGAL_REQ) + unset_pr(mpp); +- condlog(0,"%s : pr in read keys service action failed Error=%d", mpp->alias, ret); ++ condlog(0, "%s : pr in read keys service action failed Error=%d", ++ mpp->alias, ret); ++ *nr_keys = 0; + return ret; + } + +@@ -3833,22 +3850,32 @@ static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *n + condlog(was_set ? 1 : 3, + "%s: %u keys found. needed %u. prflag unset.", + mpp->alias, nr_found, *nr_keys); ++ *nr_keys = 0; + } + + return MPATH_PR_SUCCESS; + } + + /* +- * This function is called with the number of registered keys that should be ++ * This function is called with two numbers ++ * ++ * nr_keys_needed: the number of registered keys that should be + * seen for this device to know that the key has not been preempted while the + * path was getting registered. If 0 is passed in, update_mpath_pr is called + * before registering the key to figure out the number, assuming that at + * least one key must exist. + * ++ * nr_keys_wanted: Only used if nr_keys_needed is 0, so we don't know how ++ * many keys we currently have. If nr_keys_wanted in non-zero and the ++ * number of keys found by the initial call to update_map_pr() matches it, ++ * exit early, since we have all the keys we are expecting. ++ * + * The function returns the number of keys that are registered or 0 if + * it's unknown. + */ +-static unsigned int mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed) ++static unsigned int ++mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed, ++ unsigned int nr_keys_wanted) + { + struct multipath *mpp = pp->mpp; + int ret; +@@ -3864,6 +3891,8 @@ static unsigned int mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_ + nr_keys_needed = 1; + if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS) + return 0; ++ if (nr_keys_wanted && nr_keys_wanted == nr_keys_needed) ++ return nr_keys_needed; + } + + check_prhold(mpp, pp); +diff --git a/multipathd/main.h b/multipathd/main.h +index 77603751..8a4c5f88 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -60,4 +60,5 @@ int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors *vecs); + void set_pr(struct multipath *mpp); + void unset_pr(struct multipath *mpp); ++void pr_register_active_paths(struct multipath *mpp, bool check_active_nr); + #endif /* MAIN_H */ diff --git a/SOURCES/0196-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch b/SOURCES/0196-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch new file mode 100644 index 0000000..060b442 --- /dev/null +++ b/SOURCES/0196-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 10 Nov 2025 15:25:51 -0500 +Subject: [PATCH] mpathpersist: Fix REPORT CAPABILITIES output + +mpathpersist was incorrectly parsing the REPORT CAPABILITES service +action output. In reality, the type mask is two bytes where the type +information is stored in bits 7, 6, 5, 3, & 1 (0xea) of the first byte +and bit 0 (0x01) of the second byte. libmpathpersist was treating these +two bytes as a big endian 16 bit number, but mpathpersist was looking +for bits in that number as if it was little endian number. + +Ideally, libmpathpersist would treat prin_capdescr.pr_type_mask as +two bytes, like it does for the flags. But we already expose this +as a 16 bit number, where we treated the input bytes as a big endian +number. There's no great reason to mess with the libmpathpersist API, +when we can just make mpathpersist treat this data like libmpathpersist +provides it. So, fix mpathpersist to print the data out correctly. + +Additionally, instead of printing a 1 or a 0 to indicate if a type was +supported or not, it was printing the value of the type flag. Also, +Persist Through Power Loss Capable (PTPL_C) was being reported if any +bit in flags[0] was set. Fix these as well. Reformat all of the +capability printing lines, since it is less confusing than only +reformatting some of them. + +Fixes: ae4e8a6 ("mpathpersist: Add new utility for managing persistent reservation on dm multipath device") +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.h | 4 ++- + mpathpersist/main.c | 43 +++++++++++++++++++++++---------- + 2 files changed, 33 insertions(+), 14 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.h b/libmpathpersist/mpath_persist.h +index 807787b5..3d6fa51d 100644 +--- a/libmpathpersist/mpath_persist.h ++++ b/libmpathpersist/mpath_persist.h +@@ -118,7 +118,9 @@ struct prin_capdescr + { + uint16_t length; + uint8_t flags[2]; +- uint16_t pr_type_mask; ++ uint16_t pr_type_mask; /* The two bytes of the type mask are treated ++ as a single big-endian number. So the valid ++ type bits are 0xea01 */ + uint16_t _reserved; + }; + +diff --git a/mpathpersist/main.c b/mpathpersist/main.c +index 26c69b01..a0646cde 100644 +--- a/mpathpersist/main.c ++++ b/mpathpersist/main.c +@@ -754,25 +754,42 @@ void mpath_print_buf_readcap( struct prin_resp *pr_buff) + + printf("Report capabilities response:\n"); + +- printf(" Compatible Reservation Handling(CRH): %d\n", !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x10)); +- printf(" Specify Initiator Ports Capable(SIP_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x8)); +- printf(" All Target Ports Capable(ATP_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x4 )); +- printf(" Persist Through Power Loss Capable(PTPL_C): %d\n",!!(pr_buff->prin_descriptor.prin_readcap.flags[0])); +- printf(" Type Mask Valid(TMV): %d\n", !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x80)); +- printf(" Allow Commands: %d\n", !!(( pr_buff->prin_descriptor.prin_readcap.flags[1] >> 4) & 0x7)); ++ printf(" Compatible Reservation Handling(CRH): %d\n", ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x10)); ++ printf(" Specify Initiator Ports Capable(SIP_C): %d\n", ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x8)); ++ printf(" All Target Ports Capable(ATP_C): %d\n", ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x4)); ++ printf(" Persist Through Power Loss Capable(PTPL_C): %d\n", ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[0] & 0x1)); ++ printf(" Type Mask Valid(TMV): %d\n", ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x80)); ++ printf(" Allow Commands: %d\n", ++ !!((pr_buff->prin_descriptor.prin_readcap.flags[1] >> 4) & 0x7)); + printf(" Persist Through Power Loss Active(PTPL_A): %d\n", +- !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x1)); ++ !!(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x1)); + + if(pr_buff->prin_descriptor.prin_readcap.flags[1] & 0x80) + { + printf(" Support indicated in Type mask:\n"); + +- printf(" %s: %d\n", pr_type_strs[7], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x80); +- printf(" %s: %d\n", pr_type_strs[6], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x40); +- printf(" %s: %d\n", pr_type_strs[5], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x20); +- printf(" %s: %d\n", pr_type_strs[3], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x8); +- printf(" %s: %d\n", pr_type_strs[1], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x2); +- printf(" %s: %d\n", pr_type_strs[8], pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x100); ++ printf(" %s: %d\n", pr_type_strs[7], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & ++ 0x8000)); ++ printf(" %s: %d\n", pr_type_strs[6], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & ++ 0x4000)); ++ printf(" %s: %d\n", pr_type_strs[5], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & ++ 0x2000)); ++ printf(" %s: %d\n", pr_type_strs[3], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & ++ 0x800)); ++ printf(" %s: %d\n", pr_type_strs[1], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & ++ 0x200)); ++ printf(" %s: %d\n", pr_type_strs[8], ++ !!(pr_buff->prin_descriptor.prin_readcap.pr_type_mask & 0x1)); + } + } + diff --git a/SOURCES/0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch b/SOURCES/0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch new file mode 100644 index 0000000..6e6747a --- /dev/null +++ b/SOURCES/0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Xose Vazquez Perez +Date: Sat, 16 Aug 2025 20:16:23 +0200 +Subject: [PATCH] multipath-tools: update NFINIDAT/InfiniBox config in hwtable + +New recommended values for SLES-15, RHEL-8, and Ubuntu-22, or above: +https://lh3.googleusercontent.com/pw/AP1GczMchJ6bcMIohp_g3Ik5DS6BZv_IW0iwaEXf968sJsR8fr_p3mR4ThRpmHpZE_VMnlcV8j0uuuI2kz-eoqekRCUBxyTBKS7n-4WFMsUiecq8i7nIjEuhfZFrV54DBQLDzGt6ofakAAF1L-ZcAuCWW18=w0-h0 + +device { + vendor "NFINIDAT" + product "InfiniBox" + path_grouping_policy "group_by_prio" + path_checker "tur" + features 0 + hardware_handler "1 alua" + prio "alua" + rr_weight "priorities" + no_path_retry "queue" + rr_min_io 1 + rr_min_io_rq 1 + flush_on_last_del "yes" + fast_io_fail_tmo 15 + dev_loss_tmo "infinity" + path_selector "service-time 0" + failback "immediate" + detect_prio "no" + user_friendly_names "no" + } + +Cc: Martin Wilck +Cc: Benjamin Marzinski +Cc: Christophe Varoqui +Cc: DM_DEVEL-ML +Signed-off-by: Xose Vazquez Perez +Reviewed-by: Martin Wilck +Signed-off-by: Benjamin Marzinski +--- + libmultipath/hwtable.c | 11 ++++------- + 1 file changed, 4 insertions(+), 7 deletions(-) + +diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c +index 2d359829..188ad5e0 100644 +--- a/libmultipath/hwtable.c ++++ b/libmultipath/hwtable.c +@@ -1124,16 +1124,13 @@ static struct hwentry default_hw[] = { + .vendor = "NFINIDAT", + .product = "InfiniBox", + .pgpolicy = GROUP_BY_PRIO, +- .pgfailback = 30, ++ .pgfailback = -FAILBACK_IMMEDIATE, + .prio_name = PRIO_ALUA, +- .selector = "round-robin 0", +- .rr_weight = RR_WEIGHT_PRIO, +- .no_path_retry = NO_PATH_RETRY_FAIL, +- .minio = 1, +- .minio_rq = 1, ++ .no_path_retry = NO_PATH_RETRY_QUEUE, + .flush_on_last_del = FLUSH_ALWAYS, + .fast_io_fail = 15, +- .dev_loss = 15, ++ .dev_loss = MAX_DEV_LOSS_TMO, ++ .detect_prio = DETECT_PRIO_OFF, + }, + /* + * Kaminario diff --git a/SOURCES/0198-multipathd-print-path-offline-message-even-without-a.patch b/SOURCES/0198-multipathd-print-path-offline-message-even-without-a.patch new file mode 100644 index 0000000..637a4c2 --- /dev/null +++ b/SOURCES/0198-multipathd-print-path-offline-message-even-without-a.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 21 Jan 2026 16:03:13 -0500 +Subject: [PATCH] multipathd: print path offline message even without a checker + +If a path has a checker selected and is offline, multipathd will print a +"path offline" message. However if the checker isn't selected, for +instance because multipathd was started or reconfigured while the path +was offline, multipathd was not printing the "path offline" message. +Fix that. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + multipathd/main.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index a85c0db4..9beb0e06 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -97,12 +97,11 @@ mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed, + + #define LOG_MSG(lvl, pp) \ + do { \ +- if (pp->mpp && checker_selected(&pp->checker) && \ +- lvl <= libmp_verbosity) { \ ++ if (pp->mpp && lvl <= libmp_verbosity) { \ + if (pp->offline) \ + condlog(lvl, "%s: %s - path offline", \ + pp->mpp->alias, pp->dev); \ +- else { \ ++ else if (checker_selected(&pp->checker)) { \ + const char *__m = \ + checker_message(&pp->checker); \ + \ diff --git a/SOURCES/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch b/SOURCES/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch new file mode 100644 index 0000000..ae579aa --- /dev/null +++ b/SOURCES/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Thu, 9 Sep 2021 23:59:42 +0200 +Subject: [PATCH] libmultipath: improve cleanup of uevent queues on exit + +uevents listed on merge_node must be cleaned up, too. uevents +cancelled while being serviced and temporary queues, likewise. +The global uevq must be cleaned out in the uevent listener thread, +because it might have added events after the dispatcher thread +had already finished. + +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 49 ++++++++++++++++++++++++++++++++----------- + 1 file changed, 37 insertions(+), 12 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 4265904b..80941f87 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -91,16 +91,25 @@ struct uevent * alloc_uevent (void) + return uev; + } + ++static void uevq_cleanup(struct list_head *tmpq); ++ ++static void cleanup_uev(void *arg) ++{ ++ struct uevent *uev = arg; ++ ++ uevq_cleanup(&uev->merge_node); ++ if (uev->udev) ++ udev_device_unref(uev->udev); ++ free(uev); ++} ++ + static void uevq_cleanup(struct list_head *tmpq) + { + struct uevent *uev, *tmp; + + list_for_each_entry_safe(uev, tmp, tmpq, node) { + list_del_init(&uev->node); +- +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ cleanup_uev(uev); + } + } + +@@ -384,14 +393,10 @@ service_uevq(struct list_head *tmpq) + list_for_each_entry_safe(uev, tmp, tmpq, node) { + list_del_init(&uev->node); + ++ pthread_cleanup_push(cleanup_uev, uev); + if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) + condlog(0, "uevent trigger error"); +- +- uevq_cleanup(&uev->merge_node); +- +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ pthread_cleanup_pop(1); + } + } + +@@ -411,6 +416,18 @@ static void monitor_cleanup(void *arg) + udev_monitor_unref(monitor); + } + ++static void cleanup_uevq(void *arg) ++{ ++ uevq_cleanup(arg); ++} ++ ++static void cleanup_global_uevq(void *arg __attribute__((unused))) ++{ ++ pthread_mutex_lock(uevq_lockp); ++ uevq_cleanup(&uevq); ++ pthread_mutex_unlock(uevq_lockp); ++} ++ + /* + * Service the uevent queue. + */ +@@ -425,6 +442,7 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + while (1) { + LIST_HEAD(uevq_tmp); + ++ pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + servicing_uev = 0; + /* +@@ -436,14 +454,17 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + } + servicing_uev = 1; + list_splice_init(&uevq, &uevq_tmp); +- pthread_mutex_unlock(uevq_lockp); ++ pthread_cleanup_pop(1); ++ + if (!my_uev_trigger) + break; ++ ++ pthread_cleanup_push(cleanup_uevq, &uevq_tmp); + merge_uevq(&uevq_tmp); + service_uevq(&uevq_tmp); ++ pthread_cleanup_pop(1); + } + condlog(3, "Terminating uev service queue"); +- uevq_cleanup(&uevq); + return 0; + } + +@@ -600,6 +621,8 @@ int uevent_listen(struct udev *udev) + + events = 0; + gettimeofday(&start_time, NULL); ++ pthread_cleanup_push(cleanup_global_uevq, NULL); ++ pthread_cleanup_push(cleanup_uevq, &uevlisten_tmp); + while (1) { + struct uevent *uev; + struct udev_device *dev; +@@ -650,6 +673,8 @@ int uevent_listen(struct udev *udev) + gettimeofday(&start_time, NULL); + timeout = 30; + } ++ pthread_cleanup_pop(1); ++ pthread_cleanup_pop(1); + out: + pthread_cleanup_pop(1); + out_udev: diff --git a/SOURCES/0200-uevent_dispatch-use-while-in-wait-loop.patch b/SOURCES/0200-uevent_dispatch-use-while-in-wait-loop.patch new file mode 100644 index 0000000..b8aed11 --- /dev/null +++ b/SOURCES/0200-uevent_dispatch-use-while-in-wait-loop.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 16:06:25 +0200 +Subject: [PATCH] uevent_dispatch(): use while in wait loop + +Callers of pthread_cond_wait() should generally use a while loop +to test the condition. Also, remove the misleading comment. +Condition variables aren't unreliable, they're just not strictly +tied to the condition tested. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 80941f87..e3ec1ac1 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -445,13 +445,10 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + servicing_uev = 0; +- /* +- * Condition signals are unreliable, +- * so make sure we only wait if we have to. +- */ +- if (list_empty(&uevq)) { ++ ++ while (list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); +- } ++ + servicing_uev = 1; + list_splice_init(&uevq, &uevq_tmp); + pthread_cleanup_pop(1); diff --git a/SOURCES/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch b/SOURCES/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch new file mode 100644 index 0000000..4b14f78 --- /dev/null +++ b/SOURCES/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch @@ -0,0 +1,319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 18:04:42 +0200 +Subject: [PATCH] libmultipath: uevent_dispatch(): process uevents one by one + +The main rationale for delaying uevents is that the +uevent dispatcher may have to wait for other threads to release the +vecs lock, may the vecs lock for an extended amount of time, and +even sleep occasionally. By delaying them, we have the chance +to accumulate events for the same path device ("filtering") or +WWID ("merging"), thus avoiding duplicate work if we merge these +into one. + +A similar effect can be obtained in the uevent dispatcher itself +by looking for new uevents after each dispatched event, and trying +to merge the newly arrived events with those that remained +in the queue. + +When uevq_work is non-empty and we append a list of new events, +we don't need to check the entire list for filterable and mergeable +uevents. uevq_work had been filtered and merged already. So we just +need to check the newly appended events. These must of course be +checked for merges with earlier events, too. + +We must deal with some special cases here, like previously merged +uevents being filtered later. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/list.h | 53 +++++++++++++++++ + libmultipath/uevent.c | 129 ++++++++++++++++++++++++++++++------------ + 2 files changed, 147 insertions(+), 35 deletions(-) + +diff --git a/libmultipath/list.h b/libmultipath/list.h +index ced021f5..248f72bc 100644 +--- a/libmultipath/list.h ++++ b/libmultipath/list.h +@@ -246,6 +246,35 @@ static inline void list_splice_tail_init(struct list_head *list, + #define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + ++ ++/** ++ * list_pop - unlink and return the first list element ++ * @head: the &struct list_head pointer. ++ */ ++static inline struct list_head *list_pop(struct list_head *head) ++{ ++ struct list_head *tmp; ++ ++ if (list_empty(head)) ++ return NULL; ++ tmp = head->next; ++ list_del_init(tmp); ++ return tmp; ++} ++ ++/** ++ * list_pop_entry - unlink and return the entry of the first list element ++ * @head: the &struct list_head pointer. ++ * @type: the type of the struct this is embedded in. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_pop_entry(head, type, member) \ ++({ \ ++ struct list_head *__h = list_pop(head); \ ++ \ ++ (__h ? container_of(__h, type, member) : NULL); \ ++}) ++ + /** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. +@@ -334,6 +363,30 @@ static inline void list_splice_tail_init(struct list_head *list, + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + ++/** ++ * list_for_some_entry - iterate list from the given begin node to the given end node ++ * @pos: the type * to use as a loop counter. ++ * @from: the begin node of the iteration. ++ * @to: the end node of the iteration. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_for_some_entry(pos, from, to, member) \ ++ for (pos = list_entry((from)->next, typeof(*pos), member); \ ++ &pos->member != (to); \ ++ pos = list_entry(pos->member.next, typeof(*pos), member)) ++ ++/** ++ * list_for_some_entry_reverse - iterate backwards list from the given begin node to the given end node ++ * @pos: the type * to use as a loop counter. ++ * @from: the begin node of the iteration. ++ * @to: the end node of the iteration. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_for_some_entry_reverse(pos, from, to, member) \ ++ for (pos = list_entry((from)->prev, typeof(*pos), member); \ ++ &pos->member != (to); \ ++ pos = list_entry(pos->member.prev, typeof(*pos), member)) ++ + /** + * list_for_some_entry_safe - iterate list from the given begin node to the given end node safe against removal of list entry + * @pos: the type * to use as a loop counter. +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index e3ec1ac1..2198e254 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -308,17 +308,64 @@ uevent_can_merge(struct uevent *earlier, struct uevent *later) + return false; + } + ++static void uevent_delete_from_list(struct uevent *to_delete, ++ struct uevent **previous, ++ struct list_head **old_tail) ++{ ++ /* ++ * "old_tail" is the list_head before the last list element to which ++ * the caller iterates (the list anchor if the caller iterates over ++ * the entire list). If this element is removed (which can't happen ++ * for the anchor), "old_tail" must be moved. It can happen that ++ * "old_tail" ends up pointing at the anchor. ++ */ ++ if (*old_tail == &to_delete->node) ++ *old_tail = to_delete->node.prev; ++ ++ list_del_init(&to_delete->node); ++ ++ /* ++ * The "to_delete" uevent has been merged with other uevents ++ * previously. Re-insert them into the list, at the point we're ++ * currently at. This must be done after the list_del_init() above, ++ * otherwise previous->next would still point to to_delete. ++ */ ++ if (!list_empty(&to_delete->merge_node)) { ++ struct uevent *last = list_entry(to_delete->merge_node.prev, ++ typeof(*last), node); ++ ++ list_splice(&to_delete->merge_node, &(*previous)->node); ++ *previous = last; ++ } ++ if (to_delete->udev) ++ udev_device_unref(to_delete->udev); ++ ++ free(to_delete); ++} ++ ++/* ++ * Use this function to delete events that are known not to ++ * be equal to old_tail, and have an empty merge_node list. ++ * For others, use uevent_delete_from_list(). ++ */ ++static void uevent_delete_simple(struct uevent *to_delete) ++{ ++ list_del_init(&to_delete->node); ++ ++ if (to_delete->udev) ++ udev_device_unref(to_delete->udev); ++ ++ free(to_delete); ++} ++ + static void +-uevent_prepare(struct list_head *tmpq) ++uevent_prepare(struct list_head *tmpq, const struct list_head *stop) + { + struct uevent *uev, *tmp; + +- list_for_each_entry_reverse_safe(uev, tmp, tmpq, node) { ++ list_for_some_entry_reverse_safe(uev, tmp, tmpq, stop, node) { + if (uevent_can_discard(uev)) { +- list_del_init(&uev->node); +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ uevent_delete_simple(uev); + continue; + } + +@@ -329,7 +376,7 @@ uevent_prepare(struct list_head *tmpq) + } + + static void +-uevent_filter(struct uevent *later, struct list_head *tmpq) ++uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **stop) + { + struct uevent *earlier, *tmp; + +@@ -343,16 +390,13 @@ uevent_filter(struct uevent *later, struct list_head *tmpq) + earlier->kernel, earlier->action, + later->kernel, later->action); + +- list_del_init(&earlier->node); +- if (earlier->udev) +- udev_device_unref(earlier->udev); +- FREE(earlier); ++ uevent_delete_from_list(earlier, &tmp, stop); + } + } + } + + static void +-uevent_merge(struct uevent *later, struct list_head *tmpq) ++uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **stop) + { + struct uevent *earlier, *tmp; + +@@ -367,37 +411,42 @@ uevent_merge(struct uevent *later, struct list_head *tmpq) + earlier->action, earlier->kernel, earlier->wwid, + later->action, later->kernel, later->wwid); + ++ /* See comment in uevent_delete_from_list() */ ++ if (&earlier->node == *stop) ++ *stop = earlier->node.prev; ++ + list_move(&earlier->node, &later->merge_node); ++ list_splice_init(&earlier->merge_node, ++ &later->merge_node); + } + } + } + + static void +-merge_uevq(struct list_head *tmpq) ++merge_uevq(struct list_head *tmpq, struct list_head *stop) + { + struct uevent *later; + +- uevent_prepare(tmpq); +- list_for_each_entry_reverse(later, tmpq, node) { +- uevent_filter(later, tmpq); ++ uevent_prepare(tmpq, stop); ++ list_for_some_entry_reverse(later, tmpq, stop, node) { ++ uevent_filter(later, tmpq, &stop); + if(uevent_need_merge()) +- uevent_merge(later, tmpq); ++ uevent_merge(later, tmpq, &stop); + } + } + + static void + service_uevq(struct list_head *tmpq) + { +- struct uevent *uev, *tmp; +- +- list_for_each_entry_safe(uev, tmp, tmpq, node) { +- list_del_init(&uev->node); +- +- pthread_cleanup_push(cleanup_uev, uev); +- if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) +- condlog(0, "uevent trigger error"); +- pthread_cleanup_pop(1); +- } ++ struct uevent *uev = list_pop_entry(tmpq, typeof(*uev), node); ++ ++ if (uev == NULL) ++ return; ++ condlog(4, "servicing uevent '%s %s'", uev->action, uev->kernel); ++ pthread_cleanup_push(cleanup_uev, uev); ++ if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) ++ condlog(0, "uevent trigger error"); ++ pthread_cleanup_pop(1); + } + + static void uevent_cleanup(void *arg) +@@ -434,33 +483,43 @@ static void cleanup_global_uevq(void *arg __attribute__((unused))) + int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + void * trigger_data) + { ++ LIST_HEAD(uevq_work); ++ + my_uev_trigger = uev_trigger; + my_trigger_data = trigger_data; + + mlockall(MCL_CURRENT | MCL_FUTURE); + ++ pthread_cleanup_push(cleanup_uevq, &uevq_work); + while (1) { +- LIST_HEAD(uevq_tmp); ++ struct list_head *stop; + + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); +- servicing_uev = 0; + +- while (list_empty(&uevq)) ++ servicing_uev = !list_empty(&uevq_work); ++ ++ while (list_empty(&uevq_work) && list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); + + servicing_uev = 1; +- list_splice_init(&uevq, &uevq_tmp); ++ /* ++ * "stop" is the list element towards which merge_uevq() ++ * will iterate: the last element of uevq_work before ++ * appending new uevents. If uveq_is empty, uevq_work.prev ++ * equals &uevq_work, which is what we need. ++ */ ++ stop = uevq_work.prev; ++ list_splice_tail_init(&uevq, &uevq_work); + pthread_cleanup_pop(1); + + if (!my_uev_trigger) + break; + +- pthread_cleanup_push(cleanup_uevq, &uevq_tmp); +- merge_uevq(&uevq_tmp); +- service_uevq(&uevq_tmp); +- pthread_cleanup_pop(1); ++ merge_uevq(&uevq_work, stop); ++ service_uevq(&uevq_work); + } ++ pthread_cleanup_pop(1); + condlog(3, "Terminating uev service queue"); + return 0; + } diff --git a/SOURCES/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch b/SOURCES/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch new file mode 100644 index 0000000..8cee865 --- /dev/null +++ b/SOURCES/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 23:25:48 +0200 +Subject: [PATCH] libmultipath: uevent_listen(): don't delay uevents + +When multipathd starts up early, basically all devices are added +through uevent processing. This takes much more time than necessary +because of the artificial delays introduced for passing uevents +between the listener and the receiver thread in ee8888f +("multipath-tools: improve processing efficiency for addition and deletion of +multipath devices"). This delay could be up to 30s. + +It's generally not a good idea to delay uevent processing in multipathd. +ADD events must normally be handled ASAP in order to avoid maps entering +queueing mode or eventually failing. Handling REMOVE events quickly is +also important to make multipathd aware of deleted devices and keep +kernel and multipathd state in sync. + +If uevents arrive quickly, the assumption is that the dispatcher will process +them more slowly than the listener. This was the idea of commit ee8888f, +AFAIU: if a queue of unprocessed events piles up because the dispatcher is +too slow, use filtering and merging to reduce the length of the queue, and +thus the work to be done for the uevent dispatcher, especially the work +that needs to be done while holding the vecs lock. In ee8888f, the +queue was created by allowing uevents to accumulate in the listener. + +This patch changes the logic of ee8888f, while keeping the uevent +filtering and discarding features. The idea is that the uevent dispatcher +shouldn't be idle if there are uevents to process. Therefore uevents +are passed to it immediately. But it now checks for new uevents after +processing every individual event, before processing the entire queue, +and it applies filtering and merging to the queue as it grows. + +This patch set avoids any delay when the uevent dispatcher is idle or +able to keep up with the rate of incoming uevents, while applying an +increasing amount of filtering and merging as pressure on the uevent +dispatcher increases. It's reasonable to assume that filtering and +merging get more efficient with increasing queue length, because the +probability of finding matching events will increase. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 108 +++++++++++++++--------------------------- + 1 file changed, 37 insertions(+), 71 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 2198e254..c3984fef 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -54,10 +54,6 @@ + #include "blacklist.h" + #include "devmapper.h" + +-#define MAX_ACCUMULATION_COUNT 2048 +-#define MAX_ACCUMULATION_TIME 30*1000 +-#define MIN_BURST_SPEED 10 +- + typedef int (uev_trigger)(struct uevent *, void * trigger_data); + + static LIST_HEAD(uevq); +@@ -586,44 +582,43 @@ static struct uevent *uevent_from_udev_device(struct udev_device *dev) + return uev; + } + +-static bool uevent_burst(struct timeval *start_time, int events) ++#define MAX_UEVENTS 1000 ++static int uevent_receive_events(int fd, struct list_head *tmpq, ++ struct udev_monitor *monitor) + { +- struct timeval diff_time, end_time; +- unsigned long speed; +- unsigned long eclipse_ms; +- +- if(events > MAX_ACCUMULATION_COUNT) { +- condlog(2, "burst got %u uevents, too much uevents, stopped", events); +- return false; +- } ++ struct pollfd ev_poll; ++ int n = 0; + +- gettimeofday(&end_time, NULL); +- timersub(&end_time, start_time, &diff_time); ++ do { ++ struct uevent *uev; ++ struct udev_device *dev; + +- eclipse_ms = diff_time.tv_sec * 1000 + diff_time.tv_usec / 1000; ++ dev = udev_monitor_receive_device(monitor); ++ if (!dev) { ++ condlog(0, "failed getting udev device"); ++ break; ++ } ++ uev = uevent_from_udev_device(dev); ++ if (!uev) ++ break; + +- if (eclipse_ms == 0) +- return true; ++ list_add_tail(&uev->node, tmpq); ++ n++; ++ condlog(4, "received uevent \"%s %s\"", uev->action, uev->kernel); + +- if (eclipse_ms > MAX_ACCUMULATION_TIME) { +- condlog(2, "burst continued %lu ms, too long time, stopped", eclipse_ms); +- return false; +- } ++ ev_poll.fd = fd; ++ ev_poll.events = POLLIN; + +- speed = (events * 1000) / eclipse_ms; +- if (speed > MIN_BURST_SPEED) +- return true; ++ } while (n < MAX_UEVENTS && poll(&ev_poll, 1, 0) > 0); + +- return false; ++ return n; + } + + int uevent_listen(struct udev *udev) + { + int err = 2; + struct udev_monitor *monitor = NULL; +- int fd, socket_flags, events; +- struct timeval start_time; +- int timeout = 30; ++ int fd, socket_flags; + LIST_HEAD(uevlisten_tmp); + + /* +@@ -675,59 +670,30 @@ int uevent_listen(struct udev *udev) + goto out; + } + +- events = 0; +- gettimeofday(&start_time, NULL); + pthread_cleanup_push(cleanup_global_uevq, NULL); + pthread_cleanup_push(cleanup_uevq, &uevlisten_tmp); + while (1) { +- struct uevent *uev; +- struct udev_device *dev; +- struct pollfd ev_poll; +- int poll_timeout; +- int fdcount; ++ int fdcount, events; ++ struct pollfd ev_poll = { .fd = fd, .events = POLLIN, }; + +- memset(&ev_poll, 0, sizeof(struct pollfd)); +- ev_poll.fd = fd; +- ev_poll.events = POLLIN; +- poll_timeout = timeout * 1000; +- errno = 0; +- fdcount = poll(&ev_poll, 1, poll_timeout); +- if (fdcount > 0 && ev_poll.revents & POLLIN) { +- timeout = uevent_burst(&start_time, events + 1) ? 1 : 0; +- dev = udev_monitor_receive_device(monitor); +- if (!dev) { +- condlog(0, "failed getting udev device"); +- continue; +- } +- uev = uevent_from_udev_device(dev); +- if (!uev) +- continue; +- list_add_tail(&uev->node, &uevlisten_tmp); +- events++; +- continue; +- } ++ fdcount = poll(&ev_poll, 1, -1); + if (fdcount < 0) { + if (errno == EINTR) + continue; + +- condlog(0, "error receiving " +- "uevent message: %m"); ++ condlog(0, "error receiving uevent message: %m"); + err = -errno; + break; + } +- if (!list_empty(&uevlisten_tmp)) { +- /* +- * Queue uevents and poke service pthread. +- */ +- condlog(3, "Forwarding %d uevents", events); +- pthread_mutex_lock(uevq_lockp); +- list_splice_tail_init(&uevlisten_tmp, &uevq); +- pthread_cond_signal(uev_condp); +- pthread_mutex_unlock(uevq_lockp); +- events = 0; +- } +- gettimeofday(&start_time, NULL); +- timeout = 30; ++ events = uevent_receive_events(fd, &uevlisten_tmp, monitor); ++ if (events <= 0) ++ continue; ++ ++ condlog(4, "Forwarding %d uevents", events); ++ pthread_mutex_lock(uevq_lockp); ++ list_splice_tail_init(&uevlisten_tmp, &uevq); ++ pthread_cond_signal(uev_condp); ++ pthread_mutex_unlock(uevq_lockp); + } + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); diff --git a/SOURCES/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch b/SOURCES/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch new file mode 100644 index 0000000..51baf0e --- /dev/null +++ b/SOURCES/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch @@ -0,0 +1,356 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Wed, 30 Mar 2022 00:06:15 +0200 +Subject: [PATCH] libmultipath: uevent: use struct to pass parameters around + +libmultipath: uevent_dispatch(): just grab config once + +Introduce struct uevent_filter_state to pass parameters around. +This simplifies the function signatures and allows for easy extension +later. + +Instead of grabbing multipath config repeatedly, do it just +once per dispatcher iteration, and pass the pointer around in +struct uevent_filter_state. We shouldn't use different configs +for different paths in a single iteration, anyway. + +Also, properly constify get_uid_attribute_by_attrs() and +pp->uid_attribute. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/config.c | 6 +-- + libmultipath/config.h | 4 +- + libmultipath/discovery.c | 2 +- + libmultipath/structs.h | 2 +- + libmultipath/uevent.c | 110 +++++++++++++++++---------------------- + libmultipath/uevent.h | 3 +- + tests/uevent.c | 2 +- + 7 files changed, 58 insertions(+), 71 deletions(-) + +diff --git a/libmultipath/config.c b/libmultipath/config.c +index f31200a3..bd8296bf 100644 +--- a/libmultipath/config.c ++++ b/libmultipath/config.c +@@ -1112,10 +1112,10 @@ out: + return 1; + } + +-char *get_uid_attribute_by_attrs(struct config *conf, +- const char *path_dev) ++const char *get_uid_attribute_by_attrs(const struct config *conf, ++ const char *path_dev) + { +- vector uid_attrs = &conf->uid_attrs; ++ const struct _vector *uid_attrs = &conf->uid_attrs; + int j; + char *att, *col; + +diff --git a/libmultipath/config.h b/libmultipath/config.h +index 5807ac68..d3abbaea 100644 +--- a/libmultipath/config.h ++++ b/libmultipath/config.h +@@ -329,7 +329,7 @@ void libmp_put_multipath_config(void *); + void put_multipath_config(void *); + + int parse_uid_attrs(char *uid_attrs, struct config *conf); +-char *get_uid_attribute_by_attrs(struct config *conf, +- const char *path_dev); ++const char *get_uid_attribute_by_attrs(const struct config *conf, ++ const char *path_dev); + + #endif +diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c +index 22d114b3..186423e0 100644 +--- a/libmultipath/discovery.c ++++ b/libmultipath/discovery.c +@@ -2071,7 +2071,7 @@ fix_broken_nvme_wwid(struct path *pp, const char *value, size_t size) + } + + static int +-get_udev_uid(struct path * pp, char *uid_attribute, struct udev_device *udev) ++get_udev_uid(struct path * pp, const char *uid_attribute, struct udev_device *udev) + { + ssize_t len; + const char *value; +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 2f69e831..423c8b78 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -350,7 +350,7 @@ struct path { + int detect_prio; + int detect_checker; + int tpgs; +- char * uid_attribute; ++ const char *uid_attribute; + char * getuid; + struct prio prio; + struct checker checker; +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index c3984fef..4ef7181c 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -65,6 +65,12 @@ static uev_trigger *my_uev_trigger; + static void *my_trigger_data; + static int servicing_uev; + ++struct uevent_filter_state { ++ struct list_head uevq; ++ struct list_head *old_tail; ++ struct config *conf; ++}; ++ + int is_uevent_busy(void) + { + int empty; +@@ -160,40 +166,24 @@ int uevent_get_env_positive_int(const struct uevent *uev, + } + + void +-uevent_get_wwid(struct uevent *uev) ++uevent_get_wwid(struct uevent *uev, const struct config *conf) + { +- char *uid_attribute; ++ const char *uid_attribute; + const char *val; +- struct config * conf; + +- conf = get_multipath_config(); +- pthread_cleanup_push(put_multipath_config, conf); + uid_attribute = get_uid_attribute_by_attrs(conf, uev->kernel); +- pthread_cleanup_pop(1); +- + val = uevent_get_env_var(uev, uid_attribute); + if (val) + uev->wwid = val; + } + +-static bool uevent_need_merge(void) ++static bool uevent_need_merge(const struct config *conf) + { +- struct config * conf; +- bool need_merge = false; +- +- conf = get_multipath_config(); +- if (VECTOR_SIZE(&conf->uid_attrs) > 0) +- need_merge = true; +- put_multipath_config(conf); +- +- return need_merge; ++ return VECTOR_SIZE(&conf->uid_attrs) > 0; + } + +-static bool uevent_can_discard(struct uevent *uev) ++static bool uevent_can_discard(struct uevent *uev, const struct config *conf) + { +- int invalid = 0; +- struct config * conf; +- + /* + * do not filter dm devices by devnode + */ +@@ -202,15 +192,10 @@ static bool uevent_can_discard(struct uevent *uev) + /* + * filter paths devices by devnode + */ +- conf = get_multipath_config(); +- pthread_cleanup_push(put_multipath_config, conf); + if (filter_devnode(conf->blist_devnode, conf->elist_devnode, + uev->kernel) > 0) +- invalid = 1; +- pthread_cleanup_pop(1); +- +- if (invalid) + return true; ++ + return false; + } + +@@ -354,29 +339,28 @@ static void uevent_delete_simple(struct uevent *to_delete) + free(to_delete); + } + +-static void +-uevent_prepare(struct list_head *tmpq, const struct list_head *stop) ++static void uevent_prepare(struct uevent_filter_state *st) + { + struct uevent *uev, *tmp; + +- list_for_some_entry_reverse_safe(uev, tmp, tmpq, stop, node) { +- if (uevent_can_discard(uev)) { ++ list_for_some_entry_reverse_safe(uev, tmp, &st->uevq, st->old_tail, node) { ++ if (uevent_can_discard(uev, st->conf)) { + uevent_delete_simple(uev); + continue; + } + + if (strncmp(uev->kernel, "dm-", 3) && +- uevent_need_merge()) +- uevent_get_wwid(uev); ++ uevent_need_merge(st->conf)) ++ uevent_get_wwid(uev, st->conf); + } + } + + static void +-uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **stop) ++uevent_filter(struct uevent *later, struct uevent_filter_state *st) + { + struct uevent *earlier, *tmp; + +- list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) { ++ list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { + /* + * filter unnessary earlier uevents + * by the later uevent +@@ -386,17 +370,16 @@ uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **s + earlier->kernel, earlier->action, + later->kernel, later->action); + +- uevent_delete_from_list(earlier, &tmp, stop); ++ uevent_delete_from_list(earlier, &tmp, &st->old_tail); + } + } + } + +-static void +-uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **stop) ++static void uevent_merge(struct uevent *later, struct uevent_filter_state *st) + { + struct uevent *earlier, *tmp; + +- list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) { ++ list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { + if (merge_need_stop(earlier, later)) + break; + /* +@@ -408,8 +391,8 @@ uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **st + later->action, later->kernel, later->wwid); + + /* See comment in uevent_delete_from_list() */ +- if (&earlier->node == *stop) +- *stop = earlier->node.prev; ++ if (&earlier->node == st->old_tail) ++ st->old_tail = earlier->node.prev; + + list_move(&earlier->node, &later->merge_node); + list_splice_init(&earlier->merge_node, +@@ -418,16 +401,15 @@ uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **st + } + } + +-static void +-merge_uevq(struct list_head *tmpq, struct list_head *stop) ++static void merge_uevq(struct uevent_filter_state *st) + { + struct uevent *later; + +- uevent_prepare(tmpq, stop); +- list_for_some_entry_reverse(later, tmpq, stop, node) { +- uevent_filter(later, tmpq, &stop); +- if(uevent_need_merge()) +- uevent_merge(later, tmpq, &stop); ++ uevent_prepare(st); ++ list_for_some_entry_reverse(later, &st->uevq, st->old_tail, node) { ++ uevent_filter(later, st); ++ if(uevent_need_merge(st->conf)) ++ uevent_merge(later, st); + } + } + +@@ -479,41 +461,45 @@ static void cleanup_global_uevq(void *arg __attribute__((unused))) + int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + void * trigger_data) + { +- LIST_HEAD(uevq_work); ++ struct uevent_filter_state filter_state; + ++ INIT_LIST_HEAD(&filter_state.uevq); + my_uev_trigger = uev_trigger; + my_trigger_data = trigger_data; + + mlockall(MCL_CURRENT | MCL_FUTURE); + +- pthread_cleanup_push(cleanup_uevq, &uevq_work); ++ pthread_cleanup_push(cleanup_uevq, &filter_state.uevq); + while (1) { +- struct list_head *stop; +- + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + +- servicing_uev = !list_empty(&uevq_work); ++ servicing_uev = !list_empty(&filter_state.uevq); + +- while (list_empty(&uevq_work) && list_empty(&uevq)) ++ while (list_empty(&filter_state.uevq) && list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); + + servicing_uev = 1; + /* +- * "stop" is the list element towards which merge_uevq() +- * will iterate: the last element of uevq_work before +- * appending new uevents. If uveq_is empty, uevq_work.prev +- * equals &uevq_work, which is what we need. ++ * "old_tail" is the list element towards which merge_uevq() ++ * will iterate: the last element of uevq before ++ * appending new uevents. If uveq empty, uevq.prev ++ * equals &uevq, which is what we need. + */ +- stop = uevq_work.prev; +- list_splice_tail_init(&uevq, &uevq_work); ++ filter_state.old_tail = filter_state.uevq.prev; ++ list_splice_tail_init(&uevq, &filter_state.uevq); + pthread_cleanup_pop(1); + + if (!my_uev_trigger) + break; + +- merge_uevq(&uevq_work, stop); +- service_uevq(&uevq_work); ++ ++ pthread_cleanup_push(put_multipath_config, filter_state.conf); ++ filter_state.conf = get_multipath_config(); ++ merge_uevq(&filter_state); ++ pthread_cleanup_pop(1); ++ ++ service_uevq(&filter_state.uevq); + } + pthread_cleanup_pop(1); + condlog(3, "Terminating uev service queue"); +diff --git a/libmultipath/uevent.h b/libmultipath/uevent.h +index 61ca1b56..53a7ca29 100644 +--- a/libmultipath/uevent.h ++++ b/libmultipath/uevent.h +@@ -10,6 +10,7 @@ + #define OBJECT_SIZE 512 + + struct udev; ++struct config; + + struct uevent { + struct list_head node; +@@ -31,7 +32,7 @@ int uevent_listen(struct udev *udev); + int uevent_dispatch(int (*store_uev)(struct uevent *, void * trigger_data), + void * trigger_data); + bool uevent_is_mpath(const struct uevent *uev); +-void uevent_get_wwid(struct uevent *uev); ++void uevent_get_wwid(struct uevent *uev, const struct config *conf); + + int uevent_get_env_positive_int(const struct uevent *uev, + const char *attr); +diff --git a/tests/uevent.c b/tests/uevent.c +index 648ff268..e237a208 100644 +--- a/tests/uevent.c ++++ b/tests/uevent.c +@@ -111,7 +111,7 @@ static void test_uid_attrs(void **state) + static void test_wwid(void **state) + { + struct uevent *uev = *state; +- uevent_get_wwid(uev); ++ uevent_get_wwid(uev, &conf); + + assert_string_equal(uev->wwid, WWID); + } diff --git a/SOURCES/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch b/SOURCES/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch new file mode 100644 index 0000000..c5b65de --- /dev/null +++ b/SOURCES/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Fri, 5 Jan 2024 18:51:02 +0100 +Subject: [PATCH] libmultipath: is_uevent_busy(): check servicing_uev under + lock + +This fixes a coverity-reported defect (413384 Data race condition). +Indeed, we always set servicing_uev with the lock held, so it makes +sense to read it with the lock held, too. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 4ef7181c..8cd928a9 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -73,12 +73,13 @@ struct uevent_filter_state { + + int is_uevent_busy(void) + { +- int empty; ++ int empty, servicing; + + pthread_mutex_lock(uevq_lockp); + empty = list_empty(&uevq); ++ servicing = servicing_uev; + pthread_mutex_unlock(uevq_lockp); +- return (!empty || servicing_uev); ++ return (!empty || servicing); + } + + struct uevent * alloc_uevent (void) diff --git a/SOURCES/0205-multipathd-make-multipathd-show-status-busy-checker-.patch b/SOURCES/0205-multipathd-make-multipathd-show-status-busy-checker-.patch new file mode 100644 index 0000000..72d64e2 --- /dev/null +++ b/SOURCES/0205-multipathd-make-multipathd-show-status-busy-checker-.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 21 Jan 2026 16:03:12 -0500 +Subject: [PATCH] multipathd: make "multipathd show status" busy checker better + +while uevent_listen() was grabbing new uevents, "multipathd show status" +would still show show busy as "False". Add a check there, to make catch +multipathd's uevent processing earlier. Also, access servicing_uev (as +well as the new variable, adding_uev) atomically, just to make sure that +the compiler doesn't do stupid things trying to optimize them. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmultipath/uevent.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 8cd928a9..c230e963 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -64,6 +64,7 @@ static pthread_cond_t *uev_condp = &uev_cond; + static uev_trigger *my_uev_trigger; + static void *my_trigger_data; + static int servicing_uev; ++static int adding_uev; /* uatomic access only */ + + struct uevent_filter_state { + struct list_head uevq; +@@ -73,13 +74,14 @@ struct uevent_filter_state { + + int is_uevent_busy(void) + { +- int empty, servicing; ++ int empty, servicing, adding; + + pthread_mutex_lock(uevq_lockp); + empty = list_empty(&uevq); + servicing = servicing_uev; ++ adding = uatomic_read(&adding_uev); + pthread_mutex_unlock(uevq_lockp); +- return (!empty || servicing); ++ return (!empty || servicing || adding); + } + + struct uevent * alloc_uevent (void) +@@ -663,6 +665,7 @@ int uevent_listen(struct udev *udev) + int fdcount, events; + struct pollfd ev_poll = { .fd = fd, .events = POLLIN, }; + ++ uatomic_set(&adding_uev, 0); + fdcount = poll(&ev_poll, 1, -1); + if (fdcount < 0) { + if (errno == EINTR) +@@ -672,6 +675,8 @@ int uevent_listen(struct udev *udev) + err = -errno; + break; + } ++ uatomic_set(&adding_uev, 1); ++ + events = uevent_receive_events(fd, &uevlisten_tmp, monitor); + if (events <= 0) + continue; diff --git a/SOURCES/0206-libmultipath-add-purge_disconnected-configuration-op.patch b/SOURCES/0206-libmultipath-add-purge_disconnected-configuration-op.patch new file mode 100644 index 0000000..86fd877 --- /dev/null +++ b/SOURCES/0206-libmultipath-add-purge_disconnected-configuration-op.patch @@ -0,0 +1,279 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brian Bunker +Date: Fri, 9 Jan 2026 16:50:43 -0800 +Subject: [PATCH] libmultipath: add purge_disconnected configuration option + +Add a new configuration option 'purge_disconnected' that can be set +per multipath device, hardware entry, or globally. This option will +be used to control whether multipathd should automatically remove +paths that are in a disconnected state. + +The option is disabled by default (PURGE_DISCONNECTED_OFF). + +This patch only adds the configuration infrastructure. The actual +purge functionality will be implemented in a subsequent patch. + +Signed-off-by: Brian Bunker +Reviewed-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +Signed-off-by: Benjamin Marzinski +--- + libmultipath/config.c | 2 ++ + libmultipath/config.h | 3 +++ + libmultipath/configure.c | 1 + + libmultipath/defaults.h | 1 + + libmultipath/dict.c | 14 ++++++++++++++ + libmultipath/propsel.c | 16 ++++++++++++++++ + libmultipath/propsel.h | 1 + + libmultipath/structs.h | 12 ++++++++++++ + multipath/multipath.conf.5 | 22 ++++++++++++++++++++++ + 9 files changed, 72 insertions(+) + +diff --git a/libmultipath/config.c b/libmultipath/config.c +index bd8296bf..2aa69cb4 100644 +--- a/libmultipath/config.c ++++ b/libmultipath/config.c +@@ -476,6 +476,7 @@ merge_hwe (struct hwentry * dst, struct hwentry * src) + merge_num(marginal_path_err_rate_threshold); + merge_num(marginal_path_err_recheck_gap_time); + merge_num(marginal_path_double_failed_time); ++ merge_num(purge_disconnected); + + snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product); + reconcile_features_with_options(id, &dst->features, +@@ -524,6 +525,7 @@ merge_mpe(struct mpentry *dst, struct mpentry *src) + merge_num(skip_kpartx); + merge_num(max_sectors_kb); + merge_num(ghost_delay); ++ merge_num(purge_disconnected); + merge_num(uid); + merge_num(gid); + merge_num(mode); +diff --git a/libmultipath/config.h b/libmultipath/config.h +index d3abbaea..e0b1e4c8 100644 +--- a/libmultipath/config.h ++++ b/libmultipath/config.h +@@ -87,6 +87,7 @@ struct hwentry { + int marginal_path_err_rate_threshold; + int marginal_path_err_recheck_gap_time; + int marginal_path_double_failed_time; ++ int purge_disconnected; + int skip_kpartx; + int max_sectors_kb; + int ghost_delay; +@@ -130,6 +131,7 @@ struct mpentry { + int marginal_path_err_rate_threshold; + int marginal_path_err_recheck_gap_time; + int marginal_path_double_failed_time; ++ int purge_disconnected; + int skip_kpartx; + int max_sectors_kb; + int ghost_delay; +@@ -186,6 +188,7 @@ struct config { + int marginal_path_err_rate_threshold; + int marginal_path_err_recheck_gap_time; + int marginal_path_double_failed_time; ++ int purge_disconnected; + int uxsock_timeout; + int strict_timing; + int retrigger_tries; +diff --git a/libmultipath/configure.c b/libmultipath/configure.c +index 71acb968..c1b6a077 100644 +--- a/libmultipath/configure.c ++++ b/libmultipath/configure.c +@@ -389,6 +389,7 @@ int setup_map(struct multipath *mpp, char **params, struct vectors *vecs) + select_max_sectors_kb(conf, mpp); + select_ghost_delay(conf, mpp); + select_flush_on_last_del(conf, mpp); ++ select_purge_disconnected(conf, mpp); + + sysfs_set_scsi_tmo(conf, mpp); + marginal_pathgroups = conf->marginal_pathgroups; +diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h +index 1ab5b99c..caf4ccce 100644 +--- a/libmultipath/defaults.h ++++ b/libmultipath/defaults.h +@@ -55,6 +55,7 @@ + #define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF + #define DEFAULT_RECHECK_WWID RECHECK_WWID_OFF + #define DEFAULT_AUTO_RESIZE AUTO_RESIZE_NEVER ++#define DEFAULT_PURGE_DISCONNECTED PURGE_DISCONNECTED_OFF + /* Enable no foreign libraries by default */ + #define DEFAULT_ENABLE_FOREIGN "NONE" + +diff --git a/libmultipath/dict.c b/libmultipath/dict.c +index 3c011ece..421070df 100644 +--- a/libmultipath/dict.c ++++ b/libmultipath/dict.c +@@ -979,6 +979,16 @@ static int snprint_def_disable_changed_wwids(struct config *conf, + return print_ignored(buff); + } + ++declare_def_handler(purge_disconnected, set_yes_no_undef) ++declare_def_snprint_defint(purge_disconnected, print_yes_no_undef, ++ DEFAULT_PURGE_DISCONNECTED) ++declare_ovr_handler(purge_disconnected, set_yes_no_undef) ++declare_ovr_snprint(purge_disconnected, print_yes_no_undef) ++declare_hw_handler(purge_disconnected, set_yes_no_undef) ++declare_hw_snprint(purge_disconnected, print_yes_no_undef) ++declare_mp_handler(purge_disconnected, set_yes_no_undef) ++declare_mp_snprint(purge_disconnected, print_yes_no_undef) ++ + declare_def_range_handler(remove_retries, 0, INT_MAX) + declare_def_snprint(remove_retries, print_int) + +@@ -2289,6 +2299,7 @@ init_keywords(vector keywords) + 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("skip_kpartx", &def_skip_kpartx_handler, &snprint_def_skip_kpartx); ++ install_keyword("purge_disconnected", &def_purge_disconnected_handler, &snprint_def_purge_disconnected); + install_keyword("disable_changed_wwids", &def_disable_changed_wwids_handler, &snprint_def_disable_changed_wwids); + install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); + install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb); +@@ -2386,6 +2397,7 @@ init_keywords(vector keywords) + install_keyword("marginal_path_err_recheck_gap_time", &hw_marginal_path_err_recheck_gap_time_handler, &snprint_hw_marginal_path_err_recheck_gap_time); + install_keyword("marginal_path_double_failed_time", &hw_marginal_path_double_failed_time_handler, &snprint_hw_marginal_path_double_failed_time); + install_keyword("skip_kpartx", &hw_skip_kpartx_handler, &snprint_hw_skip_kpartx); ++ install_keyword("purge_disconnected", &hw_purge_disconnected_handler, &snprint_hw_purge_disconnected); + install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb); + install_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay); + install_keyword("all_tg_pt", &hw_all_tg_pt_handler, &snprint_hw_all_tg_pt); +@@ -2429,6 +2441,7 @@ init_keywords(vector keywords) + install_keyword("marginal_path_double_failed_time", &ovr_marginal_path_double_failed_time_handler, &snprint_ovr_marginal_path_double_failed_time); + + install_keyword("skip_kpartx", &ovr_skip_kpartx_handler, &snprint_ovr_skip_kpartx); ++ install_keyword("purge_disconnected", &ovr_purge_disconnected_handler, &snprint_ovr_purge_disconnected); + install_keyword("max_sectors_kb", &ovr_max_sectors_kb_handler, &snprint_ovr_max_sectors_kb); + install_keyword("ghost_delay", &ovr_ghost_delay_handler, &snprint_ovr_ghost_delay); + install_keyword("all_tg_pt", &ovr_all_tg_pt_handler, &snprint_ovr_all_tg_pt); +@@ -2474,6 +2487,7 @@ init_keywords(vector keywords) + install_keyword("marginal_path_err_recheck_gap_time", &mp_marginal_path_err_recheck_gap_time_handler, &snprint_mp_marginal_path_err_recheck_gap_time); + install_keyword("marginal_path_double_failed_time", &mp_marginal_path_double_failed_time_handler, &snprint_mp_marginal_path_double_failed_time); + install_keyword("skip_kpartx", &mp_skip_kpartx_handler, &snprint_mp_skip_kpartx); ++ install_keyword("purge_disconnected", &mp_purge_disconnected_handler, &snprint_mp_purge_disconnected); + install_keyword("max_sectors_kb", &mp_max_sectors_kb_handler, &snprint_mp_max_sectors_kb); + install_keyword("ghost_delay", &mp_ghost_delay_handler, &snprint_mp_ghost_delay); + install_sublevel_end(); +diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c +index 0b6e22c1..c45488f7 100644 +--- a/libmultipath/propsel.c ++++ b/libmultipath/propsel.c +@@ -1306,6 +1306,22 @@ out: + return 0; + } + ++int select_purge_disconnected(struct config *conf, struct multipath *mp) ++{ ++ const char *origin; ++ ++ mp_set_mpe(purge_disconnected); ++ mp_set_ovr(purge_disconnected); ++ mp_set_hwe(purge_disconnected); ++ mp_set_conf(purge_disconnected); ++ mp_set_default(purge_disconnected, DEFAULT_PURGE_DISCONNECTED); ++out: ++ condlog(3, "%s: purge_disconnected = %s %s", mp->alias, ++ (mp->purge_disconnected == PURGE_DISCONNECTED_ON) ? "yes" : "no", ++ origin); ++ return 0; ++} ++ + int select_max_sectors_kb(struct config *conf, struct multipath * mp) + { + const char *origin; +diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h +index 152ca44c..325c1ee3 100644 +--- a/libmultipath/propsel.h ++++ b/libmultipath/propsel.h +@@ -35,6 +35,7 @@ int select_marginal_path_err_rate_threshold(struct config *conf, struct multipat + int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp); + int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp); + int select_ghost_delay(struct config *conf, struct multipath * mp); ++int select_purge_disconnected(struct config *conf, struct multipath *mp); + void reconcile_features_with_options(const char *id, char **features, + int* no_path_retry, + int *retain_hwhandler); +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 423c8b78..e1969b95 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -174,6 +174,17 @@ enum auto_resize_state { + AUTO_RESIZE_GROW_SHRINK, + }; + ++/* ++ * purge_disconnected configuration option (per multipath device) ++ * Controls whether paths that become disconnected at the storage target ++ * should be automatically removed from the system via sysfs. ++ */ ++enum purge_disconnected_states { ++ PURGE_DISCONNECTED_UNDEF = YNU_UNDEF, ++ PURGE_DISCONNECTED_OFF = YNU_NO, /* Don't purge */ ++ PURGE_DISCONNECTED_ON = YNU_YES, /* Purge disconnected paths */ ++}; ++ + #define PROTOCOL_UNSET -1 + + enum scsi_protocol { +@@ -427,6 +438,7 @@ struct multipath { + int ghost_delay; + int ghost_delay_tick; + int queue_mode; ++ int purge_disconnected; + uid_t uid; + gid_t gid; + mode_t mode; +diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 +index 8684bd63..529bd98e 100644 +--- a/multipath/multipath.conf.5 ++++ b/multipath/multipath.conf.5 +@@ -1265,6 +1265,22 @@ The default is: \fBno\fR + . + . + .TP ++.B purge_disconnected ++If set to ++.I yes ++, multipathd will automatically remove devices that are in a disconnected state. ++A path is considered disconnected when the TUR (Test Unit Ready) path checker ++receives the SCSI sense code "LOGICAL UNIT NOT SUPPORTED" (sense key 0x5, ++ASC/ASCQ 0x25/0x00). This typically indicates that the LUN has been unmapped ++or is no longer presented by the storage array. This option helps clean up ++stale device entries that would otherwise remain in the system. ++.RS ++.TP ++The default is: \fBno\fR ++.RE ++. ++. ++.TP + .B disable_changed_wwids + This option is deprecated and ignored. If the WWID of a path suddenly changes, + multipathd handles it as if it was removed and then added again. +@@ -1540,6 +1556,8 @@ section: + .TP + .B skip_kpartx + .TP ++.B purge_disconnected ++.TP + .B max_sectors_kb + .TP + .B ghost_delay +@@ -1715,6 +1733,8 @@ section: + .TP + .B skip_kpartx + .TP ++.B purge_disconnected ++.TP + .B max_sectors_kb + .TP + .B ghost_delay +@@ -1801,6 +1821,8 @@ the values are taken from the \fIdevices\fR or \fIdefaults\fR sections: + .TP + .B skip_kpartx + .TP ++.B purge_disconnected ++.TP + .B max_sectors_kb + .TP + .B ghost_delay diff --git a/SOURCES/0207-multipathd-implement-purge-functionality-for-disconn.patch b/SOURCES/0207-multipathd-implement-purge-functionality-for-disconn.patch new file mode 100644 index 0000000..c6ae129 --- /dev/null +++ b/SOURCES/0207-multipathd-implement-purge-functionality-for-disconn.patch @@ -0,0 +1,752 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brian Bunker +Date: Fri, 9 Jan 2026 16:50:43 -0800 +Subject: [PATCH] multipathd: implement purge functionality for disconnected + paths + +Implement automatic purging of paths that have been disconnected at the +storage target (e.g., LUN unmapped). This builds on the purge_disconnected +configuration option added in the previous patch. + +This adds: +- New PATH_DISCONNECTED checker state to signal disconnection +- TUR checker support for detecting LUN NOT SUPPORTED (ASC/ASCQ 0x25/0x00) +- Purge thread (purgeloop) that removes paths via sysfs delete attribute +- State machine to track disconnection and delay purging +- Conversion of PATH_DISCONNECTED to PATH_DOWN for normal processing + +The purge thread runs independently and processes paths that have been +marked for purging by the checker thread. Paths are only purged after +remaining disconnected for delay_wait_checks intervals to avoid removing +paths that are temporarily flapping. + +Signed-off-by: Brian Bunker +Signed-off-by: Krishna Kant +Reviewed-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +Signed-off-by: Benjamin Marzinski +--- + libmultipath/checkers.c | 2 + + libmultipath/checkers.h | 15 +- + libmultipath/checkers/tur.c | 10 ++ + libmultipath/discovery.c | 17 ++ + libmultipath/io_err_stat.c | 1 + + libmultipath/print.c | 2 + + libmultipath/structs.h | 14 ++ + multipathd/Makefile | 2 +- + multipathd/main.c | 74 +++++++- + multipathd/purge.c | 326 ++++++++++++++++++++++++++++++++++++ + multipathd/purge.h | 41 +++++ + 11 files changed, 496 insertions(+), 8 deletions(-) + create mode 100644 multipathd/purge.c + create mode 100644 multipathd/purge.h + +diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c +index 9eb3e261..e1b84df0 100644 +--- a/libmultipath/checkers.c ++++ b/libmultipath/checkers.c +@@ -39,6 +39,7 @@ static const char *checker_state_names[PATH_MAX_STATE] = { + [PATH_TIMEOUT] = "timeout", + [PATH_REMOVED] = "removed", + [PATH_DELAYED] = "delayed", ++ [PATH_DISCONNECTED] = "disconnected", + }; + + static LIST_HEAD(checkers); +@@ -343,6 +344,7 @@ static const char *generic_msg[CHECKER_GENERIC_MSGTABLE_SIZE] = { + [CHECKER_MSGID_DOWN] = " reports path is down", + [CHECKER_MSGID_GHOST] = " reports path is ghost", + [CHECKER_MSGID_UNSUPPORTED] = " doesn't support this device", ++ [CHECKER_MSGID_DISCONNECTED] = " no access to this device", + }; + + const char *checker_message(const struct checker *c) +diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h +index 2fd1d1c6..2f32f612 100644 +--- a/libmultipath/checkers.h ++++ b/libmultipath/checkers.h +@@ -66,6 +66,15 @@ + * 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" ++ * ++ * PATH_DISCONNECTED is a special ephemeral state used to signal that a path ++ * has been disconnected at the storage target (e.g., LUN unmapped). When a ++ * checker returns PATH_DISCONNECTED: ++ * 1. The path's pp->disconnected field is set to track purge state ++ * 2. The state is immediately converted to PATH_DOWN for normal processing ++ * 3. If purge_disconnected is enabled, the path will be removed via sysfs ++ * This state should never be stored in pp->state or pp->chkrstate; it exists ++ * only as a transient return value from checkers to trigger special handling. + */ + enum path_check_state { + PATH_WILD = 0, +@@ -78,6 +87,7 @@ enum path_check_state { + PATH_TIMEOUT, + PATH_REMOVED, + PATH_DELAYED, ++ PATH_DISCONNECTED, /* Ephemeral: mapped to PATH_DOWN */ + PATH_MAX_STATE + }; + +@@ -113,9 +123,10 @@ enum { + CHECKER_MSGID_DOWN, + CHECKER_MSGID_GHOST, + CHECKER_MSGID_UNSUPPORTED, ++ CHECKER_MSGID_DISCONNECTED, + CHECKER_GENERIC_MSGTABLE_SIZE, +- CHECKER_FIRST_MSGID = 100, /* lowest msgid for checkers */ +- CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */ ++ CHECKER_FIRST_MSGID = 100, /* lowest msgid for checkers */ ++ CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */ + }; + + struct checker_class; +diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c +index d82f7dbc..370a02a6 100644 +--- a/libmultipath/checkers/tur.c ++++ b/libmultipath/checkers/tur.c +@@ -188,6 +188,16 @@ retry: + *msgid = CHECKER_MSGID_GHOST; + return PATH_GHOST; + } ++ } else if (key == 0x5) { ++ /* Illegal request */ ++ if (asc == 0x25 && ascq == 0x00) { ++ /* ++ * LUN NOT SUPPORTED: unmapped at target. ++ * Signals pp->disconnected, becomes PATH_DOWN. ++ */ ++ *msgid = CHECKER_MSGID_DISCONNECTED; ++ return PATH_DISCONNECTED; ++ } + } + *msgid = CHECKER_MSGID_DOWN; + return PATH_DOWN; +diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c +index 186423e0..c529f336 100644 +--- a/libmultipath/discovery.c ++++ b/libmultipath/discovery.c +@@ -2416,8 +2416,25 @@ int pathinfo(struct path *pp, struct config *conf, int mask) + pp->state == PATH_UNCHECKED || + pp->state == PATH_WILD) + pp->chkrstate = pp->state = newstate; ++ /* ++ * PATH_TIMEOUT and PATH_DISCONNECTED are ephemeral ++ * states that should never be stored in pp->state. ++ * Convert them to PATH_DOWN immediately. ++ */ + if (pp->state == PATH_TIMEOUT) + pp->state = PATH_DOWN; ++ if (pp->state == PATH_DISCONNECTED) { ++ int purge_enabled = pp->mpp && ++ pp->mpp->purge_disconnected == ++ PURGE_DISCONNECTED_ON; ++ if (purge_enabled && ++ pp->disconnected == NOT_DISCONNECTED) { ++ condlog(2, "%s: mark path for purge", ++ pp->dev); ++ pp->disconnected = DISCONNECTED_READY_FOR_PURGE; ++ } ++ pp->state = PATH_DOWN; ++ } + if (pp->state == PATH_UP && !pp->size) { + condlog(3, "%s: device size is 0, " + "path unusable", pp->dev); +diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c +index d8d91f64..d744d50e 100644 +--- a/libmultipath/io_err_stat.c ++++ b/libmultipath/io_err_stat.c +@@ -380,6 +380,7 @@ static void account_async_io_state(struct io_err_stat_path *pp, int rc) + switch (rc) { + case PATH_DOWN: + case PATH_TIMEOUT: ++ case PATH_DISCONNECTED: + pp->io_err_nr++; + break; + case PATH_UNCHECKED: +diff --git a/libmultipath/print.c b/libmultipath/print.c +index ff224bc4..42d1d44c 100644 +--- a/libmultipath/print.c ++++ b/libmultipath/print.c +@@ -487,6 +487,8 @@ snprint_chk_state (struct strbuf *buff, const struct path * pp) + return append_strbuf_str(buff, "i/o timeout"); + case PATH_DELAYED: + return append_strbuf_str(buff, "delayed"); ++ case PATH_DISCONNECTED: ++ return append_strbuf_str(buff, "disconnected"); + default: + return append_strbuf_str(buff, "undef"); + } +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index e1969b95..32643684 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -185,6 +185,18 @@ enum purge_disconnected_states { + PURGE_DISCONNECTED_ON = YNU_YES, /* Purge disconnected paths */ + }; + ++/* ++ * Path disconnection state (per path) ++ * Tracks whether a path has been marked for purge and whether it's already queued. ++ */ ++enum path_disconnected_state { ++ NOT_DISCONNECTED, /* Path is not disconnected */ ++ DISCONNECTED_READY_FOR_PURGE, /* Path is disconnected and ready to be ++ queued for purge */ ++ DISCONNECTED_QUEUED_FOR_PURGE, /* Path is disconnected and already ++ queued for purge */ ++}; ++ + #define PROTOCOL_UNSET -1 + + enum scsi_protocol { +@@ -355,6 +367,8 @@ struct path { + int state; + int dmstate; + int chkrstate; ++ enum path_disconnected_state disconnected; /* Marked for purge due to ++ disconnection */ + int failcount; + int priority; + int pgindex; +diff --git a/multipathd/Makefile b/multipathd/Makefile +index 00342464..a49c4973 100644 +--- a/multipathd/Makefile ++++ b/multipathd/Makefile +@@ -46,7 +46,7 @@ ifeq ($(ENABLE_DMEVENTS_POLL),0) + endif + + OBJS = main.o pidfile.o uxlsnr.o uxclnt.o cli.o cli_handlers.o waiter.o \ +- dmevents.o init_unwinder.o ++ dmevents.o init_unwinder.o purge.o + + ifeq ($(FPIN_SUPPORT),1) + OBJS += fpin_handlers.o +diff --git a/multipathd/main.c b/multipathd/main.c +index 9beb0e06..d91a4d49 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -84,6 +84,7 @@ + #include "io_err_stat.h" + #include "wwids.h" + #include "foreign.h" ++#include "purge.h" + #include "../third-party/valgrind/drd.h" + #include "init_unwinder.h" + +@@ -135,11 +136,11 @@ static volatile enum daemon_status running_state = DAEMON_INIT; + pid_t daemon_pid; + static pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER; + static pthread_cond_t config_cond; +-static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr, +- fpin_thr, fpin_consumer_thr; +-static bool check_thr_started, uevent_thr_started, uxlsnr_thr_started, +- uevq_thr_started, dmevent_thr_started, fpin_thr_started, +- fpin_consumer_thr_started; ++static pthread_t check_thr, purge_thr, uevent_thr, uxlsnr_thr, uevq_thr, ++ dmevent_thr, fpin_thr, fpin_consumer_thr; ++static bool check_thr_started, purge_thr_started, uevent_thr_started, ++ uxlsnr_thr_started, uevq_thr_started, dmevent_thr_started, ++ fpin_thr_started, fpin_consumer_thr_started; + static int pid_fd = -1; + + static inline enum daemon_status get_running_state(void) +@@ -2377,6 +2378,28 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + if (newstate == PATH_REMOVED) + newstate = PATH_DOWN; + ++ /* ++ * PATH_DISCONNECTED is an ephemeral state used to signal that a path ++ * has been disconnected at the storage target (LUN unmapped). We use ++ * it to set pp->disconnected for purge tracking, then immediately ++ * convert it to PATH_DOWN for normal path failure handling. ++ * ++ * This ensures PATH_DISCONNECTED never gets stored in pp->state or ++ * pp->chkrstate - it exists only as a transient signal from the ++ * checker to trigger special handling before becoming PATH_DOWN. ++ */ ++ if (newstate == PATH_DISCONNECTED) { ++ if (pp->mpp && ++ pp->mpp->purge_disconnected == PURGE_DISCONNECTED_ON && ++ pp->disconnected == NOT_DISCONNECTED) { ++ condlog(2, "%s: mark (%s) path for purge", pp->dev, ++ checker_state_name(newstate)); ++ pp->disconnected = DISCONNECTED_READY_FOR_PURGE; ++ } ++ /* Always convert to PATH_DOWN for normal processing */ ++ newstate = PATH_DOWN; ++ } ++ + if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) { + condlog(2, "%s: unusable path (%s) - checker failed", + pp->dev, checker_state_name(newstate)); +@@ -2684,6 +2707,7 @@ checkerloop (void *ap) + struct timespec diff_time, start_time, end_time; + int num_paths = 0, strict_timing, rc = 0; + unsigned int ticks = 0; ++ LIST_HEAD(purge_list); + + get_monotonic_time(&start_time); + if (start_time.tv_sec && last_time.tv_sec) { +@@ -2724,6 +2748,12 @@ checkerloop (void *ap) + } + lock_cleanup_pop(vecs->lock); + ++ /* ++ * Cleanup handler to free purge_list if thread is cancelled. ++ * This prevents memory leaks during shutdown. ++ */ ++ pthread_cleanup_push(cleanup_purge_list, &purge_list); ++ + pthread_cleanup_push(cleanup_lock, &vecs->lock); + lock(&vecs->lock); + pthread_testcancel(); +@@ -2731,6 +2761,11 @@ checkerloop (void *ap) + retry_count_tick(vecs->mpvec); + missing_uev_wait_tick(vecs); + ghost_delay_tick(vecs); ++ /* ++ * Build purge list for disconnected paths. ++ * The caller will queue it after releasing vecs->lock. ++ */ ++ build_purge_list(vecs, &purge_list); + lock_cleanup_pop(vecs->lock); + + if (count) +@@ -2745,6 +2780,26 @@ checkerloop (void *ap) + lock_cleanup_pop(vecs->lock); + } + ++ /* ++ * Queue purge work for disconnected paths. ++ * This is done after releasing vecs->lock to avoid holding ++ * the lock while signaling the purge thread. ++ */ ++ if (!list_empty(&purge_list)) { ++ pthread_cleanup_push(cleanup_mutex, &purge_mutex); ++ pthread_mutex_lock(&purge_mutex); ++ pthread_testcancel(); ++ list_splice_tail_init(&purge_list, &purge_queue); ++ pthread_cond_signal(&purge_cond); ++ pthread_cleanup_pop(1); ++ } ++ ++ /* ++ * Pop cleanup handler. Execute it (arg=1) to free purge_list ++ * at the end of each iteration. ++ */ ++ pthread_cleanup_pop(1); ++ + diff_time.tv_nsec = 0; + if (start_time.tv_sec) { + get_monotonic_time(&end_time); +@@ -3225,6 +3280,8 @@ static void cleanup_threads(void) + + if (check_thr_started) + pthread_cancel(check_thr); ++ if (purge_thr_started) ++ pthread_cancel(purge_thr); + if (uevent_thr_started) + pthread_cancel(uevent_thr); + if (uxlsnr_thr_started) +@@ -3241,6 +3298,8 @@ static void cleanup_threads(void) + + if (check_thr_started) + pthread_join(check_thr, NULL); ++ if (purge_thr_started) ++ pthread_join(purge_thr, NULL); + if (uevent_thr_started) + pthread_join(uevent_thr, NULL); + if (uxlsnr_thr_started) +@@ -3496,6 +3555,11 @@ child (__attribute__((unused)) void *param) + goto failed; + } else + check_thr_started = true; ++ if ((rc = pthread_create(&purge_thr, &misc_attr, purgeloop, vecs))) { ++ condlog(0, "failed to create purge loop thread: %d", rc); ++ goto failed; ++ } else ++ purge_thr_started = true; + if ((rc = pthread_create(&uevq_thr, &misc_attr, uevqloop, vecs))) { + condlog(0, "failed to create uevent dispatcher: %d", rc); + goto failed; +diff --git a/multipathd/purge.c b/multipathd/purge.c +new file mode 100644 +index 00000000..44f0c905 +--- /dev/null ++++ b/multipathd/purge.c +@@ -0,0 +1,326 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2025 Brian Bunker ++ * Copyright (C) 2025 Krishna Kant ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "vector.h" ++#include "structs.h" ++#include "structs_vec.h" ++#include "debug.h" ++#include "util.h" ++#include "lock.h" ++#include "sysfs.h" ++#include "list.h" ++#include "purge.h" ++ ++pthread_mutex_t purge_mutex = PTHREAD_MUTEX_INITIALIZER; ++pthread_cond_t purge_cond = PTHREAD_COND_INITIALIZER; ++LIST_HEAD(purge_queue); ++ ++/* ++ * Information needed to purge a path. We copy this data while holding ++ * vecs->lock, then release the lock before doing the actual sysfs write. ++ * This prevents blocking other operations while waiting for sysfs I/O. ++ * ++ * The udev device reference captures the sysfs path (including H:C:T:L). ++ * The duplicated fd prevents device name/number reuse: the kernel will not ++ * reuse the device's minor number (which maps to the device name) for a new ++ * device while we hold an open file descriptor, even if the original device ++ * has been removed. This protects against deleting a new device that reused ++ * the same name after the original was removed externally. ++ */ ++struct purge_path_info { ++ struct list_head node; /* List linkage */ ++ struct udev_device *udev; /* Udev device (refcounted) */ ++ int fd; /* Dup'd fd prevents device reuse */ ++}; ++ ++/* ++ * Attempt to delete a path by writing to the SCSI device's sysfs delete ++ * attribute. This triggers kernel-level device removal. The actual cleanup ++ * of the path structure from pathvec happens later when a uevent arrives ++ * (handled by uev_remove_path). ++ * ++ * This function does NOT require vecs->lock to be held, as it operates on ++ * copied data. This function may block while writing to sysfs, which is ++ * why it's called without holding any locks. ++ * ++ * Protection against device reuse: ++ * The duplicated fd in purge_path_info prevents the kernel from reusing ++ * the device's minor number (and thus the device name like /dev/sdd) for ++ * a new device, even if the original device has been removed externally. ++ * This ensures we cannot accidentally delete a new device that reused the ++ * same name. The kernel maintains this guarantee as long as we hold the ++ * open file descriptor. ++ */ ++static void delete_path_sysfs(struct purge_path_info *info) ++{ ++ struct udev_device *ud; ++ const char *devname; ++ ++ if (!info->udev) ++ goto out; ++ ++ devname = udev_device_get_devnode(info->udev); ++ ++ /* ++ * Get the SCSI device parent. This is where we'll write to the ++ * "delete" attribute to trigger device removal. ++ */ ++ ud = udev_device_get_parent_with_subsystem_devtype(info->udev, "scsi", ++ "scsi_device"); ++ if (!ud) { ++ condlog(3, "%s: failed to purge, no SCSI parent found", devname); ++ goto out; ++ } ++ ++ /* ++ * Write "1" to the SCSI device's delete attribute to trigger ++ * kernel-level device removal. ++ */ ++ if (sysfs_attr_set_value(ud, "delete", "1", 1) < 0) ++ condlog(3, "%s: failed to purge", devname); ++ else ++ condlog(2, "%s: purged", devname); ++ ++out: ++ return; ++} ++ ++/* ++ * Prepare purge info for a path while holding vecs->lock. ++ * Takes a reference on the udev device and duplicates the fd. ++ * Returns allocated purge_path_info on success, NULL on failure. ++ * ++ * We require a valid fd because it prevents the kernel from reusing ++ * the device's minor number (and device name) for a new device while ++ * we hold it open. This protects against accidentally deleting a new ++ * device that reused the same name after the original was removed. ++ */ ++static struct purge_path_info *prepare_purge_path_info(struct path *pp) ++{ ++ struct purge_path_info *info = NULL; ++ ++ if (!pp->udev || !pp->mpp) ++ goto out; ++ ++ /* ++ * We require a valid fd to prevent device name reuse. ++ * Without it, we cannot safely purge the device. ++ */ ++ if (pp->fd < 0) { ++ condlog(3, "%s: no fd available, cannot safely purge", pp->dev); ++ goto out; ++ } ++ ++ info = calloc(1, sizeof(*info)); ++ if (!info) ++ goto out; ++ ++ INIT_LIST_HEAD(&info->node); ++ info->udev = udev_device_ref(pp->udev); ++ if (!info->udev) ++ goto out_free; ++ ++ info->fd = dup(pp->fd); ++ if (info->fd < 0) { ++ condlog(3, "%s: failed to dup fd: %s, cannot safely purge", ++ pp->dev, strerror(errno)); ++ goto out_unref; ++ } ++ ++ return info; ++ ++out_unref: ++ udev_device_unref(info->udev); ++out_free: ++ free(info); ++ info = NULL; ++out: ++ return info; ++} ++ ++/* ++ * Clean up and free purge info. ++ */ ++static void free_purge_path_info(struct purge_path_info *info) ++{ ++ if (!info) ++ return; ++ ++ if (info->fd >= 0) ++ close(info->fd); ++ if (info->udev) ++ udev_device_unref(info->udev); ++ free(info); ++} ++ ++/* ++ * Build a list of purge_path_info for all paths marked for purge. ++ * This should be called while holding vecs->lock. It clears the ++ * disconnected flag and prepares purge info for each path, adding ++ * them to tmpq. ++ */ ++void build_purge_list(struct vectors *vecs, struct list_head *tmpq) ++{ ++ struct path *pp; ++ unsigned int i; ++ ++ vector_foreach_slot (vecs->pathvec, pp, i) { ++ struct purge_path_info *info; ++ ++ if (pp->disconnected != DISCONNECTED_READY_FOR_PURGE) ++ continue; ++ ++ /* ++ * Mark as queued whether we succeed or fail. ++ * On success, we're purging it now. ++ * On failure, retrying is unlikely to help until ++ * the checker re-evaluates the path. ++ */ ++ pp->disconnected = DISCONNECTED_QUEUED_FOR_PURGE; ++ ++ info = prepare_purge_path_info(pp); ++ if (info) { ++ condlog(2, "%s: queuing path for purge", pp->dev); ++ list_add_tail(&info->node, tmpq); ++ } else ++ condlog(3, "%s: failed to prepare purge info", pp->dev); ++ } ++} ++ ++static void rcu_unregister(__attribute__((unused)) void *param) ++{ ++ rcu_unregister_thread(); ++} ++ ++/* ++ * Cleanup handler for a single purge_path_info. ++ * Used to prevent memory leaks if thread is cancelled while processing. ++ */ ++static void cleanup_purge_path_info(void *arg) ++{ ++ struct purge_path_info *info = arg; ++ ++ free_purge_path_info(info); ++} ++ ++/* ++ * Cleanup handler for purge list. Frees all purge_path_info entries. ++ * Can be called as a pthread cleanup handler or directly. ++ */ ++void cleanup_purge_list(void *arg) ++{ ++ struct list_head *purge_list = arg; ++ struct purge_path_info *info, *tmp; ++ ++ list_for_each_entry_safe(info, tmp, purge_list, node) ++ { ++ list_del_init(&info->node); ++ free_purge_path_info(info); ++ } ++} ++ ++/* ++ * Cleanup handler for the global purge queue. ++ * Used during shutdown to free any remaining queued items. ++ */ ++static void cleanup_global_purge_queue(void *arg __attribute__((unused))) ++{ ++ pthread_mutex_lock(&purge_mutex); ++ cleanup_purge_list(&purge_queue); ++ pthread_mutex_unlock(&purge_mutex); ++} ++ ++/* ++ * Main purge thread loop. ++ * ++ * This thread waits for purge_path_info structs to be queued by the checker ++ * thread, then processes them by writing to their sysfs delete attributes. ++ * The checker thread builds the list while holding vecs->lock, so this ++ * thread doesn't need to grab that lock at all. ++ * ++ * Uses list_splice_tail_init() like uevent_dispatch() to safely transfer ++ * items from the global queue to a local list for processing. ++ * ++ * Cleanup handlers are registered for both the local purge_list and the ++ * global purge_queue (similar to uevent_listen), and for each individual ++ * purge_path_info after it's popped off the list (similar to service_uevq). ++ * This ensures no memory leaks if the thread is cancelled at any point. ++ */ ++void *purgeloop(void *ap __attribute__((unused))) ++{ ++ pthread_cleanup_push(rcu_unregister, NULL); ++ rcu_register_thread(); ++ mlockall(MCL_CURRENT | MCL_FUTURE); ++ ++ /* ++ * Cleanup handler for global purge_queue. ++ * This handles items that were queued but not yet moved to purge_list. ++ */ ++ pthread_cleanup_push(cleanup_global_purge_queue, NULL); ++ ++ while (1) { ++ LIST_HEAD(purge_list); ++ struct purge_path_info *info; ++ ++ /* ++ * Cleanup handler for local purge_list. ++ * This handles items that were moved from purge_queue but ++ * not yet processed. ++ */ ++ pthread_cleanup_push(cleanup_purge_list, &purge_list); ++ ++ /* ++ * Cleanup handler for purge_mutex. ++ * Note: pthread_cond_wait() reacquires the mutex before ++ * returning, even on cancellation, so this cleanup handler ++ * will properly unlock it if we're cancelled. ++ */ ++ pthread_cleanup_push(cleanup_mutex, &purge_mutex); ++ pthread_mutex_lock(&purge_mutex); ++ pthread_testcancel(); ++ while (list_empty(&purge_queue)) { ++ condlog(4, "purgeloop waiting for work"); ++ pthread_cond_wait(&purge_cond, &purge_mutex); ++ } ++ list_splice_tail_init(&purge_queue, &purge_list); ++ pthread_cleanup_pop(1); ++ ++ /* ++ * Process all paths in the list without holding any locks. ++ * The sysfs operations may block, but that's fine since we're ++ * not holding vecs->lock. ++ * ++ * After popping each info off the list, we immediately push ++ * a cleanup handler for it. This ensures it gets freed even ++ * if we're cancelled inside delete_path_sysfs(). ++ */ ++ while ((info = list_pop_entry(&purge_list, typeof(*info), node))) { ++ pthread_cleanup_push(cleanup_purge_path_info, info); ++ delete_path_sysfs(info); ++ pthread_cleanup_pop(1); ++ } ++ ++ /* ++ * Pop cleanup handler without executing it (0) since we've ++ * already freed everything above. The handler only runs if ++ * the thread is cancelled during processing. ++ */ ++ pthread_cleanup_pop(0); ++ } ++ ++ pthread_cleanup_pop(1); ++ pthread_cleanup_pop(1); ++ return NULL; ++} +diff --git a/multipathd/purge.h b/multipathd/purge.h +new file mode 100644 +index 00000000..1fe755f3 +--- /dev/null ++++ b/multipathd/purge.h +@@ -0,0 +1,41 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2025 Brian Bunker ++ * Copyright (C) 2025 Krishna Kant ++ */ ++ ++#ifndef PURGE_H_INCLUDED ++#define PURGE_H_INCLUDED ++ ++#include ++#include "list.h" ++ ++struct vectors; ++ ++/* ++ * Purge thread synchronization. ++ * The checker thread builds a list of paths to purge and queues them here. ++ * The purge thread picks up the queue and processes it. ++ */ ++extern pthread_mutex_t purge_mutex; ++extern pthread_cond_t purge_cond; ++extern struct list_head purge_queue; ++ ++/* ++ * Build a list of paths to purge and add them to tmpq. Called by checker ++ * thread while holding vecs->lock. ++ */ ++void build_purge_list(struct vectors *vecs, struct list_head *tmpq); ++ ++/* ++ * Cleanup handler for purge list. Frees all purge_path_info entries. ++ * Can be called as a pthread cleanup handler or directly for shutdown cleanup. ++ */ ++void cleanup_purge_list(void *arg); ++ ++/* ++ * Main purge thread loop ++ */ ++void *purgeloop(void *ap); ++ ++#endif /* PURGE_H_INCLUDED */ diff --git a/SOURCES/0208-libmpathpersist-fix-register-retry-status-checking.patch b/SOURCES/0208-libmpathpersist-fix-register-retry-status-checking.patch new file mode 100644 index 0000000..2dceac3 --- /dev/null +++ b/SOURCES/0208-libmpathpersist-fix-register-retry-status-checking.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 1 Dec 2025 22:02:10 -0500 +Subject: [PATCH] libmpathpersist: fix register retry status checking + +If there libmpathpersist failed to create a thread to retry the register +and ignore command, mpath_prout_reg should fail. Instead, the code was +simply ignoring the failed threads. Fix that. + +Fixes: 2a4ca250 ("libmpathpersist: change how reservation conflicts are handled") +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmpathpersist/mpath_persist.c | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 72be48c1..6cb56dab 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -858,18 +858,19 @@ int mpath_prout_reg(struct multipath *mpp,int rq_servact, int rq_scope, + } + } + for (i = 0; i < count; i++) { +- if (thread[i].param.status != MPATH_PR_SKIP && +- thread[i].param.status != MPATH_PR_THREAD_ERROR) { ++ if (thread[i].param.status == MPATH_PR_SKIP) ++ continue; ++ if (thread[i].param.status != MPATH_PR_THREAD_ERROR) { + rc = pthread_join(thread[i].id, NULL); + if (rc) { + condlog(3, "%s: failed to join thread while retrying %d", +- mpp->wwid, i); ++ mpp->wwid, i); + } +- if (thread[i].param.status == MPATH_PR_RETRYABLE_ERROR) +- retryable_error = true; +- else if (status == MPATH_PR_SUCCESS) +- status = thread[i].param.status; + } ++ if (thread[i].param.status == MPATH_PR_RETRYABLE_ERROR) ++ retryable_error = true; ++ else if (status == MPATH_PR_SUCCESS) ++ status = thread[i].param.status; + } + need_retry = false; + } diff --git a/SOURCES/0209-multipathd-remember-number-of-registered-keys-when-i.patch b/SOURCES/0209-multipathd-remember-number-of-registered-keys-when-i.patch new file mode 100644 index 0000000..6911cdb --- /dev/null +++ b/SOURCES/0209-multipathd-remember-number-of-registered-keys-when-i.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 1 Dec 2025 22:02:12 -0500 +Subject: [PATCH] multipathd: remember number of registered keys when ioctl + fails + +If prin_do_scsi_ioctl() fails in update_map_pr() for some reason other +than Persistent Reservations not being supported, It shouldn't clear the +number of registered keys, since there's no reason to think that it has +changed. Similarly, if update_map_pr() fails in mpath_pr_event_handle(), +don't assume that the nr_keys_needed was cleared. Just return whatever +the value is now. This saves multipathd from doing pointless calls to +update_map_pr(), if one of the paths is failing. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + multipathd/main.c | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index d91a4d49..2526d8d1 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3842,7 +3842,9 @@ void unset_pr(struct multipath *mpp) + * The number of found keys must be at least as large as *nr_keys, + * and if MPATH_PR_SUCCESS is returned and mpp->prflag is PR_SET after + * the call, *nr_keys will be set to the number of found keys. Otherwise +- * it will be set to 0. ++ * if mpp->prflag is PR_UNSET it will be set to 0. If MPATH_PR_SUCCESS ++ * is not returned and mpp->prflag is not PR_UNSET, nr_keys will not be ++ * changed. + */ + static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *nr_keys) + { +@@ -3871,11 +3873,12 @@ static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *n + + ret = prin_do_scsi_ioctl(pp->dev, MPATH_PRIN_RKEY_SA, &resp, 0); + if (ret != MPATH_PR_SUCCESS) { +- if (ret == MPATH_PR_ILLEGAL_REQ) ++ if (ret == MPATH_PR_ILLEGAL_REQ) { + unset_pr(mpp); ++ *nr_keys = 0; ++ } + condlog(0, "%s : pr in read keys service action failed Error=%d", + mpp->alias, ret); +- *nr_keys = 0; + return ret; + } + +@@ -3990,7 +3993,7 @@ retry: + clear_reg ? "Clearing" : "Setting", pp->dev, ret); + } else if (!clear_reg) { + if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS) +- return 0; ++ return nr_keys_needed; + if (mpp->prflag != PR_SET) { + memset(¶m, 0, sizeof(param)); + clear_reg = true; diff --git a/SOURCES/0210-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch b/SOURCES/0210-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch new file mode 100644 index 0000000..90b99d5 --- /dev/null +++ b/SOURCES/0210-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch @@ -0,0 +1,459 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 15 Dec 2025 15:29:58 -0500 +Subject: [PATCH] libmpathpersist: fix code for skipping multipathd path + registration + +When libmpathpersist notifies multipathd that a key has been registered, +cli_setprstatus() calls pr_register_active_paths() with a flag to let it +know that the paths are likely already registered, and it can skip +re-registering them, as long as the number of active paths matches the +number of registered keys. This shortcut can fail, causing multipathd to +not register needed paths, if either a path becomes usable and another +becomes unusable while libmpathpersist is running or if there already +were registered keys for I_T Nexus's that don't correspond to path +devices. + +To make this shortcut work in cases like that, this commit adds a new +multipathd command "setprstatus map pathlist ", where + is a quoted, whitespace separated list of scsi path devices. +libmpathpersist will send out the list of paths it registered the key +on. pr_register_active_paths() will skip calling mpath_pr_event_handle() +for paths on that list. + +In order to deal with the possiblity of a preempt occuring while +libmpathpersist was running, the code still needs to check that it has +the expected number of keys. + +Fixes: f7d6cd17 ("multipathd: Fix race while registering PR key") +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmpathpersist/mpath_persist.c | 6 +-- + libmpathpersist/mpath_updatepr.c | 50 ++++++++++++++++++------- + libmpathpersist/mpathpr.h | 2 +- + multipathd/cli.c | 2 + + multipathd/cli.h | 2 + + multipathd/cli_handlers.c | 37 +++++++++++++++++-- + multipathd/cli_handlers.h | 1 + + multipathd/main.c | 63 +++++++++++++++++++++----------- + multipathd/main.h | 4 +- + multipathd/multipathd.8 | 10 ++++- + 10 files changed, 132 insertions(+), 45 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index 6cb56dab..ab8fa630 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -527,12 +527,12 @@ static int do_mpath_persistent_reserve_out(vector curmp, vector pathvec, int fd, + case MPATH_PROUT_REG_SA: + case MPATH_PROUT_REG_IGN_SA: + if (unregistering) +- update_prflag(alias, 0); ++ update_prflag(mpp, 0); + else +- update_prflag(alias, 1); ++ update_prflag(mpp, 1); + break; + case MPATH_PROUT_CLEAR_SA: +- update_prflag(alias, 0); ++ update_prflag(mpp, 0); + if (mpp->prkey_source == PRKEY_SOURCE_FILE) + update_prkey(alias, 0); + break; +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index bfa6e089..e24432bb 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -19,9 +19,9 @@ + #include "memory.h" + #include "mpathpr.h" + #include "structs.h" ++#include "strbuf.h" + +- +-static char *do_pr(char *alias, char *str) ++static char *do_pr(char *alias, const char *str) + { + int fd; + char *reply; +@@ -51,24 +51,26 @@ static char *do_pr(char *alias, char *str) + return reply; + } + +-static int do_update_pr(char *alias, char *cmd, char *key) ++static int do_update_pr(char *alias, char *cmd, const char *data) + { +- char str[256]; ++ STRBUF_ON_STACK(buf); + char *reply = NULL; + int ret = -1; + +- if (key) +- snprintf(str,sizeof(str),"%s map %s key %s", cmd, alias, key); ++ if (data) ++ print_strbuf(&buf, "%s map %s %s %s", cmd, alias, ++ strcmp(cmd, "setprkey") ? "pathlist" : "key", data); + else +- snprintf(str,sizeof(str),"%s map %s", cmd, alias); ++ print_strbuf(&buf, "%s map %s", cmd, alias); + +- reply = do_pr(alias, str); ++ reply = do_pr(alias, get_strbuf_str(&buf)); + if (reply) { +- condlog (2, "%s: message=%s reply=%s", alias, str, reply); +- if (reply && strncmp(reply,"ok", 2) == 0) ++ if (strncmp(reply, "ok", 2) == 0) + ret = 0; + else + ret = -1; ++ condlog(ret ? 0 : 4, "%s: message=%s reply=%s", alias, ++ get_strbuf_str(&buf), reply); + } + + free(reply); +@@ -104,9 +106,31 @@ int get_prhold(char *mapname) { + return do_get_pr(mapname, "getprhold"); + } + +-int update_prflag(char *mapname, int set) { +- return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus", +- NULL); ++int update_prflag(struct multipath *mpp, int set) ++{ ++ STRBUF_ON_STACK(buf); ++ int i, j; ++ bool first = true; ++ struct pathgroup *pgp = NULL; ++ struct path *pp = NULL; ++ ++ if (!set) ++ return do_update_pr(mpp->alias, "unsetprstatus", NULL); ++ ++ append_strbuf_str(&buf, "\""); ++ vector_foreach_slot (mpp->pg, pgp, j) { ++ vector_foreach_slot (pgp->paths, pp, i) { ++ if (pp->state == PATH_UP || pp->state == PATH_GHOST) { ++ if (first) { ++ append_strbuf_str(&buf, pp->dev); ++ first = false; ++ } else ++ print_strbuf(&buf, " %s", pp->dev_t); ++ } ++ } ++ } ++ append_strbuf_str(&buf, "\""); ++ return do_update_pr(mpp->alias, "setprstatus", get_strbuf_str(&buf)); + } + + int update_prhold(char *mapname, bool set) { +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 69f402f2..67d04247 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -40,7 +40,7 @@ int mpath_prout_rel(struct multipath *mpp,int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor *paramp, int noisy, + bool unregister); + +-int update_prflag(char *mapname, int set); ++int update_prflag(struct multipath *mpp, int set); + int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); + int get_prflag(char *mapname); + int get_prhold(char *mapname); +diff --git a/multipathd/cli.c b/multipathd/cli.c +index d33b571d..9e833b02 100644 +--- a/multipathd/cli.c ++++ b/multipathd/cli.c +@@ -226,6 +226,7 @@ load_keys (void) + r += add_key(keys, "getprhold", GETPRHOLD, 0); + r += add_key(keys, "setprhold", SETPRHOLD, 0); + r += add_key(keys, "unsetprhold", UNSETPRHOLD, 0); ++ r += add_key(keys, "pathlist", PATHLIST, 1); + + if (r) { + free_keys(keys); +@@ -572,6 +573,7 @@ cli_init (void) { + add_handler(SHUTDOWN, NULL); + add_handler(GETPRSTATUS+MAP, NULL); + add_handler(SETPRSTATUS+MAP, NULL); ++ add_handler(SETPRSTATUS+MAP+PATHLIST, NULL); + add_handler(UNSETPRSTATUS+MAP, NULL); + add_handler(GETPRKEY+MAP, NULL); + add_handler(SETPRKEY+MAP+KEY, NULL); +diff --git a/multipathd/cli.h b/multipathd/cli.h +index 4d12f8fd..17344cb4 100644 +--- a/multipathd/cli.h ++++ b/multipathd/cli.h +@@ -50,6 +50,7 @@ enum { + __GETPRHOLD, + __SETPRHOLD, + __UNSETPRHOLD, ++ __PATHLIST, + }; + + #define LIST (1 << __LIST) +@@ -99,6 +100,7 @@ enum { + #define GETPRHOLD (1ULL << __GETPRHOLD) + #define SETPRHOLD (1ULL << __SETPRHOLD) + #define UNSETPRHOLD (1ULL << __UNSETPRHOLD) ++#define PATHLIST (1ULL << __PATHLIST) + + #define INITIAL_REPLY_LEN 1200 + +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index 0c63ca9a..f183fceb 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -32,6 +32,7 @@ + #include "foreign.h" + #include "strbuf.h" + #include "cli_handlers.h" ++#include + + #define SET_REPLY_AND_LEN(__rep, __len, string_literal) \ + do { \ +@@ -1308,8 +1309,8 @@ cli_getprstatus (void * v, char ** reply, int * len, void * data) + return 0; + } + +-int +-cli_setprstatus(void * v, char ** reply, int * len, void * data) ++static int do_setprstatus(void * v, char ** reply, int * len, void * data, ++ const struct _vector *registered_paths) + { + struct multipath * mpp; + struct vectors * vecs = (struct vectors *)data; +@@ -1324,7 +1325,7 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + + if (mpp->prflag != PR_SET) { + set_pr(mpp); +- pr_register_active_paths(mpp, true); ++ pr_register_active_paths(mpp, registered_paths); + if (mpp->prflag == PR_SET) + condlog(2, "%s: prflag set", param); + else +@@ -1336,6 +1337,36 @@ cli_setprstatus(void * v, char ** reply, int * len, void * data) + return 0; + } + ++int ++cli_setprstatus(void * v, char ** reply, int * len, void * data) ++{ ++ return do_setprstatus(v, reply, len, data, NULL); ++} ++ ++int ++cli_setprstatus_list(void * v, char ** reply, int * len, void *data) ++{ ++ int r; ++ struct _vector registered_paths_vec = {.allocated = 0}; ++ vector registered_paths ++ __attribute__((cleanup(cleanup_reset_vec))) = ®istered_paths_vec; ++ char *ptr = get_keyparam(v, PATHLIST); ++ ++ while (isspace(*ptr)) ++ ptr++; ++ while (*ptr) { ++ if (!vector_alloc_slot(registered_paths)) ++ return -ENOMEM; ++ vector_set_slot(registered_paths, ptr); ++ while (*ptr && !isspace(*ptr)) ++ ptr++; ++ while (isspace(*ptr)) ++ *ptr++ = '\0'; ++ } ++ r = do_setprstatus(v, reply, len, data, registered_paths); ++ return r; ++} ++ + int + cli_unsetprstatus(void * v, char ** reply, int * len, void * data) + { +diff --git a/multipathd/cli_handlers.h b/multipathd/cli_handlers.h +index 348c8485..28b6f2bb 100644 +--- a/multipathd/cli_handlers.h ++++ b/multipathd/cli_handlers.h +@@ -46,6 +46,7 @@ int cli_shutdown(void * v, char ** reply, int * len, void * data); + int cli_reassign (void * v, char ** reply, int * len, void * data); + int cli_getprstatus(void * v, char ** reply, int * len, void * data); + int cli_setprstatus(void * v, char ** reply, int * len, void * data); ++int cli_setprstatus_list(void * v, char ** reply, int * len, void * data); + int cli_unsetprstatus(void * v, char ** reply, int * len, void * data); + int cli_getprkey(void * v, char ** reply, int * len, void * data); + int cli_setprkey(void * v, char ** reply, int * len, void * data); +diff --git a/multipathd/main.c b/multipathd/main.c +index 2526d8d1..09d26fe2 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -548,28 +548,47 @@ flush_map_nopaths(struct multipath *mpp, struct vectors *vecs) { + return true; + } + +-void pr_register_active_paths(struct multipath *mpp, bool check_nr_active) ++/* ++ * If reg_paths in non-NULL, it is a vector of paths that libmpathpersist ++ * registered. If the number of registered keys is smaller than the number ++ * of registered paths, then likely a preempt that occurred while ++ * libmpathpersist was registering the key. As long as there are still some ++ * registered keys, treat the preempt as happening first, and make sure to ++ * register keys on all the paths. If the number of registered keys is at ++ * least as large as the number of registered paths, then no preempt happened, ++ * and multipathd does not need to re-register the paths that libmpathpersist ++ * handled ++ */ ++void pr_register_active_paths(struct multipath *mpp, const struct _vector *reg_paths) + { +- unsigned int i, j, nr_keys = 0; +- unsigned int nr_active = 0; ++ unsigned int i, j, k, nr_keys = 0; ++ unsigned int wanted_nr = VECTOR_SIZE(reg_paths); + struct path *pp; + struct pathgroup *pgp; +- +- if (check_nr_active) { +- nr_active = count_active_paths(mpp); +- if (!nr_active) +- return; +- } ++ char *pathname; + + vector_foreach_slot (mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { + if (mpp->prflag == PR_UNSET) + return; +- if (pp->state == PATH_UP || pp->state == PATH_GHOST) { +- nr_keys = mpath_pr_event_handle(pp, nr_keys, nr_active); +- if (check_nr_active && nr_keys == nr_active) +- return; ++ if (pp->state != PATH_UP && pp->state != PATH_GHOST) ++ continue; ++ if (wanted_nr && nr_keys) { ++ vector_foreach_slot (reg_paths, pathname, k) { ++ if (strcmp(pp->dev_t, pathname) == 0) { ++ goto skip; ++ } ++ } + } ++ nr_keys = mpath_pr_event_handle(pp, nr_keys, wanted_nr); ++ if (nr_keys && nr_keys < wanted_nr) { ++ /* ++ * Incorrect number of registered keys. Need ++ * to register all devices ++ */ ++ wanted_nr = 0; ++ } ++ skip:; /* a statement must follow a label on pre C23 clang */ + } + } + } +@@ -599,8 +618,7 @@ handle_orphaned_offline_paths(vector offline_paths) + pp->add_when_online = true; + } + +-static void +-cleanup_reset_vec(struct _vector **v) ++void cleanup_reset_vec(struct _vector **v) + { + vector_reset(*v); + } +@@ -656,7 +674,7 @@ fail: + + sync_map_state(mpp); + +- pr_register_active_paths(mpp, false); ++ pr_register_active_paths(mpp, NULL); + + if (VECTOR_SIZE(offline_paths) != 0) + handle_orphaned_offline_paths(offline_paths); +@@ -1279,7 +1297,7 @@ rescan: + + if (retries >= 0) { + if ((mpp->prflag == PR_SET && prflag != PR_SET) || start_waiter) +- pr_register_active_paths(mpp, false); ++ pr_register_active_paths(mpp, NULL); + condlog(2, "%s [%s]: path added to devmap %s", + pp->dev, pp->dev_t, mpp->alias); + return 0; +@@ -1875,6 +1893,7 @@ uxlsnrloop (void * ap) + set_unlocked_handler_callback(SHUTDOWN, cli_shutdown); + set_handler_callback(GETPRSTATUS|MAP, cli_getprstatus); + set_handler_callback(SETPRSTATUS|MAP, cli_setprstatus); ++ set_handler_callback(SETPRSTATUS|MAP|PATHLIST, cli_setprstatus_list); + set_handler_callback(UNSETPRSTATUS|MAP, cli_unsetprstatus); + set_handler_callback(FORCEQ|DAEMON, cli_force_no_daemon_q); + set_handler_callback(RESTOREQ|DAEMON, cli_restore_no_daemon_q); +@@ -2581,7 +2600,7 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks) + mpath_pr_event_handle(pp, 0, 0); + if (pp->mpp->prflag == PR_SET && + prflag != PR_SET) +- pr_register_active_paths(pp->mpp, false); ++ pr_register_active_paths(pp->mpp, NULL); + } + } + +@@ -2950,7 +2969,7 @@ configure (struct vectors * vecs) + vector_foreach_slot(mpvec, mpp, i){ + if (remember_wwid(mpp->wwid) == 1) + trigger_paths_udev_change(mpp, true); +- pr_register_active_paths(mpp, false); ++ pr_register_active_paths(mpp, NULL); + } + + /* +@@ -3933,8 +3952,8 @@ static int update_map_pr(struct multipath *mpp, struct path *pp, unsigned int *n + * + * nr_keys_wanted: Only used if nr_keys_needed is 0, so we don't know how + * many keys we currently have. If nr_keys_wanted in non-zero and the +- * number of keys found by the initial call to update_map_pr() matches it, +- * exit early, since we have all the keys we are expecting. ++ * number of keys found by the initial call to update_map_pr() is at least ++ * as large as it, exit early, since we have all the keys we are expecting. + * + * The function returns the number of keys that are registered or 0 if + * it's unknown. +@@ -3957,7 +3976,7 @@ mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed, + nr_keys_needed = 1; + if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS) + return 0; +- if (nr_keys_wanted && nr_keys_wanted == nr_keys_needed) ++ if (nr_keys_wanted && nr_keys_wanted <= nr_keys_needed) + return nr_keys_needed; + } + +diff --git a/multipathd/main.h b/multipathd/main.h +index 8a4c5f88..ea12455f 100644 +--- a/multipathd/main.h ++++ b/multipathd/main.h +@@ -60,5 +60,7 @@ int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors *vecs); + void set_pr(struct multipath *mpp); + void unset_pr(struct multipath *mpp); +-void pr_register_active_paths(struct multipath *mpp, bool check_active_nr); ++void pr_register_active_paths(struct multipath *mpp, ++ const struct _vector *registered_paths); ++void cleanup_reset_vec(struct _vector **v); + #endif /* MAIN_H */ +diff --git a/multipathd/multipathd.8 b/multipathd/multipathd.8 +index 2ed036d4..0a76f3e3 100644 +--- a/multipathd/multipathd.8 ++++ b/multipathd/multipathd.8 +@@ -324,11 +324,17 @@ will not be disabled when the daemon stops. + Restores configured queue_without_daemon mode. + . + .TP +-.B map|multipath $map setprstatus ++.B setprstatus map|multipath $map + Enable persistent reservation management on $map. + . + .TP +-.B map|multipath $map unsetprstatus ++.B setprstatus map|multipath $map pathlist $pathlist ++Enable persistent reservation management on $map, and notify multipathd of ++the paths that have been registered, so it doesn't attempt to re-register ++them. ++. ++.TP ++.B unsetprstatus map|multipath $map + Disable persistent reservation management on $map. + . + .TP diff --git a/SPECS/device-mapper-multipath.spec b/SPECS/device-mapper-multipath.spec index a2d9752..53810a0 100644 --- a/SPECS/device-mapper-multipath.spec +++ b/SPECS/device-mapper-multipath.spec @@ -1,6 +1,6 @@ Name: device-mapper-multipath Version: 0.8.7 -Release: 39%{?dist} +Release: 45%{?dist} Summary: Tools to manage multipath devices using device-mapper License: GPLv2 URL: http://christophe.varoqui.free.fr/ @@ -159,7 +159,67 @@ Patch0146: 0146-multipath-tools-add-HPE-as-vendor-for-OPEN-XP8-array.patch Patch0147: 0147-multipath-tools-add-HP-HSVX740-to-hwtable.patch Patch0148: 0148-multipath-tools-add-DellEMC-ME5-PowerVault-ME5-to-ha.patch Patch0149: 0149-multipath-tools-add-HPE-MSA-Gen7-2070-2072-to-hwtabl.patch - +Patch0150: 0150-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch +Patch0151: 0151-libmpathpersist-check-released-key-against-the-reser.patch +Patch0152: 0152-multipathd-remove-thread-from-mpath_pr_event_handle.patch +Patch0153: 0153-libmpathpersist-remove-uneeded-wrapper-function.patch +Patch0154: 0154-libmpathpersist-reduce-log-level-for-persistent-rese.patch +Patch0155: 0155-libmpathpersist-remove-pointless-update_map_pr-ret-v.patch +Patch0156: 0156-multipathd-use-update_map_pr-in-mpath_pr_event_handl.patch +Patch0157: 0157-libmpathpersist-limit-changing-prflag-in-update_map_.patch +Patch0158: 0158-multipathd-Don-t-call-update_map_pr-unnecessarily.patch +Patch0159: 0159-libmpathpersist-remove-useless-function-send_prout_a.patch +Patch0160: 0160-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch +Patch0161: 0161-limpathpersist-redesign-failed-release-workaround.patch +Patch0162: 0162-libmpathpersist-fail-the-release-if-all-threads-fail.patch +Patch0163: 0163-limpathpersist-Handle-changing-key-corner-case.patch +Patch0164: 0164-libmpathpersist-fix-command-keyword-ordering.patch +Patch0165: 0165-libmpathpersist-use-conf-timeout-for-updating-persis.patch +Patch0166: 0166-libmapthpersist-Handle-REGISTER-AND-IGNORE-changing-.patch +Patch0167: 0167-libmultipath-rename-prflag_value-enums.patch +Patch0168: 0168-libmpathpersist-use-a-switch-statement-for-prout-com.patch +Patch0169: 0169-libmpathpersist-Add-safety-check-for-preempting-on-k.patch +Patch0170: 0170-limpathpersist-remove-update_map_pr-code-for-NULL-pp.patch +Patch0171: 0171-libmpathpersist-move-update_map_pr-to-multipathd.patch +Patch0172: 0172-multipathd-clean-up-update_map_pr-and-mpath_pr_event.patch +Patch0173: 0173-libmpathpersist-clean-up-duplicate-function-declarat.patch +Patch0174: 0174-multipathd-wrap-setting-and-unsetting-prflag.patch +Patch0175: 0175-multipathd-unregister-PR-key-when-path-is-restored-i.patch +Patch0176: 0176-libmpathpersist-Fix-up-reservation_key-checking.patch +Patch0177: 0177-libmpathpersist-change-how-reservation-conflicts-are.patch +Patch0178: 0178-libmpathpersist-Clear-prkey-in-multipathd-before-unr.patch +Patch0179: 0179-libmpathpersist-only-clear-the-key-if-we-are-using-t.patch +Patch0180: 0180-libmpathpersist-Restore-old-reservation-key-on-failu.patch +Patch0181: 0181-libmpatpersist-update-reservation-key-before-checkin.patch +Patch0182: 0182-libmpathpersist-retry-on-conflicts-in-mpath_prout_co.patch +Patch0183: 0183-libmpathpersist-Don-t-always-fail-registrations-for-.patch +Patch0184: 0184-libmpathpersist-Don-t-try-release-workaround-for-inv.patch +Patch0185: 0185-libmpathpersist-Don-t-fail-RESERVE-commands-unnecess.patch +Patch0186: 0186-libmpathpersist-reregister-keys-when-self-preempting.patch +Patch0187: 0187-libmpathpersist-handle-updating-key-race-condition.patch +Patch0188: 0188-libmpathpersist-handle-preempting-all-registrants-re.patch +Patch0189: 0189-libmpathpersist-Fix-REGISTER-AND-IGNORE-while-holdin.patch +Patch0190: 0190-libmpathpersist-Handle-RESERVE-with-reservation-held.patch +Patch0191: 0191-libmpathpersist-use-check_holding_reservation-in-mpa.patch +Patch0192: 0192-libmpathpersist-Fix-unregistering-while-holding-the-.patch +Patch0193: 0193-libmpathpersist-Fix-race-between-restoring-a-path-an.patch +Patch0194: 0194-multipathd-Fix-tracking-of-old-PR-key.patch +Patch0195: 0195-multipathd-Fix-race-while-registering-PR-key.patch +Patch0196: 0196-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch +Patch0197: 0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch +Patch0198: 0198-multipathd-print-path-offline-message-even-without-a.patch +Patch0199: 0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch +Patch0200: 0200-uevent_dispatch-use-while-in-wait-loop.patch +Patch0201: 0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch +Patch0202: 0202-libmultipath-uevent_listen-don-t-delay-uevents.patch +Patch0203: 0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch +Patch0204: 0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch +Patch0205: 0205-multipathd-make-multipathd-show-status-busy-checker-.patch +Patch0206: 0206-libmultipath-add-purge_disconnected-configuration-op.patch +Patch0207: 0207-multipathd-implement-purge-functionality-for-disconn.patch +Patch0208: 0208-libmpathpersist-fix-register-retry-status-checking.patch +Patch0209: 0209-multipathd-remember-number-of-registered-keys-when-i.patch +Patch0210: 0210-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch # runtime Requires: %{name}-libs = %{version}-%{release} @@ -362,6 +422,100 @@ fi %{_pkgconfdir}/libdmmp.pc %changelog +* Thu Feb 19 2026 Benjamin Marzinski - 0.8.7-45 +- Add 0208-libmpathpersist-fix-register-retry-status-checking.patch +- Add 0209-multipathd-remember-number-of-registered-keys-when-i.patch +- Add 0210-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch + * Fixes RHEL-148462 ("Improve multipathd's handling of updating + persistent reservations on restored paths. [rhel-9]") +- Resolves: RHEL-148462 + +* Thu Jan 29 2026 Benjamin Marzinski - 0.8.7-44 +- Add 0206-libmultipath-add-purge_disconnected-configuration-op.patch +- Add 0207-multipathd-implement-purge-functionality-for-disconn.patch + * Fixes RHEL-141291 ("Add purge_disconnected support to multipathd + [rhel-9]") +- Resolves: RHEL-141291 + +* Thu Jan 22 2026 Benjamin Marzinski - 0.8.7-43 +- Add 0198-multipathd-print-path-offline-message-even-without-a.patch + * Fixes RHEL-133814 ("log_checker_err is not printing messages + repeatedly for failed path [rhel-9]") +- Add 0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch +- Add 0200-uevent_dispatch-use-while-in-wait-loop.patch +- Add 0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch +- Add 0202-libmultipath-uevent_listen-don-t-delay-uevents.patch +- Add 0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch +- Add 0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch +- Add 0205-multipathd-make-multipathd-show-status-busy-checker-.patch + * Fixes RHEL-135904 (VM reboot in RHOSP environment fails with error + "Could not open '/dev/dm-95': No such file or directory") +- Resolves: RHEL-133814 +- Resolves: RHEL-135904 + +* Wed Nov 19 2025 Benjamin Marzinski - 0.8.7-42 +- Add 0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch + * Fixes RHEL-128396 ("Update the multipath.conf stanza for Infinidat + storage [rhel-9]") +- Resolves: RHEL-128396 + +* Tue Nov 11 2025 Benjamin Marzinski - 0.8.7-41 +- Add 0195-multipathd-Fix-race-while-registering-PR-key.patch +- Add 0196-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch + * Fixes RHEL-118515 ("There are many bugs in multipath's persistent + reservation handling") +- Resolves: RHEL-118515 + +* Wed Oct 1 2025 Benjamin Marzinski - 0.8.7-40 +- Add 0150-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch +- Add 0151-libmpathpersist-check-released-key-against-the-reser.patch +- Add 0152-multipathd-remove-thread-from-mpath_pr_event_handle.patch +- Add 0153-libmpathpersist-remove-uneeded-wrapper-function.patch +- Add 0154-libmpathpersist-reduce-log-level-for-persistent-rese.patch +- Add 0155-libmpathpersist-remove-pointless-update_map_pr-ret-v.patch +- Add 0156-multipathd-use-update_map_pr-in-mpath_pr_event_handl.patch +- Add 0157-libmpathpersist-limit-changing-prflag-in-update_map_.patch +- Add 0158-multipathd-Don-t-call-update_map_pr-unnecessarily.patch +- Add 0159-libmpathpersist-remove-useless-function-send_prout_a.patch +- Add 0160-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch +- Add 0161-limpathpersist-redesign-failed-release-workaround.patch +- Add 0162-libmpathpersist-fail-the-release-if-all-threads-fail.patch +- Add 0163-limpathpersist-Handle-changing-key-corner-case.patch +- Add 0164-libmpathpersist-fix-command-keyword-ordering.patch +- Add 0165-libmpathpersist-use-conf-timeout-for-updating-persis.patch +- Add 0166-libmapthpersist-Handle-REGISTER-AND-IGNORE-changing-.patch +- Add 0167-libmultipath-rename-prflag_value-enums.patch +- Add 0168-libmpathpersist-use-a-switch-statement-for-prout-com.patch +- Add 0169-libmpathpersist-Add-safety-check-for-preempting-on-k.patch +- Add 0170-limpathpersist-remove-update_map_pr-code-for-NULL-pp.patch +- Add 0171-libmpathpersist-move-update_map_pr-to-multipathd.patch +- Add 0172-multipathd-clean-up-update_map_pr-and-mpath_pr_event.patch +- Add 0173-libmpathpersist-clean-up-duplicate-function-declarat.patch +- Add 0174-multipathd-wrap-setting-and-unsetting-prflag.patch +- Add 0175-multipathd-unregister-PR-key-when-path-is-restored-i.patch +- Add 0176-libmpathpersist-Fix-up-reservation_key-checking.patch +- Add 0177-libmpathpersist-change-how-reservation-conflicts-are.patch +- Add 0178-libmpathpersist-Clear-prkey-in-multipathd-before-unr.patch +- Add 0179-libmpathpersist-only-clear-the-key-if-we-are-using-t.patch +- Add 0180-libmpathpersist-Restore-old-reservation-key-on-failu.patch +- Add 0181-libmpatpersist-update-reservation-key-before-checkin.patch +- Add 0182-libmpathpersist-retry-on-conflicts-in-mpath_prout_co.patch +- Add 0183-libmpathpersist-Don-t-always-fail-registrations-for-.patch +- Add 0184-libmpathpersist-Don-t-try-release-workaround-for-inv.patch +- Add 0185-libmpathpersist-Don-t-fail-RESERVE-commands-unnecess.patch +- Add 0186-libmpathpersist-reregister-keys-when-self-preempting.patch +- Add 0187-libmpathpersist-handle-updating-key-race-condition.patch +- Add 0188-libmpathpersist-handle-preempting-all-registrants-re.patch +- Add 0189-libmpathpersist-Fix-REGISTER-AND-IGNORE-while-holdin.patch +- Add 0190-libmpathpersist-Handle-RESERVE-with-reservation-held.patch +- Add 0191-libmpathpersist-use-check_holding_reservation-in-mpa.patch +- Add 0192-libmpathpersist-Fix-unregistering-while-holding-the-.patch +- Add 0193-libmpathpersist-Fix-race-between-restoring-a-path-an.patch +- Add 0194-multipathd-Fix-tracking-of-old-PR-key.patch + * Fixes RHEL-118515 ("There are many bugs in multipath's persistent + reservation handling") +- Resolves: RHEL-118515 + * Mon Jul 14 2025 Benjamin Marzinski - 0.8.7-39 - Add 0145-multipath-tools-add-DellEMC-ME4-PowerVault-ME4-to-ha.patch - Add 0146-multipath-tools-add-HPE-as-vendor-for-OPEN-XP8-array.patch