When the `read-zeroes` is set, reads produce zeroes, and block status return BDRV_BLOCK_ZERO, emulating a sparse image.
If we don't set `read-zeros` we report BDRV_BLOCK_DATA, but image data is undefined; posix_memalign, _aligned_malloc, valloc, or memalign do not promise to zero allocated memory. When computing a blkhash of an image via qemu-nbd, we want to test 3 cases: 1. Sparse image: skip reading the entire image based on block status result, and use a pre-computed zero block hash. 2. Image full of zeroes: read the entire image, detect block full of zeroes and skip block hash computation. 3. Image full of data: read the entire image and compute a hash of all blocks. This change adds `read-pattern` option. If the option is set, reads produce the specified pattern. With this option we can emulate an image full of zeroes or full of non-zeroes. Specifying both `read-zeroes` and `read-pattern != 0` is not useful since `read-zeroes` implies a sparse image. In this case `read-zeroes` wins and we ignore the pattern. Maybe we need to make the options mutual exclusive. The following examples shows how the new option can be used with blksum (or nbdcopy --blkhash) to compute a blkhash of an image using the null-co driver. Sparse image - the very fast path: % ./qemu-nbd -r -t -e 0 -f raw -k /tmp/sparse.sock \ "json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-zeroes': true}}" & % time blksum 'nbd+unix:///?socket=/tmp/sparse.sock' 300ad1efddb063822fea65ae3174cd35320939d4d0b050613628c6e1e876f8f6 nbd+unix:///?socket=/tmp/sparse.sock blksum 'nbd+unix:///?socket=/tmp/sparse.sock' 0.05s user 0.01s system 92% cpu 0.061 total Image full of zeros - same hash, 268 times slower: % ./qemu-nbd -r -t -e 0 -f raw -k /tmp/zero.sock \ "json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-pattern': 0}}" & % time blksum 'nbd+unix:///?socket=/tmp/zero.sock' 300ad1efddb063822fea65ae3174cd35320939d4d0b050613628c6e1e876f8f6 nbd+unix:///?socket=/tmp/zero.sock blksum 'nbd+unix:///?socket=/tmp/zero.sock' 7.45s user 22.57s system 183% cpu 16.347 total Image full of data - difference hash, heavy cpu usage: % ./qemu-nbd -r -t -e 0 -f raw -k /tmp/data.sock \ "json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-pattern': -1}}" & % time blksum 'nbd+unix:///?socket=/tmp/data.sock' 2c122b3ed28c83ede3c08485659fa9b56ee54ba1751db74d8ba9aa13d9866432 nbd+unix:///?socket=/tmp/data.sock blksum 'nbd+unix:///?socket=/tmp/data.sock' 46.05s user 14.15s system 448% cpu 13.414 total Tested on top of https://lists.gnu.org/archive/html/qemu-devel/2025-04/msg05096.html. Signed-off-by: Nir Soffer <nir...@gmail.com> --- block/null.c | 17 +++++++++++++++++ qapi/block-core.json | 9 ++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/block/null.c b/block/null.c index 7ba87bd9a9..cbd7d1fdbd 100644 --- a/block/null.c +++ b/block/null.c @@ -22,11 +22,14 @@ #define NULL_OPT_LATENCY "latency-ns" #define NULL_OPT_ZEROES "read-zeroes" +#define NULL_OPT_PATTERN "read-pattern" typedef struct { int64_t length; int64_t latency_ns; bool read_zeroes; + bool has_read_pattern; + int read_pattern; } BDRVNullState; static QemuOptsList runtime_opts = { @@ -49,6 +52,11 @@ static QemuOptsList runtime_opts = { .type = QEMU_OPT_BOOL, .help = "return zeroes when read", }, + { + .name = NULL_OPT_PATTERN, + .type = QEMU_OPT_NUMBER, + .help = "return pattern when read", + }, { /* end of list */ } }, }; @@ -95,6 +103,10 @@ static int null_open(BlockDriverState *bs, QDict *options, int flags, ret = -EINVAL; } s->read_zeroes = qemu_opt_get_bool(opts, NULL_OPT_ZEROES, false); + s->has_read_pattern = qemu_opt_find(opts, NULL_OPT_PATTERN) != NULL; + if (s->has_read_pattern) { + s->read_pattern = qemu_opt_get_number(opts, NULL_OPT_PATTERN, 0); + } qemu_opts_del(opts); bs->supported_write_flags = BDRV_REQ_FUA; return ret; @@ -125,6 +137,8 @@ static coroutine_fn int null_co_preadv(BlockDriverState *bs, if (s->read_zeroes) { qemu_iovec_memset(qiov, 0, 0, bytes); + } else if (s->has_read_pattern) { + qemu_iovec_memset(qiov, 0, s->read_pattern, bytes); } return null_co_common(bs); @@ -199,6 +213,8 @@ static BlockAIOCB *null_aio_preadv(BlockDriverState *bs, if (s->read_zeroes) { qemu_iovec_memset(qiov, 0, 0, bytes); + } else if (s->has_read_pattern) { + qemu_iovec_memset(qiov, 0, s->read_pattern, bytes); } return null_aio_common(bs, cb, opaque); @@ -272,6 +288,7 @@ null_co_get_allocated_file_size(BlockDriverState *bs) static const char *const null_strong_runtime_opts[] = { BLOCK_OPT_SIZE, NULL_OPT_ZEROES, + NULL_OPT_PATTERN, NULL }; diff --git a/qapi/block-core.json b/qapi/block-core.json index b1937780e1..7d576cccbb 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3297,10 +3297,17 @@ # false, the buffer is left unchanged. # (default: false; since: 4.1) # +# @read-pattern: if set, reads from the device produce the specified +# pattern; if unset, the buffer is left unchanged. +# (since: 10.1) +# # Since: 2.9 ## { 'struct': 'BlockdevOptionsNull', - 'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } } + 'data': { '*size': 'int', + '*latency-ns': 'uint64', + '*read-zeroes': 'bool', + '*read-pattern': 'int' } } ## # @BlockdevOptionsNVMe: -- 2.39.5 (Apple Git-154)