* [PATCH v2 0/1] multipathd: don't leave maps read-only after write protect is cleared
@ 2026-10-21 18:27 Brian Bunker
2026-10-21 18:27 ` [PATCH v2 1/1] " Brian Bunker
0 siblings, 1 reply; 16+ messages in thread
From: Brian Bunker @ 2026-10-21 18:27 UTC (permalink / raw)
To: dm-devel; +Cc: Benjamin Marzinski, Martin Wilck, Christophe Varoqui,
Brian Bunker
This is v2. It reworks how force_readonly is set and cleared,
following Ben's review of v1. The changes are listed at the end.
A multipath map can stay read-only after write protect is cleared on
the storage, even though all of its paths are read-write. It happens
when the map's paths are removed and re-added while write protect is
set:
1. Write protect is set. The map is reloaded read-only.
uev_update_path() sets force_readonly for that reload and clears it
afterwards.
2. All of the map's paths are removed, and new paths are added while
write protect is still set. ev_add_path() sees a read-only path and
sets force_readonly = 1. Nothing ever clears it.
3. Write protect is cleared on the new paths, one after another. Each
path's DISK_RO=0 uevent is ignored while any path of the map is
still read-only.
4. The last path's uevent reloads the map. The reload sees the stale
force_readonly = 1 and loads the map read-only, logging "reloaded
(retval 0)".
force_readonly only works in one direction: ev_add_path() can set it
but never clears it. If the new paths had been added already writable,
ev_add_path() wouldn't have set the flag and the map wouldn't have
stuck. The paths have to be added while write protected and then
cleared in place.
After step 4 nothing reloads the map again. A rescan doesn't change the
paths' ro state, so it generates no DISK_RO uevent. And since
multipathd now sees the map as read-only, needs_ro_update() also
ignores the next DISK_RO=1 uevent for it.
If two paths' uevents happen to be handled after all paths are
writable, the map does recover: uev_update_path() clears
force_readonly after its reload, so the second reload is read-write.
That makes the problem look intermittent.
Example
-------
We hit this on RHEL 9 device-mapper-multipath 0.8.7, whose
ev_add_path() and uev_update_path() handle force_readonly the same way
as current master. The host's iSCSI sessions were torn down and
re-established while the array was write protecting the volume. From
the messages log, trimmed:
05:48:27 ...114d8: reload [0 51200 multipath 0 1 alua 1 1
service-time 0 6 1 135:272 1 66:624 1 69:720 1 129:560 1
132:656 1 135:752 1]
05:48:27 sdsd: map ...114d8 reloaded (retval 0)
05:51:04 kernel: sd 24:0:0:48: [sdaer] Write Protect is on
05:51:05 ...114d8: remaining active paths: 0
05:51:07 kernel: sd 29:0:0:48: [sdaoi] Write Protect is on
(and four more new paths)
06:09:01 kernel: sd 24:0:0:48: [sdaer] Write Protect is off
06:09:07 kernel: sd 29:0:0:48: [sdaoi] Write Protect is off
06:09:10 sdaoi: update path write_protect to '0' (uevent)
06:09:10 ...114d8: reload [0 51200 multipath 0 1 alua 1 1
service-time 0 6 1 67:880 1 70:992 1 130:848 1 133:960 1
65:1072 1 67:1056 1]
06:09:10 sdaoi: map ...114d8 reloaded (retval 0)
The state afterwards, as recorded by the test:
dm-106: ro
sdaer: rw sdagu: rw sdaix: rw sdala: rw sdand: rw sdaoi: rw
The patch
---------
The patch sets force_readonly right before each load from the state of
the map's paths, and dm_addmap() clears it when a load succeeds.
needs_ro_update() sets it for the reload done for a DISK_RO uevent,
and ev_add_path() and update_map() compute it from all of the map's
paths. With the patch, the 06:09:10 reload would have found no
read-only path and loaded the map read-write.
Changes since v1:
- Set force_readonly in needs_ro_update() when it returns true,
instead of in uev_update_path(). The DISK_RO=1 case uses the uevent
again, rather than rereading sysfs.
- Clear force_readonly in dm_addmap() on success, as for need_reload,
instead of after the loads in ev_add_path() and uev_update_path().
- In ev_add_path(), only compute force_readonly when need_do_map is
set.
- Walk mpp->pg rather than mpp->paths in needs_ro_update(), since
mpp->paths may be stale there. ev_add_path() and update_map() still
use mpp->paths: both call adopt_paths() first, and mpp->pg doesn't
contain the new paths until setup_map().
- Move sysfs_get_ro() above has_ro_path() instead of adding a forward
declaration. The function is unchanged.
Brian Bunker (1):
multipathd: don't leave maps read-only after write protect is cleared
libmultipath/devmapper.c | 4 +-
multipathd/main.c | 113 +++++++++++++++++++++++++--------------
2 files changed, 75 insertions(+), 42 deletions(-)
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v2 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 18:27 [PATCH v2 0/1] multipathd: don't leave maps read-only after write protect is cleared Brian Bunker @ 2026-10-21 18:27 ` Brian Bunker 2026-10-21 19:50 ` Benjamin Marzinski 2026-10-21 21:34 ` mwilck 0 siblings, 2 replies; 16+ messages in thread From: Brian Bunker @ 2026-10-21 18:27 UTC (permalink / raw) To: dm-devel; +Cc: Benjamin Marzinski, Martin Wilck, Christophe Varoqui, Brian Bunker ev_add_path() sets mpp->force_readonly when a newly added path is read-only, but never clears it. If a map is created or reloaded while its storage is write protected, the flag stays set. When write protect is cleared, the reload that uev_update_path() does for the DISK_RO=0 uevent uses the stale flag and loads the map read-only, even though all of its paths are read-write. uev_update_path() clears the flag after that reload, so the map only recovers if another DISK_RO=0 uevent reloads it again. If none comes, the map stays read-only, and since mpp->dmi.read_only is then 1, needs_ro_update() also ignores later DISK_RO=1 uevents for it. Fix this by setting force_readonly before each load and clearing it when a load succeeds: - needs_ro_update() now sets force_readonly whenever it returns true: to 1 for a DISK_RO=1 uevent, to 0 when all of the map's paths are writable, and from the paths' state when the map has no dm info. uev_update_path() no longer sets or clears it itself. - ev_add_path() sets it from all of the map's paths rather than only the path being added, and only when it is about to load the map (need_do_map). mpp->paths is up to date there after adopt_paths(), while mpp->pg doesn't contain the new paths yet. - update_map(), which does reloads that were delayed while the map was waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's paths before setting up the map. - dm_addmap() clears it after a successful load, as it does for need_reload, so it can't outlive the load it was set for. Signed-off-by: Brian Bunker <brian@purestorage.com> --- libmultipath/devmapper.c | 4 +- multipathd/main.c | 113 +++++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index e3870f59..c2939dca 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -520,8 +520,10 @@ dm_addmap (int task, const char *target, struct multipath *mpp, if (task == DM_DEVICE_CREATE) libmp_udev_wait(cookie); - if (r) + if (r) { mpp->need_reload = false; + mpp->force_readonly = 0; + } return r; } diff --git a/multipathd/main.c b/multipathd/main.c index 9bce4d6b..605e24ae 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -709,6 +709,42 @@ void cleanup_reset_vec(struct vector_s **v) vector_reset(*v); } +static int +sysfs_get_ro (struct path *pp) +{ + int ro; + char buff[3]; /* Either "0\n\0" or "1\n\0" */ + + if (!pp->udev) + return -1; + + if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { + condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); + return -1; + } + + if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { + condlog(3, "%s: Cannot parse ro attribute", pp->dev); + return -1; + } + + return ro; +} + +/* A map must be loaded read-only if any of its paths is read-only. */ +static bool +has_ro_path(vector paths) +{ + struct path *pp; + unsigned int i; + + vector_foreach_slot (paths, pp, i) { + if (sysfs_get_ro(pp) == 1) + return true; + } + return false; +} + static int update_map (struct multipath *mpp, struct vectors *vecs, int new_map) { @@ -732,6 +768,14 @@ retry: mpp->action = ACT_RELOAD; + /* + * This may be a delayed reload, and the paths' write protect + * state may have changed since force_readonly was last set. + * Recompute it from the current paths. mpp->paths is up to date + * after adopt_paths(), while mpp->pg isn't until setup_map(). + */ + mpp->force_readonly = has_ro_path(mpp->paths); + if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup new map in update", mpp->alias); retries = -1; @@ -1270,28 +1314,6 @@ out: return ret; } -static int -sysfs_get_ro (struct path *pp) -{ - int ro; - char buff[3]; /* Either "0\n\0" or "1\n\0" */ - - if (!pp->udev) - return -1; - - if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { - condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); - return -1; - } - - if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { - condlog(3, "%s: Cannot parse ro attribute", pp->dev); - return -1; - } - - return ro; -} - /* * returns: * 0: added @@ -1305,7 +1327,6 @@ ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) int retries = 3; int start_waiter = 0; int ret; - int ro; unsigned char prflag = PR_UNSET; /* @@ -1370,14 +1391,16 @@ rescan: goto fail; /* leave path added to pathvec */ } - /* ro check - if new path is ro, force map to be ro as well */ - ro = sysfs_get_ro(pp); - if (ro == 1) - mpp->force_readonly = 1; - if (!need_do_map) return 0; + /* + * Load the map read-only if any of its paths is read-only, not + * just the one being added. mpp->paths is up to date after + * adopt_paths(), while mpp->pg doesn't have the new paths yet. + */ + mpp->force_readonly = has_ro_path(mpp->paths); + if (!dm_map_present(mpp->alias)) { mpp->action = ACT_CREATE; start_waiter = 1; @@ -1597,23 +1620,35 @@ static bool needs_ro_update(struct multipath *mpp, int ro) { struct pathgroup * pgp; - struct path * pp; - unsigned int i, j; + unsigned int i; if (!mpp || ro < 0) return false; - if (!has_dm_info(mpp)) + if (!has_dm_info(mpp)) { + mpp->force_readonly = 0; + vector_foreach_slot (mpp->pg, pgp, i) { + if (has_ro_path(pgp->paths)) { + mpp->force_readonly = 1; + break; + } + } return true; + } if (mpp->dmi.read_only == ro) return false; - if (ro == 1) + if (ro == 1) { + mpp->force_readonly = 1; return true; + } vector_foreach_slot (mpp->pg, pgp, i) { - vector_foreach_slot (pgp->paths, pp, j) { - if (sysfs_get_ro(pp) == 1) - return false; - } + if (has_ro_path(pgp->paths)) + return false; } + /* + * Clear force_readonly in case it is still set from an earlier + * failed load. + */ + mpp->force_readonly = 0; return true; } @@ -1732,15 +1767,11 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) if (mpp->wait_for_udev != UDEV_WAIT_DONE) mpp->wait_for_udev = UDEV_WAIT_RELOAD; else { - if (ro == 1) - pp->mpp->force_readonly = 1; retval = reload_and_sync_map(mpp, vecs); if (retval == 2) condlog(2, "%s: map removed during reload", pp->dev); - else { - pp->mpp->force_readonly = 0; + else condlog(2, "%s: map %s reloaded (retval %d)", uev->kernel, mpp->alias, retval); - } } } if (auto_resize != AUTO_RESIZE_NEVER && mpp && -- 2.56.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 18:27 ` [PATCH v2 1/1] " Brian Bunker @ 2026-10-21 19:50 ` Benjamin Marzinski 2026-10-21 21:34 ` mwilck 1 sibling, 0 replies; 16+ messages in thread From: Benjamin Marzinski @ 2026-10-21 19:50 UTC (permalink / raw) To: Brian Bunker; +Cc: dm-devel, Martin Wilck, Christophe Varoqui On Wed, Oct 21, 2026 at 11:27:59AM -0700, Brian Bunker wrote: > ev_add_path() sets mpp->force_readonly when a newly added path is > read-only, but never clears it. If a map is created or reloaded while > its storage is write protected, the flag stays set. When write protect > is cleared, the reload that uev_update_path() does for the DISK_RO=0 > uevent uses the stale flag and loads the map read-only, even though all > of its paths are read-write. uev_update_path() clears the flag after > that reload, so the map only recovers if another DISK_RO=0 uevent > reloads it again. If none comes, the map stays read-only, and since > mpp->dmi.read_only is then 1, needs_ro_update() also ignores later > DISK_RO=1 uevents for it. > > Fix this by setting force_readonly before each load and clearing it > when a load succeeds: > > - needs_ro_update() now sets force_readonly whenever it returns true: > to 1 for a DISK_RO=1 uevent, to 0 when all of the map's paths are > writable, and from the paths' state when the map has no dm info. > uev_update_path() no longer sets or clears it itself. > > - ev_add_path() sets it from all of the map's paths rather than only > the path being added, and only when it is about to load the map > (need_do_map). mpp->paths is up to date there after adopt_paths(), > while mpp->pg doesn't contain the new paths yet. Oops. Good catch. > > - update_map(), which does reloads that were delayed while the map was > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's > paths before setting up the map. > > - dm_addmap() clears it after a successful load, as it does for > need_reload, so it can't outlive the load it was set for. > > Signed-off-by: Brian Bunker <brian@purestorage.com> Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com> ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 18:27 ` [PATCH v2 1/1] " Brian Bunker 2026-10-21 19:50 ` Benjamin Marzinski @ 2026-10-21 21:34 ` mwilck 2026-10-21 23:19 ` [PATCH v3 0/1] " Brian Bunker 1 sibling, 1 reply; 16+ messages in thread From: mwilck @ 2026-10-21 21:34 UTC (permalink / raw) To: Brian Bunker Cc: dm-devel, Benjamin Marzinski, Martin Wilck, Christophe Varoqui > ev_add_path() sets mpp->force_readonly when a newly added path is > read-only, but never clears it. If a map is created or reloaded while > its storage is write protected, the flag stays set. When write protect > is cleared, the reload that uev_update_path() does for the DISK_RO=0 > uevent uses the stale flag and loads the map read-only, even though all > of its paths are read-write. uev_update_path() clears the flag after > that reload, so the map only recovers if another DISK_RO=0 uevent > reloads it again. If none comes, the map stays read-only, and since > mpp->dmi.read_only is then 1, needs_ro_update() also ignores later > DISK_RO=1 uevents for it. > > Fix this by setting force_readonly before each load and clearing it > when a load succeeds: > > - needs_ro_update() now sets force_readonly whenever it returns true: > to 1 for a DISK_RO=1 uevent, to 0 when all of the map's paths are > writable, and from the paths' state when the map has no dm info. > uev_update_path() no longer sets or clears it itself. > > - ev_add_path() sets it from all of the map's paths rather than only > the path being added, and only when it is about to load the map > (need_do_map). mpp->paths is up to date there after adopt_paths(), > while mpp->pg doesn't contain the new paths yet. > > - update_map(), which does reloads that were delayed while the map was > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's > paths before setting up the map. > > - dm_addmap() clears it after a successful load, as it does for > need_reload, so it can't outlive the load it was set for. > > Signed-off-by: Brian Bunker <brian@purestorage.com> > > diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c > index e3870f5..c2939dc 100644 > --- a/libmultipath/devmapper.c > +++ b/libmultipath/devmapper.c > @@ -520,8 +520,10 @@ dm_addmap (int task, const char *target, struct multipath *mpp, > if (task == DM_DEVICE_CREATE) > libmp_udev_wait(cookie); > > - if (r) > + if (r) { > mpp->need_reload = false; > + mpp->force_readonly = 0; > + } > return r; > } > > diff --git a/multipathd/main.c b/multipathd/main.c > index 9bce4d6..605e24a 100644 > --- a/multipathd/main.c > +++ b/multipathd/main.c > @@ -709,6 +709,42 @@ void cleanup_reset_vec(struct vector_s **v) > vector_reset(*v); > } > > +static int > +sysfs_get_ro (struct path *pp) > +{ > + int ro; > + char buff[3]; /* Either "0\n\0" or "1\n\0" */ > + > + if (!pp->udev) > + return -1; > + > + if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { > + condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); > + return -1; > + } > + > + if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { > + condlog(3, "%s: Cannot parse ro attribute", pp->dev); > + return -1; > + } > + > + return ro; > +} > + > +/* A map must be loaded read-only if any of its paths is read-only. */ > +static bool > +has_ro_path(vector paths) > +{ > + struct path *pp; > + unsigned int i; > + > + vector_foreach_slot (paths, pp, i) { > + if (sysfs_get_ro(pp) == 1) > + return true; > + } > + return false; > +} > + > static int > update_map (struct multipath *mpp, struct vectors *vecs, int new_map) > { > @@ -732,6 +768,14 @@ retry: > > mpp->action = ACT_RELOAD; > > + /* > + * This may be a delayed reload, and the paths' write protect > + * state may have changed since force_readonly was last set. > + * Recompute it from the current paths. mpp->paths is up to date > + * after adopt_paths(), while mpp->pg isn't until setup_map(). > + */ > + mpp->force_readonly = has_ro_path(mpp->paths); > + > if (setup_map(mpp, ¶ms, vecs)) { > condlog(0, "%s: failed to setup new map in update", mpp->alias); > retries = -1; > @@ -1270,28 +1314,6 @@ out: > return ret; > } > > -static int > -sysfs_get_ro (struct path *pp) > -{ > - int ro; > - char buff[3]; /* Either "0\n\0" or "1\n\0" */ > - > - if (!pp->udev) > - return -1; > - > - if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { > - condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); > - return -1; > - } > - > - if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { > - condlog(3, "%s: Cannot parse ro attribute", pp->dev); > - return -1; > - } > - > - return ro; > -} > - > /* > * returns: > * 0: added > @@ -1305,7 +1327,6 @@ ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) > int retries = 3; > int start_waiter = 0; > int ret; > - int ro; > unsigned char prflag = PR_UNSET; > > /* > @@ -1370,14 +1391,16 @@ rescan: > goto fail; /* leave path added to pathvec */ > } > > - /* ro check - if new path is ro, force map to be ro as well */ > - ro = sysfs_get_ro(pp); > - if (ro == 1) > - mpp->force_readonly = 1; > - > if (!need_do_map) > return 0; > > + /* > + * Load the map read-only if any of its paths is read-only, not > + * just the one being added. mpp->paths is up to date after > + * adopt_paths(), while mpp->pg doesn't have the new paths yet. > + */ > + mpp->force_readonly = has_ro_path(mpp->paths); > + > if (!dm_map_present(mpp->alias)) { > mpp->action = ACT_CREATE; > start_waiter = 1; > @@ -1597,23 +1620,35 @@ static bool > needs_ro_update(struct multipath *mpp, int ro) > { > struct pathgroup * pgp; > - struct path * pp; > - unsigned int i, j; > + unsigned int i; > > if (!mpp || ro < 0) > return false; > - if (!has_dm_info(mpp)) > + if (!has_dm_info(mpp)) { This should never happen. But as we test the condition, wonder why we don't just call dm_get_info(mpp, &mpp->dmi) to obtain the missing information? If that fails, I am not sure it makes sense to return true any more. It would indicate a severe problem with the map at hand. > + mpp->force_readonly = 0; > + vector_foreach_slot (mpp->pg, pgp, i) { if (ro == 1), you can skip this loop, as you already know that there's one ro path. > + if (has_ro_path(pgp->paths)) { > + mpp->force_readonly = 1; > + break; > + } > + } > return true; > + } > if (mpp->dmi.read_only == ro) > return false; Should mpp->force_readonly be reset here? After all, there seems to be no need for reloading. That would be a similar condition as what you fix by resetting force_readonly below. Actually, as we don't seem to trust our internal state anyway, we might simply obtain the sysfs attribute for all paths, always, and compare it to dmi.read_only. That might be somewhat slow-ish, though. The alternative would be to improve our internal tracking, e.g. by saving a "ro" flag for paths when we add them or receive an uevents for them. That way we could save the effort to check sysfs repeatedly. Thanks, Martin -- Martin Wilck <mwilck@suse.com> ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 0/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 21:34 ` mwilck @ 2026-10-21 23:19 ` Brian Bunker 2026-10-21 23:19 ` [PATCH v3 1/1] " Brian Bunker 0 siblings, 1 reply; 16+ messages in thread From: Brian Bunker @ 2026-10-21 23:19 UTC (permalink / raw) To: dm-devel; +Cc: Benjamin Marzinski, Martin Wilck, Christophe Varoqui, Brian Bunker This is v3. It follows Martin's review of v2: needs_ro_update() now fetches missing dm info instead of assuming a reload is needed, and force_readonly is cleared when a load reads it rather than only when the load succeeds. The changes are listed at the end. Ben, I dropped your Reviewed-by because the needs_ro_update() logic changed. The no-dm-info case reverses the "assume it needs to be reloaded" behavior from 6e40bf92, so please check that you're OK with it. A multipath map can stay read-only after write protect is cleared on the storage, even though all of its paths are read-write. It happens when the map's paths are removed and re-added while write protect is set: 1. Write protect is set. The map is reloaded read-only. uev_update_path() sets force_readonly for that reload and clears it afterwards. 2. All of the map's paths are removed, and new paths are added while write protect is still set. ev_add_path() sees a read-only path and sets force_readonly = 1. Nothing ever clears it. 3. Write protect is cleared on the new paths, one after another. Each path's DISK_RO=0 uevent is ignored while any path of the map is still read-only. 4. The last path's uevent reloads the map. The reload sees the stale force_readonly = 1 and loads the map read-only, logging "reloaded (retval 0)". force_readonly only works in one direction: ev_add_path() can set it but never clears it. If the new paths had been added already writable, ev_add_path() wouldn't have set the flag and the map wouldn't have stuck. The paths have to be added while write protected and then cleared in place. After step 4 nothing reloads the map again. A rescan doesn't change the paths' ro state, so it generates no DISK_RO uevent. And since multipathd now sees the map as read-only, needs_ro_update() also ignores the next DISK_RO=1 uevent for it. If two paths' uevents happen to be handled after all paths are writable, the map does recover: uev_update_path() clears force_readonly after its reload, so the second reload is read-write. That makes the problem look intermittent. Example ------- We hit this on RHEL 9 device-mapper-multipath 0.8.7, whose ev_add_path() and uev_update_path() handle force_readonly the same way as current master. The host's iSCSI sessions were torn down and re-established while the array was write protecting the volume. From the messages log, trimmed: 05:48:27 ...114d8: reload [0 51200 multipath 0 1 alua 1 1 service-time 0 6 1 135:272 1 66:624 1 69:720 1 129:560 1 132:656 1 135:752 1] 05:48:27 sdsd: map ...114d8 reloaded (retval 0) 05:51:04 kernel: sd 24:0:0:48: [sdaer] Write Protect is on 05:51:05 ...114d8: remaining active paths: 0 05:51:07 kernel: sd 29:0:0:48: [sdaoi] Write Protect is on (and four more new paths) 06:09:01 kernel: sd 24:0:0:48: [sdaer] Write Protect is off 06:09:07 kernel: sd 29:0:0:48: [sdaoi] Write Protect is off 06:09:10 sdaoi: update path write_protect to '0' (uevent) 06:09:10 ...114d8: reload [0 51200 multipath 0 1 alua 1 1 service-time 0 6 1 67:880 1 70:992 1 130:848 1 133:960 1 65:1072 1 67:1056 1] 06:09:10 sdaoi: map ...114d8 reloaded (retval 0) The state afterwards, as recorded by the test: dm-106: ro sdaer: rw sdagu: rw sdaix: rw sdala: rw sdand: rw sdaoi: rw The patch --------- The patch sets force_readonly right before each load from the state of the map's paths, and dm_addmap_create() and dm_addmap_reload() clear it as soon as they have read it. needs_ro_update() sets it for the reload done for a DISK_RO uevent, and ev_add_path() and update_map() compute it from all of the map's paths. With the patch, the 06:09:10 reload would have found no read-only path and loaded the map read-write. Changes since v2: - In needs_ro_update(), fetch the dm info with dm_get_info() if the map has none, and return false if that fails, instead of assuming a reload is needed. This also removes the separate path walk for that case. - Clear force_readonly in dm_addmap_create() and dm_addmap_reload() as soon as they have read it, instead of in dm_addmap() on success. A failed load can no longer leave the flag set for a later, unrelated reload, such as one for a path group change. needs_ro_update() no longer needs to clear the flag itself. - I didn't add per-path tracking of the ro state. force_readonly is only computed from sysfs right before a load, which is rare, so the cost of reading sysfs there is small. Per-path tracking could be a follow-up if you want it. Changes since v1: - Set force_readonly in needs_ro_update() when it returns true, instead of in uev_update_path(). The DISK_RO=1 case uses the uevent again, rather than rereading sysfs. - Clear force_readonly in dm_addmap() on success, as for need_reload, instead of after the loads in ev_add_path() and uev_update_path(). - In ev_add_path(), only compute force_readonly when need_do_map is set. - Walk mpp->pg rather than mpp->paths in needs_ro_update(), since mpp->paths may be stale there. ev_add_path() and update_map() still use mpp->paths: both call adopt_paths() first, and mpp->pg doesn't contain the new paths until setup_map(). - Move sysfs_get_ro() above has_ro_path() instead of adding a forward declaration. The function is unchanged. Brian Bunker (1): multipathd: don't leave maps read-only after write protect is cleared libmultipath/devmapper.c | 17 +++++-- multipathd/main.c | 104 +++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 46 deletions(-) -- 2.56.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 23:19 ` [PATCH v3 0/1] " Brian Bunker @ 2026-10-21 23:19 ` Brian Bunker 2026-10-22 20:17 ` Martin Wilck 0 siblings, 1 reply; 16+ messages in thread From: Brian Bunker @ 2026-10-21 23:19 UTC (permalink / raw) To: dm-devel; +Cc: Benjamin Marzinski, Martin Wilck, Christophe Varoqui, Brian Bunker ev_add_path() sets mpp->force_readonly when a newly added path is read-only, but never clears it. If a map is created or reloaded while its storage is write protected, the flag stays set. When write protect is cleared, the reload that uev_update_path() does for the DISK_RO=0 uevent uses the stale flag and loads the map read-only, even though all of its paths are read-write. uev_update_path() clears the flag after that reload, so the map only recovers if another DISK_RO=0 uevent reloads it again. If none comes, the map stays read-only, and since mpp->dmi.read_only is then 1, needs_ro_update() also ignores later DISK_RO=1 uevents for it. Fix this by setting force_readonly before each load and clearing it when the load reads it: - needs_ro_update() now sets force_readonly whenever it returns true: to 1 for a DISK_RO=1 uevent, and to 0 when all of the map's paths are writable. uev_update_path() no longer sets or clears it itself. If the map has no dm info, needs_ro_update() now fetches it, and returns false if that fails, instead of assuming a reload is needed. - ev_add_path() sets it from all of the map's paths rather than only the path being added, and only when it is about to load the map (need_do_map). mpp->paths is up to date there after adopt_paths(), while mpp->pg doesn't contain the new paths yet. - update_map(), which does reloads that were delayed while the map was waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's paths before setting up the map. - dm_addmap_create() and dm_addmap_reload() clear it as soon as they have read it, so it only applies to the load it was set for, even if that load fails. Otherwise a failed load would leave it set, and a later reload for an unrelated reason, such as a path group change, would load the map read-only. Signed-off-by: Brian Bunker <brian@purestorage.com> --- libmultipath/devmapper.c | 17 +++++-- multipathd/main.c | 104 +++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index e3870f59..c994715e 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -539,10 +539,15 @@ static uint16_t build_udev_flags(const struct multipath *mpp, int reload) int dm_addmap_create (struct multipath *mpp, char *params) { - int ro; + int ro = mpp->force_readonly ? 1 : 0; uint16_t udev_flags = build_udev_flags(mpp, 0); - for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { + /* + * force_readonly only applies to this load. Clear it, so that it + * can't affect later loads, even if this one fails. + */ + mpp->force_readonly = 0; + for (; ro <= 1; ro++) { int err; if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, params, ro, @@ -578,19 +583,23 @@ int dm_addmap_create (struct multipath *mpp, char *params) int dm_addmap_reload(struct multipath *mpp, char *params, int flush) { int r = 0; + int force_ro = mpp->force_readonly; uint16_t udev_flags = build_udev_flags(mpp, 1); + /* As in dm_addmap_create(), force_readonly only applies to this load */ + mpp->force_readonly = 0; + /* * DM_DEVICE_RELOAD cannot wait on a cookie, as * the cookie will only ever be released after an * DM_DEVICE_RESUME. So call DM_DEVICE_RESUME * after each successful call to DM_DEVICE_RELOAD. */ - if (!mpp->force_readonly) + if (!force_ro) r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, ADDMAP_RW, 0); if (!r) { - if (!mpp->force_readonly && errno != EROFS) + if (!force_ro && errno != EROFS) return 0; r = dm_addmap(DM_DEVICE_RELOAD, TGT_MPATH, mpp, params, ADDMAP_RO, 0); diff --git a/multipathd/main.c b/multipathd/main.c index 9bce4d6b..c5788ef9 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -709,6 +709,42 @@ void cleanup_reset_vec(struct vector_s **v) vector_reset(*v); } +static int +sysfs_get_ro (struct path *pp) +{ + int ro; + char buff[3]; /* Either "0\n\0" or "1\n\0" */ + + if (!pp->udev) + return -1; + + if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { + condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); + return -1; + } + + if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { + condlog(3, "%s: Cannot parse ro attribute", pp->dev); + return -1; + } + + return ro; +} + +/* A map must be loaded read-only if any of its paths is read-only. */ +static bool +has_ro_path(vector paths) +{ + struct path *pp; + unsigned int i; + + vector_foreach_slot (paths, pp, i) { + if (sysfs_get_ro(pp) == 1) + return true; + } + return false; +} + static int update_map (struct multipath *mpp, struct vectors *vecs, int new_map) { @@ -732,6 +768,14 @@ retry: mpp->action = ACT_RELOAD; + /* + * This may be a delayed reload, and the paths' write protect + * state may have changed since force_readonly was last set. + * Recompute it from the current paths. mpp->paths is up to date + * after adopt_paths(), while mpp->pg isn't until setup_map(). + */ + mpp->force_readonly = has_ro_path(mpp->paths); + if (setup_map(mpp, ¶ms, vecs)) { condlog(0, "%s: failed to setup new map in update", mpp->alias); retries = -1; @@ -1270,28 +1314,6 @@ out: return ret; } -static int -sysfs_get_ro (struct path *pp) -{ - int ro; - char buff[3]; /* Either "0\n\0" or "1\n\0" */ - - if (!pp->udev) - return -1; - - if (!sysfs_attr_get_value_ok(pp->udev, "ro", buff, sizeof(buff))) { - condlog(3, "%s: Cannot read ro attribute in sysfs", pp->dev); - return -1; - } - - if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) { - condlog(3, "%s: Cannot parse ro attribute", pp->dev); - return -1; - } - - return ro; -} - /* * returns: * 0: added @@ -1305,7 +1327,6 @@ ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map) int retries = 3; int start_waiter = 0; int ret; - int ro; unsigned char prflag = PR_UNSET; /* @@ -1370,14 +1391,16 @@ rescan: goto fail; /* leave path added to pathvec */ } - /* ro check - if new path is ro, force map to be ro as well */ - ro = sysfs_get_ro(pp); - if (ro == 1) - mpp->force_readonly = 1; - if (!need_do_map) return 0; + /* + * Load the map read-only if any of its paths is read-only, not + * just the one being added. mpp->paths is up to date after + * adopt_paths(), while mpp->pg doesn't have the new paths yet. + */ + mpp->force_readonly = has_ro_path(mpp->paths); + if (!dm_map_present(mpp->alias)) { mpp->action = ACT_CREATE; start_waiter = 1; @@ -1597,23 +1620,24 @@ static bool needs_ro_update(struct multipath *mpp, int ro) { struct pathgroup * pgp; - struct path * pp; - unsigned int i, j; + unsigned int i; if (!mpp || ro < 0) return false; - if (!has_dm_info(mpp)) - return true; + if (!has_dm_info(mpp) && + dm_get_info(mpp->alias, &mpp->dmi) != DMP_OK) { + condlog(2, "%s: failed to get dm info", mpp->alias); + return false; + } if (mpp->dmi.read_only == ro) return false; - if (ro == 1) - return true; - vector_foreach_slot (mpp->pg, pgp, i) { - vector_foreach_slot (pgp->paths, pp, j) { - if (sysfs_get_ro(pp) == 1) + if (ro == 0) { + vector_foreach_slot (mpp->pg, pgp, i) { + if (has_ro_path(pgp->paths)) return false; } } + mpp->force_readonly = ro; return true; } @@ -1732,15 +1756,11 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) if (mpp->wait_for_udev != UDEV_WAIT_DONE) mpp->wait_for_udev = UDEV_WAIT_RELOAD; else { - if (ro == 1) - pp->mpp->force_readonly = 1; retval = reload_and_sync_map(mpp, vecs); if (retval == 2) condlog(2, "%s: map removed during reload", pp->dev); - else { - pp->mpp->force_readonly = 0; + else condlog(2, "%s: map %s reloaded (retval %d)", uev->kernel, mpp->alias, retval); - } } } if (auto_resize != AUTO_RESIZE_NEVER && mpp && -- 2.56.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-21 23:19 ` [PATCH v3 1/1] " Brian Bunker @ 2026-10-22 20:17 ` Martin Wilck 2026-10-23 0:24 ` Benjamin Marzinski 0 siblings, 1 reply; 16+ messages in thread From: Martin Wilck @ 2026-10-22 20:17 UTC (permalink / raw) To: Brian Bunker Cc: dm-devel, Benjamin Marzinski, Martin Wilck, Christophe Varoqui > ev_add_path() sets mpp->force_readonly when a newly added path is > read-only, but never clears it. If a map is created or reloaded while > its storage is write protected, the flag stays set. When write protect > is cleared, the reload that uev_update_path() does for the DISK_RO=0 > uevent uses the stale flag and loads the map read-only, even though all > of its paths are read-write. uev_update_path() clears the flag after > that reload, so the map only recovers if another DISK_RO=0 uevent > reloads it again. If none comes, the map stays read-only, and since > mpp->dmi.read_only is then 1, needs_ro_update() also ignores later > DISK_RO=1 uevents for it. > > Fix this by setting force_readonly before each load and clearing it > when the load reads it: > > - needs_ro_update() now sets force_readonly whenever it returns true: > to 1 for a DISK_RO=1 uevent, and to 0 when all of the map's paths > are writable. uev_update_path() no longer sets or clears it itself. > If the map has no dm info, needs_ro_update() now fetches it, and > returns false if that fails, instead of assuming a reload is needed. > > - ev_add_path() sets it from all of the map's paths rather than only > the path being added, and only when it is about to load the map > (need_do_map). mpp->paths is up to date there after adopt_paths(), > while mpp->pg doesn't contain the new paths yet. > > - update_map(), which does reloads that were delayed while the map was > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's > paths before setting up the map. > > - dm_addmap_create() and dm_addmap_reload() clear it as soon as they > have read it, so it only applies to the load it was set for, even if > that load fails. Otherwise a failed load would leave it set, and a > later reload for an unrelated reason, such as a path group change, > would load the map read-only. > > Signed-off-by: Brian Bunker <brian@purestorage.com> Almost there. One remark below. > > diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c > index e3870f5..c994715 100644 > --- a/libmultipath/devmapper.c > +++ b/libmultipath/devmapper.c > @@ -539,10 +539,15 @@ static uint16_t build_udev_flags(const struct multipath *mpp, int reload) > > int dm_addmap_create (struct multipath *mpp, char *params) > { > - int ro; > + int ro = mpp->force_readonly ? 1 : 0; > uint16_t udev_flags = build_udev_flags(mpp, 0); > > - for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { > + /* > + * force_readonly only applies to this load. Clear it, so that it > + * can't affect later loads, even if this one fails. > + */ > + mpp->force_readonly = 0; > + for (; ro <= 1; ro++) { > int err; > > if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, params, ro, > @@ -578,19 +583,23 @@ int dm_addmap_create (struct multipath *mpp, char *params) > int dm_addmap_reload(struct multipath *mpp, char *params, int flush) > { > int r = 0; > + int force_ro = mpp->force_readonly; > uint16_t udev_flags = build_udev_flags(mpp, 1); > > + /* As in dm_addmap_create(), force_readonly only applies to this load */ > + mpp->force_readonly = 0; > + One minor issue here. If the reload below fails, the map is still read-write in the kernel. As we reset force_readonly, it will remain so, at least until we get another event with RO=1. If the RO reload fails with force_readonly=1, we could set force_readonly to 1 again after the failure. In needs_ro_update(), we'd then need to modify the fast return clause "if (mpp->dmi.read_only == ro)"; if both are 0 and force_readonly is still set, you'd need to check the paths and see if there's still a read-only path around. Martin -- Martin Wilck <mwilck@suse.com> ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-22 20:17 ` Martin Wilck @ 2026-10-23 0:24 ` Benjamin Marzinski 2026-10-23 20:50 ` Martin Wilck 0 siblings, 1 reply; 16+ messages in thread From: Benjamin Marzinski @ 2026-10-23 0:24 UTC (permalink / raw) To: Martin Wilck; +Cc: Brian Bunker, dm-devel, Martin Wilck, Christophe Varoqui On Thu, Oct 22, 2026 at 10:17:39PM +0200, Martin Wilck wrote: > > ev_add_path() sets mpp->force_readonly when a newly added path is > > read-only, but never clears it. If a map is created or reloaded while > > its storage is write protected, the flag stays set. When write protect > > is cleared, the reload that uev_update_path() does for the DISK_RO=0 > > uevent uses the stale flag and loads the map read-only, even though all > > of its paths are read-write. uev_update_path() clears the flag after > > that reload, so the map only recovers if another DISK_RO=0 uevent > > reloads it again. If none comes, the map stays read-only, and since > > mpp->dmi.read_only is then 1, needs_ro_update() also ignores later > > DISK_RO=1 uevents for it. > > > > Fix this by setting force_readonly before each load and clearing it > > when the load reads it: > > > > - needs_ro_update() now sets force_readonly whenever it returns true: > > to 1 for a DISK_RO=1 uevent, and to 0 when all of the map's paths > > are writable. uev_update_path() no longer sets or clears it itself. > > If the map has no dm info, needs_ro_update() now fetches it, and > > returns false if that fails, instead of assuming a reload is needed. > > > > - ev_add_path() sets it from all of the map's paths rather than only > > the path being added, and only when it is about to load the map > > (need_do_map). mpp->paths is up to date there after adopt_paths(), > > while mpp->pg doesn't contain the new paths yet. > > > > - update_map(), which does reloads that were delayed while the map was > > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the map's > > paths before setting up the map. > > > > - dm_addmap_create() and dm_addmap_reload() clear it as soon as they > > have read it, so it only applies to the load it was set for, even if > > that load fails. Otherwise a failed load would leave it set, and a > > later reload for an unrelated reason, such as a path group change, > > would load the map read-only. > > > > Signed-off-by: Brian Bunker <brian@purestorage.com> > > Almost there. One remark below. > > > > > diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c > > index e3870f5..c994715 100644 > > --- a/libmultipath/devmapper.c > > +++ b/libmultipath/devmapper.c > > @@ -539,10 +539,15 @@ static uint16_t build_udev_flags(const struct multipath *mpp, int reload) > > > > int dm_addmap_create (struct multipath *mpp, char *params) > > { > > - int ro; > > + int ro = mpp->force_readonly ? 1 : 0; > > uint16_t udev_flags = build_udev_flags(mpp, 0); > > > > - for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { > > + /* > > + * force_readonly only applies to this load. Clear it, so that it > > + * can't affect later loads, even if this one fails. > > + */ > > + mpp->force_readonly = 0; > > + for (; ro <= 1; ro++) { > > int err; > > > > if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, params, ro, > > @@ -578,19 +583,23 @@ int dm_addmap_create (struct multipath *mpp, char *params) > > int dm_addmap_reload(struct multipath *mpp, char *params, int flush) > > { > > int r = 0; > > + int force_ro = mpp->force_readonly; > > uint16_t udev_flags = build_udev_flags(mpp, 1); > > > > + /* As in dm_addmap_create(), force_readonly only applies to this load */ > > + mpp->force_readonly = 0; > > + > > One minor issue here. If the reload below fails, the map is still read-write > in the kernel. As we reset force_readonly, it will remain so, at least until > we get another event with RO=1. > > If the RO reload fails with force_readonly=1, we could set force_readonly to 1 again > after the failure. In needs_ro_update(), we'd then need to modify the fast > return clause "if (mpp->dmi.read_only == ro)"; if both are 0 and force_readonly is > still set, you'd need to check the paths and see if there's still a read-only path > around. I can actually see both sides of this argument. If the reload fails, and the next reload doesn't check the paths again, we really don't know what the correct setting is. This may be overthinking things, but we could make force_readonly a three state value, somthing like: MP_RELOAD_RO MP_RELOAD_RW MP_RELOAD_RECHECK If mpp->force_readonly was MP_RELOAD_RECHECK needs_ro_update() would always return true, and dm_addmap_create() and dm_addmap_reload() would check the paths themselves. Again, because these could be called in cases where we can't trust mpp->paths to be up to date, They should probably loop through the mpp->pg. You can make it always correct to use mpp->pg in has_ro_path() by moving the calls to has_ro_path() to after setup_map() is called. If that succeeds, that pathgroups have to be set up correctly. If we don't do something complicated, it might be better to always unset mpp->force_readonly like it patch currently does. It's an unlikely corner case and at worst you would get a readwrite mpath device where writes failed, because the device really should be read-only. That seems better that making a read-only device that should be read-write. Thoughts? -Ben > > Martin > > -- > Martin Wilck <mwilck@suse.com> ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-23 0:24 ` Benjamin Marzinski @ 2026-10-23 20:50 ` Martin Wilck 2026-10-23 23:40 ` Benjamin Marzinski 0 siblings, 1 reply; 16+ messages in thread From: Martin Wilck @ 2026-10-23 20:50 UTC (permalink / raw) To: Benjamin Marzinski, Brian Bunker; +Cc: dm-devel, Christophe Varoqui On Thu, 2026-10-22 at 20:24 -0400, Benjamin Marzinski wrote: > On Thu, Oct 22, 2026 at 10:17:39PM +0200, Martin Wilck wrote: > > > ev_add_path() sets mpp->force_readonly when a newly added path is > > > read-only, but never clears it. If a map is created or reloaded > > > while > > > its storage is write protected, the flag stays set. When write > > > protect > > > is cleared, the reload that uev_update_path() does for the > > > DISK_RO=0 > > > uevent uses the stale flag and loads the map read-only, even > > > though all > > > of its paths are read-write. uev_update_path() clears the flag > > > after > > > that reload, so the map only recovers if another DISK_RO=0 uevent > > > reloads it again. If none comes, the map stays read-only, and > > > since > > > mpp->dmi.read_only is then 1, needs_ro_update() also ignores > > > later > > > DISK_RO=1 uevents for it. > > > > > > Fix this by setting force_readonly before each load and clearing > > > it > > > when the load reads it: > > > > > > - needs_ro_update() now sets force_readonly whenever it returns > > > true: > > > to 1 for a DISK_RO=1 uevent, and to 0 when all of the map's > > > paths > > > are writable. uev_update_path() no longer sets or clears it > > > itself. > > > If the map has no dm info, needs_ro_update() now fetches it, > > > and > > > returns false if that fails, instead of assuming a reload is > > > needed. > > > > > > - ev_add_path() sets it from all of the map's paths rather than > > > only > > > the path being added, and only when it is about to load the map > > > (need_do_map). mpp->paths is up to date there after > > > adopt_paths(), > > > while mpp->pg doesn't contain the new paths yet. > > > > > > - update_map(), which does reloads that were delayed while the > > > map was > > > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the > > > map's > > > paths before setting up the map. > > > > > > - dm_addmap_create() and dm_addmap_reload() clear it as soon as > > > they > > > have read it, so it only applies to the load it was set for, > > > even if > > > that load fails. Otherwise a failed load would leave it set, > > > and a > > > later reload for an unrelated reason, such as a path group > > > change, > > > would load the map read-only. > > > > > > Signed-off-by: Brian Bunker <brian@purestorage.com> > > > > Almost there. One remark below. > > > > > > > > diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c > > > index e3870f5..c994715 100644 > > > --- a/libmultipath/devmapper.c > > > +++ b/libmultipath/devmapper.c > > > @@ -539,10 +539,15 @@ static uint16_t build_udev_flags(const > > > struct multipath *mpp, int reload) > > > > > > int dm_addmap_create (struct multipath *mpp, char *params) > > > { > > > - int ro; > > > + int ro = mpp->force_readonly ? 1 : 0; > > > uint16_t udev_flags = build_udev_flags(mpp, 0); > > > > > > - for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { > > > + /* > > > + * force_readonly only applies to this load. Clear it, > > > so that it > > > + * can't affect later loads, even if this one fails. > > > + */ > > > + mpp->force_readonly = 0; > > > + for (; ro <= 1; ro++) { > > > int err; > > > > > > if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, > > > params, ro, > > > @@ -578,19 +583,23 @@ int dm_addmap_create (struct multipath > > > *mpp, char *params) > > > int dm_addmap_reload(struct multipath *mpp, char *params, int > > > flush) > > > { > > > int r = 0; > > > + int force_ro = mpp->force_readonly; > > > uint16_t udev_flags = build_udev_flags(mpp, 1); > > > > > > + /* As in dm_addmap_create(), force_readonly only applies > > > to this load */ > > > + mpp->force_readonly = 0; > > > + > > > > One minor issue here. If the reload below fails, the map is still > > read-write > > in the kernel. As we reset force_readonly, it will remain so, at > > least until > > we get another event with RO=1. > > > > If the RO reload fails with force_readonly=1, we could set > > force_readonly to 1 again > > after the failure. In needs_ro_update(), we'd then need to modify > > the fast > > return clause "if (mpp->dmi.read_only == ro)"; if both are 0 and > > force_readonly is > > still set, you'd need to check the paths and see if there's still a > > read-only path > > around. > > I can actually see both sides of this argument. If the reload fails, > and > the next reload doesn't check the paths again, we really don't know > what > the correct setting is. > > This may be overthinking things, but we could make > force_readonly a three state value, somthing like: > > MP_RELOAD_RO > MP_RELOAD_RW > MP_RELOAD_RECHECK > > If mpp->force_readonly was MP_RELOAD_RECHECK needs_ro_update() would > always return true, and dm_addmap_create() and dm_addmap_reload() > would > check the paths themselves. Again, because these could be called in > cases where we can't trust mpp->paths to be up to date, They should > probably loop through the mpp->pg. You can make it always correct to > use > mpp->pg in has_ro_path() by moving the calls to has_ro_path() to > after > setup_map() is called. If that succeeds, that pathgroups have to be > set up > correctly. > > If we don't do something complicated, it might be better to always > unset > mpp->force_readonly like it patch currently does. It's an unlikely > corner case and at worst you would get a readwrite mpath device where > writes failed, because the device really should be read-only. I agree that it isn't a big issue if such writes fail. But if some paths are still read-write, writes might actually _succeed_ on them, Data corruption would be possible, as some writes would succeed an others wouldn't. Am I overlooking something, and this is actually impossible? I guess it's highly unlikely because in practice the ro/rw is toggled on the storage side, and happens per volume, i.e. for all paths simultaneously. But if that's true and writes-succeeding-although-they-shouldn't is not an issue, I wonder why we go through the hassle of setting the read- only attribute on our maps at all. We could just keep them read-write and trust that the writes (which shouldn't be attempted on read-only devices after all) will fail at lower layers. > That seems > better that making a read-only device that should be read-write. If this is consensus, Brian's v3 would actually be preferred to his v4. It isn't a religious matter to me, I agree that failing to reload read- only is a highly unlikely thing to happen. But I'd like to clarify the question above. I am not so enthusastic about the idea of the 3-valued force_readonly. If we want to do this right, we need to check on every reload. And IMO that means that we need to track the read-only state of the paths in multipathd. It's cheap to retrieve the "ro" attribute in sysfs_pathinfo() and track it. If it changes, we expect an uevent, where we check DISK_RO anyway. The logic could be similar as it is now, but we can check on each reload whether the map should be read-only, because the check would be just a cheap loop over the paths. And we wouldn't need force_readonly at all any more. Regards, Martin -- Dr. Martin Wilck <mwilck@suse.com> SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, Germany Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-23 20:50 ` Martin Wilck @ 2026-10-23 23:40 ` Benjamin Marzinski 2026-10-26 9:26 ` Martin Wilck 2026-10-26 22:20 ` Martin Wilck 0 siblings, 2 replies; 16+ messages in thread From: Benjamin Marzinski @ 2026-10-23 23:40 UTC (permalink / raw) To: Martin Wilck; +Cc: Brian Bunker, dm-devel, Christophe Varoqui On Fri, Oct 23, 2026 at 10:50:08PM +0200, Martin Wilck wrote: > On Thu, 2026-10-22 at 20:24 -0400, Benjamin Marzinski wrote: > > On Thu, Oct 22, 2026 at 10:17:39PM +0200, Martin Wilck wrote: > > > > ev_add_path() sets mpp->force_readonly when a newly added path is > > > > read-only, but never clears it. If a map is created or reloaded > > > > while > > > > its storage is write protected, the flag stays set. When write > > > > protect > > > > is cleared, the reload that uev_update_path() does for the > > > > DISK_RO=0 > > > > uevent uses the stale flag and loads the map read-only, even > > > > though all > > > > of its paths are read-write. uev_update_path() clears the flag > > > > after > > > > that reload, so the map only recovers if another DISK_RO=0 uevent > > > > reloads it again. If none comes, the map stays read-only, and > > > > since > > > > mpp->dmi.read_only is then 1, needs_ro_update() also ignores > > > > later > > > > DISK_RO=1 uevents for it. > > > > > > > > Fix this by setting force_readonly before each load and clearing > > > > it > > > > when the load reads it: > > > > > > > > - needs_ro_update() now sets force_readonly whenever it returns > > > > true: > > > > to 1 for a DISK_RO=1 uevent, and to 0 when all of the map's > > > > paths > > > > are writable. uev_update_path() no longer sets or clears it > > > > itself. > > > > If the map has no dm info, needs_ro_update() now fetches it, > > > > and > > > > returns false if that fails, instead of assuming a reload is > > > > needed. > > > > > > > > - ev_add_path() sets it from all of the map's paths rather than > > > > only > > > > the path being added, and only when it is about to load the map > > > > (need_do_map). mpp->paths is up to date there after > > > > adopt_paths(), > > > > while mpp->pg doesn't contain the new paths yet. > > > > > > > > - update_map(), which does reloads that were delayed while the > > > > map was > > > > waiting for udev (UDEV_WAIT_RELOAD), recomputes it from the > > > > map's > > > > paths before setting up the map. > > > > > > > > - dm_addmap_create() and dm_addmap_reload() clear it as soon as > > > > they > > > > have read it, so it only applies to the load it was set for, > > > > even if > > > > that load fails. Otherwise a failed load would leave it set, > > > > and a > > > > later reload for an unrelated reason, such as a path group > > > > change, > > > > would load the map read-only. > > > > > > > > Signed-off-by: Brian Bunker <brian@purestorage.com> > > > > > > Almost there. One remark below. > > > > > > > > > > > diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c > > > > index e3870f5..c994715 100644 > > > > --- a/libmultipath/devmapper.c > > > > +++ b/libmultipath/devmapper.c > > > > @@ -539,10 +539,15 @@ static uint16_t build_udev_flags(const > > > > struct multipath *mpp, int reload) > > > > > > > > int dm_addmap_create (struct multipath *mpp, char *params) > > > > { > > > > - int ro; > > > > + int ro = mpp->force_readonly ? 1 : 0; > > > > uint16_t udev_flags = build_udev_flags(mpp, 0); > > > > > > > > - for (ro = mpp->force_readonly ? 1 : 0; ro <= 1; ro++) { > > > > + /* > > > > + * force_readonly only applies to this load. Clear it, > > > > so that it > > > > + * can't affect later loads, even if this one fails. > > > > + */ > > > > + mpp->force_readonly = 0; > > > > + for (; ro <= 1; ro++) { > > > > int err; > > > > > > > > if (dm_addmap(DM_DEVICE_CREATE, TGT_MPATH, mpp, > > > > params, ro, > > > > @@ -578,19 +583,23 @@ int dm_addmap_create (struct multipath > > > > *mpp, char *params) > > > > int dm_addmap_reload(struct multipath *mpp, char *params, int > > > > flush) > > > > { > > > > int r = 0; > > > > + int force_ro = mpp->force_readonly; > > > > uint16_t udev_flags = build_udev_flags(mpp, 1); > > > > > > > > + /* As in dm_addmap_create(), force_readonly only applies > > > > to this load */ > > > > + mpp->force_readonly = 0; > > > > + > > > > > > One minor issue here. If the reload below fails, the map is still > > > read-write > > > in the kernel. As we reset force_readonly, it will remain so, at > > > least until > > > we get another event with RO=1. > > > > > > If the RO reload fails with force_readonly=1, we could set > > > force_readonly to 1 again > > > after the failure. In needs_ro_update(), we'd then need to modify > > > the fast > > > return clause "if (mpp->dmi.read_only == ro)"; if both are 0 and > > > force_readonly is > > > still set, you'd need to check the paths and see if there's still a > > > read-only path > > > around. > > > > I can actually see both sides of this argument. If the reload fails, > > and > > the next reload doesn't check the paths again, we really don't know > > what > > the correct setting is. > > > > This may be overthinking things, but we could make > > force_readonly a three state value, somthing like: > > > > MP_RELOAD_RO > > MP_RELOAD_RW > > MP_RELOAD_RECHECK > > > > If mpp->force_readonly was MP_RELOAD_RECHECK needs_ro_update() would > > always return true, and dm_addmap_create() and dm_addmap_reload() > > would > > check the paths themselves. Again, because these could be called in > > cases where we can't trust mpp->paths to be up to date, They should > > probably loop through the mpp->pg. You can make it always correct to > > use > > mpp->pg in has_ro_path() by moving the calls to has_ro_path() to > > after > > setup_map() is called. If that succeeds, that pathgroups have to be > > set up > > correctly. > > > > If we don't do something complicated, it might be better to always > > unset > > mpp->force_readonly like it patch currently does. It's an unlikely > > corner case and at worst you would get a readwrite mpath device where > > writes failed, because the device really should be read-only. > > I agree that it isn't a big issue if such writes fail. But if some > paths are still read-write, writes might actually _succeed_ on them, > Data corruption would be possible, as some writes would succeed an > others wouldn't. > > Am I overlooking something, and this is actually impossible? I guess > it's highly unlikely because in practice the ro/rw is toggled on the > storage side, and happens per volume, i.e. for all paths > simultaneously. Yeah. I don't think there really is such a thing as only one path being read-only. If you have a read/write dm device, all of the underlying devices must be opened read/write. The table won't load if they can't be opened with a matching mode. So the IO will go through to the scsi device, and I think it will fail on any path. > But if that's true and writes-succeeding-although-they-shouldn't is not > an issue, I wonder why we go through the hassle of setting the read- > only attribute on our maps at all. We could just keep them read-write > and trust that the writes (which shouldn't be attempted on read-only > devices after all) will fail at lower layers. Originally, dm would open the underlying devices anew whenever you loaded a new table. This caused problems with multipath, because sometimes failed device aren't openable when you try to reload the table. It used to cause multipath to be unable to reload the device table when it included failed paths in certain states. To fix that, if a device in a new inactive table already exists in the existing active table, opened in the same mode as the new table wants, dm no longer opens the device again. It just shares the opened device between the two tables. However this caused a problem with multipath's ability to automatically switch to read-only when a path switched to read-only. When dm still opened the devices per table, the read-write load of the table would fail, because it couldn't open all the path devices read-write. However now that dm shares the path devices between tables, multipath devices would never switch to read-only if they were previously loaded read-write, as long as you weren't adding a new path device in the new table. All the existing devices were already opened read-write, and the first time multipath tries to reload the table it tries read-write. Since it just shared the devices instead of opening them again, this would succeed, regardless of whether or not the devices really were read-write. To solve this, whenever multipath detects that a device has switched to read-only, it forces a read-only table reload, so that all the path devices get re-opened read-only (since their requested mode doesn't match their current mode). After that, the regular reload code will work, since it will have to re-open all the paths read-write for the read-write multipath table load-to succeed (again, since the requested mode changed). Actually, this makes me realize that there's no reason to care about force_readonly in dm_addmap_create(). > > That seems > > better that making a read-only device that should be read-write. I do still waver a bit on whether or not we could leave force_readonly set on failed reloads. Once a device turns read-only, it should stay that way until we get another uevent, at which point we will call needs_ro_update() again. Perhaps we just need to make sure that we clear force_readonly when needs_ro_update() returns false. In that case, it seems like the only way we would do the wrong thing is if we somehow missed all the uevents for setting the path devices back to read-write. Thoughts? I'm clearly overthinking this. -Ben > If this is consensus, Brian's v3 would actually be preferred to his v4. > It isn't a religious matter to me, I agree that failing to reload read- > only is a highly unlikely thing to happen. But I'd like to clarify the > question above. > > I am not so enthusastic about the idea of the 3-valued force_readonly. > > If we want to do this right, we need to check on every reload. And IMO > that means that we need to track the read-only state of the paths in > multipathd. It's cheap to retrieve the "ro" attribute in > sysfs_pathinfo() and track it. If it changes, we expect an uevent, > where we check DISK_RO anyway. The logic could be similar as it is now, > but we can check on each reload whether the map should be read-only, > because the check would be just a cheap loop over the paths. And we > wouldn't need force_readonly at all any more. > > Regards, > Martin > > -- > Dr. Martin Wilck <mwilck@suse.com> > SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, > Germany > Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB > 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-23 23:40 ` Benjamin Marzinski @ 2026-10-26 9:26 ` Martin Wilck 2026-10-26 18:04 ` Brian Bunker 2026-10-26 22:20 ` Martin Wilck 1 sibling, 1 reply; 16+ messages in thread From: Martin Wilck @ 2026-10-26 9:26 UTC (permalink / raw) To: Benjamin Marzinski, Brian Bunker; +Cc: dm-devel, Christophe Varoqui On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > I do still waver a bit on whether or not we could leave > force_readonly > set on failed reloads. Once a device turns read-only, it should stay > that way until we get another uevent, at which point we will call > needs_ro_update() again. Perhaps we just need to make sure that we > clear > force_readonly when needs_ro_update() returns false. AFAICS, this is exactly what Brian's v4 patch does. No? Martin -- Dr. Martin Wilck <mwilck@suse.com> SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, Germany Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-26 9:26 ` Martin Wilck @ 2026-10-26 18:04 ` Brian Bunker 2026-10-26 22:23 ` Martin Wilck 0 siblings, 1 reply; 16+ messages in thread From: Brian Bunker @ 2026-10-26 18:04 UTC (permalink / raw) To: Martin Wilck, Benjamin Marzinski Cc: Brian Bunker, dm-devel, Christophe Varoqui On Mon, 2026-10-26 at 10:26 +0100, Martin Wilck wrote: > On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > > > I do still waver a bit on whether or not we could leave > > force_readonly > > set on failed reloads. Once a device turns read-only, it should stay > > that way until we get another uevent, at which point we will call > > needs_ro_update() again. Perhaps we just need to make sure that we > > clear > > force_readonly when needs_ro_update() returns false. > > AFAICS, this is exactly what Brian's v4 patch does. No? Mostly, yes. v4 sets force_readonly back in dm_addmap_reload() when a reload fails, so the next reload tries read-only again. needs_ro_update() doesn't clear it on every false return, though. It clears it in the dmi.read_only == ro case only when both the map and the uevent are read-write and none of the map's paths is read-only. If a path is still read-only, the flag stays set, so a map whose read-only reload failed keeps retrying instead of staying read-write. About dm_addmap_create(): agreed that the flag isn't needed for correctness there, since a new map opens all paths fresh and a read-write create fails with EROFS anyway. It only saves that first attempt. Create still clears the flag, so that a value set by ev_add_path() can't carry over to a later reload. I can drop the read-only shortcut in create if you'd prefer. Uday Shankar, who did 430f8f89, also suggested dropping force_readonly altogether, and tracking the ro state per path in sysfs_pathinfo() sounds like the right direction to me. But it changes behavior: every reload, not just the one done for a DISK_RO uevent, would follow the paths' ro state, and so would "multipath -r", since dm_addmap_reload() is in libmultipath. So I'd rather do it as a separate series on top of this fix, and keep this one small enough to backport. Brian ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-26 18:04 ` Brian Bunker @ 2026-10-26 22:23 ` Martin Wilck 2026-10-27 4:29 ` Benjamin Marzinski 0 siblings, 1 reply; 16+ messages in thread From: Martin Wilck @ 2026-10-26 22:23 UTC (permalink / raw) To: Brian Bunker, Benjamin Marzinski; +Cc: dm-devel, Christophe Varoqui On Mon, 2026-10-26 at 11:04 -0700, Brian Bunker wrote: > On Mon, 2026-10-26 at 10:26 +0100, Martin Wilck wrote: > > On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > > > > > I do still waver a bit on whether or not we could leave > > > force_readonly > > > set on failed reloads. Once a device turns read-only, it should > > > stay > > > that way until we get another uevent, at which point we will call > > > needs_ro_update() again. Perhaps we just need to make sure that > > > we > > > clear > > > force_readonly when needs_ro_update() returns false. > > > > AFAICS, this is exactly what Brian's v4 patch does. No? > > Mostly, yes. v4 sets force_readonly back in dm_addmap_reload() when a > reload fails, so the next reload tries read-only again. > > needs_ro_update() doesn't clear it on every false return, though. It > clears it in the dmi.read_only == ro case only when both the map and > the uevent are read-write and none of the map's paths is read-only. > If > a path is still read-only, the flag stays set, so a map whose read- > only > reload failed keeps retrying instead of staying read-write. Which is correct. If the map is read-write but there are ro paths, we need to retry. There are two other cases where needs_ro_update() returns false: 1. ro == 1 && dmi.read_only == 1 I don't see how force_readonly could be one in this case. But maybe we should just reset it anyway, just to be sure. 2. ro == 0 && dmi.read_only == 1 && map_has_ro_path() Same here, read_only is already set, no need to force it, but resetting force_read_only wouldn't hurt. > About dm_addmap_create(): agreed that the flag isn't needed for > correctness there, since a new map opens all paths fresh and a > read-write create fails with EROFS anyway. It only saves that first > attempt. Create still clears the flag, so that a value set by > ev_add_path() can't carry over to a later reload. I can drop the > read-only shortcut in create if you'd prefer. I am uncertain about this. If we know in advance that trying to set up the map read-write will fail, how much sense does it make to try? It's just a minimal simplification of our code. > Uday Shankar, who did 430f8f89, also suggested dropping > force_readonly > altogether, > and tracking the ro state per path in sysfs_pathinfo() > sounds like the right direction to me. But it changes behavior: every > reload, not just the one done for a DISK_RO uevent, would follow the > paths' ro state, and so would "multipath -r", since > dm_addmap_reload() > is in libmultipath. So I'd rather do it as a separate series on top > of > this fix, and keep this one small enough to backport. Sounds good to me, even if it may mean that this particular patch will be short-lived. Regards Martin -- Dr. Martin Wilck <mwilck@suse.com> SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, Germany Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-26 22:23 ` Martin Wilck @ 2026-10-27 4:29 ` Benjamin Marzinski 0 siblings, 0 replies; 16+ messages in thread From: Benjamin Marzinski @ 2026-10-27 4:29 UTC (permalink / raw) To: Martin Wilck; +Cc: Brian Bunker, dm-devel, Christophe Varoqui On Mon, Oct 26, 2026 at 11:23:09PM +0100, Martin Wilck wrote: > On Mon, 2026-10-26 at 11:04 -0700, Brian Bunker wrote: > > On Mon, 2026-10-26 at 10:26 +0100, Martin Wilck wrote: > > > On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > > > > > > > I do still waver a bit on whether or not we could leave > > > > force_readonly > > > > set on failed reloads. Once a device turns read-only, it should > > > > stay > > > > that way until we get another uevent, at which point we will call > > > > needs_ro_update() again. Perhaps we just need to make sure that > > > > we > > > > clear > > > > force_readonly when needs_ro_update() returns false. > > > > > > AFAICS, this is exactly what Brian's v4 patch does. No? > > > > Mostly, yes. v4 sets force_readonly back in dm_addmap_reload() when a > > reload fails, so the next reload tries read-only again. > > > > needs_ro_update() doesn't clear it on every false return, though. It > > clears it in the dmi.read_only == ro case only when both the map and > > the uevent are read-write and none of the map's paths is read-only. > > If > > a path is still read-only, the flag stays set, so a map whose read- > > only > > reload failed keeps retrying instead of staying read-write. > > Which is correct. If the map is read-write but there are ro paths, we > need to retry. > > There are two other cases where needs_ro_update() returns false: > > 1. ro == 1 && dmi.read_only == 1 > I don't see how force_readonly could be one in this case. > But maybe we should just reset it anyway, just to be sure. > 2. ro == 0 && dmi.read_only == 1 && map_has_ro_path() > Same here, read_only is already set, no need to force it, > but resetting force_read_only wouldn't hurt. > > > About dm_addmap_create(): agreed that the flag isn't needed for > > correctness there, since a new map opens all paths fresh and a > > read-write create fails with EROFS anyway. It only saves that first > > attempt. Create still clears the flag, so that a value set by > > ev_add_path() can't carry over to a later reload. I can drop the > > read-only shortcut in create if you'd prefer. > > I am uncertain about this. If we know in advance that trying to set up > the map read-write will fail, how much sense does it make to try? It's > just a minimal simplification of our code. Also, looking at it now, I'm kind of dubious about 430f8f89 ("multipath-tools: update mpp->force_readonly in ev_add_path"), but it does appear that there was an issue where multipath over NVMe should not attempt to create the device read-write, if it had read-only paths. My problem with the patch is that if that's true, I don't see why a regular reload wouldn't just cause the device to switch to read-write again. But I agree with Martin. There's much to be gained by pulling out the dm_addmap_create() code. > > Uday Shankar, who did 430f8f89, also suggested dropping > > force_readonly > > altogether, > > and tracking the ro state per path in sysfs_pathinfo() > > sounds like the right direction to me. But it changes behavior: every > > reload, not just the one done for a DISK_RO uevent, would follow the > > paths' ro state, and so would "multipath -r", since > > dm_addmap_reload() > > is in libmultipath. So I'd rather do it as a separate series on top > > of > > this fix, and keep this one small enough to backport. > > Sounds good to me, even if it may mean that this particular patch > will be short-lived. > > Regards > Martin > > -- > Dr. Martin Wilck <mwilck@suse.com> > SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, > Germany > Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB > 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-23 23:40 ` Benjamin Marzinski 2026-10-26 9:26 ` Martin Wilck @ 2026-10-26 22:20 ` Martin Wilck 2026-10-27 4:52 ` Benjamin Marzinski 1 sibling, 1 reply; 16+ messages in thread From: Martin Wilck @ 2026-10-26 22:20 UTC (permalink / raw) To: Benjamin Marzinski; +Cc: Brian Bunker, dm-devel, Christophe Varoqui On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > To solve this, whenever multipath detects that a device has switched > to > read-only, it forces a read-only table reload, so that all the path > devices get re-opened read-only (since their requested mode doesn't > match their current mode). I am sorry, I guess I am just looking in the wrong place. But the way I read the code in dm_get_device() [1], the kernel will re-open a device only if the mode is upgraded, i.e. switched from ro to rw. [1] https://elixir.bootlin.com/linux/v7.3/source/drivers/md/dm-table.c#L398 I don't see how a switch from rw to ro would cause a device to be reopened. Thanks Martin -- Dr. Martin Wilck <mwilck@suse.com> SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, Germany Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] multipathd: don't leave maps read-only after write protect is cleared 2026-10-26 22:20 ` Martin Wilck @ 2026-10-27 4:52 ` Benjamin Marzinski 0 siblings, 0 replies; 16+ messages in thread From: Benjamin Marzinski @ 2026-10-27 4:52 UTC (permalink / raw) To: Martin Wilck; +Cc: Brian Bunker, dm-devel, Christophe Varoqui On Mon, Oct 26, 2026 at 11:20:35PM +0100, Martin Wilck wrote: > On Fri, 2026-10-23 at 19:40 -0400, Benjamin Marzinski wrote: > > > > To solve this, whenever multipath detects that a device has switched > > to > > read-only, it forces a read-only table reload, so that all the path > > devices get re-opened read-only (since their requested mode doesn't > > match their current mode). > > I am sorry, I guess I am just looking in the wrong place. But the way I > read the code in dm_get_device() [1], the kernel will re-open a device > only if the mode is upgraded, i.e. switched from ro to rw. > > [1] https://elixir.bootlin.com/linux/v7.3/source/drivers/md/dm-table.c#L398 > > I don't see how a switch from rw to ro would cause a device to be > reopened. That only happens if find_device() already found that device in THIS table (it's called with t->devices). For instance, the dm-delay table can list the same device three times. Instead of opening the device 3 times, you just open it once. Additionally, if you have already opened the device in the current table in a mode that includes the capabilites of the mode you need in a future call to dm_get_device() it will just use the existing open. If you need more capabilites, you will upgrade the open. What matters in the multipath case is if the device isn't found in the existing table. In that case you call dm_get_table_device(), which calls find_table_device(). This looks for a device in the md->table_devices list, which includes devices from all tables. But in this case, the mode must match exactly https://elixir.bootlin.com/linux/v7.3/source/drivers/md/dm.c#L807 Thinking about it now, I'm actually not sure if that upgrading from dm_get_device() is a good thing. If a target asks to open multiple copies of the same device with different modes, and dm gives it one open that has a union of all the modes, it seems like we've dropped some safety guardrails. I'm not sure off the top of my head if there actually is a target that does this. -Ben > > Thanks > Martin > > -- > Dr. Martin Wilck <mwilck@suse.com> > SUSE Software Solutions Germany GmbH, Frankenstr. 146, 90461 Nürnberg, > Germany > Geschäftsführer: Stefan Gaiser, Jochen Jaser, Abhinav Puri (HRB > 36809,AG Nürnberg) ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-10-27 4:52 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-21 18:27 [PATCH v2 0/1] multipathd: don't leave maps read-only after write protect is cleared Brian Bunker 2026-10-21 18:27 ` [PATCH v2 1/1] " Brian Bunker 2026-10-21 19:50 ` Benjamin Marzinski 2026-10-21 21:34 ` mwilck 2026-10-21 23:19 ` [PATCH v3 0/1] " Brian Bunker 2026-10-21 23:19 ` [PATCH v3 1/1] " Brian Bunker 2026-10-22 20:17 ` Martin Wilck 2026-10-23 0:24 ` Benjamin Marzinski 2026-10-23 20:50 ` Martin Wilck 2026-10-23 23:40 ` Benjamin Marzinski 2026-10-26 9:26 ` Martin Wilck 2026-10-26 18:04 ` Brian Bunker 2026-10-26 22:23 ` Martin Wilck 2026-10-27 4:29 ` Benjamin Marzinski 2026-10-26 22:20 ` Martin Wilck 2026-10-27 4:52 ` Benjamin Marzinski
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.