1
The following changes since commit 063833a6ec2a6747e27c5f9866bb44c7e8de1265:
1
The following changes since commit 1be5a765c08cee3a9587c8a8d3fc2ea247b13f9c:
2
2
3
Merge remote-tracking branch 'remotes/mcayland/tags/qemu-sparc-signed' into staging (2017-10-19 18:42:51 +0100)
3
Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into staging (2022-04-19 18:22:16 -0700)
4
4
5
are available in the git repository at:
5
are available in the Git repository at:
6
6
7
git://github.com/stefanha/qemu.git tags/block-pull-request
7
https://gitlab.com/hreitz/qemu.git tags/pull-block-2022-04-20
8
8
9
for you to fetch changes up to e947d47da0b16e80d237c510e9d2e80799578c7f:
9
for you to fetch changes up to 0423f75351ab83b844a31349218b0eadd830e07a:
10
10
11
oslib-posix: Fix compiler warning and some data types (2017-10-20 11:16:27 +0200)
11
qcow2: Add errp to rebuild_refcount_structure() (2022-04-20 12:09:17 +0200)
12
12
13
----------------------------------------------------------------
13
----------------------------------------------------------------
14
Block patches:
15
- Some changes for qcow2's refcount repair algorithm to make it work for
16
qcow2 images stored on block devices
17
- Skip test cases that require zstd when support for it is missing
18
- Some refactoring in the iotests' meson.build
14
19
15
----------------------------------------------------------------
20
----------------------------------------------------------------
21
Hanna Reitz (6):
22
iotests.py: Add supports_qcow2_zstd_compression()
23
iotests/065: Check for zstd support
24
iotests/303: Check for zstd support
25
qcow2: Improve refcount structure rebuilding
26
iotests/108: Test new refcount rebuild algorithm
27
qcow2: Add errp to rebuild_refcount_structure()
16
28
17
Stefan Weil (1):
29
Thomas Huth (2):
18
oslib-posix: Fix compiler warning and some data types
30
tests/qemu-iotests/meson.build: Improve the indentation
31
tests/qemu-iotests: Move the bash and sanitizer checks to meson.build
19
32
20
util/oslib-posix.c | 15 ++++++++-------
33
block/qcow2-refcount.c | 353 +++++++++++++++++++++++----------
21
1 file changed, 8 insertions(+), 7 deletions(-)
34
tests/check-block.sh | 26 ---
35
tests/qemu-iotests/065 | 24 ++-
36
tests/qemu-iotests/108 | 259 +++++++++++++++++++++++-
37
tests/qemu-iotests/108.out | 81 ++++++++
38
tests/qemu-iotests/303 | 4 +-
39
tests/qemu-iotests/iotests.py | 20 ++
40
tests/qemu-iotests/meson.build | 73 ++++---
41
8 files changed, 673 insertions(+), 167 deletions(-)
22
42
23
--
43
--
24
2.13.6
44
2.35.1
25
26
diff view generated by jsdifflib
1
From: Stefan Weil <sw@weilnetz.de>
1
From: Thomas Huth <thuth@redhat.com>
2
2
3
gcc warning:
3
By using subdir_done(), we can get rid of one level of indentation
4
in this file. This will make it easier to add more conditions to
5
skip the iotests in future patches.
4
6
5
/qemu/util/oslib-posix.c:304:11: error:
7
Reviewed-by: Hanna Reitz <hreitz@redhat.com>
6
variable ‘addr’ might be clobbered by ‘longjmp’ or ‘vfork’
8
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
7
[-Werror=clobbered]
9
Signed-off-by: Thomas Huth <thuth@redhat.com>
10
Message-Id: <20220223093840.2515281-3-thuth@redhat.com>
11
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
12
---
13
tests/qemu-iotests/meson.build | 61 ++++++++++++++++++----------------
14
1 file changed, 32 insertions(+), 29 deletions(-)
8
15
9
Fix also some related data types:
16
diff --git a/tests/qemu-iotests/meson.build b/tests/qemu-iotests/meson.build
10
11
numpages, hpagesize are used as pointer offset.
12
Always use size_t for them and also for the derived
13
numpages_per_thread and size_per_thread.
14
15
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
16
Signed-off-by: Stefan Weil <sw@weilnetz.de>
17
Message-id: 20171016202912.1117-1-sw@weilnetz.de
18
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
19
---
20
util/oslib-posix.c | 15 ++++++++-------
21
1 file changed, 8 insertions(+), 7 deletions(-)
22
23
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
24
index XXXXXXX..XXXXXXX 100644
17
index XXXXXXX..XXXXXXX 100644
25
--- a/util/oslib-posix.c
18
--- a/tests/qemu-iotests/meson.build
26
+++ b/util/oslib-posix.c
19
+++ b/tests/qemu-iotests/meson.build
27
@@ -XXX,XX +XXX,XX @@
20
@@ -XXX,XX +XXX,XX @@
28
21
-if have_tools and targetos != 'windows' and not get_option('gprof')
29
struct MemsetThread {
22
- qemu_iotests_binaries = [qemu_img, qemu_io, qemu_nbd, qsd]
30
char *addr;
23
- qemu_iotests_env = {'PYTHON': python.full_path()}
31
- uint64_t numpages;
24
- qemu_iotests_formats = {
32
- uint64_t hpagesize;
25
- 'qcow2': 'quick',
33
+ size_t numpages;
26
- 'raw': 'slow',
34
+ size_t hpagesize;
27
- 'qed': 'thorough',
35
QemuThread pgthread;
28
- 'vmdk': 'thorough',
36
sigjmp_buf env;
29
- 'vpc': 'thorough'
37
};
30
- }
38
@@ -XXX,XX +XXX,XX @@ static void sigbus_handler(int signal)
31
-
39
static void *do_touch_pages(void *arg)
32
- foreach k, v : emulators
40
{
33
- if k.startswith('qemu-system-')
41
MemsetThread *memset_args = (MemsetThread *)arg;
34
- qemu_iotests_binaries += v
42
- char *addr = memset_args->addr;
35
- endif
43
- uint64_t numpages = memset_args->numpages;
36
- endforeach
44
- uint64_t hpagesize = memset_args->hpagesize;
37
- foreach format, speed: qemu_iotests_formats
45
sigset_t set, oldset;
38
- if speed == 'quick'
46
- int i = 0;
39
- suites = 'block'
47
40
- else
48
/* unblock SIGBUS */
41
- suites = ['block-' + speed, speed]
49
sigemptyset(&set);
42
- endif
50
@@ -XXX,XX +XXX,XX @@ static void *do_touch_pages(void *arg)
43
- test('qemu-iotests ' + format, sh, args: [files('../check-block.sh'), format],
51
if (sigsetjmp(memset_args->env, 1)) {
44
- depends: qemu_iotests_binaries, env: qemu_iotests_env,
52
memset_thread_failed = true;
45
- protocol: 'tap',
53
} else {
46
- suite: suites,
54
+ char *addr = memset_args->addr;
47
- timeout: 0,
55
+ size_t numpages = memset_args->numpages;
48
- is_parallel: false)
56
+ size_t hpagesize = memset_args->hpagesize;
49
- endforeach
57
+ size_t i;
50
+if not have_tools or targetos == 'windows' or get_option('gprof')
58
for (i = 0; i < numpages; i++) {
51
+ subdir_done()
59
/*
52
endif
60
* Read & write back the same value, so we don't
53
+
61
@@ -XXX,XX +XXX,XX @@ static inline int get_memset_num_threads(int smp_cpus)
54
+qemu_iotests_binaries = [qemu_img, qemu_io, qemu_nbd, qsd]
62
static bool touch_all_pages(char *area, size_t hpagesize, size_t numpages,
55
+qemu_iotests_env = {'PYTHON': python.full_path()}
63
int smp_cpus)
56
+qemu_iotests_formats = {
64
{
57
+ 'qcow2': 'quick',
65
- uint64_t numpages_per_thread, size_per_thread;
58
+ 'raw': 'slow',
66
+ size_t numpages_per_thread;
59
+ 'qed': 'thorough',
67
+ size_t size_per_thread;
60
+ 'vmdk': 'thorough',
68
char *addr = area;
61
+ 'vpc': 'thorough'
69
int i = 0;
62
+}
70
63
+
64
+foreach k, v : emulators
65
+ if k.startswith('qemu-system-')
66
+ qemu_iotests_binaries += v
67
+ endif
68
+endforeach
69
+
70
+foreach format, speed: qemu_iotests_formats
71
+ if speed == 'quick'
72
+ suites = 'block'
73
+ else
74
+ suites = ['block-' + speed, speed]
75
+ endif
76
+ test('qemu-iotests ' + format, sh, args: [files('../check-block.sh'), format],
77
+ depends: qemu_iotests_binaries, env: qemu_iotests_env,
78
+ protocol: 'tap',
79
+ suite: suites,
80
+ timeout: 0,
81
+ is_parallel: false)
82
+endforeach
71
--
83
--
72
2.13.6
84
2.35.1
73
85
74
86
diff view generated by jsdifflib
New patch
1
From: Thomas Huth <thuth@redhat.com>
1
2
3
We want to get rid of check-block.sh in the long run, so let's move
4
the checks for the bash version and sanitizers from check-block.sh
5
into the meson.build file instead.
6
7
Signed-off-by: Thomas Huth <thuth@redhat.com>
8
Message-Id: <20220223093840.2515281-4-thuth@redhat.com>
9
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
10
---
11
tests/check-block.sh | 26 --------------------------
12
tests/qemu-iotests/meson.build | 14 ++++++++++++++
13
2 files changed, 14 insertions(+), 26 deletions(-)
14
15
diff --git a/tests/check-block.sh b/tests/check-block.sh
16
index XXXXXXX..XXXXXXX 100755
17
--- a/tests/check-block.sh
18
+++ b/tests/check-block.sh
19
@@ -XXX,XX +XXX,XX @@ skip() {
20
exit 0
21
}
22
23
-# Disable tests with any sanitizer except for specific ones
24
-SANITIZE_FLAGS=$( grep "CFLAGS.*-fsanitize" config-host.mak 2>/dev/null )
25
-ALLOWED_SANITIZE_FLAGS="safe-stack cfi-icall"
26
-#Remove all occurrencies of allowed Sanitize flags
27
-for j in ${ALLOWED_SANITIZE_FLAGS}; do
28
- TMP_FLAGS=${SANITIZE_FLAGS}
29
- SANITIZE_FLAGS=""
30
- for i in ${TMP_FLAGS}; do
31
- if ! echo ${i} | grep -q "${j}" 2>/dev/null; then
32
- SANITIZE_FLAGS="${SANITIZE_FLAGS} ${i}"
33
- fi
34
- done
35
-done
36
-if echo ${SANITIZE_FLAGS} | grep -q "\-fsanitize" 2>/dev/null; then
37
- # Have a sanitize flag that is not allowed, stop
38
- skip "Sanitizers are enabled ==> Not running the qemu-iotests."
39
-fi
40
-
41
if [ -z "$(find . -name 'qemu-system-*' -print)" ]; then
42
skip "No qemu-system binary available ==> Not running the qemu-iotests."
43
fi
44
45
-if ! command -v bash >/dev/null 2>&1 ; then
46
- skip "bash not available ==> Not running the qemu-iotests."
47
-fi
48
-
49
-if LANG=C bash --version | grep -q 'GNU bash, version [123]' ; then
50
- skip "bash version too old ==> Not running the qemu-iotests."
51
-fi
52
-
53
cd tests/qemu-iotests
54
55
# QEMU_CHECK_BLOCK_AUTO is used to disable some unstable sub-tests
56
diff --git a/tests/qemu-iotests/meson.build b/tests/qemu-iotests/meson.build
57
index XXXXXXX..XXXXXXX 100644
58
--- a/tests/qemu-iotests/meson.build
59
+++ b/tests/qemu-iotests/meson.build
60
@@ -XXX,XX +XXX,XX @@ if not have_tools or targetos == 'windows' or get_option('gprof')
61
subdir_done()
62
endif
63
64
+foreach cflag: config_host['QEMU_CFLAGS'].split()
65
+ if cflag.startswith('-fsanitize') and \
66
+ not cflag.contains('safe-stack') and not cflag.contains('cfi-icall')
67
+ message('Sanitizers are enabled ==> Disabled the qemu-iotests.')
68
+ subdir_done()
69
+ endif
70
+endforeach
71
+
72
+bash = find_program('bash', required: false, version: '>= 4.0')
73
+if not bash.found()
74
+ message('bash >= v4.0 not available ==> Disabled the qemu-iotests.')
75
+ subdir_done()
76
+endif
77
+
78
qemu_iotests_binaries = [qemu_img, qemu_io, qemu_nbd, qsd]
79
qemu_iotests_env = {'PYTHON': python.full_path()}
80
qemu_iotests_formats = {
81
--
82
2.35.1
diff view generated by jsdifflib
New patch
1
Suggested-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
3
Message-Id: <20220323105522.53660-2-hreitz@redhat.com>
4
Reviewed-by: Vladimir Sementsov-Ogievskiy <v.sementsov-og@mail.ru>
5
---
6
tests/qemu-iotests/iotests.py | 20 ++++++++++++++++++++
7
1 file changed, 20 insertions(+)
1
8
9
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
10
index XXXXXXX..XXXXXXX 100644
11
--- a/tests/qemu-iotests/iotests.py
12
+++ b/tests/qemu-iotests/iotests.py
13
@@ -XXX,XX +XXX,XX @@ def verify_working_luks():
14
if not working:
15
notrun(reason)
16
17
+def supports_qcow2_zstd_compression() -> bool:
18
+ img_file = f'{test_dir}/qcow2-zstd-test.qcow2'
19
+ res = qemu_img('create', '-f', 'qcow2', '-o', 'compression_type=zstd',
20
+ img_file, '0',
21
+ check=False)
22
+ try:
23
+ os.remove(img_file)
24
+ except OSError:
25
+ pass
26
+
27
+ if res.returncode == 1 and \
28
+ "'compression-type' does not accept value 'zstd'" in res.stdout:
29
+ return False
30
+ else:
31
+ return True
32
+
33
+def verify_qcow2_zstd_compression():
34
+ if not supports_qcow2_zstd_compression():
35
+ notrun('zstd compression not supported')
36
+
37
def qemu_pipe(*args: str) -> str:
38
"""
39
Run qemu with an option to print something and exit (e.g. a help option).
40
--
41
2.35.1
diff view generated by jsdifflib
New patch
1
Some test cases run in iotest 065 want to run with zstd compression just
2
for added coverage. Run them with zlib if there is no zstd support
3
compiled in.
1
4
5
Reported-by: Thomas Huth <thuth@redhat.com>
6
Fixes: 12a936171d71f839dc907ff ("iotest 065: explicit compression type")
7
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
8
Message-Id: <20220323105522.53660-3-hreitz@redhat.com>
9
Reviewed-by: Vladimir Sementsov-Ogievskiy <v.sementsov-og@mail.ru>
10
---
11
tests/qemu-iotests/065 | 24 ++++++++++++++++++------
12
1 file changed, 18 insertions(+), 6 deletions(-)
13
14
diff --git a/tests/qemu-iotests/065 b/tests/qemu-iotests/065
15
index XXXXXXX..XXXXXXX 100755
16
--- a/tests/qemu-iotests/065
17
+++ b/tests/qemu-iotests/065
18
@@ -XXX,XX +XXX,XX @@ import os
19
import re
20
import json
21
import iotests
22
-from iotests import qemu_img, qemu_img_info
23
+from iotests import qemu_img, qemu_img_info, supports_qcow2_zstd_compression
24
import unittest
25
26
test_img = os.path.join(iotests.test_dir, 'test.img')
27
@@ -XXX,XX +XXX,XX @@ class TestQCow2(TestQemuImgInfo):
28
29
class TestQCow3NotLazy(TestQemuImgInfo):
30
'''Testing a qcow2 version 3 image with lazy refcounts disabled'''
31
- img_options = 'compat=1.1,lazy_refcounts=off,compression_type=zstd'
32
+ if supports_qcow2_zstd_compression():
33
+ compression_type = 'zstd'
34
+ else:
35
+ compression_type = 'zlib'
36
+
37
+ img_options = 'compat=1.1,lazy_refcounts=off'
38
+ img_options += f',compression_type={compression_type}'
39
json_compare = { 'compat': '1.1', 'lazy-refcounts': False,
40
'refcount-bits': 16, 'corrupt': False,
41
- 'compression-type': 'zstd', 'extended-l2': False }
42
- human_compare = [ 'compat: 1.1', 'compression type: zstd',
43
+ 'compression-type': compression_type, 'extended-l2': False }
44
+ human_compare = [ 'compat: 1.1', f'compression type: {compression_type}',
45
'lazy refcounts: false', 'refcount bits: 16',
46
'corrupt: false', 'extended l2: false' ]
47
48
@@ -XXX,XX +XXX,XX @@ class TestQCow3NotLazyQMP(TestQMP):
49
class TestQCow3LazyQMP(TestQMP):
50
'''Testing a qcow2 version 3 image with lazy refcounts enabled, opening
51
with lazy refcounts disabled'''
52
- img_options = 'compat=1.1,lazy_refcounts=on,compression_type=zstd'
53
+ if supports_qcow2_zstd_compression():
54
+ compression_type = 'zstd'
55
+ else:
56
+ compression_type = 'zlib'
57
+
58
+ img_options = 'compat=1.1,lazy_refcounts=on'
59
+ img_options += f',compression_type={compression_type}'
60
qemu_options = 'lazy-refcounts=off'
61
compare = { 'compat': '1.1', 'lazy-refcounts': True,
62
'refcount-bits': 16, 'corrupt': False,
63
- 'compression-type': 'zstd', 'extended-l2': False }
64
+ 'compression-type': compression_type, 'extended-l2': False }
65
66
TestImageInfoSpecific = None
67
TestQemuImgInfo = None
68
--
69
2.35.1
diff view generated by jsdifflib
New patch
1
303 runs two test cases, one of which requires zstd support.
2
Unfortunately, given that this is not a unittest-style test, we cannot
3
easily skip that single case, and instead can only skip the whole test.
1
4
5
(Alternatively, we could split this test into a zlib and a zstd part,
6
but that seems excessive, given that this test is not in auto and thus
7
likely only run by developers who have zstd support compiled in.)
8
9
Fixes: 677e0bae686e7c670a71d1f ("iotest 303: explicit compression type")
10
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
11
Reviewed-by: Vladimir Sementsov-Ogievskiy <v.sementsov-og@mail.ru>
12
Message-Id: <20220323105522.53660-4-hreitz@redhat.com>
13
---
14
tests/qemu-iotests/303 | 4 +++-
15
1 file changed, 3 insertions(+), 1 deletion(-)
16
17
diff --git a/tests/qemu-iotests/303 b/tests/qemu-iotests/303
18
index XXXXXXX..XXXXXXX 100755
19
--- a/tests/qemu-iotests/303
20
+++ b/tests/qemu-iotests/303
21
@@ -XXX,XX +XXX,XX @@
22
23
import iotests
24
import subprocess
25
-from iotests import qemu_img_create, qemu_io, file_path, log, filter_qemu_io
26
+from iotests import qemu_img_create, qemu_io, file_path, log, filter_qemu_io, \
27
+ verify_qcow2_zstd_compression
28
29
iotests.script_initialize(supported_fmts=['qcow2'],
30
unsupported_imgopts=['refcount_bits', 'compat'])
31
+verify_qcow2_zstd_compression()
32
33
disk = file_path('disk')
34
chunk = 1024 * 1024
35
--
36
2.35.1
diff view generated by jsdifflib
New patch
1
When rebuilding the refcount structures (when qemu-img check -r found
2
errors with refcount = 0, but reference count > 0), the new refcount
3
table defaults to being put at the image file end[1]. There is no good
4
reason for that except that it means we will not have to rewrite any
5
refblocks we already wrote to disk.
1
6
7
Changing the code to rewrite those refblocks is not too difficult,
8
though, so let us do that. That is beneficial for images on block
9
devices, where we cannot really write beyond the end of the image file.
10
11
Use this opportunity to add extensive comments to the code, and refactor
12
it a bit, getting rid of the backwards-jumping goto.
13
14
[1] Unless there is something allocated in the area pointed to by the
15
last refblock, so we have to write that refblock. In that case, we
16
try to put the reftable in there.
17
18
Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1519071
19
Closes: https://gitlab.com/qemu-project/qemu/-/issues/941
20
Reviewed-by: Eric Blake <eblake@redhat.com>
21
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
22
Message-Id: <20220405134652.19278-2-hreitz@redhat.com>
23
---
24
block/qcow2-refcount.c | 332 +++++++++++++++++++++++++++++------------
25
1 file changed, 235 insertions(+), 97 deletions(-)
26
27
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
28
index XXXXXXX..XXXXXXX 100644
29
--- a/block/qcow2-refcount.c
30
+++ b/block/qcow2-refcount.c
31
@@ -XXX,XX +XXX,XX @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
32
}
33
34
/*
35
- * Creates a new refcount structure based solely on the in-memory information
36
- * given through *refcount_table. All necessary allocations will be reflected
37
- * in that array.
38
+ * Helper function for rebuild_refcount_structure().
39
*
40
- * On success, the old refcount structure is leaked (it will be covered by the
41
- * new refcount structure).
42
+ * Scan the range of clusters [first_cluster, end_cluster) for allocated
43
+ * clusters and write all corresponding refblocks to disk. The refblock
44
+ * and allocation data is taken from the in-memory refcount table
45
+ * *refcount_table[] (of size *nb_clusters), which is basically one big
46
+ * (unlimited size) refblock for the whole image.
47
+ *
48
+ * For these refblocks, clusters are allocated using said in-memory
49
+ * refcount table. Care is taken that these allocations are reflected
50
+ * in the refblocks written to disk.
51
+ *
52
+ * The refblocks' offsets are written into a reftable, which is
53
+ * *on_disk_reftable_ptr[] (of size *on_disk_reftable_entries_ptr). If
54
+ * that reftable is of insufficient size, it will be resized to fit.
55
+ * This reftable is not written to disk.
56
+ *
57
+ * (If *on_disk_reftable_ptr is not NULL, the entries within are assumed
58
+ * to point to existing valid refblocks that do not need to be allocated
59
+ * again.)
60
+ *
61
+ * Return whether the on-disk reftable array was resized (true/false),
62
+ * or -errno on error.
63
*/
64
-static int rebuild_refcount_structure(BlockDriverState *bs,
65
- BdrvCheckResult *res,
66
- void **refcount_table,
67
- int64_t *nb_clusters)
68
+static int rebuild_refcounts_write_refblocks(
69
+ BlockDriverState *bs, void **refcount_table, int64_t *nb_clusters,
70
+ int64_t first_cluster, int64_t end_cluster,
71
+ uint64_t **on_disk_reftable_ptr, uint32_t *on_disk_reftable_entries_ptr
72
+ )
73
{
74
BDRVQcow2State *s = bs->opaque;
75
- int64_t first_free_cluster = 0, reftable_offset = -1, cluster = 0;
76
+ int64_t cluster;
77
int64_t refblock_offset, refblock_start, refblock_index;
78
- uint32_t reftable_size = 0;
79
- uint64_t *on_disk_reftable = NULL;
80
+ int64_t first_free_cluster = 0;
81
+ uint64_t *on_disk_reftable = *on_disk_reftable_ptr;
82
+ uint32_t on_disk_reftable_entries = *on_disk_reftable_entries_ptr;
83
void *on_disk_refblock;
84
- int ret = 0;
85
- struct {
86
- uint64_t reftable_offset;
87
- uint32_t reftable_clusters;
88
- } QEMU_PACKED reftable_offset_and_clusters;
89
-
90
- qcow2_cache_empty(bs, s->refcount_block_cache);
91
+ bool reftable_grown = false;
92
+ int ret;
93
94
-write_refblocks:
95
- for (; cluster < *nb_clusters; cluster++) {
96
+ for (cluster = first_cluster; cluster < end_cluster; cluster++) {
97
+ /* Check all clusters to find refblocks that contain non-zero entries */
98
if (!s->get_refcount(*refcount_table, cluster)) {
99
continue;
100
}
101
102
+ /*
103
+ * This cluster is allocated, so we need to create a refblock
104
+ * for it. The data we will write to disk is just the
105
+ * respective slice from *refcount_table, so it will contain
106
+ * accurate refcounts for all clusters belonging to this
107
+ * refblock. After we have written it, we will therefore skip
108
+ * all remaining clusters in this refblock.
109
+ */
110
+
111
refblock_index = cluster >> s->refcount_block_bits;
112
refblock_start = refblock_index << s->refcount_block_bits;
113
114
- /* Don't allocate a cluster in a refblock already written to disk */
115
- if (first_free_cluster < refblock_start) {
116
- first_free_cluster = refblock_start;
117
- }
118
- refblock_offset = alloc_clusters_imrt(bs, 1, refcount_table,
119
- nb_clusters, &first_free_cluster);
120
- if (refblock_offset < 0) {
121
- fprintf(stderr, "ERROR allocating refblock: %s\n",
122
- strerror(-refblock_offset));
123
- res->check_errors++;
124
- ret = refblock_offset;
125
- goto fail;
126
- }
127
+ if (on_disk_reftable_entries > refblock_index &&
128
+ on_disk_reftable[refblock_index])
129
+ {
130
+ /*
131
+ * We can get here after a `goto write_refblocks`: We have a
132
+ * reftable from a previous run, and the refblock is already
133
+ * allocated. No need to allocate it again.
134
+ */
135
+ refblock_offset = on_disk_reftable[refblock_index];
136
+ } else {
137
+ int64_t refblock_cluster_index;
138
139
- if (reftable_size <= refblock_index) {
140
- uint32_t old_reftable_size = reftable_size;
141
- uint64_t *new_on_disk_reftable;
142
+ /* Don't allocate a cluster in a refblock already written to disk */
143
+ if (first_free_cluster < refblock_start) {
144
+ first_free_cluster = refblock_start;
145
+ }
146
+ refblock_offset = alloc_clusters_imrt(bs, 1, refcount_table,
147
+ nb_clusters,
148
+ &first_free_cluster);
149
+ if (refblock_offset < 0) {
150
+ fprintf(stderr, "ERROR allocating refblock: %s\n",
151
+ strerror(-refblock_offset));
152
+ return refblock_offset;
153
+ }
154
155
- reftable_size = ROUND_UP((refblock_index + 1) * REFTABLE_ENTRY_SIZE,
156
- s->cluster_size) / REFTABLE_ENTRY_SIZE;
157
- new_on_disk_reftable = g_try_realloc(on_disk_reftable,
158
- reftable_size *
159
- REFTABLE_ENTRY_SIZE);
160
- if (!new_on_disk_reftable) {
161
- res->check_errors++;
162
- ret = -ENOMEM;
163
- goto fail;
164
+ refblock_cluster_index = refblock_offset / s->cluster_size;
165
+ if (refblock_cluster_index >= end_cluster) {
166
+ /*
167
+ * We must write the refblock that holds this refblock's
168
+ * refcount
169
+ */
170
+ end_cluster = refblock_cluster_index + 1;
171
}
172
- on_disk_reftable = new_on_disk_reftable;
173
174
- memset(on_disk_reftable + old_reftable_size, 0,
175
- (reftable_size - old_reftable_size) * REFTABLE_ENTRY_SIZE);
176
+ if (on_disk_reftable_entries <= refblock_index) {
177
+ on_disk_reftable_entries =
178
+ ROUND_UP((refblock_index + 1) * REFTABLE_ENTRY_SIZE,
179
+ s->cluster_size) / REFTABLE_ENTRY_SIZE;
180
+ on_disk_reftable =
181
+ g_try_realloc(on_disk_reftable,
182
+ on_disk_reftable_entries *
183
+ REFTABLE_ENTRY_SIZE);
184
+ if (!on_disk_reftable) {
185
+ return -ENOMEM;
186
+ }
187
188
- /* The offset we have for the reftable is now no longer valid;
189
- * this will leak that range, but we can easily fix that by running
190
- * a leak-fixing check after this rebuild operation */
191
- reftable_offset = -1;
192
- } else {
193
- assert(on_disk_reftable);
194
- }
195
- on_disk_reftable[refblock_index] = refblock_offset;
196
+ memset(on_disk_reftable + *on_disk_reftable_entries_ptr, 0,
197
+ (on_disk_reftable_entries -
198
+ *on_disk_reftable_entries_ptr) *
199
+ REFTABLE_ENTRY_SIZE);
200
201
- /* If this is apparently the last refblock (for now), try to squeeze the
202
- * reftable in */
203
- if (refblock_index == (*nb_clusters - 1) >> s->refcount_block_bits &&
204
- reftable_offset < 0)
205
- {
206
- uint64_t reftable_clusters = size_to_clusters(s, reftable_size *
207
- REFTABLE_ENTRY_SIZE);
208
- reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,
209
- refcount_table, nb_clusters,
210
- &first_free_cluster);
211
- if (reftable_offset < 0) {
212
- fprintf(stderr, "ERROR allocating reftable: %s\n",
213
- strerror(-reftable_offset));
214
- res->check_errors++;
215
- ret = reftable_offset;
216
- goto fail;
217
+ *on_disk_reftable_ptr = on_disk_reftable;
218
+ *on_disk_reftable_entries_ptr = on_disk_reftable_entries;
219
+
220
+ reftable_grown = true;
221
+ } else {
222
+ assert(on_disk_reftable);
223
}
224
+ on_disk_reftable[refblock_index] = refblock_offset;
225
}
226
227
+ /* Refblock is allocated, write it to disk */
228
+
229
ret = qcow2_pre_write_overlap_check(bs, 0, refblock_offset,
230
s->cluster_size, false);
231
if (ret < 0) {
232
fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
233
- goto fail;
234
+ return ret;
235
}
236
237
- /* The size of *refcount_table is always cluster-aligned, therefore the
238
- * write operation will not overflow */
239
+ /*
240
+ * The refblock is simply a slice of *refcount_table.
241
+ * Note that the size of *refcount_table is always aligned to
242
+ * whole clusters, so the write operation will not result in
243
+ * out-of-bounds accesses.
244
+ */
245
on_disk_refblock = (void *)((char *) *refcount_table +
246
refblock_index * s->cluster_size);
247
248
@@ -XXX,XX +XXX,XX @@ write_refblocks:
249
s->cluster_size);
250
if (ret < 0) {
251
fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
252
- goto fail;
253
+ return ret;
254
}
255
256
- /* Go to the end of this refblock */
257
+ /* This refblock is done, skip to its end */
258
cluster = refblock_start + s->refcount_block_size - 1;
259
}
260
261
- if (reftable_offset < 0) {
262
- uint64_t post_refblock_start, reftable_clusters;
263
+ return reftable_grown;
264
+}
265
+
266
+/*
267
+ * Creates a new refcount structure based solely on the in-memory information
268
+ * given through *refcount_table (this in-memory information is basically just
269
+ * the concatenation of all refblocks). All necessary allocations will be
270
+ * reflected in that array.
271
+ *
272
+ * On success, the old refcount structure is leaked (it will be covered by the
273
+ * new refcount structure).
274
+ */
275
+static int rebuild_refcount_structure(BlockDriverState *bs,
276
+ BdrvCheckResult *res,
277
+ void **refcount_table,
278
+ int64_t *nb_clusters)
279
+{
280
+ BDRVQcow2State *s = bs->opaque;
281
+ int64_t reftable_offset = -1;
282
+ int64_t reftable_length = 0;
283
+ int64_t reftable_clusters;
284
+ int64_t refblock_index;
285
+ uint32_t on_disk_reftable_entries = 0;
286
+ uint64_t *on_disk_reftable = NULL;
287
+ int ret = 0;
288
+ int reftable_size_changed = 0;
289
+ struct {
290
+ uint64_t reftable_offset;
291
+ uint32_t reftable_clusters;
292
+ } QEMU_PACKED reftable_offset_and_clusters;
293
+
294
+ qcow2_cache_empty(bs, s->refcount_block_cache);
295
+
296
+ /*
297
+ * For each refblock containing entries, we try to allocate a
298
+ * cluster (in the in-memory refcount table) and write its offset
299
+ * into on_disk_reftable[]. We then write the whole refblock to
300
+ * disk (as a slice of the in-memory refcount table).
301
+ * This is done by rebuild_refcounts_write_refblocks().
302
+ *
303
+ * Once we have scanned all clusters, we try to find space for the
304
+ * reftable. This will dirty the in-memory refcount table (i.e.
305
+ * make it differ from the refblocks we have already written), so we
306
+ * need to run rebuild_refcounts_write_refblocks() again for the
307
+ * range of clusters where the reftable has been allocated.
308
+ *
309
+ * This second run might make the reftable grow again, in which case
310
+ * we will need to allocate another space for it, which is why we
311
+ * repeat all this until the reftable stops growing.
312
+ *
313
+ * (This loop will terminate, because with every cluster the
314
+ * reftable grows, it can accomodate a multitude of more refcounts,
315
+ * so that at some point this must be able to cover the reftable
316
+ * and all refblocks describing it.)
317
+ *
318
+ * We then convert the reftable to big-endian and write it to disk.
319
+ *
320
+ * Note that we never free any reftable allocations. Doing so would
321
+ * needlessly complicate the algorithm: The eventual second check
322
+ * run we do will clean up all leaks we have caused.
323
+ */
324
+
325
+ reftable_size_changed =
326
+ rebuild_refcounts_write_refblocks(bs, refcount_table, nb_clusters,
327
+ 0, *nb_clusters,
328
+ &on_disk_reftable,
329
+ &on_disk_reftable_entries);
330
+ if (reftable_size_changed < 0) {
331
+ res->check_errors++;
332
+ ret = reftable_size_changed;
333
+ goto fail;
334
+ }
335
+
336
+ /*
337
+ * There was no reftable before, so rebuild_refcounts_write_refblocks()
338
+ * must have increased its size (from 0 to something).
339
+ */
340
+ assert(reftable_size_changed);
341
+
342
+ do {
343
+ int64_t reftable_start_cluster, reftable_end_cluster;
344
+ int64_t first_free_cluster = 0;
345
+
346
+ reftable_length = on_disk_reftable_entries * REFTABLE_ENTRY_SIZE;
347
+ reftable_clusters = size_to_clusters(s, reftable_length);
348
349
- post_refblock_start = ROUND_UP(*nb_clusters, s->refcount_block_size);
350
- reftable_clusters =
351
- size_to_clusters(s, reftable_size * REFTABLE_ENTRY_SIZE);
352
- /* Not pretty but simple */
353
- if (first_free_cluster < post_refblock_start) {
354
- first_free_cluster = post_refblock_start;
355
- }
356
reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,
357
refcount_table, nb_clusters,
358
&first_free_cluster);
359
@@ -XXX,XX +XXX,XX @@ write_refblocks:
360
goto fail;
361
}
362
363
- goto write_refblocks;
364
- }
365
+ /*
366
+ * We need to update the affected refblocks, so re-run the
367
+ * write_refblocks loop for the reftable's range of clusters.
368
+ */
369
+ assert(offset_into_cluster(s, reftable_offset) == 0);
370
+ reftable_start_cluster = reftable_offset / s->cluster_size;
371
+ reftable_end_cluster = reftable_start_cluster + reftable_clusters;
372
+ reftable_size_changed =
373
+ rebuild_refcounts_write_refblocks(bs, refcount_table, nb_clusters,
374
+ reftable_start_cluster,
375
+ reftable_end_cluster,
376
+ &on_disk_reftable,
377
+ &on_disk_reftable_entries);
378
+ if (reftable_size_changed < 0) {
379
+ res->check_errors++;
380
+ ret = reftable_size_changed;
381
+ goto fail;
382
+ }
383
+
384
+ /*
385
+ * If the reftable size has changed, we will need to find a new
386
+ * allocation, repeating the loop.
387
+ */
388
+ } while (reftable_size_changed);
389
390
- for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {
391
+ /* The above loop must have run at least once */
392
+ assert(reftable_offset >= 0);
393
+
394
+ /*
395
+ * All allocations are done, all refblocks are written, convert the
396
+ * reftable to big-endian and write it to disk.
397
+ */
398
+
399
+ for (refblock_index = 0; refblock_index < on_disk_reftable_entries;
400
+ refblock_index++)
401
+ {
402
cpu_to_be64s(&on_disk_reftable[refblock_index]);
403
}
404
405
- ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset,
406
- reftable_size * REFTABLE_ENTRY_SIZE,
407
+ ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset, reftable_length,
408
false);
409
if (ret < 0) {
410
fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
411
goto fail;
412
}
413
414
- assert(reftable_size < INT_MAX / REFTABLE_ENTRY_SIZE);
415
+ assert(reftable_length < INT_MAX);
416
ret = bdrv_pwrite(bs->file, reftable_offset, on_disk_reftable,
417
- reftable_size * REFTABLE_ENTRY_SIZE);
418
+ reftable_length);
419
if (ret < 0) {
420
fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
421
goto fail;
422
@@ -XXX,XX +XXX,XX @@ write_refblocks:
423
/* Enter new reftable into the image header */
424
reftable_offset_and_clusters.reftable_offset = cpu_to_be64(reftable_offset);
425
reftable_offset_and_clusters.reftable_clusters =
426
- cpu_to_be32(size_to_clusters(s, reftable_size * REFTABLE_ENTRY_SIZE));
427
+ cpu_to_be32(reftable_clusters);
428
ret = bdrv_pwrite_sync(bs->file,
429
offsetof(QCowHeader, refcount_table_offset),
430
&reftable_offset_and_clusters,
431
@@ -XXX,XX +XXX,XX @@ write_refblocks:
432
goto fail;
433
}
434
435
- for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {
436
+ for (refblock_index = 0; refblock_index < on_disk_reftable_entries;
437
+ refblock_index++)
438
+ {
439
be64_to_cpus(&on_disk_reftable[refblock_index]);
440
}
441
s->refcount_table = on_disk_reftable;
442
s->refcount_table_offset = reftable_offset;
443
- s->refcount_table_size = reftable_size;
444
+ s->refcount_table_size = on_disk_reftable_entries;
445
update_max_refcount_table_index(s);
446
447
return 0;
448
--
449
2.35.1
diff view generated by jsdifflib
New patch
1
One clear problem with how qcow2's refcount structure rebuild algorithm
2
used to be before "qcow2: Improve refcount structure rebuilding" was
3
that it is prone to failure for qcow2 images on block devices: There is
4
generally unused space after the actual image, and if that exceeds what
5
one refblock covers, the old algorithm would invariably write the
6
reftable past the block device's end, which cannot work. The new
7
algorithm does not have this problem.
1
8
9
Test it with three tests:
10
(1) Create an image with more empty space at the end than what one
11
refblock covers, see whether rebuilding the refcount structures
12
results in a change in the image file length. (It should not.)
13
14
(2) Leave precisely enough space somewhere at the beginning of the image
15
for the new reftable (and the refblock for that place), see whether
16
the new algorithm puts the reftable there. (It should.)
17
18
(3) Test the original problem: Create (something like) a block device
19
with a fixed size, then create a qcow2 image in there, write some
20
data, and then have qemu-img check rebuild the refcount structures.
21
Before HEAD^, the reftable would have been written past the image
22
file end, i.e. outside of what the block device provides, which
23
cannot work. HEAD^ should have fixed that.
24
("Something like a block device" means a loop device if we can use
25
one ("sudo -n losetup" works), or a FUSE block export with
26
growable=false otherwise.)
27
28
Reviewed-by: Eric Blake <eblake@redhat.com>
29
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
30
Message-Id: <20220405134652.19278-3-hreitz@redhat.com>
31
---
32
tests/qemu-iotests/108 | 259 ++++++++++++++++++++++++++++++++++++-
33
tests/qemu-iotests/108.out | 81 ++++++++++++
34
2 files changed, 339 insertions(+), 1 deletion(-)
35
36
diff --git a/tests/qemu-iotests/108 b/tests/qemu-iotests/108
37
index XXXXXXX..XXXXXXX 100755
38
--- a/tests/qemu-iotests/108
39
+++ b/tests/qemu-iotests/108
40
@@ -XXX,XX +XXX,XX @@ status=1    # failure is the default!
41
42
_cleanup()
43
{
44
-    _cleanup_test_img
45
+ _cleanup_test_img
46
+ if [ -f "$TEST_DIR/qsd.pid" ]; then
47
+ qsd_pid=$(cat "$TEST_DIR/qsd.pid")
48
+ kill -KILL "$qsd_pid"
49
+ fusermount -u "$TEST_DIR/fuse-export" &>/dev/null
50
+ fi
51
+ rm -f "$TEST_DIR/fuse-export"
52
}
53
trap "_cleanup; exit \$status" 0 1 2 3 15
54
55
# get standard environment, filters and checks
56
. ./common.rc
57
. ./common.filter
58
+. ./common.qemu
59
60
# This tests qcow2-specific low-level functionality
61
_supported_fmt qcow2
62
@@ -XXX,XX +XXX,XX @@ _supported_os Linux
63
# files
64
_unsupported_imgopts 'refcount_bits=\([^1]\|.\([^6]\|$\)\)' data_file
65
66
+# This test either needs sudo -n losetup or FUSE exports to work
67
+if sudo -n losetup &>/dev/null; then
68
+ loopdev=true
69
+else
70
+ loopdev=false
71
+
72
+ # QSD --export fuse will either yield "Parameter 'id' is missing"
73
+ # or "Invalid parameter 'fuse'", depending on whether there is
74
+ # FUSE support or not.
75
+ error=$($QSD --export fuse 2>&1)
76
+ if [[ $error = *"'fuse'"* ]]; then
77
+ _notrun 'Passwordless sudo for losetup or FUSE support required, but' \
78
+ 'neither is available'
79
+ fi
80
+fi
81
+
82
echo
83
echo '=== Repairing an image without any refcount table ==='
84
echo
85
@@ -XXX,XX +XXX,XX @@ _make_test_img 64M
86
poke_file "$TEST_IMG" $((0x10008)) "\xff\xff\xff\xff\xff\xff\x00\x00"
87
_check_test_img -r all
88
89
+echo
90
+echo '=== Check rebuilt reftable location ==='
91
+
92
+# In an earlier version of the refcount rebuild algorithm, the
93
+# reftable was generally placed at the image end (unless something was
94
+# allocated in the area covered by the refblock right before the image
95
+# file end, then we would try to place the reftable in that refblock).
96
+# This was later changed so the reftable would be placed in the
97
+# earliest possible location. Test this.
98
+
99
+echo
100
+echo '--- Does the image size increase? ---'
101
+echo
102
+
103
+# First test: Just create some image, write some data to it, and
104
+# resize it so there is free space at the end of the image (enough
105
+# that it spans at least one full refblock, which for cluster_size=512
106
+# images, spans 128k). With the old algorithm, the reftable would
107
+# have then been placed at the end of the image file, but with the new
108
+# one, it will be put in that free space.
109
+# We want to check whether the size of the image file increases due to
110
+# rebuilding the refcount structures (it should not).
111
+
112
+_make_test_img -o 'cluster_size=512' 1M
113
+# Write something
114
+$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io
115
+
116
+# Add free space
117
+file_len=$(stat -c '%s' "$TEST_IMG")
118
+truncate -s $((file_len + 256 * 1024)) "$TEST_IMG"
119
+
120
+# Corrupt the image by saying the image header was not allocated
121
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
122
+rb_offset=$(peek_file_be "$TEST_IMG" $rt_offset 8)
123
+poke_file "$TEST_IMG" $rb_offset "\x00\x00"
124
+
125
+# Check whether rebuilding the refcount structures increases the image
126
+# file size
127
+file_len=$(stat -c '%s' "$TEST_IMG")
128
+echo
129
+# The only leaks there can be are the old refcount structures that are
130
+# leaked during rebuilding, no need to clutter the output with them
131
+_check_test_img -r all | grep -v '^Repairing cluster.*refcount=1 reference=0'
132
+echo
133
+post_repair_file_len=$(stat -c '%s' "$TEST_IMG")
134
+
135
+if [[ $file_len -eq $post_repair_file_len ]]; then
136
+ echo 'OK: Image size did not change'
137
+else
138
+ echo 'ERROR: Image size differs' \
139
+ "($file_len before, $post_repair_file_len after)"
140
+fi
141
+
142
+echo
143
+echo '--- Will the reftable occupy a hole specifically left for it? ---'
144
+echo
145
+
146
+# Note: With cluster_size=512, every refblock covers 128k.
147
+# The reftable covers 8M per reftable cluster.
148
+
149
+# Create an image that requires two reftable clusters (just because
150
+# this is more interesting than a single-clustered reftable).
151
+_make_test_img -o 'cluster_size=512' 9M
152
+$QEMU_IO -c 'write 0 8M' "$TEST_IMG" | _filter_qemu_io
153
+
154
+# Writing 8M will have resized the reftable. Unfortunately, doing so
155
+# will leave holes in the file, so we need to fill them up so we can
156
+# be sure the whole file is allocated. Do that by writing
157
+# consecutively smaller chunks starting from 8 MB, until the file
158
+# length increases even with a chunk size of 512. Then we must have
159
+# filled all holes.
160
+ofs=$((8 * 1024 * 1024))
161
+block_len=$((16 * 1024))
162
+while [[ $block_len -ge 512 ]]; do
163
+ file_len=$(stat -c '%s' "$TEST_IMG")
164
+ while [[ $(stat -c '%s' "$TEST_IMG") -eq $file_len ]]; do
165
+ # Do not include this in the reference output, it does not
166
+ # really matter which qemu-io calls we do here exactly
167
+ $QEMU_IO -c "write $ofs $block_len" "$TEST_IMG" >/dev/null
168
+ ofs=$((ofs + block_len))
169
+ done
170
+ block_len=$((block_len / 2))
171
+done
172
+
173
+# Fill up to 9M (do not include this in the reference output either,
174
+# $ofs is random for all we know)
175
+$QEMU_IO -c "write $ofs $((9 * 1024 * 1024 - ofs))" "$TEST_IMG" >/dev/null
176
+
177
+# Make space as follows:
178
+# - For the first refblock: Right at the beginning of the image (this
179
+# refblock is placed in the first place possible),
180
+# - For the reftable somewhere soon afterwards, still near the
181
+# beginning of the image (i.e. covered by the first refblock); the
182
+# reftable too is placed in the first place possible, but only after
183
+# all refblocks have been placed)
184
+# No space is needed for the other refblocks, because no refblock is
185
+# put before the space it covers. In this test case, we do not mind
186
+# if they are placed at the image file's end.
187
+
188
+# Before we make that space, we have to find out the host offset of
189
+# the area that belonged to the two data clusters at guest offset 4k,
190
+# because we expect the reftable to be placed there, and we will have
191
+# to verify that it is.
192
+
193
+l1_offset=$(peek_file_be "$TEST_IMG" 40 8)
194
+l2_offset=$(peek_file_be "$TEST_IMG" $l1_offset 8)
195
+l2_offset=$((l2_offset & 0x00fffffffffffe00))
196
+data_4k_offset=$(peek_file_be "$TEST_IMG" \
197
+ $((l2_offset + 4096 / 512 * 8)) 8)
198
+data_4k_offset=$((data_4k_offset & 0x00fffffffffffe00))
199
+
200
+$QEMU_IO -c "discard 0 512" -c "discard 4k 1k" "$TEST_IMG" | _filter_qemu_io
201
+
202
+# Corrupt the image by saying the image header was not allocated
203
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
204
+rb_offset=$(peek_file_be "$TEST_IMG" $rt_offset 8)
205
+poke_file "$TEST_IMG" $rb_offset "\x00\x00"
206
+
207
+echo
208
+# The only leaks there can be are the old refcount structures that are
209
+# leaked during rebuilding, no need to clutter the output with them
210
+_check_test_img -r all | grep -v '^Repairing cluster.*refcount=1 reference=0'
211
+echo
212
+
213
+# Check whether the reftable was put where we expected
214
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
215
+if [[ $rt_offset -eq $data_4k_offset ]]; then
216
+ echo 'OK: Reftable is where we expect it'
217
+else
218
+ echo "ERROR: Reftable is at $rt_offset, but was expected at $data_4k_offset"
219
+fi
220
+
221
+echo
222
+echo '--- Rebuilding refcount structures on block devices ---'
223
+echo
224
+
225
+# A block device cannot really grow, at least not during qemu-img
226
+# check. As mentioned in the above cases, rebuilding the refcount
227
+# structure may lead to new refcount structures being written after
228
+# the end of the image, and in the past that happened even if there
229
+# was more than sufficient space in the image. Such post-EOF writes
230
+# will not work on block devices, so test that the new algorithm
231
+# avoids it.
232
+
233
+# If we have passwordless sudo and losetup, we can use those to create
234
+# a block device. Otherwise, we can resort to qemu's FUSE export to
235
+# create a file that isn't growable, which effectively tests the same
236
+# thing.
237
+
238
+_cleanup_test_img
239
+truncate -s $((64 * 1024 * 1024)) "$TEST_IMG"
240
+
241
+if $loopdev; then
242
+ export_mp=$(sudo -n losetup --show -f "$TEST_IMG")
243
+ export_mp_driver=host_device
244
+ sudo -n chmod go+rw "$export_mp"
245
+else
246
+ # Create non-growable FUSE export that is a bit like an empty
247
+ # block device
248
+ export_mp="$TEST_DIR/fuse-export"
249
+ export_mp_driver=file
250
+ touch "$export_mp"
251
+
252
+ $QSD \
253
+ --blockdev file,node-name=export-node,filename="$TEST_IMG" \
254
+ --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=off \
255
+ --pidfile "$TEST_DIR/qsd.pid" \
256
+ --daemonize
257
+fi
258
+
259
+# Now create a qcow2 image on the device -- unfortunately, qemu-img
260
+# create force-creates the file, so we have to resort to the
261
+# blockdev-create job.
262
+_launch_qemu \
263
+ --blockdev $export_mp_driver,node-name=file,filename="$export_mp"
264
+
265
+_send_qemu_cmd \
266
+ $QEMU_HANDLE \
267
+ '{ "execute": "qmp_capabilities" }' \
268
+ 'return'
269
+
270
+# Small cluster size again, so the image needs multiple refblocks
271
+_send_qemu_cmd \
272
+ $QEMU_HANDLE \
273
+ '{ "execute": "blockdev-create",
274
+ "arguments": {
275
+ "job-id": "create",
276
+ "options": {
277
+ "driver": "qcow2",
278
+ "file": "file",
279
+ "size": '$((64 * 1024 * 1024))',
280
+ "cluster-size": 512
281
+ } } }' \
282
+ '"concluded"'
283
+
284
+_send_qemu_cmd \
285
+ $QEMU_HANDLE \
286
+ '{ "execute": "job-dismiss", "arguments": { "id": "create" } }' \
287
+ 'return'
288
+
289
+_send_qemu_cmd \
290
+ $QEMU_HANDLE \
291
+ '{ "execute": "quit" }' \
292
+ 'return'
293
+
294
+wait=y _cleanup_qemu
295
+echo
296
+
297
+# Write some data
298
+$QEMU_IO -c 'write 0 64k' "$export_mp" | _filter_qemu_io
299
+
300
+# Corrupt the image by saying the image header was not allocated
301
+rt_offset=$(peek_file_be "$export_mp" 48 8)
302
+rb_offset=$(peek_file_be "$export_mp" $rt_offset 8)
303
+poke_file "$export_mp" $rb_offset "\x00\x00"
304
+
305
+# Repairing such a simple case should just work
306
+# (We used to put the reftable at the end of the image file, which can
307
+# never work for non-growable devices.)
308
+echo
309
+TEST_IMG="$export_mp" _check_test_img -r all \
310
+ | grep -v '^Repairing cluster.*refcount=1 reference=0'
311
+
312
+if $loopdev; then
313
+ sudo -n losetup -d "$export_mp"
314
+else
315
+ qsd_pid=$(cat "$TEST_DIR/qsd.pid")
316
+ kill -TERM "$qsd_pid"
317
+ # Wait for process to exit (cannot `wait` because the QSD is daemonized)
318
+ while [ -f "$TEST_DIR/qsd.pid" ]; do
319
+ true
320
+ done
321
+fi
322
+
323
# success, all done
324
echo '*** done'
325
rm -f $seq.full
326
diff --git a/tests/qemu-iotests/108.out b/tests/qemu-iotests/108.out
327
index XXXXXXX..XXXXXXX 100644
328
--- a/tests/qemu-iotests/108.out
329
+++ b/tests/qemu-iotests/108.out
330
@@ -XXX,XX +XXX,XX @@ The following inconsistencies were found and repaired:
331
0 leaked clusters
332
1 corruptions
333
334
+Double checking the fixed image now...
335
+No errors were found on the image.
336
+
337
+=== Check rebuilt reftable location ===
338
+
339
+--- Does the image size increase? ---
340
+
341
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
342
+wrote 65536/65536 bytes at offset 0
343
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
344
+
345
+ERROR cluster 0 refcount=0 reference=1
346
+Rebuilding refcount structure
347
+The following inconsistencies were found and repaired:
348
+
349
+ 0 leaked clusters
350
+ 1 corruptions
351
+
352
+Double checking the fixed image now...
353
+No errors were found on the image.
354
+
355
+OK: Image size did not change
356
+
357
+--- Will the reftable occupy a hole specifically left for it? ---
358
+
359
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=9437184
360
+wrote 8388608/8388608 bytes at offset 0
361
+8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
362
+discard 512/512 bytes at offset 0
363
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
364
+discard 1024/1024 bytes at offset 4096
365
+1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
366
+
367
+ERROR cluster 0 refcount=0 reference=1
368
+Rebuilding refcount structure
369
+The following inconsistencies were found and repaired:
370
+
371
+ 0 leaked clusters
372
+ 1 corruptions
373
+
374
+Double checking the fixed image now...
375
+No errors were found on the image.
376
+
377
+OK: Reftable is where we expect it
378
+
379
+--- Rebuilding refcount structures on block devices ---
380
+
381
+{ "execute": "qmp_capabilities" }
382
+{"return": {}}
383
+{ "execute": "blockdev-create",
384
+ "arguments": {
385
+ "job-id": "create",
386
+ "options": {
387
+ "driver": "IMGFMT",
388
+ "file": "file",
389
+ "size": 67108864,
390
+ "cluster-size": 512
391
+ } } }
392
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "create"}}
393
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "create"}}
394
+{"return": {}}
395
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "create"}}
396
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "create"}}
397
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "create"}}
398
+{ "execute": "job-dismiss", "arguments": { "id": "create" } }
399
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "create"}}
400
+{"return": {}}
401
+{ "execute": "quit" }
402
+{"return": {}}
403
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
404
+
405
+wrote 65536/65536 bytes at offset 0
406
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
407
+
408
+ERROR cluster 0 refcount=0 reference=1
409
+Rebuilding refcount structure
410
+The following inconsistencies were found and repaired:
411
+
412
+ 0 leaked clusters
413
+ 1 corruptions
414
+
415
Double checking the fixed image now...
416
No errors were found on the image.
417
*** done
418
--
419
2.35.1
diff view generated by jsdifflib
New patch
1
Instead of fprint()-ing error messages in rebuild_refcount_structure()
2
and its rebuild_refcounts_write_refblocks() helper, pass them through an
3
Error object to qcow2_check_refcounts() (which will then print it).
1
4
5
Suggested-by: Eric Blake <eblake@redhat.com>
6
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
7
Message-Id: <20220405134652.19278-4-hreitz@redhat.com>
8
Reviewed-by: Eric Blake <eblake@redhat.com>
9
---
10
block/qcow2-refcount.c | 33 +++++++++++++++++++--------------
11
1 file changed, 19 insertions(+), 14 deletions(-)
12
13
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
14
index XXXXXXX..XXXXXXX 100644
15
--- a/block/qcow2-refcount.c
16
+++ b/block/qcow2-refcount.c
17
@@ -XXX,XX +XXX,XX @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
18
static int rebuild_refcounts_write_refblocks(
19
BlockDriverState *bs, void **refcount_table, int64_t *nb_clusters,
20
int64_t first_cluster, int64_t end_cluster,
21
- uint64_t **on_disk_reftable_ptr, uint32_t *on_disk_reftable_entries_ptr
22
+ uint64_t **on_disk_reftable_ptr, uint32_t *on_disk_reftable_entries_ptr,
23
+ Error **errp
24
)
25
{
26
BDRVQcow2State *s = bs->opaque;
27
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcounts_write_refblocks(
28
nb_clusters,
29
&first_free_cluster);
30
if (refblock_offset < 0) {
31
- fprintf(stderr, "ERROR allocating refblock: %s\n",
32
- strerror(-refblock_offset));
33
+ error_setg_errno(errp, -refblock_offset,
34
+ "ERROR allocating refblock");
35
return refblock_offset;
36
}
37
38
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcounts_write_refblocks(
39
on_disk_reftable_entries *
40
REFTABLE_ENTRY_SIZE);
41
if (!on_disk_reftable) {
42
+ error_setg(errp, "ERROR allocating reftable memory");
43
return -ENOMEM;
44
}
45
46
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcounts_write_refblocks(
47
ret = qcow2_pre_write_overlap_check(bs, 0, refblock_offset,
48
s->cluster_size, false);
49
if (ret < 0) {
50
- fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
51
+ error_setg_errno(errp, -ret, "ERROR writing refblock");
52
return ret;
53
}
54
55
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcounts_write_refblocks(
56
ret = bdrv_pwrite(bs->file, refblock_offset, on_disk_refblock,
57
s->cluster_size);
58
if (ret < 0) {
59
- fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
60
+ error_setg_errno(errp, -ret, "ERROR writing refblock");
61
return ret;
62
}
63
64
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcounts_write_refblocks(
65
static int rebuild_refcount_structure(BlockDriverState *bs,
66
BdrvCheckResult *res,
67
void **refcount_table,
68
- int64_t *nb_clusters)
69
+ int64_t *nb_clusters,
70
+ Error **errp)
71
{
72
BDRVQcow2State *s = bs->opaque;
73
int64_t reftable_offset = -1;
74
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
75
rebuild_refcounts_write_refblocks(bs, refcount_table, nb_clusters,
76
0, *nb_clusters,
77
&on_disk_reftable,
78
- &on_disk_reftable_entries);
79
+ &on_disk_reftable_entries, errp);
80
if (reftable_size_changed < 0) {
81
res->check_errors++;
82
ret = reftable_size_changed;
83
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
84
refcount_table, nb_clusters,
85
&first_free_cluster);
86
if (reftable_offset < 0) {
87
- fprintf(stderr, "ERROR allocating reftable: %s\n",
88
- strerror(-reftable_offset));
89
+ error_setg_errno(errp, -reftable_offset,
90
+ "ERROR allocating reftable");
91
res->check_errors++;
92
ret = reftable_offset;
93
goto fail;
94
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
95
reftable_start_cluster,
96
reftable_end_cluster,
97
&on_disk_reftable,
98
- &on_disk_reftable_entries);
99
+ &on_disk_reftable_entries, errp);
100
if (reftable_size_changed < 0) {
101
res->check_errors++;
102
ret = reftable_size_changed;
103
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
104
ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset, reftable_length,
105
false);
106
if (ret < 0) {
107
- fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
108
+ error_setg_errno(errp, -ret, "ERROR writing reftable");
109
goto fail;
110
}
111
112
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
113
ret = bdrv_pwrite(bs->file, reftable_offset, on_disk_reftable,
114
reftable_length);
115
if (ret < 0) {
116
- fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
117
+ error_setg_errno(errp, -ret, "ERROR writing reftable");
118
goto fail;
119
}
120
121
@@ -XXX,XX +XXX,XX @@ static int rebuild_refcount_structure(BlockDriverState *bs,
122
&reftable_offset_and_clusters,
123
sizeof(reftable_offset_and_clusters));
124
if (ret < 0) {
125
- fprintf(stderr, "ERROR setting reftable: %s\n", strerror(-ret));
126
+ error_setg_errno(errp, -ret, "ERROR setting reftable");
127
goto fail;
128
}
129
130
@@ -XXX,XX +XXX,XX @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
131
if (rebuild && (fix & BDRV_FIX_ERRORS)) {
132
BdrvCheckResult old_res = *res;
133
int fresh_leaks = 0;
134
+ Error *local_err = NULL;
135
136
fprintf(stderr, "Rebuilding refcount structure\n");
137
ret = rebuild_refcount_structure(bs, res, &refcount_table,
138
- &nb_clusters);
139
+ &nb_clusters, &local_err);
140
if (ret < 0) {
141
+ error_report_err(local_err);
142
goto fail;
143
}
144
145
--
146
2.35.1
diff view generated by jsdifflib