Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 0/6] New ones plugin and evil filter
This addresses most of the issues from the review of v1 here: https://listman.redhat.com/archives/libguestfs/2023-May/031520.html * The filter now only handles 1e-12 <= P <= 1/8. Probabilities outside this range are treated as 0% or 100% respectively. The reasons are given in the code and man page, but basically are to do with stopping things from blowing up. The restriction is not essential - it might be fixed in future. I also added tests for small and large probabilities. * I did *not* change probability parsing or the "inf/nan" etc case or return the numerator and denominator separately. The parsing could be changed/fixed later. The API/ABI could not be changed, but I'm unconvinced that returning two numbers instead of one does anything except push the problem elsewhere. * A new commit is added to make <ispowerof2.h> log_2_bits work on 64 bit ints on all platforms. I tested this inside a 32 bit mock. * Add more debugging, and in particular print the seed. * Use Python to count bits instead of scary shell script. * When comparing subsets in the tests, make sure they contain a stuck bit. * Use '**' for exponentiation consistently. * Various other comments / typos / etc fixed and some improvements to the man page. * Retest. Now I must get back to doing real work! Rich.
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 1/6] Add new public nbdkit_parse_probability function
In nbdkit-error-filter we need to parse parameters as probabilities.
This is useful enough to add to nbdkit, since we will use it in
another filter in future.
---
 docs/nbdkit-plugin.pod                  | 19 +++++++
 plugins/python/nbdkit-python-plugin.pod |  6 ++
 include/nbdkit-common.h                 |  2 +
 server/nbdkit.syms                      |  1 +
 server/public.c                         | 37 +++++++++++++
 server/test-public.c                    | 73 +++++++++++++++++++++++++
 plugins/ocaml/NBDKit.mli                | 11 ++--
 plugins/ocaml/NBDKit.ml                 |  2 +
 plugins/ocaml/bindings.c                | 16 ++++++
 plugins/python/modfunctions.c           | 21 +++++++
 tests/test-python-plugin.py             | 12 ++++
 tests/test_ocaml_plugin.ml              |  1 +
 12 files changed, 196 insertions(+), 5 deletions(-)
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 860c5cecb..e8d30a98e 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1433,6 +1433,25 @@ C<str> can be a string containing a
case-insensitive form of various
 common toggle values.  The function returns 0 or 1 if the parse was
 successful.  If there was an error, it returns C<-1>.
 
+=head2 Parsing probabilities
+
+Use the C<nbdkit_parse_probability> utility function to parse
+probabilities.  Common formats understood include: C<"0.1">,
C<"10%">
+or C<"1:10">, which all mean a probability of 1 in 10.
+
+ int nbdkit_parse_probability (const char *what, const char *str,
+                               double *ret);
+
+The C<what> parameter is printed in error messages to provide context.
+The C<str> parameter is the probability string.
+
+On success the function returns C<0> and sets C<*ret>. 
B<Note> that
+the probability returned may be outside the range S<[ 0.0..1.0 ]>, for
+example if C<str == "200%">.  If you want to clamp the result
you must
+check that yourself.
+
+On error, nbdkit_error is called and C<-1> is returned.
+
 =head2 Reading passwords
 
 The C<nbdkit_read_password> utility function can be used to read
diff --git a/plugins/python/nbdkit-python-plugin.pod
b/plugins/python/nbdkit-python-plugin.pod
index e328cc2e0..0e55dcfcc 100644
--- a/plugins/python/nbdkit-python-plugin.pod
+++ b/plugins/python/nbdkit-python-plugin.pod
@@ -136,6 +136,12 @@ C<import errno>.
 Parse a string (such as "100M") into a size in bytes. Wraps the
 C<nbdkit_parse_size()> C function.
 
+=head3 C<nbdkit.parse_probability(what, str)>
+
+Parse a string (such as "100%") into a probability, returning a
+floating point number. Wraps the C<nbdkit_parse_probability()> C
+function.
+
 =head3 C<nbdkit.shutdown()>
 
 Request asynchronous server shutdown.
diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index e070245b8..b44e77323 100644
--- a/include/nbdkit-common.h
+++ b/include/nbdkit-common.h
@@ -105,6 +105,8 @@ NBDKIT_EXTERN_DECL (void, nbdkit_vdebug,
 
 NBDKIT_EXTERN_DECL (char *, nbdkit_absolute_path, (const char *path));
 NBDKIT_EXTERN_DECL (int64_t, nbdkit_parse_size, (const char *str));
+NBDKIT_EXTERN_DECL (int, nbdkit_parse_probability,
+                    (const char *what, const char *str, double *r));
 NBDKIT_EXTERN_DECL (int, nbdkit_parse_bool, (const char *str));
 NBDKIT_EXTERN_DECL (int, nbdkit_parse_int,
                     (const char *what, const char *str, int *r));
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 353ea2a98..c6480e43e 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -67,6 +67,7 @@
     nbdkit_parse_int64_t;
     nbdkit_parse_int8_t;
     nbdkit_parse_int;
+    nbdkit_parse_probability;
     nbdkit_parse_size;
     nbdkit_parse_uint16_t;
     nbdkit_parse_uint32_t;
diff --git a/server/public.c b/server/public.c
index 71ea6779d..fe2b48c7c 100644
--- a/server/public.c
+++ b/server/public.c
@@ -421,6 +421,43 @@ nbdkit_parse_size (const char *str)
   return size * scale;
 }
 
+NBDKIT_DLL_PUBLIC int
+nbdkit_parse_probability (const char *what, const char *str,
+                          double *retp)
+{
+  double d, d2;
+  char c;
+  int n;
+
+  if (sscanf (str, "%lg%[:/]%lg%n", &d, &c, &d2, &n)
== 3 &&
+      strcmp (&str[n], "") == 0) { /* N:M or N/M */
+    if (d == 0 && d2 == 0)         /* 0/0 is OK */
+      ;
+    else if (d2 == 0)              /* N/0 is bad */
+      goto bad_parse;
+    else
+      d /= d2;
+  }
+  else if (sscanf (str, "%lg%n", &d, &n) == 1) {
+    if (strcmp (&str[n], "%") == 0) /* percentage */
+      d /= 100.0;
+    else if (strcmp (&str[n], "") == 0) /* probability */
+      ;
+    else
+      goto bad_parse;
+  }
+  else
+    goto bad_parse;
+
+  if (retp)
+    *retp = d;
+  return 0;
+
+ bad_parse:
+  nbdkit_error ("%s: could not parse '%s'", what, str);
+  return -1;
+}
+
 /* Parse a string as a boolean, or return -1 after reporting the error.
  */
 NBDKIT_DLL_PUBLIC int
diff --git a/server/test-public.c b/server/test-public.c
index 676411290..0d84abdd2 100644
--- a/server/test-public.c
+++ b/server/test-public.c
@@ -200,6 +200,78 @@ test_nbdkit_parse_size (void)
   return pass;
 }
 
+static bool
+test_nbdkit_parse_probability (void)
+{
+  size_t i;
+  bool pass = true;
+  struct pair {
+    const char *str;
+    int result;
+    double expected;
+  } tests[] = {
+    /* Bogus strings */
+    { "", -1 },
+    { "garbage", -1 },
+    { "0garbage", -1 },
+    { "1X", -1 },
+    { "1%%", -1 },
+    { "1:", -1 },
+    { "1:1:1", -1 },
+    { "1:0", -1 }, /* format is valid but divide by zero is not
allowed */
+    { "1/", -1 },
+    { "1/2/3", -1 },
+
+    /* Numbers. */
+    { "0", 0, 0 },
+    { "1", 0, 1 },
+    { "2", 0, 2 }, /* values outside [0..1] range are allowed */
+    { "0.1", 0, 0.1 },
+    { "0.5", 0, 0.5 },
+    { "0.9", 0, 0.9 },
+    { "1.0000", 0, 1 },
+
+    /* Percentages. */
+    { "0%", 0, 0 },
+    { "50%", 0, 0.5 },
+    { "100%", 0, 1 },
+    { "90.25%", 0, 0.9025 },
+
+    /* N in M */
+    { "1:1000", 0, 0.001 },
+    { "1/1000", 0, 0.001 },
+    { "2:99", 0, 2.0/99 },
+    { "2/99", 0, 2.0/99 },
+    { "0:1000000", 0, 0 },
+  };
+
+  for (i = 0; i < ARRAY_SIZE (tests); i++) {
+    int r;
+    double d;
+
+    error_flagged = false;
+    r = nbdkit_parse_probability ("test", tests[i].str, &d);
+    if (r != tests[i].result) {
+      fprintf (stderr,
+               "Wrong return value for %s, got %d, expected %d\n",
+               tests[i].str, r, tests[i].result);
+      pass = false;
+    }
+    if (r == 0 && d != tests[i].expected) {
+      fprintf (stderr,
+               "Wrong result for %s, got %g, expected %g\n",
+               tests[i].str, d, tests[i].expected);
+      pass = false;
+    }
+    if ((r == -1) != error_flagged) {
+      fprintf (stderr, "Wrong error message handling for %s\n",
tests[i].str);
+      pass = false;
+    }
+  }
+
+  return pass;
+}
+
 static bool
 test_nbdkit_parse_ints (void)
 {
@@ -503,6 +575,7 @@ main (int argc, char *argv[])
 {
   bool pass = true;
   pass &= test_nbdkit_parse_size ();
+  pass &= test_nbdkit_parse_probability ();
   pass &= test_nbdkit_parse_ints ();
   pass &= test_nbdkit_read_password ();
   /* nbdkit_absolute_path and nbdkit_nanosleep not unit-tested here, but
diff --git a/plugins/ocaml/NBDKit.mli b/plugins/ocaml/NBDKit.mli
index 81447d07d..bc190f267 100644
--- a/plugins/ocaml/NBDKit.mli
+++ b/plugins/ocaml/NBDKit.mli
@@ -134,9 +134,9 @@ val register_plugin :
     into [EINVAL]. *)
 val set_error : Unix.error -> unit
 
-(** Bindings for [nbdkit_parse_size], [nbdkit_parse_bool] and
-    [nbdkit_read_password].  See nbdkit-plugin(3) for information
-    about these functions.
+(** Bindings for [nbdkit_parse_size], [nbdkit_parse_probability],
+    [nbdkit_parse_bool] and [nbdkit_read_password].  See
+    nbdkit-plugin(3) for information about these functions.
 
     On error these functions all raise [Invalid_argument].  The
     actual error is sent to the nbdkit error log and is not
@@ -145,10 +145,11 @@ val set_error : Unix.error -> unit
 (* Note OCaml has functions already for parsing other integers, so
  * there is no need to bind them here.  We only bind the functions
  * which have special abilities in nbdkit: [parse_size] can parse
- * human sizes, [parse_bool] parses a range of nbdkit-specific
- * boolean strings, and [read_password] suppresses echo.
+ * human sizes, [parse_probability] and [parse_bool] parses a range
+ * of nbdkit-specific strings, and [read_password] suppresses echo.
  *)
 val parse_size : string -> int64
+val parse_probability : string -> string -> float
 val parse_bool : string -> bool
 val read_password : string -> string
 
diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml
index e1cf28c94..2d1696917 100644
--- a/plugins/ocaml/NBDKit.ml
+++ b/plugins/ocaml/NBDKit.ml
@@ -160,6 +160,8 @@ let register_plugin ~name
 (* Bindings to nbdkit server functions. *)
 external set_error : Unix.error -> unit = "ocaml_nbdkit_set_error"
[@@noalloc]
 external parse_size : string -> int64 = "ocaml_nbdkit_parse_size"
+external parse_probability : string -> string -> float + 
"ocaml_nbdkit_parse_probability"
 external parse_bool : string -> bool = "ocaml_nbdkit_parse_bool"
 external read_password : string -> string =
"ocaml_nbdkit_read_password"
 external realpath : string -> string = "ocaml_nbdkit_realpath"
diff --git a/plugins/ocaml/bindings.c b/plugins/ocaml/bindings.c
index f2c9ca07d..4885feac5 100644
--- a/plugins/ocaml/bindings.c
+++ b/plugins/ocaml/bindings.c
@@ -74,6 +74,22 @@ ocaml_nbdkit_parse_size (value strv)
   CAMLreturn (rv);
 }
 
+NBDKIT_DLL_PUBLIC value
+ocaml_nbdkit_parse_probability (value whatv, value strv)
+{
+  CAMLparam2 (whatv, strv);
+  CAMLlocal1 (dv);
+  int r;
+  double d;
+
+  r = nbdkit_parse_probability (String_val (whatv), String_val (strv), &d);
+  if (r == -1)
+    caml_invalid_argument ("nbdkit_parse_probability");
+  dv = caml_copy_double (d);
+
+  CAMLreturn (dv);
+}
+
 NBDKIT_DLL_PUBLIC value
 ocaml_nbdkit_parse_bool (value strv)
 {
diff --git a/plugins/python/modfunctions.c b/plugins/python/modfunctions.c
index 479707e78..74bfd6f27 100644
--- a/plugins/python/modfunctions.c
+++ b/plugins/python/modfunctions.c
@@ -122,11 +122,32 @@ parse_size (PyObject *self, PyObject *args)
   return PyLong_FromSize_t ((size_t)size);
 }
 
+/* nbdkit.parse_probability */
+static PyObject *
+parse_probability (PyObject *self, PyObject *args)
+{
+  const char *what, *str;
+  double d;
+
+  if (!PyArg_ParseTuple (args, "ss:parse_probability", &what,
&str))
+    return NULL;
+
+  if (nbdkit_parse_probability (what, str, &d) == -1) {
+    PyErr_SetString (PyExc_ValueError,
+                     "Unable to parse string as probability");
+    return NULL;
+  }
+
+  return PyFloat_FromDouble (d);
+}
+
 static PyMethodDef NbdkitMethods[] = {
   { "debug", debug, METH_VARARGS,
     "Print a debug message" },
   { "export_name", export_name, METH_NOARGS,
     "Return the optional export name negotiated with the client" },
+  { "parse_probability", parse_probability, METH_VARARGS,
+    "Parse probability strings into floating point number" },
   { "parse_size", parse_size, METH_VARARGS,
     "Parse human-readable size strings into bytes" },
   { "set_error", set_error, METH_VARARGS,
diff --git a/tests/test-python-plugin.py b/tests/test-python-plugin.py
index c3232a112..7f3a2c2e4 100644
--- a/tests/test-python-plugin.py
+++ b/tests/test-python-plugin.py
@@ -55,8 +55,20 @@ class TestAPI(unittest.TestCase):
         with self.assertRaises(ValueError):
             nbdkit.parse_size('foo')
 
+    def test_parse_probability(self):
+        self.assertEqual(nbdkit.parse_probability('test',
'1:10'), 0.1)
+        self.assertEqual(nbdkit.parse_probability('test',
'100%'), 1)
+        self.assertEqual(nbdkit.parse_probability('test', '0'),
0)
+
+        with self.assertRaises(TypeError):
+            nbdkit.parse_probability('test', 17)
+
+        with self.assertRaises(ValueError):
+            nbdkit.parse_probability('test', 'bar')
+
 
 TestAPI().test_parse_size()
+TestAPI().test_parse_probability()
 
 
 def config(k, v):
diff --git a/tests/test_ocaml_plugin.ml b/tests/test_ocaml_plugin.ml
index 8132de8f8..bf998d361 100644
--- a/tests/test_ocaml_plugin.ml
+++ b/tests/test_ocaml_plugin.ml
@@ -39,6 +39,7 @@ let sparse = Bytes.make nr_sectors '\000' (*
sparseness bitmap *)
 (* Test parse_* functions. *)
 let ()    assert (NBDKit.parse_size "1M" = Int64.of_int (1024*1024));
+  assert (NBDKit.parse_probability "test parse probability"
"1:10" = 0.1);
   assert (NBDKit.parse_bool "true" = true);
   assert (NBDKit.parse_bool "0" = false)
 
-- 
2.39.2
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 2/6] error: Use new nbdkit_parse_probability
This replaces the existing ad hoc parsing with the new public function.
---
 filters/error/nbdkit-error-filter.pod | 12 +++++++-----
 filters/error/error.c                 | 14 +-------------
 2 files changed, 8 insertions(+), 18 deletions(-)
diff --git a/filters/error/nbdkit-error-filter.pod
b/filters/error/nbdkit-error-filter.pod
index bedd97924..49f21d10a 100644
--- a/filters/error/nbdkit-error-filter.pod
+++ b/filters/error/nbdkit-error-filter.pod
@@ -6,7 +6,7 @@ nbdkit-error-filter - inject errors for testing clients
 
  nbdkit --filter=error PLUGIN
      [error=EPERM|EIO|ENOMEM|EINVAL|ENOSPC|ESHUTDOWN]
-     [error-rate=10%|0.1]
+     [error-rate=10%|0.1|1:10]
      [error-file=/tmp/inject]
      [error-pread=...] [error-pread-rate=...] [error-pread-file=...]
      [error-pwrite=...] [error-pwrite-rate=...] [error-pwrite-file=...]
@@ -66,11 +66,13 @@ This parameter is optional and the default is C<EIO>
 
 =item B<error-rate=>0..1
 
+=item B<error-rate=>NB<:>M
+
 The rate of injected errors per NBD request.  This can be expressed as
-either a percentage between C<0%> and C<100%> or as a probability
-between C<0> and C<1>.  If C<0%> or C<0> is used then
no errors are
-ever injected, and if C<100%> or C<1> is used then all requests
return
-errors.
+a percentage between C<0%> and C<100%>, or as a probability between
+C<0> and C<1>, or as a ratio like C<1:10>.  If C<0%> or
C<0> is used
+then no errors are ever injected, and if C<100%> or C<1> is used
then
+all requests return errors.
 
 This parameter is optional and the default is C<0%>.
 B<Unless you set this, the filter will do nothing.>
diff --git a/filters/error/error.c b/filters/error/error.c
index ac4da8c16..08e19e3bd 100644
--- a/filters/error/error.c
+++ b/filters/error/error.c
@@ -130,21 +130,9 @@ static int
 parse_error_rate (const char *key, const char *value, double *retp)
 {
   double d;
-  int n;
 
-  if (sscanf (value, "%lg%n", &d, &n) == 1) {
-    if (strcmp (&value[n], "%") == 0) /* percentage */
-      d /= 100.0;
-    else if (strcmp (&value[n], "") == 0) /* probability */
-      ;
-    else
-      goto bad_parse;
-  }
-  else {
-  bad_parse:
-    nbdkit_error ("%s: could not parse rate '%s'", key,
value);
+  if (nbdkit_parse_probability (key, value, &d) == -1)
     return -1;
-  }
   if (d < 0 || d > 1) {
     nbdkit_error ("%s: rate out of range: '%s' parsed as %g",
key, value, d);
     return -1;
-- 
2.39.2
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 3/6] common/include: Add next_power_of_2 function
It takes a 64 bit integer and finds the next power of 2,
eg. next_power_of_2 (510) => 512 (2^9)
Taken from https://jameshfisher.com/2018/03/30/round-up-power-2/
with some fixes.
Thanks: Eric Blake
---
 common/include/ispowerof2.h      |  9 +++++++++
 common/include/test-ispowerof2.c | 15 +++++++++++++++
 2 files changed, 24 insertions(+)
diff --git a/common/include/ispowerof2.h b/common/include/ispowerof2.h
index f067caf07..a4cb52de3 100644
--- a/common/include/ispowerof2.h
+++ b/common/include/ispowerof2.h
@@ -59,4 +59,13 @@ log_2_bits (unsigned long v)
   return SIZEOF_LONG*8 - __builtin_clzl (v) - 1;
 }
 
+/* Round up to next power of 2.
+ * https://jameshfisher.com/2018/03/30/round-up-power-2/
+ */
+static inline uint64_t
+next_power_of_2 (uint64_t x)
+{
+  return x <= 1 ? 1 : UINT64_C(1) << (64 - __builtin_clzll (x-1));
+}
+
 #endif /* NBDKIT_ISPOWEROF2_H */
diff --git a/common/include/test-ispowerof2.c b/common/include/test-ispowerof2.c
index 9620192f0..1221ac09c 100644
--- a/common/include/test-ispowerof2.c
+++ b/common/include/test-ispowerof2.c
@@ -68,5 +68,20 @@ main (void)
   assert (log_2_bits (0x8000000000000000) == 63);
 #endif
 
+  /* Test next power of 2. */
+  assert (next_power_of_2 (0) == 1);
+  assert (next_power_of_2 (1) == 1);
+  assert (next_power_of_2 (3) == 4);
+  assert (next_power_of_2 (8) == 8);
+  assert (next_power_of_2 (9) == 16);
+  assert (next_power_of_2 (0xffff) == 0x10000);
+  assert (next_power_of_2 (0x10000) == 0x10000);
+  assert (next_power_of_2 (UINT64_C ( 0xffffffff)) == 0x100000000);
+  assert (next_power_of_2 (UINT64_C (0x100000000)) == 0x100000000);
+  assert (next_power_of_2 (UINT64_C (0x200000001)) == 0x400000000);
+  assert (next_power_of_2 (UINT64_C (0x6ffffffff)) == 0x800000000);
+  assert (next_power_of_2 (UINT64_C (0x700000001)) == 0x800000000);
+  assert (next_power_of_2 (UINT64_C (0x800000000)) == 0x800000000);
+
   exit (EXIT_SUCCESS);
 }
-- 
2.39.2
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 4/6] common/include: Make log_2_bits work on 64 bit ints
Previously it only worked for 64 bit ints on 64 bit platforms, but we
can easily adjust the function to work on any platform.
---
 common/include/ispowerof2.h      | 4 ++--
 common/include/test-ispowerof2.c | 6 ++----
 2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/common/include/ispowerof2.h b/common/include/ispowerof2.h
index a4cb52de3..8f56c8dbf 100644
--- a/common/include/ispowerof2.h
+++ b/common/include/ispowerof2.h
@@ -54,9 +54,9 @@ is_power_of_2 (unsigned long v)
  * __builtin_clzl is available in GCC and clang.
  */
 static inline int
-log_2_bits (unsigned long v)
+log_2_bits (uint64_t v)
 {
-  return SIZEOF_LONG*8 - __builtin_clzl (v) - 1;
+  return 64 - __builtin_clzll (v) - 1;
 }
 
 /* Round up to next power of 2.
diff --git a/common/include/test-ispowerof2.c b/common/include/test-ispowerof2.c
index 1221ac09c..09d248889 100644
--- a/common/include/test-ispowerof2.c
+++ b/common/include/test-ispowerof2.c
@@ -63,10 +63,8 @@ main (void)
   assert (log_2_bits (512) == 9);
   assert (log_2_bits (4096) == 12);
   assert (log_2_bits (0x80000000) == 31);
-#if SIZEOF_LONG == 8
-  assert (log_2_bits (0x100000000) == 32);
-  assert (log_2_bits (0x8000000000000000) == 63);
-#endif
+  assert (log_2_bits (UINT64_C (0x100000000)) == 32);
+  assert (log_2_bits (UINT64_C (0x8000000000000000)) == 63);
 
   /* Test next power of 2. */
   assert (next_power_of_2 (0) == 1);
-- 
2.39.2
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 5/6] New plugin: ones
Returns a fully allocated disk containing all 0xff (all ones), or
another byte of your choice.
---
 plugins/data/nbdkit-data-plugin.pod           |   1 +
 plugins/full/nbdkit-full-plugin.pod           |   1 +
 plugins/null/nbdkit-null-plugin.pod           |   1 +
 plugins/ones/nbdkit-ones-plugin.pod           |  77 +++++++
 plugins/pattern/nbdkit-pattern-plugin.pod     |   4 +-
 plugins/random/nbdkit-random-plugin.pod       |   1 +
 .../nbdkit-sparse-random-plugin.pod           |   1 +
 plugins/zero/nbdkit-zero-plugin.pod           |   1 +
 configure.ac                                  |   2 +
 plugins/ones/Makefile.am                      |  70 +++++++
 tests/Makefile.am                             |   4 +
 plugins/ones/ones.c                           | 191 ++++++++++++++++++
 tests/test-ones.sh                            |  53 +++++
 13 files changed, 406 insertions(+), 1 deletion(-)
diff --git a/plugins/data/nbdkit-data-plugin.pod
b/plugins/data/nbdkit-data-plugin.pod
index cec1af7f7..46daa859e 100644
--- a/plugins/data/nbdkit-data-plugin.pod
+++ b/plugins/data/nbdkit-data-plugin.pod
@@ -404,6 +404,7 @@ L<nbdkit-plugin(3)>,
 L<nbdkit-info-plugin(1)>,
 L<nbdkit-memory-plugin(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-partitioning-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-random-plugin(1)>,
diff --git a/plugins/full/nbdkit-full-plugin.pod
b/plugins/full/nbdkit-full-plugin.pod
index 5664aa2da..18c86a327 100644
--- a/plugins/full/nbdkit-full-plugin.pod
+++ b/plugins/full/nbdkit-full-plugin.pod
@@ -65,6 +65,7 @@ L<nbdkit(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-error-filter(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-random-plugin(1)>.
 L<nbdkit-sparse-random-plugin(1)>,
diff --git a/plugins/null/nbdkit-null-plugin.pod
b/plugins/null/nbdkit-null-plugin.pod
index 27fe68746..c71dc2c6f 100644
--- a/plugins/null/nbdkit-null-plugin.pod
+++ b/plugins/null/nbdkit-null-plugin.pod
@@ -55,6 +55,7 @@ C<nbdkit-null-plugin> first appeared in nbdkit 1.2.
 L<nbdkit(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-full-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-random-plugin(1)>.
 L<nbdkit-sparse-random-plugin(1)>,
diff --git a/plugins/ones/nbdkit-ones-plugin.pod
b/plugins/ones/nbdkit-ones-plugin.pod
new file mode 100644
index 000000000..50ccfe272
--- /dev/null
+++ b/plugins/ones/nbdkit-ones-plugin.pod
@@ -0,0 +1,77 @@
+=head1 NAME
+
+nbdkit-ones-plugin - nbdkit plugin filled with repeated 0xff or other bytes
+
+=head1 SYNOPSIS
+
+ nbdkit ones [[size=]SIZE] [byte=N]
+
+=head1 DESCRIPTION
+
+C<nbdkit-ones-plugin> is a plugin for L<nbdkit(1)>.  Any read
returns
+a repeating pattern of C<0xff> (all ones) bytes, or another repeating
+byte if you use the C<byte> parameter.  Everything written to the
+virtual device is discarded.
+
+Most NBD consumers will be very confused by this device.  It is mainly
+useful for testing.  L<nbdkit-data-plugin(1)> can be used to create a
+modifiable RAM disk initialized with fixed data.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<byte=>N
+
+Set the repeating byte to C<N>.  The default is C<0xff> (all ones).
+
+Instead of C<byte=0> it is more efficient to use
+L<nbdkit-null-plugin(1)>.
+
+=item [B<size=>]SIZE
+
+Specify the virtual size of the disk image.
+
+This parameter is optional.  If omitted then a zero-sized device is
+created.
+
+C<size=> is a magic config key and may be omitted in most cases.
+See L<nbdkit(1)/Magic parameters>.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$plugindir/nbdkit-ones-plugin.so>
+
+The plugin.
+
+Use C<nbdkit --dump-config> to find the location of C<$plugindir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-ones-plugin> first appeared in nbdkit 1.36.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>,
+L<nbdkit-data-plugin(1)>,
+L<nbdkit-full-plugin(1)>,
+L<nbdkit-null-plugin(1)>,
+L<nbdkit-pattern-plugin(1)>,
+L<nbdkit-random-plugin(1)>.
+L<nbdkit-sparse-random-plugin(1)>,
+L<nbdkit-zero-plugin(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright Red Hat
diff --git a/plugins/pattern/nbdkit-pattern-plugin.pod
b/plugins/pattern/nbdkit-pattern-plugin.pod
index c5d4f0d36..f4ae324ed 100644
--- a/plugins/pattern/nbdkit-pattern-plugin.pod
+++ b/plugins/pattern/nbdkit-pattern-plugin.pod
@@ -11,7 +11,8 @@ nbdkit-pattern-plugin - plugin to serve a fixed pattern of
data for testing
 C<nbdkit-pattern-plugin> is a plugin for L<nbdkit(1)> which serves
a
 fixed pattern of data, read only.  This is used for testing nbdkit
 filters and NBD clients.  To create test disks filled with other
-repeated patterns use L<nbdkit-data-plugin(1)> instead.
+repeated patterns use L<nbdkit-data-plugin(1)> or
+L<nbdkit-ones-plugin(1)> instead.
 
 The fixed pattern is the offset, as a 64 bit big endian integer, every
 8 bytes.  In hexadecimal this looks like:
@@ -88,6 +89,7 @@ L<nbdkit-plugin(3)>,
 L<nbdkit-data-plugin(1)>,
 L<nbdkit-full-plugin(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-offset-filter(1)>,
 L<nbdkit-random-plugin(1)>,
 L<nbdkit-sparse-random-plugin(1)>,
diff --git a/plugins/random/nbdkit-random-plugin.pod
b/plugins/random/nbdkit-random-plugin.pod
index 040d86bfc..0d6da8d5f 100644
--- a/plugins/random/nbdkit-random-plugin.pod
+++ b/plugins/random/nbdkit-random-plugin.pod
@@ -76,6 +76,7 @@ L<nbdkit-plugin(3)>,
 L<nbdkit-data-plugin(1)>,
 L<nbdkit-full-plugin(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-sparse-random-plugin(1)>,
 L<nbdkit-zero-plugin(1)>,
diff --git a/plugins/sparse-random/nbdkit-sparse-random-plugin.pod
b/plugins/sparse-random/nbdkit-sparse-random-plugin.pod
index 03e98ee16..5636e76c4 100644
--- a/plugins/sparse-random/nbdkit-sparse-random-plugin.pod
+++ b/plugins/sparse-random/nbdkit-sparse-random-plugin.pod
@@ -110,6 +110,7 @@ L<nbdkit-plugin(3)>,
 L<nbdkit-data-plugin(1)>,
 L<nbdkit-full-plugin(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-random-plugin(1)>,
 L<nbdkit-zero-plugin(1)>,
diff --git a/plugins/zero/nbdkit-zero-plugin.pod
b/plugins/zero/nbdkit-zero-plugin.pod
index 03544ad6e..2ac846027 100644
--- a/plugins/zero/nbdkit-zero-plugin.pod
+++ b/plugins/zero/nbdkit-zero-plugin.pod
@@ -37,6 +37,7 @@ L<nbdkit-plugin(3)>,
 L<nbdkit-data-plugin(1)>,
 L<nbdkit-full-plugin(1)>,
 L<nbdkit-null-plugin(1)>,
+L<nbdkit-ones-plugin(1)>,
 L<nbdkit-pattern-plugin(1)>,
 L<nbdkit-random-plugin(1)>,
 L<nbdkit-sparse-random-plugin(1)>.
diff --git a/configure.ac b/configure.ac
index dafddcc29..310de7ac1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -95,6 +95,7 @@ non_lang_plugins="\
         nbd \
         null \
         ondemand \
+        ones \
         partitioning \
         pattern \
         random \
@@ -1500,6 +1501,7 @@ AC_CONFIG_FILES([Makefile
                  plugins/null/Makefile
                  plugins/ocaml/Makefile
                  plugins/ondemand/Makefile
+                 plugins/ones/Makefile
                  plugins/partitioning/Makefile
                  plugins/pattern/Makefile
                  plugins/perl/Makefile
diff --git a/plugins/ones/Makefile.am b/plugins/ones/Makefile.am
new file mode 100644
index 000000000..9f8cbc4e2
--- /dev/null
+++ b/plugins/ones/Makefile.am
@@ -0,0 +1,70 @@
+# 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-ones-plugin.pod
+
+plugin_LTLIBRARIES = nbdkit-ones-plugin.la
+
+nbdkit_ones_plugin_la_SOURCES = \
+	ones.c \
+	$(top_srcdir)/include/nbdkit-plugin.h \
+	$(NULL)
+
+nbdkit_ones_plugin_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir)/include \
+	$(NULL)
+nbdkit_ones_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_ones_plugin_la_LIBADD = \
+	$(IMPORT_LIBRARY_ON_WINDOWS) \
+	$(NULL)
+nbdkit_ones_plugin_la_LDFLAGS = \
+	-module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \
+	$(NULL)
+if USE_LINKER_SCRIPT
+nbdkit_ones_plugin_la_LDFLAGS += \
+	-Wl,--version-script=$(top_srcdir)/plugins/plugins.syms
+endif
+
+if HAVE_POD
+
+man_MANS = nbdkit-ones-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-ones-plugin.1: nbdkit-ones-plugin.pod \
+		$(top_builddir)/podwrapper.pl
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3c8123f45..3ae13d660 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -955,6 +955,10 @@ EXTRA_DIST += \
 	test-ondemand-locking.sh \
 	$(NULL)
 
+# ones plugin test.
+TESTS += test-ones.sh
+EXTRA_DIST += test-ones.sh
+
 # partitioning plugin test.
 TESTS += \
 	test-partitioning1.sh \
diff --git a/plugins/ones/ones.c b/plugins/ones/ones.c
new file mode 100644
index 000000000..2ff1ed028
--- /dev/null
+++ b/plugins/ones/ones.c
@@ -0,0 +1,191 @@
+/* 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#define NBDKIT_API_VERSION 2
+
+#include <nbdkit-plugin.h>
+
+/* The size of disk in bytes (initialized by size=<SIZE> parameter). */
+static int64_t size = 0;
+
+/* Repeating byte. */
+static uint8_t byte = 0xff;
+
+static int
+ones_config (const char *key, const char *value)
+{
+  int64_t r;
+
+  if (strcmp (key, "size") == 0) {
+    r = nbdkit_parse_size (value);
+    if (r == -1)
+      return -1;
+    size = r;
+  }
+  else if (strcmp (key, "byte") == 0) {
+    if (nbdkit_parse_uint8_t ("byte", value, &byte) == -1)
+      return -1;
+  }
+  else {
+    nbdkit_error ("unknown parameter '%s'", key);
+    return -1;
+  }
+
+  return 0;
+}
+
+#define ones_config_help \
+  "byte=<BYTE>             Repeating byte to use (default:
0xff).\n" \
+  "size=<SIZE>             Size of the backing disk."
+
+/* Create the per-connection handle. */
+static void *
+ones_open (int readonly)
+{
+  return NBDKIT_HANDLE_NOT_NEEDED;
+}
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+/* Get the disk size. */
+static int64_t
+ones_get_size (void *handle)
+{
+  return size;
+}
+
+/* Serves the same data over multiple connections. */
+static int
+ones_can_multi_conn (void *handle)
+{
+  return 1;
+}
+
+/* Cache. */
+static int
+ones_can_cache (void *handle)
+{
+  /* Everything is already in memory, returning this without
+   * implementing .cache lets nbdkit do the correct no-op.
+   */
+  return NBDKIT_CACHE_NATIVE;
+}
+
+/* Read data. */
+static int
+ones_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
+            uint32_t flags)
+{
+  memset (buf, byte, count);
+  return 0;
+}
+
+/* Write data. */
+static int
+ones_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
+             uint32_t flags)
+{
+  /* nothing */
+  return 0;
+}
+
+/* Write zeroes. */
+static int
+ones_zero (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
+{
+  /* nothing */
+  return 0;
+}
+
+/* Flush is a no-op, so advertise native FUA support */
+static int
+ones_can_fua (void *handle)
+{
+  return NBDKIT_FUA_NATIVE;
+}
+
+/* Trim. */
+static int
+ones_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
+{
+  /* nothing */
+  return 0;
+}
+
+/* Nothing is persistent, so flush is trivially supported */
+static int
+ones_flush (void *handle, uint32_t flags)
+{
+  return 0;
+}
+
+/* Extents. */
+static int
+ones_extents (void *handle, uint32_t count, uint64_t offset, uint32_t flags,
+              struct nbdkit_extents *extents)
+{
+  /* Return a fully allocated data extent covering the entire disk. */
+  return nbdkit_add_extent (extents, 0, size, 0);
+}
+
+static struct nbdkit_plugin plugin = {
+  .name              = "ones",
+  .version           = PACKAGE_VERSION,
+  .config            = ones_config,
+  .config_help       = ones_config_help,
+  .magic_config_key  = "size",
+  .open              = ones_open,
+  .get_size          = ones_get_size,
+  .can_multi_conn    = ones_can_multi_conn,
+  .can_cache         = ones_can_cache,
+  .pread             = ones_pread,
+  .pwrite            = ones_pwrite,
+  .zero              = ones_zero,
+  .trim              = ones_trim,
+  .can_fua           = ones_can_fua,
+  .flush             = ones_flush,
+  .extents           = ones_extents,
+  /* In this plugin, errno is preserved properly along error return
+   * paths from failed system calls.
+   */
+  .errno_is_preserved = 1,
+};
+
+NBDKIT_REGISTER_PLUGIN (plugin)
diff --git a/tests/test-ones.sh b/tests/test-ones.sh
new file mode 100755
index 000000000..1a2b22caf
--- /dev/null
+++ b/tests/test-ones.sh
@@ -0,0 +1,53 @@
+#!/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.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin ones
+requires_nbdsh_uri
+requires_nbdinfo
+
+# Check the disk contains 0xff by default.
+nbdkit -U - ones 1024 --run '
+nbdsh -u "$uri" -c "assert (h.pread(1024,0) ==
b\"\\xff\"*1024)"
+'
+
+# Change the byte.
+nbdkit -U - ones 1024 byte=0x5a --run '
+nbdsh -u "$uri" -c "assert (h.pread(1024,0) ==
b\"\\x5a\"*1024)"
+'
+
+# Check the disk is fully allocated.
+nbdkit -U - ones 1G --run 'nbdinfo --map "$uri"' | \
+    grep -E '0[[:space:]]+1073741824[[:space:]]+0[[:space:]]+data'
-- 
2.39.2
Richard W.M. Jones
2023-May-17  10:06 UTC
[Libguestfs] [PATCH nbdkit v2 6/6] New filter: evil
This filter adds random data corruption when reading from the
underlying plugin.
---
 filters/error/nbdkit-error-filter.pod |   4 +
 filters/evil/nbdkit-evil-filter.pod   | 167 ++++++++++
 configure.ac                          |   2 +
 filters/evil/Makefile.am              |  77 +++++
 tests/Makefile.am                     |  18 ++
 filters/evil/evil.c                   | 422 ++++++++++++++++++++++++++
 tests/test-evil-cosmic.sh             |  77 +++++
 tests/test-evil-large-p.sh            |  55 ++++
 tests/test-evil-small-p.sh            |  57 ++++
 tests/test-evil-stuck-high-bits.sh    |  91 ++++++
 tests/test-evil-stuck-low-bits.sh     |  84 +++++
 tests/test-evil-stuck-wires.sh        |  85 ++++++
 12 files changed, 1139 insertions(+)
diff --git a/filters/error/nbdkit-error-filter.pod
b/filters/error/nbdkit-error-filter.pod
index 49f21d10a..91af7f713 100644
--- a/filters/error/nbdkit-error-filter.pod
+++ b/filters/error/nbdkit-error-filter.pod
@@ -25,6 +25,9 @@ All parameters are optional, but you should usually specify
one of the
 C<error-rate> or C<error-*-rate> parameters,
 B<otherwise this filter will do nothing>.
 
+L<nbdkit-evil-filter(1)> is a related filter that injects data
+corruption instead of errors.
+
 =head1 EXAMPLES
 
 Inject a low rate of errors randomly into the connection:
@@ -162,6 +165,7 @@ C<nbdkit-error-filter> first appeared in nbdkit 1.6.
 =head1 SEE ALSO
 
 L<nbdkit(1)>,
+L<nbdkit-evil-filter(1)>,
 L<nbdkit-file-plugin(1)>,
 L<nbdkit-full-plugin(1)>,
 L<nbdkit-retry-filter(1)>,
diff --git a/filters/evil/nbdkit-evil-filter.pod
b/filters/evil/nbdkit-evil-filter.pod
new file mode 100644
index 000000000..ff4010d33
--- /dev/null
+++ b/filters/evil/nbdkit-evil-filter.pod
@@ -0,0 +1,167 @@
+=head1 NAME
+
+nbdkit-evil-filter - add random data corruption to reads
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=evil PLUGIN [PLUGIN-ARGS...]
+        evil=[cosmic-rays|stuck-bits|stuck-wires]
+        [evil-probability=PROB] [evil-stuck-probability=PROB]
+        [evil-seed=SEED]
+
+=head1 DESCRIPTION
+
+nbdkit-evil-filter is a Byzantine filter for L<nbdkit(1)> that
+randomly corrupts data when reading from the underlying plugin.  This
+can be used for testing filesystem checksums.  Note that it does not
+change write operations, so the underlying plugin contains the correct
+data.
+
+L<nbdkit-error-filter(1)> is a related filter that injects hard errors
+into the NBD protocol.
+
+This filter has several modes, controlled using the C<evil=...>
+parameter.  These are:
+
+=over 4
+
+=item C<evil=cosmic-rays>
+
+Bits are flipped at random when reading data.  The probability that a
+bit is flipped is controlled using the C<evil-probability> parameter,
+defaulting to 1e-8 (on average 1 in every 100 million bits read is
+flipped).
+
+=item C<evil=stuck-bits>
+
+This is the default mode.
+
+Fixed bits in the backing file are stuck randomly high or low.  The
+C<evil-probability> parameter controls the expected probability that a
+particular bit is stuck, defaulting in this mode to 1e-8 (1 in 100
+million).  C<evil-stuck-probability> controls the probability that a
+stuck bit is read as its stuck value or its correct value, defaulting
+to 100% (always read as a stuck bit).
+
+=item C<evil=stuck-wires>
+
+This is similar to C<stuck-bits> but instead of simulating bad backing
+data, it simulates stuck wires along the data path (eg. in a
+register).  The difference is that when reading, the stuck bit always
+happens at the same position in the packet of data being read,
+regardless of where on the underlying disk it is being read from.
+C<evil-probability> and controls the probability of a stuck wire,
+defaulting in this mode to 1e-6 (1 in 1 million).
+C<evil-stuck-probability> controls the probability that a stuck bit is
+read as its stuck value or its correct value, defaulting to 100%
+(always read as a stuck bit).
+
+=back
+
+=head1 EXAMPLES
+
+Add some stuck bits to the backing file at random:
+
+ nbdkit --filter=evil file disk.img
+
+Cosmic rays will flip (on average) one in every 100 million bits
+copied from the backing file over NBD:
+
+ nbdkit --filter=evil file disk.img evil=cosmic-rays \
+        --run 'nbdcopy $uri output.img'
+
+=head1 NOTES
+
+=head2 Extents
+
+Plugins can be sparse.  This filter only corrupts bits in non-sparse
+parts of the backing disk and it leaves sparse regions unchanged
+(which is realistic behaviour).  If you wish to use this filter to
+corrupt sparse regions, then combine this filter with
+L<nbdkit-noextents-filter(1)>.  For example:
+
+ nbdkit --filter=evil --filter=noextents memory 1G
+
+=head2 Probability limited to [ 1e-12 .. 1/8 ]
+
+The current implementation limits probabilities to the range
+S<[ 1e-12 .. 1/8 ]>.  Values below this range are treated the same as
+0%.  Values above this range are treated the same as 100%.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<evil=cosmic-rays>
+
+=item B<evil=stuck-bits>
+
+=item B<evil=stuck-wires>
+
+Select the mode of evil.  See the L</DESCRIPTION> above.  The default
+is C<stuck-bits>.
+
+=item B<evil-probability=>N
+
+=item B<evil-probability=>NB<:>M
+
+=item B<evil-probability=>NB<%>
+
+Set the probability for the mode.  You can either use a floating point
+number between 0 and 1, eg. C<evil-probability=0.001> or
+C<evil-probability=1e-6>.  Or you can write it as N in M, eg.
+C<evil-probability=1:1000000> or C<evil-probability=3.33:100000>. 
Or
+you can write this as a percentage, eg. C<evil-probability=1%>.
+
+The default probability depends on the mode.
+
+=item B<evil-seed=>SEED
+
+To make runs repeatable, use this to set a seed for the random number
+generator.  The default is to choose a seed at random.
+
+=item B<evil-stuck-probability=>N
+
+=item B<evil-stuck-probability=>NB<:>M
+
+=item B<evil-stuck-probability=>NB<%>
+
+For the "stuck-*" modes, the probability that when reading a stuck
bit
+you will read the stuck bit or the correct value.  This defaults to 1
+(ie. 100%) which means the bit is always stuck.  Setting it to 0.5 for
+example will mean that half the time the bit appears stuck and half
+the time you see the correct value.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$filterdir/nbdkit-evil-filter.so>
+
+The filter.
+
+Use C<nbdkit --dump-config> to find the location of C<$filterdir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-pause-filter> first appeared in nbdkit 1.36.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-filter(3)>,
+L<nbdkit-delay-filter(1)>,
+L<nbdkit-noextents-filter(1)>,
+L<nbdkit-error-filter(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright Red Hat
diff --git a/configure.ac b/configure.ac
index 310de7ac1..18431958a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -124,6 +124,7 @@ filters="\
         exportname \
         ext2 \
         extentlist \
+        evil \
         fua \
         gzip \
         ip \
@@ -1534,6 +1535,7 @@ AC_CONFIG_FILES([Makefile
                  filters/exportname/Makefile
                  filters/ext2/Makefile
                  filters/extentlist/Makefile
+                 filters/evil/Makefile
                  filters/fua/Makefile
                  filters/gzip/Makefile
                  filters/ip/Makefile
diff --git a/filters/evil/Makefile.am b/filters/evil/Makefile.am
new file mode 100644
index 000000000..ac6337428
--- /dev/null
+++ b/filters/evil/Makefile.am
@@ -0,0 +1,77 @@
+# 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-evil-filter.pod
+
+# Relies on a Unix domain socket.
+if !IS_WINDOWS
+
+filter_LTLIBRARIES = nbdkit-evil-filter.la
+
+nbdkit_evil_filter_la_SOURCES = \
+	evil.c \
+	$(top_srcdir)/include/nbdkit-filter.h \
+	$(NULL)
+
+nbdkit_evil_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+nbdkit_evil_filter_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_evil_filter_la_LIBADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(IMPORT_LIBRARY_ON_WINDOWS) \
+	$(NULL)
+nbdkit_evil_filter_la_LDFLAGS = \
+	-module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \
+	$(NULL)
+if USE_LINKER_SCRIPT
+nbdkit_evil_filter_la_LDFLAGS += \
+	-Wl,--version-script=$(top_srcdir)/filters/filters.syms
+endif
+
+if HAVE_POD
+
+man_MANS = nbdkit-evil-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-evil-filter.1: nbdkit-evil-filter.pod \
+		$(top_builddir)/podwrapper.pl
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
+endif !IS_WINDOWS
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3ae13d660..9233c371e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1598,6 +1598,24 @@ EXTRA_DIST += \
 	test-error-triggered.sh \
 	$(NULL)
 
+# evil filter test.
+TESTS += \
+	test-evil-cosmic.sh \
+	test-evil-large-p.sh \
+	test-evil-small-p.sh \
+	test-evil-stuck-high-bits.sh \
+	test-evil-stuck-low-bits.sh \
+	test-evil-stuck-wires.sh \
+	$(NULL)
+EXTRA_DIST += \
+	test-evil-cosmic.sh \
+	test-evil-large-p.sh \
+	test-evil-small-p.sh \
+	test-evil-stuck-high-bits.sh \
+	test-evil-stuck-low-bits.sh \
+	test-evil-stuck-wires.sh \
+	$(NULL)
+
 # exitlast filter test.
 TESTS += test-exitlast.sh
 EXTRA_DIST += test-exitlast.sh
diff --git a/filters/evil/evil.c b/filters/evil/evil.c
new file mode 100644
index 000000000..a6e0a9ed2
--- /dev/null
+++ b/filters/evil/evil.c
@@ -0,0 +1,422 @@
+/* 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+#include <nbdkit-filter.h>
+
+#include "minmax.h"
+#include "ispowerof2.h"
+#include "random.h"
+
+enum mode {
+  COSMIC_RAYS,
+  STUCK_BITS,
+  STUCK_WIRES,
+};
+static const char *evil_mode_to_string (enum mode);
+
+static enum mode evil_mode = STUCK_BITS;
+static double evil_probability = -1; /* default depends on mode */
+static double evil_stuck_probability = 1.0;
+static uint32_t evil_seed;
+
+/* Probabilities < ? are treated as zero to avoid both divide by zero
+ * problems and potentially exploding values in calculations.
+ */
+#define EPSILON 1e-12
+
+/* Probabilities > MAXP are treated as 100%.  This is because our
+ * algorithm below can corrupt at most 1 bit per byte and doesn't make
+ * progress otherwise.
+ */
+#define MAXP (1.0/8.0)
+
+static void
+evil_load (void)
+{
+  evil_seed = time (NULL);
+}
+
+static int
+evil_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
+             const char *key, const char *value)
+{
+  if (strcmp (key, "evil") == 0 || strcmp (key,
"evil-mode") == 0) {
+    if (strcmp (value, "cosmic-rays") == 0 ||
+        strcmp (value, "cosmic") == 0) {
+      evil_mode = COSMIC_RAYS;
+      return 0;
+    }
+    else if (strcmp (value, "stuck-bits") == 0 ||
+             strcmp (value, "stuck-bit") == 0 ||
+             strcmp (value, "stuck") == 0) {
+      evil_mode = STUCK_BITS;
+      return 0;
+    }
+    else if (strcmp (value, "stuck-wires") == 0 ||
+             strcmp (value, "stuck-wire") == 0) {
+      evil_mode = STUCK_WIRES;
+      return 0;
+    }
+    else {
+      nbdkit_error ("evil: unknown mode: %s", value);
+      return -1;
+    }
+  }
+  else if (strcmp (key, "evil-probability") == 0) {
+    if (nbdkit_parse_probability ("evil-probability", value,
+                                  &evil_probability) == -1)
+      return -1;
+    if (evil_probability < 0 || evil_probability > 1) {
+      nbdkit_error ("%s: probability out of range, should be [0..1]",
key);
+      return -1;
+    }
+    return 0;
+  }
+  else if (strcmp (key, "evil-stuck-probability") == 0) {
+    if (nbdkit_parse_probability ("evil-stuck-probability", value,
+                                  &evil_stuck_probability) == -1)
+      return -1;
+    if (evil_stuck_probability < 0 || evil_stuck_probability > 1) {
+      nbdkit_error ("%s: probability out of range, should be [0..1]",
key);
+      return -1;
+    }
+    return 0;
+  }
+  else if (strcmp (key, "evil-seed") == 0) {
+    if (nbdkit_parse_uint32_t ("evil-seed", value, &evil_seed) ==
-1)
+      return -1;
+    return 0;
+  }
+  else
+    return next (nxdata, key, value);
+}
+
+static int
+evil_config_complete (nbdkit_next_config_complete *next,
+                      nbdkit_backend *nxdata)
+{
+  if (evil_probability < 0) {
+    /* Choose default probability based on the chosen mode. */
+    switch (evil_mode) {
+    case COSMIC_RAYS:
+    case STUCK_BITS:
+      evil_probability = 1e-8;
+      break;
+    case STUCK_WIRES:
+      evil_probability = 1e-6;
+    }
+  }
+
+  return next (nxdata);
+}
+
+#define evil_config_help \
+  "evil=cosmic-rays|stuck-bits|stuck-wires\n" \
+  "                               Set the mode (default:
cosmic-rays).\n" \
+  "evil-probability=PROB          Probability of flipped or stuck
bit.\n" \
+  "evil-seed=SEED                 Random number seed.\n" \
+  "evil-stuck-probability=PROB    Probability of stuck bit being
stuck."
+
+/* This is the heart of the algorithm, the function which corrupts
+ * the buffer after reading it from the plugin.
+ *
+ * The observation is that if we have a block of (eg) size 10**6 bits
+ * and our probability of finding a corrupt bit is (eg) 1/10**4, then
+ * we expect approximately 100 bits in the block to be corrupted.
+ *
+ * For stuck bits we want the corrupted bits to be the same on each
+ * access, either relative to the backing disk (STUCK_BITS) or to the
+ * request (STUCK_WIRES).
+ *
+ * Instead of creating an expensive bitmap ahead of time covering the
+ * whole disk, we can use the random number generator with a fixed
+ * seed derived from the offset of the start of the block.  We can
+ * then choose a random number uniformly in the range [0..2*(1/P)] (in
+ * the example [0..2*10**4]) as the distance to the next corrupt bit.
+ * We jump forwards, corrupt that bit, and repeat until we reach the
+ * end of the block.
+ *
+ * "Corrupted" in this case can mean flipped by cosmic rays or stuck,
+ * depending on the filter mode.
+ *
+ * On average this will choose the right number of bits in the block.
+ * (Although their distribution will be suboptimal.  In a uniform
+ * distribution it should be possible for two corrupted bits to be
+ * greater than 2*(1/P) apart, but the above algorithm would not do
+ * this.  In practice this probably doesn't matter.)
+ *
+ * Note that "block" != "buffer", especially in the
STUCK_BITS mode.
+ * We iterate over blocks as above, but only corrupt a bit when it
+ * happens to coincide with the buffer we have just read.
+ *
+ * We choose the block size adaptively so that at least 100 bits in
+ * the block will be corrupted.  The block size must be a power of 2.
+ * The block size thus depends on the probability.
+ */
+enum corruption_type { FLIP, STUCK };
+
+static uint64_t block_size;     /* in bytes */
+static struct random_state state; /* only used for cosmic-rays */
+
+static int
+evil_thread_model (void)
+{
+  switch (evil_mode) {
+  case COSMIC_RAYS:
+    /* Because cosmic-rays uses the global random state we need to
+     * tighten the thread model.
+     */
+    return NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS;
+
+  case STUCK_BITS:
+  case STUCK_WIRES:
+    return NBDKIT_THREAD_MODEL_PARALLEL;
+  }
+  abort ();
+}
+
+static int
+evil_get_ready (int thread_model)
+{
+  switch (evil_mode) {
+  case COSMIC_RAYS:
+    xsrandom ((uint64_t) evil_seed, &state);
+    break;
+
+  case STUCK_BITS:
+  case STUCK_WIRES:
+    ;
+  }
+
+  /* Choose the block size based on the probability, so that at least
+   * 100 bits are expected to be corrupted in the block.  Block size
+   * must be a power of 2.
+   *
+   * Example: P = 1e-4
+   *          => ideal block_size = 100 / 1e-4 = 1e6 (bits) = 1e6 / 8
(bytes)
+   *          => next power of 2 block_size = 131072 = 2**17
+   *          => expected bits per block = ~104
+   */
+  if (evil_probability < EPSILON || evil_probability > MAXP)
+    block_size = 1024*1024;     /* unused so value doesn't matter */
+  else
+    block_size = next_power_of_2 ((uint64_t) (100. / evil_probability) / 8);
+
+  nbdkit_debug ("evil: mode: %s, P: %lg, seed: %" PRIu32,
+                evil_mode_to_string (evil_mode),
+                evil_probability, evil_seed);
+  nbdkit_debug ("evil: block_size: %" PRIu64 " (2**%d)",
+                block_size, log_2_bits (block_size));
+  nbdkit_debug ("evil: expected bits per block: %g",
+                8 * block_size * evil_probability);
+
+  return 0;
+}
+
+static void corrupt_all_bits (uint8_t *buf, uint32_t count,
+                              struct random_state *rs,
+                              enum corruption_type ct);
+static uint8_t corrupt_one_bit (uint8_t byte, unsigned bit,
+                                uint64_t rand, enum corruption_type ct);
+
+static void
+corrupt_buffer (uint8_t *buf, uint32_t count, uint64_t offset_in_block,
+                struct random_state *rs, enum corruption_type ct)
+{
+  /* No corruption, and avoids a divide by zero below. */
+  if (evil_probability < EPSILON) return;
+
+  /* 100% corruption, avoids lack of progress in the loop below. */
+  if (evil_probability > MAXP) {
+    corrupt_all_bits (buf, count, rs, ct);
+    return;
+  }
+
+  uint64_t offs, intvl, i, rand;
+  const uint64_t invp2 = (uint64_t) (2.0 / evil_probability);
+
+  assert ((offset_in_block & ~(block_size-1)) == 0);
+
+  /* Iterate over the whole block from the start. */
+  for (offs = 0; offs < offset_in_block + count; ) {
+    /* Choose the length of the interval to the next corrupted bit, by
+     * picking a random number in [0..2*(1/P)].
+     *
+     * Remember this is in bits!
+     */
+    intvl = xrandom (rs) % invp2;
+
+    /* Consume one more random state.  We may or may not use this.
+     * But we need to always consume two random states per iteration
+     * to make the output predictable.
+     */
+    rand = xrandom (rs);
+
+    /* Adjust offs to that byte. */
+    offs += intvl / 8;
+
+    /* If we have gone past the end of buffer, stop. */
+    if (offs >= offset_in_block + count) break;
+
+    /* If the current offs lies within the buffer, corrupt a bit. */
+    if (offs >= offset_in_block) {
+      i = offs - offset_in_block;
+      assert (i < count);
+      buf[i] = corrupt_one_bit (buf[i], intvl & 7, rand, ct);
+    }
+  }
+}
+
+static void
+corrupt_all_bits (uint8_t *buf, uint32_t count,
+                  struct random_state *rs, enum corruption_type ct)
+{
+  size_t i;
+  unsigned bit;
+  uint64_t rand;
+
+  /* This is used when MAXP < P <= 100%.  We treat it the same as 100%
+   * and corrupt all bits.
+   */
+  for (i = 0; i < count; ++i) {
+    for (bit = 0; bit < 8; ++bit) {
+      rand = xrandom (rs);
+      buf[i] = corrupt_one_bit (buf[i], bit, rand, ct);
+    }
+  }
+}
+
+static uint8_t
+corrupt_one_bit (uint8_t byte, unsigned bit,
+                 uint64_t rand, enum corruption_type ct)
+{
+  const unsigned mask = 1 << bit;
+
+  switch (ct) {
+  case FLIP:
+    byte ^= mask;
+    break;
+  case STUCK:
+    rand &= 0xffffffff;
+    if (evil_stuck_probability * 0x100000000 > rand) {
+      if (rand & 1) /* stuck high or low? */
+        byte |= mask;
+      else
+        byte &= ~mask;
+    }
+  }
+  return byte;
+}
+
+/* Read data. */
+static int
+evil_pread (nbdkit_next *next,
+            void *handle, void *buf, uint32_t count, uint64_t offset,
+            uint32_t flags, int *err)
+{
+  uint64_t seed, bstart, len;
+  struct random_state local_state;
+
+  if (next->pread (next, buf, count, offset, flags, err) == -1)
+    return -1;
+
+  switch (evil_mode) {
+  case COSMIC_RAYS:
+    /* Use the global random state because we want to flip bits at random. */
+    corrupt_buffer (buf, count, 0, &state, FLIP);
+    break;
+
+  case STUCK_BITS:
+    /* Split the request to align with blocks. */
+    bstart = offset & ~(block_size-1);
+    while (count > 0) {
+      /* Set the seed so we corrupt the same bits relative to the offset. */
+      seed = (int64_t) evil_seed + bstart;
+      xsrandom (seed, &local_state);
+      /* If the buffer straddles two blocks, shorten to just the part
+       * inside the first block.
+       */
+      len = MIN (count, bstart + block_size - offset);
+      corrupt_buffer (buf, len, offset - bstart, &local_state, STUCK);
+      bstart += block_size;
+      offset += len;
+      buf += len;
+      count -= len;
+    }
+    break;
+
+  case STUCK_WIRES:
+    /* Set the seed so we corrupt the same bits in every request. */
+    seed = (int64_t) evil_seed;
+    xsrandom (seed, &local_state);
+    corrupt_buffer (buf, count, 0, &local_state, STUCK);
+    break;
+  }
+
+  return 0;
+}
+
+static const char *
+evil_mode_to_string (enum mode mode)
+{
+  switch (mode) {
+  case COSMIC_RAYS: return "cosmic-rays";
+  case STUCK_BITS:  return "stuck-bits";
+  case STUCK_WIRES: return "stuck-wires";
+  }
+  abort ();
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "evil",
+  .longname          = "nbdkit evil filter",
+  .load              = evil_load,
+  .config            = evil_config,
+  .config_complete   = evil_config_complete,
+  .config_help       = evil_config_help,
+  .thread_model      = evil_thread_model,
+  .get_ready         = evil_get_ready,
+  .pread             = evil_pread,
+};
+
+NBDKIT_REGISTER_FILTER (filter)
diff --git a/tests/test-evil-cosmic.sh b/tests/test-evil-cosmic.sh
new file mode 100755
index 000000000..2e5e6ab24
--- /dev/null
+++ b/tests/test-evil-cosmic.sh
@@ -0,0 +1,77 @@
+#!/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 evil filter with cosmic rays.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin null
+requires_filter evil
+requires_filter noextents
+requires nbdcopy --version
+requires $PYTHON --version
+
+f="test-evil-cosmic.out"
+rm -f $f
+cleanup_fn rm -f $f
+
+# 80 million zero bits in the backing disk, and the filter will
+# randomly flip (ie. set high) 1 in 800,000 bits, or about 100.
+
+# XXX Actually the number of set bits clusters around 80.  There could
+# be a mistake in my calculations or the interval algorithm we use
+# might be biased.
+
+export f
+nbdkit -U - null 10000000 \
+       --filter=evil --filter=noextents \
+       evil=cosmic-rays evil-probability=1/800000 \
+       --run 'nbdcopy "$uri" $f'
+
+# Count the number of bits set in the output file.  Easier to use
+# Python here ...
+
+$PYTHON -c '
+import os
+fh = open(os.environ["f"], "rb")
+buf = bytearray(fh.read())
+r = 0
+for b in buf:
+    if b != 0:
+        r += bin(b).count("1")
+
+print("non-zero bits: %d" % r)
+
+assert(r > 20 and r < 180)
+'
diff --git a/tests/test-evil-large-p.sh b/tests/test-evil-large-p.sh
new file mode 100755
index 000000000..47d558678
--- /dev/null
+++ b/tests/test-evil-large-p.sh
@@ -0,0 +1,55 @@
+#!/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 evil filter with large probabilities.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin null
+requires_filter evil
+requires_filter noextents
+requires nbdcopy --version
+
+# This is the largest probability we support.
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=1/8 \
+       --run 'nbdcopy "$uri" null:'
+
+# Anything larger is treated as 100%.
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=0.5 \
+       --run 'nbdcopy "$uri" null:'
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=1.0 \
+       --run 'nbdcopy "$uri" null:'
diff --git a/tests/test-evil-small-p.sh b/tests/test-evil-small-p.sh
new file mode 100755
index 000000000..377ef579b
--- /dev/null
+++ b/tests/test-evil-small-p.sh
@@ -0,0 +1,57 @@
+#!/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 evil filter with small probabilities.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin null
+requires_filter evil
+requires_filter noextents
+requires nbdcopy --version
+
+# Check absence of divide by zero errors.
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=0 \
+       --run 'nbdcopy "$uri" null:'
+
+# Smallest valid probability.
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=1e-12 \
+       --run 'nbdcopy "$uri" null:'
+
+# Should be treated same as P = 0.
+nbdkit -U - -fv null 1M \
+       --filter=evil --filter=noextents evil-probability=1e-13 \
+       --run 'nbdcopy "$uri" null:'
diff --git a/tests/test-evil-stuck-high-bits.sh
b/tests/test-evil-stuck-high-bits.sh
new file mode 100755
index 000000000..7af1bfdc8
--- /dev/null
+++ b/tests/test-evil-stuck-high-bits.sh
@@ -0,0 +1,91 @@
+#!/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 evil filter in the default mode ("stuck-bits").
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin null
+requires_filter evil
+requires_filter noextents
+requires_nbdsh_uri
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=evil-stuck-high-bits.pid
+files="$sock $pidfile"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run nbdkit with the evil filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=evil --filter=noextents \
+             null 1G evil-probability=1/800000
+
+# Since 1 in 800,000 bits are stuck (on average), for every 100,000
+# bytes that we read we expect about 1 stuck bit.  Note however that
+# bits are stuck randomly low or high, and against the null filter you
+# cannot see a stuck low bit, so in fact we expect to see only 1 stuck
+# bit per 200,000 bytes.
+#
+# There is a separate test for stuck low bits (test-evil-stuck-low-bits.sh).
+#
+# Also stuck bits should be consistent across reads.
+
+nbdsh -u "nbd+unix://?socket=$sock" \
+      -c - <<EOF
+def count_bits(buf):
+    r = 0
+    for i in range(0, len(buf)-1):
+        if buf[i] != 0:
+            r += bin(buf[i]).count("1")
+    return r
+
+def find_bit(buf):
+    for i in range(0, len(buf)-1):
+        if buf[i] != 0:
+            return i
+    return 0
+
+# Expect about 50 stuck-high bits.
+buf = h.pread(10000000, 0)
+bits = count_bits(buf)
+print("stuck high bits: %d (expected 50)" % bits)
+assert(bits > 20 and bits < 80)
+
+# If we read subsets they should match the contents of the buffer.
+i = find_bit(buf)
+buf1 = h.pread(1000, i)
+assert(buf1 == buf[i:i+1000])
+
+EOF
diff --git a/tests/test-evil-stuck-low-bits.sh
b/tests/test-evil-stuck-low-bits.sh
new file mode 100755
index 000000000..6f90b21ba
--- /dev/null
+++ b/tests/test-evil-stuck-low-bits.sh
@@ -0,0 +1,84 @@
+#!/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 evil filter in the default mode ("stuck-bits").
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin ones
+requires_filter evil
+requires_nbdsh_uri
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=evil-stuck-low-bits.pid
+files="$sock $pidfile"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run nbdkit with the evil filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=evil \
+             ones 1G evil-probability=1/800000
+
+# See description in test-evil-stuck-high-bits.sh.  This test uses the
+# ones plugin to test for stuck low bits.  The other parameters are
+# the same.
+
+nbdsh -u "nbd+unix://?socket=$sock" \
+      -c - <<EOF
+def count_bits(buf):
+    r = 0
+    for i in range(0, len(buf)-1):
+        if buf[i] != 0xff:
+            r += 8 - bin(buf[i]).count("1")
+    return r
+
+def find_bit(buf):
+    for i in range(0, len(buf)-1):
+        if buf[i] != 0xff:
+            return i
+    return 0
+
+# Expect about 50 stuck-low bits.
+buf = h.pread(10000000, 32*1024*1024)
+bits = count_bits(buf)
+print("stuck low bits: %d (expected 50)" % bits)
+assert(bits > 20 and bits < 80)
+
+# If we read subsets they should match the contents of the buffer.
+i = find_bit(buf)
+buf1 = h.pread(1000, 32*1024*1024 + i)
+assert(buf1 == buf[i:i+1000])
+
+EOF
diff --git a/tests/test-evil-stuck-wires.sh b/tests/test-evil-stuck-wires.sh
new file mode 100755
index 000000000..262b02049
--- /dev/null
+++ b/tests/test-evil-stuck-wires.sh
@@ -0,0 +1,85 @@
+#!/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 evil filter in stuck-wires mode.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin null
+requires_filter evil
+requires_filter noextents
+requires_nbdsh_uri
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=evil-stuck-wires.pid
+files="$sock $pidfile"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run nbdkit with the evil filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=evil --filter=noextents \
+             null 1G evil=stuck-wires evil-probability=1/10000
+
+# Reads from the filter should have 1:10,000 bits stuck high or low.
+# However we don't see stuck low bits since we are always reading
+# zeroes, so we only expect about 1:20,000 bits stuck high.
+#
+# If we read 10,000,000 bytes (80,000,000 bits) we would expect about
+# 4000 stuck bits.
+#
+# No matter where we read from the pattern of stuck bits should be the
+# same (stuck wires, not backing bits).
+
+nbdsh -u "nbd+unix://?socket=$sock" \
+      -c - <<EOF
+def count_bits(buf):
+    r = 0
+    for i in range(0, len(buf)-1):
+        if buf[i] != 0:
+            r += bin(buf[i]).count("1")
+    return r
+
+buf1 = h.pread(10000000, 0)
+bits = count_bits(buf1)
+print("stuck high bits: %d (expected 4000)" % bits)
+assert(bits > 3000 and bits < 5000)
+
+# These buffers should be identical.
+buf2 = h.pread(10000000, 1024)
+buf3 = h.pread(10000000, 32*1024*1024 - 9999)
+assert(buf1 == buf2)
+assert(buf1 == buf3)
+
+EOF
-- 
2.39.2