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