* [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements
@ 2026-08-19 0:07 Andrey Zhadchenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp Andrey Zhadchenko
` (8 more replies)
0 siblings, 9 replies; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
This patchset is mostly about improving dm-qcow2 lseek speed. L2 md
pages can now be scanned in batch with simple for() instead of heavy
parse_metadata(). Seek qios for backing images are now called with
a range bigger than 1 cluster if possible.
Also fix a few clamping bugs and use correct comparison for seek
modes.
v2:
- patch #4 "fix seek skip to the next L2 table"
- changed l2 calculation to a simplier round_up
- allow l2 skip if the backing data does not exist (e.g. lower is
shorter)
- patch #6 "seek unallocated L2 entries in one pass within md"
- add forgotten to_sector() when skipping unallocated entries
- NEW patch #7 adressing patch #5 edge case when we do to_sector(end),
where 'end' may be not sector aligned, thus causing endless loop.
Andrey Zhadchenko (7):
drivers/md/dm-qcow2: fix lower delta seek window clamp
drivers/md/dm-qcow2: keep seek parse window within limit
drivers/md/dm-qcow2: fix seek constant comparison
drivers/md/dm-qcow2: fix seek skip to the next L2 table
drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry
drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md
drivers/md/dm-qcow2: forbid unaligned virtual size for images
drivers/md/dm-qcow2-map.c | 117 ++++++++++++++++++++++++-----------
drivers/md/dm-qcow2-target.c | 1 +
2 files changed, 82 insertions(+), 36 deletions(-)
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 2/7] drivers/md/dm-qcow2: keep seek parse window within limit Andrey Zhadchenko
` (7 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
size may be smaller than old_qio->bi_iter.bi_size: we calculate it
beforehand with calc_front_qio_bytes().
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index db21efb45e17a..542c7949321e5 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4437,12 +4437,11 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
{
struct qio *new_qio;
- loff_t start, old_end;
+ loff_t start;
start = to_bytes(old_qio->bi_iter.bi_sector);
- old_end = start + old_qio->bi_iter.bi_size;
- if (old_end > old_qio->qcow2->lower->hdr.size)
+ if (start + size > old_qio->qcow2->lower->hdr.size)
size = old_qio->qcow2->lower->hdr.size - start;
new_qio = alloc_seek_qio(old_qio->qcow2->lower, old_qio, start + size);
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 2/7] drivers/md/dm-qcow2: keep seek parse window within limit
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 3/7] drivers/md/dm-qcow2: fix seek constant comparison Andrey Zhadchenko
` (6 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
seek_qio_next_clu() always sets the window size to the whole
cluster, so the window may cross the scan limit. The top level qio
then parses metadata past the virtual disk end if the disk size is
not cluster aligned, and SEEK_HOLE may report "sector + size" past
the limit.
This is especially painful for lower delta seeks, since at top
level this is luckily alleviated by blkdev_llseek_wrapper().
Add seek_qio_set_sector(), which sets bi_sector and also clamps
bi_size if needed.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 542c7949321e5..342e4af688509 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4352,6 +4352,16 @@ struct qio_data_llseek_hole {
#define SEEK_QIO_DATA(qio) ((struct qio_data_llseek_hole *)qio->data)
+static void seek_qio_set_sector(struct qio *qio, sector_t bi_sector)
+{
+ loff_t pos = to_bytes(bi_sector);
+ loff_t end = round_up(pos + 1, qio->qcow2->clu_size);
+
+ qio->bi_iter.bi_sector = bi_sector;
+ end = min_t(loff_t, end, SEEK_QIO_DATA(qio)->lim);
+ qio->bi_iter.bi_size = end > pos ? end - pos : 0;
+}
+
static struct qio *alloc_seek_qio(struct qcow2 *qcow2, struct qio *parent, loff_t new_lim)
{
struct qio_data_llseek_hole *data;
@@ -4376,12 +4386,7 @@ static struct qio *alloc_seek_qio(struct qcow2 *qcow2, struct qio *parent, loff_
if (parent) {
data->higher = parent;
- qio->bi_iter.bi_sector = parent->bi_iter.bi_sector;
-
- if (to_bytes(qio->bi_iter.bi_sector) + parent->bi_iter.bi_size > new_lim)
- qio->bi_iter.bi_size = new_lim - to_bytes(qio->bi_iter.bi_sector);
- else
- qio->bi_iter.bi_size = parent->bi_iter.bi_size;
+ seek_qio_set_sector(qio, parent->bi_iter.bi_sector);
}
return qio;
@@ -4422,16 +4427,18 @@ static inline sector_t get_next_clu(struct qio *qio)
static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
{
+ sector_t bi_sector;
+
/*
* Whole L2 table is unmapped - skip to next l2 table,
* but only if there is no backing image
*/
if (map && !(map->level & L2_LEVEL) && !qio->qcow2->lower)
- qio->bi_iter.bi_sector = get_next_l2(qio);
+ bi_sector = get_next_l2(qio);
else
- qio->bi_iter.bi_sector = get_next_clu(qio);
+ bi_sector = get_next_clu(qio);
- qio->bi_iter.bi_size = qio->qcow2->clu_size;
+ seek_qio_set_sector(qio, bi_sector);
}
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
@@ -4567,9 +4574,7 @@ loff_t qcow2_llseek_hole(struct dm_target *ti, loff_t offset, int whence)
if (!qio)
return -ENOMEM;
- qio->bi_iter.bi_sector = to_sector(offset);
- qio->bi_iter.bi_size = qcow2->clu_size -
- to_bytes(qio->bi_iter.bi_sector) % qcow2->clu_size;
+ seek_qio_set_sector(qio, to_sector(offset));
ret = qcow2_llseek_hole_qio(qio, whence, &result);
/* In case of error remap ENXIO as it have special meaning for llseek */
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 3/7] drivers/md/dm-qcow2: fix seek constant comparison
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp Andrey Zhadchenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 2/7] drivers/md/dm-qcow2: keep seek parse window within limit Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 4/7] drivers/md/dm-qcow2: fix seek skip to the next L2 table Andrey Zhadchenko
` (5 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
This is just an enum, not a mask.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 342e4af688509..d6d15b04215c6 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4520,7 +4520,7 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
}
}
- if (whence & SEEK_HOLE) {
+ if (whence == SEEK_HOLE) {
if (arg.zeroes || arg.unmapped) {
*result = to_bytes(qio->bi_iter.bi_sector);
ret = 0;
@@ -4536,7 +4536,7 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
}
}
- if (whence & SEEK_DATA) {
+ if (whence == SEEK_DATA) {
if (!arg.zeroes && !arg.unmapped) {
*result = to_bytes(qio->bi_iter.bi_sector);
ret = 0;
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 4/7] drivers/md/dm-qcow2: fix seek skip to the next L2 table
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (2 preceding siblings ...)
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 3/7] drivers/md/dm-qcow2: fix seek constant comparison Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 5/7] drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry Andrey Zhadchenko
` (4 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
get_next_l2() adds the number of remaining clusters to the current
sector, so for a position which is not cluster aligned (e.g. the
llseek offset itself) the result overshoots the L2 table boundary by
the in-cluster offset.
Compute the boundary by simple round_up to the size covered by a
single L2 table.
Also allow skipping l2 if we have lower delta but range does not
exist there (e.g. past the delta end).
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
v2:
- changed l2 calculation to a simplier round_up()
- expanded l2 skip for non-existing backing. Updated commit
message
drivers/md/dm-qcow2-map.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index d6d15b04215c6..43188ceebc9af 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4405,12 +4405,9 @@ static struct qio *free_seek_qio_ret_higher(struct qio *qio)
static inline sector_t get_next_l2(struct qio *qio)
{
struct qcow2 *qcow2 = qio->qcow2;
- loff_t start, add;
+ u64 l2_cover = to_sector((u64)qcow2->clu_size * qcow2->l2_entries);
- start = to_bytes(qio->bi_iter.bi_sector);
- add = qcow2->l2_entries - (start / qcow2->clu_size) % qcow2->l2_entries;
-
- return qio->bi_iter.bi_sector + (qcow2->clu_size / to_bytes(1)) * add;
+ return round_up(qio->bi_iter.bi_sector + 1, l2_cover);
}
static inline sector_t get_next_clu(struct qio *qio)
@@ -4431,9 +4428,10 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
/*
* Whole L2 table is unmapped - skip to next l2 table,
- * but only if there is no backing image
+ * but only if there is no backing data
*/
- if (map && !(map->level & L2_LEVEL) && !qio->qcow2->lower)
+ if (map && !(map->level & L2_LEVEL) &&
+ !maybe_mapped_in_lower_delta(qio->qcow2, qio))
bi_sector = get_next_l2(qio);
else
bi_sector = get_next_clu(qio);
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 5/7] drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (3 preceding siblings ...)
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 4/7] drivers/md/dm-qcow2: fix seek skip to the next L2 table Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 6/7] drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md Andrey Zhadchenko
` (3 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
When an L1 entry is absent and there is a backing image, the seek
code descends into the lower delta one cluster at a time.
Change advance_and_spawn_lower_seek_qio() to take limit instead of
a size. Pass bigger range if L1 entry is empty.
On a 16G image (128K clusters, extended L2) with no L1 entries over a
backing image with data at 15G, warm-cache SEEK_DATA improves from
40.2 ms to 0.01 ms.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 43188ceebc9af..879d2f1827006 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4439,26 +4439,19 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
seek_qio_set_sector(qio, bi_sector);
}
-static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
+static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, loff_t end)
{
+ struct qcow2 *lower = old_qio->qcow2->lower;
struct qio *new_qio;
- loff_t start;
- start = to_bytes(old_qio->bi_iter.bi_sector);
+ if (end > lower->hdr.size)
+ end = lower->hdr.size;
- if (start + size > old_qio->qcow2->lower->hdr.size)
- size = old_qio->qcow2->lower->hdr.size - start;
-
- new_qio = alloc_seek_qio(old_qio->qcow2->lower, old_qio, start + size);
+ new_qio = alloc_seek_qio(lower, old_qio, end);
if (!new_qio)
return NULL;
- if (old_qio->bi_iter.bi_size == size) {
- seek_qio_next_clu(old_qio, NULL);
- } else {
- old_qio->bi_iter.bi_sector += to_sector(size);
- old_qio->bi_iter.bi_size -= size;
- }
+ seek_qio_set_sector(old_qio, to_sector(end));
return new_qio;
}
@@ -4506,8 +4499,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
*/
if (to_bytes(qio->bi_iter.bi_sector) < qio->qcow2->lower->hdr.size) {
struct qio *new_qio;
+ loff_t end;
+
+ if (!(map.level & L2_LEVEL))
+ end = min_t(loff_t,
+ to_bytes(get_next_l2(qio)),
+ SEEK_QIO_DATA(qio)->lim);
+ else
+ end = to_bytes(qio->bi_iter.bi_sector) + size;
- new_qio = advance_and_spawn_lower_seek_qio(qio, size);
+ new_qio = advance_and_spawn_lower_seek_qio(qio, end);
if (!new_qio) {
ret = -ENOMEM;
break;
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 6/7] drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (4 preceding siblings ...)
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 5/7] drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 7/7] drivers/md/dm-qcow2: forbid unaligned virtual size for images Andrey Zhadchenko
` (2 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
When a cluster is unallocated but its L2 table exists, the seek code
walks it one cluster at a time: SEEK_DATA re-parses metadata for
every cluster of the range, and with a backing image each cluster
additionally costs a qio and completion allocation for the lower
delta descent.
If the unmapped range reaches the cluster border, prolong it over the
following unallocated L2 entries: they are cached in the very md page
the current entry was parsed from, so this is just scanning for
non-zero words. Then the lower delta is scanned in one pass over the
whole range, and SEEK_DATA without a backing image skips it in one go.
Entries with any subcluster or "reads as zeroes" bits set stop the
scan and spin parse_metadata() machinery.
On a 16G image (128K clusters, extended L2) with all L2 tables
allocated but no clusters mapped over a backing image with data at
15G, warm-cache SEEK_DATA improves from 44.8 ms to 0.4 ms.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
v2:
- added missing to_sector() when skipping unallocated entries:
new 'next' variable now contains next sector.
drivers/md/dm-qcow2-map.c | 46 +++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 879d2f1827006..eaaf0a521d7bd 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4439,6 +4439,34 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
seek_qio_set_sector(qio, bi_sector);
}
+/*
+ * Manually scan L2 metadata pages until we hit something interesting.
+ * We do not take md_pages_lock here because we have no promises about
+ * concurrent writes anyway.
+ */
+static loff_t seek_extend_unmapped_end(struct qio *qio, struct qcow2_map *map,
+ loff_t end)
+{
+ struct qcow2 *qcow2 = qio->qcow2;
+ loff_t lim = SEEK_QIO_DATA(qio)->lim;
+ u32 step = 1 + qcow2->ext_l2;
+ u32 index = map->l2.index_in_page + step;
+ u64 *entries;
+
+ entries = kmap_local_page(map->l2.md->page);
+ for (; index + step <= PAGE_SIZE / sizeof(u64) && end < lim;
+ index += step) {
+ /* Zero test does not need be64_to_cpu() */
+ if (READ_ONCE(entries[index]) ||
+ (qcow2->ext_l2 && READ_ONCE(entries[index + 1])))
+ break;
+ end += qcow2->clu_size;
+ }
+ kunmap_local(entries);
+
+ return min_t(loff_t, end, lim);
+}
+
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, loff_t end)
{
struct qcow2 *lower = old_qio->qcow2->lower;
@@ -4501,12 +4529,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
struct qio *new_qio;
loff_t end;
- if (!(map.level & L2_LEVEL))
+ if (!(map.level & L2_LEVEL)) {
end = min_t(loff_t,
to_bytes(get_next_l2(qio)),
SEEK_QIO_DATA(qio)->lim);
- else
+ } else {
end = to_bytes(qio->bi_iter.bi_sector) + size;
+ /* extend operates on cluster granularity */
+ if (!CLU_OFF(qio->qcow2, end))
+ end = seek_extend_unmapped_end(qio, &map, end);
+ }
new_qio = advance_and_spawn_lower_seek_qio(qio, end);
if (!new_qio) {
@@ -4550,6 +4582,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
qio->bi_iter.bi_sector += to_sector(size);
qio->bi_iter.bi_size -= size;
goto calc_subclu;
+ } else if (arg.unmapped && (map.level & L2_LEVEL)) {
+ loff_t end = to_bytes(qio->bi_iter.bi_sector) + size;
+ sector_t next;
+
+ /* Skip the following unallocated entries in one go */
+ if (!CLU_OFF(qio->qcow2, end)) {
+ next = to_sector(seek_extend_unmapped_end(qio, &map, end));
+ seek_qio_set_sector(qio, next);
+ continue;
+ }
}
}
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH VZ10 v2 7/7] drivers/md/dm-qcow2: forbid unaligned virtual size for images
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (5 preceding siblings ...)
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 6/7] drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md Andrey Zhadchenko
@ 2026-08-19 0:07 ` Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 8:54 ` [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Vasileios Almpanis
2026-08-24 14:38 ` Pavel Tikhomirov
8 siblings, 1 reply; 17+ messages in thread
From: Andrey Zhadchenko @ 2026-08-19 0:07 UTC (permalink / raw)
There are a few places where we do to_sector() conversions which
may cause us bugs when the image size is not aligned. For example,
advance_and_spawn_lower_seek_qio() advances old_qio to
to_sector(end). If end is not aliged, it may round down to the
same sector already set in old_qio. Thus we will endlessly loop.
It is OK to forbid such images since QEMU rounds up virtual size
on qcow2 creation and rejects unaligned resizes.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-target.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
index 3f65897ce9da2..6ad510772410c 100644
--- a/drivers/md/dm-qcow2-target.c
+++ b/drivers/md/dm-qcow2-target.c
@@ -541,6 +541,7 @@ static int qcow2_check_convert_hdr(struct dm_target *ti,
clu_size = 1 << hdr->cluster_bits;
if (hdr->size < min_len || hdr->size > max_len ||
+ !IS_ALIGNED(hdr->size, SECTOR_SIZE) ||
/* Note, we do not extend L1 table: */
(u64)hdr->l1_size * clu_size / sizeof(u64) * clu_size < min_len)
return -EBADSLT;
--
2.43.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (6 preceding siblings ...)
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 7/7] drivers/md/dm-qcow2: forbid unaligned virtual size for images Andrey Zhadchenko
@ 2026-08-19 8:54 ` Vasileios Almpanis
2026-08-24 14:38 ` Pavel Tikhomirov
8 siblings, 0 replies; 17+ messages in thread
From: Vasileios Almpanis @ 2026-08-19 8:54 UTC (permalink / raw)
> This patchset is mostly about improving dm-qcow2 lseek speed. L2 md
> pages can now be scanned in batch with simple for() instead of heavy
> parse_metadata(). Seek qios for backing images are now called with
> a range bigger than 1 cluster if possible.
> Also fix a few clamping bugs and use correct comparison for seek
> modes.
LGTM
--
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
` (7 preceding siblings ...)
2026-08-19 8:54 ` [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Vasileios Almpanis
@ 2026-08-24 14:38 ` Pavel Tikhomirov
8 siblings, 0 replies; 17+ messages in thread
From: Pavel Tikhomirov @ 2026-08-24 14:38 UTC (permalink / raw)
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
On 8/19/26 02:07, Andrey Zhadchenko wrote:
> This patchset is mostly about improving dm-qcow2 lseek speed. L2 md
> pages can now be scanned in batch with simple for() instead of heavy
> parse_metadata(). Seek qios for backing images are now called with
> a range bigger than 1 cluster if possible.
> Also fix a few clamping bugs and use correct comparison for seek
> modes.
>
> v2:
> - patch #4 "fix seek skip to the next L2 table"
> - changed l2 calculation to a simplier round_up
> - allow l2 skip if the backing data does not exist (e.g. lower is
> shorter)
> - patch #6 "seek unallocated L2 entries in one pass within md"
> - add forgotten to_sector() when skipping unallocated entries
> - NEW patch #7 adressing patch #5 edge case when we do to_sector(end),
> where 'end' may be not sector aligned, thus causing endless loop.
>
> Andrey Zhadchenko (7):
> drivers/md/dm-qcow2: fix lower delta seek window clamp
> drivers/md/dm-qcow2: keep seek parse window within limit
> drivers/md/dm-qcow2: fix seek constant comparison
> drivers/md/dm-qcow2: fix seek skip to the next L2 table
> drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry
> drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md
> drivers/md/dm-qcow2: forbid unaligned virtual size for images
>
> drivers/md/dm-qcow2-map.c | 117 ++++++++++++++++++++++++-----------
> drivers/md/dm-qcow2-target.c | 1 +
> 2 files changed, 82 insertions(+), 36 deletions(-)
>
--
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: fix lower delta seek window clamp
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit fde033dabdab8ffa4e3c21fc692029c165cd2a4b
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:39 2026 +0300
dm-qcow2: fix lower delta seek window clamp
size may be smaller than old_qio->bi_iter.bi_size: we calculate it
beforehand with calc_front_qio_bytes().
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index db21efb45e17a..542c7949321e5 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4437,12 +4437,11 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
{
struct qio *new_qio;
- loff_t start, old_end;
+ loff_t start;
start = to_bytes(old_qio->bi_iter.bi_sector);
- old_end = start + old_qio->bi_iter.bi_size;
- if (old_end > old_qio->qcow2->lower->hdr.size)
+ if (start + size > old_qio->qcow2->lower->hdr.size)
size = old_qio->qcow2->lower->hdr.size - start;
new_qio = alloc_seek_qio(old_qio->qcow2->lower, old_qio, start + size);
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: keep seek parse window within limit
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 2/7] drivers/md/dm-qcow2: keep seek parse window within limit Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit 1c541ce04d8b6336ee269b95e52ad8bf02cc954d
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:40 2026 +0300
dm-qcow2: keep seek parse window within limit
seek_qio_next_clu() always sets the window size to the whole
cluster, so the window may cross the scan limit. The top level qio
then parses metadata past the virtual disk end if the disk size is
not cluster aligned, and SEEK_HOLE may report "sector + size" past
the limit.
This is especially painful for lower delta seeks, since at top
level this is luckily alleviated by blkdev_llseek_wrapper().
Add seek_qio_set_sector(), which sets bi_sector and also clamps
bi_size if needed.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 542c7949321e5..342e4af688509 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4352,6 +4352,16 @@ struct qio_data_llseek_hole {
#define SEEK_QIO_DATA(qio) ((struct qio_data_llseek_hole *)qio->data)
+static void seek_qio_set_sector(struct qio *qio, sector_t bi_sector)
+{
+ loff_t pos = to_bytes(bi_sector);
+ loff_t end = round_up(pos + 1, qio->qcow2->clu_size);
+
+ qio->bi_iter.bi_sector = bi_sector;
+ end = min_t(loff_t, end, SEEK_QIO_DATA(qio)->lim);
+ qio->bi_iter.bi_size = end > pos ? end - pos : 0;
+}
+
static struct qio *alloc_seek_qio(struct qcow2 *qcow2, struct qio *parent, loff_t new_lim)
{
struct qio_data_llseek_hole *data;
@@ -4376,12 +4386,7 @@ static struct qio *alloc_seek_qio(struct qcow2 *qcow2, struct qio *parent, loff_
if (parent) {
data->higher = parent;
- qio->bi_iter.bi_sector = parent->bi_iter.bi_sector;
-
- if (to_bytes(qio->bi_iter.bi_sector) + parent->bi_iter.bi_size > new_lim)
- qio->bi_iter.bi_size = new_lim - to_bytes(qio->bi_iter.bi_sector);
- else
- qio->bi_iter.bi_size = parent->bi_iter.bi_size;
+ seek_qio_set_sector(qio, parent->bi_iter.bi_sector);
}
return qio;
@@ -4422,16 +4427,18 @@ static inline sector_t get_next_clu(struct qio *qio)
static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
{
+ sector_t bi_sector;
+
/*
* Whole L2 table is unmapped - skip to next l2 table,
* but only if there is no backing image
*/
if (map && !(map->level & L2_LEVEL) && !qio->qcow2->lower)
- qio->bi_iter.bi_sector = get_next_l2(qio);
+ bi_sector = get_next_l2(qio);
else
- qio->bi_iter.bi_sector = get_next_clu(qio);
+ bi_sector = get_next_clu(qio);
- qio->bi_iter.bi_size = qio->qcow2->clu_size;
+ seek_qio_set_sector(qio, bi_sector);
}
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
@@ -4567,9 +4574,7 @@ loff_t qcow2_llseek_hole(struct dm_target *ti, loff_t offset, int whence)
if (!qio)
return -ENOMEM;
- qio->bi_iter.bi_sector = to_sector(offset);
- qio->bi_iter.bi_size = qcow2->clu_size -
- to_bytes(qio->bi_iter.bi_sector) % qcow2->clu_size;
+ seek_qio_set_sector(qio, to_sector(offset));
ret = qcow2_llseek_hole_qio(qio, whence, &result);
/* In case of error remap ENXIO as it have special meaning for llseek */
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: fix seek constant comparison
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 3/7] drivers/md/dm-qcow2: fix seek constant comparison Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit 55c027bb79909b0c9a0befdb2ad654598bd997e9
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:41 2026 +0300
dm-qcow2: fix seek constant comparison
This is just an enum, not a mask.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 342e4af688509..d6d15b04215c6 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4520,7 +4520,7 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
}
}
- if (whence & SEEK_HOLE) {
+ if (whence == SEEK_HOLE) {
if (arg.zeroes || arg.unmapped) {
*result = to_bytes(qio->bi_iter.bi_sector);
ret = 0;
@@ -4536,7 +4536,7 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
}
}
- if (whence & SEEK_DATA) {
+ if (whence == SEEK_DATA) {
if (!arg.zeroes && !arg.unmapped) {
*result = to_bytes(qio->bi_iter.bi_sector);
ret = 0;
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: fix seek skip to the next L2 table
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 4/7] drivers/md/dm-qcow2: fix seek skip to the next L2 table Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit fef1e3ed3933512286b531bf4d01bc3d4b721621
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:42 2026 +0300
dm-qcow2: fix seek skip to the next L2 table
get_next_l2() adds the number of remaining clusters to the current
sector, so for a position which is not cluster aligned (e.g. the
llseek offset itself) the result overshoots the L2 table boundary by
the in-cluster offset.
Compute the boundary by simple round_up to the size covered by a
single L2 table.
Also allow skipping l2 if we have lower delta but range does not
exist there (e.g. past the delta end).
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index d6d15b04215c6..43188ceebc9af 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4405,12 +4405,9 @@ static struct qio *free_seek_qio_ret_higher(struct qio *qio)
static inline sector_t get_next_l2(struct qio *qio)
{
struct qcow2 *qcow2 = qio->qcow2;
- loff_t start, add;
+ u64 l2_cover = to_sector((u64)qcow2->clu_size * qcow2->l2_entries);
- start = to_bytes(qio->bi_iter.bi_sector);
- add = qcow2->l2_entries - (start / qcow2->clu_size) % qcow2->l2_entries;
-
- return qio->bi_iter.bi_sector + (qcow2->clu_size / to_bytes(1)) * add;
+ return round_up(qio->bi_iter.bi_sector + 1, l2_cover);
}
static inline sector_t get_next_clu(struct qio *qio)
@@ -4431,9 +4428,10 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
/*
* Whole L2 table is unmapped - skip to next l2 table,
- * but only if there is no backing image
+ * but only if there is no backing data
*/
- if (map && !(map->level & L2_LEVEL) && !qio->qcow2->lower)
+ if (map && !(map->level & L2_LEVEL) &&
+ !maybe_mapped_in_lower_delta(qio->qcow2, qio))
bi_sector = get_next_l2(qio);
else
bi_sector = get_next_clu(qio);
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: scan lower delta in one pass over empty L1 entry
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 5/7] drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit 7af917e3954e3085a2c0aed3d3d6cf9db7797cf8
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:43 2026 +0300
dm-qcow2: scan lower delta in one pass over empty L1 entry
When an L1 entry is absent and there is a backing image, the seek
code descends into the lower delta one cluster at a time.
Change advance_and_spawn_lower_seek_qio() to take limit instead of
a size. Pass bigger range if L1 entry is empty.
On a 16G image (128K clusters, extended L2) with no L1 entries over a
backing image with data at 15G, warm-cache SEEK_DATA improves from
40.2 ms to 0.01 ms.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 43188ceebc9af..879d2f1827006 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4439,26 +4439,19 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
seek_qio_set_sector(qio, bi_sector);
}
-static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, u32 size)
+static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, loff_t end)
{
+ struct qcow2 *lower = old_qio->qcow2->lower;
struct qio *new_qio;
- loff_t start;
- start = to_bytes(old_qio->bi_iter.bi_sector);
+ if (end > lower->hdr.size)
+ end = lower->hdr.size;
- if (start + size > old_qio->qcow2->lower->hdr.size)
- size = old_qio->qcow2->lower->hdr.size - start;
-
- new_qio = alloc_seek_qio(old_qio->qcow2->lower, old_qio, start + size);
+ new_qio = alloc_seek_qio(lower, old_qio, end);
if (!new_qio)
return NULL;
- if (old_qio->bi_iter.bi_size == size) {
- seek_qio_next_clu(old_qio, NULL);
- } else {
- old_qio->bi_iter.bi_sector += to_sector(size);
- old_qio->bi_iter.bi_size -= size;
- }
+ seek_qio_set_sector(old_qio, to_sector(end));
return new_qio;
}
@@ -4506,8 +4499,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
*/
if (to_bytes(qio->bi_iter.bi_sector) < qio->qcow2->lower->hdr.size) {
struct qio *new_qio;
+ loff_t end;
+
+ if (!(map.level & L2_LEVEL))
+ end = min_t(loff_t,
+ to_bytes(get_next_l2(qio)),
+ SEEK_QIO_DATA(qio)->lim);
+ else
+ end = to_bytes(qio->bi_iter.bi_sector) + size;
- new_qio = advance_and_spawn_lower_seek_qio(qio, size);
+ new_qio = advance_and_spawn_lower_seek_qio(qio, end);
if (!new_qio) {
ret = -ENOMEM;
break;
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: seek unallocated L2 entries in one pass within md
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 6/7] drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit 056b0452a90b769a5445d978bc7dde0536ae3b7a
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:44 2026 +0300
dm-qcow2: seek unallocated L2 entries in one pass within md
When a cluster is unallocated but its L2 table exists, the seek code
walks it one cluster at a time: SEEK_DATA re-parses metadata for
every cluster of the range, and with a backing image each cluster
additionally costs a qio and completion allocation for the lower
delta descent.
If the unmapped range reaches the cluster border, prolong it over the
following unallocated L2 entries: they are cached in the very md page
the current entry was parsed from, so this is just scanning for
non-zero words. Then the lower delta is scanned in one pass over the
whole range, and SEEK_DATA without a backing image skips it in one go.
Entries with any subcluster or "reads as zeroes" bits set stop the
scan and spin parse_metadata() machinery.
On a 16G image (128K clusters, extended L2) with all L2 tables
allocated but no clusters mapped over a backing image with data at
15G, warm-cache SEEK_DATA improves from 44.8 ms to 0.4 ms.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 879d2f1827006..eaaf0a521d7bd 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -4439,6 +4439,34 @@ static inline void seek_qio_next_clu(struct qio *qio, struct qcow2_map *map)
seek_qio_set_sector(qio, bi_sector);
}
+/*
+ * Manually scan L2 metadata pages until we hit something interesting.
+ * We do not take md_pages_lock here because we have no promises about
+ * concurrent writes anyway.
+ */
+static loff_t seek_extend_unmapped_end(struct qio *qio, struct qcow2_map *map,
+ loff_t end)
+{
+ struct qcow2 *qcow2 = qio->qcow2;
+ loff_t lim = SEEK_QIO_DATA(qio)->lim;
+ u32 step = 1 + qcow2->ext_l2;
+ u32 index = map->l2.index_in_page + step;
+ u64 *entries;
+
+ entries = kmap_local_page(map->l2.md->page);
+ for (; index + step <= PAGE_SIZE / sizeof(u64) && end < lim;
+ index += step) {
+ /* Zero test does not need be64_to_cpu() */
+ if (READ_ONCE(entries[index]) ||
+ (qcow2->ext_l2 && READ_ONCE(entries[index + 1])))
+ break;
+ end += qcow2->clu_size;
+ }
+ kunmap_local(entries);
+
+ return min_t(loff_t, end, lim);
+}
+
static struct qio *advance_and_spawn_lower_seek_qio(struct qio *old_qio, loff_t end)
{
struct qcow2 *lower = old_qio->qcow2->lower;
@@ -4501,12 +4529,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
struct qio *new_qio;
loff_t end;
- if (!(map.level & L2_LEVEL))
+ if (!(map.level & L2_LEVEL)) {
end = min_t(loff_t,
to_bytes(get_next_l2(qio)),
SEEK_QIO_DATA(qio)->lim);
- else
+ } else {
end = to_bytes(qio->bi_iter.bi_sector) + size;
+ /* extend operates on cluster granularity */
+ if (!CLU_OFF(qio->qcow2, end))
+ end = seek_extend_unmapped_end(qio, &map, end);
+ }
new_qio = advance_and_spawn_lower_seek_qio(qio, end);
if (!new_qio) {
@@ -4550,6 +4582,16 @@ static int qcow2_llseek_hole_qio(struct qio *qio, int whence, loff_t *result)
qio->bi_iter.bi_sector += to_sector(size);
qio->bi_iter.bi_size -= size;
goto calc_subclu;
+ } else if (arg.unmapped && (map.level & L2_LEVEL)) {
+ loff_t end = to_bytes(qio->bi_iter.bi_sector) + size;
+ sector_t next;
+
+ /* Skip the following unallocated entries in one go */
+ if (!CLU_OFF(qio->qcow2, end)) {
+ next = to_sector(seek_extend_unmapped_end(qio, &map, end));
+ seek_qio_set_sector(qio, next);
+ continue;
+ }
}
}
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: forbid unaligned virtual size for images
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 7/7] drivers/md/dm-qcow2: forbid unaligned virtual size for images Andrey Zhadchenko
@ 2026-08-25 10:41 ` Konstantin Khorenko
0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Khorenko @ 2026-08-25 10:41 UTC (permalink / raw)
The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit ad515b7f0e026dbec6cb947562e21f7f014543c7
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date: Wed Aug 19 03:07:45 2026 +0300
dm-qcow2: forbid unaligned virtual size for images
There are a few places where we do to_sector() conversions which
may cause us bugs when the image size is not aligned. For example,
advance_and_spawn_lower_seek_qio() advances old_qio to
to_sector(end). If end is not aliged, it may round down to the
same sector already set in old_qio. Thus we will endlessly loop.
It is OK to forbid such images since QEMU rounds up virtual size
on qcow2 creation and rejects unaligned resizes.
https://virtuozzo.atlassian.net/browse/VSTOR-139407
Feature: dm-qcow2: block device over QCOW2 files driver
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
drivers/md/dm-qcow2-target.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
index de1fb628735b7..2877de6f11c12 100644
--- a/drivers/md/dm-qcow2-target.c
+++ b/drivers/md/dm-qcow2-target.c
@@ -541,6 +541,7 @@ static int qcow2_check_convert_hdr(struct dm_target *ti,
clu_size = 1 << hdr->cluster_bits;
if (hdr->size < min_len || hdr->size > max_len ||
+ !IS_ALIGNED(hdr->size, SECTOR_SIZE) ||
/* Note, we do not extend L1 table: */
(u64)hdr->l1_size * clu_size / sizeof(u64) * clu_size < min_len)
return -EBADSLT;
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-08-25 10:41 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-19 0:07 [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Andrey Zhadchenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 1/7] drivers/md/dm-qcow2: fix lower delta seek window clamp Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 2/7] drivers/md/dm-qcow2: keep seek parse window within limit Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 3/7] drivers/md/dm-qcow2: fix seek constant comparison Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 4/7] drivers/md/dm-qcow2: fix seek skip to the next L2 table Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 5/7] drivers/md/dm-qcow2: scan lower delta in one pass over empty L1 entry Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 6/7] drivers/md/dm-qcow2: seek unallocated L2 entries in one pass within md Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 0:07 ` [Devel] [PATCH VZ10 v2 7/7] drivers/md/dm-qcow2: forbid unaligned virtual size for images Andrey Zhadchenko
2026-08-25 10:41 ` [Devel] [PATCH RHEL10 COMMIT] dm-qcow2: " Konstantin Khorenko
2026-08-19 8:54 ` [Devel] [PATCH VZ10 v2 0/7] dm-qcow: lseek improvements Vasileios Almpanis
2026-08-24 14:38 ` Pavel Tikhomirov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox