diff --git a/block/qapi-sysemu.c b/block/qapi-sysemu.c index 8498402ad4..680c7ee342 100644 --- a/block/qapi-sysemu.c +++ b/block/qapi-sysemu.c @@ -318,6 +318,7 @@ void qmp_blockdev_change_medium(bool has_device, const char *device, bool has_id, const char *id, const char *filename, bool has_format, const char *format, + bool has_force, bool force, bool has_read_only, BlockdevChangeReadOnlyMode read_only, Error **errp) @@ -380,7 +381,7 @@ void qmp_blockdev_change_medium(bool has_device, const char *device, rc = do_open_tray(has_device ? device : NULL, has_id ? id : NULL, - false, &err); + force, &err); if (rc && rc != -ENOSYS) { error_propagate(errp, err); goto fail; diff --git a/hmp-commands.hx b/hmp-commands.hx index dd4006d355..34947d6d20 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -202,9 +202,9 @@ ERST { .name = "change", - .args_type = "device:B,target:F,arg:s?,read-only-mode:s?", - .params = "device filename [format [read-only-mode]]", - .help = "change a removable medium, optional format", + .args_type = "device:B,force:-f,target:F,arg:s?,read-only-mode:s?", + .params = "device [-f] filename [format [read-only-mode]]", + .help = "change a removable medium, optional format, use -f to force the operation", .cmd = hmp_change, }, @@ -212,11 +212,14 @@ SRST ``change`` *device* *setting* Change the configuration of a device. - ``change`` *diskdevice* *filename* [*format* [*read-only-mode*]] + ``change`` *diskdevice* [-f] *filename* [*format* [*read-only-mode*]] Change the medium for a removable disk device to point to *filename*. eg:: (qemu) change ide1-cd0 /path/to/some.iso + ``-f`` + forces the operation even if the guest has locked the tray. + *format* is optional. *read-only-mode* may be used to change the read-only status of the device. diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 634968498b..d8b98bed6c 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -1472,6 +1472,7 @@ void hmp_change(Monitor *mon, const QDict *qdict) const char *target = qdict_get_str(qdict, "target"); const char *arg = qdict_get_try_str(qdict, "arg"); const char *read_only = qdict_get_try_str(qdict, "read-only-mode"); + bool force = qdict_get_try_bool(qdict, "force", false); BlockdevChangeReadOnlyMode read_only_mode = 0; Error *err = NULL; @@ -1508,7 +1509,8 @@ void hmp_change(Monitor *mon, const QDict *qdict) } qmp_blockdev_change_medium(true, device, false, NULL, target, - !!arg, arg, !!read_only, read_only_mode, + !!arg, arg, true, force, + !!read_only, read_only_mode, &err); } diff --git a/qapi/block.json b/qapi/block.json index 82fcf2c914..3f100d4887 100644 --- a/qapi/block.json +++ b/qapi/block.json @@ -326,6 +326,11 @@ # @read-only-mode: change the read-only mode of the device; defaults # to 'retain' # +# @force: if false (the default), an eject request through blockdev-open-tray +# will be sent to the guest if it has locked the tray (and the tray +# will not be opened immediately); if true, the tray will be opened +# regardless of whether it is locked. (since 7.1) +# # Features: # @deprecated: Member @device is deprecated. Use @id instead. # @@ -367,6 +372,7 @@ '*id': 'str', 'filename': 'str', '*format': 'str', + '*force': 'bool', '*read-only-mode': 'BlockdevChangeReadOnlyMode' } } diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030 index 18eddcc734..98595d47fe 100755 --- a/tests/qemu-iotests/030 +++ b/tests/qemu-iotests/030 @@ -64,16 +64,18 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), - qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, + 'image file map does not match backing file after streaming') def test_stream_intermediate(self): self.assert_no_active_block_jobs() - self.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img), - qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img), - 'image file map matches backing file before streaming') + self.assertNotEqual( + qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img).stdout, + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img).stdout, + 'image file map matches backing file before streaming') result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid') self.assert_qmp(result, 'return', {}) @@ -83,9 +85,10 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), - qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img).stdout, + 'image file map does not match backing file after streaming') def test_stream_pause(self): self.assert_no_active_block_jobs() @@ -113,15 +116,17 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), - qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, + 'image file map does not match backing file after streaming') def test_stream_no_op(self): self.assert_no_active_block_jobs() # The image map is empty before the operation - empty_map = qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', test_img) + empty_map = qemu_io( + '-f', iotests.imgfmt, '-rU', '-c', 'map', test_img).stdout # This is a no-op: no data should ever be copied from the base image result = self.vm.qmp('block-stream', device='drive0', base=mid_img) @@ -132,8 +137,9 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), - empty_map, 'image file map changed after a no-op') + self.assertEqual( + qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, + empty_map, 'image file map changed after a no-op') def test_stream_partial(self): self.assert_no_active_block_jobs() @@ -146,9 +152,10 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), - qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, + 'image file map does not match backing file after streaming') def test_device_not_found(self): result = self.vm.qmp('block-stream', device='nonexistent') @@ -236,9 +243,10 @@ class TestParallelOps(iotests.QMPTestCase): # Check that the maps don't match before the streaming operations for i in range(2, self.num_imgs, 2): - self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]), - qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]), - 'image file map matches backing file before streaming') + self.assertNotEqual( + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]).stdout, + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]).stdout, + 'image file map matches backing file before streaming') # Create all streaming jobs pending_jobs = [] @@ -278,9 +286,10 @@ class TestParallelOps(iotests.QMPTestCase): # Check that all maps match now for i in range(2, self.num_imgs, 2): - self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), - qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]).stdout, + 'image file map does not match backing file after streaming') # Test that it's not possible to perform two block-stream # operations if there are nodes involved in both. @@ -514,9 +523,10 @@ class TestParallelOps(iotests.QMPTestCase): def test_stream_base_node_name(self): self.assert_no_active_block_jobs() - self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]), - qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]), - 'image file map matches backing file before streaming') + self.assertNotEqual( + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]).stdout, + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]).stdout, + 'image file map matches backing file before streaming') # Error: the base node does not exist result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') @@ -547,9 +557,10 @@ class TestParallelOps(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), - qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), - 'image file map matches backing file after streaming') + self.assertEqual( + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]).stdout, + 'image file map matches backing file after streaming') class TestQuorum(iotests.QMPTestCase): num_children = 3 @@ -588,9 +599,10 @@ class TestQuorum(iotests.QMPTestCase): os.remove(img) def test_stream_quorum(self): - self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]), - qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]), - 'image file map matches backing file before streaming') + self.assertNotEqual( + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]).stdout, + qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]).stdout, + 'image file map matches backing file before streaming') self.assert_no_active_block_jobs() @@ -602,9 +614,10 @@ class TestQuorum(iotests.QMPTestCase): self.assert_no_active_block_jobs() self.vm.shutdown() - self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), - qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), - 'image file map does not match backing file after streaming') + self.assertEqual( + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]).stdout, + qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]).stdout, + 'image file map does not match backing file after streaming') class TestSmallerBackingFile(iotests.QMPTestCase): backing_len = 1 * 1024 * 1024 # MB diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 0e1cfd7e49..30eb97829e 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -86,8 +86,10 @@ class TestSingleDrive(ImageCommitTestCase): qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, '-F', iotests.imgfmt, test_img) - qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img) - qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img) + if self.image_len: + qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', + mid_img) self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=mid,backing.backing.node-name=base", interface="none") self.vm.add_device('virtio-scsi') self.vm.add_device("scsi-hd,id=scsi0,drive=drive0") @@ -101,13 +103,17 @@ class TestSingleDrive(ImageCommitTestCase): def test_commit(self): self.run_commit_test(mid_img, backing_img) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) + if not self.image_len: + return + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img) def test_commit_node(self): self.run_commit_test("mid", "base", node_names=True) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) + if not self.image_len: + return + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img) @iotests.skip_if_unsupported(['throttle']) def test_commit_with_filter_and_quit(self): @@ -192,13 +198,17 @@ class TestSingleDrive(ImageCommitTestCase): def test_top_is_active(self): self.run_commit_test(test_img, backing_img, need_ready=True) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) + if not self.image_len: + return + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img) def test_top_is_default_active(self): self.run_default_commit_test() - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) + if not self.image_len: + return + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img) def test_top_and_base_reversed(self): self.assert_no_active_block_jobs() @@ -334,8 +344,8 @@ class TestRelativePaths(ImageCommitTestCase): def test_commit(self): self.run_commit_test(self.mid_img, self.backing_img) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed")) + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs) def test_device_not_found(self): result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % self.mid_img) @@ -361,8 +371,8 @@ class TestRelativePaths(ImageCommitTestCase): def test_top_is_active(self): self.run_commit_test(self.test_img, self.backing_img) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed")) - self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed")) + qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs) + qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs) def test_top_and_base_reversed(self): self.assert_no_active_block_jobs() @@ -738,11 +748,10 @@ class TestCommitWithFilters(iotests.QMPTestCase): def do_test_io(self, read_or_write): for index, pattern_file in enumerate(self.pattern_files): - result = qemu_io('-f', iotests.imgfmt, - '-c', - f'{read_or_write} -P {index + 1} {index}M 1M', - pattern_file) - self.assertFalse('Pattern verification failed' in result) + qemu_io('-f', iotests.imgfmt, + '-c', + f'{read_or_write} -P {index + 1} {index}M 1M', + pattern_file) @iotests.skip_if_unsupported(['throttle']) def setUp(self): @@ -827,7 +836,8 @@ class TestCommitWithFilters(iotests.QMPTestCase): job_id='commit', device='top-filter', top_node='cow-2', - base_node='cow-1') + base_node='cow-1', + backing_file=self.img1) self.assert_qmp(result, 'return', {}) self.wait_until_completed(drive='commit') @@ -843,7 +853,8 @@ class TestCommitWithFilters(iotests.QMPTestCase): job_id='commit', device='top-filter', top_node='cow-1', - base_node='cow-0') + base_node='cow-0', + backing_file=self.img0) self.assert_qmp(result, 'return', {}) self.wait_until_completed(drive='commit') diff --git a/tests/qemu-iotests/056 b/tests/qemu-iotests/056 index b459a3f1e8..bef865eec4 100755 --- a/tests/qemu-iotests/056 +++ b/tests/qemu-iotests/056 @@ -102,7 +102,7 @@ class TestSyncModesNoneAndTop(iotests.QMPTestCase): self.vm.shutdown() time.sleep(1) - self.assertEqual(-1, qemu_io('-c', 'read -P0x41 0 512', target_img).find("verification failed")) + qemu_io('-c', 'read -P0x41 0 512', target_img) class TestBeforeWriteNotifier(iotests.QMPTestCase): def setUp(self): diff --git a/tests/qemu-iotests/108 b/tests/qemu-iotests/108 index 688d3ae8f6..9e923d6a59 100755 --- a/tests/qemu-iotests/108 +++ b/tests/qemu-iotests/108 @@ -326,7 +326,7 @@ else $QSD \ --blockdev file,node-name=export-node,filename="$TEST_IMG" \ - --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=off \ + --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=off,allow-other=off \ --pidfile "$TEST_DIR/qsd.pid" \ --daemonize fi diff --git a/tests/qemu-iotests/149 b/tests/qemu-iotests/149 index 9bb96d6a1d..2ae318f16f 100755 --- a/tests/qemu-iotests/149 +++ b/tests/qemu-iotests/149 @@ -295,7 +295,8 @@ def qemu_io_write_pattern(config, pattern, offset_mb, size_mb, dev=False): args = ["-c", "write -P 0x%x %dM %dM" % (pattern, offset_mb, size_mb)] args.extend(qemu_io_image_args(config, dev)) iotests.log("qemu-io " + " ".join(args), filters=[iotests.filter_test_dir]) - iotests.log(check_cipher_support(config, iotests.qemu_io(*args)), + output = iotests.qemu_io(*args, check=False).stdout + iotests.log(check_cipher_support(config, output), filters=[iotests.filter_test_dir, iotests.filter_qemu_io]) @@ -307,7 +308,8 @@ def qemu_io_read_pattern(config, pattern, offset_mb, size_mb, dev=False): args = ["-c", "read -P 0x%x %dM %dM" % (pattern, offset_mb, size_mb)] args.extend(qemu_io_image_args(config, dev)) iotests.log("qemu-io " + " ".join(args), filters=[iotests.filter_test_dir]) - iotests.log(check_cipher_support(config, iotests.qemu_io(*args)), + output = iotests.qemu_io(*args, check=False).stdout + iotests.log(check_cipher_support(config, output), filters=[iotests.filter_test_dir, iotests.filter_qemu_io]) diff --git a/tests/qemu-iotests/163 b/tests/qemu-iotests/163 index e4cd4b230f..c94ad16f4a 100755 --- a/tests/qemu-iotests/163 +++ b/tests/qemu-iotests/163 @@ -113,10 +113,7 @@ class ShrinkBaseClass(iotests.QMPTestCase): qemu_img('resize', '-f', iotests.imgfmt, '--shrink', test_img, self.shrink_size) - self.assertEqual( - qemu_io('-c', 'read -P 0x00 %s'%self.shrink_size, test_img), - qemu_io('-c', 'read -P 0x00 %s'%self.shrink_size, check_img), - "Verifying image content") + qemu_io('-c', f"read -P 0x00 0 {self.shrink_size}", test_img) self.image_verify() diff --git a/tests/qemu-iotests/205 b/tests/qemu-iotests/205 index c0e107328f..15f798288a 100755 --- a/tests/qemu-iotests/205 +++ b/tests/qemu-iotests/205 @@ -85,13 +85,13 @@ class TestNbdServerRemove(iotests.QMPTestCase): def do_test_connect_after_remove(self, mode=None): args = ('-r', '-f', 'raw', '-c', 'read 0 512', nbd_uri) - self.assertReadOk(qemu_io(*args)) + self.assertReadOk(qemu_io(*args).stdout) result = self.remove_export('exp', mode) self.assert_qmp(result, 'return', {}) self.assertExportNotFound('exp') - self.assertConnectFailed(qemu_io(*args)) + self.assertConnectFailed(qemu_io(*args, check=False).stdout) def test_connect_after_remove_default(self): self.do_test_connect_after_remove() diff --git a/tests/qemu-iotests/216 b/tests/qemu-iotests/216 index c531abfded..311e02af3a 100755 --- a/tests/qemu-iotests/216 +++ b/tests/qemu-iotests/216 @@ -21,7 +21,7 @@ # Creator/Owner: Hanna Reitz import iotests -from iotests import log, qemu_img, qemu_io_silent +from iotests import log, qemu_img, qemu_io # Need backing file support iotests.script_initialize(supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk'], @@ -52,10 +52,10 @@ with iotests.FilePath('base.img') as base_img_path, \ log('') qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') - assert qemu_io_silent(base_img_path, '-c', 'write -P 1 0M 1M') == 0 + qemu_io(base_img_path, '-c', 'write -P 1 0M 1M') qemu_img('create', '-f', iotests.imgfmt, '-b', base_img_path, '-F', iotests.imgfmt, top_img_path) - assert qemu_io_silent(top_img_path, '-c', 'write -P 2 1M 1M') == 0 + qemu_io(top_img_path, '-c', 'write -P 2 1M 1M') log('Done') @@ -110,8 +110,8 @@ with iotests.FilePath('base.img') as base_img_path, \ log('--- Checking COR result ---') log('') - assert qemu_io_silent(base_img_path, '-c', 'discard 0 64M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 1 0M 1M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 2 1M 1M') == 0 + qemu_io(base_img_path, '-c', 'discard 0 64M') + qemu_io(top_img_path, '-c', 'read -P 1 0M 1M') + qemu_io(top_img_path, '-c', 'read -P 2 1M 1M') log('Done') diff --git a/tests/qemu-iotests/218 b/tests/qemu-iotests/218 index 8345793902..6320c4cb56 100755 --- a/tests/qemu-iotests/218 +++ b/tests/qemu-iotests/218 @@ -28,7 +28,7 @@ # Creator/Owner: Hanna Reitz import iotests -from iotests import log, qemu_img, qemu_io_silent +from iotests import log, qemu_img, qemu_io iotests.script_initialize(supported_fmts=['qcow2', 'raw']) @@ -146,8 +146,7 @@ with iotests.VM() as vm, \ iotests.FilePath('src.img') as src_img_path: qemu_img('create', '-f', iotests.imgfmt, src_img_path, '64M') - assert qemu_io_silent('-f', iotests.imgfmt, src_img_path, - '-c', 'write -P 42 0M 64M') == 0 + qemu_io('-f', iotests.imgfmt, src_img_path, '-c', 'write -P 42 0M 64M') vm.launch() diff --git a/tests/qemu-iotests/224 b/tests/qemu-iotests/224 index 4df5157e8d..542d0eefa6 100755 --- a/tests/qemu-iotests/224 +++ b/tests/qemu-iotests/224 @@ -22,7 +22,7 @@ # Creator/Owner: Hanna Reitz import iotests -from iotests import log, qemu_img, qemu_io_silent, filter_qmp_testfiles, \ +from iotests import log, qemu_img, qemu_io, filter_qmp_testfiles, \ filter_qmp_imgfmt import json @@ -54,7 +54,7 @@ for filter_node_name in False, True: '-F', iotests.imgfmt, top_img_path) # Something to commit - assert qemu_io_silent(mid_img_path, '-c', 'write -P 1 0 1M') == 0 + qemu_io(mid_img_path, '-c', 'write -P 1 0 1M') vm.launch() diff --git a/tests/qemu-iotests/242 b/tests/qemu-iotests/242 index b3afd36d72..c89f0c6cb3 100755 --- a/tests/qemu-iotests/242 +++ b/tests/qemu-iotests/242 @@ -22,8 +22,8 @@ import iotests import json import struct -from iotests import qemu_img_create, qemu_io, qemu_img_info, \ - file_path, img_info_log, log, filter_qemu_io +from iotests import qemu_img_create, qemu_io_log, qemu_img_info, \ + file_path, img_info_log, log iotests.script_initialize(supported_fmts=['qcow2'], supported_protocols=['file'], @@ -61,7 +61,7 @@ def add_bitmap(bitmap_number, persistent, disabled): def write_to_disk(offset, size): write = 'write {} {}'.format(offset, size) - log(qemu_io('-c', write, disk), filters=[filter_qemu_io]) + qemu_io_log('-c', write, disk) def toggle_flag(offset): diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 index 8cbed7821b..edaf29094b 100755 --- a/tests/qemu-iotests/245 +++ b/tests/qemu-iotests/245 @@ -20,11 +20,13 @@ # along with this program. If not, see . # -import os -import re -import iotests import copy import json +import os +import re +from subprocess import CalledProcessError + +import iotests from iotests import qemu_img, qemu_io hd_path = [ @@ -216,11 +218,14 @@ class TestBlockdevReopen(iotests.QMPTestCase): # Reopen an image several times changing some of its options def test_reopen(self): - # Check whether the filesystem supports O_DIRECT - if 'O_DIRECT' in qemu_io('-f', 'raw', '-t', 'none', '-c', 'quit', hd_path[0]): - supports_direct = False - else: + try: + qemu_io('-f', 'raw', '-t', 'none', '-c', 'quit', hd_path[0]) supports_direct = True + except CalledProcessError as exc: + if 'O_DIRECT' in exc.stdout: + supports_direct = False + else: + raise # Open the hd1 image passing all backing options opts = hd_opts(1) diff --git a/tests/qemu-iotests/255 b/tests/qemu-iotests/255 index f86fa851b6..88b29d64b4 100755 --- a/tests/qemu-iotests/255 +++ b/tests/qemu-iotests/255 @@ -95,9 +95,7 @@ with iotests.FilePath('src.qcow2') as src_path, \ iotests.qemu_img_create('-f', iotests.imgfmt, src_path, size_str) iotests.qemu_img_create('-f', iotests.imgfmt, dst_path, size_str) - iotests.log(iotests.qemu_io('-f', iotests.imgfmt, '-c', 'write 0 1M', - src_path), - filters=[iotests.filter_test_dir, iotests.filter_qemu_io]) + iotests.qemu_io_log('-f', iotests.imgfmt, '-c', 'write 0 1M', src_path), vm.add_object('throttle-group,x-bps-read=4096,id=throttle0') diff --git a/tests/qemu-iotests/258 b/tests/qemu-iotests/258 index cfd536d6dc..73d4af645f 100755 --- a/tests/qemu-iotests/258 +++ b/tests/qemu-iotests/258 @@ -21,7 +21,7 @@ # Creator/Owner: Hanna Reitz import iotests -from iotests import log, qemu_img, qemu_io_silent, \ +from iotests import log, qemu_img, qemu_io, \ filter_qmp_testfiles, filter_qmp_imgfmt # Returns a node for blockdev-add @@ -86,15 +86,14 @@ def test_concurrent_finish(write_to_stream_node): if write_to_stream_node: # This is what (most of the time) makes commit finish # earlier and then pull in stream - assert qemu_io_silent(node2_path, - '-c', 'write %iK 64K' % (65536 - 192), - '-c', 'write %iK 64K' % (65536 - 64)) == 0 + qemu_io(node2_path, + '-c', 'write %iK 64K' % (65536 - 192), + '-c', 'write %iK 64K' % (65536 - 64)) stream_throttle='tg' else: # And this makes stream finish earlier - assert qemu_io_silent(node1_path, - '-c', 'write %iK 64K' % (65536 - 64)) == 0 + qemu_io(node1_path, '-c', 'write %iK 64K' % (65536 - 64)) commit_throttle='tg' diff --git a/tests/qemu-iotests/298 b/tests/qemu-iotests/298 index fae72211b1..ad560e2941 100755 --- a/tests/qemu-iotests/298 +++ b/tests/qemu-iotests/298 @@ -129,16 +129,13 @@ class TestTruncate(iotests.QMPTestCase): os.remove(refdisk) def do_test(self, prealloc_mode, new_size): - ret = iotests.qemu_io_silent('--image-opts', '-c', 'write 0 10M', '-c', - f'truncate -m {prealloc_mode} {new_size}', - drive_opts) - self.assertEqual(ret, 0) + iotests.qemu_io('--image-opts', '-c', 'write 0 10M', '-c', + f'truncate -m {prealloc_mode} {new_size}', + drive_opts) - ret = iotests.qemu_io_silent('-f', iotests.imgfmt, '-c', 'write 0 10M', - '-c', - f'truncate -m {prealloc_mode} {new_size}', - refdisk) - self.assertEqual(ret, 0) + iotests.qemu_io('-f', iotests.imgfmt, '-c', 'write 0 10M', + '-c', f'truncate -m {prealloc_mode} {new_size}', + refdisk) stat = os.stat(disk) refstat = os.stat(refdisk) diff --git a/tests/qemu-iotests/303 b/tests/qemu-iotests/303 index 40e947f26c..a8cc6a23df 100755 --- a/tests/qemu-iotests/303 +++ b/tests/qemu-iotests/303 @@ -21,7 +21,7 @@ import iotests import subprocess -from iotests import qemu_img_create, qemu_io, file_path, log, filter_qemu_io, \ +from iotests import qemu_img_create, qemu_io_log, file_path, log, \ verify_qcow2_zstd_compression iotests.script_initialize(supported_fmts=['qcow2'], @@ -45,7 +45,7 @@ def create_bitmap(bitmap_number, disabled): def write_to_disk(offset, size): write = f'write {offset} {size}' - log(qemu_io('-c', write, disk), filters=[filter_qemu_io]) + qemu_io_log('-c', write, disk) def add_bitmap(num, begin, end, disabled): diff --git a/tests/qemu-iotests/310 b/tests/qemu-iotests/310 index 00fc5618f6..650d2cb6fb 100755 --- a/tests/qemu-iotests/310 +++ b/tests/qemu-iotests/310 @@ -21,7 +21,7 @@ # import iotests -from iotests import log, qemu_img, qemu_io_silent +from iotests import log, qemu_img, qemu_io # Need backing file support iotests.script_initialize(supported_fmts=['qcow2'], @@ -44,15 +44,15 @@ with iotests.FilePath('base.img') as base_img_path, \ log('') qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') - assert qemu_io_silent(base_img_path, '-c', 'write -P 1 0M 1M') == 0 - assert qemu_io_silent(base_img_path, '-c', 'write -P 1 3M 1M') == 0 + qemu_io(base_img_path, '-c', 'write -P 1 0M 1M') + qemu_io(base_img_path, '-c', 'write -P 1 3M 1M') qemu_img('create', '-f', iotests.imgfmt, '-b', base_img_path, '-F', iotests.imgfmt, mid_img_path) - assert qemu_io_silent(mid_img_path, '-c', 'write -P 3 2M 1M') == 0 - assert qemu_io_silent(mid_img_path, '-c', 'write -P 3 4M 1M') == 0 + qemu_io(mid_img_path, '-c', 'write -P 3 2M 1M') + qemu_io(mid_img_path, '-c', 'write -P 3 4M 1M') qemu_img('create', '-f', iotests.imgfmt, '-b', mid_img_path, '-F', iotests.imgfmt, top_img_path) - assert qemu_io_silent(top_img_path, '-c', 'write -P 2 1M 1M') == 0 + qemu_io(top_img_path, '-c', 'write -P 2 1M 1M') # 0 1 2 3 4 # top 2 @@ -107,10 +107,10 @@ with iotests.FilePath('base.img') as base_img_path, \ # Detach backing to check that we can read the data from the top level now qemu_img('rebase', '-u', '-b', '', '-f', iotests.imgfmt, top_img_path) - assert qemu_io_silent(top_img_path, '-c', 'read -P 0 0 1M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 2 1M 1M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 3 2M 1M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 0 3M 1M') == 0 - assert qemu_io_silent(top_img_path, '-c', 'read -P 3 4M 1M') == 0 + qemu_io(top_img_path, '-c', 'read -P 0 0 1M') + qemu_io(top_img_path, '-c', 'read -P 2 1M 1M') + qemu_io(top_img_path, '-c', 'read -P 3 2M 1M') + qemu_io(top_img_path, '-c', 'read -P 0 3M 1M') + qemu_io(top_img_path, '-c', 'read -P 3 4M 1M') log('Done') diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 33a44671aa..da7d6637e1 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -206,15 +206,13 @@ def qemu_img_create_prepare_args(args: List[str]) -> List[str]: return result -def qemu_img(*args: str, check: bool = True, combine_stdio: bool = True - ) -> 'subprocess.CompletedProcess[str]': + +def qemu_tool(*args: str, check: bool = True, combine_stdio: bool = True + ) -> 'subprocess.CompletedProcess[str]': """ - Run qemu_img and return the status code and console output. + Run a qemu tool and return its status code and console output. - This function always prepends QEMU_IMG_OPTIONS and may further alter - the args for 'create' commands. - - :param args: command-line arguments to qemu-img. + :param args: full command line to run. :param check: Enforce a return code of zero. :param combine_stdio: set to False to keep stdout/stderr separated. @@ -231,10 +229,8 @@ def qemu_img(*args: str, check: bool = True, combine_stdio: bool = True properties. If streams are not combined, it will also have a stderr property. """ - full_args = qemu_img_args + qemu_img_create_prepare_args(list(args)) - subp = subprocess.run( - full_args, + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT if combine_stdio else subprocess.PIPE, universal_newlines=True, @@ -243,7 +239,7 @@ def qemu_img(*args: str, check: bool = True, combine_stdio: bool = True if check and subp.returncode or (subp.returncode < 0): raise VerboseProcessError( - subp.returncode, full_args, + subp.returncode, args, output=subp.stdout, stderr=subp.stderr, ) @@ -251,6 +247,20 @@ def qemu_img(*args: str, check: bool = True, combine_stdio: bool = True return subp +def qemu_img(*args: str, check: bool = True, combine_stdio: bool = True + ) -> 'subprocess.CompletedProcess[str]': + """ + Run QEMU_IMG_PROG and return its status code and console output. + + This function always prepends QEMU_IMG_OPTIONS and may further alter + the args for 'create' commands. + + See `qemu_tool()` for greater detail. + """ + full_args = qemu_img_args + qemu_img_create_prepare_args(list(args)) + return qemu_tool(*full_args, check=check, combine_stdio=combine_stdio) + + def ordered_qmp(qmsg, conv_keys=True): # Dictionaries are not ordered prior to 3.6, therefore: if isinstance(qmsg, list): @@ -343,34 +353,23 @@ def qemu_io_wrap_args(args: Sequence[str]) -> List[str]: def qemu_io_popen(*args): return qemu_tool_popen(qemu_io_wrap_args(args)) -def qemu_io(*args): - '''Run qemu-io and return the stdout data''' - return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args))[0] +def qemu_io(*args: str, check: bool = True, combine_stdio: bool = True + ) -> 'subprocess.CompletedProcess[str]': + """ + Run QEMU_IO_PROG and return the status code and console output. -def qemu_io_pipe_and_status(*args): - return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args)) + This function always prepends either QEMU_IO_OPTIONS or + QEMU_IO_OPTIONS_NO_FMT. + """ + return qemu_tool(*qemu_io_wrap_args(args), + check=check, combine_stdio=combine_stdio) -def qemu_io_log(*args): - result = qemu_io(*args) - log(result, filters=[filter_testfiles, filter_qemu_io]) +def qemu_io_log(*args: str, check: bool = True + ) -> 'subprocess.CompletedProcess[str]': + result = qemu_io(*args, check=check) + log(result.stdout, filters=[filter_testfiles, filter_qemu_io]) return result -def qemu_io_silent(*args): - '''Run qemu-io and return the exit code, suppressing stdout''' - args = qemu_io_wrap_args(args) - result = subprocess.run(args, stdout=subprocess.DEVNULL, check=False) - if result.returncode < 0: - sys.stderr.write('qemu-io received signal %i: %s\n' % - (-result.returncode, ' '.join(args))) - return result.returncode - -def qemu_io_silent_check(*args): - '''Run qemu-io and return the true if subprocess returned 0''' - args = qemu_io_wrap_args(args) - result = subprocess.run(args, stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT, check=False) - return result.returncode == 0 - class QemuIoInteractive: def __init__(self, *args): self.args = qemu_io_wrap_args(args) diff --git a/tests/qemu-iotests/tests/image-fleecing b/tests/qemu-iotests/tests/image-fleecing index b7e5076104..f6e449d071 100755 --- a/tests/qemu-iotests/tests/image-fleecing +++ b/tests/qemu-iotests/tests/image-fleecing @@ -22,9 +22,10 @@ # # Creator/Owner: John Snow +from subprocess import CalledProcessError + import iotests -from iotests import log, qemu_img, qemu_io, qemu_io_silent, \ - qemu_io_pipe_and_status +from iotests import log, qemu_img, qemu_io iotests.script_initialize( supported_fmts=['qcow2'], @@ -185,10 +186,14 @@ def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, for p in patterns + zeroes: cmd = 'read -P%s %s %s' % p log(cmd) - out, ret = qemu_io_pipe_and_status('-r', '-f', 'raw', '-c', cmd, - nbd_uri) - if ret != 0: - print(out) + + try: + qemu_io('-r', '-f', 'raw', '-c', cmd, nbd_uri) + except CalledProcessError as exc: + if bitmap and p in zeroes: + log(exc.stdout) + else: + raise log('') log('--- Testing COW ---') @@ -228,9 +233,14 @@ def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, args += [target_img_path] else: args += ['-f', 'raw', nbd_uri] - out, ret = qemu_io_pipe_and_status(*args) - if ret != 0: - print(out) + + try: + qemu_io(*args) + except CalledProcessError as exc: + if bitmap and p in zeroes: + log(exc.stdout) + else: + raise log('') log('--- Cleanup ---') @@ -260,7 +270,7 @@ def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, for p in overwrite + remainder: cmd = 'read -P%s %s %s' % p log(cmd) - assert qemu_io_silent(base_img_path, '-c', cmd) == 0 + qemu_io(base_img_path, '-c', cmd) log('') log('Done') diff --git a/tests/qemu-iotests/tests/migration-permissions b/tests/qemu-iotests/tests/migration-permissions index 6be02581c7..4e1da369c9 100755 --- a/tests/qemu-iotests/tests/migration-permissions +++ b/tests/qemu-iotests/tests/migration-permissions @@ -18,6 +18,8 @@ # import os +from subprocess import CalledProcessError + import iotests from iotests import imgfmt, qemu_img_create, qemu_io @@ -69,13 +71,12 @@ class TestMigrationPermissions(iotests.QMPTestCase): def test_post_migration_permissions(self): # Try to access the image R/W, which should fail because virtio-blk # has not been configured with share-rw=on - log = qemu_io('-f', imgfmt, '-c', 'quit', test_img) - if not log.strip(): - print('ERROR (pre-migration): qemu-io should not be able to ' - 'access this image, but it reported no error') - else: - # This is the expected output - assert 'Is another process using the image' in log + emsg = ('ERROR (pre-migration): qemu-io should not be able to ' + 'access this image, but it reported no error') + with self.assertRaises(CalledProcessError, msg=emsg) as ctx: + qemu_io('-f', imgfmt, '-c', 'quit', test_img) + if 'Is another process using the image' not in ctx.exception.stdout: + raise ctx.exception # Now migrate the VM self.vm_s.qmp('migrate', uri=f'unix:{mig_sock}') @@ -84,13 +85,12 @@ class TestMigrationPermissions(iotests.QMPTestCase): # Try the same qemu-io access again, verifying that the WRITE # permission remains unshared - log = qemu_io('-f', imgfmt, '-c', 'quit', test_img) - if not log.strip(): - print('ERROR (post-migration): qemu-io should not be able to ' - 'access this image, but it reported no error') - else: - # This is the expected output - assert 'Is another process using the image' in log + emsg = ('ERROR (post-migration): qemu-io should not be able to ' + 'access this image, but it reported no error') + with self.assertRaises(CalledProcessError, msg=emsg) as ctx: + qemu_io('-f', imgfmt, '-c', 'quit', test_img) + if 'Is another process using the image' not in ctx.exception.stdout: + raise ctx.exception if __name__ == '__main__': diff --git a/tests/qemu-iotests/tests/mirror-ready-cancel-error b/tests/qemu-iotests/tests/mirror-ready-cancel-error index 1d0e333b5e..01217459b9 100755 --- a/tests/qemu-iotests/tests/mirror-ready-cancel-error +++ b/tests/qemu-iotests/tests/mirror-ready-cancel-error @@ -37,7 +37,7 @@ class TestMirrorReadyCancelError(iotests.QMPTestCase): # Ensure that mirror will copy something before READY so the # target format layer will forward the pre-READY flush to its # file child - assert iotests.qemu_io_silent('-c', 'write -P 1 0 64k', source) == 0 + iotests.qemu_io('-c', 'write -P 1 0 64k', source) self.vm = iotests.VM() self.vm.launch() diff --git a/tests/qemu-iotests/tests/nbd-reconnect-on-open b/tests/qemu-iotests/tests/nbd-reconnect-on-open index 8be721a24f..d0b401b060 100755 --- a/tests/qemu-iotests/tests/nbd-reconnect-on-open +++ b/tests/qemu-iotests/tests/nbd-reconnect-on-open @@ -39,7 +39,7 @@ def check_fail_to_connect(open_timeout): log(f'Check fail to connect with {open_timeout} seconds of timeout') start_t = time.time() - qemu_io_log(*create_args(open_timeout)) + qemu_io_log(*create_args(open_timeout), check=False) delta_t = time.time() - start_t max_delta = open_timeout + 0.2 diff --git a/tests/qemu-iotests/tests/stream-error-on-reset b/tests/qemu-iotests/tests/stream-error-on-reset index 389ae822b8..5a8c3a9e8d 100755 --- a/tests/qemu-iotests/tests/stream-error-on-reset +++ b/tests/qemu-iotests/tests/stream-error-on-reset @@ -21,7 +21,7 @@ import os import iotests -from iotests import imgfmt, qemu_img_create, qemu_io_silent, QMPTestCase +from iotests import imgfmt, qemu_img_create, qemu_io, QMPTestCase image_size = 1 * 1024 * 1024 @@ -55,7 +55,7 @@ class TestStreamErrorOnReset(QMPTestCase): - top image is attached to a virtio-scsi device """ qemu_img_create('-f', imgfmt, base, str(image_size)) - assert qemu_io_silent('-c', f'write 0 {data_size}', base) == 0 + qemu_io('-c', f'write 0 {data_size}', base) qemu_img_create('-f', imgfmt, top, str(image_size)) self.vm = iotests.VM() diff --git a/ui/cocoa.m b/ui/cocoa.m index 68bff4ff37..09a62817f2 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -1531,6 +1531,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven [file cStringUsingEncoding: NSASCIIStringEncoding], true, "raw", + true, false, false, 0, &err); });