From a04be5570906e1a561794afc5a567cfde579b6f5 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Wed, 21 Jan 2026 16:03:10 -0500 Subject: [PATCH 1/9] multipathd: don't add removed/partial paths to new maps During configure(), when the old multipath devices are added in map_discovery(), it's possible that they have paths in the INIT_PARTIAL or INIT_REMOVED state. These paths were not found during path_discovery(). Don't add them to the new multipath maps. coalesce_maps() will make sure that they point to the old maps, so they will get cleaned up when those maps are removed. Reviewed-by: Martin Wilck Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/configure.c | 12 +++++++++++- libmultipath/structs_vec.c | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 0bcec0890..a713ddc45 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -1113,7 +1113,17 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, continue; } - /* 4. path is out of scope */ + /* + * 4. The path wasn't found in path_discovery. It only exists + * in an old map. + */ + if (pp1->initialized == INIT_PARTIAL || + pp1->initialized == INIT_REMOVED) { + orphan_path(pp1, "path not found"); + continue; + } + + /* 5. path is out of scope */ if (refwwid && strncmp(pp1->wwid, refwwid, WWID_SIZE - 1)) continue; diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c index 9384de017..3e63e9c3f 100644 --- a/libmultipath/structs_vec.c +++ b/libmultipath/structs_vec.c @@ -315,7 +315,8 @@ int adopt_paths(vector pathvec, struct multipath *mpp, pp->dev, mpp->alias); continue; } - if (pp->initialized == INIT_REMOVED) + if (pp->initialized == INIT_REMOVED || + pp->initialized == INIT_PARTIAL) continue; if (mpp->queue_mode == QUEUE_MODE_RQ && pp->bus == SYSFS_BUS_NVME && From 1942fb136a388c9bdd964741116ed0a89cf9e198 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Wed, 21 Jan 2026 16:03:11 -0500 Subject: [PATCH 2/9] multipathd: finish initalization of paths added while offline If a path in a mulitpath device is offline while multipathd is reconfigured, it will get added to the updated multipath device, just like it was in the old multipath device. However the device will still be in the INIT_NEW state because it can't get initilized while offline. This is different than the INIT_PARTIAL state because the path was discovered in path_discovery(). INIT_PARTIAL is for paths that multipathd did not discover in path_discovery() or receive a uevent for, but are part of a multipath device that was added, and which should receive a uevent shortly. There is no reason to expect a uevent for these offline paths. When the path comes back online, multipathd will run the checker and prioritizer on it. The only two pathinfo checks that won't happen are the DI_WWID and DI_IOCTL ones. Modify pathinfo() to make sure that if DI_IOCTL was skipped for offline paths, it gets called the next time pathinfo() is called after the fd can be opened. Also, make sure that when one of these offline paths becomes usable, its WWID is rechecked. With those changes, all the DI_ALL checks will be accounted for, and the path can be marked INIT_OK. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/discovery.c | 2 ++ multipathd/main.c | 53 ++++++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index 0c5e5ca69..0efb8213d 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -2585,6 +2585,8 @@ int pathinfo(struct path *pp, struct config *conf, int mask) * Recoverable error, for example faulty or offline path */ pp->chkrstate = pp->state = PATH_DOWN; + if (mask & DI_IOCTL && pp->ioctl_info == IOCTL_INFO_NOT_REQUESTED) + pp->ioctl_info = IOCTL_INFO_SKIPPED; if (pp->initialized == INIT_NEW || pp->initialized == INIT_FAILED) memset(pp->wwid, 0, WWID_SIZE); diff --git a/multipathd/main.c b/multipathd/main.c index 61e0ea346..90472d69d 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -2572,6 +2572,26 @@ static int sync_mpp(struct vectors *vecs, struct multipath *mpp, unsigned int ti return do_sync_mpp(vecs, mpp); } +/* + * pp->wwid should never be empty when this function is called, but if it + * is, this function can set it. + */ +static bool new_path_wwid_changed(struct path *pp, int state) +{ + char wwid[WWID_SIZE]; + + strlcpy(wwid, pp->wwid, WWID_SIZE); + if (get_uid(pp, state, pp->udev, 1) != 0) { + strlcpy(pp->wwid, wwid, WWID_SIZE); + return false; + } + if (strlen(wwid) && strncmp(wwid, pp->wwid, WWID_SIZE) != 0) { + strlcpy(pp->wwid, wwid, WWID_SIZE); + return true; + } + return false; +} + static int update_path_state (struct vectors * vecs, struct path * pp) { @@ -2601,14 +2621,33 @@ update_path_state (struct vectors * vecs, struct path * pp) return CHECK_PATH_SKIPPED; } - if (pp->recheck_wwid == RECHECK_WWID_ON && - (newstate == PATH_UP || newstate == PATH_GHOST) && + if ((newstate == PATH_UP || newstate == PATH_GHOST) && ((pp->state != PATH_UP && pp->state != PATH_GHOST) || - pp->dmstate == PSTATE_FAILED) && - check_path_wwid_change(pp)) { - condlog(0, "%s: path wwid change detected. Removing", pp->dev); - return handle_path_wwid_change(pp, vecs)? CHECK_PATH_REMOVED : - CHECK_PATH_SKIPPED; + pp->dmstate == PSTATE_FAILED)) { + bool wwid_changed = false; + + if (pp->initialized == INIT_NEW) { + /* + * Path was added to map while offline, mark it as + * initialized. + * DI_SYSFS was checked when the path was added + * DI_IOCTL was checked when the checker was selected + * DI_CHECKER just got checked + * DI_WWID is about to be checked + * DI_PRIO will get checked at the end of this checker + * loop + */ + pp->initialized = INIT_OK; + wwid_changed = new_path_wwid_changed(pp, newstate); + } else if (pp->recheck_wwid == RECHECK_WWID_ON) + wwid_changed = check_path_wwid_change(pp); + if (wwid_changed) { + condlog(0, "%s: path wwid change detected. Removing", + pp->dev); + return handle_path_wwid_change(pp, vecs) + ? CHECK_PATH_REMOVED + : CHECK_PATH_SKIPPED; + } } if ((newstate != PATH_UP && newstate != PATH_GHOST && newstate != PATH_PENDING) && (pp->state == PATH_DELAYED)) { From 7fdd93b84499480b289d5d996d302aebb4974dfc Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Wed, 21 Jan 2026 16:03:12 -0500 Subject: [PATCH 3/9] 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 be199af00..b0c433403 100644 --- a/libmultipath/uevent.c +++ b/libmultipath/uevent.c @@ -52,6 +52,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; @@ -70,13 +71,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) @@ -730,6 +732,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) @@ -739,6 +742,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; From 1a364a1699baec8cb886c0e82a5e4699142ac153 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Wed, 21 Jan 2026 16:03:13 -0500 Subject: [PATCH 4/9] 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 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/multipathd/main.c b/multipathd/main.c index 90472d69d..05dd65e6a 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -96,12 +96,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->sysfs_state == PATH_DOWN) \ + if (pp->mpp && lvl <= libmp_verbosity) { \ + if (pp->sysfs_state != PATH_UP) \ 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); \ \ From 2b23e760ecc621785cca44cf0f824aff123e4fc5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 22 Jan 2026 00:05:44 +0100 Subject: [PATCH 5/9] GitHub workflows: spelling: add path restrictions for PRs We were only applying path restrictions for push events. Apply the same restrictions also for PRs. Signed-off-by: Martin Wilck --- .github/workflows/spelling.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 7943ebb8e..bfce4e662 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -86,6 +86,23 @@ on: - 'opened' - 'reopened' - 'synchronize' + paths: + - '.github/workflows/spelling.yml' + - 'README*' + - 'NEWS.md' + - '**.3' + - '**.5' + - '**.8' + - '**.8' + - '**.in' + - '**.service' + - '**.socket' + - '**.rules' + - '**/libdmmp.h' + - '**/mpath_valid.h' + - '**/mpath_cmd.h' + - '**/mpath_persist.h' + - '.github/actions/spelling/*' jobs: spelling: From f4e1c7250d25ef784cd569b93781ba7e5aed45b6 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 23 Jan 2026 15:47:26 +0100 Subject: [PATCH 6/9] GitHub workflows: spelling: add pattern for 7-digit commit IDs Signed-off-by: Martin Wilck --- .github/actions/spelling/patterns.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index 55d4742eb..b6a7aaeb7 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -15,6 +15,9 @@ \b0x[0-9a-f]{8}\b \b0x[0-9a-f]{16}\b +# Commit SHAs +\b[0-9a-f]{7}\b + # WWNN/WWPN (NAA identifiers) \b(?:0x)?10[0-9a-f]{14}\b \b(?:0x|3)?[25][0-9a-f]{15}\b From fab5d44094ed24be39ce11e38e2324d57f5e0601 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 26 Jan 2026 12:29:31 +0100 Subject: [PATCH 7/9] kpartx: fix segfault when operating on regular files The following problem has been introduced in multipath-tools 0.14.0: > truncate -s1G /tmp/img > kpartx -a /tmp/img double free or corruption (out) Aborted (core dumped) kpartx -a /tmp/img Fix it by always allocating "uuid" on the heap, rather than using a static char array. Fixes: 8c39e60 ("kpartx: fix some memory leaks") Fixes: https://github.com/opensvc/multipath-tools/issues/139 Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- kpartx/devmapper.c | 16 ++++++++-------- kpartx/kpartx.c | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/kpartx/devmapper.c b/kpartx/devmapper.c index d49c6806a..45dac585e 100644 --- a/kpartx/devmapper.c +++ b/kpartx/devmapper.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2004, 2005 Christophe Varoqui */ +#define _GNU_SOURCE #include #include #include @@ -699,14 +700,13 @@ int dm_find_part(const char *parent, const char *delim, int part, char *nondm_create_uuid(dev_t devt) { -#define NONDM_UUID_BUFLEN (34 + sizeof(NONDM_UUID_PREFIX) + \ - sizeof(NONDM_UUID_SUFFIX)) - static char uuid_buf[NONDM_UUID_BUFLEN]; - snprintf(uuid_buf, sizeof(uuid_buf), "%s_%u:%u_%s", - NONDM_UUID_PREFIX, major(devt), minor(devt), - NONDM_UUID_SUFFIX); - uuid_buf[NONDM_UUID_BUFLEN-1] = '\0'; - return uuid_buf; + char *uuid; + + if (asprintf(&uuid, "%s_%u:%u_%s", NONDM_UUID_PREFIX, major(devt), + minor(devt), NONDM_UUID_SUFFIX) >= 0) + return uuid; + else + return NULL; } int nondm_parse_uuid(const char *uuid, unsigned int *major, unsigned int *minor) diff --git a/kpartx/kpartx.c b/kpartx/kpartx.c index 9bdd20409..cfd821284 100644 --- a/kpartx/kpartx.c +++ b/kpartx/kpartx.c @@ -334,8 +334,11 @@ main(int argc, char **argv){ * This allows deletion of partitions created with older kpartx * versions which didn't use the fake UUID during creation. */ - if (!uuid && !(what == DELETE && force_devmap)) + if (!uuid && !(what == DELETE && force_devmap)) { uuid = nondm_create_uuid(buf.st_rdev); + if (!uuid) + exit(1); + } if (delim == NULL) { delim = xmalloc(DELIM_SIZE); From 12deae81aea2dff5192ab3f8fa177f90001c30cf Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 21 Jan 2026 23:50:38 +0100 Subject: [PATCH 8/9] Update NEWS.md Signed-off-by: Martin Wilck --- NEWS.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/NEWS.md b/NEWS.md index a842648e4..7aa94cd30 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,28 @@ release. These bug fixes will be tracked in stable branches. See [README.md](README.md) for additional information. +## multipath-tools 0.14.1, 2026/01 + +### Bug fixes + +* kpartx: Fix freeing static buffer when operating on regular files. + Fixes 0.14.0. Commit fab5d44. + Fixes [#139](https://github.com/opensvc/multipath-tools/issues/139). +* Fix initialization of paths that were offline during path detection. + Commit 1942fb1. +* Fix printing the "path offline" log message for offline paths that don't + have a path checker configured. Commit 1a364a1. + +### Other changes + +* If path devices that are members of multipath maps aren't detected during + multipathd startup or `reconfigure`, don't add them to newly created maps + in the first place. In previous versions, such paths would be added to the + maps, only to be removed later. Commit a04be55. +* Improve the detection of "busy" state in the `show status` command, such + that reading udev events from udevd is also counted as busy. + Commit 7fdd93b. + ## multipath-tools 0.14.0, 2026/01 ### User-visible changes From f1f0576a8a4e5ada9941c8c704315ac0e56f5386 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 23 Jan 2026 11:33:18 +0100 Subject: [PATCH 9/9] libmultipath: bump version to 0.14.1 Signed-off-by: Martin Wilck --- libmultipath/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libmultipath/version.h b/libmultipath/version.h index 445ba9093..ddc7591fb 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -11,9 +11,9 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION_CODE 0x000E00 +#define VERSION_CODE 0x000E01 /* MMDDYY, in hex */ -#define DATE_CODE 0x01101A +#define DATE_CODE 0x01171A #define PROG "multipath-tools"