diff --git a/block/backup.c b/block/backup.c index dddcf77f53..cf62b1a38c 100644 --- a/block/backup.c +++ b/block/backup.c @@ -474,10 +474,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, if (sync_bitmap) { bdrv_reclaim_dirty_bitmap(sync_bitmap, NULL); } - if (job) { - backup_clean(&job->common.job); - job_early_fail(&job->common.job); - } else if (backup_top) { + if (backup_top) { bdrv_backup_top_drop(backup_top); } diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index ef965d7895..0d64bf5a5e 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -3455,6 +3455,8 @@ int qcow2_detect_metadata_preallocation(BlockDriverState *bs) int64_t i, end_cluster, cluster_count = 0, threshold; int64_t file_length, real_allocation, real_clusters; + qemu_co_mutex_assert_locked(&s->lock); + file_length = bdrv_getlength(bs->file->bs); if (file_length < 0) { return file_length; diff --git a/block/qcow2.c b/block/qcow2.c index 8b05933565..0bc69e6996 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1916,6 +1916,8 @@ static int coroutine_fn qcow2_co_block_status(BlockDriverState *bs, unsigned int bytes; int status = 0; + qemu_co_mutex_lock(&s->lock); + if (!s->metadata_preallocation_checked) { ret = qcow2_detect_metadata_preallocation(bs); s->metadata_preallocation = (ret == 1); @@ -1923,7 +1925,6 @@ static int coroutine_fn qcow2_co_block_status(BlockDriverState *bs, } bytes = MIN(INT_MAX, count); - qemu_co_mutex_lock(&s->lock); ret = qcow2_get_cluster_offset(bs, offset, &bytes, &cluster_offset); qemu_co_mutex_unlock(&s->lock); if (ret < 0) { diff --git a/blockdev.c b/blockdev.c index 03c7cd7651..ba491e3ef5 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1088,11 +1088,11 @@ void hmp_commit(Monitor *mon, const QDict *qdict) blk = blk_by_name(device); if (!blk) { - monitor_printf(mon, "Device '%s' not found\n", device); + error_report("Device '%s' not found", device); return; } if (!blk_is_available(blk)) { - monitor_printf(mon, "Device '%s' has no medium\n", device); + error_report("Device '%s' has no medium", device); return; } @@ -1105,8 +1105,7 @@ void hmp_commit(Monitor *mon, const QDict *qdict) aio_context_release(aio_context); } if (ret < 0) { - monitor_printf(mon, "'commit' error for '%s': %s\n", device, - strerror(-ret)); + error_report("'commit' error for '%s': %s", device, strerror(-ret)); } } diff --git a/include/qemu/coroutine.h b/include/qemu/coroutine.h index 8d55663062..dfd261c5b1 100644 --- a/include/qemu/coroutine.h +++ b/include/qemu/coroutine.h @@ -167,6 +167,21 @@ void coroutine_fn qemu_co_mutex_lock(CoMutex *mutex); */ void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex); +/** + * Assert that the current coroutine holds @mutex. + */ +static inline coroutine_fn void qemu_co_mutex_assert_locked(CoMutex *mutex) +{ + /* + * mutex->holder doesn't need any synchronisation if the assertion holds + * true because the mutex protects it. If it doesn't hold true, we still + * don't mind if another thread takes or releases mutex behind our back, + * because the condition will be false no matter whether we read NULL or + * the pointer for any other coroutine. + */ + assert(atomic_read(&mutex->locked) && + mutex->holder == qemu_coroutine_self()); +} /** * CoQueues are a mechanism to queue coroutines in order to continue executing diff --git a/qapi/block-core.json b/qapi/block-core.json index b274aef713..aa97ee2641 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2883,12 +2883,13 @@ # @nvme: Since 2.12 # @copy-on-read: Since 3.0 # @blklogwrites: Since 3.0 +# @blkreplay: Since 4.2 # # Since: 2.9 ## { 'enum': 'BlockdevDriver', - 'data': [ 'blkdebug', 'blklogwrites', 'blkverify', 'bochs', 'cloop', - 'copy-on-read', 'dmg', 'file', 'ftp', 'ftps', 'gluster', + 'data': [ 'blkdebug', 'blklogwrites', 'blkreplay', 'blkverify', 'bochs', + 'cloop', 'copy-on-read', 'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom', 'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs', 'null-aio', 'null-co', 'nvme', 'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd', @@ -3501,6 +3502,18 @@ 'data': { 'test': 'BlockdevRef', 'raw': 'BlockdevRef' } } +## +# @BlockdevOptionsBlkreplay: +# +# Driver specific block device options for blkreplay. +# +# @image: disk image which should be controlled with blkreplay +# +# Since: 4.2 +## +{ 'struct': 'BlockdevOptionsBlkreplay', + 'data': { 'image': 'BlockdevRef' } } + ## # @QuorumReadPattern: # @@ -4028,6 +4041,7 @@ 'blkdebug': 'BlockdevOptionsBlkdebug', 'blklogwrites':'BlockdevOptionsBlklogwrites', 'blkverify': 'BlockdevOptionsBlkverify', + 'blkreplay': 'BlockdevOptionsBlkreplay', 'bochs': 'BlockdevOptionsGenericFormat', 'cloop': 'BlockdevOptionsGenericFormat', 'copy-on-read':'BlockdevOptionsGenericFormat', diff --git a/qemu-options.hx b/qemu-options.hx index 88b05599b1..b95bf9fbed 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -864,7 +864,8 @@ ETEXI DEF("blockdev", HAS_ARG, QEMU_OPTION_blockdev, "-blockdev [driver=]driver[,node-name=N][,discard=ignore|unmap]\n" " [,cache.direct=on|off][,cache.no-flush=on|off]\n" - " [,read-only=on|off][,detect-zeroes=on|off|unmap]\n" + " [,read-only=on|off][,auto-read-only=on|off]\n" + " [,force-share=on|off][,detect-zeroes=on|off|unmap]\n" " [,driver specific parameters...]\n" " configure a block backend\n", QEMU_ARCH_ALL) STEXI @@ -900,6 +901,25 @@ name is not intended to be predictable and changes between QEMU invocations. For the top level, an explicit node name must be specified. @item read-only Open the node read-only. Guest write attempts will fail. + +Note that some block drivers support only read-only access, either generally or +in certain configurations. In this case, the default value +@option{read-only=off} does not work and the option must be specified +explicitly. +@item auto-read-only +If @option{auto-read-only=on} is set, QEMU may fall back to read-only usage +even when @option{read-only=off} is requested, or even switch between modes as +needed, e.g. depending on whether the image file is writable or whether a +writing user is attached to the node. +@item force-share +Override the image locking system of QEMU by forcing the node to utilize +weaker shared access for permissions where it would normally request exclusive +access. When there is the potential for multiple instances to have the same +file open (whether this invocation of QEMU is the first or the second +instance), both instances must permit shared access for the second instance to +succeed at opening the file. + +Enabling @option{force-share=on} requires @option{read-only=on}. @item cache.direct The host page cache can be avoided with @option{cache.direct=on}. This will attempt to do disk IO directly to the guest's memory. QEMU may still perform an diff --git a/tests/qemu-iotests/118 b/tests/qemu-iotests/118 index ea0b326ae0..e20080e9a6 100755 --- a/tests/qemu-iotests/118 +++ b/tests/qemu-iotests/118 @@ -446,6 +446,7 @@ class TestChangeReadOnly(ChangeBaseClass): self.assert_qmp(result, 'return[0]/inserted/ro', True) self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + @iotests.skip_if_user_is_root def test_rw_ro_retain(self): os.chmod(new_img, 0o444) self.vm.add_drive(old_img, 'media=disk', 'none') @@ -530,6 +531,7 @@ class TestChangeReadOnly(ChangeBaseClass): self.assert_qmp(result, 'return[0]/inserted/ro', True) self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + @iotests.skip_if_user_is_root def test_make_ro_rw(self): os.chmod(new_img, 0o444) self.vm.add_drive(old_img, 'media=disk', 'none') @@ -571,6 +573,7 @@ class TestChangeReadOnly(ChangeBaseClass): self.assert_qmp(result, 'return[0]/inserted/ro', True) self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + @iotests.skip_if_user_is_root def test_make_ro_rw_by_retain(self): os.chmod(new_img, 0o444) self.vm.add_drive(old_img, 'media=disk', 'none') diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 693fde155a..709def4d5d 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -931,6 +931,16 @@ def skip_if_unsupported(required_formats=[], read_only=False): return func_wrapper return skip_test_decorator +def skip_if_user_is_root(func): + '''Skip Test Decorator + Runs the test only without root permissions''' + def func_wrapper(*args, **kwargs): + if os.getuid() == 0: + case_notrun('{}: cannot be run as root'.format(args[0])) + else: + return func(*args, **kwargs) + return func_wrapper + def execute_unittest(output, verbosity, debug): runner = unittest.TextTestRunner(stream=output, descriptions=True, verbosity=verbosity)