Eric Blake
2022-Feb-23 15:45 UTC
[Libguestfs] [nbdkit PATCH 0/2] Saner --filter=blocksize defaults
Now that we can learn block size limits of the underlying plugin, we might as well obey them by default in our blocksize filter. Eric Blake (2): blocksize: Refactor to per-handle constraints blocksize: Default internal block size based on plugin filters/blocksize/nbdkit-blocksize-filter.pod | 14 +- tests/Makefile.am | 14 +- filters/blocksize/blocksize.c | 177 +++++++++++++----- tests/test-blocksize-wrap.sh | 58 ++++++ tests/test-blocksize.sh | 3 - 5 files changed, 205 insertions(+), 61 deletions(-) create mode 100755 tests/test-blocksize-wrap.sh -- 2.35.1
Eric Blake
2022-Feb-23 15:45 UTC
[Libguestfs] [nbdkit PATCH 1/2] blocksize: Refactor to per-handle constraints
An upcoming patch wants to make the blocksize filter more responsive to the actual .block_size limits from the underlying plugin. But those limits can differ per export (for example, qemu can export multiple images, where one backed by a file has minblock 1, but another backed by encryption has minblock 512). To do that, we need to track block size constraints per handle, rather than globally. At this point, no functional change is intended; the change is merely the refactoring to get per-handle values, and deferring the settling of defaults not specified during .config to now be initialized during .prepare rather than .config_complete. The next patch can then modify .block_size to actually honor the underlying plugin constraints. --- I wrote this patch by temporarily renaming the globals min_block and so on, to let the compiler flag where we want to use the h->minblock per-handle variant. If we want to leave the globals named slightly differently for safety, let me know, and I can reinstate that. --- filters/blocksize/blocksize.c | 162 +++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 50 deletions(-) diff --git a/filters/blocksize/blocksize.c b/filters/blocksize/blocksize.c index 38b57c1e..9a59a1aa 100644 --- a/filters/blocksize/blocksize.c +++ b/filters/blocksize/blocksize.c @@ -61,10 +61,18 @@ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; */ static char bounce[BLOCKSIZE_MIN_LIMIT]; +/* Globals set by .config */ static unsigned int minblock; static unsigned int maxdata; static unsigned int maxlen; +/* Per-handle values set during .prepare */ +struct blocksize_handle { + uint32_t minblock; + uint32_t maxdata; + uint32_t maxlen; +}; + static int blocksize_parse (const char *name, const char *s, unsigned int *v) { @@ -114,28 +122,20 @@ blocksize_config_complete (nbdkit_next_config_complete *next, return -1; } } - else - minblock = 1; - if (maxdata) { + if (maxdata && minblock) { if (maxdata & (minblock - 1)) { nbdkit_error ("maxdata must be a multiple of %u", minblock); return -1; } } - else if (maxlen) - maxdata = MIN (maxlen, 64 * 1024 * 1024); - else - maxdata = 64 * 1024 * 1024; - if (maxlen) { + if (maxlen && minblock) { if (maxlen & (minblock - 1)) { nbdkit_error ("maxlen must be a multiple of %u", minblock); return -1; } } - else - maxlen = -minblock; return next (nxdata); } @@ -145,16 +145,66 @@ blocksize_config_complete (nbdkit_next_config_complete *next, "maxdata=<SIZE> Maximum size for read/write (default 64M).\n" \ "maxlen=<SIZE> Maximum size for trim/zero (default 4G-minblock)." +static void * +blocksize_open (nbdkit_next_open *next, nbdkit_context *nxdata, + int readonly, const char *exportname, int is_tls) +{ + struct blocksize_handle *h; + + if (next (nxdata, readonly, exportname) == -1) + return NULL; + + h = malloc (sizeof *h); + if (h == NULL) { + nbdkit_error ("malloc: %m"); + return NULL; + } + + h->minblock = minblock; + h->maxdata = maxdata; + h->maxlen = maxlen; + return h; +} + +static int +blocksize_prepare (nbdkit_next *next, void *handle, + int readonly) +{ + struct blocksize_handle *h = handle; + + if (h->minblock == 0) + h->minblock = 1; + if (h->maxdata == 0) { + if (h->maxlen) + h->maxdata = MIN (h->maxlen, 64 * 1024 * 1024); + else + h->maxdata = 64 * 1024 * 1024; + } + if (h->maxlen == 0) + h->maxlen = -h->minblock; + + return 0; +} + + +static void +blocksize_close (void *handle) +{ + free (handle); +} + + /* Round size down to avoid issues at end of file. */ static int64_t blocksize_get_size (nbdkit_next *next, void *handle) { + struct blocksize_handle *h = handle; int64_t size = next->get_size (next); if (size == -1) return -1; - return ROUND_DOWN (size, minblock); + return ROUND_DOWN (size, h->minblock); } /* Block size constraints. @@ -168,11 +218,13 @@ static int blocksize_block_size (nbdkit_next *next, void *handle, uint32_t *minimum, uint32_t *preferred, uint32_t *maximum) { + struct blocksize_handle *h = handle; + if (next->block_size (next, minimum, preferred, maximum) == -1) return -1; if (*preferred == 0) - *preferred = MAX (4096, minblock); + *preferred = MAX (4096, h->minblock); *minimum = 1; *maximum = 0xffffffff; @@ -185,16 +237,17 @@ blocksize_pread (nbdkit_next *next, void *handle, void *b, uint32_t count, uint64_t offs, uint32_t flags, int *err) { + struct blocksize_handle *h = handle; char *buf = b; uint32_t keep; uint32_t drop; /* Unaligned head */ - if (offs & (minblock - 1)) { + if (offs & (h->minblock - 1)) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - drop = offs & (minblock - 1); - keep = MIN (minblock - drop, count); - if (next->pread (next, bounce, minblock, offs - drop, flags, err) == -1) + drop = offs & (h->minblock - 1); + keep = MIN (h->minblock - drop, count); + if (next->pread (next, bounce, h->minblock, offs - drop, flags, err) == -1) return -1; memcpy (buf, bounce + drop, keep); buf += keep; @@ -203,8 +256,8 @@ blocksize_pread (nbdkit_next *next, } /* Aligned body */ - while (count >= minblock) { - keep = MIN (maxdata, ROUND_DOWN (count, minblock)); + while (count >= h->minblock) { + keep = MIN (h->maxdata, ROUND_DOWN (count, h->minblock)); if (next->pread (next, buf, keep, offs, flags, err) == -1) return -1; buf += keep; @@ -215,7 +268,7 @@ blocksize_pread (nbdkit_next *next, /* Unaligned tail */ if (count) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - if (next->pread (next, bounce, minblock, offs, flags, err) == -1) + if (next->pread (next, bounce, h->minblock, offs, flags, err) == -1) return -1; memcpy (buf, bounce, count); } @@ -228,6 +281,7 @@ blocksize_pwrite (nbdkit_next *next, void *handle, const void *b, uint32_t count, uint64_t offs, uint32_t flags, int *err) { + struct blocksize_handle *h = handle; const char *buf = b; uint32_t keep; uint32_t drop; @@ -240,14 +294,14 @@ blocksize_pwrite (nbdkit_next *next, } /* Unaligned head */ - if (offs & (minblock - 1)) { + if (offs & (h->minblock - 1)) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - drop = offs & (minblock - 1); - keep = MIN (minblock - drop, count); - if (next->pread (next, bounce, minblock, offs - drop, 0, err) == -1) + drop = offs & (h->minblock - 1); + keep = MIN (h->minblock - drop, count); + if (next->pread (next, bounce, h->minblock, offs - drop, 0, err) == -1) return -1; memcpy (bounce + drop, buf, keep); - if (next->pwrite (next, bounce, minblock, offs - drop, flags, err) == -1) + if (next->pwrite (next, bounce, h->minblock, offs - drop, flags, err) == -1) return -1; buf += keep; offs += keep; @@ -255,8 +309,8 @@ blocksize_pwrite (nbdkit_next *next, } /* Aligned body */ - while (count >= minblock) { - keep = MIN (maxdata, ROUND_DOWN (count, minblock)); + while (count >= h->minblock) { + keep = MIN (h->maxdata, ROUND_DOWN (count, h->minblock)); if (next->pwrite (next, buf, keep, offs, flags, err) == -1) return -1; buf += keep; @@ -267,10 +321,10 @@ blocksize_pwrite (nbdkit_next *next, /* Unaligned tail */ if (count) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - if (next->pread (next, bounce, minblock, offs, 0, err) == -1) + if (next->pread (next, bounce, h->minblock, offs, 0, err) == -1) return -1; memcpy (bounce, buf, count); - if (next->pwrite (next, bounce, minblock, offs, flags, err) == -1) + if (next->pwrite (next, bounce, h->minblock, offs, flags, err) == -1) return -1; } @@ -284,6 +338,7 @@ blocksize_trim (nbdkit_next *next, void *handle, uint32_t count, uint64_t offs, uint32_t flags, int *err) { + struct blocksize_handle *h = handle; uint32_t keep; bool need_flush = false; @@ -294,18 +349,18 @@ blocksize_trim (nbdkit_next *next, } /* Ignore unaligned head */ - if (offs & (minblock - 1)) { - keep = MIN (minblock - (offs & (minblock - 1)), count); + if (offs & (h->minblock - 1)) { + keep = MIN (h->minblock - (offs & (h->minblock - 1)), count); offs += keep; count -= keep; } /* Ignore unaligned tail */ - count = ROUND_DOWN (count, minblock); + count = ROUND_DOWN (count, h->minblock); /* Aligned body */ while (count) { - keep = MIN (maxlen, count); + keep = MIN (h->maxlen, count); if (next->trim (next, keep, offs, flags, err) == -1) return -1; offs += keep; @@ -322,6 +377,7 @@ blocksize_zero (nbdkit_next *next, void *handle, uint32_t count, uint64_t offs, uint32_t flags, int *err) { + struct blocksize_handle *h = handle; uint32_t keep; uint32_t drop; bool need_flush = false; @@ -332,7 +388,7 @@ blocksize_zero (nbdkit_next *next, * calls; it's easier to just declare that anything that can't be * done in one call to the plugin is not fast. */ - if ((offs | count) & (minblock - 1) || count > maxlen) { + if ((offs | count) & (h->minblock - 1) || count > h->maxlen) { *err = ENOTSUP; return -1; } @@ -345,14 +401,14 @@ blocksize_zero (nbdkit_next *next, } /* Unaligned head */ - if (offs & (minblock - 1)) { + if (offs & (h->minblock - 1)) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - drop = offs & (minblock - 1); - keep = MIN (minblock - drop, count); - if (next->pread (next, bounce, minblock, offs - drop, 0, err) == -1) + drop = offs & (h->minblock - 1); + keep = MIN (h->minblock - drop, count); + if (next->pread (next, bounce, h->minblock, offs - drop, 0, err) == -1) return -1; memset (bounce + drop, 0, keep); - if (next->pwrite (next, bounce, minblock, offs - drop, + if (next->pwrite (next, bounce, h->minblock, offs - drop, flags & ~NBDKIT_FLAG_MAY_TRIM, err) == -1) return -1; offs += keep; @@ -360,8 +416,8 @@ blocksize_zero (nbdkit_next *next, } /* Aligned body */ - while (count >= minblock) { - keep = MIN (maxlen, ROUND_DOWN (count, minblock)); + while (count >= h->minblock) { + keep = MIN (h->maxlen, ROUND_DOWN (count, h->minblock)); if (next->zero (next, keep, offs, flags, err) == -1) return -1; offs += keep; @@ -371,10 +427,10 @@ blocksize_zero (nbdkit_next *next, /* Unaligned tail */ if (count) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - if (next->pread (next, bounce, minblock, offs, 0, err) == -1) + if (next->pread (next, bounce, h->minblock, offs, 0, err) == -1) return -1; memset (bounce, 0, count); - if (next->pwrite (next, bounce, minblock, offs, + if (next->pwrite (next, bounce, h->minblock, offs, flags & ~NBDKIT_FLAG_MAY_TRIM, err) == -1) return -1; } @@ -395,20 +451,22 @@ blocksize_extents (nbdkit_next *next, * fine to return less than the full count as long as we're making * progress. */ + struct blocksize_handle *h = handle; CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL; size_t i; struct nbdkit_extent e; - extents2 = nbdkit_extents_new (ROUND_DOWN (offset, minblock), - ROUND_UP (offset + count, minblock)); + extents2 = nbdkit_extents_new (ROUND_DOWN (offset, h->minblock), + ROUND_UP (offset + count, h->minblock)); if (extents2 == NULL) { *err = errno; return -1; } - if (nbdkit_extents_aligned (next, MIN (ROUND_UP (count, minblock), maxlen), - ROUND_DOWN (offset, minblock), flags, minblock, - extents2, err) == -1) + if (nbdkit_extents_aligned (next, MIN (ROUND_UP (count, h->minblock), + h->maxlen), + ROUND_DOWN (offset, h->minblock), flags, + h->minblock, extents2, err) == -1) return -1; for (i = 0; i < nbdkit_extents_count (extents2); ++i) { @@ -426,20 +484,21 @@ blocksize_cache (nbdkit_next *next, void *handle, uint32_t count, uint64_t offs, uint32_t flags, int *err) { + struct blocksize_handle *h = handle; uint32_t limit; uint64_t remaining = count; /* Rounding out could exceed 32 bits */ /* Unaligned head */ - limit = offs & (minblock - 1); + limit = offs & (h->minblock - 1); remaining += limit; offs -= limit; /* Unaligned tail */ - remaining = ROUND_UP (remaining, minblock); + remaining = ROUND_UP (remaining, h->minblock); /* Aligned body */ while (remaining) { - limit = MIN (maxdata, remaining); + limit = MIN (h->maxdata, remaining); if (next->cache (next, limit, offs, flags, err) == -1) return -1; offs += limit; @@ -455,6 +514,9 @@ static struct nbdkit_filter filter = { .config = blocksize_config, .config_complete = blocksize_config_complete, .config_help = blocksize_config_help, + .open = blocksize_open, + .prepare = blocksize_prepare, + .close = blocksize_close, .get_size = blocksize_get_size, .block_size = blocksize_block_size, .pread = blocksize_pread, -- 2.35.1
Eric Blake
2022-Feb-23 15:45 UTC
[Libguestfs] [nbdkit PATCH 2/2] blocksize: Default internal block size based on plugin
Now that we have .block_size, if the plugin reports specific block constraints, honor those by default rather than requiring the user to duplicate work by passing in extra filter parameters on the command line. The TODO in test-blocksize.sh is no longer relevant (we now advertise block size, so modern qemu no longer does read-modify-write), but that test is still valuable as written, so the real coverage of the new default behavior is in a new test. --- filters/blocksize/nbdkit-blocksize-filter.pod | 14 +++-- tests/Makefile.am | 14 ++++- filters/blocksize/blocksize.c | 21 +++++-- tests/test-blocksize-wrap.sh | 58 +++++++++++++++++++ tests/test-blocksize.sh | 3 - 5 files changed, 96 insertions(+), 14 deletions(-) create mode 100755 tests/test-blocksize-wrap.sh diff --git a/filters/blocksize/nbdkit-blocksize-filter.pod b/filters/blocksize/nbdkit-blocksize-filter.pod index 5df9e719..4a1ebded 100644 --- a/filters/blocksize/nbdkit-blocksize-filter.pod +++ b/filters/blocksize/nbdkit-blocksize-filter.pod @@ -23,6 +23,9 @@ refuses a read or write larger than 64 megabytes, while many other NBD servers limit things to 32 megabytes). The blocksize filter can be used to modify the client requests to meet the plugin restrictions. +This filter can be combined with L<nbdkit-blocksize-policy-filter(1)> +to advertise different block sizes to the client. + =head1 PARAMETERS The nbdkit-blocksize-filter accepts the following parameters. @@ -33,7 +36,8 @@ The nbdkit-blocksize-filter accepts the following parameters. The minimum block size and alignment to pass to the plugin. This must be a power of two, and no larger than 64k. If omitted, this defaults -to 1 (that is, no minimum size restrictions). The filter rounds up +to the minimum block size of the underlying plugin, or 1 if the plugin +did not report a minimum block size. The filter rounds up read requests to alignment boundaries, performs read-modify-write cycles for any unaligned head or tail of a write or zero request, and silently ignores any unaligned head or tail of a trim request. The @@ -47,8 +51,9 @@ This parameter understands the suffix 'k' for 1024. =item B<maxdata=>SIZE The maximum block size for any single transaction with data (read and -write). If omitted, this defaults to 64 megabytes (that is, the -nbdkit maximum). This need not be a power of two, but must be an +write). If omitted, this defaults to the minimum of 64 megabytes (that +is, the nbdkit maximum) or any maximum reported by the underlying plugin. +This need not be a power of two, but must be an integer multiple of C<minblock>. The filter fragments any larger client request into multiple plugin requests. @@ -113,6 +118,7 @@ L<nbdkit(1)>, L<nbdkit-nbd-plugin(1)>, L<nbdkit-vddk-plugin(1)>, L<nbdkit-filter(3)>, +L<nbdkit-blocksize-policy-filter(1)>, L<nbdkit-truncate-filter(1)>. =head1 AUTHORS @@ -121,4 +127,4 @@ Eric Blake =head1 COPYRIGHT -Copyright (C) 2018 Red Hat Inc. +Copyright (C) 2018-2022 Red Hat Inc. diff --git a/tests/Makefile.am b/tests/Makefile.am index dd3b4ded..e90de42f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ # nbdkit -# Copyright (C) 2013-2021 Red Hat Inc. +# Copyright (C) 2013-2022 Red Hat Inc. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -1408,8 +1408,16 @@ test_layers_filter3_la_LDFLAGS = \ test_layers_filter3_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS) # blocksize filter test. -TESTS += test-blocksize.sh test-blocksize-extents.sh -EXTRA_DIST += test-blocksize.sh test-blocksize-extents.sh +TESTS += \ + test-blocksize.sh \ + test-blocksize-extents.sh \ + test-blocksize-wrap.sh \ + $(NULL) +EXTRA_DIST += \ + test-blocksize.sh \ + test-blocksize-extents.sh \ + test-blocksize-wrap.sh \ + $(NULL) # blocksize-policy filter test. TESTS += test-blocksize-policy.sh test-blocksize-error-policy.sh diff --git a/filters/blocksize/blocksize.c b/filters/blocksize/blocksize.c index 9a59a1aa..444e2041 100644 --- a/filters/blocksize/blocksize.c +++ b/filters/blocksize/blocksize.c @@ -171,17 +171,30 @@ blocksize_prepare (nbdkit_next *next, void *handle, int readonly) { struct blocksize_handle *h = handle; + uint32_t minimum, preferred, maximum; + + /* Here, minimum and maximum will clamp per-handle defaults not set + * by globals in .config; preferred has no impact until .block_size. + */ + if (next->block_size (next, &minimum, &preferred, &maximum) == -1) + return -1; + + h->minblock = MAX (MAX (h->minblock, 1), minimum); - if (h->minblock == 0) - h->minblock = 1; if (h->maxdata == 0) { if (h->maxlen) h->maxdata = MIN (h->maxlen, 64 * 1024 * 1024); else h->maxdata = 64 * 1024 * 1024; } + if (maximum) + h->maxdata = MIN (h->maxdata, maximum); + h->maxdata = ROUND_DOWN (h->maxdata, h->minblock); + if (h->maxlen == 0) h->maxlen = -h->minblock; + else + h->maxlen = ROUND_DOWN (h->maxlen, h->minblock); return 0; } @@ -220,11 +233,11 @@ blocksize_block_size (nbdkit_next *next, void *handle, { struct blocksize_handle *h = handle; + /* Here we only need preferred; see also blocksize_prepare. */ if (next->block_size (next, minimum, preferred, maximum) == -1) return -1; - if (*preferred == 0) - *preferred = MAX (4096, h->minblock); + *preferred = MAX (MAX (*preferred, 4096), h->minblock); *minimum = 1; *maximum = 0xffffffff; diff --git a/tests/test-blocksize-wrap.sh b/tests/test-blocksize-wrap.sh new file mode 100755 index 00000000..f635e0c7 --- /dev/null +++ b/tests/test-blocksize-wrap.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2019-2022 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +source ./functions.sh +set -e +set -x + +requires_plugin eval +requires nbdsh -c 'print(h.get_block_size)' + +# Create an nbdkit eval plugin which presents block size constraints. +# Check that the blocksize filter advertises full access, but that +# underlying plugin access uses read-modify-write. +nbdkit -v -U - --filter=blocksize eval \ + block_size="echo 1K 128K 512K" \ + get_size="echo 2M" \ + pread='case $3.$4 in + $((1*1024)).0 | $((512*1024)).$((1*1024)) | \ + $((511*1024)).$((513*1024)) | $((1*1024)).$((1024*1024)) ) + dd if=/dev/zero count=$3 iflag=count_bytes ;; + *) echo EIO >&2; exit 1 ;; + esac' \ + --run 'nbdsh \ + -u "$uri" \ + -c "assert h.get_block_size(nbd.SIZE_MINIMUM) == 1" \ + -c "assert h.get_block_size(nbd.SIZE_PREFERRED) == 128 * 1024" \ + -c "assert h.get_block_size(nbd.SIZE_MAXIMUM) == 4294967295" \ + -c "assert h.pread(1024*1024 + 1, 1) == b\"\\0\" * (1024*1024 + 1)" \ + ' diff --git a/tests/test-blocksize.sh b/tests/test-blocksize.sh index 64196bb8..ee325eff 100755 --- a/tests/test-blocksize.sh +++ b/tests/test-blocksize.sh @@ -42,9 +42,6 @@ files="blocksize1.img blocksize1.log $sock1 blocksize1.pid rm -f $files # Prep images, and check that qemu-io understands the actions we plan on doing. -# TODO: Until we implement NBD_INFO_BLOCK_SIZE, qemu-io does its own -# read-modify-write at 512-byte alignment, while we'd like to ultimately test -# 1-byte accesses truncate -s 10M blocksize1.img if ! qemu-io -f raw -c 'r 0 1' -c 'w -z 1000 2000' \ -c 'w -P 0 1M 2M' -c 'discard 3M 4M' blocksize1.img; then -- 2.35.1