The host_device block driver refuses to open a block device read-write
if the BLKROGET ioctl tells it that it's actually read-only. For such
devices it's possible to open a read-write file descriptor, but any
writes to the device would fail.

There are valid use cases for which this check gets in the way, as
explained in the bug report:

    However, this behavior prevents a legitimate enterprise use case:
    running VMs against storage replication target LUNs in a disaster
    recovery configuration. Enterprise storage arrays (Dell Powermax,
    Pure Storage) set the SCSI Write Protect bit on replication target
    LUNs, causing the Linux kernel to mark them read-only via BLKROGET.
    The VMs on the passive DR site need to start with these LUNs
    attached - writes are expected to fail (the application middleware
    knows it's on the passive side), and after a DR role swap the LUN
    becomes writable and writes should succeed immediately without any
    VM restart.

In order to allow such use cases in which a currently read-only device
is deliberately opened read-write, add a new option to the host_device
driver that allows the user to skip the check.

Buglink: https://redhat.atlassian.net/browse/RHEL-186136
Signed-off-by: Kevin Wolf <[email protected]>
---
 qapi/block-core.json | 19 ++++++++++++++++++-
 block/file-posix.c   | 23 ++++++++++++++++++++---
 2 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 741c8165c06..80272b878c5 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3390,6 +3390,23 @@
   'features': [ { 'name': 'dynamic-auto-read-only',
                   'if': 'CONFIG_POSIX' } ] }
 
+##
+# @BlockdevOptionsHostDevice:
+#     Driver specific block device options for the host_device
+#     backend.
+#
+# @accept-read-only-device: allow opening a read-only device as
+#     read-write.  If this is false, opening a host device read-write
+#     requires on Linux that the BLKROGET ioctl considers the device
+#     writable.  If it is true, this check is skipped.  (since: 11.2,
+#     default: false)
+#
+# Since: 11.2
+##
+{ 'struct': 'BlockdevOptionsHostDevice',
+  'base': 'BlockdevOptionsFile',
+  'data': { '*accept-read-only-device': 'bool' } }
+
 ##
 # @BlockdevOptionsNull:
 #     Driver specific block device options for the null backend.
@@ -4777,7 +4794,7 @@
       'ftps':       'BlockdevOptionsCurlFtps',
       'host_cdrom':  { 'type': 'BlockdevOptionsFile',
                        'if': 'HAVE_HOST_BLOCK_DEVICE' },
-      'host_device': { 'type': 'BlockdevOptionsFile',
+      'host_device': { 'type': 'BlockdevOptionsHostDevice',
                        'if': 'HAVE_HOST_BLOCK_DEVICE' },
       'http':       'BlockdevOptionsCurlHttp',
       'https':      'BlockdevOptionsCurlHttps',
diff --git a/block/file-posix.c b/block/file-posix.c
index bad7ccdb733..3318912b5ac 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -177,6 +177,7 @@ typedef struct BDRVRawState {
     bool has_laio_fdsync:1;
     bool use_linux_io_uring:1;
     bool use_mpath:1;
+    bool accept_read_only_device:1;
     int page_cache_inconsistent; /* errno from fdatasync failure */
     bool has_fallocate;
     bool needs_alignment;
@@ -513,7 +514,7 @@ static void raw_probe_alignment(BlockDriverState *bs, int 
fd, Error **errp)
     }
 }
 
-static int check_hdev_writable(int fd)
+static int check_hdev_writable(BlockDriverState *bs, int fd)
 {
 #if defined(BLKROGET)
     /* Linux block devices can be configured "read-only" using blockdev(8).
@@ -523,10 +524,19 @@ static int check_hdev_writable(int fd)
      * bdrv_open() is supposed to fail if the disk is read-only.  Explicitly
      * check for read-only block devices so that Linux block devices behave
      * properly.
+     *
+     * Users can use the accept-read-only-device option to skip the check if
+     * they deliberately open a read-only block device as writable, e.g. 
because
+     * it may become read-write later.
      */
+    BDRVRawState *s = bs->opaque;
     struct stat st;
     int readonly = 0;
 
+    if (s->accept_read_only_device) {
+        return 0;
+    }
+
     if (fstat(fd, &st)) {
         return -errno;
     }
@@ -620,6 +630,11 @@ static QemuOptsList raw_runtime_opts = {
             .type = QEMU_OPT_BOOL,
             .help = "check that page cache was dropped on live migration 
(default: off)"
         },
+        {
+            .name = "accept-read-only-device",
+            .type = QEMU_OPT_BOOL,
+            .help = "allow opening a read-only device as read-write (default: 
off)"
+        },
         { /* end of list */ }
     },
 };
@@ -737,8 +752,10 @@ static int raw_open_common(BlockDriverState *bs, QDict 
*options,
     s->fd = fd;
 
     /* Check s->open_flags rather than bdrv_flags due to auto-read-only */
+    s->accept_read_only_device =
+        qemu_opt_get_bool(opts, "accept-read-only-device", false);
     if (s->open_flags & O_RDWR) {
-        ret = check_hdev_writable(s->fd);
+        ret = check_hdev_writable(bs, s->fd);
         if (ret < 0) {
             error_setg_errno(errp, -ret, "The device is not writable");
             goto fail;
@@ -1116,7 +1133,7 @@ static int raw_reconfigure_getfd(BlockDriverState *bs, 
int flags,
     }
 
     if (fd != -1 && (*open_flags & O_RDWR)) {
-        ret = check_hdev_writable(fd);
+        ret = check_hdev_writable(bs, fd);
         if (ret < 0) {
             qemu_close(fd);
             error_setg_errno(errp, -ret, "The device is not writable");
-- 
2.56.0


Reply via email to