diff mbox series

[1/5] nbd: Improve per-export flag handling in server

Message ID 20190823143726.27062-2-eblake@redhat.com (mailing list archive)
State New, archived
Headers show
Series Add NBD fast zero support to qemu client and server | expand

Commit Message

Eric Blake Aug. 23, 2019, 2:37 p.m. UTC
When creating a read-only image, we are still advertising support for
TRIM and WRITE_ZEROES to the client, even though the client should not
be issuing those commands.  But seeing this requires looking across
multiple functions:

All callers to nbd_export_new() passed a single flag based solely on
whether the export allows writes.  Later, we then pass a constant set
of flags to nbd_negotiate_options() (namely, the set of flags which we
always support, at least for writable images), which is then further
dynamically modified based on client requests for structured options.
Finally, when processing NBD_OPT_EXPORT_NAME or NBD_OPT_EXPORT_GO we
bitwise-or the original caller's flag with the runtime set of flags
we've built up over several functions.

Let's refactor things to instead compute a baseline of flags as soon
as possible, in nbd_export_new(), and changing the signature for the
callers to pass in a simpler bool rather than having to figure out
flags.  We can then get rid of the 'myflags' parameter to various
functions, and instead refer to client for everything we need (we
still have to perform a bitwise-OR during NBD_OPT_EXPORT_NAME and
NBD_OPT_EXPORT_GO, but it's easier to see what is being computed).
This lets us quit advertising senseless flags for read-only images, as
well as making the next patch for exposing FAST_ZERO support easier to
write.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/block/nbd.h |  2 +-
 blockdev-nbd.c      |  3 +--
 nbd/server.c        | 62 +++++++++++++++++++++++++--------------------
 qemu-nbd.c          |  6 ++---
 4 files changed, 39 insertions(+), 34 deletions(-)

Comments

Vladimir Sementsov-Ogievskiy Aug. 30, 2019, 6 p.m. UTC | #1
23.08.2019 17:37, Eric Blake wrote:
> When creating a read-only image, we are still advertising support for
> TRIM and WRITE_ZEROES to the client, even though the client should not
> be issuing those commands.  But seeing this requires looking across
> multiple functions:
> 
> All callers to nbd_export_new() passed a single flag based solely on
> whether the export allows writes.  Later, we then pass a constant set
> of flags to nbd_negotiate_options() (namely, the set of flags which we
> always support, at least for writable images), which is then further
> dynamically modified based on client requests for structured options.
> Finally, when processing NBD_OPT_EXPORT_NAME or NBD_OPT_EXPORT_GO we
> bitwise-or the original caller's flag with the runtime set of flags
> we've built up over several functions.
> 
> Let's refactor things to instead compute a baseline of flags as soon
> as possible, in nbd_export_new(), and changing the signature for the
> callers to pass in a simpler bool rather than having to figure out
> flags.  We can then get rid of the 'myflags' parameter to various
> functions, and instead refer to client for everything we need (we
> still have to perform a bitwise-OR during NBD_OPT_EXPORT_NAME and
> NBD_OPT_EXPORT_GO, but it's easier to see what is being computed).
> This lets us quit advertising senseless flags for read-only images, as
> well as making the next patch for exposing FAST_ZERO support easier to
> write.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   include/block/nbd.h |  2 +-
>   blockdev-nbd.c      |  3 +--
>   nbd/server.c        | 62 +++++++++++++++++++++++++--------------------
>   qemu-nbd.c          |  6 ++---
>   4 files changed, 39 insertions(+), 34 deletions(-)
> 
> diff --git a/include/block/nbd.h b/include/block/nbd.h
> index 991fd52a5134..2c87b42dfd48 100644
> --- a/include/block/nbd.h
> +++ b/include/block/nbd.h
> @@ -326,7 +326,7 @@ typedef struct NBDClient NBDClient;
> 
>   NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
>                             uint64_t size, const char *name, const char *desc,
> -                          const char *bitmap, uint16_t nbdflags, bool shared,
> +                          const char *bitmap, bool readonly, bool shared,
>                             void (*close)(NBDExport *), bool writethrough,
>                             BlockBackend *on_eject_blk, Error **errp);
>   void nbd_export_close(NBDExport *exp);
> diff --git a/blockdev-nbd.c b/blockdev-nbd.c
> index ecfa2ef0adb5..1cdffab4fce1 100644
> --- a/blockdev-nbd.c
> +++ b/blockdev-nbd.c
> @@ -187,8 +187,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
>           writable = false;
>       }
> 
> -    exp = nbd_export_new(bs, 0, len, name, NULL, bitmap,
> -                         writable ? 0 : NBD_FLAG_READ_ONLY, !writable,
> +    exp = nbd_export_new(bs, 0, len, name, NULL, bitmap, !writable, !writable,
>                            NULL, false, on_eject_blk, errp);
>       if (!exp) {
>           return;
> diff --git a/nbd/server.c b/nbd/server.c
> index 0fb41c6c50ea..b5577828aa44 100644
> --- a/nbd/server.c
> +++ b/nbd/server.c
> @@ -423,14 +423,14 @@ static void nbd_check_meta_export(NBDClient *client)
> 
>   /* Send a reply to NBD_OPT_EXPORT_NAME.
>    * Return -errno on error, 0 on success. */
> -static int nbd_negotiate_handle_export_name(NBDClient *client,
> -                                            uint16_t myflags, bool no_zeroes,
> +static int nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
>                                               Error **errp)
>   {
>       char name[NBD_MAX_NAME_SIZE + 1];
>       char buf[NBD_REPLY_EXPORT_NAME_SIZE] = "";
>       size_t len;
>       int ret;
> +    uint16_t myflags;
> 
>       /* Client sends:
>           [20 ..  xx]   export name (length bytes)
> @@ -458,10 +458,13 @@ static int nbd_negotiate_handle_export_name(NBDClient *client,
>           return -EINVAL;
>       }
> 
> -    trace_nbd_negotiate_new_style_size_flags(client->exp->size,
> -                                             client->exp->nbdflags | myflags);
> +    myflags = client->exp->nbdflags;
> +    if (client->structured_reply) {
> +        myflags |= NBD_FLAG_SEND_DF;
> +    }


why we cant do just
client->exp->nbdflags |= NBD_FLAG_SEND_DF ?

Or even do it earlier, when client->structured_reply becomes true?

> +    trace_nbd_negotiate_new_style_size_flags(client->exp->size, myflags);
>       stq_be_p(buf, client->exp->size);
> -    stw_be_p(buf + 8, client->exp->nbdflags | myflags);
> +    stw_be_p(buf + 8, myflags);
>       len = no_zeroes ? 10 : sizeof(buf);
>       ret = nbd_write(client->ioc, buf, len, errp);
>       if (ret < 0) {
> @@ -526,8 +529,7 @@ static int nbd_reject_length(NBDClient *client, bool fatal, Error **errp)
>   /* Handle NBD_OPT_INFO and NBD_OPT_GO.
>    * Return -errno on error, 0 if ready for next option, and 1 to move
>    * into transmission phase.  */
> -static int nbd_negotiate_handle_info(NBDClient *client, uint16_t myflags,
> -                                     Error **errp)
> +static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
>   {
>       int rc;
>       char name[NBD_MAX_NAME_SIZE + 1];

[..]
Eric Blake Aug. 30, 2019, 11:10 p.m. UTC | #2
On 8/30/19 1:00 PM, Vladimir Sementsov-Ogievskiy wrote:
> 23.08.2019 17:37, Eric Blake wrote:
>> When creating a read-only image, we are still advertising support for
>> TRIM and WRITE_ZEROES to the client, even though the client should not
>> be issuing those commands.  But seeing this requires looking across
>> multiple functions:
>>

>> @@ -458,10 +458,13 @@ static int nbd_negotiate_handle_export_name(NBDClient *client,
>>           return -EINVAL;
>>       }
>>
>> -    trace_nbd_negotiate_new_style_size_flags(client->exp->size,
>> -                                             client->exp->nbdflags | myflags);
>> +    myflags = client->exp->nbdflags;
>> +    if (client->structured_reply) {
>> +        myflags |= NBD_FLAG_SEND_DF;
>> +    }
> 
> 
> why we cant do just
> client->exp->nbdflags |= NBD_FLAG_SEND_DF ?

Because myflags is the runtime flags for _this_ client, while
client->exp->nbdflags are the base flags shared by _all_ clients.  If
client A requests structured reply, but client B does not, then we don't
want to advertise DF to client B; but amending client->exp->nbdflags
would have that effect.
Eric Blake Aug. 30, 2019, 11:32 p.m. UTC | #3
On 8/30/19 6:10 PM, Eric Blake wrote:
> On 8/30/19 1:00 PM, Vladimir Sementsov-Ogievskiy wrote:
>> 23.08.2019 17:37, Eric Blake wrote:
>>> When creating a read-only image, we are still advertising support for
>>> TRIM and WRITE_ZEROES to the client, even though the client should not
>>> be issuing those commands.  But seeing this requires looking across
>>> multiple functions:
>>>
> 
>>> @@ -458,10 +458,13 @@ static int nbd_negotiate_handle_export_name(NBDClient *client,
>>>           return -EINVAL;
>>>       }
>>>
>>> -    trace_nbd_negotiate_new_style_size_flags(client->exp->size,
>>> -                                             client->exp->nbdflags | myflags);
>>> +    myflags = client->exp->nbdflags;
>>> +    if (client->structured_reply) {
>>> +        myflags |= NBD_FLAG_SEND_DF;
>>> +    }
>>
>>
>> why we cant do just
>> client->exp->nbdflags |= NBD_FLAG_SEND_DF ?
> 
> Because myflags is the runtime flags for _this_ client, while
> client->exp->nbdflags are the base flags shared by _all_ clients.  If
> client A requests structured reply, but client B does not, then we don't
> want to advertise DF to client B; but amending client->exp->nbdflags
> would have that effect.

I stand corrected - it looks like a fresh client->exp is created per
client, as evidenced by:

diff --git i/nbd/client.c w/nbd/client.c
index b9dc829175f9..9e05f1a0e2a3 100644
--- i/nbd/client.c
+++ w/nbd/client.c
@@ -1011,6 +1011,8 @@ int nbd_receive_negotiate(AioContext *aio_context,
QIOChannel *ioc,
     assert(info->name);
     trace_nbd_receive_negotiate_name(info->name);

+    if (getenv ("MY_HACK"))
+        info->structured_reply = false;
     result = nbd_start_negotiate(aio_context, ioc, tlscreds, hostname,
outioc,
                                  info->structured_reply, &zeroes, errp);

diff --git i/nbd/server.c w/nbd/server.c
index d5078f7468af..6f3a83704fb3 100644
--- i/nbd/server.c
+++ w/nbd/server.c
@@ -457,6 +457,7 @@ static int
nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
     myflags = client->exp->nbdflags;
     if (client->structured_reply) {
         myflags |= NBD_FLAG_SEND_DF;
+        client->exp->nbdflags |= NBD_FLAG_SEND_DF;
     }
     trace_nbd_negotiate_new_style_size_flags(client->exp->size, myflags);
     stq_be_p(buf, client->exp->size);

$ ./qemu-nbd -r -f raw file -t &

$  ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
nbd://localhost:10809 -c quit
32145@1567207628.519883:nbd_receive_negotiate_size_flags Size is
1049088, export flags 0x48f

$ MY_HACK=1 ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
nbd://localhost:10809 -c quit
32156@1567207630.417815:nbd_receive_negotiate_size_flags Size is
1049088, export flags 0x40f

$  ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
nbd://localhost:10809 -c quit
32167@1567207635.202940:nbd_receive_negotiate_size_flags Size is
1049088, export flags 0x48f

The export flags change per client, so I _can_ store into
client->exp->nbdflags.  Will do that for v2.

Meanwhile, this points out a missing feature in libnbd - for testing
purposes, it would be really nice to be able to purposefully cripple the
client to NOT request structured replies automatically (default enabled,
but the ability to turn it off is useful for interop testing, as in this
thread).  I already recently added a --no-sr flag to nbdkit for a
similar reason (but that's creating a server which refuses to advertise,
where here I want a guest that refuses to ask).  Guess I'll be adding a
patch for that, too :)
Eric Blake Sept. 3, 2019, 4:39 p.m. UTC | #4
On 8/30/19 6:32 PM, Eric Blake wrote:

>>>> @@ -458,10 +458,13 @@ static int nbd_negotiate_handle_export_name(NBDClient *client,
>>>>           return -EINVAL;
>>>>       }
>>>>
>>>> -    trace_nbd_negotiate_new_style_size_flags(client->exp->size,
>>>> -                                             client->exp->nbdflags | myflags);
>>>> +    myflags = client->exp->nbdflags;
>>>> +    if (client->structured_reply) {
>>>> +        myflags |= NBD_FLAG_SEND_DF;
>>>> +    }
>>>
>>>
>>> why we cant do just
>>> client->exp->nbdflags |= NBD_FLAG_SEND_DF ?
>>
>> Because myflags is the runtime flags for _this_ client, while
>> client->exp->nbdflags are the base flags shared by _all_ clients.  If
>> client A requests structured reply, but client B does not, then we don't
>> want to advertise DF to client B; but amending client->exp->nbdflags
>> would have that effect.
> 
> I stand corrected - it looks like a fresh client->exp is created per
> client, as evidenced by:

I need to quit replying to myself, but my test was flawed.  Modern
clients don't go through NBD_OPT_EXPORT_NAME, so my added line...


> +++ w/nbd/server.c
> @@ -457,6 +457,7 @@ static int
> nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
>      myflags = client->exp->nbdflags;
>      if (client->structured_reply) {
>          myflags |= NBD_FLAG_SEND_DF;
> +        client->exp->nbdflags |= NBD_FLAG_SEND_DF;
>      }

...was not getting reached.  If I instead tweak NBD_OPT_GO:

diff --git i/nbd/server.c w/nbd/server.c
index 6f3a83704fb3..da1ef793f6df 100644
--- i/nbd/server.c
+++ w/nbd/server.c
@@ -640,6 +640,7 @@ static int nbd_negotiate_handle_info(NBDClient
*client, Error **errp)
     myflags = exp->nbdflags;
     if (client->structured_reply) {
         myflags |= NBD_FLAG_SEND_DF;
+        exp->nbdflags |= NBD_FLAG_SEND_DF;
     }
     trace_nbd_negotiate_new_style_size_flags(exp->size, myflags);
     stq_be_p(buf, exp->size);


> $ ./qemu-nbd -r -f raw file -t &
> 
> $  ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
> nbd://localhost:10809 -c quit
> 32145@1567207628.519883:nbd_receive_negotiate_size_flags Size is
> 1049088, export flags 0x48f
> 
> $ MY_HACK=1 ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
> nbd://localhost:10809 -c quit
> 32156@1567207630.417815:nbd_receive_negotiate_size_flags Size is
> 1049088, export flags 0x40f
> 

Then this reports 0x48f, proving that my initial reaction was correct:
client->exp is a shared resource across multiple connections, but
advertising DF must be a per-connection decision.

> $  ~/qemu/qemu-io -r -f raw --trace=nbd_\*size_flags
> nbd://localhost:10809 -c quit
> 32167@1567207635.202940:nbd_receive_negotiate_size_flags Size is
> 1049088, export flags 0x48f
> 
> The export flags change per client, so I _can_ store into
> client->exp->nbdflags.  Will do that for v2.

I see nothing to change for v2, so I'm inclined to take this patch as is.
Vladimir Sementsov-Ogievskiy Sept. 4, 2019, 5:08 p.m. UTC | #5
23.08.2019 17:37, Eric Blake wrote:
> When creating a read-only image, we are still advertising support for
> TRIM and WRITE_ZEROES to the client, even though the client should not
> be issuing those commands.  But seeing this requires looking across
> multiple functions:
> 
> All callers to nbd_export_new() passed a single flag based solely on
> whether the export allows writes.  Later, we then pass a constant set
> of flags to nbd_negotiate_options() (namely, the set of flags which we
> always support, at least for writable images), which is then further
> dynamically modified based on client requests for structured options.
> Finally, when processing NBD_OPT_EXPORT_NAME or NBD_OPT_EXPORT_GO we
> bitwise-or the original caller's flag with the runtime set of flags
> we've built up over several functions.
> 
> Let's refactor things to instead compute a baseline of flags as soon
> as possible, in nbd_export_new(), and changing the signature for the
> callers to pass in a simpler bool rather than having to figure out
> flags.  We can then get rid of the 'myflags' parameter to various
> functions, and instead refer to client for everything we need (we
> still have to perform a bitwise-OR during NBD_OPT_EXPORT_NAME and
> NBD_OPT_EXPORT_GO, but it's easier to see what is being computed).
> This lets us quit advertising senseless flags for read-only images, as
> well as making the next patch for exposing FAST_ZERO support easier to
> write.
> 
> Signed-off-by: Eric Blake<eblake@redhat.com>

Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
diff mbox series

Patch

diff --git a/include/block/nbd.h b/include/block/nbd.h
index 991fd52a5134..2c87b42dfd48 100644
--- a/include/block/nbd.h
+++ b/include/block/nbd.h
@@ -326,7 +326,7 @@  typedef struct NBDClient NBDClient;

 NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
                           uint64_t size, const char *name, const char *desc,
-                          const char *bitmap, uint16_t nbdflags, bool shared,
+                          const char *bitmap, bool readonly, bool shared,
                           void (*close)(NBDExport *), bool writethrough,
                           BlockBackend *on_eject_blk, Error **errp);
 void nbd_export_close(NBDExport *exp);
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index ecfa2ef0adb5..1cdffab4fce1 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -187,8 +187,7 @@  void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
         writable = false;
     }

-    exp = nbd_export_new(bs, 0, len, name, NULL, bitmap,
-                         writable ? 0 : NBD_FLAG_READ_ONLY, !writable,
+    exp = nbd_export_new(bs, 0, len, name, NULL, bitmap, !writable, !writable,
                          NULL, false, on_eject_blk, errp);
     if (!exp) {
         return;
diff --git a/nbd/server.c b/nbd/server.c
index 0fb41c6c50ea..b5577828aa44 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -423,14 +423,14 @@  static void nbd_check_meta_export(NBDClient *client)

 /* Send a reply to NBD_OPT_EXPORT_NAME.
  * Return -errno on error, 0 on success. */
-static int nbd_negotiate_handle_export_name(NBDClient *client,
-                                            uint16_t myflags, bool no_zeroes,
+static int nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
                                             Error **errp)
 {
     char name[NBD_MAX_NAME_SIZE + 1];
     char buf[NBD_REPLY_EXPORT_NAME_SIZE] = "";
     size_t len;
     int ret;
+    uint16_t myflags;

     /* Client sends:
         [20 ..  xx]   export name (length bytes)
@@ -458,10 +458,13 @@  static int nbd_negotiate_handle_export_name(NBDClient *client,
         return -EINVAL;
     }

-    trace_nbd_negotiate_new_style_size_flags(client->exp->size,
-                                             client->exp->nbdflags | myflags);
+    myflags = client->exp->nbdflags;
+    if (client->structured_reply) {
+        myflags |= NBD_FLAG_SEND_DF;
+    }
+    trace_nbd_negotiate_new_style_size_flags(client->exp->size, myflags);
     stq_be_p(buf, client->exp->size);
-    stw_be_p(buf + 8, client->exp->nbdflags | myflags);
+    stw_be_p(buf + 8, myflags);
     len = no_zeroes ? 10 : sizeof(buf);
     ret = nbd_write(client->ioc, buf, len, errp);
     if (ret < 0) {
@@ -526,8 +529,7 @@  static int nbd_reject_length(NBDClient *client, bool fatal, Error **errp)
 /* Handle NBD_OPT_INFO and NBD_OPT_GO.
  * Return -errno on error, 0 if ready for next option, and 1 to move
  * into transmission phase.  */
-static int nbd_negotiate_handle_info(NBDClient *client, uint16_t myflags,
-                                     Error **errp)
+static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
 {
     int rc;
     char name[NBD_MAX_NAME_SIZE + 1];
@@ -540,6 +542,7 @@  static int nbd_negotiate_handle_info(NBDClient *client, uint16_t myflags,
     uint32_t sizes[3];
     char buf[sizeof(uint64_t) + sizeof(uint16_t)];
     uint32_t check_align = 0;
+    uint16_t myflags;

     /* Client sends:
         4 bytes: L, name length (can be 0)
@@ -637,10 +640,13 @@  static int nbd_negotiate_handle_info(NBDClient *client, uint16_t myflags,
     }

     /* Send NBD_INFO_EXPORT always */
-    trace_nbd_negotiate_new_style_size_flags(exp->size,
-                                             exp->nbdflags | myflags);
+    myflags = exp->nbdflags;
+    if (client->structured_reply) {
+        myflags |= NBD_FLAG_SEND_DF;
+    }
+    trace_nbd_negotiate_new_style_size_flags(exp->size, myflags);
     stq_be_p(buf, exp->size);
-    stw_be_p(buf + 8, exp->nbdflags | myflags);
+    stw_be_p(buf + 8, myflags);
     rc = nbd_negotiate_send_info(client, NBD_INFO_EXPORT,
                                  sizeof(buf), buf, errp);
     if (rc < 0) {
@@ -1037,8 +1043,7 @@  static int nbd_negotiate_meta_queries(NBDClient *client,
  * 1       if client sent NBD_OPT_ABORT, i.e. on valid disconnect,
  *         errp is not set
  */
-static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
-                                 Error **errp)
+static int nbd_negotiate_options(NBDClient *client, Error **errp)
 {
     uint32_t flags;
     bool fixedNewstyle = false;
@@ -1172,13 +1177,12 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                 return 1;

             case NBD_OPT_EXPORT_NAME:
-                return nbd_negotiate_handle_export_name(client,
-                                                        myflags, no_zeroes,
+                return nbd_negotiate_handle_export_name(client, no_zeroes,
                                                         errp);

             case NBD_OPT_INFO:
             case NBD_OPT_GO:
-                ret = nbd_negotiate_handle_info(client, myflags, errp);
+                ret = nbd_negotiate_handle_info(client, errp);
                 if (ret == 1) {
                     assert(option == NBD_OPT_GO);
                     return 0;
@@ -1209,7 +1213,6 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                 } else {
                     ret = nbd_negotiate_send_rep(client, NBD_REP_ACK, errp);
                     client->structured_reply = true;
-                    myflags |= NBD_FLAG_SEND_DF;
                 }
                 break;

@@ -1232,8 +1235,7 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
              */
             switch (option) {
             case NBD_OPT_EXPORT_NAME:
-                return nbd_negotiate_handle_export_name(client,
-                                                        myflags, no_zeroes,
+                return nbd_negotiate_handle_export_name(client, no_zeroes,
                                                         errp);

             default:
@@ -1259,9 +1261,6 @@  static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp)
 {
     char buf[NBD_OLDSTYLE_NEGOTIATE_SIZE] = "";
     int ret;
-    const uint16_t myflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_TRIM |
-                              NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA |
-                              NBD_FLAG_SEND_WRITE_ZEROES | NBD_FLAG_SEND_CACHE);

     /* Old style negotiation header, no room for options
         [ 0 ..   7]   passwd       ("NBDMAGIC")
@@ -1289,7 +1288,7 @@  static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp)
         error_prepend(errp, "write failed: ");
         return -EINVAL;
     }
-    ret = nbd_negotiate_options(client, myflags, errp);
+    ret = nbd_negotiate_options(client, errp);
     if (ret != 0) {
         if (ret < 0) {
             error_prepend(errp, "option negotiation failed: ");
@@ -1461,7 +1460,7 @@  static void nbd_eject_notifier(Notifier *n, void *data)

 NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
                           uint64_t size, const char *name, const char *desc,
-                          const char *bitmap, uint16_t nbdflags, bool shared,
+                          const char *bitmap, bool readonly, bool shared,
                           void (*close)(NBDExport *), bool writethrough,
                           BlockBackend *on_eject_blk, Error **errp)
 {
@@ -1485,10 +1484,8 @@  NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
     /* Don't allow resize while the NBD server is running, otherwise we don't
      * care what happens with the node. */
     perm = BLK_PERM_CONSISTENT_READ;
-    if ((nbdflags & NBD_FLAG_READ_ONLY) == 0) {
+    if (!readonly) {
         perm |= BLK_PERM_WRITE;
-    } else if (shared) {
-        nbdflags |= NBD_FLAG_CAN_MULTI_CONN;
     }
     blk = blk_new(bdrv_get_aio_context(bs), perm,
                   BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
@@ -1507,7 +1504,16 @@  NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
     exp->dev_offset = dev_offset;
     exp->name = g_strdup(name);
     exp->description = g_strdup(desc);
-    exp->nbdflags = nbdflags;
+    exp->nbdflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_FLUSH |
+                     NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_CACHE);
+    if (readonly) {
+        exp->nbdflags |= NBD_FLAG_READ_ONLY;
+        if (shared) {
+            exp->nbdflags |= NBD_FLAG_CAN_MULTI_CONN;
+        }
+    } else {
+        exp->nbdflags |= NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_WRITE_ZEROES;
+    }
     assert(size <= INT64_MAX - dev_offset);
     exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);

@@ -1532,7 +1538,7 @@  NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
             goto fail;
         }

-        if ((nbdflags & NBD_FLAG_READ_ONLY) && bdrv_is_writable(bs) &&
+        if (readonly && bdrv_is_writable(bs) &&
             bdrv_dirty_bitmap_enabled(bm)) {
             error_setg(errp,
                        "Enabled bitmap '%s' incompatible with readonly export",
diff --git a/qemu-nbd.c b/qemu-nbd.c
index 55f5ceaf5c92..079702bb837f 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -600,7 +600,7 @@  int main(int argc, char **argv)
     BlockBackend *blk;
     BlockDriverState *bs;
     uint64_t dev_offset = 0;
-    uint16_t nbdflags = 0;
+    bool readonly = false;
     bool disconnect = false;
     const char *bindto = NULL;
     const char *port = NULL;
@@ -782,7 +782,7 @@  int main(int argc, char **argv)
             }
             /* fall through */
         case 'r':
-            nbdflags |= NBD_FLAG_READ_ONLY;
+            readonly = true;
             flags &= ~BDRV_O_RDWR;
             break;
         case 'P':
@@ -1173,7 +1173,7 @@  int main(int argc, char **argv)
     }

     export = nbd_export_new(bs, dev_offset, fd_size, export_name,
-                            export_description, bitmap, nbdflags, shared > 1,
+                            export_description, bitmap, readonly, shared > 1,
                             nbd_export_closed, writethrough, NULL,
                             &error_fatal);