import CS device-mapper-multipath-0.8.7-45.el9

This commit is contained in:
AlmaLinux RelEng Bot 2026-03-30 11:19:37 -04:00
parent 4607fe0059
commit c4627c7fb1
62 changed files with 8813 additions and 2 deletions

View File

@ -0,0 +1,151 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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)
{

View File

@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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){

View File

@ -0,0 +1,112 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,60 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,93 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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:

View File

@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,227 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,76 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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 )
{

View File

@ -0,0 +1,76 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);
}
/*

View File

@ -0,0 +1,86 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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 *)(&param));
- 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);

View File

@ -0,0 +1,72 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,307 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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 {

View File

@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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){

View File

@ -0,0 +1,124 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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,
+ &paramp, 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;

View File

@ -0,0 +1,70 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <miaoguanqin@huawei.com>
Cc: lixiaokeng <lixiaokeng@huawei.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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);
}

View File

@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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 <mpath_persist.h>
#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;

View File

@ -0,0 +1,180 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <map>" command.
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,242 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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));

View File

@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);
}

View File

@ -0,0 +1,477 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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;

View File

@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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)

View File

@ -0,0 +1,208 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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;

View File

@ -0,0 +1,150 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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(&param, 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, &param, 0);
if (ret != MPATH_PR_SUCCESS )
{
condlog(0,"%s: Reservation registration failed. Error: %d", pp->dev, ret);
}
-
- free(param);
}

View File

@ -0,0 +1,78 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
Date: Tue, 15 Jul 2025 14:50:55 -0400
Subject: [PATCH] libmpathpersist: clean up duplicate function declarations
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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 <string.h>
#include <errno.h>
#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);

View File

@ -0,0 +1,148 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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 */

View File

@ -0,0 +1,141 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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(&param, 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, &param, 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);
}
}

View File

@ -0,0 +1,108 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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

View File

@ -0,0 +1,137 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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(&paramp->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(&paramp->key, &paramp->sa_key, 8);
- memset(&paramp->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;

View File

@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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:

View File

@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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:

View File

@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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)
{

View File

@ -0,0 +1,169 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,78 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,151 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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)

View File

@ -0,0 +1,46 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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

View File

@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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;
}

View File

@ -0,0 +1,203 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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,
+ &paramp, noisy, &pp);
+ if (status != MPATH_PR_SUCCESS) {
+ condlog(0, "%s: self preempt command failed.", mpp->wwid);
+ goto fail_resume;
+ }
+
+ if (do_release) {
+ memset(&paramp, 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, &paramp, noisy);
+ if (rel_status != MPATH_PR_SUCCESS)
+ condlog(0, "%s: release on alternate path failed.",
+ mpp->wwid);
+ }
+
+ memset(&paramp, 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, &paramp, 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)

View File

@ -0,0 +1,108 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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;
}

View File

@ -0,0 +1,94 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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,
&paramp, noisy, &pp);
if (status != MPATH_PR_SUCCESS) {

View File

@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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)
{

View File

@ -0,0 +1,168 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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,
- &paramp, noisy, NULL);
+ &paramp, 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,
- &paramp, noisy, &pp);
+ &paramp, noisy, &pp, NULL);
if (status != MPATH_PR_SUCCESS) {
condlog(0, "%s: self preempt command failed.", mpp->wwid);
goto fail_resume;

View File

@ -0,0 +1,68 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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;
}

View File

@ -0,0 +1,287 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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(&paramp, 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,
+ &paramp, noisy);
+ if (rel_status != MPATH_PR_SUCCESS)
+ condlog(0, "%s: final self preempt unregister failed,",
+ mpp->wwid);
+ }
+ }
+ if (work != PREE_WORK_REL_UNREG) {
+ memset(&paramp, 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, &paramp, noisy);
+ if (status != MPATH_PR_SUCCESS)
+ condlog(0, "%s: self preempt failed to reregister paths.",
+ mpp->wwid);
}
-
- memset(&paramp, 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, &paramp, 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);

View File

@ -0,0 +1,215 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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(&param, 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, &param, 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(&param, 0, sizeof(param));
+ clear_reg = true;
+ goto retry;
+ }
+ return nr_keys_needed;
}
+ return 0;
}

View File

@ -0,0 +1,49 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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)

View File

@ -0,0 +1,246 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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 */

View File

@ -0,0 +1,108 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
---
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));
}
}

View File

@ -0,0 +1,65 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Xose Vazquez Perez <xose.vazquez@gmail.com>
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 <mwilck@suse.com>
Cc: Benjamin Marzinski <bmarzins@redhat.com>
Cc: Christophe Varoqui <christophe.varoqui@opensvc.com>
Cc: DM_DEVEL-ML <dm-devel@lists.linux.dev>
Signed-off-by: Xose Vazquez Perez <xose.vazquez@gmail.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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

View File

@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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); \
\

View File

@ -0,0 +1,133 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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:

View File

@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,319 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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;
}

View File

@ -0,0 +1,204 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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);

View File

@ -0,0 +1,356 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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);
}

View File

@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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)

View File

@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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;

View File

@ -0,0 +1,279 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Brian Bunker <brian@purestorage.com>
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 <brian@purestorage.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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

View File

@ -0,0 +1,752 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Brian Bunker <brian@purestorage.com>
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 <brian@purestorage.com>
Signed-off-by: Krishna Kant <krishna.kant@purestorage.com>
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
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 <brian@purestorage.com>
+ * Copyright (C) 2025 Krishna Kant <krishna.kant@purestorage.com>
+ */
+
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <libudev.h>
+#include <urcu.h>
+
+#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 <brian@purestorage.com>
+ * Copyright (C) 2025 Krishna Kant <krishna.kant@purestorage.com>
+ */
+
+#ifndef PURGE_H_INCLUDED
+#define PURGE_H_INCLUDED
+
+#include <pthread.h>
+#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 */

View File

@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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;
}

View File

@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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(&param, 0, sizeof(param));
clear_reg = true;

View File

@ -0,0 +1,459 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
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 <map> pathlist <pathlist>", where
<pathlist> 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 <bmarzins@redhat.com>
Reviewed-by: Martin Wilck <mwilck@suse.com>
---
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 <ctype.h>
#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))) = &registered_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

View File

@ -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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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 <bmarzins@redhat.com> - 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