| #!/usr/bin/env python3 |
| # group: rw |
| # |
| # Test permissions taken by the mirror-top filter |
| # |
| # Copyright (C) 2021 Red Hat, Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| import os |
| |
| from qemu.machine import machine |
| |
| import iotests |
| from iotests import change_log_level, qemu_img |
| |
| |
| image_size = 1 * 1024 * 1024 |
| source = os.path.join(iotests.test_dir, 'source.img') |
| |
| |
| class TestMirrorTopPerms(iotests.QMPTestCase): |
| def setUp(self): |
| qemu_img('create', '-f', iotests.imgfmt, source, str(image_size)) |
| self.vm = iotests.VM() |
| self.vm.add_drive(source) |
| self.vm.add_blockdev(f'null-co,node-name=null,size={image_size}') |
| self.vm.launch() |
| |
| # Will be created by the test function itself |
| self.vm_b = None |
| |
| def tearDown(self): |
| try: |
| self.vm.shutdown() |
| except machine.AbnormalShutdown: |
| pass |
| |
| if self.vm_b is not None: |
| self.vm_b.shutdown() |
| |
| os.remove(source) |
| |
| def test_cancel(self): |
| """ |
| Before commit 53431b9086b28, mirror-top used to not take any |
| permissions but WRITE and share all permissions. Because it |
| is inserted between the source's original parents and the |
| source, there generally was no parent that would have taken or |
| unshared any permissions on the source, which means that an |
| external process could access the image unhindered by locks. |
| (Unless there was a parent above the protocol node that would |
| take its own locks, e.g. a format driver.) |
| This is bad enough, but if the mirror job is then cancelled, |
| the mirroring VM tries to take back the image, restores the |
| original permissions taken and unshared, and assumes this must |
| just work. But it will not, and so the VM aborts. |
| |
| Commit 53431b9086b28 made mirror keep the original permissions |
| and so no other process can "steal" the image. |
| |
| (Note that you cannot really do the same with the target image |
| and then completing the job, because the mirror job always |
| took/unshared the correct permissions on the target. For |
| example, it does not share READ_CONSISTENT, which makes it |
| difficult to let some other qemu process open the image.) |
| """ |
| |
| result = self.vm.qmp('blockdev-mirror', |
| job_id='mirror', |
| device='drive0', |
| target='null', |
| sync='full') |
| self.assert_qmp(result, 'return', {}) |
| |
| self.vm.event_wait('BLOCK_JOB_READY') |
| |
| # We want this to fail because the image cannot be locked. |
| # If it does not fail, continue still and see what happens. |
| self.vm_b = iotests.VM(path_suffix='b') |
| # Must use -blockdev -device so we can use share-rw. |
| # (And we need share-rw=on because mirror-top was always |
| # forced to take the WRITE permission so it can write to the |
| # source image.) |
| self.vm_b.add_blockdev(f'file,node-name=drive0,filename={source}') |
| self.vm_b.add_device('virtio-blk,drive=drive0,share-rw=on') |
| try: |
| # Silence QMP logging errors temporarily. |
| with change_log_level('qemu.qmp'): |
| self.vm_b.launch() |
| print('ERROR: VM B launched successfully, ' |
| 'this should not have happened') |
| except machine.VMLaunchFailure as exc: |
| assert 'Is another process using the image' in exc.output |
| |
| result = self.vm.qmp('block-job-cancel', |
| device='mirror') |
| self.assert_qmp(result, 'return', {}) |
| |
| self.vm.event_wait('BLOCK_JOB_COMPLETED') |
| |
| |
| if __name__ == '__main__': |
| # No metadata format driver supported, because they would for |
| # example always unshare the WRITE permission. The raw driver |
| # just passes through the permissions from the guest device, and |
| # those are the permissions that we want to test. |
| iotests.main(supported_fmts=['raw'], |
| supported_protocols=['file']) |