device-mapper-multipath-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
This commit is contained in:
Benjamin Marzinski 2026-01-23 00:57:37 -05:00
parent c6e35bf163
commit 51ade5ca63
9 changed files with 1211 additions and 1 deletions

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

@ -1,6 +1,6 @@
Name: device-mapper-multipath
Version: 0.8.7
Release: 42%{?dist}
Release: 43%{?dist}
Summary: Tools to manage multipath devices using device-mapper
License: GPLv2
URL: http://christophe.varoqui.free.fr/
@ -207,6 +207,14 @@ 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
# runtime
Requires: %{name}-libs = %{version}-%{release}
@ -409,6 +417,22 @@ fi
%{_pkgconfdir}/libdmmp.pc
%changelog
* 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