From 51ade5ca634d59c85de3571a63f7ea0954da64a8 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Fri, 23 Jan 2026 00:57:37 -0500 Subject: [PATCH] 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 --- ...-path-offline-message-even-without-a.patch | 36 ++ ...rove-cleanup-of-uevent-queues-on-exi.patch | 133 +++++++ ...vent_dispatch-use-while-in-wait-loop.patch | 38 ++ ...ent_dispatch-process-uevents-one-by-.patch | 319 ++++++++++++++++ ...th-uevent_listen-don-t-delay-uevents.patch | 204 ++++++++++ ...ent-use-struct-to-pass-parameters-ar.patch | 356 ++++++++++++++++++ ...uevent_busy-check-servicing_uev-unde.patch | 37 ++ ...multipathd-show-status-busy-checker-.patch | 63 ++++ device-mapper-multipath.spec | 26 +- 9 files changed, 1211 insertions(+), 1 deletion(-) create mode 100644 0198-multipathd-print-path-offline-message-even-without-a.patch create mode 100644 0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch create mode 100644 0200-uevent_dispatch-use-while-in-wait-loop.patch create mode 100644 0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch create mode 100644 0202-libmultipath-uevent_listen-don-t-delay-uevents.patch create mode 100644 0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch create mode 100644 0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch create mode 100644 0205-multipathd-make-multipathd-show-status-busy-checker-.patch diff --git a/0198-multipathd-print-path-offline-message-even-without-a.patch b/0198-multipathd-print-path-offline-message-even-without-a.patch new file mode 100644 index 0000000..637a4c2 --- /dev/null +++ b/0198-multipathd-print-path-offline-message-even-without-a.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 21 Jan 2026 16:03:13 -0500 +Subject: [PATCH] multipathd: print path offline message even without a checker + +If a path has a checker selected and is offline, multipathd will print a +"path offline" message. However if the checker isn't selected, for +instance because multipathd was started or reconfigured while the path +was offline, multipathd was not printing the "path offline" message. +Fix that. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + multipathd/main.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/multipathd/main.c b/multipathd/main.c +index a85c0db4..9beb0e06 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -97,12 +97,11 @@ mpath_pr_event_handle(struct path *pp, unsigned int nr_keys_needed, + + #define LOG_MSG(lvl, pp) \ + do { \ +- if (pp->mpp && checker_selected(&pp->checker) && \ +- lvl <= libmp_verbosity) { \ ++ if (pp->mpp && lvl <= libmp_verbosity) { \ + if (pp->offline) \ + condlog(lvl, "%s: %s - path offline", \ + pp->mpp->alias, pp->dev); \ +- else { \ ++ else if (checker_selected(&pp->checker)) { \ + const char *__m = \ + checker_message(&pp->checker); \ + \ diff --git a/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch b/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch new file mode 100644 index 0000000..ae579aa --- /dev/null +++ b/0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Thu, 9 Sep 2021 23:59:42 +0200 +Subject: [PATCH] libmultipath: improve cleanup of uevent queues on exit + +uevents listed on merge_node must be cleaned up, too. uevents +cancelled while being serviced and temporary queues, likewise. +The global uevq must be cleaned out in the uevent listener thread, +because it might have added events after the dispatcher thread +had already finished. + +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 49 ++++++++++++++++++++++++++++++++----------- + 1 file changed, 37 insertions(+), 12 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 4265904b..80941f87 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -91,16 +91,25 @@ struct uevent * alloc_uevent (void) + return uev; + } + ++static void uevq_cleanup(struct list_head *tmpq); ++ ++static void cleanup_uev(void *arg) ++{ ++ struct uevent *uev = arg; ++ ++ uevq_cleanup(&uev->merge_node); ++ if (uev->udev) ++ udev_device_unref(uev->udev); ++ free(uev); ++} ++ + static void uevq_cleanup(struct list_head *tmpq) + { + struct uevent *uev, *tmp; + + list_for_each_entry_safe(uev, tmp, tmpq, node) { + list_del_init(&uev->node); +- +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ cleanup_uev(uev); + } + } + +@@ -384,14 +393,10 @@ service_uevq(struct list_head *tmpq) + list_for_each_entry_safe(uev, tmp, tmpq, node) { + list_del_init(&uev->node); + ++ pthread_cleanup_push(cleanup_uev, uev); + if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) + condlog(0, "uevent trigger error"); +- +- uevq_cleanup(&uev->merge_node); +- +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ pthread_cleanup_pop(1); + } + } + +@@ -411,6 +416,18 @@ static void monitor_cleanup(void *arg) + udev_monitor_unref(monitor); + } + ++static void cleanup_uevq(void *arg) ++{ ++ uevq_cleanup(arg); ++} ++ ++static void cleanup_global_uevq(void *arg __attribute__((unused))) ++{ ++ pthread_mutex_lock(uevq_lockp); ++ uevq_cleanup(&uevq); ++ pthread_mutex_unlock(uevq_lockp); ++} ++ + /* + * Service the uevent queue. + */ +@@ -425,6 +442,7 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + while (1) { + LIST_HEAD(uevq_tmp); + ++ pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + servicing_uev = 0; + /* +@@ -436,14 +454,17 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + } + servicing_uev = 1; + list_splice_init(&uevq, &uevq_tmp); +- pthread_mutex_unlock(uevq_lockp); ++ pthread_cleanup_pop(1); ++ + if (!my_uev_trigger) + break; ++ ++ pthread_cleanup_push(cleanup_uevq, &uevq_tmp); + merge_uevq(&uevq_tmp); + service_uevq(&uevq_tmp); ++ pthread_cleanup_pop(1); + } + condlog(3, "Terminating uev service queue"); +- uevq_cleanup(&uevq); + return 0; + } + +@@ -600,6 +621,8 @@ int uevent_listen(struct udev *udev) + + events = 0; + gettimeofday(&start_time, NULL); ++ pthread_cleanup_push(cleanup_global_uevq, NULL); ++ pthread_cleanup_push(cleanup_uevq, &uevlisten_tmp); + while (1) { + struct uevent *uev; + struct udev_device *dev; +@@ -650,6 +673,8 @@ int uevent_listen(struct udev *udev) + gettimeofday(&start_time, NULL); + timeout = 30; + } ++ pthread_cleanup_pop(1); ++ pthread_cleanup_pop(1); + out: + pthread_cleanup_pop(1); + out_udev: diff --git a/0200-uevent_dispatch-use-while-in-wait-loop.patch b/0200-uevent_dispatch-use-while-in-wait-loop.patch new file mode 100644 index 0000000..b8aed11 --- /dev/null +++ b/0200-uevent_dispatch-use-while-in-wait-loop.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 16:06:25 +0200 +Subject: [PATCH] uevent_dispatch(): use while in wait loop + +Callers of pthread_cond_wait() should generally use a while loop +to test the condition. Also, remove the misleading comment. +Condition variables aren't unreliable, they're just not strictly +tied to the condition tested. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 80941f87..e3ec1ac1 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -445,13 +445,10 @@ int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + servicing_uev = 0; +- /* +- * Condition signals are unreliable, +- * so make sure we only wait if we have to. +- */ +- if (list_empty(&uevq)) { ++ ++ while (list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); +- } ++ + servicing_uev = 1; + list_splice_init(&uevq, &uevq_tmp); + pthread_cleanup_pop(1); diff --git a/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch b/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch new file mode 100644 index 0000000..4b14f78 --- /dev/null +++ b/0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch @@ -0,0 +1,319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 18:04:42 +0200 +Subject: [PATCH] libmultipath: uevent_dispatch(): process uevents one by one + +The main rationale for delaying uevents is that the +uevent dispatcher may have to wait for other threads to release the +vecs lock, may the vecs lock for an extended amount of time, and +even sleep occasionally. By delaying them, we have the chance +to accumulate events for the same path device ("filtering") or +WWID ("merging"), thus avoiding duplicate work if we merge these +into one. + +A similar effect can be obtained in the uevent dispatcher itself +by looking for new uevents after each dispatched event, and trying +to merge the newly arrived events with those that remained +in the queue. + +When uevq_work is non-empty and we append a list of new events, +we don't need to check the entire list for filterable and mergeable +uevents. uevq_work had been filtered and merged already. So we just +need to check the newly appended events. These must of course be +checked for merges with earlier events, too. + +We must deal with some special cases here, like previously merged +uevents being filtered later. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/list.h | 53 +++++++++++++++++ + libmultipath/uevent.c | 129 ++++++++++++++++++++++++++++++------------ + 2 files changed, 147 insertions(+), 35 deletions(-) + +diff --git a/libmultipath/list.h b/libmultipath/list.h +index ced021f5..248f72bc 100644 +--- a/libmultipath/list.h ++++ b/libmultipath/list.h +@@ -246,6 +246,35 @@ static inline void list_splice_tail_init(struct list_head *list, + #define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + ++ ++/** ++ * list_pop - unlink and return the first list element ++ * @head: the &struct list_head pointer. ++ */ ++static inline struct list_head *list_pop(struct list_head *head) ++{ ++ struct list_head *tmp; ++ ++ if (list_empty(head)) ++ return NULL; ++ tmp = head->next; ++ list_del_init(tmp); ++ return tmp; ++} ++ ++/** ++ * list_pop_entry - unlink and return the entry of the first list element ++ * @head: the &struct list_head pointer. ++ * @type: the type of the struct this is embedded in. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_pop_entry(head, type, member) \ ++({ \ ++ struct list_head *__h = list_pop(head); \ ++ \ ++ (__h ? container_of(__h, type, member) : NULL); \ ++}) ++ + /** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. +@@ -334,6 +363,30 @@ static inline void list_splice_tail_init(struct list_head *list, + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + ++/** ++ * list_for_some_entry - iterate list from the given begin node to the given end node ++ * @pos: the type * to use as a loop counter. ++ * @from: the begin node of the iteration. ++ * @to: the end node of the iteration. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_for_some_entry(pos, from, to, member) \ ++ for (pos = list_entry((from)->next, typeof(*pos), member); \ ++ &pos->member != (to); \ ++ pos = list_entry(pos->member.next, typeof(*pos), member)) ++ ++/** ++ * list_for_some_entry_reverse - iterate backwards list from the given begin node to the given end node ++ * @pos: the type * to use as a loop counter. ++ * @from: the begin node of the iteration. ++ * @to: the end node of the iteration. ++ * @member: the name of the list_struct within the struct. ++ */ ++#define list_for_some_entry_reverse(pos, from, to, member) \ ++ for (pos = list_entry((from)->prev, typeof(*pos), member); \ ++ &pos->member != (to); \ ++ pos = list_entry(pos->member.prev, typeof(*pos), member)) ++ + /** + * list_for_some_entry_safe - iterate list from the given begin node to the given end node safe against removal of list entry + * @pos: the type * to use as a loop counter. +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index e3ec1ac1..2198e254 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -308,17 +308,64 @@ uevent_can_merge(struct uevent *earlier, struct uevent *later) + return false; + } + ++static void uevent_delete_from_list(struct uevent *to_delete, ++ struct uevent **previous, ++ struct list_head **old_tail) ++{ ++ /* ++ * "old_tail" is the list_head before the last list element to which ++ * the caller iterates (the list anchor if the caller iterates over ++ * the entire list). If this element is removed (which can't happen ++ * for the anchor), "old_tail" must be moved. It can happen that ++ * "old_tail" ends up pointing at the anchor. ++ */ ++ if (*old_tail == &to_delete->node) ++ *old_tail = to_delete->node.prev; ++ ++ list_del_init(&to_delete->node); ++ ++ /* ++ * The "to_delete" uevent has been merged with other uevents ++ * previously. Re-insert them into the list, at the point we're ++ * currently at. This must be done after the list_del_init() above, ++ * otherwise previous->next would still point to to_delete. ++ */ ++ if (!list_empty(&to_delete->merge_node)) { ++ struct uevent *last = list_entry(to_delete->merge_node.prev, ++ typeof(*last), node); ++ ++ list_splice(&to_delete->merge_node, &(*previous)->node); ++ *previous = last; ++ } ++ if (to_delete->udev) ++ udev_device_unref(to_delete->udev); ++ ++ free(to_delete); ++} ++ ++/* ++ * Use this function to delete events that are known not to ++ * be equal to old_tail, and have an empty merge_node list. ++ * For others, use uevent_delete_from_list(). ++ */ ++static void uevent_delete_simple(struct uevent *to_delete) ++{ ++ list_del_init(&to_delete->node); ++ ++ if (to_delete->udev) ++ udev_device_unref(to_delete->udev); ++ ++ free(to_delete); ++} ++ + static void +-uevent_prepare(struct list_head *tmpq) ++uevent_prepare(struct list_head *tmpq, const struct list_head *stop) + { + struct uevent *uev, *tmp; + +- list_for_each_entry_reverse_safe(uev, tmp, tmpq, node) { ++ list_for_some_entry_reverse_safe(uev, tmp, tmpq, stop, node) { + if (uevent_can_discard(uev)) { +- list_del_init(&uev->node); +- if (uev->udev) +- udev_device_unref(uev->udev); +- FREE(uev); ++ uevent_delete_simple(uev); + continue; + } + +@@ -329,7 +376,7 @@ uevent_prepare(struct list_head *tmpq) + } + + static void +-uevent_filter(struct uevent *later, struct list_head *tmpq) ++uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **stop) + { + struct uevent *earlier, *tmp; + +@@ -343,16 +390,13 @@ uevent_filter(struct uevent *later, struct list_head *tmpq) + earlier->kernel, earlier->action, + later->kernel, later->action); + +- list_del_init(&earlier->node); +- if (earlier->udev) +- udev_device_unref(earlier->udev); +- FREE(earlier); ++ uevent_delete_from_list(earlier, &tmp, stop); + } + } + } + + static void +-uevent_merge(struct uevent *later, struct list_head *tmpq) ++uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **stop) + { + struct uevent *earlier, *tmp; + +@@ -367,37 +411,42 @@ uevent_merge(struct uevent *later, struct list_head *tmpq) + earlier->action, earlier->kernel, earlier->wwid, + later->action, later->kernel, later->wwid); + ++ /* See comment in uevent_delete_from_list() */ ++ if (&earlier->node == *stop) ++ *stop = earlier->node.prev; ++ + list_move(&earlier->node, &later->merge_node); ++ list_splice_init(&earlier->merge_node, ++ &later->merge_node); + } + } + } + + static void +-merge_uevq(struct list_head *tmpq) ++merge_uevq(struct list_head *tmpq, struct list_head *stop) + { + struct uevent *later; + +- uevent_prepare(tmpq); +- list_for_each_entry_reverse(later, tmpq, node) { +- uevent_filter(later, tmpq); ++ uevent_prepare(tmpq, stop); ++ list_for_some_entry_reverse(later, tmpq, stop, node) { ++ uevent_filter(later, tmpq, &stop); + if(uevent_need_merge()) +- uevent_merge(later, tmpq); ++ uevent_merge(later, tmpq, &stop); + } + } + + static void + service_uevq(struct list_head *tmpq) + { +- struct uevent *uev, *tmp; +- +- list_for_each_entry_safe(uev, tmp, tmpq, node) { +- list_del_init(&uev->node); +- +- pthread_cleanup_push(cleanup_uev, uev); +- if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) +- condlog(0, "uevent trigger error"); +- pthread_cleanup_pop(1); +- } ++ struct uevent *uev = list_pop_entry(tmpq, typeof(*uev), node); ++ ++ if (uev == NULL) ++ return; ++ condlog(4, "servicing uevent '%s %s'", uev->action, uev->kernel); ++ pthread_cleanup_push(cleanup_uev, uev); ++ if (my_uev_trigger && my_uev_trigger(uev, my_trigger_data)) ++ condlog(0, "uevent trigger error"); ++ pthread_cleanup_pop(1); + } + + static void uevent_cleanup(void *arg) +@@ -434,33 +483,43 @@ static void cleanup_global_uevq(void *arg __attribute__((unused))) + int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + void * trigger_data) + { ++ LIST_HEAD(uevq_work); ++ + my_uev_trigger = uev_trigger; + my_trigger_data = trigger_data; + + mlockall(MCL_CURRENT | MCL_FUTURE); + ++ pthread_cleanup_push(cleanup_uevq, &uevq_work); + while (1) { +- LIST_HEAD(uevq_tmp); ++ struct list_head *stop; + + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); +- servicing_uev = 0; + +- while (list_empty(&uevq)) ++ servicing_uev = !list_empty(&uevq_work); ++ ++ while (list_empty(&uevq_work) && list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); + + servicing_uev = 1; +- list_splice_init(&uevq, &uevq_tmp); ++ /* ++ * "stop" is the list element towards which merge_uevq() ++ * will iterate: the last element of uevq_work before ++ * appending new uevents. If uveq_is empty, uevq_work.prev ++ * equals &uevq_work, which is what we need. ++ */ ++ stop = uevq_work.prev; ++ list_splice_tail_init(&uevq, &uevq_work); + pthread_cleanup_pop(1); + + if (!my_uev_trigger) + break; + +- pthread_cleanup_push(cleanup_uevq, &uevq_tmp); +- merge_uevq(&uevq_tmp); +- service_uevq(&uevq_tmp); +- pthread_cleanup_pop(1); ++ merge_uevq(&uevq_work, stop); ++ service_uevq(&uevq_work); + } ++ pthread_cleanup_pop(1); + condlog(3, "Terminating uev service queue"); + return 0; + } diff --git a/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch b/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch new file mode 100644 index 0000000..8cee865 --- /dev/null +++ b/0202-libmultipath-uevent_listen-don-t-delay-uevents.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Tue, 29 Mar 2022 23:25:48 +0200 +Subject: [PATCH] libmultipath: uevent_listen(): don't delay uevents + +When multipathd starts up early, basically all devices are added +through uevent processing. This takes much more time than necessary +because of the artificial delays introduced for passing uevents +between the listener and the receiver thread in ee8888f +("multipath-tools: improve processing efficiency for addition and deletion of +multipath devices"). This delay could be up to 30s. + +It's generally not a good idea to delay uevent processing in multipathd. +ADD events must normally be handled ASAP in order to avoid maps entering +queueing mode or eventually failing. Handling REMOVE events quickly is +also important to make multipathd aware of deleted devices and keep +kernel and multipathd state in sync. + +If uevents arrive quickly, the assumption is that the dispatcher will process +them more slowly than the listener. This was the idea of commit ee8888f, +AFAIU: if a queue of unprocessed events piles up because the dispatcher is +too slow, use filtering and merging to reduce the length of the queue, and +thus the work to be done for the uevent dispatcher, especially the work +that needs to be done while holding the vecs lock. In ee8888f, the +queue was created by allowing uevents to accumulate in the listener. + +This patch changes the logic of ee8888f, while keeping the uevent +filtering and discarding features. The idea is that the uevent dispatcher +shouldn't be idle if there are uevents to process. Therefore uevents +are passed to it immediately. But it now checks for new uevents after +processing every individual event, before processing the entire queue, +and it applies filtering and merging to the queue as it grows. + +This patch set avoids any delay when the uevent dispatcher is idle or +able to keep up with the rate of incoming uevents, while applying an +increasing amount of filtering and merging as pressure on the uevent +dispatcher increases. It's reasonable to assume that filtering and +merging get more efficient with increasing queue length, because the +probability of finding matching events will increase. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 108 +++++++++++++++--------------------------- + 1 file changed, 37 insertions(+), 71 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 2198e254..c3984fef 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -54,10 +54,6 @@ + #include "blacklist.h" + #include "devmapper.h" + +-#define MAX_ACCUMULATION_COUNT 2048 +-#define MAX_ACCUMULATION_TIME 30*1000 +-#define MIN_BURST_SPEED 10 +- + typedef int (uev_trigger)(struct uevent *, void * trigger_data); + + static LIST_HEAD(uevq); +@@ -586,44 +582,43 @@ static struct uevent *uevent_from_udev_device(struct udev_device *dev) + return uev; + } + +-static bool uevent_burst(struct timeval *start_time, int events) ++#define MAX_UEVENTS 1000 ++static int uevent_receive_events(int fd, struct list_head *tmpq, ++ struct udev_monitor *monitor) + { +- struct timeval diff_time, end_time; +- unsigned long speed; +- unsigned long eclipse_ms; +- +- if(events > MAX_ACCUMULATION_COUNT) { +- condlog(2, "burst got %u uevents, too much uevents, stopped", events); +- return false; +- } ++ struct pollfd ev_poll; ++ int n = 0; + +- gettimeofday(&end_time, NULL); +- timersub(&end_time, start_time, &diff_time); ++ do { ++ struct uevent *uev; ++ struct udev_device *dev; + +- eclipse_ms = diff_time.tv_sec * 1000 + diff_time.tv_usec / 1000; ++ dev = udev_monitor_receive_device(monitor); ++ if (!dev) { ++ condlog(0, "failed getting udev device"); ++ break; ++ } ++ uev = uevent_from_udev_device(dev); ++ if (!uev) ++ break; + +- if (eclipse_ms == 0) +- return true; ++ list_add_tail(&uev->node, tmpq); ++ n++; ++ condlog(4, "received uevent \"%s %s\"", uev->action, uev->kernel); + +- if (eclipse_ms > MAX_ACCUMULATION_TIME) { +- condlog(2, "burst continued %lu ms, too long time, stopped", eclipse_ms); +- return false; +- } ++ ev_poll.fd = fd; ++ ev_poll.events = POLLIN; + +- speed = (events * 1000) / eclipse_ms; +- if (speed > MIN_BURST_SPEED) +- return true; ++ } while (n < MAX_UEVENTS && poll(&ev_poll, 1, 0) > 0); + +- return false; ++ return n; + } + + int uevent_listen(struct udev *udev) + { + int err = 2; + struct udev_monitor *monitor = NULL; +- int fd, socket_flags, events; +- struct timeval start_time; +- int timeout = 30; ++ int fd, socket_flags; + LIST_HEAD(uevlisten_tmp); + + /* +@@ -675,59 +670,30 @@ int uevent_listen(struct udev *udev) + goto out; + } + +- events = 0; +- gettimeofday(&start_time, NULL); + pthread_cleanup_push(cleanup_global_uevq, NULL); + pthread_cleanup_push(cleanup_uevq, &uevlisten_tmp); + while (1) { +- struct uevent *uev; +- struct udev_device *dev; +- struct pollfd ev_poll; +- int poll_timeout; +- int fdcount; ++ int fdcount, events; ++ struct pollfd ev_poll = { .fd = fd, .events = POLLIN, }; + +- memset(&ev_poll, 0, sizeof(struct pollfd)); +- ev_poll.fd = fd; +- ev_poll.events = POLLIN; +- poll_timeout = timeout * 1000; +- errno = 0; +- fdcount = poll(&ev_poll, 1, poll_timeout); +- if (fdcount > 0 && ev_poll.revents & POLLIN) { +- timeout = uevent_burst(&start_time, events + 1) ? 1 : 0; +- dev = udev_monitor_receive_device(monitor); +- if (!dev) { +- condlog(0, "failed getting udev device"); +- continue; +- } +- uev = uevent_from_udev_device(dev); +- if (!uev) +- continue; +- list_add_tail(&uev->node, &uevlisten_tmp); +- events++; +- continue; +- } ++ fdcount = poll(&ev_poll, 1, -1); + if (fdcount < 0) { + if (errno == EINTR) + continue; + +- condlog(0, "error receiving " +- "uevent message: %m"); ++ condlog(0, "error receiving uevent message: %m"); + err = -errno; + break; + } +- if (!list_empty(&uevlisten_tmp)) { +- /* +- * Queue uevents and poke service pthread. +- */ +- condlog(3, "Forwarding %d uevents", events); +- pthread_mutex_lock(uevq_lockp); +- list_splice_tail_init(&uevlisten_tmp, &uevq); +- pthread_cond_signal(uev_condp); +- pthread_mutex_unlock(uevq_lockp); +- events = 0; +- } +- gettimeofday(&start_time, NULL); +- timeout = 30; ++ events = uevent_receive_events(fd, &uevlisten_tmp, monitor); ++ if (events <= 0) ++ continue; ++ ++ condlog(4, "Forwarding %d uevents", events); ++ pthread_mutex_lock(uevq_lockp); ++ list_splice_tail_init(&uevlisten_tmp, &uevq); ++ pthread_cond_signal(uev_condp); ++ pthread_mutex_unlock(uevq_lockp); + } + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); diff --git a/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch b/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch new file mode 100644 index 0000000..51baf0e --- /dev/null +++ b/0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch @@ -0,0 +1,356 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Wed, 30 Mar 2022 00:06:15 +0200 +Subject: [PATCH] libmultipath: uevent: use struct to pass parameters around + +libmultipath: uevent_dispatch(): just grab config once + +Introduce struct uevent_filter_state to pass parameters around. +This simplifies the function signatures and allows for easy extension +later. + +Instead of grabbing multipath config repeatedly, do it just +once per dispatcher iteration, and pass the pointer around in +struct uevent_filter_state. We shouldn't use different configs +for different paths in a single iteration, anyway. + +Also, properly constify get_uid_attribute_by_attrs() and +pp->uid_attribute. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/config.c | 6 +-- + libmultipath/config.h | 4 +- + libmultipath/discovery.c | 2 +- + libmultipath/structs.h | 2 +- + libmultipath/uevent.c | 110 +++++++++++++++++---------------------- + libmultipath/uevent.h | 3 +- + tests/uevent.c | 2 +- + 7 files changed, 58 insertions(+), 71 deletions(-) + +diff --git a/libmultipath/config.c b/libmultipath/config.c +index f31200a3..bd8296bf 100644 +--- a/libmultipath/config.c ++++ b/libmultipath/config.c +@@ -1112,10 +1112,10 @@ out: + return 1; + } + +-char *get_uid_attribute_by_attrs(struct config *conf, +- const char *path_dev) ++const char *get_uid_attribute_by_attrs(const struct config *conf, ++ const char *path_dev) + { +- vector uid_attrs = &conf->uid_attrs; ++ const struct _vector *uid_attrs = &conf->uid_attrs; + int j; + char *att, *col; + +diff --git a/libmultipath/config.h b/libmultipath/config.h +index 5807ac68..d3abbaea 100644 +--- a/libmultipath/config.h ++++ b/libmultipath/config.h +@@ -329,7 +329,7 @@ void libmp_put_multipath_config(void *); + void put_multipath_config(void *); + + int parse_uid_attrs(char *uid_attrs, struct config *conf); +-char *get_uid_attribute_by_attrs(struct config *conf, +- const char *path_dev); ++const char *get_uid_attribute_by_attrs(const struct config *conf, ++ const char *path_dev); + + #endif +diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c +index 22d114b3..186423e0 100644 +--- a/libmultipath/discovery.c ++++ b/libmultipath/discovery.c +@@ -2071,7 +2071,7 @@ fix_broken_nvme_wwid(struct path *pp, const char *value, size_t size) + } + + static int +-get_udev_uid(struct path * pp, char *uid_attribute, struct udev_device *udev) ++get_udev_uid(struct path * pp, const char *uid_attribute, struct udev_device *udev) + { + ssize_t len; + const char *value; +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 2f69e831..423c8b78 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -350,7 +350,7 @@ struct path { + int detect_prio; + int detect_checker; + int tpgs; +- char * uid_attribute; ++ const char *uid_attribute; + char * getuid; + struct prio prio; + struct checker checker; +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index c3984fef..4ef7181c 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -65,6 +65,12 @@ static uev_trigger *my_uev_trigger; + static void *my_trigger_data; + static int servicing_uev; + ++struct uevent_filter_state { ++ struct list_head uevq; ++ struct list_head *old_tail; ++ struct config *conf; ++}; ++ + int is_uevent_busy(void) + { + int empty; +@@ -160,40 +166,24 @@ int uevent_get_env_positive_int(const struct uevent *uev, + } + + void +-uevent_get_wwid(struct uevent *uev) ++uevent_get_wwid(struct uevent *uev, const struct config *conf) + { +- char *uid_attribute; ++ const char *uid_attribute; + const char *val; +- struct config * conf; + +- conf = get_multipath_config(); +- pthread_cleanup_push(put_multipath_config, conf); + uid_attribute = get_uid_attribute_by_attrs(conf, uev->kernel); +- pthread_cleanup_pop(1); +- + val = uevent_get_env_var(uev, uid_attribute); + if (val) + uev->wwid = val; + } + +-static bool uevent_need_merge(void) ++static bool uevent_need_merge(const struct config *conf) + { +- struct config * conf; +- bool need_merge = false; +- +- conf = get_multipath_config(); +- if (VECTOR_SIZE(&conf->uid_attrs) > 0) +- need_merge = true; +- put_multipath_config(conf); +- +- return need_merge; ++ return VECTOR_SIZE(&conf->uid_attrs) > 0; + } + +-static bool uevent_can_discard(struct uevent *uev) ++static bool uevent_can_discard(struct uevent *uev, const struct config *conf) + { +- int invalid = 0; +- struct config * conf; +- + /* + * do not filter dm devices by devnode + */ +@@ -202,15 +192,10 @@ static bool uevent_can_discard(struct uevent *uev) + /* + * filter paths devices by devnode + */ +- conf = get_multipath_config(); +- pthread_cleanup_push(put_multipath_config, conf); + if (filter_devnode(conf->blist_devnode, conf->elist_devnode, + uev->kernel) > 0) +- invalid = 1; +- pthread_cleanup_pop(1); +- +- if (invalid) + return true; ++ + return false; + } + +@@ -354,29 +339,28 @@ static void uevent_delete_simple(struct uevent *to_delete) + free(to_delete); + } + +-static void +-uevent_prepare(struct list_head *tmpq, const struct list_head *stop) ++static void uevent_prepare(struct uevent_filter_state *st) + { + struct uevent *uev, *tmp; + +- list_for_some_entry_reverse_safe(uev, tmp, tmpq, stop, node) { +- if (uevent_can_discard(uev)) { ++ list_for_some_entry_reverse_safe(uev, tmp, &st->uevq, st->old_tail, node) { ++ if (uevent_can_discard(uev, st->conf)) { + uevent_delete_simple(uev); + continue; + } + + if (strncmp(uev->kernel, "dm-", 3) && +- uevent_need_merge()) +- uevent_get_wwid(uev); ++ uevent_need_merge(st->conf)) ++ uevent_get_wwid(uev, st->conf); + } + } + + static void +-uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **stop) ++uevent_filter(struct uevent *later, struct uevent_filter_state *st) + { + struct uevent *earlier, *tmp; + +- list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) { ++ list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { + /* + * filter unnessary earlier uevents + * by the later uevent +@@ -386,17 +370,16 @@ uevent_filter(struct uevent *later, struct list_head *tmpq, struct list_head **s + earlier->kernel, earlier->action, + later->kernel, later->action); + +- uevent_delete_from_list(earlier, &tmp, stop); ++ uevent_delete_from_list(earlier, &tmp, &st->old_tail); + } + } + } + +-static void +-uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **stop) ++static void uevent_merge(struct uevent *later, struct uevent_filter_state *st) + { + struct uevent *earlier, *tmp; + +- list_for_some_entry_reverse_safe(earlier, tmp, &later->node, tmpq, node) { ++ list_for_some_entry_reverse_safe(earlier, tmp, &later->node, &st->uevq, node) { + if (merge_need_stop(earlier, later)) + break; + /* +@@ -408,8 +391,8 @@ uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **st + later->action, later->kernel, later->wwid); + + /* See comment in uevent_delete_from_list() */ +- if (&earlier->node == *stop) +- *stop = earlier->node.prev; ++ if (&earlier->node == st->old_tail) ++ st->old_tail = earlier->node.prev; + + list_move(&earlier->node, &later->merge_node); + list_splice_init(&earlier->merge_node, +@@ -418,16 +401,15 @@ uevent_merge(struct uevent *later, struct list_head *tmpq, struct list_head **st + } + } + +-static void +-merge_uevq(struct list_head *tmpq, struct list_head *stop) ++static void merge_uevq(struct uevent_filter_state *st) + { + struct uevent *later; + +- uevent_prepare(tmpq, stop); +- list_for_some_entry_reverse(later, tmpq, stop, node) { +- uevent_filter(later, tmpq, &stop); +- if(uevent_need_merge()) +- uevent_merge(later, tmpq, &stop); ++ uevent_prepare(st); ++ list_for_some_entry_reverse(later, &st->uevq, st->old_tail, node) { ++ uevent_filter(later, st); ++ if(uevent_need_merge(st->conf)) ++ uevent_merge(later, st); + } + } + +@@ -479,41 +461,45 @@ static void cleanup_global_uevq(void *arg __attribute__((unused))) + int uevent_dispatch(int (*uev_trigger)(struct uevent *, void * trigger_data), + void * trigger_data) + { +- LIST_HEAD(uevq_work); ++ struct uevent_filter_state filter_state; + ++ INIT_LIST_HEAD(&filter_state.uevq); + my_uev_trigger = uev_trigger; + my_trigger_data = trigger_data; + + mlockall(MCL_CURRENT | MCL_FUTURE); + +- pthread_cleanup_push(cleanup_uevq, &uevq_work); ++ pthread_cleanup_push(cleanup_uevq, &filter_state.uevq); + while (1) { +- struct list_head *stop; +- + pthread_cleanup_push(cleanup_mutex, uevq_lockp); + pthread_mutex_lock(uevq_lockp); + +- servicing_uev = !list_empty(&uevq_work); ++ servicing_uev = !list_empty(&filter_state.uevq); + +- while (list_empty(&uevq_work) && list_empty(&uevq)) ++ while (list_empty(&filter_state.uevq) && list_empty(&uevq)) + pthread_cond_wait(uev_condp, uevq_lockp); + + servicing_uev = 1; + /* +- * "stop" is the list element towards which merge_uevq() +- * will iterate: the last element of uevq_work before +- * appending new uevents. If uveq_is empty, uevq_work.prev +- * equals &uevq_work, which is what we need. ++ * "old_tail" is the list element towards which merge_uevq() ++ * will iterate: the last element of uevq before ++ * appending new uevents. If uveq empty, uevq.prev ++ * equals &uevq, which is what we need. + */ +- stop = uevq_work.prev; +- list_splice_tail_init(&uevq, &uevq_work); ++ filter_state.old_tail = filter_state.uevq.prev; ++ list_splice_tail_init(&uevq, &filter_state.uevq); + pthread_cleanup_pop(1); + + if (!my_uev_trigger) + break; + +- merge_uevq(&uevq_work, stop); +- service_uevq(&uevq_work); ++ ++ pthread_cleanup_push(put_multipath_config, filter_state.conf); ++ filter_state.conf = get_multipath_config(); ++ merge_uevq(&filter_state); ++ pthread_cleanup_pop(1); ++ ++ service_uevq(&filter_state.uevq); + } + pthread_cleanup_pop(1); + condlog(3, "Terminating uev service queue"); +diff --git a/libmultipath/uevent.h b/libmultipath/uevent.h +index 61ca1b56..53a7ca29 100644 +--- a/libmultipath/uevent.h ++++ b/libmultipath/uevent.h +@@ -10,6 +10,7 @@ + #define OBJECT_SIZE 512 + + struct udev; ++struct config; + + struct uevent { + struct list_head node; +@@ -31,7 +32,7 @@ int uevent_listen(struct udev *udev); + int uevent_dispatch(int (*store_uev)(struct uevent *, void * trigger_data), + void * trigger_data); + bool uevent_is_mpath(const struct uevent *uev); +-void uevent_get_wwid(struct uevent *uev); ++void uevent_get_wwid(struct uevent *uev, const struct config *conf); + + int uevent_get_env_positive_int(const struct uevent *uev, + const char *attr); +diff --git a/tests/uevent.c b/tests/uevent.c +index 648ff268..e237a208 100644 +--- a/tests/uevent.c ++++ b/tests/uevent.c +@@ -111,7 +111,7 @@ static void test_uid_attrs(void **state) + static void test_wwid(void **state) + { + struct uevent *uev = *state; +- uevent_get_wwid(uev); ++ uevent_get_wwid(uev, &conf); + + assert_string_equal(uev->wwid, WWID); + } diff --git a/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch b/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch new file mode 100644 index 0000000..c5b65de --- /dev/null +++ b/0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Fri, 5 Jan 2024 18:51:02 +0100 +Subject: [PATCH] libmultipath: is_uevent_busy(): check servicing_uev under + lock + +This fixes a coverity-reported defect (413384 Data race condition). +Indeed, we always set servicing_uev with the lock held, so it makes +sense to read it with the lock held, too. + +Signed-off-by: Martin Wilck +Reviewed-by: Benjamin Marzinski +Signed-off-by: Benjamin Marzinski +--- + libmultipath/uevent.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 4ef7181c..8cd928a9 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -73,12 +73,13 @@ struct uevent_filter_state { + + int is_uevent_busy(void) + { +- int empty; ++ int empty, servicing; + + pthread_mutex_lock(uevq_lockp); + empty = list_empty(&uevq); ++ servicing = servicing_uev; + pthread_mutex_unlock(uevq_lockp); +- return (!empty || servicing_uev); ++ return (!empty || servicing); + } + + struct uevent * alloc_uevent (void) diff --git a/0205-multipathd-make-multipathd-show-status-busy-checker-.patch b/0205-multipathd-make-multipathd-show-status-busy-checker-.patch new file mode 100644 index 0000000..72d64e2 --- /dev/null +++ b/0205-multipathd-make-multipathd-show-status-busy-checker-.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 21 Jan 2026 16:03:12 -0500 +Subject: [PATCH] multipathd: make "multipathd show status" busy checker better + +while uevent_listen() was grabbing new uevents, "multipathd show status" +would still show show busy as "False". Add a check there, to make catch +multipathd's uevent processing earlier. Also, access servicing_uev (as +well as the new variable, adding_uev) atomically, just to make sure that +the compiler doesn't do stupid things trying to optimize them. + +Signed-off-by: Benjamin Marzinski +Reviewed-by: Martin Wilck +--- + libmultipath/uevent.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/libmultipath/uevent.c b/libmultipath/uevent.c +index 8cd928a9..c230e963 100644 +--- a/libmultipath/uevent.c ++++ b/libmultipath/uevent.c +@@ -64,6 +64,7 @@ static pthread_cond_t *uev_condp = &uev_cond; + static uev_trigger *my_uev_trigger; + static void *my_trigger_data; + static int servicing_uev; ++static int adding_uev; /* uatomic access only */ + + struct uevent_filter_state { + struct list_head uevq; +@@ -73,13 +74,14 @@ struct uevent_filter_state { + + int is_uevent_busy(void) + { +- int empty, servicing; ++ int empty, servicing, adding; + + pthread_mutex_lock(uevq_lockp); + empty = list_empty(&uevq); + servicing = servicing_uev; ++ adding = uatomic_read(&adding_uev); + pthread_mutex_unlock(uevq_lockp); +- return (!empty || servicing); ++ return (!empty || servicing || adding); + } + + struct uevent * alloc_uevent (void) +@@ -663,6 +665,7 @@ int uevent_listen(struct udev *udev) + int fdcount, events; + struct pollfd ev_poll = { .fd = fd, .events = POLLIN, }; + ++ uatomic_set(&adding_uev, 0); + fdcount = poll(&ev_poll, 1, -1); + if (fdcount < 0) { + if (errno == EINTR) +@@ -672,6 +675,8 @@ int uevent_listen(struct udev *udev) + err = -errno; + break; + } ++ uatomic_set(&adding_uev, 1); ++ + events = uevent_receive_events(fd, &uevlisten_tmp, monitor); + if (events <= 0) + continue; diff --git a/device-mapper-multipath.spec b/device-mapper-multipath.spec index 87159a9..d638e55 100644 --- a/device-mapper-multipath.spec +++ b/device-mapper-multipath.spec @@ -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 - 0.8.7-43 +- Add 0198-multipathd-print-path-offline-message-even-without-a.patch + * Fixes RHEL-133814 ("log_checker_err is not printing messages + repeatedly for failed path [rhel-9]") +- Add 0199-libmultipath-improve-cleanup-of-uevent-queues-on-exi.patch +- Add 0200-uevent_dispatch-use-while-in-wait-loop.patch +- Add 0201-libmultipath-uevent_dispatch-process-uevents-one-by-.patch +- Add 0202-libmultipath-uevent_listen-don-t-delay-uevents.patch +- Add 0203-libmultipath-uevent-use-struct-to-pass-parameters-ar.patch +- Add 0204-libmultipath-is_uevent_busy-check-servicing_uev-unde.patch +- Add 0205-multipathd-make-multipathd-show-status-busy-checker-.patch + * Fixes RHEL-135904 (VM reboot in RHOSP environment fails with error + "Could not open '/dev/dm-95': No such file or directory") +- Resolves: RHEL-133814 +- Resolves: RHEL-135904 + * Wed Nov 19 2025 Benjamin Marzinski - 0.8.7-42 - Add 0197-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch * Fixes RHEL-128396 ("Update the multipath.conf stanza for Infinidat