diff mbox series

[v5,05/31] block-backend: special comments for blk_set/get_perm due to fuse

Message ID 20211124064418.3120601-6-eesposit@redhat.com (mailing list archive)
State New, archived
Headers show
Series block layer: split block APIs in global state and I/O | expand

Commit Message

Emanuele Giuseppe Esposito Nov. 24, 2021, 6:43 a.m. UTC
Fuse logic can be classified as I/O, so there is no BQL held
during its execution. And yet, it uses blk_{get/set}_perm
functions, that are classified as BQL and clearly require
the BQL lock. Since there is no easy solution for this,
add a couple of TODOs and FIXME in the relevant sections of the
code.

Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
 block/block-backend.c | 10 ++++++++++
 block/export/fuse.c   | 16 ++++++++++++++--
 2 files changed, 24 insertions(+), 2 deletions(-)

Comments

Hanna Czenczek Dec. 10, 2021, 2:38 p.m. UTC | #1
On 24.11.21 07:43, Emanuele Giuseppe Esposito wrote:
> Fuse logic can be classified as I/O, so there is no BQL held
> during its execution. And yet, it uses blk_{get/set}_perm
> functions, that are classified as BQL and clearly require
> the BQL lock. Since there is no easy solution for this,
> add a couple of TODOs and FIXME in the relevant sections of the
> code.

Well, the problem goes deeper, because we still consider them GS 
functions.  So it’s fine for the permission function 
raw_handle_perm_lock() to call bdrv_get_flags(), which is a GS function, 
and then you still get:

qemu-storage-daemon: ../block.c:6195: bdrv_get_flags: Assertion 
`qemu_in_main_thread()' failed.

It looks like in this case making bdrv_get_flags() not a GS function 
would be feasible and might solve the problem, but I guess we should 
instead think about adding something like

if (!exp->growable && !qemu_in_main_thread()) {
     /* Changing permissions like below only works in the main thread */
     return -EPERM;
}

to fuse_do_truncate().

Ideally, to make up for this, we should probably take the RESIZE 
permission in fuse_export_create() for writable exports in iothreads.

> Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
> ---
>   block/block-backend.c | 10 ++++++++++
>   block/export/fuse.c   | 16 ++++++++++++++--
>   2 files changed, 24 insertions(+), 2 deletions(-)
>
> diff --git a/block/block-backend.c b/block/block-backend.c
> index 1f0bda578e..7a4b50a8f0 100644
> --- a/block/block-backend.c
> +++ b/block/block-backend.c
> @@ -888,6 +888,11 @@ int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
>                    Error **errp)
>   {
>       int ret;
> +    /*
> +     * FIXME: blk_{get/set}_perm should be always called under
> +     * BQL, but it is not the case right now (see block/export/fuse.c)
> +     */
> +    /* assert(qemu_in_main_thread()); */
>   
>       if (blk->root && !blk->disable_perm) {
>           ret = bdrv_child_try_set_perm(blk->root, perm, shared_perm, errp);
> @@ -904,6 +909,11 @@ int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
>   
>   void blk_get_perm(BlockBackend *blk, uint64_t *perm, uint64_t *shared_perm)
>   {
> +    /*
> +     * FIXME: blk_{get/set}_perm should be always called under
> +     * BQL, but it is not the case right now (see block/export/fuse.c)
> +     */
> +    /* assert(qemu_in_main_thread()); */
>       *perm = blk->perm;
>       *shared_perm = blk->shared_perm;
>   }
> diff --git a/block/export/fuse.c b/block/export/fuse.c
> index 823c126d23..7ceb8d783b 100644
> --- a/block/export/fuse.c
> +++ b/block/export/fuse.c
> @@ -89,7 +89,10 @@ static int fuse_export_create(BlockExport *blk_exp,
>       /* For growable exports, take the RESIZE permission */
>       if (args->growable) {
>           uint64_t blk_perm, blk_shared_perm;
> -
> +        /*
> +         * FIXME: blk_{get/set}_perm should not be here, as permissions
> +         * should be modified only under BQL and here we are not!
> +         */

I believe we are, BlockExportDriver.create() is called by blk_exp_add(), 
which looks very much like it runs in the main thread (calling 
bdrv_lookup_bs(), bdrv_try_set_aio_context(), blk_new(), and whatnot).

Hanna

>           blk_get_perm(exp->common.blk, &blk_perm, &blk_shared_perm);
>   
>           ret = blk_set_perm(exp->common.blk, blk_perm | BLK_PERM_RESIZE,
> @@ -400,6 +403,11 @@ static int fuse_do_truncate(const FuseExport *exp, int64_t size,
>   
>       /* Growable exports have a permanent RESIZE permission */
>       if (!exp->growable) {
> +
> +        /*
> +         * FIXME: blk_{get/set}_perm should not be here, as permissions
> +         * should be modified only under BQL and here we are not!
> +         */
>           blk_get_perm(exp->common.blk, &blk_perm, &blk_shared_perm);
>   
>           ret = blk_set_perm(exp->common.blk, blk_perm | BLK_PERM_RESIZE,
> @@ -413,7 +421,11 @@ static int fuse_do_truncate(const FuseExport *exp, int64_t size,
>                          truncate_flags, NULL);
>   
>       if (!exp->growable) {
> -        /* Must succeed, because we are only giving up the RESIZE permission */
> +        /*
> +         * Must succeed, because we are only giving up the RESIZE permission.
> +         * FIXME: blk_{get/set}_perm should not be here, as permissions
> +         * should be modified only under BQL and here we are not!
> +         */
>           blk_set_perm(exp->common.blk, blk_perm, blk_shared_perm, &error_abort);
>       }
>
Emanuele Giuseppe Esposito Dec. 15, 2021, 8:57 a.m. UTC | #2
On 10/12/2021 15:38, Hanna Reitz wrote:
> On 24.11.21 07:43, Emanuele Giuseppe Esposito wrote:
>> Fuse logic can be classified as I/O, so there is no BQL held
>> during its execution. And yet, it uses blk_{get/set}_perm
>> functions, that are classified as BQL and clearly require
>> the BQL lock. Since there is no easy solution for this,
>> add a couple of TODOs and FIXME in the relevant sections of the
>> code.
> 
> Well, the problem goes deeper, because we still consider them GS 
> functions.  So it’s fine for the permission function 
> raw_handle_perm_lock() to call bdrv_get_flags(), which is a GS function, 
> and then you still get:
> 
> qemu-storage-daemon: ../block.c:6195: bdrv_get_flags: Assertion 
> `qemu_in_main_thread()' failed.
> 
> It looks like in this case making bdrv_get_flags() not a GS function 
> would be feasible and might solve the problem, but I guess we should 
> instead think about adding something like
> 
> if (!exp->growable && !qemu_in_main_thread()) {
>      /* Changing permissions like below only works in the main thread */
>      return -EPERM;
> }
> 
> to fuse_do_truncate().
> 
> Ideally, to make up for this, we should probably take the RESIZE 
> permission in fuse_export_create() for writable exports in iothreads.

I think I got it. I will send the RESIZE permission fix in a separate patch.

Thank you,
Emanuele
Emanuele Giuseppe Esposito Dec. 15, 2021, 10:13 a.m. UTC | #3
On 15/12/2021 09:57, Emanuele Giuseppe Esposito wrote:
> 
> 
> On 10/12/2021 15:38, Hanna Reitz wrote:
>> On 24.11.21 07:43, Emanuele Giuseppe Esposito wrote:
>>> Fuse logic can be classified as I/O, so there is no BQL held
>>> during its execution. And yet, it uses blk_{get/set}_perm
>>> functions, that are classified as BQL and clearly require
>>> the BQL lock. Since there is no easy solution for this,
>>> add a couple of TODOs and FIXME in the relevant sections of the
>>> code.
>>
>> Well, the problem goes deeper, because we still consider them GS 
>> functions.  So it’s fine for the permission function 
>> raw_handle_perm_lock() to call bdrv_get_flags(), which is a GS 
>> function, and then you still get:
>>
>> qemu-storage-daemon: ../block.c:6195: bdrv_get_flags: Assertion 
>> `qemu_in_main_thread()' failed.

Wait... Now that I read it more carefully I am confused about this. I 
don't think the above has to do with fuse, right?
Can you share the command that makes qemu-storage-daemon fail?

raw_handle_perm_lock() is currently called by these three callbacks:
     .bdrv_check_perm = raw_check_perm,
     .bdrv_set_perm   = raw_set_perm,
     .bdrv_abort_perm_update = raw_abort_perm_update,

all three function pointers are defined as GS functions. So I don't 
understand why is this failing.

>>
>> It looks like in this case making bdrv_get_flags() not a GS function 
>> would be feasible and might solve the problem, but I guess we should 
>> instead think about adding something like
>>
>> if (!exp->growable && !qemu_in_main_thread()) {
>>      /* Changing permissions like below only works in the main thread */
>>      return -EPERM;
>> }
>>
>> to fuse_do_truncate().
>>
>> Ideally, to make up for this, we should probably take the RESIZE 
>> permission in fuse_export_create() for writable exports in iothreads.
> 
> I think I got it. I will send the RESIZE permission fix in a separate 
> patch.
> 
> Thank you,
> Emanuele
Hanna Czenczek Dec. 16, 2021, 2 p.m. UTC | #4
On 15.12.21 11:13, Emanuele Giuseppe Esposito wrote:
>
>
> On 15/12/2021 09:57, Emanuele Giuseppe Esposito wrote:
>>
>>
>> On 10/12/2021 15:38, Hanna Reitz wrote:
>>> On 24.11.21 07:43, Emanuele Giuseppe Esposito wrote:
>>>> Fuse logic can be classified as I/O, so there is no BQL held
>>>> during its execution. And yet, it uses blk_{get/set}_perm
>>>> functions, that are classified as BQL and clearly require
>>>> the BQL lock. Since there is no easy solution for this,
>>>> add a couple of TODOs and FIXME in the relevant sections of the
>>>> code.
>>>
>>> Well, the problem goes deeper, because we still consider them GS 
>>> functions.  So it’s fine for the permission function 
>>> raw_handle_perm_lock() to call bdrv_get_flags(), which is a GS 
>>> function, and then you still get:
>>>
>>> qemu-storage-daemon: ../block.c:6195: bdrv_get_flags: Assertion 
>>> `qemu_in_main_thread()' failed.
>
> Wait... Now that I read it more carefully I am confused about this. I 
> don't think the above has to do with fuse, right?

It does, because the problem is that the FUSE export code 
(fuse_do_truncate()) calls a permission function (blk_set_perm()), and 
then we assume in those permission functions that they can call GS 
functions, like bdrv_get_flags() (called from raw_handle_perm_lock()).  
So in practice, the permission functions are still effectively GS 
functions, and just dropping the assertions from blk_set/get_perm() 
doesn’t really help.

(This is the state on this patch; patch 7 adds an assertion in 
bdrv_child_try_set_perm(), and so from then on, that assertion fails 
before the one in bdrv_get_flags() can.)

> Can you share the command that makes qemu-storage-daemon fail?

Sure:

$ touch /tmp/fuse-export

$ storage-daemon/qemu-storage-daemon \
   --object iothread,id=iothr0 \
   --blockdev file,node-name=node0,filename=/tmp/fuse-export \
   --export 
fuse,id=exp0,node-name=node0,mountpoint=/tmp/fuse-export,iothread=iothr0,writable=true 
\
   &
[1] 96997

$ truncate /tmp/fuse-export -s 1M
qemu-storage-daemon: ../block.c:6197: bdrv_get_flags: Assertion 
`qemu_in_main_thread()' failed.
truncate: failed to truncate '/tmp/fuse-export' at 1048576 bytes: 
Software caused connection abort
truncate: failed to close '/tmp/fuse-export': Transport endpoint is not 
connected
[1]  + 96997 IOT instruction (core dumped) 
storage-daemon/qemu-storage-daemon --object iothread,id=iothr0 --blockdev

$ fusermount -u /tmp/fuse-export

Relevant part of the stacktrace:

#5  0x00007f68322243a6 in __GI___assert_fail 
(assertion=assertion@entry=0x562380ca2f53 "qemu_in_main_thread()", 
file=file@entry=0x562380ca53cd "../block.c", line=line@entry=6197,
     function=function@entry=0x562380ca78e8 <__PRETTY_FUNCTION__.63> 
"bdrv_get_flags") at assert.c:101
#6  0x0000562380b64283 in bdrv_get_flags (bs=0x562382440680) at 
../block.c:6197
#7  0x0000562380b68506 in bdrv_get_flags (bs=bs@entry=0x562382440680) at 
../block.c:6199
#8  0x0000562380be6d18 in raw_handle_perm_lock (errp=0x7f68277103b0, 
new_shared=31, new_perm=11, op=RAW_PL_PREPARE, bs=0x562382440680) at 
../block/file-posix.c:975
#9  raw_check_perm (bs=0x562382440680, perm=11, shared=31, 
errp=0x7f68277103b0) at ../block/file-posix.c:3172
#10 0x0000562380b66db5 in bdrv_drv_set_perm (errp=0x7f68277103b0, 
tran=0x7f68180038b0, shared_perm=31, perm=11, bs=0x562382440680) at 
../block.c:2272
#11 bdrv_node_refresh_perm (errp=0x7f68277103b0, tran=0x7f68180038b0, 
q=0x0, bs=0x562382440680) at ../block.c:2441
#12 bdrv_list_refresh_perms (list=list@entry=0x56238243a1c0 = {...}, 
q=q@entry=0x0, tran=tran@entry=0x7f68180038b0, 
errp=errp@entry=0x7f68277103b0) at ../block.c:2479
#13 0x0000562380b67098 in bdrv_refresh_perms (bs=0x562382440680, 
errp=errp@entry=0x7f68277103b0) at ../block.c:2542
#14 0x0000562380b6727c in bdrv_child_try_set_perm (c=0x56238243cf70, 
perm=11, shared=31, errp=0x0) at ../block.c:2557
#15 0x0000562380b8632d in blk_set_perm (blk=0x56238243e7a0, perm=11, 
shared_perm=31, errp=errp@entry=0x0) at ../block/block-backend.c:890
#16 0x0000562380b57a7d in fuse_do_truncate 
(exp=exp@entry=0x56238243eb20, size=1048576, 
req_zero_write=req_zero_write@entry=true, 
prealloc=prealloc@entry=PREALLOC_MODE_OFF) at ../block/export/fuse.c:405
#17 0x0000562380b58121 in fuse_setattr (req=0x7f6818003800, inode=1, 
statbuf=0x7f68277104e0, to_set=8, fi=0x7f68277104b0) at 
../block/export/fuse.c:476

> raw_handle_perm_lock() is currently called by these three callbacks:
>     .bdrv_check_perm = raw_check_perm,
>     .bdrv_set_perm   = raw_set_perm,
>     .bdrv_abort_perm_update = raw_abort_perm_update,
>
> all three function pointers are defined as GS functions. So I don't 
> understand why is this failing.

Because this patch explicitly allows I/O code to call 
blk_set/get_perm().  But as you rightly say, all permission functions 
are still classified as GS code, so we can’t allow it.

Hanna

>>>
>>> It looks like in this case making bdrv_get_flags() not a GS function 
>>> would be feasible and might solve the problem, but I guess we should 
>>> instead think about adding something like
>>>
>>> if (!exp->growable && !qemu_in_main_thread()) {
>>>      /* Changing permissions like below only works in the main 
>>> thread */
>>>      return -EPERM;
>>> }
>>>
>>> to fuse_do_truncate().
>>>
>>> Ideally, to make up for this, we should probably take the RESIZE 
>>> permission in fuse_export_create() for writable exports in iothreads.
>>
>> I think I got it. I will send the RESIZE permission fix in a separate 
>> patch.
>>
>> Thank you,
>> Emanuele
>
diff mbox series

Patch

diff --git a/block/block-backend.c b/block/block-backend.c
index 1f0bda578e..7a4b50a8f0 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -888,6 +888,11 @@  int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
                  Error **errp)
 {
     int ret;
+    /*
+     * FIXME: blk_{get/set}_perm should be always called under
+     * BQL, but it is not the case right now (see block/export/fuse.c)
+     */
+    /* assert(qemu_in_main_thread()); */
 
     if (blk->root && !blk->disable_perm) {
         ret = bdrv_child_try_set_perm(blk->root, perm, shared_perm, errp);
@@ -904,6 +909,11 @@  int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
 
 void blk_get_perm(BlockBackend *blk, uint64_t *perm, uint64_t *shared_perm)
 {
+    /*
+     * FIXME: blk_{get/set}_perm should be always called under
+     * BQL, but it is not the case right now (see block/export/fuse.c)
+     */
+    /* assert(qemu_in_main_thread()); */
     *perm = blk->perm;
     *shared_perm = blk->shared_perm;
 }
diff --git a/block/export/fuse.c b/block/export/fuse.c
index 823c126d23..7ceb8d783b 100644
--- a/block/export/fuse.c
+++ b/block/export/fuse.c
@@ -89,7 +89,10 @@  static int fuse_export_create(BlockExport *blk_exp,
     /* For growable exports, take the RESIZE permission */
     if (args->growable) {
         uint64_t blk_perm, blk_shared_perm;
-
+        /*
+         * FIXME: blk_{get/set}_perm should not be here, as permissions
+         * should be modified only under BQL and here we are not!
+         */
         blk_get_perm(exp->common.blk, &blk_perm, &blk_shared_perm);
 
         ret = blk_set_perm(exp->common.blk, blk_perm | BLK_PERM_RESIZE,
@@ -400,6 +403,11 @@  static int fuse_do_truncate(const FuseExport *exp, int64_t size,
 
     /* Growable exports have a permanent RESIZE permission */
     if (!exp->growable) {
+
+        /*
+         * FIXME: blk_{get/set}_perm should not be here, as permissions
+         * should be modified only under BQL and here we are not!
+         */
         blk_get_perm(exp->common.blk, &blk_perm, &blk_shared_perm);
 
         ret = blk_set_perm(exp->common.blk, blk_perm | BLK_PERM_RESIZE,
@@ -413,7 +421,11 @@  static int fuse_do_truncate(const FuseExport *exp, int64_t size,
                        truncate_flags, NULL);
 
     if (!exp->growable) {
-        /* Must succeed, because we are only giving up the RESIZE permission */
+        /*
+         * Must succeed, because we are only giving up the RESIZE permission.
+         * FIXME: blk_{get/set}_perm should not be here, as permissions
+         * should be modified only under BQL and here we are not!
+         */
         blk_set_perm(exp->common.blk, blk_perm, blk_shared_perm, &error_abort);
     }