diff mbox series

multipathd: avoid unnecessary path read-only reloads

Message ID 1637275667-13436-1-git-send-email-bmarzins@redhat.com (mailing list archive)
State Not Applicable, archived
Delegated to: christophe varoqui
Headers show
Series multipathd: avoid unnecessary path read-only reloads | expand

Commit Message

Benjamin Marzinski Nov. 18, 2021, 10:47 p.m. UTC
A mulitpath device can only be reloaded read/write when all paths are
read/write. Also, whenever a read-only device is rescanned, the scsi
subsystem will first unconditionally issue a uevent with DISK_RO=0
before checking the read-only status, and if it the device is still
read-only, issuing another uevent with DISK_RO=1. These uevents cause
pointless reloads when read-only paths are rescanned. To avoid this,
check to see if all paths are read/write before changing a multipath
device from read-only to read/write.

Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
 libmultipath/libmultipath.version |  5 +++++
 libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
 libmultipath/sysfs.h              |  1 +
 multipathd/main.c                 | 31 ++++++++++++++++++++++++++++++-
 4 files changed, 58 insertions(+), 1 deletion(-)

Comments

Martin Wilck Nov. 19, 2021, 9:33 p.m. UTC | #1
On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> A mulitpath device can only be reloaded read/write when all paths are
> read/write. Also, whenever a read-only device is rescanned, the scsi
> subsystem will first unconditionally issue a uevent with DISK_RO=0
> before checking the read-only status, and if it the device is still
> read-only, issuing another uevent with DISK_RO=1. These uevents cause
> pointless reloads when read-only paths are rescanned. To avoid this,
> check to see if all paths are read/write before changing a multipath
> device from read-only to read/write.
> 
> Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> ---
>  libmultipath/libmultipath.version |  5 +++++
>  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
>  libmultipath/sysfs.h              |  1 +
>  multipathd/main.c                 | 31
> ++++++++++++++++++++++++++++++-
>  4 files changed, 58 insertions(+), 1 deletion(-)
> 
> diff --git a/libmultipath/libmultipath.version
> b/libmultipath/libmultipath.version
> index 58a7d1be..ab4c7e30 100644
> --- a/multipathd/main.c
> +++ b/multipathd/main.c
> @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> vectors * vecs)
>         return -1;
>  }
>  
> +static bool
> +needs_ro_update(struct multipath *mpp, int ro)
> +{
> +       struct pathgroup * pgp;
> +       struct path * pp;
> +       unsigned int i, j;
> +       struct dm_info *dmi = NULL;
> +
> +       if (!mpp || ro < 0)
> +               return false;
> +       dm_get_info(mpp->alias, &dmi);

Why can't you just use mpp->dmi here?

> +       if (!dmi) /* assume we do need to reload the device */
> +               return true;

Why that? I'd assume that if a DM_DEVICE_INFO ioctl fails on the
device, a RELOAD would almost certainly fail, too.

> +       if (dmi->read_only == ro) {
> +               free(dmi);
> +               return false;
> +       }
> +       free(dmi);
> +       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)
> +                               return false;

I think you should also return false here if sysfs_get_ro() returns
error.

Regards,
Martin

> +               }
> +       }
> +       return true;
> +}
> +
>  static int
>  uev_update_path (struct uevent *uev, struct vectors * vecs)
>  {
> @@ -1512,7 +1541,7 @@ uev_update_path (struct uevent *uev, struct
> vectors * vecs)
>                 }
>  
>                 ro = uevent_get_disk_ro(uev);
> -               if (mpp && ro >= 0) {
> +               if (needs_ro_update(mpp, ro)) {
>                         condlog(2, "%s: update path write_protect to
> '%d' (uevent)", uev->kernel, ro);
>  
>                         if (mpp->wait_for_udev)


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Benjamin Marzinski Nov. 22, 2021, 3:35 p.m. UTC | #2
On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > A mulitpath device can only be reloaded read/write when all paths are
> > read/write. Also, whenever a read-only device is rescanned, the scsi
> > subsystem will first unconditionally issue a uevent with DISK_RO=0
> > before checking the read-only status, and if it the device is still
> > read-only, issuing another uevent with DISK_RO=1. These uevents cause
> > pointless rereads when read-only paths are rescanned. To avoid this,
> > check to see if all paths are read/write before changing a multipath
> > device from read-only to read/write.
> > 
> > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > ---
> >  libmultipath/libmultipath.version |  5 +++++
> >  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
> >  libmultipath/sysfs.h              |  1 +
> >  multipathd/main.c                 | 31
> > ++++++++++++++++++++++++++++++-
> >  4 files changed, 58 insertions(+), 1 deletion(-)
> > 
> > diff --git a/libmultipath/libmultipath.version
> > b/libmultipath/libmultipath.version
> > index 58a7d1be..ab4c7e30 100644
> > --- a/multipathd/main.c
> > +++ b/multipathd/main.c
> > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> > vectors * vecs)
> >         return -1;
> >  }
> >  
> > +static bool
> > +needs_ro_update(struct multipath *mpp, int ro)
> > +{
> > +       struct pathgroup * pgp;
> > +       struct path * pp;
> > +       unsigned int i, j;
> > +       struct dm_info *dmi = NULL;
> > +
> > +       if (!mpp || ro < 0)
> > +               return false;
> > +       dm_get_info(mpp->alias, &dmi);
> 
> Why can't you just use mpp->dmi here?

Since that value is set when the dmi is originally created, I didn't
want to not reload a map, if we simply haven't updated it yet to reflect
a change in the read-only value, like with do with dm_is_suspended()
or dm_get_deferred_remove(), etc. I could make a dm_get_read_only()
function and put it libmultipath/devmapper.c like the others, if you'd
rather.
 
> > +       if (!dmi) /* assume we do need to reload the device */
> > +               return true;
>
> Why that? I'd assume that if a DM_DEVICE_INFO ioctl fails on the
> device, a RELOAD would almost certainly fail, too.
> 

Since reloading when it's not necessary doesn't do any harm (it's what
we currently do) while not switching to read/write when we should is a
problem, I thought that I'd error on the side of caution, but I agree
that the reload is unlikey to succeed, so I can change this if you'd
like.

> > +       if (dmi->read_only == ro) {
> > +               free(dmi);
> > +               return false;
> > +       }
> > +       free(dmi);
> > +       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)
> > +                               return false;
> 
> I think you should also return false here if sysfs_get_ro() returns
> error.

Same thing here. I was erroring on the side of caution, but it should be
fine to change.

-Ben

 
> Regards,
> Martin
> 
> > +               }
> > +       }
> > +       return true;
> > +}
> > +
> >  static int
> >  uev_update_path (struct uevent *uev, struct vectors * vecs)
> >  {
> > @@ -1512,7 +1541,7 @@ uev_update_path (struct uevent *uev, struct
> > vectors * vecs)
> >                 }
> >  
> >                 ro = uevent_get_disk_ro(uev);
> > -               if (mpp && ro >= 0) {
> > +               if (needs_ro_update(mpp, ro)) {
> >                         condlog(2, "%s: update path write_protect to
> > '%d' (uevent)", uev->kernel, ro);
> >  
> >                         if (mpp->wait_for_udev)

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Nov. 22, 2021, 4:48 p.m. UTC | #3
On Mon, 2021-11-22 at 09:35 -0600, Benjamin Marzinski wrote:
> On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > A mulitpath device can only be reloaded read/write when all paths
> > > are
> > > read/write. Also, whenever a read-only device is rescanned, the
> > > scsi
> > > subsystem will first unconditionally issue a uevent with
> > > DISK_RO=0
> > > before checking the read-only status, and if it the device is
> > > still
> > > read-only, issuing another uevent with DISK_RO=1. These uevents
> > > cause
> > > pointless rereads when read-only paths are rescanned. To avoid
> > > this,
> > > check to see if all paths are read/write before changing a
> > > multipath
> > > device from read-only to read/write.
> > > 
> > > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > > ---
> > >  libmultipath/libmultipath.version |  5 +++++
> > >  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
> > >  libmultipath/sysfs.h              |  1 +
> > >  multipathd/main.c                 | 31
> > > ++++++++++++++++++++++++++++++-
> > >  4 files changed, 58 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/libmultipath/libmultipath.version
> > > b/libmultipath/libmultipath.version
> > > index 58a7d1be..ab4c7e30 100644
> > > --- a/multipathd/main.c
> > > +++ b/multipathd/main.c
> > > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> > > vectors * vecs)
> > >         return -1;
> > >  }
> > >  
> > > +static bool
> > > +needs_ro_update(struct multipath *mpp, int ro)
> > > +{
> > > +       struct pathgroup * pgp;
> > > +       struct path * pp;
> > > +       unsigned int i, j;
> > > +       struct dm_info *dmi = NULL;
> > > +
> > > +       if (!mpp || ro < 0)
> > > +               return false;
> > > +       dm_get_info(mpp->alias, &dmi);
> > 
> > Why can't you just use mpp->dmi here?
> 
> Since that value is set when the dmi is originally created, I didn't
> want to not reload a map, if we simply haven't updated it yet to
> reflect
> a change in the read-only value, like with do with dm_is_suspended()
> or dm_get_deferred_remove(), etc. I could make a dm_get_read_only()
> function and put it libmultipath/devmapper.c like the others, if
> you'd
> rather.

I had expected that this property wouldn't silently change under us.
Actually, I do think that we should get an uevent if this happens.
Not sure if we process it properly, though.

>  
> > > +       if (!dmi) /* assume we do need to reload the device */
> > > +               return true;
> > 
> > Why that? I'd assume that if a DM_DEVICE_INFO ioctl fails on the
> > device, a RELOAD would almost certainly fail, too.
> > 
> 
> Since reloading when it's not necessary doesn't do any harm (it's
> what
> we currently do) while not switching to read/write when we should is
> a
> problem, I thought that I'd error on the side of caution, but I agree
> that the reload is unlikey to succeed, so I can change this if you'd
> like.

I misunderstood what being "cautious" means in this context. I gather
now that it's "if in doubt, reload". Fine with me.

> 
> > > +       if (dmi->read_only == ro) {
> > > +               free(dmi);
> > > +               return false;
> > > +       }
> > > +       free(dmi);
> > > +       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)
> > > +                               return false;
> > 
> > I think you should also return false here if sysfs_get_ro() returns
> > error.
> 
> Same thing here. I was erroring on the side of caution, but it should
> be
> fine to change.

Same misunderstanding on my part. Thanks for the clarification.

Regards
Martin

> 
> -Ben
> 
>  
> > Regards,
> > Martin
> > 
> > > +               }
> > > +       }
> > > +       return true;
> > > +}
> > > +
> > >  static int
> > >  uev_update_path (struct uevent *uev, struct vectors * vecs)
> > >  {
> > > @@ -1512,7 +1541,7 @@ uev_update_path (struct uevent *uev, struct
> > > vectors * vecs)
> > >                 }
> > >  
> > >                 ro = uevent_get_disk_ro(uev);
> > > -               if (mpp && ro >= 0) {
> > > +               if (needs_ro_update(mpp, ro)) {
> > >                         condlog(2, "%s: update path write_protect
> > > to
> > > '%d' (uevent)", uev->kernel, ro);
> > >  
> > >                         if (mpp->wait_for_udev)
> 


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Benjamin Marzinski Nov. 22, 2021, 5:43 p.m. UTC | #4
On Mon, Nov 22, 2021 at 04:48:06PM +0000, Martin Wilck wrote:
> On Mon, 2021-11-22 at 09:35 -0600, Benjamin Marzinski wrote:
> > On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> > > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > > A mulitpath device can only be reloaded read/write when all paths
> > > > are
> > > > read/write. Also, whenever a read-only device is rescanned, the
> > > > scsi
> > > > subsystem will first unconditionally issue a uevent with
> > > > DISK_RO=0
> > > > before checking the read-only status, and if it the device is
> > > > still
> > > > read-only, issuing another uevent with DISK_RO=1. These uevents
> > > > cause
> > > > pointless rereads when read-only paths are rescanned. To avoid
> > > > this,
> > > > check to see if all paths are read/write before changing a
> > > > multipath
> > > > device from read-only to read/write.
> > > > 
> > > > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > > > ---
> > > >  libmultipath/libmultipath.version |  5 +++++
> > > >  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
> > > >  libmultipath/sysfs.h              |  1 +
> > > >  multipathd/main.c                 | 31
> > > > ++++++++++++++++++++++++++++++-
> > > >  4 files changed, 58 insertions(+), 1 deletion(-)
> > > > 
> > > > diff --git a/libmultipath/libmultipath.version
> > > > b/libmultipath/libmultipath.version
> > > > index 58a7d1be..ab4c7e30 100644
> > > > --- a/multipathd/main.c
> > > > +++ b/multipathd/main.c
> > > > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> > > > vectors * vecs)
> > > >         return -1;
> > > >  }
> > > >  
> > > > +static bool
> > > > +needs_ro_update(struct multipath *mpp, int ro)
> > > > +{
> > > > +       struct pathgroup * pgp;
> > > > +       struct path * pp;
> > > > +       unsigned int i, j;
> > > > +       struct dm_info *dmi = NULL;
> > > > +
> > > > +       if (!mpp || ro < 0)
> > > > +               return false;
> > > > +       dm_get_info(mpp->alias, &dmi);
> > > 
> > > Why can't you just use mpp->dmi here?
> > 
> > Since that value is set when the dmi is originally created, I didn't
> > want to not reload a map, if we simply haven't updated it yet to
> > reflect
> > a change in the read-only value, like with do with dm_is_suspended()
> > or dm_get_deferred_remove(), etc. I could make a dm_get_read_only()
> > function and put it libmultipath/devmapper.c like the others, if
> > you'd
> > rather.
> 
> I had expected that this property wouldn't silently change under us.
> Actually, I do think that we should get an uevent if this happens.
> Not sure if we process it properly, though.

I think we will update the dmi, but I'm not sure that these uevents
won't race. The worry was that the device would switch to read-only and
then back too quickly, and we would get this event and still see the
device in read/write because we haven't processed the event which would
update the multipath dmi.

> >  
> > > > +       if (!dmi) /* assume we do need to reload the device */
> > > > +               return true;
> > > 
> > > Why that? I'd assume that if a DM_DEVICE_INFO ioctl fails on the
> > > device, a RELOAD would almost certainly fail, too.
> > > 
> > 
> > Since reloading when it's not necessary doesn't do any harm (it's
> > what
> > we currently do) while not switching to read/write when we should is
> > a
> > problem, I thought that I'd error on the side of caution, but I agree
> > that the reload is unlikey to succeed, so I can change this if you'd
> > like.
> 
> I misunderstood what being "cautious" means in this context. I gather
> now that it's "if in doubt, reload". Fine with me.
> 
> > 
> > > > +       if (dmi->read_only == ro) {
> > > > +               free(dmi);
> > > > +               return false;
> > > > +       }
> > > > +       free(dmi);
> > > > +       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)
> > > > +                               return false;
> > > 
> > > I think you should also return false here if sysfs_get_ro() returns
> > > error.
> > 
> > Same thing here. I was erroring on the side of caution, but it should
> > be
> > fine to change.
> 
> Same misunderstanding on my part. Thanks for the clarification.
> 
> Regards
> Martin
> 
> > 
> > -Ben
> > 
> >  
> > > Regards,
> > > Martin
> > > 
> > > > +               }
> > > > +       }
> > > > +       return true;
> > > > +}
> > > > +
> > > >  static int
> > > >  uev_update_path (struct uevent *uev, struct vectors * vecs)
> > > >  {
> > > > @@ -1512,7 +1541,7 @@ uev_update_path (struct uevent *uev, struct
> > > > vectors * vecs)
> > > >                 }
> > > >  
> > > >                 ro = uevent_get_disk_ro(uev);
> > > > -               if (mpp && ro >= 0) {
> > > > +               if (needs_ro_update(mpp, ro)) {
> > > >                         condlog(2, "%s: update path write_protect
> > > > to
> > > > '%d' (uevent)", uev->kernel, ro);
> > > >  
> > > >                         if (mpp->wait_for_udev)
> > 

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Nov. 22, 2021, 7:39 p.m. UTC | #5
On Mon, 2021-11-22 at 11:43 -0600, Benjamin Marzinski wrote:
> On Mon, Nov 22, 2021 at 04:48:06PM +0000, Martin Wilck wrote:
> > On Mon, 2021-11-22 at 09:35 -0600, Benjamin Marzinski wrote:
> > > On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> > > > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > > > A mulitpath device can only be reloaded read/write when all
> > > > > paths
> > > > > are
> > > > > read/write. Also, whenever a read-only device is rescanned,
> > > > > the
> > > > > scsi
> > > > > subsystem will first unconditionally issue a uevent with
> > > > > DISK_RO=0
> > > > > before checking the read-only status, and if it the device is
> > > > > still
> > > > > read-only, issuing another uevent with DISK_RO=1. These
> > > > > uevents
> > > > > cause
> > > > > pointless rereads when read-only paths are rescanned. To
> > > > > avoid
> > > > > this,
> > > > > check to see if all paths are read/write before changing a
> > > > > multipath
> > > > > device from read-only to read/write.
> > > > > 
> > > > > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > > > > ---
> > > > >  libmultipath/libmultipath.version |  5 +++++
> > > > >  libmultipath/sysfs.c              | 22
> > > > > ++++++++++++++++++++++
> > > > >  libmultipath/sysfs.h              |  1 +
> > > > >  multipathd/main.c                 | 31
> > > > > ++++++++++++++++++++++++++++++-
> > > > >  4 files changed, 58 insertions(+), 1 deletion(-)
> > > > > 
> > > > > diff --git a/libmultipath/libmultipath.version
> > > > > b/libmultipath/libmultipath.version
> > > > > index 58a7d1be..ab4c7e30 100644
> > > > > --- a/multipathd/main.c
> > > > > +++ b/multipathd/main.c
> > > > > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp,
> > > > > struct
> > > > > vectors * vecs)
> > > > >         return -1;
> > > > >  }
> > > > >  
> > > > > +static bool
> > > > > +needs_ro_update(struct multipath *mpp, int ro)
> > > > > +{
> > > > > +       struct pathgroup * pgp;
> > > > > +       struct path * pp;
> > > > > +       unsigned int i, j;
> > > > > +       struct dm_info *dmi = NULL;
> > > > > +
> > > > > +       if (!mpp || ro < 0)
> > > > > +               return false;
> > > > > +       dm_get_info(mpp->alias, &dmi);
> > > > 
> > > > Why can't you just use mpp->dmi here?
> > > 
> > > Since that value is set when the dmi is originally created, I
> > > didn't
> > > want to not reload a map, if we simply haven't updated it yet to
> > > reflect
> > > a change in the read-only value, like with do with
> > > dm_is_suspended()
> > > or dm_get_deferred_remove(), etc. I could make a
> > > dm_get_read_only()
> > > function and put it libmultipath/devmapper.c like the others, if
> > > you'd
> > > rather.
> > 
> > I had expected that this property wouldn't silently change under
> > us.
> > Actually, I do think that we should get an uevent if this happens.
> > Not sure if we process it properly, though.
> 
> I think we will update the dmi, but I'm not sure that these uevents
> won't race. The worry was that the device would switch to read-only
> and
> then back too quickly, and we would get this event and still see the
> device in read/write because we haven't processed the event which
> would
> update the multipath dmi.

OK. I'm fine with the patch, perhaps explain these subtleties some more
in the commit message for future reference.

Martin


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Nov. 23, 2021, 11:05 a.m. UTC | #6
Hi Ben,

some more thoughts about the ro handling.

On Mon, 2021-11-22 at 20:39 +0100, Martin Wilck wrote:
> On Mon, 2021-11-22 at 11:43 -0600, Benjamin Marzinski wrote:
> > On Mon, Nov 22, 2021 at 04:48:06PM +0000, Martin Wilck wrote:
> > > On Mon, 2021-11-22 at 09:35 -0600, Benjamin Marzinski wrote:
> > > > On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> > > > > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > > > > 
> > > > > > +static bool
> > > > > > +needs_ro_update(struct multipath *mpp, int ro)
> > > > > > +{
> > > > > > +       struct pathgroup * pgp;
> > > > > > +       struct path * pp;
> > > > > > +       unsigned int i, j;
> > > > > > +       struct dm_info *dmi = NULL;
> > > > > > +
> > > > > > +       if (!mpp || ro < 0)
> > > > > > +               return false;
> > > > > > +       dm_get_info(mpp->alias, &dmi);
> > > > > 
> > > > > Why can't you just use mpp->dmi here?
> > > > 
> > > > Since that value is set when the dmi is originally created, I
> > > > didn't
> > > > want to not reload a map, if we simply haven't updated it yet
> > > > to
> > > > reflect
> > > > a change in the read-only value, like with do with
> > > > dm_is_suspended()
> > > > or dm_get_deferred_remove(), etc. I could make a
> > > > dm_get_read_only()
> > > > function and put it libmultipath/devmapper.c like the others,
> > > > if
> > > > you'd
> > > > rather.
> > > 
> > > I had expected that this property wouldn't silently change under
> > > us.
> > > Actually, I do think that we should get an uevent if this
> > > happens.
> > > Not sure if we process it properly, though.
> > 
> > I think we will update the dmi, 

This would need to be done on a change uevent for the dm device in
ev_add_map(), but AFAICS we don't. ev_add_map() is basically a noop if
the map is already known, unless wait_for_udev is 2.

> > but I'm not sure that these uevents
> > won't race. The worry was that the device would switch to read-only
> > and
> > then back too quickly, and we would get this event and still see
> > the
> > device in read/write because we haven't processed the event which
> > would
> > update the multipath dmi.
> 
> OK. I'm fine with the patch, perhaps explain these subtleties some
> more
> in the commit message for future reference.

I've never looked into the ro attribute processing closely. I just did.
I'm unsure how a race would come to pass, in particular with your patch
applied:

 1. path change uevent arrives
 2. ro attribute of path device has changed
 3. map reload occurs if 
    a) map was rw before (thus all paths, too) and the path changed to
ro
    b) map was ro before and all paths have changed to rw
 4. kernel will call set_disk_ro() depending on the DM_READONLY_FLAG;
    set_disk_ro() triggers an uevent for the block device if and only
    if the ro flag changed
 5. we set mpp->dmi in __setup_multipath().

We hold the vecs lock between 3 and 5, so even if the uevent arrived
before setup_multipath() was called, I don't see how it could race.
mpp->dmi as derived in 5 should reflect the state correctly.

What we could do is remember the ro-state of the map in dm_addmap(),
e.g. in a mpp->ro field. If map creation with ro=0 succeeded, we can be
pretty certain that the map is in read-write state. Otherwise we'd
fallback to ro=1, and remember that state, too. We could verify that
state once more against the dmi info in setup_multipath(). By doing
that we'd cover the time span between the dm ioctl and retrieving the
dmi in setup_multipath(). That would IMHO be more consistent than the
current use of the temporary force_readonly flag.

I've been wondering whether we should use your logic during map
creation, too, and not even try to setup the map with ro=0 if we have
paths in read-only state.

Regards
Martin



--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Benjamin Marzinski Nov. 23, 2021, 3:57 p.m. UTC | #7
On Tue, Nov 23, 2021 at 11:05:20AM +0000, Martin Wilck wrote:
> Hi Ben,
> 
> some more thoughts about the ro handling.
> 
> On Mon, 2021-11-22 at 20:39 +0100, Martin Wilck wrote:
> > On Mon, 2021-11-22 at 11:43 -0600, Benjamin Marzinski wrote:
> > > On Mon, Nov 22, 2021 at 04:48:06PM +0000, Martin Wilck wrote:
> > > > On Mon, 2021-11-22 at 09:35 -0600, Benjamin Marzinski wrote:
> > > > > On Fri, Nov 19, 2021 at 09:33:39PM +0000, Martin Wilck wrote:
> > > > > > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > > > > > 
> > > > > > > +static bool
> > > > > > > +needs_ro_update(struct multipath *mpp, int ro)
> > > > > > > +{
> > > > > > > +       struct pathgroup * pgp;
> > > > > > > +       struct path * pp;
> > > > > > > +       unsigned int i, j;
> > > > > > > +       struct dm_info *dmi = NULL;
> > > > > > > +
> > > > > > > +       if (!mpp || ro < 0)
> > > > > > > +               return false;
> > > > > > > +       dm_get_info(mpp->alias, &dmi);
> > > > > > 
> > > > > > Why can't you just use mpp->dmi here?
> > > > > 
> > > > > Since that value is set when the dmi is originally created, I
> > > > > didn't
> > > > > want to not reload a map, if we simply haven't updated it yet
> > > > > to
> > > > > reflect
> > > > > a change in the read-only value, like with do with
> > > > > dm_is_suspended()
> > > > > or dm_get_deferred_remove(), etc. I could make a
> > > > > dm_get_read_only()
> > > > > function and put it libmultipath/devmapper.c like the others,
> > > > > if
> > > > > you'd
> > > > > rather.
> > > > 
> > > > I had expected that this property wouldn't silently change under
> > > > us.
> > > > Actually, I do think that we should get an uevent if this
> > > > happens.
> > > > Not sure if we process it properly, though.
> > > 
> > > I think we will update the dmi, 
> 
> This would need to be done on a change uevent for the dm device in
> ev_add_map(), but AFAICS we don't. ev_add_map() is basically a noop if
> the map is already known, unless wait_for_udev is 2.
> 
> > > but I'm not sure that these uevents
> > > won't race. The worry was that the device would switch to read-only
> > > and
> > > then back too quickly, and we would get this event and still see
> > > the
> > > device in read/write because we haven't processed the event which
> > > would
> > > update the multipath dmi.
> > 
> > OK. I'm fine with the patch, perhaps explain these subtleties some
> > more
> > in the commit message for future reference.
> 
> I've never looked into the ro attribute processing closely. I just did.
> I'm unsure how a race would come to pass, in particular with your patch
> applied:
> 
>  1. path change uevent arrives
>  2. ro attribute of path device has changed
>  3. map reload occurs if 
>     a) map was rw before (thus all paths, too) and the path changed to
> ro
>     b) map was ro before and all paths have changed to rw
>  4. kernel will call set_disk_ro() depending on the DM_READONLY_FLAG;
>     set_disk_ro() triggers an uevent for the block device if and only
>     if the ro flag changed
>  5. we set mpp->dmi in __setup_multipath().
> 
> We hold the vecs lock between 3 and 5, so even if the uevent arrived
> before setup_multipath() was called, I don't see how it could race.
> mpp->dmi as derived in 5 should reflect the state correctly.

I admit, I didn't find a definitive race. I was just worried about the
possibility of the dmi being outdated. While there's always the
possibility of the multipath device's RO state getting changed outside
of multipathd (by a multipath call for example), this is not the place
to deal with that. ev_add_map() would be. After looking at this, I'm
o.k. with trusting the existing dmi, especially if we are updating it in
ev_add_map().
 
> What we could do is remember the ro-state of the map in dm_addmap(),
> e.g. in a mpp->ro field. If map creation with ro=0 succeeded, we can be
> pretty certain that the map is in read-write state. Otherwise we'd
> fallback to ro=1, and remember that state, too. We could verify that
> state once more against the dmi info in setup_multipath(). By doing
> that we'd cover the time span between the dm ioctl and retrieving the
> dmi in setup_multipath(). That would IMHO be more consistent than the
> current use of the temporary force_readonly flag.

So the idea would be to never try reloading read-write when the map is
marked as RO, until we get a path event updating the RO state? I do
worry about cases where well fail to reload the map correctly then.
Imagine that we have a map with mpp->ro=1 with one read-only path. The
read-only path gets removed. If we just assume that the mpp->ro state is
correct until with get a path_event changing the read-only state, we
will won't reload read/write here.  The other option would be to check
the path's RO state every time we reload, or at least whenever we're
reloading to remove a path. That has the advantage that it doesn't
produce a dm error message like a failed reload does, but I'm not sure
if it's any less work. Or am I misunderstanding what you are suggesting
here?

> I've been wondering whether we should use your logic during map
> creation, too, and not even try to setup the map with ro=0 if we have
> paths in read-only state.

If sysfs says one of the paths is read-only, it seems reasonable to skip
the read/write reload.

-Ben
 
> Regards
> Martin
> 

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Nov. 23, 2021, 8:03 p.m. UTC | #8
On Tue, 2021-11-23 at 09:57 -0600, Benjamin Marzinski wrote:
> On Tue, Nov 23, 2021 at 11:05:20AM +0000, Martin Wilck wrote:
> > 
> > I've never looked into the ro attribute processing closely. I just
> > did.
> > I'm unsure how a race would come to pass, in particular with your
> > patch
> > applied:
> > 
> >  1. path change uevent arrives
> >  2. ro attribute of path device has changed
> >  3. map reload occurs if 
> >     a) map was rw before (thus all paths, too) and the path changed
> > to
> > ro
> >     b) map was ro before and all paths have changed to rw
> >  4. kernel will call set_disk_ro() depending on the
> > DM_READONLY_FLAG;
> >     set_disk_ro() triggers an uevent for the block device if and
> > only
> >     if the ro flag changed
> >  5. we set mpp->dmi in __setup_multipath().
> > 
> > We hold the vecs lock between 3 and 5, so even if the uevent
> > arrived
> > before setup_multipath() was called, I don't see how it could race.
> > mpp->dmi as derived in 5 should reflect the state correctly.
> 
> I admit, I didn't find a definitive race. I was just worried about
> the
> possibility of the dmi being outdated. While there's always the
> possibility of the multipath device's RO state getting changed
> outside
> of multipathd (by a multipath call for example), this is not the
> place
> to deal with that. ev_add_map() would be. After looking at this, I'm
> o.k. with trusting the existing dmi, especially if we are updating it
> in
> ev_add_map().
>  
> > What we could do is remember the ro-state of the map in
> > dm_addmap(),
> > e.g. in a mpp->ro field. If map creation with ro=0 succeeded, we
> > can be
> > pretty certain that the map is in read-write state. Otherwise we'd
> > fallback to ro=1, and remember that state, too. We could verify
> > that
> > state once more against the dmi info in setup_multipath(). By doing
> > that we'd cover the time span between the dm ioctl and retrieving
> > the
> > dmi in setup_multipath(). That would IMHO be more consistent than
> > the
> > current use of the temporary force_readonly flag.
> 
> So the idea would be to never try reloading read-write when the map
> is
> marked as RO, until we get a path event updating the RO state? I do
> worry about cases where well fail to reload the map correctly then.
> Imagine that we have a map with mpp->ro=1 with one read-only path.
> The
> read-only path gets removed. If we just assume that the mpp->ro state
> is
> correct until with get a path_event changing the read-only state, we
> will won't reload read/write here.

I didn't think about this possibility. TBH, it sounds pretty
pathological to me.

>   The other option would be to check
> the path's RO state every time we reload, or at least whenever we're
> reloading to remove a path. That has the advantage that it doesn't
> produce a dm error message like a failed reload does, but I'm not
> sure
> if it's any less work. Or am I misunderstanding what you are
> suggesting
> here?

Hm, I guess not. I'm worried that that may slow down reload operations
quite a bit. Under normal circumstances, all paths will be r/w most of
the time (in my experience, read-only multipath storage is pretty
rare). The idea to re-check this only on path removal sounds ok-ish. We
could also track the ro attribute of paths, so that we don't have to
check sysfs every time....

But before things get overly complicated, trying to load r/w first and
checking -EROFS like we do now, and doing the sysfs check when we
receive uevents, is just as good, if not better.

> > I've been wondering whether we should use your logic during map
> > creation, too, and not even try to setup the map with ro=0 if we
> > have
> > paths in read-only state.
> 
> If sysfs says one of the paths is read-only, it seems reasonable to
> skip
> the read/write reload.

The only problem being that we'll have to check all paths in almost
every case.

Martin


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Dec. 3, 2021, 8:25 a.m. UTC | #9
On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> A mulitpath device can only be reloaded read/write when all paths are
> read/write. Also, whenever a read-only device is rescanned, the scsi
> subsystem will first unconditionally issue a uevent with DISK_RO=0
> before checking the read-only status, and if it the device is still
> read-only, issuing another uevent with DISK_RO=1. These uevents cause
> pointless reloads when read-only paths are rescanned. To avoid this,
> check to see if all paths are read/write before changing a multipath
> device from read-only to read/write.
> 
> Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> ---
>  libmultipath/libmultipath.version |  5 +++++
>  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
>  libmultipath/sysfs.h              |  1 +
>  multipathd/main.c                 | 31
> ++++++++++++++++++++++++++++++-
>  4 files changed, 58 insertions(+), 1 deletion(-)
> 
> diff --git a/libmultipath/libmultipath.version
> b/libmultipath/libmultipath.version
> index 58a7d1be..ab4c7e30 100644
> --- a/libmultipath/libmultipath.version
> +++ b/libmultipath/libmultipath.version
> @@ -296,3 +296,8 @@ global:
>  local:
>         *;
>  };
> +
> +LIBMULTIPATH_11.1.0 {
> +global:
> +       sysfs_get_ro;
> +} LIBMULTIPATH_11.0.0;
> diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c
> index 9ff145f2..24c12b6a 100644
> --- a/libmultipath/sysfs.c
> +++ b/libmultipath/sysfs.c
> @@ -236,6 +236,28 @@ sysfs_get_size (struct path *pp, unsigned long
> long * size)
>         return 0;
>  }
>  
> +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(pp->udev, "ro", buff, sizeof(buff))
> <= 0) {
> +               condlog(3, "%s: Cannot read ro attribute in sysfs",
> pp->dev);
> +               return -1;
> +       }
> +
> +       if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) {

This is ok, but for self-evidently correct code in multipath-tools,
it'd be better to read just 2 bytes and set buff[2] = '\0' explicitly.
I haven't checked, but coverity might stumble on this.

> +               condlog(3, "%s: Cannot parse ro attribute", pp->dev);
> +               return -1;
> +       }
> +
> +       return ro;
> +}
> +

Does this function need to be in libmultipath? We could avoid extending
the ABI  by adding it as a static function to multipathd. After all,
it's just sysfs_attr_get_value() + sscanf().


>  int sysfs_check_holders(char * check_devt, char * new_devt)
>  {
>         unsigned int major, new_minor, table_minor;
> diff --git a/libmultipath/sysfs.h b/libmultipath/sysfs.h
> index 72b39ab2..c948c467 100644
> --- a/libmultipath/sysfs.h
> +++ b/libmultipath/sysfs.h
> @@ -13,6 +13,7 @@ ssize_t sysfs_attr_get_value(struct udev_device
> *dev, const char *attr_name,
>  ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char
> *attr_name,
>                                  unsigned char * value, size_t
> value_len);
>  int sysfs_get_size (struct path *pp, unsigned long long * size);
> +int sysfs_get_ro(struct path *pp);
>  int sysfs_check_holders(char * check_devt, char * new_devt);
>  bool sysfs_is_multipathed(struct path *pp, bool set_wwid);
>  #endif
> diff --git a/multipathd/main.c b/multipathd/main.c
> index 08a8a592..a1665494 100644
> --- a/multipathd/main.c
> +++ b/multipathd/main.c
> @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> vectors * vecs)
>         return -1;
>  }
>  
> +static bool
> +needs_ro_update(struct multipath *mpp, int ro)
> +{
> +       struct pathgroup * pgp;
> +       struct path * pp;
> +       unsigned int i, j;
> +       struct dm_info *dmi = NULL;
> +
> +       if (!mpp || ro < 0)
> +               return false;
> +       dm_get_info(mpp->alias, &dmi);

I'm still not convinced that we need this call, in particular as you
have added a call to dm_get_info() in the uev_update_map() call, and it
was called in the dmevent handler, too.

In general, I think that multipath-tools could rely more on the kernel
to send events for status changes than it currently does. 
This is particularly true for dm properties, where we have not only one
event mechanism but two (uevent + dmevent).

Regards
Martin


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Benjamin Marzinski Dec. 6, 2021, 3:27 p.m. UTC | #10
On Fri, Dec 03, 2021 at 08:25:00AM +0000, Martin Wilck wrote:
> On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > A mulitpath device can only be reloaded read/write when all paths are
> > read/write. Also, whenever a read-only device is rescanned, the scsi
> > subsystem will first unconditionally issue a uevent with DISK_RO=0
> > before checking the read-only status, and if it the device is still
> > read-only, issuing another uevent with DISK_RO=1. These uevents cause
> > pointless reloads when read-only paths are rescanned. To avoid this,
> > check to see if all paths are read/write before changing a multipath
> > device from read-only to read/write.
> > 
> > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > ---
> >  libmultipath/libmultipath.version |  5 +++++
> >  libmultipath/sysfs.c              | 22 ++++++++++++++++++++++
> >  libmultipath/sysfs.h              |  1 +
> >  multipathd/main.c                 | 31
> > ++++++++++++++++++++++++++++++-
> >  4 files changed, 58 insertions(+), 1 deletion(-)
> > 
> > diff --git a/libmultipath/libmultipath.version
> > b/libmultipath/libmultipath.version
> > index 58a7d1be..ab4c7e30 100644
> > --- a/libmultipath/libmultipath.version
> > +++ b/libmultipath/libmultipath.version
> > @@ -296,3 +296,8 @@ global:
> >  local:
> >         *;
> >  };
> > +
> > +LIBMULTIPATH_11.1.0 {
> > +global:
> > +       sysfs_get_ro;
> > +} LIBMULTIPATH_11.0.0;
> > diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c
> > index 9ff145f2..24c12b6a 100644
> > --- a/libmultipath/sysfs.c
> > +++ b/libmultipath/sysfs.c
> > @@ -236,6 +236,28 @@ sysfs_get_size (struct path *pp, unsigned long
> > long * size)
> >         return 0;
> >  }
> >  
> > +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(pp->udev, "ro", buff, sizeof(buff))
> > <= 0) {
> > +               condlog(3, "%s: Cannot read ro attribute in sysfs",
> > pp->dev);
> > +               return -1;
> > +       }
> > +
> > +       if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) {
> 
> This is ok, but for self-evidently correct code in multipath-tools,
> it'd be better to read just 2 bytes and set buff[2] = '\0' explicitly.
> I haven't checked, but coverity might stumble on this.

Sure.
 
> > +               condlog(3, "%s: Cannot parse ro attribute", pp->dev);
> > +               return -1;
> > +       }
> > +
> > +       return ro;
> > +}
> > +
> 
> Does this function need to be in libmultipath? We could avoid extending
> the ABI  by adding it as a static function to multipathd. After all,
> it's just sysfs_attr_get_value() + sscanf().
>

Sure.

> 
> >  int sysfs_check_holders(char * check_devt, char * new_devt)
> >  {
> >         unsigned int major, new_minor, table_minor;
> > diff --git a/libmultipath/sysfs.h b/libmultipath/sysfs.h
> > index 72b39ab2..c948c467 100644
> > --- a/libmultipath/sysfs.h
> > +++ b/libmultipath/sysfs.h
> > @@ -13,6 +13,7 @@ ssize_t sysfs_attr_get_value(struct udev_device
> > *dev, const char *attr_name,
> >  ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char
> > *attr_name,
> >                                  unsigned char * value, size_t
> > value_len);
> >  int sysfs_get_size (struct path *pp, unsigned long long * size);
> > +int sysfs_get_ro(struct path *pp);
> >  int sysfs_check_holders(char * check_devt, char * new_devt);
> >  bool sysfs_is_multipathed(struct path *pp, bool set_wwid);
> >  #endif
> > diff --git a/multipathd/main.c b/multipathd/main.c
> > index 08a8a592..a1665494 100644
> > --- a/multipathd/main.c
> > +++ b/multipathd/main.c
> > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> > vectors * vecs)
> >         return -1;
> >  }
> >  
> > +static bool
> > +needs_ro_update(struct multipath *mpp, int ro)
> > +{
> > +       struct pathgroup * pgp;
> > +       struct path * pp;
> > +       unsigned int i, j;
> > +       struct dm_info *dmi = NULL;
> > +
> > +       if (!mpp || ro < 0)
> > +               return false;
> > +       dm_get_info(mpp->alias, &dmi);
> 
> I'm still not convinced that we need this call, in particular as you
> have added a call to dm_get_info() in the uev_update_map() call, and it
> was called in the dmevent handler, too.

You are reviewing my original patch again here, not the V2 version,
which has this removed.

-Ben


> In general, I think that multipath-tools could rely more on the kernel
> to send events for status changes than it currently does. 
> This is particularly true for dm properties, where we have not only one
> event mechanism but two (uevent + dmevent).
> 
> Regards
> Martin

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Benjamin Marzinski Dec. 15, 2021, 12:49 a.m. UTC | #11
On Fri, Dec 03, 2021 at 08:25:00AM +0000, Martin Wilck wrote:
> On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > +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(pp->udev, "ro", buff, sizeof(buff))
> > <= 0) {
> > +               condlog(3, "%s: Cannot read ro attribute in sysfs",
> > pp->dev);
> > +               return -1;
> > +       }
> > +
> > +       if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) {
> 
> This is ok, but for self-evidently correct code in multipath-tools,
> it'd be better to read just 2 bytes and set buff[2] = '\0' explicitly.
> I haven't checked, but coverity might stumble on this.

Actually this is just the way sysfs_attr_get_value() works. You have to
provide a larger buffer than you will read (in this case, ro will only
ever have two bytes), otherwise it will think that it overflowed.
sysfs_attr_get_value() also does the NULL termination itself.

-Ben

> > +               condlog(3, "%s: Cannot parse ro attribute", pp->dev);
> > +               return -1;
> > +       }
> > +
> > +       return ro;
> > +}
> > +
> 
> Does this function need to be in libmultipath? We could avoid extending
> the ABI  by adding it as a static function to multipathd. After all,
> it's just sysfs_attr_get_value() + sscanf().
> 
> 
> >  int sysfs_check_holders(char * check_devt, char * new_devt)
> >  {
> >         unsigned int major, new_minor, table_minor;
> > diff --git a/libmultipath/sysfs.h b/libmultipath/sysfs.h
> > index 72b39ab2..c948c467 100644
> > --- a/libmultipath/sysfs.h
> > +++ b/libmultipath/sysfs.h
> > @@ -13,6 +13,7 @@ ssize_t sysfs_attr_get_value(struct udev_device
> > *dev, const char *attr_name,
> >  ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char
> > *attr_name,
> >                                  unsigned char * value, size_t
> > value_len);
> >  int sysfs_get_size (struct path *pp, unsigned long long * size);
> > +int sysfs_get_ro(struct path *pp);
> >  int sysfs_check_holders(char * check_devt, char * new_devt);
> >  bool sysfs_is_multipathed(struct path *pp, bool set_wwid);
> >  #endif
> > diff --git a/multipathd/main.c b/multipathd/main.c
> > index 08a8a592..a1665494 100644
> > --- a/multipathd/main.c
> > +++ b/multipathd/main.c
> > @@ -1440,6 +1440,35 @@ finish_path_init(struct path *pp, struct
> > vectors * vecs)
> >         return -1;
> >  }
> >  
> > +static bool
> > +needs_ro_update(struct multipath *mpp, int ro)
> > +{
> > +       struct pathgroup * pgp;
> > +       struct path * pp;
> > +       unsigned int i, j;
> > +       struct dm_info *dmi = NULL;
> > +
> > +       if (!mpp || ro < 0)
> > +               return false;
> > +       dm_get_info(mpp->alias, &dmi);
> 
> I'm still not convinced that we need this call, in particular as you
> have added a call to dm_get_info() in the uev_update_map() call, and it
> was called in the dmevent handler, too.
> 
> In general, I think that multipath-tools could rely more on the kernel
> to send events for status changes than it currently does. 
> This is particularly true for dm properties, where we have not only one
> event mechanism but two (uevent + dmevent).
> 
> Regards
> Martin

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Dec. 15, 2021, 7:34 a.m. UTC | #12
On Tue, 2021-12-14 at 18:49 -0600, Benjamin Marzinski wrote:
> On Fri, Dec 03, 2021 at 08:25:00AM +0000, Martin Wilck wrote:
> > On Thu, 2021-11-18 at 16:47 -0600, Benjamin Marzinski wrote:
> > > +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(pp->udev, "ro", buff,
> > > sizeof(buff))
> > > <= 0) {
> > > +               condlog(3, "%s: Cannot read ro attribute in
> > > sysfs",
> > > pp->dev);
> > > +               return -1;
> > > +       }
> > > +
> > > +       if (sscanf(buff, "%d\n", &ro) != 1 || ro < 0 || ro > 1) {
> > 
> > This is ok, but for self-evidently correct code in multipath-tools,
> > it'd be better to read just 2 bytes and set buff[2] = '\0'
> > explicitly.
> > I haven't checked, but coverity might stumble on this.
> 
> Actually this is just the way sysfs_attr_get_value() works. You have
> to
> provide a larger buffer than you will read (in this case, ro will
> only
> ever have two bytes), otherwise it will think that it overflowed.
> sysfs_attr_get_value() also does the NULL termination itself.
> 

Well, fine then. It's a nit anyway.

Martin


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
diff mbox series

Patch

diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
index 58a7d1be..ab4c7e30 100644
--- a/libmultipath/libmultipath.version
+++ b/libmultipath/libmultipath.version
@@ -296,3 +296,8 @@  global:
 local:
 	*;
 };
+
+LIBMULTIPATH_11.1.0 {
+global:
+	sysfs_get_ro;
+} LIBMULTIPATH_11.0.0;
diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c
index 9ff145f2..24c12b6a 100644
--- a/libmultipath/sysfs.c
+++ b/libmultipath/sysfs.c
@@ -236,6 +236,28 @@  sysfs_get_size (struct path *pp, unsigned long long * size)
 	return 0;
 }
 
+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(pp->udev, "ro", buff, sizeof(buff)) <= 0) {
+		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;
+}
+
 int sysfs_check_holders(char * check_devt, char * new_devt)
 {
 	unsigned int major, new_minor, table_minor;
diff --git a/libmultipath/sysfs.h b/libmultipath/sysfs.h
index 72b39ab2..c948c467 100644
--- a/libmultipath/sysfs.h
+++ b/libmultipath/sysfs.h
@@ -13,6 +13,7 @@  ssize_t sysfs_attr_get_value(struct udev_device *dev, const char *attr_name,
 ssize_t sysfs_bin_attr_get_value(struct udev_device *dev, const char *attr_name,
 				 unsigned char * value, size_t value_len);
 int sysfs_get_size (struct path *pp, unsigned long long * size);
+int sysfs_get_ro(struct path *pp);
 int sysfs_check_holders(char * check_devt, char * new_devt);
 bool sysfs_is_multipathed(struct path *pp, bool set_wwid);
 #endif
diff --git a/multipathd/main.c b/multipathd/main.c
index 08a8a592..a1665494 100644
--- a/multipathd/main.c
+++ b/multipathd/main.c
@@ -1440,6 +1440,35 @@  finish_path_init(struct path *pp, struct vectors * vecs)
 	return -1;
 }
 
+static bool
+needs_ro_update(struct multipath *mpp, int ro)
+{
+	struct pathgroup * pgp;
+	struct path * pp;
+	unsigned int i, j;
+	struct dm_info *dmi = NULL;
+
+	if (!mpp || ro < 0)
+		return false;
+	dm_get_info(mpp->alias, &dmi);
+	if (!dmi) /* assume we do need to reload the device */
+		return true;
+	if (dmi->read_only == ro) {
+		free(dmi);
+		return false;
+	}
+	free(dmi);
+	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)
+				return false;
+		}
+	}
+	return true;
+}
+
 static int
 uev_update_path (struct uevent *uev, struct vectors * vecs)
 {
@@ -1512,7 +1541,7 @@  uev_update_path (struct uevent *uev, struct vectors * vecs)
 		}
 
 		ro = uevent_get_disk_ro(uev);
-		if (mpp && ro >= 0) {
+		if (needs_ro_update(mpp, ro)) {
 			condlog(2, "%s: update path write_protect to '%d' (uevent)", uev->kernel, ro);
 
 			if (mpp->wait_for_udev)