import UBI device-mapper-multipath-0.9.9-18.el10
This commit is contained in:
parent
0ce2edc8b7
commit
72db61e697
@ -0,0 +1,64 @@
|
||||
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, 5 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/libmultipath/hwtable.c b/libmultipath/hwtable.c
|
||||
index 17ba5b45..689cd8da 100644
|
||||
--- a/libmultipath/hwtable.c
|
||||
+++ b/libmultipath/hwtable.c
|
||||
@@ -1153,14 +1153,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 = MAX_DEV_LOSS_TMO,
|
||||
+ .detect_prio = DETECT_PRIO_OFF,
|
||||
},
|
||||
/*
|
||||
* Kaminario
|
||||
@ -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 32135d7e..7cebf4f3 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;
|
||||
@@ -82,13 +83,14 @@ static void reset_filter_state(struct uevent_filter_state *st)
|
||||
|
||||
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)
|
||||
@@ -742,6 +744,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)
|
||||
@@ -751,6 +754,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;
|
||||
@ -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 0882ef2a..e9165350 100644
|
||||
--- a/multipathd/main.c
|
||||
+++ b/multipathd/main.c
|
||||
@@ -95,12 +95,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); \
|
||||
\
|
||||
279
0083-libmultipath-add-purge_disconnected-configuration-op.patch
Normal file
279
0083-libmultipath-add-purge_disconnected-configuration-op.patch
Normal 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.in | 22 ++++++++++++++++++++++
|
||||
9 files changed, 72 insertions(+)
|
||||
|
||||
diff --git a/libmultipath/config.c b/libmultipath/config.c
|
||||
index 3d5943d3..5c87fbe0 100644
|
||||
--- a/libmultipath/config.c
|
||||
+++ b/libmultipath/config.c
|
||||
@@ -470,6 +470,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,
|
||||
@@ -517,6 +518,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 158cebf0..c9e4029e 100644
|
||||
--- a/libmultipath/config.h
|
||||
+++ b/libmultipath/config.h
|
||||
@@ -89,6 +89,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;
|
||||
@@ -131,6 +132,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;
|
||||
@@ -189,6 +191,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 b5c701fa..e92c905a 100644
|
||||
--- a/libmultipath/configure.c
|
||||
+++ b/libmultipath/configure.c
|
||||
@@ -355,6 +355,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 3602636c..ed31e475 100644
|
||||
--- a/libmultipath/defaults.h
|
||||
+++ b/libmultipath/defaults.h
|
||||
@@ -57,6 +57,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 546103f2..968163d7 100644
|
||||
--- a/libmultipath/dict.c
|
||||
+++ b/libmultipath/dict.c
|
||||
@@ -941,6 +941,16 @@ declare_hw_snprint(skip_kpartx, print_yes_no_undef)
|
||||
declare_mp_handler(skip_kpartx, set_yes_no_undef)
|
||||
declare_mp_snprint(skip_kpartx, print_yes_no_undef)
|
||||
|
||||
+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)
|
||||
|
||||
@@ -2227,6 +2237,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", &deprecated_disable_changed_wwids_handler, &snprint_deprecated);
|
||||
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);
|
||||
@@ -2310,6 +2321,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);
|
||||
@@ -2355,6 +2367,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);
|
||||
@@ -2400,6 +2413,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 5ad0b78c..9b7aba43 100644
|
||||
--- a/libmultipath/propsel.c
|
||||
+++ b/libmultipath/propsel.c
|
||||
@@ -1367,6 +1367,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 73615c2f..ee83253e 100644
|
||||
--- a/libmultipath/propsel.h
|
||||
+++ b/libmultipath/propsel.h
|
||||
@@ -37,6 +37,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 2ff195f3..e3f2ffb9 100644
|
||||
--- a/libmultipath/structs.h
|
||||
+++ b/libmultipath/structs.h
|
||||
@@ -186,6 +186,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 {
|
||||
@@ -453,6 +464,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.in b/multipath/multipath.conf.5.in
|
||||
index 31740a1f..af85092e 100644
|
||||
--- a/multipath/multipath.conf.5.in
|
||||
+++ b/multipath/multipath.conf.5.in
|
||||
@@ -1304,6 +1304,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
|
||||
(Deprecated) This option is not supported anymore, and will be ignored.
|
||||
.RE
|
||||
@@ -1601,6 +1617,8 @@ section:
|
||||
.TP
|
||||
.B skip_kpartx
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+.TP
|
||||
.B max_sectors_kb
|
||||
.TP
|
||||
.B ghost_delay
|
||||
@@ -1786,6 +1804,8 @@ section:
|
||||
.TP
|
||||
.B skip_kpartx
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+.TP
|
||||
.B max_sectors_kb
|
||||
.TP
|
||||
.B ghost_delay
|
||||
@@ -1870,6 +1890,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
|
||||
752
0084-multipathd-implement-purge-functionality-for-disconn.patch
Normal file
752
0084-multipathd-implement-purge-functionality-for-disconn.patch
Normal 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 fdb91e17..5eca2b44 100644
|
||||
--- a/libmultipath/checkers.c
|
||||
+++ b/libmultipath/checkers.c
|
||||
@@ -41,6 +41,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);
|
||||
@@ -344,6 +345,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 ea1e8af6..a0df88db 100644
|
||||
--- a/libmultipath/checkers.h
|
||||
+++ b/libmultipath/checkers.h
|
||||
@@ -65,6 +65,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,
|
||||
@@ -77,6 +86,7 @@ enum path_check_state {
|
||||
PATH_TIMEOUT,
|
||||
PATH_REMOVED,
|
||||
PATH_DELAYED,
|
||||
+ PATH_DISCONNECTED, /* Ephemeral: mapped to PATH_DOWN */
|
||||
PATH_MAX_STATE
|
||||
};
|
||||
|
||||
@@ -112,9 +122,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 2800446d..7fd9e959 100644
|
||||
--- a/libmultipath/checkers/tur.c
|
||||
+++ b/libmultipath/checkers/tur.c
|
||||
@@ -196,6 +196,16 @@ retry:
|
||||
*msgid = MSG_TUR_TRANSITIONING;
|
||||
return PATH_PENDING;
|
||||
}
|
||||
+ } 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 a1284e73..289c97c7 100644
|
||||
--- a/libmultipath/discovery.c
|
||||
+++ b/libmultipath/discovery.c
|
||||
@@ -2461,8 +2461,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 1c594451..ca23187d 100644
|
||||
--- a/libmultipath/io_err_stat.c
|
||||
+++ b/libmultipath/io_err_stat.c
|
||||
@@ -397,6 +397,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 fbed2dd5..1946728f 100644
|
||||
--- a/libmultipath/print.c
|
||||
+++ b/libmultipath/print.c
|
||||
@@ -547,6 +547,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 e3f2ffb9..dc184a93 100644
|
||||
--- a/libmultipath/structs.h
|
||||
+++ b/libmultipath/structs.h
|
||||
@@ -197,6 +197,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 {
|
||||
@@ -376,6 +388,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 61cf1af6..bedb09bc 100644
|
||||
--- a/multipathd/Makefile
|
||||
+++ b/multipathd/Makefile
|
||||
@@ -36,7 +36,7 @@ endif
|
||||
|
||||
CLI_OBJS := multipathc.o cli.o
|
||||
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
|
||||
endif
|
||||
diff --git a/multipathd/main.c b/multipathd/main.c
|
||||
index e9165350..7cb57e3c 100644
|
||||
--- a/multipathd/main.c
|
||||
+++ b/multipathd/main.c
|
||||
@@ -83,6 +83,7 @@
|
||||
#include "dmevents.h"
|
||||
#include "io_err_stat.h"
|
||||
#include "foreign.h"
|
||||
+#include "purge.h"
|
||||
#include "../third-party/valgrind/drd.h"
|
||||
#include "init_unwinder.h"
|
||||
|
||||
@@ -135,11 +136,11 @@ static bool __delayed_reconfig;
|
||||
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)
|
||||
@@ -2484,6 +2485,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));
|
||||
@@ -2800,6 +2823,7 @@ checkerloop (void *ap)
|
||||
int num_paths = 0, strict_timing, rc = 0, i = 0;
|
||||
unsigned int ticks = 0;
|
||||
enum checker_state checker_state = CHECKER_STARTING;
|
||||
+ LIST_HEAD(purge_list);
|
||||
|
||||
if (set_config_state(DAEMON_RUNNING) != DAEMON_RUNNING)
|
||||
/* daemon shutdown */
|
||||
@@ -2878,6 +2902,12 @@ unlock:
|
||||
}
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * 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();
|
||||
@@ -2886,6 +2916,11 @@ unlock:
|
||||
missing_uev_wait_tick(vecs);
|
||||
ghost_delay_tick(vecs);
|
||||
partial_retrigger_tick(vecs->pathvec);
|
||||
+ /*
|
||||
+ * 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)
|
||||
@@ -2900,6 +2935,26 @@ unlock:
|
||||
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);
|
||||
+
|
||||
get_monotonic_time(&end_time);
|
||||
timespecsub(&end_time, &start_time, &diff_time);
|
||||
if (num_paths) {
|
||||
@@ -3396,6 +3451,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)
|
||||
@@ -3412,6 +3469,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)
|
||||
@@ -3664,6 +3723,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 */
|
||||
@ -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_int.c | 14 +++++++-------
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/libmpathpersist/mpath_persist_int.c b/libmpathpersist/mpath_persist_int.c
|
||||
index 00334a1a..3f331294 100644
|
||||
--- a/libmpathpersist/mpath_persist_int.c
|
||||
+++ b/libmpathpersist/mpath_persist_int.c
|
||||
@@ -542,19 +542,19 @@ static 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);
|
||||
}
|
||||
- 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;
|
||||
}
|
||||
@ -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 7cb57e3c..46600527 100644
|
||||
--- a/multipathd/main.c
|
||||
+++ b/multipathd/main.c
|
||||
@@ -4059,7 +4059,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)
|
||||
{
|
||||
@@ -4088,11 +4090,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;
|
||||
}
|
||||
|
||||
@@ -4208,7 +4211,7 @@ retry:
|
||||
clear_reg ? "Clearing" : "Setting", pp->dev, ret);
|
||||
} else if (!clear_reg) {
|
||||
if (update_map_pr(mpp, pp, &nr_keys_needed) != MPATH_PR_SUCCESS)
|
||||
- return 0;
|
||||
+ return nr_keys_needed;
|
||||
if (mpp->prflag != PR_SET) {
|
||||
memset(¶m, 0, sizeof(param));
|
||||
clear_reg = true;
|
||||
461
0087-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch
Normal file
461
0087-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch
Normal file
@ -0,0 +1,461 @@
|
||||
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>
|
||||
---
|
||||
.github/actions/spelling/expect.txt | 1 +
|
||||
libmpathpersist/mpath_persist_int.c | 6 +--
|
||||
libmpathpersist/mpath_updatepr.c | 49 +++++++++++++++++------
|
||||
libmpathpersist/mpathpr.h | 4 +-
|
||||
multipathd/callbacks.c | 2 +
|
||||
multipathd/cli.c | 1 +
|
||||
multipathd/cli.h | 2 +
|
||||
multipathd/cli_handlers.c | 35 ++++++++++++++--
|
||||
multipathd/main.c | 62 +++++++++++++++++++----------
|
||||
multipathd/main.h | 4 +-
|
||||
multipathd/multipathd.8.in | 10 ++++-
|
||||
11 files changed, 132 insertions(+), 44 deletions(-)
|
||||
|
||||
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
|
||||
index e2d4acf9..9bb8f8dd 100644
|
||||
--- a/.github/actions/spelling/expect.txt
|
||||
+++ b/.github/actions/spelling/expect.txt
|
||||
@@ -143,6 +143,7 @@ OPTFLAGS
|
||||
paramp
|
||||
partx
|
||||
pathgroup
|
||||
+pathlist
|
||||
petabytes
|
||||
pgpolicy
|
||||
plugindir
|
||||
diff --git a/libmpathpersist/mpath_persist_int.c b/libmpathpersist/mpath_persist_int.c
|
||||
index 3f331294..b2c3e27a 100644
|
||||
--- a/libmpathpersist/mpath_persist_int.c
|
||||
+++ b/libmpathpersist/mpath_persist_int.c
|
||||
@@ -1018,12 +1018,12 @@ 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 dd8dd48e..f943b1bf 100644
|
||||
--- a/libmpathpersist/mpath_updatepr.c
|
||||
+++ b/libmpathpersist/mpath_updatepr.c
|
||||
@@ -20,8 +20,9 @@
|
||||
#include "uxsock.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 +52,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);
|
||||
@@ -106,9 +109,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 b668ef58..baee9a59 100644
|
||||
--- a/libmpathpersist/mpathpr.h
|
||||
+++ b/libmpathpersist/mpathpr.h
|
||||
@@ -1,12 +1,14 @@
|
||||
#ifndef MPATHPR_H
|
||||
#define MPATHPR_H
|
||||
|
||||
+#include "structs.h"
|
||||
+
|
||||
/*
|
||||
* This header file contains symbols that are only used by
|
||||
* libmpathpersist internally.
|
||||
*/
|
||||
|
||||
-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/callbacks.c b/multipathd/callbacks.c
|
||||
index b6b57f45..2027c3a6 100644
|
||||
--- a/multipathd/callbacks.c
|
||||
+++ b/multipathd/callbacks.c
|
||||
@@ -59,6 +59,8 @@ void init_handler_callbacks(void)
|
||||
set_unlocked_handler_callback(VRB_SHUTDOWN, HANDLER(cli_shutdown));
|
||||
set_handler_callback(VRB_GETPRSTATUS | Q1_MAP, HANDLER(cli_getprstatus));
|
||||
set_handler_callback(VRB_SETPRSTATUS | Q1_MAP, HANDLER(cli_setprstatus));
|
||||
+ set_handler_callback(VRB_SETPRSTATUS | Q1_MAP | Q2_PATHLIST,
|
||||
+ HANDLER(cli_setprstatus_list));
|
||||
set_handler_callback(VRB_UNSETPRSTATUS | Q1_MAP, HANDLER(cli_unsetprstatus));
|
||||
set_handler_callback(VRB_FORCEQ | Q1_DAEMON, HANDLER(cli_force_no_daemon_q));
|
||||
set_handler_callback(VRB_RESTOREQ | Q1_DAEMON, HANDLER(cli_restore_no_daemon_q));
|
||||
diff --git a/multipathd/cli.c b/multipathd/cli.c
|
||||
index bccdda48..4ff229a5 100644
|
||||
--- a/multipathd/cli.c
|
||||
+++ b/multipathd/cli.c
|
||||
@@ -227,6 +227,7 @@ load_keys (void)
|
||||
r += add_key(keys, "getprhold", VRB_GETPRHOLD, 0);
|
||||
r += add_key(keys, "setprhold", VRB_SETPRHOLD, 0);
|
||||
r += add_key(keys, "unsetprhold", VRB_UNSETPRHOLD, 0);
|
||||
+ r += add_key(keys, "pathlist", KEY_PATHLIST, 1);
|
||||
|
||||
if (r) {
|
||||
free_keys(keys);
|
||||
diff --git a/multipathd/cli.h b/multipathd/cli.h
|
||||
index 925575ad..70a2d3f0 100644
|
||||
--- a/multipathd/cli.h
|
||||
+++ b/multipathd/cli.h
|
||||
@@ -62,6 +62,7 @@ enum {
|
||||
KEY_LOCAL = 81,
|
||||
KEY_GROUP = 82,
|
||||
KEY_KEY = 83,
|
||||
+ KEY_PATHLIST = 84,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -94,6 +95,7 @@ enum {
|
||||
Q2_LOCAL = KEY_LOCAL << 16,
|
||||
Q2_GROUP = KEY_GROUP << 16,
|
||||
Q2_KEY = KEY_KEY << 16,
|
||||
+ Q2_PATHLIST = KEY_PATHLIST << 16,
|
||||
|
||||
/* byte 3: qualifier 3 */
|
||||
Q3_FMT = KEY_FMT << 24,
|
||||
diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c
|
||||
index 1677dd1e..854f6192 100644
|
||||
--- a/multipathd/cli_handlers.c
|
||||
+++ b/multipathd/cli_handlers.c
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "strbuf.h"
|
||||
#include "cli_handlers.h"
|
||||
#include "devmapper.h"
|
||||
+#include <ctype.h>
|
||||
|
||||
static struct path *
|
||||
find_path_by_str(const struct _vector *pathvec, const char *str,
|
||||
@@ -1266,8 +1267,8 @@ cli_getprstatus (void * v, struct strbuf *reply, void * data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int
|
||||
-cli_setprstatus(void * v, struct strbuf *reply, void * data)
|
||||
+static int do_setprstatus(void *v, struct strbuf *reply, void *data,
|
||||
+ const struct _vector *registered_paths)
|
||||
{
|
||||
struct multipath * mpp;
|
||||
struct vectors * vecs = (struct vectors *)data;
|
||||
@@ -1281,7 +1282,7 @@ cli_setprstatus(void * v, struct strbuf *reply, 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
|
||||
@@ -1292,6 +1293,34 @@ cli_setprstatus(void * v, struct strbuf *reply, void * data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int cli_setprstatus(void *v, struct strbuf *reply, void *data)
|
||||
+{
|
||||
+ return do_setprstatus(v, reply, data, NULL);
|
||||
+}
|
||||
+
|
||||
+static int cli_setprstatus_list(void *v, struct strbuf *reply, void *data)
|
||||
+{
|
||||
+ int r;
|
||||
+ struct _vector registered_paths_vec = {.allocated = 0};
|
||||
+ vector registered_paths
|
||||
+ __attribute__((cleanup(cleanup_reset_vec))) = ®istered_paths_vec;
|
||||
+ char *ptr = get_keyparam(v, KEY_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, data, registered_paths);
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
static int
|
||||
cli_unsetprstatus(void * v, struct strbuf *reply, void * data)
|
||||
{
|
||||
diff --git a/multipathd/main.c b/multipathd/main.c
|
||||
index 46600527..bdb04a1f 100644
|
||||
--- a/multipathd/main.c
|
||||
+++ b/multipathd/main.c
|
||||
@@ -637,28 +637,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 */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,8 +707,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);
|
||||
}
|
||||
@@ -745,7 +763,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);
|
||||
@@ -1393,7 +1411,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;
|
||||
@@ -2688,7 +2706,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3091,7 +3109,7 @@ configure (struct vectors * vecs, enum force_reload_types reload_type)
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4151,8 +4169,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.
|
||||
@@ -4175,7 +4193,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 25ddbaa7..0742ca2e 100644
|
||||
--- a/multipathd/main.h
|
||||
+++ b/multipathd/main.h
|
||||
@@ -54,5 +54,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.in b/multipathd/multipathd.8.in
|
||||
index 342e363e..2420d698 100644
|
||||
--- a/multipathd/multipathd.8.in
|
||||
+++ b/multipathd/multipathd.8.in
|
||||
@@ -346,11 +346,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
|
||||
@ -1,6 +1,6 @@
|
||||
Name: device-mapper-multipath
|
||||
Version: 0.9.9
|
||||
Release: 12%{?dist}.1
|
||||
Release: 18%{?dist}
|
||||
Summary: Tools to manage multipath devices using device-mapper
|
||||
License: GPLv2
|
||||
URL: http://christophe.varoqui.free.fr/
|
||||
@ -89,6 +89,14 @@ Patch0076: 0076-libmpathpersist-Fix-race-between-restoring-a-path-an.patch
|
||||
Patch0077: 0077-multipathd-Fix-tracking-of-old-PR-key.patch
|
||||
Patch0078: 0078-multipathd-Fix-race-while-registering-PR-key.patch
|
||||
Patch0079: 0079-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch
|
||||
Patch0080: 0080-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch
|
||||
Patch0081: 0081-multipathd-make-multipathd-show-status-busy-checker-.patch
|
||||
Patch0082: 0082-multipathd-print-path-offline-message-even-without-a.patch
|
||||
Patch0083: 0083-libmultipath-add-purge_disconnected-configuration-op.patch
|
||||
Patch0084: 0084-multipathd-implement-purge-functionality-for-disconn.patch
|
||||
Patch0085: 0085-libmpathpersist-fix-register-retry-status-checking.patch
|
||||
Patch0086: 0086-multipathd-remember-number-of-registered-keys-when-i.patch
|
||||
Patch0087: 0087-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch
|
||||
|
||||
# runtime
|
||||
Requires: %{name}-libs = %{version}-%{release}
|
||||
@ -298,7 +306,45 @@ fi
|
||||
%{_pkgconfdir}/libdmmp.pc
|
||||
|
||||
%changelog
|
||||
* Tue Nov 11 2025 Lin Li <lilin@redhat.com> - 0.9.9-12.1
|
||||
* Thu Feb 19 2026 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-18
|
||||
- Add 0085-libmpathpersist-fix-register-retry-status-checking.patch
|
||||
- Add 0086-multipathd-remember-number-of-registered-keys-when-i.patch
|
||||
- Add 0087-libmpathpersist-fix-code-for-skipping-multipathd-pat.patch
|
||||
* Fixes RHEL-129442 ("Improve multipathd's handling of updating
|
||||
persistent reservations on restored paths.")
|
||||
- Resolves: RHEL-129442
|
||||
|
||||
* Thu Jan 29 2026 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-17
|
||||
- Add 0083-libmultipath-add-purge_disconnected-configuration-op.patch
|
||||
- Add 0084-multipathd-implement-purge-functionality-for-disconn.patch
|
||||
* Fixes RHEL-141287 ("Add purge_disconnected support to multipathd")
|
||||
- Resolves: RHEL-141287
|
||||
|
||||
* Thu Jan 22 2026 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-16
|
||||
- Add 0081-multipathd-make-multipathd-show-status-busy-checker-.patch
|
||||
* Fixes RHEL-136405 ("improve Busy checking for multipathd show status
|
||||
output")
|
||||
- Add 0082-multipathd-print-path-offline-message-even-without-a.patch
|
||||
* Fixes RHEL-133815 ("log_checker_err is not printing messages
|
||||
repeatedly for failed path [rhel-10]")
|
||||
- Resolves: RHEL-133815
|
||||
- Resolves: RHEL-136405
|
||||
|
||||
|
||||
* Wed Nov 19 2025 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-15
|
||||
- Add 0080-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch
|
||||
* Fixes RHEL-128338 ("Update the multipath.conf stanza for Infinidat
|
||||
storage")
|
||||
- Resolves: RHEL-128338
|
||||
|
||||
* Tue Nov 11 2025 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-14
|
||||
- Add 0078-multipathd-Fix-race-while-registering-PR-key.patch
|
||||
- Add 0079-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch
|
||||
* Fixes RHEL-118720 ("There are many bugs in multipath's persistent
|
||||
reservation handling [rhel-10]")
|
||||
- Resolves: RHEL-118720
|
||||
|
||||
* Wed Oct 1 2025 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-13
|
||||
- Add 0035-libmpathpersist-fix-memory-leak-in-mpath_prout_rel.patch
|
||||
- Add 0036-libmpathpersist-retry-commands-on-other-paths-in-mpa.patch
|
||||
- Add 0037-libmpathpersist-check-released-key-against-the-reser.patch
|
||||
@ -342,11 +388,9 @@ fi
|
||||
- Add 0075-libmpathpersist-Fix-unregistering-while-holding-the-.patch
|
||||
- Add 0076-libmpathpersist-Fix-race-between-restoring-a-path-an.patch
|
||||
- Add 0077-multipathd-Fix-tracking-of-old-PR-key.patch
|
||||
- Add 0078-multipathd-Fix-race-while-registering-PR-key.patch
|
||||
- Add 0079-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch
|
||||
* Fixes RHEL-125288 ("There are many bugs in multipath's persistent
|
||||
reservation handling [rhel-10.1.z]")
|
||||
- Resolves: RHEL-125288
|
||||
* Fixes RHEL-118720 ("There are many bugs in multipath's persistent
|
||||
reservation handling [rhel-10]")
|
||||
- Resolves: RHEL-118720
|
||||
|
||||
* Sat Sep 13 2025 Lin Li <lilin@redhat.com> - 0.9.9-12
|
||||
Add 0034-libmultipath-fix-crash-in-print_foreign_topology.patch
|
||||
|
||||
Loading…
Reference in New Issue
Block a user