diff mbox series

[2/2] scsi: add block job opblockers for scsi-block

Message ID 20180207163622.29935-3-pbonzini@redhat.com
State New
Headers show
Series scsi: add block job opblockers for scsi-block | expand

Commit Message

Paolo Bonzini Feb. 7, 2018, 4:36 p.m. UTC
scsi-block bypasses the dirty bitmaps and pre-write notifiers, so it
cannot be the source of a block job.  The gist of the fix is to add
op-blockers to the BlockBackend, and remove them at "unrealize" time,
but things are a little more complex because quit closes the BlockBackend
without going through unrealize.

So use Notifiers: the remove_bs notifier is called by bdrv_close_all, and
the insert_bs notifier might not be really necessary but make things a
little more symmetric.

Suggested-by: Karen Noel <knoel@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/block-backend.c          |  9 ++++++
 hw/scsi/scsi-disk.c            | 62 ++++++++++++++++++++++++++++++++++++++++++
 include/sysemu/block-backend.h |  1 +
 3 files changed, 72 insertions(+)

Comments

Fam Zheng Feb. 8, 2018, 1:35 a.m. UTC | #1
On Wed, 02/07 17:36, Paolo Bonzini wrote:
> scsi-block bypasses the dirty bitmaps and pre-write notifiers, so it
> cannot be the source of a block job.  The gist of the fix is to add
> op-blockers to the BlockBackend, and remove them at "unrealize" time,
> but things are a little more complex because quit closes the BlockBackend
> without going through unrealize.
> 
> So use Notifiers: the remove_bs notifier is called by bdrv_close_all, and
> the insert_bs notifier might not be really necessary but make things a
> little more symmetric.
> 
> Suggested-by: Karen Noel <knoel@redhat.com>

:)

> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

Reviewed-by: Fam Zheng <famz@redhat.com>

Though I have one comment below.

> ---
>  block/block-backend.c          |  9 ++++++
>  hw/scsi/scsi-disk.c            | 62 ++++++++++++++++++++++++++++++++++++++++++
>  include/sysemu/block-backend.h |  1 +
>  3 files changed, 72 insertions(+)
> 
> diff --git a/block/block-backend.c b/block/block-backend.c
> index baef8e7abc..1759639a4a 100644
> --- a/block/block-backend.c
> +++ b/block/block-backend.c
> @@ -1747,6 +1747,15 @@ bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp)
>      return bdrv_op_is_blocked(bs, op, errp);
>  }
>  
> +void blk_op_block(BlockBackend *blk, BlockOpType op, Error *reason)
> +{
> +    BlockDriverState *bs = blk_bs(blk);
> +
> +    if (bs) {
> +        bdrv_op_block(bs, op, reason);
> +    }
> +}
> +
>  void blk_op_unblock(BlockBackend *blk, BlockOpType op, Error *reason)
>  {
>      BlockDriverState *bs = blk_bs(blk);
> diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
> index 49d2559d93..023673cb04 100644
> --- a/hw/scsi/scsi-disk.c
> +++ b/hw/scsi/scsi-disk.c
> @@ -2578,9 +2578,39 @@ static int get_device_type(SCSIDiskState *s)
>      return 0;
>  }
>  
> +typedef struct SCSIBlockState {
> +    SCSIDiskState sd;
> +    Error *mirror_source;
> +    Error *backup_source;
> +    Error *commit_source;
> +    Notifier insert_bs;
> +    Notifier remove_bs;
> +} SCSIBlockState;
> +
> +static void scsi_block_insert_bs(Notifier *n, void *opaque)
> +{
> +    SCSIBlockState *sb = container_of(n, SCSIBlockState, insert_bs);
> +    SCSIDiskState *s = &sb->sd;
> +
> +    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_MIRROR_SOURCE, sb->mirror_source);
> +    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, sb->commit_source);
> +    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, sb->backup_source);
> +}
> +
> +static void scsi_block_remove_bs(Notifier *n, void *opaque)
> +{
> +    SCSIBlockState *sb = container_of(n, SCSIBlockState, remove_bs);
> +    SCSIDiskState *s = &sb->sd;
> +
> +    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_MIRROR_SOURCE, sb->mirror_source);
> +    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, sb->commit_source);
> +    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, sb->backup_source);
> +}
> +
>  static void scsi_block_realize(SCSIDevice *dev, Error **errp)
>  {
>      SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
> +    SCSIBlockState *sb = DO_UPCAST(SCSIBlockState, sd, s);
>      int sg_version;
>      int rc;
>  
> @@ -2626,6 +2656,36 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
>  
>      scsi_realize(&s->qdev, errp);
>      scsi_generic_read_device_identification(&s->qdev);
> +
> +    /* For op blockers, due to lack of support for dirty bitmaps.  */
> +    error_setg(&sb->mirror_source,
> +               "scsi-block does not support acting as a mirroring source");
> +    error_setg(&sb->commit_source,
> +               "scsi-block does not support acting as an active commit source");

An alternative way would be adding BLOCK_OP_TYPE_DIRTY_BITMAP. The error message
will not be as nice but it can be useful for another (blockjob) operation that
requires dirty bitmap support, or another device that doesn't support dirty
bitmaps. Though there isn't one for now.

> +
> +    /* For op blockers, due to lack of support for write notifiers.  */
> +    error_setg(&sb->backup_source,
> +               "scsi-block does not support acting as a backup source");
> +
> +    sb->insert_bs.notify = scsi_block_insert_bs;
> +    blk_add_insert_bs_notifier(s->qdev.conf.blk, &sb->insert_bs);
> +    sb->remove_bs.notify = scsi_block_remove_bs;
> +    blk_add_remove_bs_notifier(s->qdev.conf.blk, &sb->remove_bs);
> +
> +    scsi_block_insert_bs(&sb->insert_bs, s->qdev.conf.blk);
> +}
> +
> +static void scsi_block_unrealize(SCSIDevice *dev, Error **errp)
> +{
> +    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
> +    SCSIBlockState *sb = DO_UPCAST(SCSIBlockState, sd, s);
> +
> +    notifier_remove(&sb->insert_bs);
> +    notifier_remove(&sb->remove_bs);
> +    scsi_block_remove_bs(&sb->insert_bs, s->qdev.conf.blk);
> +    error_free(sb->mirror_source);
> +    error_free(sb->commit_source);
> +    error_free(sb->backup_source);
>  }
>  
>  typedef struct SCSIBlockReq {
> @@ -3017,6 +3077,7 @@ static void scsi_block_class_initfn(ObjectClass *klass, void *data)
>      SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
>  
>      sc->realize      = scsi_block_realize;
> +    sc->unrealize    = scsi_block_unrealize;
>      sc->alloc_req    = scsi_block_new_request;
>      sc->parse_cdb    = scsi_block_parse_cdb;
>      sdc->dma_readv   = scsi_block_dma_readv;
> @@ -3031,6 +3092,7 @@ static const TypeInfo scsi_block_info = {
>      .name          = "scsi-block",
>      .parent        = TYPE_SCSI_DISK_BASE,
>      .class_init    = scsi_block_class_initfn,
> +    .instance_size = sizeof(SCSIBlockState),
>  };
>  #endif
>  
> diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
> index c4e52a5fa3..a48a49ca79 100644
> --- a/include/sysemu/block-backend.h
> +++ b/include/sysemu/block-backend.h
> @@ -182,6 +182,7 @@ void blk_set_guest_block_size(BlockBackend *blk, int align);
>  void *blk_try_blockalign(BlockBackend *blk, size_t size);
>  void *blk_blockalign(BlockBackend *blk, size_t size);
>  bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp);
> +void blk_op_block(BlockBackend *blk, BlockOpType op, Error *reason);
>  void blk_op_unblock(BlockBackend *blk, BlockOpType op, Error *reason);
>  void blk_op_block_all(BlockBackend *blk, Error *reason);
>  void blk_op_unblock_all(BlockBackend *blk, Error *reason);
> -- 
> 2.14.3
> 
> 

Fam
Paolo Bonzini Feb. 8, 2018, 10:42 a.m. UTC | #2
On 08/02/2018 02:35, Fam Zheng wrote:
> On Wed, 02/07 17:36, Paolo Bonzini wrote:
>> @@ -2626,6 +2656,36 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
>>  
>>      scsi_realize(&s->qdev, errp);
>>      scsi_generic_read_device_identification(&s->qdev);
>> +
>> +    /* For op blockers, due to lack of support for dirty bitmaps.  */
>> +    error_setg(&sb->mirror_source,
>> +               "scsi-block does not support acting as a mirroring source");
>> +    error_setg(&sb->commit_source,
>> +               "scsi-block does not support acting as an active commit source");
> 
> An alternative way would be adding BLOCK_OP_TYPE_DIRTY_BITMAP. The error message
> will not be as nice but it can be useful for another (blockjob) operation that
> requires dirty bitmap support, or another device that doesn't support dirty
> bitmaps. Though there isn't one for now.

Yeah, I thought about it.  Another possibility is make BLOCK_OP_TYPE_* a
bitmask.  Then you can easily add a single Error * for multiple
blockers, and BLOCK_OP_TYPE_DIRTY_BITMAP can be defined as
BLOCK_OP_TYPE_MIRROR_SOURCE|BLOCK_OP_TYPE_COMMIT_SOURCE; likewise for
notifiers below.

Paolo

>> +
>> +    /* For op blockers, due to lack of support for write notifiers.  */
>> +    error_setg(&sb->backup_source,
>> +               "scsi-block does not support acting as a backup source");
>> +
>> +    sb->insert_bs.notify = scsi_block_insert_bs;
>> +    blk_add_insert_bs_notifier(s->qdev.conf.blk, &sb->insert_bs);
>> +    sb->remove_bs.notify = scsi_block_remove_bs;
>> +    blk_add_remove_bs_notifier(s->qdev.conf.blk, &sb->remove_bs);
>> +
>> +    scsi_block_insert_bs(&sb->insert_bs, s->qdev.conf.blk);
>> +}
>> +
>> +static void scsi_block_unrealize(SCSIDevice *dev, Error **errp)
>> +{
>> +    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
>> +    SCSIBlockState *sb = DO_UPCAST(SCSIBlockState, sd, s);
>> +
>> +    notifier_remove(&sb->insert_bs);
>> +    notifier_remove(&sb->remove_bs);
>> +    scsi_block_remove_bs(&sb->insert_bs, s->qdev.conf.blk);
>> +    error_free(sb->mirror_source);
>> +    error_free(sb->commit_source);
>> +    error_free(sb->backup_source);
>>  }
>>  
>>  typedef struct SCSIBlockReq {
>> @@ -3017,6 +3077,7 @@ static void scsi_block_class_initfn(ObjectClass *klass, void *data)
>>      SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
>>  
>>      sc->realize      = scsi_block_realize;
>> +    sc->unrealize    = scsi_block_unrealize;
>>      sc->alloc_req    = scsi_block_new_request;
>>      sc->parse_cdb    = scsi_block_parse_cdb;
>>      sdc->dma_readv   = scsi_block_dma_readv;
>> @@ -3031,6 +3092,7 @@ static const TypeInfo scsi_block_info = {
>>      .name          = "scsi-block",
>>      .parent        = TYPE_SCSI_DISK_BASE,
>>      .class_init    = scsi_block_class_initfn,
>> +    .instance_size = sizeof(SCSIBlockState),
>>  };
>>  #endif
>>  
>> diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
>> index c4e52a5fa3..a48a49ca79 100644
>> --- a/include/sysemu/block-backend.h
>> +++ b/include/sysemu/block-backend.h
>> @@ -182,6 +182,7 @@ void blk_set_guest_block_size(BlockBackend *blk, int align);
>>  void *blk_try_blockalign(BlockBackend *blk, size_t size);
>>  void *blk_blockalign(BlockBackend *blk, size_t size);
>>  bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp);
>> +void blk_op_block(BlockBackend *blk, BlockOpType op, Error *reason);
>>  void blk_op_unblock(BlockBackend *blk, BlockOpType op, Error *reason);
>>  void blk_op_block_all(BlockBackend *blk, Error *reason);
>>  void blk_op_unblock_all(BlockBackend *blk, Error *reason);
>> -- 
>> 2.14.3
>>
>>
> 
> Fam
>
Kevin Wolf Feb. 12, 2018, 1:52 p.m. UTC | #3
Am 08.02.2018 um 11:42 hat Paolo Bonzini geschrieben:
> On 08/02/2018 02:35, Fam Zheng wrote:
> > On Wed, 02/07 17:36, Paolo Bonzini wrote:
> >> @@ -2626,6 +2656,36 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
> >>  
> >>      scsi_realize(&s->qdev, errp);
> >>      scsi_generic_read_device_identification(&s->qdev);
> >> +
> >> +    /* For op blockers, due to lack of support for dirty bitmaps.  */
> >> +    error_setg(&sb->mirror_source,
> >> +               "scsi-block does not support acting as a mirroring source");
> >> +    error_setg(&sb->commit_source,
> >> +               "scsi-block does not support acting as an active commit source");
> > 
> > An alternative way would be adding BLOCK_OP_TYPE_DIRTY_BITMAP. The error message
> > will not be as nice but it can be useful for another (blockjob) operation that
> > requires dirty bitmap support, or another device that doesn't support dirty
> > bitmaps. Though there isn't one for now.
> 
> Yeah, I thought about it.  Another possibility is make BLOCK_OP_TYPE_* a
> bitmask.  Then you can easily add a single Error * for multiple
> blockers, and BLOCK_OP_TYPE_DIRTY_BITMAP can be defined as
> BLOCK_OP_TYPE_MIRROR_SOURCE|BLOCK_OP_TYPE_COMMIT_SOURCE; likewise for
> notifiers below.

We shouldn't be adding new instances of BLOCK_OP_* at all. I couldn't
find the time yet to remove the existing ones, but any new protections
should be using the permission system.

I propose a new BLK_PERM_BYPASS that allows its users to bypass the
block layer I/O functions. In other words, bdrv_aio_ioctl() would
require that you got this permission. A dirty bitmap would keep a
BdrvChild with perm=0, shared=BLK_PERM_ALL & ~BLK_PERM_BYPASS, so you
can never have a dirty bitmap and a device using ioctls attached to the
BDS at the same time.

Kevin
Paolo Bonzini Feb. 12, 2018, 2 p.m. UTC | #4
On 12/02/2018 14:52, Kevin Wolf wrote:
> Am 08.02.2018 um 11:42 hat Paolo Bonzini geschrieben:
>> On 08/02/2018 02:35, Fam Zheng wrote:
>>> On Wed, 02/07 17:36, Paolo Bonzini wrote:
>>>> @@ -2626,6 +2656,36 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
>>>>  
>>>>      scsi_realize(&s->qdev, errp);
>>>>      scsi_generic_read_device_identification(&s->qdev);
>>>> +
>>>> +    /* For op blockers, due to lack of support for dirty bitmaps.  */
>>>> +    error_setg(&sb->mirror_source,
>>>> +               "scsi-block does not support acting as a mirroring source");
>>>> +    error_setg(&sb->commit_source,
>>>> +               "scsi-block does not support acting as an active commit source");
>>>
>>> An alternative way would be adding BLOCK_OP_TYPE_DIRTY_BITMAP. The error message
>>> will not be as nice but it can be useful for another (blockjob) operation that
>>> requires dirty bitmap support, or another device that doesn't support dirty
>>> bitmaps. Though there isn't one for now.
>>
>> Yeah, I thought about it.  Another possibility is make BLOCK_OP_TYPE_* a
>> bitmask.  Then you can easily add a single Error * for multiple
>> blockers, and BLOCK_OP_TYPE_DIRTY_BITMAP can be defined as
>> BLOCK_OP_TYPE_MIRROR_SOURCE|BLOCK_OP_TYPE_COMMIT_SOURCE; likewise for
>> notifiers below.
> 
> We shouldn't be adding new instances of BLOCK_OP_* at all. I couldn't
> find the time yet to remove the existing ones, but any new protections
> should be using the permission system.

I agree.  But does this include not fixing bugs wherever clients are
using the old op blockers?

Paolo

> I propose a new BLK_PERM_BYPASS that allows its users to bypass the
> block layer I/O functions. In other words, bdrv_aio_ioctl() would
> require that you got this permission. A dirty bitmap would keep a
> BdrvChild with perm=0, shared=BLK_PERM_ALL & ~BLK_PERM_BYPASS, so you
> can never have a dirty bitmap and a device using ioctls attached to the
> BDS at the same time.
Kevin Wolf Feb. 12, 2018, 2:30 p.m. UTC | #5
Am 12.02.2018 um 15:00 hat Paolo Bonzini geschrieben:
> On 12/02/2018 14:52, Kevin Wolf wrote:
> > Am 08.02.2018 um 11:42 hat Paolo Bonzini geschrieben:
> >> On 08/02/2018 02:35, Fam Zheng wrote:
> >>> On Wed, 02/07 17:36, Paolo Bonzini wrote:
> >>>> @@ -2626,6 +2656,36 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
> >>>>  
> >>>>      scsi_realize(&s->qdev, errp);
> >>>>      scsi_generic_read_device_identification(&s->qdev);
> >>>> +
> >>>> +    /* For op blockers, due to lack of support for dirty bitmaps.  */
> >>>> +    error_setg(&sb->mirror_source,
> >>>> +               "scsi-block does not support acting as a mirroring source");
> >>>> +    error_setg(&sb->commit_source,
> >>>> +               "scsi-block does not support acting as an active commit source");
> >>>
> >>> An alternative way would be adding BLOCK_OP_TYPE_DIRTY_BITMAP. The error message
> >>> will not be as nice but it can be useful for another (blockjob) operation that
> >>> requires dirty bitmap support, or another device that doesn't support dirty
> >>> bitmaps. Though there isn't one for now.
> >>
> >> Yeah, I thought about it.  Another possibility is make BLOCK_OP_TYPE_* a
> >> bitmask.  Then you can easily add a single Error * for multiple
> >> blockers, and BLOCK_OP_TYPE_DIRTY_BITMAP can be defined as
> >> BLOCK_OP_TYPE_MIRROR_SOURCE|BLOCK_OP_TYPE_COMMIT_SOURCE; likewise for
> >> notifiers below.
> > 
> > We shouldn't be adding new instances of BLOCK_OP_* at all. I couldn't
> > find the time yet to remove the existing ones, but any new protections
> > should be using the permission system.
> 
> I agree.  But does this include not fixing bugs wherever clients are
> using the old op blockers?

I'm not saying that we shouldn't fix the bug, just that we should fix it
properly with the best infrastructure we have.

The old op blockers are "fixing" the problem at the symptom level, and
you have to check for each high-level operation if it does something
problematic internally. You have to repeat this analysis every time you
add a new operation or modifiy an existing one (which noone ever does).
The risk that this breaks sooner or later is pretty high.

The new permission system, on the other hand, directly addresses the
root cause, and any new feature that uses dirty bitmaps will then
automatically get the protection, too.

So in fact, I would say that the bug isn't really fixed (but at best
papered over) until we add a proper fix on the permission level.

Kevin
Paolo Bonzini Feb. 12, 2018, 2:32 p.m. UTC | #6
On 12/02/2018 15:30, Kevin Wolf wrote:
>>> We shouldn't be adding new instances of BLOCK_OP_* at all. I couldn't
>>> find the time yet to remove the existing ones, but any new protections
>>> should be using the permission system.
>> I agree.  But does this include not fixing bugs wherever clients are
>> using the old op blockers?
> I'm not saying that we shouldn't fix the bug, just that we should fix it
> properly with the best infrastructure we have.
> 
> The old op blockers are "fixing" the problem at the symptom level, and
> you have to check for each high-level operation if it does something
> problematic internally. You have to repeat this analysis every time you
> add a new operation or modifiy an existing one (which noone ever does).
> The risk that this breaks sooner or later is pretty high.
> 
> The new permission system, on the other hand, directly addresses the
> root cause, and any new feature that uses dirty bitmaps will then
> automatically get the protection, too.
> 
> So in fact, I would say that the bug isn't really fixed (but at best
> papered over) until we add a proper fix on the permission level.

Okay, we are in agreement about this and you expressed very well why I
(at the gut feeling level) didn't like the old op blockers.  But you
bypassed the real question, which is: should I send a pull request for
these two patches or not? :)

Paolo
Kevin Wolf Feb. 12, 2018, 2:48 p.m. UTC | #7
Am 12.02.2018 um 15:32 hat Paolo Bonzini geschrieben:
> On 12/02/2018 15:30, Kevin Wolf wrote:
> >>> We shouldn't be adding new instances of BLOCK_OP_* at all. I couldn't
> >>> find the time yet to remove the existing ones, but any new protections
> >>> should be using the permission system.
> >> I agree.  But does this include not fixing bugs wherever clients are
> >> using the old op blockers?
> > I'm not saying that we shouldn't fix the bug, just that we should fix it
> > properly with the best infrastructure we have.
> > 
> > The old op blockers are "fixing" the problem at the symptom level, and
> > you have to check for each high-level operation if it does something
> > problematic internally. You have to repeat this analysis every time you
> > add a new operation or modifiy an existing one (which noone ever does).
> > The risk that this breaks sooner or later is pretty high.
> > 
> > The new permission system, on the other hand, directly addresses the
> > root cause, and any new feature that uses dirty bitmaps will then
> > automatically get the protection, too.
> > 
> > So in fact, I would say that the bug isn't really fixed (but at best
> > papered over) until we add a proper fix on the permission level.
> 
> Okay, we are in agreement about this and you expressed very well why I
> (at the gut feeling level) didn't like the old op blockers.  But you
> bypassed the real question, which is: should I send a pull request for
> these two patches or not? :)

I didn't spell it out that explicitly, but this is essentially a NACK.
I'd very much prefer if you could replace it with the proper solution.
Of course, we can always make exceptions when there is a good reason,
but with 2.12 still two months away, I doubt we have one.

Kevin
Paolo Bonzini Feb. 12, 2018, 2:50 p.m. UTC | #8
On 12/02/2018 15:48, Kevin Wolf wrote:
> Am 12.02.2018 um 15:32 hat Paolo Bonzini geschrieben:
>> Okay, we are in agreement about this and you expressed very well why I
>> (at the gut feeling level) didn't like the old op blockers.  But you
>> bypassed the real question, which is: should I send a pull request for
>> these two patches or not? :)
> 
> I didn't spell it out that explicitly, but this is essentially a NACK.
> I'd very much prefer if you could replace it with the proper solution.
> Of course, we can always make exceptions when there is a good reason,
> but with 2.12 still two months away, I doubt we have one.

Ok, I don't mind explicitness.  I'll keep these two patches in the queue
for now.

Paolo
Paolo Bonzini March 12, 2018, 11:10 a.m. UTC | #9
On 12/02/2018 15:50, Paolo Bonzini wrote:
> On 12/02/2018 15:48, Kevin Wolf wrote:
>> Am 12.02.2018 um 15:32 hat Paolo Bonzini geschrieben:
>>> Okay, we are in agreement about this and you expressed very well why I
>>> (at the gut feeling level) didn't like the old op blockers.  But you
>>> bypassed the real question, which is: should I send a pull request for
>>> these two patches or not? :)
>> I didn't spell it out that explicitly, but this is essentially a NACK.
>> I'd very much prefer if you could replace it with the proper solution.
>> Of course, we can always make exceptions when there is a good reason,
>> but with 2.12 still two months away, I doubt we have one.
> Ok, I don't mind explicitness.  I'll keep these two patches in the queue
> for now.

It's now one month away.  Regarding the solution below:

> I propose a new BLK_PERM_BYPASS that allows its users to bypass the
> block layer I/O functions. In other words, bdrv_aio_ioctl() would
> require that you got this permission. A dirty bitmap would keep a
> BdrvChild with perm=0, shared=BLK_PERM_ALL & ~BLK_PERM_BYPASS, so you
> can never have a dirty bitmap and a device using ioctls attached to the
> BDS at the same time.

I suppose it would be like:

- scsi-block/scsi-generic call blk_set_perm with perm == shared ==
BLK_PERM_BYPASS

- users of dirty bitmaps would call use perm/shared_perm as in your
message above

- dirty bitmaps creation calls bdrv_get_cumulative_perm (which should
now become public) and checks that it doesn't have BLK_PERM_BYPASS in
shared_perm

Anything I'm missing?

Paolo
Kevin Wolf March 12, 2018, 11:58 a.m. UTC | #10
Am 12.03.2018 um 12:10 hat Paolo Bonzini geschrieben:
> On 12/02/2018 15:50, Paolo Bonzini wrote:
> > On 12/02/2018 15:48, Kevin Wolf wrote:
> >> Am 12.02.2018 um 15:32 hat Paolo Bonzini geschrieben:
> >>> Okay, we are in agreement about this and you expressed very well why I
> >>> (at the gut feeling level) didn't like the old op blockers.  But you
> >>> bypassed the real question, which is: should I send a pull request for
> >>> these two patches or not? :)
> >> I didn't spell it out that explicitly, but this is essentially a NACK.
> >> I'd very much prefer if you could replace it with the proper solution.
> >> Of course, we can always make exceptions when there is a good reason,
> >> but with 2.12 still two months away, I doubt we have one.
> > Ok, I don't mind explicitness.  I'll keep these two patches in the queue
> > for now.
> 
> It's now one month away.  Regarding the solution below:
> 
> > I propose a new BLK_PERM_BYPASS that allows its users to bypass the
> > block layer I/O functions. In other words, bdrv_aio_ioctl() would
> > require that you got this permission. A dirty bitmap would keep a
> > BdrvChild with perm=0, shared=BLK_PERM_ALL & ~BLK_PERM_BYPASS, so you
> > can never have a dirty bitmap and a device using ioctls attached to the
> > BDS at the same time.
> 
> I suppose it would be like:
> 
> - scsi-block/scsi-generic call blk_set_perm with perm == shared ==
> BLK_PERM_BYPASS

perm = BLK_PERM_BYPASS is fine, but for shared it seems overly
restrictive. I don't think the device minds another user accessing the
device.

Other block devices do this in blkconf_apply_backend_options():

    shared_perm = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
                  BLK_PERM_GRAPH_MOD;
    if (resizable) {
        shared_perm |= BLK_PERM_RESIZE;
    }
    if (conf->share_rw) {
        shared_perm |= BLK_PERM_WRITE;
    }

I suppose scsi-generic is never resizable, so that part can go away, but
we do have a share-rw qdev property that can be used.

> - users of dirty bitmaps would call use perm/shared_perm as in your
> message above
> 
> - dirty bitmaps creation calls bdrv_get_cumulative_perm (which should
> now become public) and checks that it doesn't have BLK_PERM_BYPASS in
> shared_perm

My proposal was really that users of dirty bitmaps don't change
anything, but we do everything in the dirty bitmap implementation. Dirty
bitmap creation would add a BdrvChild with the above permissions.
Deleting a dirty bitmap would remove the BdrvChild again.

Then you don't need to manually call bdrv_get_cumulative_perm(), because
the permission check is included when you attach the BdrvChild.

> Anything I'm missing?

Ideally, bdrv_co_ioctl() should take a BdrvChild instead of a BDS and
assert that the caller correctly requested the permission:

    assert(child->perm & BLK_PERM_BYPASS);

Kevin
Paolo Bonzini April 5, 2018, 11:59 a.m. UTC | #11
On 12/03/2018 12:58, Kevin Wolf wrote:
>> - users of dirty bitmaps would call use perm/shared_perm as in your
>> message above
>>
>> - dirty bitmaps creation calls bdrv_get_cumulative_perm (which should
>> now become public) and checks that it doesn't have BLK_PERM_BYPASS in
>> shared_perm
> 
> My proposal was really that users of dirty bitmaps don't change
> anything, but we do everything in the dirty bitmap implementation. Dirty
> bitmap creation would add a BdrvChild with the above permissions.
> Deleting a dirty bitmap would remove the BdrvChild again.

This is also better because it works if somebody requests
BLK_PERM_BYPASS after dirty bitmap creation.

However, is there any better way than also creating a dummy BlockDriver
and BlockDriverState?  I first thought of a root role similar to
BlockBackend's, but BdrvChildRole doesn't have a way to inject its own
permissions.  I then tried moving bdrv_child_perm to BdrvChildRole, and
that almost works except that child_backing has special requirements
(mostly due to commit_top and mirror_top's special block drivers).

Perhaps child_perm should be in BdrvChildRole and BlockDriverState
should only have a bdrv_get_backing_perm (called by
child_backing.child_perm).  This makes sense to me since those
permissions are specific to the driver, e.g. whether it has metadata at
all.  But this becomes 2.13 material.

Do you still object to the two opblocker patches?

Paolo
Kevin Wolf April 5, 2018, 12:43 p.m. UTC | #12
Am 05.04.2018 um 13:59 hat Paolo Bonzini geschrieben:
> On 12/03/2018 12:58, Kevin Wolf wrote:
> >> - users of dirty bitmaps would call use perm/shared_perm as in your
> >> message above
> >>
> >> - dirty bitmaps creation calls bdrv_get_cumulative_perm (which should
> >> now become public) and checks that it doesn't have BLK_PERM_BYPASS in
> >> shared_perm
> > 
> > My proposal was really that users of dirty bitmaps don't change
> > anything, but we do everything in the dirty bitmap implementation. Dirty
> > bitmap creation would add a BdrvChild with the above permissions.
> > Deleting a dirty bitmap would remove the BdrvChild again.
> 
> This is also better because it works if somebody requests
> BLK_PERM_BYPASS after dirty bitmap creation.
> 
> However, is there any better way than also creating a dummy BlockDriver
> and BlockDriverState?  I first thought of a root role similar to
> BlockBackend's, but BdrvChildRole doesn't have a way to inject its own
> permissions.  I then tried moving bdrv_child_perm to BdrvChildRole, and
> that almost works except that child_backing has special requirements
> (mostly due to commit_top and mirror_top's special block drivers).

Have a look at block_job_add_bdrv(), which does the same thing to
restrict permissions while a block job is working on the subchain.

Essentially, yes, you'll probably have a new BdrvChildRole (I suppose
with .stay_at_node = true and .get_parent_desc only), but the place
where you specify the permissions is the bdrv_root_attach_child() call.
When you're done, you call bdrv_root_unref_child().

Kevin

> Perhaps child_perm should be in BdrvChildRole and BlockDriverState
> should only have a bdrv_get_backing_perm (called by
> child_backing.child_perm).  This makes sense to me since those
> permissions are specific to the driver, e.g. whether it has metadata at
> all.  But this becomes 2.13 material.
> 
> Do you still object to the two opblocker patches?
> 
> Paolo
diff mbox series

Patch

diff --git a/block/block-backend.c b/block/block-backend.c
index baef8e7abc..1759639a4a 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -1747,6 +1747,15 @@  bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp)
     return bdrv_op_is_blocked(bs, op, errp);
 }
 
+void blk_op_block(BlockBackend *blk, BlockOpType op, Error *reason)
+{
+    BlockDriverState *bs = blk_bs(blk);
+
+    if (bs) {
+        bdrv_op_block(bs, op, reason);
+    }
+}
+
 void blk_op_unblock(BlockBackend *blk, BlockOpType op, Error *reason)
 {
     BlockDriverState *bs = blk_bs(blk);
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 49d2559d93..023673cb04 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -2578,9 +2578,39 @@  static int get_device_type(SCSIDiskState *s)
     return 0;
 }
 
+typedef struct SCSIBlockState {
+    SCSIDiskState sd;
+    Error *mirror_source;
+    Error *backup_source;
+    Error *commit_source;
+    Notifier insert_bs;
+    Notifier remove_bs;
+} SCSIBlockState;
+
+static void scsi_block_insert_bs(Notifier *n, void *opaque)
+{
+    SCSIBlockState *sb = container_of(n, SCSIBlockState, insert_bs);
+    SCSIDiskState *s = &sb->sd;
+
+    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_MIRROR_SOURCE, sb->mirror_source);
+    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, sb->commit_source);
+    blk_op_block(s->qdev.conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, sb->backup_source);
+}
+
+static void scsi_block_remove_bs(Notifier *n, void *opaque)
+{
+    SCSIBlockState *sb = container_of(n, SCSIBlockState, remove_bs);
+    SCSIDiskState *s = &sb->sd;
+
+    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_MIRROR_SOURCE, sb->mirror_source);
+    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, sb->commit_source);
+    blk_op_unblock(s->qdev.conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, sb->backup_source);
+}
+
 static void scsi_block_realize(SCSIDevice *dev, Error **errp)
 {
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+    SCSIBlockState *sb = DO_UPCAST(SCSIBlockState, sd, s);
     int sg_version;
     int rc;
 
@@ -2626,6 +2656,36 @@  static void scsi_block_realize(SCSIDevice *dev, Error **errp)
 
     scsi_realize(&s->qdev, errp);
     scsi_generic_read_device_identification(&s->qdev);
+
+    /* For op blockers, due to lack of support for dirty bitmaps.  */
+    error_setg(&sb->mirror_source,
+               "scsi-block does not support acting as a mirroring source");
+    error_setg(&sb->commit_source,
+               "scsi-block does not support acting as an active commit source");
+
+    /* For op blockers, due to lack of support for write notifiers.  */
+    error_setg(&sb->backup_source,
+               "scsi-block does not support acting as a backup source");
+
+    sb->insert_bs.notify = scsi_block_insert_bs;
+    blk_add_insert_bs_notifier(s->qdev.conf.blk, &sb->insert_bs);
+    sb->remove_bs.notify = scsi_block_remove_bs;
+    blk_add_remove_bs_notifier(s->qdev.conf.blk, &sb->remove_bs);
+
+    scsi_block_insert_bs(&sb->insert_bs, s->qdev.conf.blk);
+}
+
+static void scsi_block_unrealize(SCSIDevice *dev, Error **errp)
+{
+    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+    SCSIBlockState *sb = DO_UPCAST(SCSIBlockState, sd, s);
+
+    notifier_remove(&sb->insert_bs);
+    notifier_remove(&sb->remove_bs);
+    scsi_block_remove_bs(&sb->insert_bs, s->qdev.conf.blk);
+    error_free(sb->mirror_source);
+    error_free(sb->commit_source);
+    error_free(sb->backup_source);
 }
 
 typedef struct SCSIBlockReq {
@@ -3017,6 +3077,7 @@  static void scsi_block_class_initfn(ObjectClass *klass, void *data)
     SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
 
     sc->realize      = scsi_block_realize;
+    sc->unrealize    = scsi_block_unrealize;
     sc->alloc_req    = scsi_block_new_request;
     sc->parse_cdb    = scsi_block_parse_cdb;
     sdc->dma_readv   = scsi_block_dma_readv;
@@ -3031,6 +3092,7 @@  static const TypeInfo scsi_block_info = {
     .name          = "scsi-block",
     .parent        = TYPE_SCSI_DISK_BASE,
     .class_init    = scsi_block_class_initfn,
+    .instance_size = sizeof(SCSIBlockState),
 };
 #endif
 
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index c4e52a5fa3..a48a49ca79 100644
--- a/include/sysemu/block-backend.h
+++ b/include/sysemu/block-backend.h
@@ -182,6 +182,7 @@  void blk_set_guest_block_size(BlockBackend *blk, int align);
 void *blk_try_blockalign(BlockBackend *blk, size_t size);
 void *blk_blockalign(BlockBackend *blk, size_t size);
 bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp);
+void blk_op_block(BlockBackend *blk, BlockOpType op, Error *reason);
 void blk_op_unblock(BlockBackend *blk, BlockOpType op, Error *reason);
 void blk_op_block_all(BlockBackend *blk, Error *reason);
 void blk_op_unblock_all(BlockBackend *blk, Error *reason);