From 99153ed5592c9a08c5d5c393a625c54a5a0ea0c0 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 30 Dec 2025 17:09:07 +0000 Subject: [PATCH] checkwrite: Display differences if -D checkwrite.showdiffs=1 is used Add a debug flag which shows the differences between what was written and what we expected. (cherry picked from commit 6a1c92f6a30941f8a891e704eeb95296e6d5669a) --- filters/checkwrite/checkwrite.c | 27 +++++-- .../checkwrite/nbdkit-checkwrite-filter.pod | 14 +++- tests/Makefile.am | 2 + tests/test-checkwrite-evil.sh | 80 +++++++++++++++++++ tests/test-checkwrite-fail.sh | 1 + tests/test-checkwrite.sh | 3 +- 6 files changed, 120 insertions(+), 7 deletions(-) create mode 100755 tests/test-checkwrite-evil.sh diff --git a/filters/checkwrite/checkwrite.c b/filters/checkwrite/checkwrite.c index d5f20e54..44fb22aa 100644 --- a/filters/checkwrite/checkwrite.c +++ b/filters/checkwrite/checkwrite.c @@ -108,9 +108,26 @@ checkwrite_can_multi_conn (nbdkit_next *next, return 1; } +NBDKIT_DLL_PUBLIC int checkwrite_debug_showdiffs = 0; + static inline int -data_does_not_match (int *err) +data_does_not_match (int *err, + const void *expected, const void *actual, size_t count, + uint64_t offset) { + if (checkwrite_debug_showdiffs) { + /* Caller passes expected == NULL to mean we expected zeroes. + * However hexdiff requires an actual buffer, so ... + */ + CLEANUP_FREE char *zerobuf = NULL; + + if (expected == NULL) + zerobuf = calloc (count, 1); + + if (expected || zerobuf) + nbdkit_debug_hexdiff (expected ? : zerobuf, actual, count, NULL, offset); + } + *err = EIO; nbdkit_error ("data written does not match expected"); return -1; @@ -137,8 +154,8 @@ checkwrite_pwrite (nbdkit_next *next, return -1; /* If data written doesn't match data expected, inject EIO. */ - if (memcmp (buf, expected, count) != 0) - return data_does_not_match (err); + if (memcmp (expected, buf, count) != 0) + return data_does_not_match (err, expected, buf, count, offset); return 0; } @@ -207,7 +224,7 @@ checkwrite_trim_zero (nbdkit_next *next, if (next->pread (next, buf, buflen, offset, 0, err) == -1) return -1; if (! is_zero (buf, buflen)) - return data_does_not_match (err); + return data_does_not_match (err, NULL, buf, buflen, offset); count -= buflen; offset += buflen; @@ -243,7 +260,7 @@ checkwrite_trim_zero (nbdkit_next *next, if (next->pread (next, buf, n, offset, 0, err) == -1) return -1; if (! is_zero (buf, n)) - return data_does_not_match (err); + return data_does_not_match (err, NULL, buf, n, offset); count -= n; offset += n; } diff --git a/filters/checkwrite/nbdkit-checkwrite-filter.pod b/filters/checkwrite/nbdkit-checkwrite-filter.pod index 6855d798..0689fb26 100644 --- a/filters/checkwrite/nbdkit-checkwrite-filter.pod +++ b/filters/checkwrite/nbdkit-checkwrite-filter.pod @@ -4,7 +4,7 @@ nbdkit-checkwrite-filter - check writes match contents of plugin =head1 SYNOPSIS - nbdkit --filter=checkwrite PLUGIN + nbdkit --filter=checkwrite PLUGIN [-D checkwrite.showdiffs=1] =head1 DESCRIPTION @@ -59,6 +59,17 @@ with arbitrary plugins. There are no parameters specific to this filter. Parameters are passed through to the underlying plugin. +=head1 DEBUG FLAG + +When both foreground mode and debugging are enabled (I<-fv>) you can +get checkwrite to show the differences between what was written and +what should have been written by adding: + + nbdkit -fv --filter=checkwrite -D checkwrite.showdiffs=1 [...] + +For further information on the format used see +L. + =head1 FILES =over 4 @@ -82,6 +93,7 @@ L, L, L, L, +L, L, L. diff --git a/tests/Makefile.am b/tests/Makefile.am index a887ac75..beeb0fc3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1806,12 +1806,14 @@ EXTRA_DIST += \ TESTS += \ test-checkwrite.sh \ test-checkwrite-bounds.sh \ + test-checkwrite-evil.sh \ test-checkwrite-fail.sh \ test-checkwrite-fastzero.sh \ $(NULL) EXTRA_DIST += \ test-checkwrite.sh \ test-checkwrite-bounds.sh \ + test-checkwrite-evil.sh \ test-checkwrite-fail.sh \ test-checkwrite-fastzero.sh \ $(NULL) diff --git a/tests/test-checkwrite-evil.sh b/tests/test-checkwrite-evil.sh new file mode 100755 index 00000000..2d9e25b4 --- /dev/null +++ b/tests/test-checkwrite-evil.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright Red Hat +# +# 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. + +# Test checkwrite + evil filters together. + +source ./functions.sh +set -e +set -x +set -u + +# Across a 1G disk, with the default failure probability of the evil +# filter, we expect about 43 stuck high bits. The test actually fails +# at the first stuck high bit so we don't end up grinding across the +# whole virtual size. +size=1G + +requires_plugin null +requires_filter checkwrite +requires_filter evil +requires_filter noextents + +# nbdcopy >= 1.5.9 required for this test. +requires_nbdcopy +requires_libnbd_version 1.5.9 + +sock1=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) +sock2=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) +pid1=checkwrite-evil.pid1 +pid2=checkwrite-evil.pid2 +files="$sock1 $sock2 $pid1 $pid2" +rm -f $files +cleanup_fn rm -f $files + +# Source nbdkit. +start_nbdkit -P $pid1 -U $sock1 \ + null $size \ + --filter=evil \ + --filter=noextents + +# Target nbdkit. +start_nbdkit -P $pid2 -U $sock2 \ + -v -D checkwrite.showdiffs=1 \ + null $size \ + --filter=checkwrite + +fail= +nbdcopy "nbd+unix:///?socket=$sock1" "nbd+unix:///?socket=$sock2" || fail=1 +if ! test "$fail"; then + echo "$0: expected nbdcopy to fail but it did not" + exit 1 +fi diff --git a/tests/test-checkwrite-fail.sh b/tests/test-checkwrite-fail.sh index d46c2865..c577b12d 100755 --- a/tests/test-checkwrite-fail.sh +++ b/tests/test-checkwrite-fail.sh @@ -49,6 +49,7 @@ import sys h.connect_command(["nbdkit", "-s", "-v", "--filter=checkwrite", + "-D", "checkwrite.showdiffs=1", "memory", "1M"]) try: h.pwrite(b"1", 0) diff --git a/tests/test-checkwrite.sh b/tests/test-checkwrite.sh index aecb729c..490ab45b 100755 --- a/tests/test-checkwrite.sh +++ b/tests/test-checkwrite.sh @@ -46,7 +46,8 @@ requires_libnbd_version 1.5.9 do_test () { - nbdkit -v --filter=checkwrite "$@" --run 'nbdcopy "$uri" "$uri"' + nbdkit -v --filter=checkwrite -D checkwrite.showdiffs=1 "$@" \ + --run 'nbdcopy "$uri" "$uri"' } # Tests zero-sized disk. -- 2.47.3