From b4d42fa29b78e4d964f166d3bc9457621c15bad6 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 26 Jul 2021 19:44:01 +0100 Subject: [PATCH] More efficient cache and cow filters. Add nbdkit-cow-filter cow-on-read option. Add nbdkit-cache-filter cache-on-read=/PATH. Add nbdkit-cache-filter cache-min-block-size option. Add nbdkit-delay-filter delay-open and delay-close options. Reduce verbosity of debugging from virt-v2v. Miscellaneous bugfixes resolves: rhbz#1950632 --- ...l_shutdown-when-unloading-the-plugin.patch | 4 +- ...nding-by-only-ignoring-caml_stat_all.patch | 4 +- ...ally-call-.get_ready-method-in-test-.patch | 5 +- 0004-ocaml-Rearrange-the-callbacks.patch | 256 +++++----- ...l-Fix-comment-on-plugin-.pread-field.patch | 4 +- 0006-docs-Correct-selinux-label-example.patch | 4 +- ...ow-Fix-assert-failure-in-cow_extents.patch | 46 +- ...x-misleading-LRU-diagram-and-comment.patch | 79 +++ ...umentation-of-.can_cache-and-.cache-.patch | 104 ++++ ...documentation-of-cow-on-cache-option.patch | 37 ++ ...cache-Simplify-test-cache-on-read.sh.patch | 40 ++ ...-cache-Reduce-verbosity-of-debugging.patch | 124 +++++ ...e-cow-Add-blk_read_multiple-function.patch | 400 +++++++++++++++ ...cow-Use-full-pread-pwrite-operations.patch | 215 ++++++++ 0015-cache-Implement-cache-on-read-PATH.patch | 151 ++++++ ...e-Add-cache-min-block-size-parameter.patch | 278 +++++++++++ ...-cow-Use-a-64K-block-size-by-default.patch | 138 ++++++ ...tor-printing-state-into-new-function.patch | 50 ++ ...t-cache-on-read-option-really-caches.patch | 147 ++++++ 0020-cow-Implement-cow-on-read.patch | 457 ++++++++++++++++++ ...delay-Add-delay-open-and-delay-close.patch | 172 +++++++ copy-patches.sh | 2 +- nbdkit.spec | 46 +- 23 files changed, 2589 insertions(+), 174 deletions(-) create mode 100644 0008-cache-Fix-misleading-LRU-diagram-and-comment.patch create mode 100644 0009-docs-Improve-documentation-of-.can_cache-and-.cache-.patch create mode 100644 0010-cow-Improve-documentation-of-cow-on-cache-option.patch create mode 100644 0011-tests-cache-Simplify-test-cache-on-read.sh.patch create mode 100644 0012-cache-Reduce-verbosity-of-debugging.patch create mode 100644 0013-cache-cow-Add-blk_read_multiple-function.patch create mode 100644 0014-cache-cow-Use-full-pread-pwrite-operations.patch create mode 100644 0015-cache-Implement-cache-on-read-PATH.patch create mode 100644 0016-cache-Add-cache-min-block-size-parameter.patch create mode 100644 0017-cache-cow-Use-a-64K-block-size-by-default.patch create mode 100644 0018-cache-Refactor-printing-state-into-new-function.patch create mode 100644 0019-tests-cache-Test-cache-on-read-option-really-caches.patch create mode 100644 0020-cow-Implement-cow-on-read.patch create mode 100644 0021-delay-Add-delay-open-and-delay-close.patch diff --git a/0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch b/0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch index ea629d1..7c916ed 100644 --- a/0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch +++ b/0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch @@ -1,7 +1,7 @@ From 5a23c7cf3c5eccac6e6de775722bc1136a66be83 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 5 Jul 2021 17:54:45 +0100 -Subject: [PATCH 1/7] ocaml: Call caml_shutdown when unloading the plugin +Subject: [PATCH] ocaml: Call caml_shutdown when unloading the plugin This has several useful effects (taken from the OCaml documentation): @@ -72,5 +72,5 @@ index 00959cb6..9d7d72ad 100644 static void -- -2.32.0 +2.31.1 diff --git a/0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch b/0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch index aefbb25..64acf73 100644 --- a/0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch +++ b/0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch @@ -1,7 +1,7 @@ From 397b7b245aee178b2683de8a34847843f658b43d Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 5 Jul 2021 18:00:28 +0100 -Subject: [PATCH 2/7] ocaml: Fix valgrinding by only ignoring caml_stat_alloc* +Subject: [PATCH] ocaml: Fix valgrinding by only ignoring caml_stat_alloc* functions These are meant to be "static" so are not freed by design. Other @@ -35,5 +35,5 @@ index f74b0943..a2b7fc60 100644 + fun:caml_stat_alloc* } -- -2.32.0 +2.31.1 diff --git a/0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch b/0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch index f3c88e6..b84017e 100644 --- a/0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch +++ b/0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch @@ -1,8 +1,7 @@ From 4efeffd80a5e85abf5603f20631910b2ef180317 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 13 Jul 2021 16:50:16 +0100 -Subject: [PATCH 3/7] ocaml: tests: Actually call .get_ready method in test - plugin +Subject: [PATCH] ocaml: tests: Actually call .get_ready method in test plugin It was added in a previous commit, but never called. @@ -25,5 +24,5 @@ index 2bbaa218..fee8528e 100644 config = Some config; config_complete = Some config_complete; -- -2.32.0 +2.31.1 diff --git a/0004-ocaml-Rearrange-the-callbacks.patch b/0004-ocaml-Rearrange-the-callbacks.patch index 24a311f..7b3738e 100644 --- a/0004-ocaml-Rearrange-the-callbacks.patch +++ b/0004-ocaml-Rearrange-the-callbacks.patch @@ -1,143 +1,18 @@ From 1610c0865534819eccefec55fd2d751843bb6d64 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 13 Jul 2021 16:15:45 +0100 -Subject: [PATCH 4/7] ocaml: Rearrange the callbacks +Subject: [PATCH] ocaml: Rearrange the callbacks Just refactoring. (cherry picked from commit e54e16e81c51dcbb16d70d83c5b0403babdf5f99) --- + plugins/ocaml/NBDKit.ml | 68 ++++++++++++++++++-------------------- plugins/ocaml/NBDKit.mli | 31 +++++++++-------- plugins/ocaml/callbacks.h | 34 +++++++++---------- - plugins/ocaml/NBDKit.ml | 68 ++++++++++++++++++-------------------- tests/test_ocaml_plugin.ml | 9 ++--- 4 files changed, 72 insertions(+), 70 deletions(-) -diff --git a/plugins/ocaml/NBDKit.mli b/plugins/ocaml/NBDKit.mli -index ac4b0cbc..cda09f44 100644 ---- a/plugins/ocaml/NBDKit.mli -+++ b/plugins/ocaml/NBDKit.mli -@@ -69,33 +69,31 @@ type thread_model = - The ['a] parameter is the handle type returned by your - [open_connection] method and passed back to all connected calls. *) - type 'a plugin = { -- name : string; (* required *) -+ (* Plugin description. *) -+ name : string; (** required field *) - longname : string; - version : string; - description : string; - -+ (* Plugin lifecycle. *) - load : (unit -> unit) option; -+ get_ready : (unit -> unit) option; -+ after_fork : (unit -> unit) option; - unload : (unit -> unit) option; - -- dump_plugin : (unit -> unit) option; -- -+ (* Plugin configuration. *) - config : (string -> string -> unit) option; - config_complete : (unit -> unit) option; - config_help : string; - thread_model : (unit -> thread_model) option; - -- get_ready : (unit -> unit) option; -- after_fork : (unit -> unit) option; -- -+ (* Connection lifecycle. *) - preconnect : (bool -> unit) option; -- list_exports : (bool -> bool -> export list) option; -- default_export : (bool -> bool -> string) option; -- open_connection : (bool -> 'a) option; (* required *) -+ open_connection : (bool -> 'a) option; (** required field *) - close : ('a -> unit) option; - -- get_size : ('a -> int64) option; (* required *) -- export_description : ('a -> string) option; -- -+ (* NBD negotiation. *) -+ get_size : ('a -> int64) option; (** required field *) - can_cache : ('a -> cache_flag) option; - can_extents : ('a -> bool) option; - can_fast_zero : ('a -> bool) option; -@@ -107,13 +105,20 @@ type 'a plugin = { - can_zero : ('a -> bool) option; - is_rotational : ('a -> bool) option; - -- pread : ('a -> int32 -> int64 -> flags -> string) option; (* required *) -+ (* Serving data. *) -+ pread : ('a -> int32 -> int64 -> flags -> string) option; (* required field *) - pwrite : ('a -> string -> int64 -> flags -> unit) option; - flush : ('a -> flags -> unit) option; - trim : ('a -> int32 -> int64 -> flags -> unit) option; - zero : ('a -> int32 -> int64 -> flags -> unit) option; - extents : ('a -> int32 -> int64 -> flags -> extent list) option; - cache : ('a -> int32 -> int64 -> flags -> unit) option; -+ -+ (* Miscellaneous. *) -+ dump_plugin : (unit -> unit) option; -+ list_exports : (bool -> bool -> export list) option; -+ default_export : (bool -> bool -> string) option; -+ export_description : ('a -> string) option; - } - - (** The plugin with all fields set to [None], so you can write -diff --git a/plugins/ocaml/callbacks.h b/plugins/ocaml/callbacks.h -index 7171ef21..4d29fb73 100644 ---- a/plugins/ocaml/callbacks.h -+++ b/plugins/ocaml/callbacks.h -@@ -33,21 +33,8 @@ - /* This is not a header file. It is included at various places in - * plugin.c as a convenient way to define per-callback things. - */ --CB(load) --CB(unload) --CB(dump_plugin) --CB(config) --CB(config_complete) --CB(thread_model) --CB(get_ready) - CB(after_fork) --CB(preconnect) --CB(list_exports) --CB(default_export) --CB(open) --CB(close) --CB(get_size) --CB(export_description) -+CB(cache) - CB(can_cache) - CB(can_extents) - CB(can_fast_zero) -@@ -57,11 +44,24 @@ CB(can_multi_conn) - CB(can_trim) - CB(can_write) - CB(can_zero) -+CB(close) -+CB(config) -+CB(config_complete) -+CB(default_export) -+CB(dump_plugin) -+CB(export_description) -+CB(extents) -+CB(flush) -+CB(get_ready) -+CB(get_size) - CB(is_rotational) -+CB(list_exports) -+CB(load) -+CB(open) - CB(pread) -+CB(preconnect) - CB(pwrite) --CB(flush) -+CB(thread_model) - CB(trim) -+CB(unload) - CB(zero) --CB(extents) --CB(cache) diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml index cdc3bc58..529618d2 100644 --- a/plugins/ocaml/NBDKit.ml @@ -281,6 +156,131 @@ index cdc3bc58..529618d2 100644 (* Bindings to nbdkit server functions. *) +diff --git a/plugins/ocaml/NBDKit.mli b/plugins/ocaml/NBDKit.mli +index ac4b0cbc..cda09f44 100644 +--- a/plugins/ocaml/NBDKit.mli ++++ b/plugins/ocaml/NBDKit.mli +@@ -69,33 +69,31 @@ type thread_model = + The ['a] parameter is the handle type returned by your + [open_connection] method and passed back to all connected calls. *) + type 'a plugin = { +- name : string; (* required *) ++ (* Plugin description. *) ++ name : string; (** required field *) + longname : string; + version : string; + description : string; + ++ (* Plugin lifecycle. *) + load : (unit -> unit) option; ++ get_ready : (unit -> unit) option; ++ after_fork : (unit -> unit) option; + unload : (unit -> unit) option; + +- dump_plugin : (unit -> unit) option; +- ++ (* Plugin configuration. *) + config : (string -> string -> unit) option; + config_complete : (unit -> unit) option; + config_help : string; + thread_model : (unit -> thread_model) option; + +- get_ready : (unit -> unit) option; +- after_fork : (unit -> unit) option; +- ++ (* Connection lifecycle. *) + preconnect : (bool -> unit) option; +- list_exports : (bool -> bool -> export list) option; +- default_export : (bool -> bool -> string) option; +- open_connection : (bool -> 'a) option; (* required *) ++ open_connection : (bool -> 'a) option; (** required field *) + close : ('a -> unit) option; + +- get_size : ('a -> int64) option; (* required *) +- export_description : ('a -> string) option; +- ++ (* NBD negotiation. *) ++ get_size : ('a -> int64) option; (** required field *) + can_cache : ('a -> cache_flag) option; + can_extents : ('a -> bool) option; + can_fast_zero : ('a -> bool) option; +@@ -107,13 +105,20 @@ type 'a plugin = { + can_zero : ('a -> bool) option; + is_rotational : ('a -> bool) option; + +- pread : ('a -> int32 -> int64 -> flags -> string) option; (* required *) ++ (* Serving data. *) ++ pread : ('a -> int32 -> int64 -> flags -> string) option; (* required field *) + pwrite : ('a -> string -> int64 -> flags -> unit) option; + flush : ('a -> flags -> unit) option; + trim : ('a -> int32 -> int64 -> flags -> unit) option; + zero : ('a -> int32 -> int64 -> flags -> unit) option; + extents : ('a -> int32 -> int64 -> flags -> extent list) option; + cache : ('a -> int32 -> int64 -> flags -> unit) option; ++ ++ (* Miscellaneous. *) ++ dump_plugin : (unit -> unit) option; ++ list_exports : (bool -> bool -> export list) option; ++ default_export : (bool -> bool -> string) option; ++ export_description : ('a -> string) option; + } + + (** The plugin with all fields set to [None], so you can write +diff --git a/plugins/ocaml/callbacks.h b/plugins/ocaml/callbacks.h +index 7171ef21..4d29fb73 100644 +--- a/plugins/ocaml/callbacks.h ++++ b/plugins/ocaml/callbacks.h +@@ -33,21 +33,8 @@ + /* This is not a header file. It is included at various places in + * plugin.c as a convenient way to define per-callback things. + */ +-CB(load) +-CB(unload) +-CB(dump_plugin) +-CB(config) +-CB(config_complete) +-CB(thread_model) +-CB(get_ready) + CB(after_fork) +-CB(preconnect) +-CB(list_exports) +-CB(default_export) +-CB(open) +-CB(close) +-CB(get_size) +-CB(export_description) ++CB(cache) + CB(can_cache) + CB(can_extents) + CB(can_fast_zero) +@@ -57,11 +44,24 @@ CB(can_multi_conn) + CB(can_trim) + CB(can_write) + CB(can_zero) ++CB(close) ++CB(config) ++CB(config_complete) ++CB(default_export) ++CB(dump_plugin) ++CB(export_description) ++CB(extents) ++CB(flush) ++CB(get_ready) ++CB(get_size) + CB(is_rotational) ++CB(list_exports) ++CB(load) ++CB(open) + CB(pread) ++CB(preconnect) + CB(pwrite) +-CB(flush) ++CB(thread_model) + CB(trim) ++CB(unload) + CB(zero) +-CB(extents) +-CB(cache) diff --git a/tests/test_ocaml_plugin.ml b/tests/test_ocaml_plugin.ml index fee8528e..00a65a75 100644 --- a/tests/test_ocaml_plugin.ml @@ -311,5 +311,5 @@ index fee8528e..00a65a75 100644 let () = NBDKit.register_plugin plugin -- -2.32.0 +2.31.1 diff --git a/0005-ocaml-Fix-comment-on-plugin-.pread-field.patch b/0005-ocaml-Fix-comment-on-plugin-.pread-field.patch index 9a2e20c..65f165a 100644 --- a/0005-ocaml-Fix-comment-on-plugin-.pread-field.patch +++ b/0005-ocaml-Fix-comment-on-plugin-.pread-field.patch @@ -1,7 +1,7 @@ From 229f106d65e2a54aa21afde9182b0e110a83b0df Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 15 Jul 2021 20:41:03 +0100 -Subject: [PATCH 5/7] ocaml: Fix comment on plugin .pread field +Subject: [PATCH] ocaml: Fix comment on plugin .pread field Incorrectly updated in earlier commit. @@ -25,5 +25,5 @@ index cda09f44..0f7b87e9 100644 flush : ('a -> flags -> unit) option; trim : ('a -> int32 -> int64 -> flags -> unit) option; -- -2.32.0 +2.31.1 diff --git a/0006-docs-Correct-selinux-label-example.patch b/0006-docs-Correct-selinux-label-example.patch index a6c398c..abc81a3 100644 --- a/0006-docs-Correct-selinux-label-example.patch +++ b/0006-docs-Correct-selinux-label-example.patch @@ -1,7 +1,7 @@ From 8c86f8bbc326ff1578989a03b3c98b06634f62c1 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 22 Jul 2021 16:31:34 +0100 -Subject: [PATCH 6/7] docs: Correct --selinux-label example +Subject: [PATCH] docs: Correct --selinux-label example The actual label you should use for the internal socket is system_u:object_r:svirt_socket_t:s0 (not svirt_t). @@ -36,5 +36,5 @@ index 68399eca..5b679895 100644 =item B<--swap> -- -2.32.0 +2.31.1 diff --git a/0007-cow-Fix-assert-failure-in-cow_extents.patch b/0007-cow-Fix-assert-failure-in-cow_extents.patch index 637d9bf..a13aa16 100644 --- a/0007-cow-Fix-assert-failure-in-cow_extents.patch +++ b/0007-cow-Fix-assert-failure-in-cow_extents.patch @@ -1,7 +1,7 @@ From c0c0728f40466cf4a8ab4868002e331df6d85b1e Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 24 Jul 2021 13:30:55 +0100 -Subject: [PATCH 7/7] cow: Fix assert failure in cow_extents +Subject: [PATCH] cow: Fix assert failure in cow_extents $ nbdkit sparse-random 4G --filter=cow --run 'nbdinfo --map $uri' nbdkit: cow.c:591: cow_extents: Assertion `count > 0' failed. @@ -21,32 +21,12 @@ https://gitlab.com/nbdkit/libnbd/-/blob/c55c5d9960809efd27cd044d007a33ea1636f4b0 (cherry picked from commit 4d66ab72b29fc56190c7a6368eff3a6ba94c0f9f) --- - tests/Makefile.am | 2 ++ filters/cow/cow.c | 16 +++++++++--- + tests/Makefile.am | 2 ++ tests/test-cow-extents-large.sh | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100755 tests/test-cow-extents-large.sh -diff --git a/tests/Makefile.am b/tests/Makefile.am -index e0b31ba9..9630205d 100644 ---- a/tests/Makefile.am -+++ b/tests/Makefile.am -@@ -1402,6 +1402,7 @@ TESTS += \ - test-cow.sh \ - test-cow-extents1.sh \ - test-cow-extents2.sh \ -+ test-cow-extents-large.sh \ - test-cow-unaligned.sh \ - $(NULL) - endif -@@ -1410,6 +1411,7 @@ EXTRA_DIST += \ - test-cow.sh \ - test-cow-extents1.sh \ - test-cow-extents2.sh \ -+ test-cow-extents-large.sh \ - test-cow-null.sh \ - test-cow-unaligned.sh \ - $(NULL) diff --git a/filters/cow/cow.c b/filters/cow/cow.c index 83844845..3bd09399 100644 --- a/filters/cow/cow.c @@ -91,6 +71,26 @@ index 83844845..3bd09399 100644 blknum++; offset += BLKSIZE; count -= BLKSIZE; +diff --git a/tests/Makefile.am b/tests/Makefile.am +index e0b31ba9..9630205d 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1402,6 +1402,7 @@ TESTS += \ + test-cow.sh \ + test-cow-extents1.sh \ + test-cow-extents2.sh \ ++ test-cow-extents-large.sh \ + test-cow-unaligned.sh \ + $(NULL) + endif +@@ -1410,6 +1411,7 @@ EXTRA_DIST += \ + test-cow.sh \ + test-cow-extents1.sh \ + test-cow-extents2.sh \ ++ test-cow-extents-large.sh \ + test-cow-null.sh \ + test-cow-unaligned.sh \ + $(NULL) diff --git a/tests/test-cow-extents-large.sh b/tests/test-cow-extents-large.sh new file mode 100755 index 00000000..ea981dcb @@ -144,5 +144,5 @@ index 00000000..ea981dcb + nbdkit -U - sparse-random $size --filter=cow --run 'nbdinfo --map $uri' +done -- -2.32.0 +2.31.1 diff --git a/0008-cache-Fix-misleading-LRU-diagram-and-comment.patch b/0008-cache-Fix-misleading-LRU-diagram-and-comment.patch new file mode 100644 index 0000000..c694555 --- /dev/null +++ b/0008-cache-Fix-misleading-LRU-diagram-and-comment.patch @@ -0,0 +1,79 @@ +From b436ca6c69ef7d8d826be609820027f10134274d Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 27 Jul 2021 21:28:48 +0100 +Subject: [PATCH] cache: Fix misleading LRU diagram and comment + +Only comment changes. + +(cherry picked from commit 7b33c86e0910d941dc34bdb481d61806f31cdcef) +--- + filters/cache/lru.c | 32 ++++++++++++++++++++------------ + 1 file changed, 20 insertions(+), 12 deletions(-) + +diff --git a/filters/cache/lru.c b/filters/cache/lru.c +index 1c3c3e10..716b4984 100644 +--- a/filters/cache/lru.c ++++ b/filters/cache/lru.c +@@ -53,12 +53,14 @@ + + /* LRU bitmaps. These bitmaps implement a simple, fast LRU structure. + * +- * bm[0] bm[1] blocks not in either bitmap +- * ┌─────────┬──────────────────┬─────────────────────────────┐ +- * │ │ │ │ +- * └─────────┴──────────────────┴─────────────────────────────┘ +- * ↑ c1 bits set +- * c0 bits set ++ * bm[0] ++ * ┌───────────────────────┐ ++ * │ X XX X XXX │ c0 bits set ++ * └───────────────────────┘ ++ * bm[1] ++ * ┌───────────────────────┐ ++ * │ X XX X X │ c1 bits set ++ * └───────────────────────┘ + * + * The LRU structure keeps track of the [approx] last N distinct + * blocks which have been most recently accessed. It can answer in +@@ -69,8 +71,7 @@ + * + * When a new block is accessed, we set the corresponding bit in bm[0] + * and increment c0 (c0 counts the number of bits set in bm[0]). If +- * c0 == N/2 then we swap the two bitmaps, clear bm[0], and reset c0 +- * to 0. ++ * c0 == N/2 then we move bm[1] <- bm[0], clear bm[0] and set c0 <- 0. + * + * To check if a block has been accessed within the previous N + * distinct accesses, we simply have to check both bitmaps. If it is +@@ -78,9 +79,11 @@ + * reclaimed. + * + * You'll note that in fact we only keep track of between N/2 and N +- * recently accessed blocks. We could make the estimate more accurate +- * by having more bitmaps, but as this is only a heuristic we choose +- * to keep the implementation simple and memory usage low instead. ++ * recently accessed blocks because the same block can appear in both ++ * bitmaps. bm[1] is a last chance to hold on to blocks which are ++ * soon to be reclaimed. We could make the estimate more accurate by ++ * having more bitmaps, but as this is only a heuristic we choose to ++ * keep the implementation simple and memory usage low instead. + */ + static struct bitmap bm[2]; + static unsigned c0 = 0, c1 = 0; +@@ -129,7 +132,12 @@ lru_set_recently_accessed (uint64_t blknum) + bitmap_set_blk (&bm[0], blknum, true); + c0++; + +- /* If we've reached N/2 then we need to swap over the bitmaps. */ ++ /* If we've reached N/2 then we need to swap over the bitmaps. Note ++ * the purpose of swapping here is to ensure that we do not have to ++ * copy the dynamically allocated bm->bitmap field (the pointers are ++ * swapped instead). The bm[0].bitmap field is immediately zeroed ++ * after the swap. ++ */ + if (c0 >= N/2) { + struct bitmap tmp; + +-- +2.31.1 + diff --git a/0009-docs-Improve-documentation-of-.can_cache-and-.cache-.patch b/0009-docs-Improve-documentation-of-.can_cache-and-.cache-.patch new file mode 100644 index 0000000..ad9a6b8 --- /dev/null +++ b/0009-docs-Improve-documentation-of-.can_cache-and-.cache-.patch @@ -0,0 +1,104 @@ +From b6e1d14a052caf65dcc7e8fec2bf0d079e1f8a38 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 27 Jul 2021 22:42:52 +0100 +Subject: [PATCH] docs: Improve documentation of .can_cache and .cache methods + +(cherry picked from commit 0a6be5ae01a6079767e1fabd70cca73fc8520b1d) +--- + docs/nbdkit-plugin.pod | 71 +++++++++++++++++++++++++----------------- + 1 file changed, 43 insertions(+), 28 deletions(-) + +diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod +index 7a1fae8c..5e085e12 100644 +--- a/docs/nbdkit-plugin.pod ++++ b/docs/nbdkit-plugin.pod +@@ -1047,19 +1047,29 @@ This callback is not required. If omitted, then we return false. + int can_cache (void *handle); + + This is called during the option negotiation phase to find out if the +-plugin supports a cache operation. The nature of the caching is +-unspecified (including whether there are limits on how much can be +-cached at once, and whether writes to a cached region have +-write-through or write-back semantics), but the command exists to let +-clients issue a hint to the server that they will be accessing that +-region of the export. +- +-If this returns C, cache support is not advertised +-to the client; if this returns C, caching is +-emulated by the server calling C<.pread> and ignoring the results; if +-this returns C, then the C<.cache> callback will +-be used. If there is an error, C<.can_cache> should call +-C with an error message and return C<-1>. ++plugin supports a cache or prefetch operation. ++ ++This can return: ++ ++=over 4 ++ ++=item C ++ ++Cache support is not advertised to the client. ++ ++=item C ++ ++Caching is emulated by the server calling C<.pread> and discarding the ++result. ++ ++=item C ++ ++The C<.cache> callback will be called. ++ ++=back ++ ++If there is an error, C<.can_cache> should call C with ++an error message and return C<-1>. + + This callback is not required. If omitted, then we return + C if the C<.cache> callback is missing, or +@@ -1284,23 +1294,28 @@ called. C will be set to a suitable value. + + During the data serving phase, this callback is used to give the + plugin a hint that the client intends to make further accesses to the +-given region of the export. The nature of caching is not specified +-further by the NBD specification (for example, a server may place +-limits on how much may be cached at once, and there is no way to +-control if writes to a cached area have write-through or write-back +-semantics). In fact, the cache command can always fail and still be +-compliant, and success might not guarantee a performance gain. If +-this callback is omitted, then the results of C<.can_cache> determine +-whether nbdkit will reject cache requests, treat them as instant +-success, or emulate caching by calling C<.pread> over the same region +-and ignoring the results. ++given region of the export. ++ ++The nature of caching/prefetching is not specified further by the NBD ++specification. For example, a server may place limits on how much may ++be cached at once, and there is no way to control if writes to a ++cached area have write-through or write-back semantics. In fact, the ++cache command can always fail and still be compliant, and success ++might not guarantee a performance gain. ++ ++If this callback is omitted, then the results of C<.can_cache> ++determine whether nbdkit will reject cache requests, treat them as ++instant success, or emulate caching by calling C<.pread> over the same ++region and ignoring the results. + + This function will not be called if C<.can_cache> did not return +-C. The parameter C exists in case of +-future NBD protocol extensions; at this time, it will be 0 on input. A +-plugin must fail this function if C includes an unrecognized +-flag, as that may indicate a requirement that the plugin comply must +-with a specific caching semantic. ++C. ++ ++The C parameter exists in case of future NBD protocol ++extensions; at this time, it will be 0 on input. A plugin must fail ++this function if C includes an unrecognized flag, as that may ++indicate a requirement that the plugin comply must with a specific ++caching semantic. + + If there is an error, C<.cache> should call C with an + error message, and C to record an appropriate error +-- +2.31.1 + diff --git a/0010-cow-Improve-documentation-of-cow-on-cache-option.patch b/0010-cow-Improve-documentation-of-cow-on-cache-option.patch new file mode 100644 index 0000000..2996f0b --- /dev/null +++ b/0010-cow-Improve-documentation-of-cow-on-cache-option.patch @@ -0,0 +1,37 @@ +From ef0ee0166b0594b04c73376f84a729c2985ca064 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 27 Jul 2021 23:10:24 +0100 +Subject: [PATCH] cow: Improve documentation of cow-on-cache option + +(cherry picked from commit 9731e80d58c3aed2514d249e7925c2053d6eb0e8) +--- + filters/cow/nbdkit-cow-filter.pod | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/filters/cow/nbdkit-cow-filter.pod b/filters/cow/nbdkit-cow-filter.pod +index 64df3fbd..2a693ebe 100644 +--- a/filters/cow/nbdkit-cow-filter.pod ++++ b/filters/cow/nbdkit-cow-filter.pod +@@ -54,12 +54,13 @@ serve the same data to each client. + + =item B + +-Treat a client cache request as a shortcut for copying unmodified data +-from the plugin to the overlay, rather than the default of passing +-cache requests on to the plugin. This parameter defaults to false +-(which leaves the overlay as small as possible), but setting it can be +-useful for converting cache commands into a form of copy-on-read +-behavior, in addition to the filter's normal copy-on-write semantics. ++When the client issues a cache (prefetch) request, preemptively save ++the data from the plugin into the overlay. ++ ++=item B ++ ++Do not save data from cache (prefetch) requests in the overlay. This ++leaves the overlay as small as possible. This is the default. + + =back + +-- +2.31.1 + diff --git a/0011-tests-cache-Simplify-test-cache-on-read.sh.patch b/0011-tests-cache-Simplify-test-cache-on-read.sh.patch new file mode 100644 index 0000000..f2e0b9e --- /dev/null +++ b/0011-tests-cache-Simplify-test-cache-on-read.sh.patch @@ -0,0 +1,40 @@ +From a09b06f2c104c01d7b0ff5e657c0c64bf1c4cc41 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 29 Jul 2021 20:14:24 +0100 +Subject: [PATCH] tests: cache: Simplify test-cache-on-read.sh + +We can use the memory plugin instead of a backing file. + +(cherry picked from commit 5527b28e323b7c9c35af8e1bb6b05e6468e68950) +--- + tests/test-cache-on-read.sh | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/tests/test-cache-on-read.sh b/tests/test-cache-on-read.sh +index 3b3c7657..f8584dcd 100755 +--- a/tests/test-cache-on-read.sh ++++ b/tests/test-cache-on-read.sh +@@ -38,18 +38,14 @@ requires_filter cache + requires_nbdsh_uri + + sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) +-files="cache-on-read.img $sock cache-on-read.pid" ++files="$sock cache-on-read.pid" + rm -f $files + cleanup_fn rm -f $files + +-# Create an empty base image. +-truncate -s 128K cache-on-read.img +- + # Run nbdkit with the caching filter and cache-on-read set. + start_nbdkit -P cache-on-read.pid -U $sock \ + --filter=cache \ +- file cache-on-read.img \ +- cache-on-read=true ++ memory 128K cache-on-read=true + + nbdsh --connect "nbd+unix://?socket=$sock" \ + -c ' +-- +2.31.1 + diff --git a/0012-cache-Reduce-verbosity-of-debugging.patch b/0012-cache-Reduce-verbosity-of-debugging.patch new file mode 100644 index 0000000..a19c780 --- /dev/null +++ b/0012-cache-Reduce-verbosity-of-debugging.patch @@ -0,0 +1,124 @@ +From 37844f524c01b54b28755b77b68b7c1ec2b79512 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 11:59:43 +0100 +Subject: [PATCH] cache: Reduce verbosity of debugging + +The cache filter is very verbose in its debugging. Reduce the default +level. Use -D cache.verbose=1 to restore original debugging. + +Compare commit 745a0f13662031c2b9c9b69f62b4ae3a6b2f38f0. + +(cherry picked from commit 6be735edf7d5fb3fb8350c72e6d9525badbab14d) +--- + filters/cache/blk.c | 53 +++++++++++++++++++++++++++------------------ + 1 file changed, 32 insertions(+), 21 deletions(-) + +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index 12e8407e..f52f30e3 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -93,6 +93,9 @@ enum bm_entry { + BLOCK_DIRTY = 3, + }; + ++/* Extra debugging (-D cache.verbose=1). */ ++NBDKIT_DLL_PUBLIC int cache_debug_verbose = 0; ++ + int + blk_init (void) + { +@@ -199,12 +202,14 @@ blk_read (nbdkit_next *next, + + reclaim (fd, &bm); + +- nbdkit_debug ("cache: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s", +- blknum, (uint64_t) offset, +- state == BLOCK_NOT_CACHED ? "not cached" : +- state == BLOCK_CLEAN ? "clean" : +- state == BLOCK_DIRTY ? "dirty" : +- "unknown"); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: blk_read block %" PRIu64 ++ " (offset %" PRIu64 ") is %s", ++ blknum, (uint64_t) offset, ++ state == BLOCK_NOT_CACHED ? "not cached" : ++ state == BLOCK_CLEAN ? "clean" : ++ state == BLOCK_DIRTY ? "dirty" : ++ "unknown"); + + if (state == BLOCK_NOT_CACHED) { /* Read underlying plugin. */ + unsigned n = blksize, tail = 0; +@@ -225,9 +230,10 @@ blk_read (nbdkit_next *next, + + /* If cache-on-read, copy the block to the cache. */ + if (cache_on_read) { +- nbdkit_debug ("cache: cache-on-read block %" PRIu64 +- " (offset %" PRIu64 ")", +- blknum, (uint64_t) offset); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: cache-on-read block %" PRIu64 ++ " (offset %" PRIu64 ")", ++ blknum, (uint64_t) offset); + + if (pwrite (fd, block, blksize, offset) == -1) { + *err = errno; +@@ -259,12 +265,14 @@ blk_cache (nbdkit_next *next, + + reclaim (fd, &bm); + +- nbdkit_debug ("cache: blk_cache block %" PRIu64 " (offset %" PRIu64 ") is %s", +- blknum, (uint64_t) offset, +- state == BLOCK_NOT_CACHED ? "not cached" : +- state == BLOCK_CLEAN ? "clean" : +- state == BLOCK_DIRTY ? "dirty" : +- "unknown"); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: blk_cache block %" PRIu64 ++ " (offset %" PRIu64 ") is %s", ++ blknum, (uint64_t) offset, ++ state == BLOCK_NOT_CACHED ? "not cached" : ++ state == BLOCK_CLEAN ? "clean" : ++ state == BLOCK_DIRTY ? "dirty" : ++ "unknown"); + + if (state == BLOCK_NOT_CACHED) { + /* Read underlying plugin, copy to cache regardless of cache-on-read. */ +@@ -284,8 +292,9 @@ blk_cache (nbdkit_next *next, + */ + memset (block + n, 0, tail); + +- nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", +- blknum, (uint64_t) offset); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", ++ blknum, (uint64_t) offset); + + if (pwrite (fd, block, blksize, offset) == -1) { + *err = errno; +@@ -324,8 +333,9 @@ blk_writethrough (nbdkit_next *next, + + reclaim (fd, &bm); + +- nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", +- blknum, (uint64_t) offset); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", ++ blknum, (uint64_t) offset); + + if (pwrite (fd, block, blksize, offset) == -1) { + *err = errno; +@@ -357,8 +367,9 @@ blk_write (nbdkit_next *next, + + reclaim (fd, &bm); + +- nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", +- blknum, (uint64_t) offset); ++ if (cache_debug_verbose) ++ nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", ++ blknum, (uint64_t) offset); + + if (pwrite (fd, block, blksize, offset) == -1) { + *err = errno; +-- +2.31.1 + diff --git a/0013-cache-cow-Add-blk_read_multiple-function.patch b/0013-cache-cow-Add-blk_read_multiple-function.patch new file mode 100644 index 0000000..0d273b2 --- /dev/null +++ b/0013-cache-cow-Add-blk_read_multiple-function.patch @@ -0,0 +1,400 @@ +From a118e05670659b3efd1ab191023cc0bc24cf29e7 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 13:55:21 +0100 +Subject: [PATCH] cache, cow: Add blk_read_multiple function + +Currently the cache and cow filters break up large requests into many +single block-sized requests to the underlying plugin. For some +plugins (eg. curl) this is very inefficient and causes huge +slow-downs. + +For example I tested nbdkit + curl vs nbdkit + cache + curl against a +slow, remote VMware server. A simple run of virt-inspector was at +least 6-7 times slower with the cache filter. (It was so slow that I +didn't actually let it run to completion - I am estimating the +slowdown multiple using interim debug messages). + +Implement a new blk_read_multiple function in the cache filter. It +does not break up "runs" of blocks which all have the same cache +state. The cache .pread method uses the new function to read the +block-aligned part of the request. + +(cherry picked from commit ab661ccef5b3369fa22c33d0289baddc251b73bf) +--- + filters/cache/blk.c | 83 ++++++++++++++++++++++++++++++++----------- + filters/cache/blk.h | 6 ++++ + filters/cache/cache.c | 21 +++++------ + filters/cow/blk.c | 63 +++++++++++++++++++++++--------- + filters/cow/blk.h | 6 ++++ + filters/cow/cow.c | 21 +++++------ + 6 files changed, 138 insertions(+), 62 deletions(-) + +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index f52f30e3..f85ada35 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -44,6 +44,7 @@ + #include + #include + #include ++#include + #include + + #ifdef HAVE_SYS_STATVFS_H +@@ -193,26 +194,40 @@ blk_set_size (uint64_t new_size) + return 0; + } + +-int +-blk_read (nbdkit_next *next, +- uint64_t blknum, uint8_t *block, int *err) ++static int ++_blk_read_multiple (nbdkit_next *next, ++ uint64_t blknum, uint64_t nrblocks, ++ uint8_t *block, int *err) + { + off_t offset = blknum * blksize; +- enum bm_entry state = bitmap_get_blk (&bm, blknum, BLOCK_NOT_CACHED); ++ bool not_cached = ++ bitmap_get_blk (&bm, blknum, BLOCK_NOT_CACHED) == BLOCK_NOT_CACHED; ++ uint64_t b, runblocks; + +- reclaim (fd, &bm); ++ assert (nrblocks > 0); + + if (cache_debug_verbose) +- nbdkit_debug ("cache: blk_read block %" PRIu64 ++ nbdkit_debug ("cache: blk_read_multiple block %" PRIu64 + " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, +- state == BLOCK_NOT_CACHED ? "not cached" : +- state == BLOCK_CLEAN ? "clean" : +- state == BLOCK_DIRTY ? "dirty" : +- "unknown"); ++ not_cached ? "not cached" : "cached"); + +- if (state == BLOCK_NOT_CACHED) { /* Read underlying plugin. */ +- unsigned n = blksize, tail = 0; ++ /* Find out how many of the following blocks form a "run" with the ++ * same cached/not-cached state. We can process that many blocks in ++ * one go. ++ */ ++ for (b = 1, runblocks = 1; b < nrblocks; ++b, ++runblocks) { ++ bool s = ++ bitmap_get_blk (&bm, blknum + b, BLOCK_NOT_CACHED) == BLOCK_NOT_CACHED; ++ if (not_cached != s) ++ break; ++ } ++ ++ if (not_cached) { /* Read underlying plugin. */ ++ unsigned n, tail = 0; ++ ++ assert (blksize * runblocks <= UINT_MAX); ++ n = blksize * runblocks; + + if (offset + n > size) { + tail = offset + n - size; +@@ -228,32 +243,60 @@ blk_read (nbdkit_next *next, + */ + memset (block + n, 0, tail); + +- /* If cache-on-read, copy the block to the cache. */ ++ /* If cache-on-read, copy the blocks to the cache. */ + if (cache_on_read) { + if (cache_debug_verbose) + nbdkit_debug ("cache: cache-on-read block %" PRIu64 + " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, blksize, offset) == -1) { ++ if (pwrite (fd, block, blksize * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; + } +- bitmap_set_blk (&bm, blknum, BLOCK_CLEAN); +- lru_set_recently_accessed (blknum); ++ for (b = 0; b < runblocks; ++b) { ++ bitmap_set_blk (&bm, blknum + b, BLOCK_CLEAN); ++ lru_set_recently_accessed (blknum + b); ++ } + } +- return 0; + } + else { /* Read cache. */ +- if (pread (fd, block, blksize, offset) == -1) { ++ if (pread (fd, block, blksize * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pread: %m"); + return -1; + } +- lru_set_recently_accessed (blknum); +- return 0; ++ for (b = 0; b < runblocks; ++b) ++ lru_set_recently_accessed (blknum + b); + } ++ ++ /* If all done, return. */ ++ if (runblocks == nrblocks) ++ return 0; ++ ++ /* Recurse to read remaining blocks. */ ++ return _blk_read_multiple (next, ++ blknum + runblocks, ++ nrblocks - runblocks, ++ block + blksize * runblocks, ++ err); ++} ++ ++int ++blk_read_multiple (nbdkit_next *next, ++ uint64_t blknum, uint64_t nrblocks, ++ uint8_t *block, int *err) ++{ ++ reclaim (fd, &bm); ++ return _blk_read_multiple (next, blknum, nrblocks, block, err); ++} ++ ++int ++blk_read (nbdkit_next *next, ++ uint64_t blknum, uint8_t *block, int *err) ++{ ++ return blk_read_multiple (next, blknum, 1, block, err); + } + + int +diff --git a/filters/cache/blk.h b/filters/cache/blk.h +index 87c753e2..1ee33ed7 100644 +--- a/filters/cache/blk.h ++++ b/filters/cache/blk.h +@@ -55,6 +55,12 @@ extern int blk_read (nbdkit_next *next, + uint64_t blknum, uint8_t *block, int *err) + __attribute__((__nonnull__ (1, 3, 4))); + ++/* As above, but read multiple blocks. */ ++extern int blk_read_multiple (nbdkit_next *next, ++ uint64_t blknum, uint64_t nrblocks, ++ uint8_t *block, int *err) ++ __attribute__((__nonnull__ (1, 4, 5))); ++ + /* If a single block is not cached, copy it from the plugin. */ + extern int blk_cache (nbdkit_next *next, + uint64_t blknum, uint8_t *block, int *err) +diff --git a/filters/cache/cache.c b/filters/cache/cache.c +index 499aec68..9c081948 100644 +--- a/filters/cache/cache.c ++++ b/filters/cache/cache.c +@@ -313,7 +313,7 @@ cache_pread (nbdkit_next *next, + uint32_t flags, int *err) + { + CLEANUP_FREE uint8_t *block = NULL; +- uint64_t blknum, blkoffs; ++ uint64_t blknum, blkoffs, nrblocks; + int r; + + assert (!flags); +@@ -348,22 +348,17 @@ cache_pread (nbdkit_next *next, + } + + /* Aligned body */ +- /* XXX This breaks up large read requests into smaller ones, which +- * is a problem for plugins which have a large, fixed per-request +- * overhead (hello, curl). We should try to keep large requests +- * together as much as possible, but that requires us to be much +- * smarter here. +- */ +- while (count >= blksize) { ++ nrblocks = count / blksize; ++ if (nrblocks > 0) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); +- r = blk_read (next, blknum, buf, err); ++ r = blk_read_multiple (next, blknum, nrblocks, buf, err); + if (r == -1) + return -1; + +- buf += blksize; +- count -= blksize; +- offset += blksize; +- blknum++; ++ buf += nrblocks * blksize; ++ count -= nrblocks * blksize; ++ offset += nrblocks * blksize; ++ blknum += nrblocks; + } + + /* Unaligned tail */ +diff --git a/filters/cow/blk.c b/filters/cow/blk.c +index 0f12d510..9e6c8879 100644 +--- a/filters/cow/blk.c ++++ b/filters/cow/blk.c +@@ -79,6 +79,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -223,33 +224,48 @@ blk_status (uint64_t blknum, bool *present, bool *trimmed) + *trimmed = state == BLOCK_TRIMMED; + } + +-/* These are the block operations. They always read or write a single +- * whole block of size ‘blksize’. ++/* These are the block operations. They always read or write whole ++ * blocks of size ‘blksize’. + */ + int +-blk_read (nbdkit_next *next, +- uint64_t blknum, uint8_t *block, int *err) ++blk_read_multiple (nbdkit_next *next, ++ uint64_t blknum, uint64_t nrblocks, ++ uint8_t *block, int *err) + { + off_t offset = blknum * BLKSIZE; + enum bm_entry state; ++ uint64_t b, runblocks; + +- /* The state might be modified from another thread - for example +- * another thread might write (BLOCK_NOT_ALLOCATED -> +- * BLOCK_ALLOCATED) while we are reading from the plugin, returning +- * the old data. However a read issued after the write returns +- * should always return the correct data. ++ /* Find out how many of the following blocks form a "run" with the ++ * same state. We can process that many blocks in one go. ++ * ++ * About the locking: The state might be modified from another ++ * thread - for example another thread might write ++ * (BLOCK_NOT_ALLOCATED -> BLOCK_ALLOCATED) while we are reading ++ * from the plugin, returning the old data. However a read issued ++ * after the write returns should always return the correct data. + */ + { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + state = bitmap_get_blk (&bm, blknum, BLOCK_NOT_ALLOCATED); ++ ++ for (b = 1, runblocks = 1; b < nrblocks; ++b, ++runblocks) { ++ enum bm_entry s = bitmap_get_blk (&bm, blknum + b, BLOCK_NOT_ALLOCATED); ++ if (state != s) ++ break; ++ } + } + + if (cow_debug_verbose) +- nbdkit_debug ("cow: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s", ++ nbdkit_debug ("cow: blk_read_multiple block %" PRIu64 ++ " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, state_to_string (state)); + + if (state == BLOCK_NOT_ALLOCATED) { /* Read underlying plugin. */ +- unsigned n = BLKSIZE, tail = 0; ++ unsigned n, tail = 0; ++ ++ assert (BLKSIZE * runblocks <= UINT_MAX); ++ n = BLKSIZE * runblocks; + + if (offset + n > size) { + tail = offset + n - size; +@@ -264,20 +280,35 @@ blk_read (nbdkit_next *next, + * zeroing the tail. + */ + memset (block + n, 0, tail); +- return 0; + } + else if (state == BLOCK_ALLOCATED) { /* Read overlay. */ +- if (pread (fd, block, BLKSIZE, offset) == -1) { ++ if (pread (fd, block, BLKSIZE * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pread: %m"); + return -1; + } +- return 0; + } + else /* state == BLOCK_TRIMMED */ { +- memset (block, 0, BLKSIZE); +- return 0; ++ memset (block, 0, BLKSIZE * runblocks); + } ++ ++ /* If all done, return. */ ++ if (runblocks == nrblocks) ++ return 0; ++ ++ /* Recurse to read remaining blocks. */ ++ return blk_read_multiple (next, ++ blknum + runblocks, ++ nrblocks - runblocks, ++ block + BLKSIZE * runblocks, ++ err); ++} ++ ++int ++blk_read (nbdkit_next *next, ++ uint64_t blknum, uint8_t *block, int *err) ++{ ++ return blk_read_multiple (next, blknum, 1, block, err); + } + + int +diff --git a/filters/cow/blk.h b/filters/cow/blk.h +index e6fd7417..b066c602 100644 +--- a/filters/cow/blk.h ++++ b/filters/cow/blk.h +@@ -55,6 +55,12 @@ extern int blk_read (nbdkit_next *next, + uint64_t blknum, uint8_t *block, int *err) + __attribute__((__nonnull__ (1, 3, 4))); + ++/* Read multiple blocks from the overlay or plugin. */ ++extern int blk_read_multiple (nbdkit_next *next, ++ uint64_t blknum, uint64_t nrblocks, ++ uint8_t *block, int *err) ++ __attribute__((__nonnull__ (1, 4, 5))); ++ + /* Cache mode for blocks not already in overlay */ + enum cache_mode { + BLK_CACHE_IGNORE, /* Do nothing */ +diff --git a/filters/cow/cow.c b/filters/cow/cow.c +index 3bd09399..f74c0a34 100644 +--- a/filters/cow/cow.c ++++ b/filters/cow/cow.c +@@ -210,7 +210,7 @@ cow_pread (nbdkit_next *next, + uint32_t flags, int *err) + { + CLEANUP_FREE uint8_t *block = NULL; +- uint64_t blknum, blkoffs; ++ uint64_t blknum, blkoffs, nrblocks; + int r; + + if (!IS_ALIGNED (count | offset, BLKSIZE)) { +@@ -243,21 +243,16 @@ cow_pread (nbdkit_next *next, + } + + /* Aligned body */ +- /* XXX This breaks up large read requests into smaller ones, which +- * is a problem for plugins which have a large, fixed per-request +- * overhead (hello, curl). We should try to keep large requests +- * together as much as possible, but that requires us to be much +- * smarter here. +- */ +- while (count >= BLKSIZE) { +- r = blk_read (next, blknum, buf, err); ++ nrblocks = count / BLKSIZE; ++ if (nrblocks > 0) { ++ r = blk_read_multiple (next, blknum, nrblocks, buf, err); + if (r == -1) + return -1; + +- buf += BLKSIZE; +- count -= BLKSIZE; +- offset += BLKSIZE; +- blknum++; ++ buf += nrblocks * BLKSIZE; ++ count -= nrblocks * BLKSIZE; ++ offset += nrblocks * BLKSIZE; ++ blknum += nrblocks; + } + + /* Unaligned tail */ +-- +2.31.1 + diff --git a/0014-cache-cow-Use-full-pread-pwrite-operations.patch b/0014-cache-cow-Use-full-pread-pwrite-operations.patch new file mode 100644 index 0000000..eab056e --- /dev/null +++ b/0014-cache-cow-Use-full-pread-pwrite-operations.patch @@ -0,0 +1,215 @@ +From bf82947dabe08a0d51f87eb14619291900c65574 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 15:21:18 +0100 +Subject: [PATCH] cache, cow: Use full pread/pwrite operations + +Although it probably cannot happen on Linux, POSIX allows pread/pwrite +to return or write fewer bytes than requested. The cache and cow +filters didn't handle this situation. Replace the raw +pread(2)/pwrite(2) syscalls with alternate versions which can handle +this. + +(cherry picked from commit ce0db9d7736dd28dd0f10951ce65853e50b35e41) +--- + common/utils/Makefile.am | 1 + + common/utils/full-rw.c | 81 ++++++++++++++++++++++++++++++++++++++++ + common/utils/utils.h | 2 + + filters/cache/blk.c | 10 ++--- + filters/cow/blk.c | 6 +-- + 5 files changed, 92 insertions(+), 8 deletions(-) + create mode 100644 common/utils/full-rw.c + +diff --git a/common/utils/Makefile.am b/common/utils/Makefile.am +index 1708a4c8..14e9dfc4 100644 +--- a/common/utils/Makefile.am ++++ b/common/utils/Makefile.am +@@ -40,6 +40,7 @@ libutils_la_SOURCES = \ + cleanup-nbdkit.c \ + cleanup.h \ + environ.c \ ++ full-rw.c \ + quote.c \ + utils.c \ + utils.h \ +diff --git a/common/utils/full-rw.c b/common/utils/full-rw.c +new file mode 100644 +index 00000000..55b32cdd +--- /dev/null ++++ b/common/utils/full-rw.c +@@ -0,0 +1,81 @@ ++/* nbdkit ++ * Copyright (C) 2021 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. ++ */ ++ ++/* These functions are like pread(2)/pwrite(2) but they always read or ++ * write the full amount, or fail. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++ssize_t ++full_pread (int fd, void *buf, size_t count, off_t offset) ++{ ++ ssize_t ret = 0, r; ++ ++ while (count > 0) { ++ r = pread (fd, buf, count, offset); ++ if (r == -1) return -1; ++ if (r == 0) { ++ /* Presumably the caller wasn't expecting end-of-file here, so ++ * return an error. ++ */ ++ errno = EIO; ++ return -1; ++ } ++ ret += r; ++ offset += r; ++ count -= r; ++ } ++ ++ return ret; ++} ++ ++ssize_t ++full_pwrite (int fd, const void *buf, size_t count, off_t offset) ++{ ++ ssize_t ret = 0, r; ++ ++ while (count > 0) { ++ r = pwrite (fd, buf, count, offset); ++ if (r == -1) return -1; ++ ret += r; ++ offset += r; ++ count -= r; ++ } ++ ++ return ret; ++} +diff --git a/common/utils/utils.h b/common/utils/utils.h +index f8f70212..83397ae1 100644 +--- a/common/utils/utils.h ++++ b/common/utils/utils.h +@@ -40,5 +40,7 @@ extern int set_cloexec (int fd); + extern int set_nonblock (int fd); + extern char **copy_environ (char **env, ...) __attribute__((__sentinel__)); + extern char *make_temporary_directory (void); ++extern ssize_t full_pread (int fd, void *buf, size_t count, off_t offset); ++extern ssize_t full_pwrite (int fd, const void *buf, size_t count, off_t offset); + + #endif /* NBDKIT_UTILS_H */ +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index f85ada35..42bd3779 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -250,7 +250,7 @@ _blk_read_multiple (nbdkit_next *next, + " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, blksize * runblocks, offset) == -1) { ++ if (full_pwrite (fd, block, blksize * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +@@ -262,7 +262,7 @@ _blk_read_multiple (nbdkit_next *next, + } + } + else { /* Read cache. */ +- if (pread (fd, block, blksize * runblocks, offset) == -1) { ++ if (full_pread (fd, block, blksize * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pread: %m"); + return -1; +@@ -339,7 +339,7 @@ blk_cache (nbdkit_next *next, + nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, blksize, offset) == -1) { ++ if (full_pwrite (fd, block, blksize, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +@@ -380,7 +380,7 @@ blk_writethrough (nbdkit_next *next, + nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, blksize, offset) == -1) { ++ if (full_pwrite (fd, block, blksize, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +@@ -414,7 +414,7 @@ blk_write (nbdkit_next *next, + nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, blksize, offset) == -1) { ++ if (full_pwrite (fd, block, blksize, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +diff --git a/filters/cow/blk.c b/filters/cow/blk.c +index 9e6c8879..cebd9454 100644 +--- a/filters/cow/blk.c ++++ b/filters/cow/blk.c +@@ -282,7 +282,7 @@ blk_read_multiple (nbdkit_next *next, + memset (block + n, 0, tail); + } + else if (state == BLOCK_ALLOCATED) { /* Read overlay. */ +- if (pread (fd, block, BLKSIZE * runblocks, offset) == -1) { ++ if (full_pread (fd, block, BLKSIZE * runblocks, offset) == -1) { + *err = errno; + nbdkit_error ("pread: %m"); + return -1; +@@ -357,7 +357,7 @@ blk_cache (nbdkit_next *next, + memset (block + n, 0, tail); + + if (mode == BLK_CACHE_COW) { +- if (pwrite (fd, block, BLKSIZE, offset) == -1) { ++ if (full_pwrite (fd, block, BLKSIZE, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +@@ -376,7 +376,7 @@ blk_write (uint64_t blknum, const uint8_t *block, int *err) + nbdkit_debug ("cow: blk_write block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + +- if (pwrite (fd, block, BLKSIZE, offset) == -1) { ++ if (full_pwrite (fd, block, BLKSIZE, offset) == -1) { + *err = errno; + nbdkit_error ("pwrite: %m"); + return -1; +-- +2.31.1 + diff --git a/0015-cache-Implement-cache-on-read-PATH.patch b/0015-cache-Implement-cache-on-read-PATH.patch new file mode 100644 index 0000000..126ff58 --- /dev/null +++ b/0015-cache-Implement-cache-on-read-PATH.patch @@ -0,0 +1,151 @@ +From b7fe9b7b6c6d317291c76f15910215828bbfd4ff Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 16:16:15 +0100 +Subject: [PATCH] cache: Implement cache-on-read=/PATH + +For virt-v2v we will need to be able to turn cache-on-read on while +performing inspection and modification of the guest, and off when +doing the bulk copy. To do that allow the cache-on-read parameter to +refer to a path where the existence of the path toggles the feature. + +(We could restart nbdkit between these phases, but this change avoids +doing that.) + +(cherry picked from commit c8b575241b15b3bf0adaf15313e67e5ed4270b5a) +--- + filters/cache/blk.c | 2 +- + filters/cache/cache.c | 33 ++++++++++++++++++++------- + filters/cache/cache.h | 10 ++++++-- + filters/cache/nbdkit-cache-filter.pod | 11 ++++++++- + 4 files changed, 44 insertions(+), 12 deletions(-) + +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index 42bd3779..19f79605 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -244,7 +244,7 @@ _blk_read_multiple (nbdkit_next *next, + memset (block + n, 0, tail); + + /* If cache-on-read, copy the blocks to the cache. */ +- if (cache_on_read) { ++ if (cache_on_read ()) { + if (cache_debug_verbose) + nbdkit_debug ("cache: cache-on-read block %" PRIu64 + " (offset %" PRIu64 ")", +diff --git a/filters/cache/cache.c b/filters/cache/cache.c +index 9c081948..8af52106 100644 +--- a/filters/cache/cache.c ++++ b/filters/cache/cache.c +@@ -74,7 +74,8 @@ unsigned blksize; + enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; + int64_t max_size = -1; + unsigned hi_thresh = 95, lo_thresh = 80; +-bool cache_on_read = false; ++enum cor_mode cor_mode = COR_OFF; ++const char *cor_path; + + static int cache_flush (nbdkit_next *next, void *handle, uint32_t flags, + int *err); +@@ -161,12 +162,16 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + } + #endif /* !HAVE_CACHE_RECLAIM */ + else if (strcmp (key, "cache-on-read") == 0) { +- int r; +- +- r = nbdkit_parse_bool (value); +- if (r == -1) +- return -1; +- cache_on_read = r; ++ if (value[0] == '/') { ++ cor_path = value; ++ cor_mode = COR_PATH; ++ } ++ else { ++ int r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ cor_mode = r ? COR_ON : COR_OFF; ++ } + return 0; + } + else { +@@ -177,7 +182,7 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + #define cache_config_help_common \ + "cache=MODE Set cache MODE, one of writeback (default),\n" \ + " writethrough, or unsafe.\n" \ +- "cache-on-read=BOOL Set to true to cache on reads (default false).\n" ++ "cache-on-read=BOOL|/PATH Set to true to cache on reads (default false).\n" + #ifndef HAVE_CACHE_RECLAIM + #define cache_config_help cache_config_help_common + #else +@@ -187,6 +192,18 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + "cache-low-threshold=PCT Percentage of max size where reclaim ends.\n" + #endif + ++/* Decide if cache-on-read is currently on or off. */ ++bool ++cache_on_read (void) ++{ ++ switch (cor_mode) { ++ case COR_ON: return true; ++ case COR_OFF: return false; ++ case COR_PATH: return access (cor_path, F_OK) == 0; ++ default: abort (); ++ } ++} ++ + static int + cache_config_complete (nbdkit_next_config_complete *next, + nbdkit_backend *nxdata) +diff --git a/filters/cache/cache.h b/filters/cache/cache.h +index 2b72221f..a559adef 100644 +--- a/filters/cache/cache.h ++++ b/filters/cache/cache.h +@@ -49,7 +49,13 @@ extern unsigned blksize; + extern int64_t max_size; + extern unsigned hi_thresh, lo_thresh; + +-/* Cache read requests. */ +-extern bool cache_on_read; ++/* Cache on read mode. */ ++extern enum cor_mode { ++ COR_OFF, ++ COR_ON, ++ COR_PATH, ++} cor_mode; ++extern const char *cor_path; ++extern bool cache_on_read (void); + + #endif /* NBDKIT_CACHE_H */ +diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod +index 34fd0b29..2ac307e0 100644 +--- a/filters/cache/nbdkit-cache-filter.pod ++++ b/filters/cache/nbdkit-cache-filter.pod +@@ -8,7 +8,7 @@ nbdkit-cache-filter - nbdkit caching filter + [cache-max-size=SIZE] + [cache-high-threshold=N] + [cache-low-threshold=N] +- [cache-on-read=true|false] ++ [cache-on-read=true|false|/PATH] + [plugin-args...] + + =head1 DESCRIPTION +@@ -87,6 +87,15 @@ the plugin. + + Do not cache read requests (this is the default). + ++=item B ++ ++(nbdkit E 1.28) ++ ++When F (which must be an absolute path) exists, this behaves ++like C, and when it does not exist like ++C. This allows you to control the cache-on-read ++behaviour while nbdkit is running. ++ + =back + + =head1 CACHE MAXIMUM SIZE +-- +2.31.1 + diff --git a/0016-cache-Add-cache-min-block-size-parameter.patch b/0016-cache-Add-cache-min-block-size-parameter.patch new file mode 100644 index 0000000..0d5bc11 --- /dev/null +++ b/0016-cache-Add-cache-min-block-size-parameter.patch @@ -0,0 +1,278 @@ +From 743b49ed9cd8d302d0274fc16ebc7783978b0c2e Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 16:30:26 +0100 +Subject: [PATCH] cache: Add cache-min-block-size parameter + +This allows you to choose a larger block size. I found experimentally +that this improves performance because of locality in access patterns. +The idea came from qcow2 which implicitly does the same thing because +of the relatively large cluster size (32K). + +nbdkit + cache-filter with 4K block size + cache-on-read + curl +(to a very slow remote site): +=> virt-inspector took 22 mins + +same with 64K block size: +=> virt-inspector took 19 mins + +However compared to a qcow2 file using qemu's copy-on-read, backed +with nbdkit + curl we are still a lot slower, possibly because having +the cache inside virt-inspector greatly reduces round trip overhead: +=> virt-inspector took 13 mins + +(cherry picked from commit 4ceacb6caa64e12bd78af5f90e86ee591e055944) +--- + filters/cache/blk.c | 2 +- + filters/cache/cache.c | 36 ++++++++++---- + filters/cache/cache.h | 3 ++ + filters/cache/nbdkit-cache-filter.pod | 9 ++++ + tests/Makefile.am | 2 + + tests/test-cache-block-size.sh | 70 +++++++++++++++++++++++++++ + 6 files changed, 112 insertions(+), 10 deletions(-) + create mode 100755 tests/test-cache-block-size.sh + +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index 19f79605..6276985f 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -149,7 +149,7 @@ blk_init (void) + nbdkit_error ("fstatvfs: %s: %m", tmpdir); + return -1; + } +- blksize = MAX (4096, statvfs.f_bsize); ++ blksize = MAX (min_block_size, statvfs.f_bsize); + nbdkit_debug ("cache: block size: %u", blksize); + + bitmap_init (&bm, blksize, 2 /* bits per block */); +diff --git a/filters/cache/cache.c b/filters/cache/cache.c +index 8af52106..48a20c3b 100644 +--- a/filters/cache/cache.c ++++ b/filters/cache/cache.c +@@ -40,6 +40,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -62,6 +63,7 @@ + #include "blk.h" + #include "reclaim.h" + #include "isaligned.h" ++#include "ispowerof2.h" + #include "minmax.h" + #include "rounding.h" + +@@ -70,7 +72,8 @@ + */ + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +-unsigned blksize; ++unsigned blksize; /* actual block size (picked by blk.c) */ ++unsigned min_block_size = 4096; + enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; + int64_t max_size = -1; + unsigned hi_thresh = 95, lo_thresh = 80; +@@ -80,13 +83,6 @@ const char *cor_path; + static int cache_flush (nbdkit_next *next, void *handle, uint32_t flags, + int *err); + +-static void +-cache_load (void) +-{ +- if (blk_init () == -1) +- exit (EXIT_FAILURE); +-} +- + static void + cache_unload (void) + { +@@ -116,6 +112,19 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + return -1; + } + } ++ else if (strcmp (key, "cache-min-block-size") == 0) { ++ int64_t r; ++ ++ r = nbdkit_parse_size (value); ++ if (r == -1) ++ return -1; ++ if (r < 4096 || !is_power_of_2 (r) || r > UINT_MAX) { ++ nbdkit_error ("cache-min-block-size is not a power of 2, or is too small or too large"); ++ return -1; ++ } ++ min_block_size = r; ++ return 0; ++ } + #ifdef HAVE_CACHE_RECLAIM + else if (strcmp (key, "cache-max-size") == 0) { + int64_t r; +@@ -220,6 +229,15 @@ cache_config_complete (nbdkit_next_config_complete *next, + return next (nxdata); + } + ++static int ++cache_get_ready (int thread_model) ++{ ++ if (blk_init () == -1) ++ return -1; ++ ++ return 0; ++} ++ + /* Get the file size, set the cache size. */ + static int64_t + cache_get_size (nbdkit_next *next, +@@ -691,11 +709,11 @@ cache_cache (nbdkit_next *next, + static struct nbdkit_filter filter = { + .name = "cache", + .longname = "nbdkit caching filter", +- .load = cache_load, + .unload = cache_unload, + .config = cache_config, + .config_complete = cache_config_complete, + .config_help = cache_config_help, ++ .get_ready = cache_get_ready, + .prepare = cache_prepare, + .get_size = cache_get_size, + .can_cache = cache_can_cache, +diff --git a/filters/cache/cache.h b/filters/cache/cache.h +index a559adef..5c32c37c 100644 +--- a/filters/cache/cache.h ++++ b/filters/cache/cache.h +@@ -45,6 +45,9 @@ extern enum cache_mode { + /* Size of a block in the cache. */ + extern unsigned blksize; + ++/* Minimum block size (cache-min-block-size parameter). */ ++extern unsigned min_block_size; ++ + /* Maximum size of the cache and high/low thresholds. */ + extern int64_t max_size; + extern unsigned hi_thresh, lo_thresh; +diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod +index 2ac307e0..9511e91b 100644 +--- a/filters/cache/nbdkit-cache-filter.pod ++++ b/filters/cache/nbdkit-cache-filter.pod +@@ -5,6 +5,7 @@ nbdkit-cache-filter - nbdkit caching filter + =head1 SYNOPSIS + + nbdkit --filter=cache plugin [cache=writeback|writethrough|unsafe] ++ [cache-min-block-size=SIZE] + [cache-max-size=SIZE] + [cache-high-threshold=N] + [cache-low-threshold=N] +@@ -59,6 +60,14 @@ This is dangerous and can cause data loss, but this may be acceptable + if you only use it for testing or with data that you don't care about + or can cheaply reconstruct. + ++=item BSIZE ++ ++Set the minimum block size used by the cache. This must be a power of ++2 and E 4096. ++ ++The default is 4096, or the block size of the filesystem which ++contains the temporary file storing the cache (whichever is larger). ++ + =item BSIZE + + =item BN +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 9630205d..a038eabc 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1371,12 +1371,14 @@ EXTRA_DIST += test-blocksize.sh test-blocksize-extents.sh + # cache filter test. + TESTS += \ + test-cache.sh \ ++ test-cache-block-size.sh \ + test-cache-on-read.sh \ + test-cache-max-size.sh \ + test-cache-unaligned.sh \ + $(NULL) + EXTRA_DIST += \ + test-cache.sh \ ++ test-cache-block-size.sh \ + test-cache-on-read.sh \ + test-cache-max-size.sh \ + test-cache-unaligned.sh \ +diff --git a/tests/test-cache-block-size.sh b/tests/test-cache-block-size.sh +new file mode 100755 +index 00000000..a2a27407 +--- /dev/null ++++ b/tests/test-cache-block-size.sh +@@ -0,0 +1,70 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2021 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_filter cache ++requires_nbdsh_uri ++ ++sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) ++files="cache-block-size.img $sock cache-block-size.pid" ++rm -f $files ++cleanup_fn rm -f $files ++ ++# Create an empty base image. ++truncate -s 128K cache-block-size.img ++ ++# Run nbdkit with the caching filter. ++start_nbdkit -P cache-block-size.pid -U $sock --filter=cache \ ++ file cache-block-size.img cache-min-block-size=64K ++ ++nbdsh --connect "nbd+unix://?socket=$sock" \ ++ -c ' ++# Write some pattern data to the overlay and check it reads back OK. ++buf = b"abcd" * 16384 ++h.pwrite(buf, 32768) ++zero = h.pread(32768, 0) ++assert zero == bytearray(32768) ++buf2 = h.pread(65536, 32768) ++assert buf == buf2 ++ ++# Flushing should write through to the underlying file. ++h.flush() ++ ++with open("cache-block-size.img", "rb") as file: ++ zero = file.read(32768) ++ assert zero == bytearray(32768) ++ buf2 = file.read(65536) ++ assert buf == buf2 ++' +-- +2.31.1 + diff --git a/0017-cache-cow-Use-a-64K-block-size-by-default.patch b/0017-cache-cow-Use-a-64K-block-size-by-default.patch new file mode 100644 index 0000000..44d09b3 --- /dev/null +++ b/0017-cache-cow-Use-a-64K-block-size-by-default.patch @@ -0,0 +1,138 @@ +From 70e0df6462c34c4946b64e172d163b58121cf424 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Mon, 26 Jul 2021 17:39:23 +0100 +Subject: [PATCH] cache, cow: Use a 64K block size by default + +Based on the results presented in the previous commit, use a 64K block +size by default in both the cache and cow filters. For the cache +filter you could go back to a 4K block size if you wanted by using the +cache-min-block-size=4K parameter. For cow it is compiled in so +cannot be adjusted. + +(cherry picked from commit c1905b0a28677d961babdb16d6f30ae61042c825) +--- + filters/cache/cache.c | 2 +- + filters/cache/nbdkit-cache-filter.pod | 4 ++-- + filters/cow/blk.h | 2 +- + tests/test-cache-block-size.sh | 2 +- + tests/test-cow-extents1.sh | 33 +++++++++++++++------------ + 5 files changed, 23 insertions(+), 20 deletions(-) + +diff --git a/filters/cache/cache.c b/filters/cache/cache.c +index 48a20c3b..f7b01039 100644 +--- a/filters/cache/cache.c ++++ b/filters/cache/cache.c +@@ -73,7 +73,7 @@ + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + + unsigned blksize; /* actual block size (picked by blk.c) */ +-unsigned min_block_size = 4096; ++unsigned min_block_size = 65536; + enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; + int64_t max_size = -1; + unsigned hi_thresh = 95, lo_thresh = 80; +diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod +index 9511e91b..99707373 100644 +--- a/filters/cache/nbdkit-cache-filter.pod ++++ b/filters/cache/nbdkit-cache-filter.pod +@@ -65,8 +65,8 @@ or can cheaply reconstruct. + Set the minimum block size used by the cache. This must be a power of + 2 and E 4096. + +-The default is 4096, or the block size of the filesystem which +-contains the temporary file storing the cache (whichever is larger). ++The default is 64K, or the block size of the filesystem which contains ++the temporary file storing the cache (whichever is larger). + + =item BSIZE + +diff --git a/filters/cow/blk.h b/filters/cow/blk.h +index b066c602..1bc85283 100644 +--- a/filters/cow/blk.h ++++ b/filters/cow/blk.h +@@ -36,7 +36,7 @@ + /* Size of a block in the overlay. A 4K block size means that we need + * 64 MB of memory to store the bitmap for a 1 TB underlying image. + */ +-#define BLKSIZE 4096 ++#define BLKSIZE 65536 + + /* Initialize the overlay and bitmap. */ + extern int blk_init (void); +diff --git a/tests/test-cache-block-size.sh b/tests/test-cache-block-size.sh +index a2a27407..d20cc940 100755 +--- a/tests/test-cache-block-size.sh ++++ b/tests/test-cache-block-size.sh +@@ -47,7 +47,7 @@ truncate -s 128K cache-block-size.img + + # Run nbdkit with the caching filter. + start_nbdkit -P cache-block-size.pid -U $sock --filter=cache \ +- file cache-block-size.img cache-min-block-size=64K ++ file cache-block-size.img cache-min-block-size=4K + + nbdsh --connect "nbd+unix://?socket=$sock" \ + -c ' +diff --git a/tests/test-cow-extents1.sh b/tests/test-cow-extents1.sh +index 8e0e0383..ebfd83f6 100755 +--- a/tests/test-cow-extents1.sh ++++ b/tests/test-cow-extents1.sh +@@ -65,7 +65,7 @@ cleanup_fn rm -f $files + + # Create a base file which is half allocated, half sparse. + dd if=/dev/urandom of=$base count=128 bs=1K +-truncate -s 256K $base ++truncate -s 4M $base + lastmod="$(stat -c "%y" $base)" + + # Run nbdkit with a COW overlay. +@@ -76,30 +76,33 @@ uri="nbd+unix:///?socket=$sock" + nbdinfo --map "$uri" > $out + cat $out + if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 131072 0 +- 131072 131072 3" ]; then ++ 131072 4063232 3" ]; then + echo "$0: unexpected initial file map" + exit 1 + fi + + # Punch some holes. + nbdsh -u "$uri" \ +- -c 'h.trim(4096, 4096)' \ +- -c 'h.trim(4098, 16383)' \ +- -c 'h.pwrite(b"1"*4096, 65536)' \ +- -c 'h.trim(8192, 131072)' \ +- -c 'h.pwrite(b"2"*8192, 196608)' ++ -c 'bs = 65536' \ ++ -c 'h.trim(bs, bs)' \ ++ -c 'h.trim(bs+2, 4*bs-1)' \ ++ -c 'h.pwrite(b"1"*bs, 16*bs)' \ ++ -c 'h.trim(2*bs, 32*bs)' \ ++ -c 'h.pwrite(b"2"*(2*bs), 48*bs)' + + # The extents map should be fully allocated. + nbdinfo --map "$uri" > $out + cat $out +-if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 4096 0 +- 4096 4096 3 +- 8192 8192 0 +- 16384 4096 3 +- 20480 110592 0 +- 131072 65536 3 +- 196608 8192 0 +- 204800 57344 3" ]; then ++if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 65536 0 ++ 65536 131072 3 ++ 196608 65536 0 ++ 262144 65536 3 ++ 327680 65536 0 ++ 393216 655360 3 ++ 1048576 65536 0 ++ 1114112 2031616 3 ++ 3145728 131072 0 ++ 3276800 917504 3" ]; then + echo "$0: unexpected trimmed file map" + exit 1 + fi +-- +2.31.1 + diff --git a/0018-cache-Refactor-printing-state-into-new-function.patch b/0018-cache-Refactor-printing-state-into-new-function.patch new file mode 100644 index 0000000..59401dc --- /dev/null +++ b/0018-cache-Refactor-printing-state-into-new-function.patch @@ -0,0 +1,50 @@ +From 9cf300962b9f453972deaf744c202327c42970db Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 27 Jul 2021 21:16:30 +0100 +Subject: [PATCH] cache: Refactor printing state into new function + +This minor refactoring just makes the cache and cow filters' blk.c a +little bit more similar. + +(cherry picked from commit bdb86ea14c00a950f2a2d34071ac1e0799d29132) +--- + filters/cache/blk.c | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/filters/cache/blk.c b/filters/cache/blk.c +index 6276985f..e50a7f24 100644 +--- a/filters/cache/blk.c ++++ b/filters/cache/blk.c +@@ -94,6 +94,17 @@ enum bm_entry { + BLOCK_DIRTY = 3, + }; + ++static const char * ++state_to_string (enum bm_entry state) ++{ ++ switch (state) { ++ case BLOCK_NOT_CACHED: return "not cached"; ++ case BLOCK_CLEAN: return "clean"; ++ case BLOCK_DIRTY: return "dirty"; ++ default: abort (); ++ } ++} ++ + /* Extra debugging (-D cache.verbose=1). */ + NBDKIT_DLL_PUBLIC int cache_debug_verbose = 0; + +@@ -312,10 +323,7 @@ blk_cache (nbdkit_next *next, + nbdkit_debug ("cache: blk_cache block %" PRIu64 + " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, +- state == BLOCK_NOT_CACHED ? "not cached" : +- state == BLOCK_CLEAN ? "clean" : +- state == BLOCK_DIRTY ? "dirty" : +- "unknown"); ++ state_to_string (state)); + + if (state == BLOCK_NOT_CACHED) { + /* Read underlying plugin, copy to cache regardless of cache-on-read. */ +-- +2.31.1 + diff --git a/0019-tests-cache-Test-cache-on-read-option-really-caches.patch b/0019-tests-cache-Test-cache-on-read-option-really-caches.patch new file mode 100644 index 0000000..1c8f34c --- /dev/null +++ b/0019-tests-cache-Test-cache-on-read-option-really-caches.patch @@ -0,0 +1,147 @@ +From a203296a125ce6a28d1d73d248f0899754c3677c Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 29 Jul 2021 20:16:43 +0100 +Subject: [PATCH] tests: cache: Test cache-on-read option really caches + +By making use of the delay filter to add a penalty for hitting the +plugin we can check whether or not the cache-on-read option is +working. + +(cherry picked from commit 3ae7aa533bb9322ab6dc6deecb687ded76634ab4) +--- + tests/Makefile.am | 2 + + tests/test-cache-on-read-caches.sh | 87 ++++++++++++++++++++++++++++++ + tests/test-cache-on-read.sh | 5 -- + 3 files changed, 89 insertions(+), 5 deletions(-) + create mode 100755 tests/test-cache-on-read-caches.sh + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index a038eabc..51ca913a 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1373,6 +1373,7 @@ TESTS += \ + test-cache.sh \ + test-cache-block-size.sh \ + test-cache-on-read.sh \ ++ test-cache-on-read-caches.sh \ + test-cache-max-size.sh \ + test-cache-unaligned.sh \ + $(NULL) +@@ -1380,6 +1381,7 @@ EXTRA_DIST += \ + test-cache.sh \ + test-cache-block-size.sh \ + test-cache-on-read.sh \ ++ test-cache-on-read-caches.sh \ + test-cache-max-size.sh \ + test-cache-unaligned.sh \ + $(NULL) +diff --git a/tests/test-cache-on-read-caches.sh b/tests/test-cache-on-read-caches.sh +new file mode 100755 +index 00000000..80b34159 +--- /dev/null ++++ b/tests/test-cache-on-read-caches.sh +@@ -0,0 +1,87 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2021 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_filter cache ++requires_filter delay ++requires_nbdsh_uri ++ ++sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) ++files="$sock cache-on-read-caches.pid" ++rm -f $files ++cleanup_fn rm -f $files ++ ++# Run nbdkit with the cache filter, cache-on-read and a read delay. ++start_nbdkit -P cache-on-read-caches.pid -U $sock \ ++ --filter=cache --filter=delay \ ++ memory 64K cache-on-read=true rdelay=10 ++ ++nbdsh --connect "nbd+unix://?socket=$sock" \ ++ -c ' ++from time import time ++ ++# First read should suffer a penalty. Because we are reading ++# a single 64K block (same size as the cache block), we should ++# only suffer one penalty of approx. 10 seconds. ++st = time() ++zb = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert et-st >= 10 ++assert zb == bytearray(65536) ++ ++# Second read should not suffer a penalty. ++st = time() ++zb = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert el < 10 ++assert zb == bytearray(65536) ++ ++# Write something. ++buf = b"abcd" * 16384 ++h.pwrite(buf, 0) ++ ++# Reading back should be quick since it is stored in the overlay. ++st = time() ++buf2 = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert el < 10 ++assert buf == buf2 ++' +diff --git a/tests/test-cache-on-read.sh b/tests/test-cache-on-read.sh +index f8584dcd..85ca83d4 100755 +--- a/tests/test-cache-on-read.sh ++++ b/tests/test-cache-on-read.sh +@@ -56,9 +56,4 @@ zero = h.pread(32768, 0) + assert zero == bytearray(32768) + buf2 = h.pread(65536, 32768) + assert buf == buf2 +- +-# XXX Suggestion to improve this test: Use the delay filter below the +-# cache filter, and time reads to prove that the second read is faster +-# because it is not going through the delay filter and plugin. +-# XXX second h.pread here ... + ' +-- +2.31.1 + diff --git a/0020-cow-Implement-cow-on-read.patch b/0020-cow-Implement-cow-on-read.patch new file mode 100644 index 0000000..5578f1f --- /dev/null +++ b/0020-cow-Implement-cow-on-read.patch @@ -0,0 +1,457 @@ +From 330a2b99378c5bb6c57ab8ffb8069d21e64d5312 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 27 Jul 2021 23:01:52 +0100 +Subject: [PATCH] cow: Implement cow-on-read + +This is very similar to the nbdkit-cache-filter cache-on-read flag. + +(cherry picked from commit bd93b3f27246f917de48a6cc2525d9c424c07976) +--- + filters/cow/blk.c | 21 ++++++-- + filters/cow/blk.h | 10 ++-- + filters/cow/cow.c | 56 ++++++++++++++++---- + filters/cow/nbdkit-cow-filter.pod | 17 ++++++ + tests/Makefile.am | 4 ++ + tests/test-cow-on-read-caches.sh | 87 +++++++++++++++++++++++++++++++ + tests/test-cow-on-read.sh | 59 +++++++++++++++++++++ + 7 files changed, 236 insertions(+), 18 deletions(-) + create mode 100755 tests/test-cow-on-read-caches.sh + create mode 100755 tests/test-cow-on-read.sh + +diff --git a/filters/cow/blk.c b/filters/cow/blk.c +index cebd9454..9d42b5fc 100644 +--- a/filters/cow/blk.c ++++ b/filters/cow/blk.c +@@ -230,7 +230,7 @@ blk_status (uint64_t blknum, bool *present, bool *trimmed) + int + blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, +- uint8_t *block, int *err) ++ uint8_t *block, bool cow_on_read, int *err) + { + off_t offset = blknum * BLKSIZE; + enum bm_entry state; +@@ -280,6 +280,19 @@ blk_read_multiple (nbdkit_next *next, + * zeroing the tail. + */ + memset (block + n, 0, tail); ++ ++ /* If cow-on-read is true then copy the blocks to the cache and ++ * set them as allocated. ++ */ ++ if (cow_on_read) { ++ if (full_pwrite (fd, block, BLKSIZE * runblocks, offset) == -1) { ++ *err = errno; ++ nbdkit_error ("pwrite: %m"); ++ return -1; ++ } ++ for (b = 0; b < runblocks; ++b) ++ bitmap_set_blk (&bm, blknum+b, BLOCK_ALLOCATED); ++ } + } + else if (state == BLOCK_ALLOCATED) { /* Read overlay. */ + if (full_pread (fd, block, BLKSIZE * runblocks, offset) == -1) { +@@ -301,14 +314,14 @@ blk_read_multiple (nbdkit_next *next, + blknum + runblocks, + nrblocks - runblocks, + block + BLKSIZE * runblocks, +- err); ++ cow_on_read, err); + } + + int + blk_read (nbdkit_next *next, +- uint64_t blknum, uint8_t *block, int *err) ++ uint64_t blknum, uint8_t *block, bool cow_on_read, int *err) + { +- return blk_read_multiple (next, blknum, 1, block, err); ++ return blk_read_multiple (next, blknum, 1, block, cow_on_read, err); + } + + int +diff --git a/filters/cow/blk.h b/filters/cow/blk.h +index 1bc85283..b7e6f092 100644 +--- a/filters/cow/blk.h ++++ b/filters/cow/blk.h +@@ -52,14 +52,16 @@ extern void blk_status (uint64_t blknum, bool *present, bool *trimmed); + + /* Read a single block from the overlay or plugin. */ + extern int blk_read (nbdkit_next *next, +- uint64_t blknum, uint8_t *block, int *err) +- __attribute__((__nonnull__ (1, 3, 4))); ++ uint64_t blknum, uint8_t *block, ++ bool cow_on_read, int *err) ++ __attribute__((__nonnull__ (1, 3, 5))); + + /* Read multiple blocks from the overlay or plugin. */ + extern int blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, +- uint8_t *block, int *err) +- __attribute__((__nonnull__ (1, 4, 5))); ++ uint8_t *block, ++ bool cow_on_read, int *err) ++ __attribute__((__nonnull__ (1, 4, 6))); + + /* Cache mode for blocks not already in overlay */ + enum cache_mode { +diff --git a/filters/cow/cow.c b/filters/cow/cow.c +index f74c0a34..74fcd61c 100644 +--- a/filters/cow/cow.c ++++ b/filters/cow/cow.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -59,6 +60,15 @@ static pthread_mutex_t rmw_lock = PTHREAD_MUTEX_INITIALIZER; + + bool cow_on_cache; + ++/* Cache on read ("cow-on-read") mode. */ ++extern enum cor_mode { ++ COR_OFF, ++ COR_ON, ++ COR_PATH, ++} cor_mode; ++enum cor_mode cor_mode = COR_OFF; ++const char *cor_path; ++ + static void + cow_load (void) + { +@@ -85,13 +95,39 @@ cow_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + cow_on_cache = r; + return 0; + } ++ else if (strcmp (key, "cow-on-read") == 0) { ++ if (value[0] == '/') { ++ cor_path = value; ++ cor_mode = COR_PATH; ++ } ++ else { ++ int r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ cor_mode = r ? COR_ON : COR_OFF; ++ } ++ return 0; ++ } + else { + return next (nxdata, key, value); + } + } + + #define cow_config_help \ +- "cow-on-cache= Set to true to treat client cache requests as writes.\n" ++ "cow-on-cache= Copy cache (prefetch) requests to the overlay.\n" \ ++ "cow-on-read=|/PATH Copy read requests to the overlay." ++ ++/* Decide if cow-on-read is currently on or off. */ ++bool ++cow_on_read (void) ++{ ++ switch (cor_mode) { ++ case COR_ON: return true; ++ case COR_OFF: return false; ++ case COR_PATH: return access (cor_path, F_OK) == 0; ++ default: abort (); ++ } ++} + + static void * + cow_open (nbdkit_next_open *next, nbdkit_context *nxdata, +@@ -230,7 +266,7 @@ cow_pread (nbdkit_next *next, + uint64_t n = MIN (BLKSIZE - blkoffs, count); + + assert (block); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r == -1) + return -1; + +@@ -245,7 +281,7 @@ cow_pread (nbdkit_next *next, + /* Aligned body */ + nrblocks = count / BLKSIZE; + if (nrblocks > 0) { +- r = blk_read_multiple (next, blknum, nrblocks, buf, err); ++ r = blk_read_multiple (next, blknum, nrblocks, buf, cow_on_read (), err); + if (r == -1) + return -1; + +@@ -258,7 +294,7 @@ cow_pread (nbdkit_next *next, + /* Unaligned tail */ + if (count) { + assert (block); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r == -1) + return -1; + +@@ -299,7 +335,7 @@ cow_pwrite (nbdkit_next *next, + */ + assert (block); + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memcpy (&block[blkoffs], buf, n); + r = blk_write (blknum, block, err); +@@ -329,7 +365,7 @@ cow_pwrite (nbdkit_next *next, + if (count) { + assert (block); + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memcpy (block, buf, count); + r = blk_write (blknum, block, err); +@@ -379,7 +415,7 @@ cow_zero (nbdkit_next *next, + * Hold the rmw_lock over the whole operation. + */ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memset (&block[blkoffs], 0, n); + r = blk_write (blknum, block, err); +@@ -411,7 +447,7 @@ cow_zero (nbdkit_next *next, + /* Unaligned tail */ + if (count) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memset (&block[count], 0, BLKSIZE - count); + r = blk_write (blknum, block, err); +@@ -455,7 +491,7 @@ cow_trim (nbdkit_next *next, + * Hold the lock over the whole operation. + */ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memset (&block[blkoffs], 0, n); + r = blk_write (blknum, block, err); +@@ -482,7 +518,7 @@ cow_trim (nbdkit_next *next, + /* Unaligned tail */ + if (count) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&rmw_lock); +- r = blk_read (next, blknum, block, err); ++ r = blk_read (next, blknum, block, cow_on_read (), err); + if (r != -1) { + memset (&block[count], 0, BLKSIZE - count); + r = blk_write (blknum, block, err); +diff --git a/filters/cow/nbdkit-cow-filter.pod b/filters/cow/nbdkit-cow-filter.pod +index 2a693ebe..6366d8a8 100644 +--- a/filters/cow/nbdkit-cow-filter.pod ++++ b/filters/cow/nbdkit-cow-filter.pod +@@ -62,6 +62,23 @@ the data from the plugin into the overlay. + Do not save data from cache (prefetch) requests in the overlay. This + leaves the overlay as small as possible. This is the default. + ++=item B ++ ++When the client issues a read request, copy the data into the overlay ++so that the same data can be served more quickly later. ++ ++=item B ++ ++Do not save data from read requests in the overlay. This leaves the ++overlay as small as possible. This is the default. ++ ++=item B ++ ++When F (which must be an absolute path) exists, this behaves ++like C, and when it does not exist like ++C. This allows you to control the C ++behaviour while nbdkit is running. ++ + =back + + =head1 EXAMPLES +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 51ca913a..edc8d66d 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1407,6 +1407,8 @@ TESTS += \ + test-cow-extents1.sh \ + test-cow-extents2.sh \ + test-cow-extents-large.sh \ ++ test-cow-on-read.sh \ ++ test-cow-on-read-caches.sh \ + test-cow-unaligned.sh \ + $(NULL) + endif +@@ -1417,6 +1419,8 @@ EXTRA_DIST += \ + test-cow-extents2.sh \ + test-cow-extents-large.sh \ + test-cow-null.sh \ ++ test-cow-on-read.sh \ ++ test-cow-on-read-caches.sh \ + test-cow-unaligned.sh \ + $(NULL) + +diff --git a/tests/test-cow-on-read-caches.sh b/tests/test-cow-on-read-caches.sh +new file mode 100755 +index 00000000..c5b60198 +--- /dev/null ++++ b/tests/test-cow-on-read-caches.sh +@@ -0,0 +1,87 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2021 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_filter cow ++requires_filter delay ++requires_nbdsh_uri ++ ++sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) ++files="$sock cow-on-read-caches.pid" ++rm -f $files ++cleanup_fn rm -f $files ++ ++# Run nbdkit with the cow filter, cow-on-read and a read delay. ++start_nbdkit -P cow-on-read-caches.pid -U $sock \ ++ --filter=cow --filter=delay \ ++ memory 64K cow-on-read=true rdelay=10 ++ ++nbdsh --connect "nbd+unix://?socket=$sock" \ ++ -c ' ++from time import time ++ ++# First read should suffer a penalty. Because we are reading ++# a single 64K block (same size as the COW block), we should ++# only suffer one penalty of approx. 10 seconds. ++st = time() ++zb = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert et-st >= 10 ++assert zb == bytearray(65536) ++ ++# Second read should not suffer a penalty. ++st = time() ++zb = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert el < 10 ++assert zb == bytearray(65536) ++ ++# Write something. ++buf = b"abcd" * 16384 ++h.pwrite(buf, 0) ++ ++# Reading back should be quick since it is stored in the overlay. ++st = time() ++buf2 = h.pread(65536, 0) ++et = time() ++el = et-st ++print("elapsed time: %g" % el) ++assert el < 10 ++assert buf == buf2 ++' +diff --git a/tests/test-cow-on-read.sh b/tests/test-cow-on-read.sh +new file mode 100755 +index 00000000..4f58b33b +--- /dev/null ++++ b/tests/test-cow-on-read.sh +@@ -0,0 +1,59 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2021 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_filter cow ++requires_nbdsh_uri ++ ++sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) ++files="$sock cow-on-read.pid" ++rm -f $files ++cleanup_fn rm -f $files ++ ++# Run nbdkit with the cow filter and cow-on-read. ++start_nbdkit -P cow-on-read.pid -U $sock \ ++ --filter=cow \ ++ memory 128K cow-on-read=true ++ ++nbdsh --connect "nbd+unix://?socket=$sock" \ ++ -c ' ++# Write some pattern data to the overlay and check it reads back OK. ++buf = b"abcd" * 16384 ++h.pwrite(buf, 32768) ++zero = h.pread(32768, 0) ++assert zero == bytearray(32768) ++buf2 = h.pread(65536, 32768) ++assert buf == buf2 ++' +-- +2.31.1 + diff --git a/0021-delay-Add-delay-open-and-delay-close.patch b/0021-delay-Add-delay-open-and-delay-close.patch new file mode 100644 index 0000000..43910a8 --- /dev/null +++ b/0021-delay-Add-delay-open-and-delay-close.patch @@ -0,0 +1,172 @@ +From f1fa60f1388bf177ebd83625cc13a164936a187c Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Fri, 30 Jul 2021 10:19:57 +0100 +Subject: [PATCH] delay: Add delay-open and delay-close + +Useful for simulating VDDK which has very slow connection. + +(cherry picked from commit de8dcd3a34a38b088a0f9a6f8ca754702ad1f598) +--- + filters/delay/delay.c | 60 ++++++++++++++++++++++++++- + filters/delay/nbdkit-delay-filter.pod | 27 ++++++++++-- + 2 files changed, 82 insertions(+), 5 deletions(-) + +diff --git a/filters/delay/delay.c b/filters/delay/delay.c +index 7e7fe195..5bd21321 100644 +--- a/filters/delay/delay.c ++++ b/filters/delay/delay.c +@@ -49,6 +49,8 @@ static int delay_trim_ms = 0; /* trim delay (milliseconds) */ + static int delay_extents_ms = 0;/* extents delay (milliseconds) */ + static int delay_cache_ms = 0; /* cache delay (milliseconds) */ + static int delay_fast_zero = 1; /* whether delaying zero includes fast zero */ ++static int delay_open_ms = 0; /* open delay (milliseconds) */ ++static int delay_close_ms = 0; /* close delay (milliseconds) */ + + static int + parse_delay (const char *key, const char *value) +@@ -128,6 +130,18 @@ cache_delay (int *err) + return delay (delay_cache_ms, err); + } + ++static int ++open_delay (int *err) ++{ ++ return delay (delay_open_ms, err); ++} ++ ++static int ++close_delay (int *err) ++{ ++ return delay (delay_close_ms, err); ++} ++ + /* Called for each key=value passed on the command line. */ + static int + delay_config (nbdkit_next_config *next, nbdkit_backend *nxdata, +@@ -191,6 +205,18 @@ delay_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + return -1; + return 0; + } ++ else if (strcmp (key, "delay-open") == 0) { ++ delay_open_ms = parse_delay (key, value); ++ if (delay_open_ms == -1) ++ return -1; ++ return 0; ++ } ++ else if (strcmp (key, "delay-close") == 0) { ++ delay_close_ms = parse_delay (key, value); ++ if (delay_close_ms == -1) ++ return -1; ++ return 0; ++ } + else + return next (nxdata, key, value); + } +@@ -204,7 +230,9 @@ delay_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + "delay-extents=[ms] Extents delay in seconds/milliseconds.\n" \ + "delay-cache=[ms] Cache delay in seconds/milliseconds.\n" \ + "wdelay=[ms] Write, zero and trim delay in secs/msecs.\n" \ +- "delay-fast-zero= Delay fast zero requests (default true).\n" ++ "delay-fast-zero= Delay fast zero requests (default true).\n" \ ++ "delay-open=[ms] Open delay in seconds/milliseconds.\n" \ ++ "delay-close=[ms] Close delay in seconds/milliseconds." + + /* Override the plugin's .can_fast_zero if needed */ + static int +@@ -217,6 +245,34 @@ delay_can_fast_zero (nbdkit_next *next, + return next->can_fast_zero (next); + } + ++/* Open connection. */ ++static void * ++delay_open (nbdkit_next_open *next, nbdkit_context *nxdata, ++ int readonly, const char *exportname, int is_tls) ++{ ++ int err; ++ ++ if (open_delay (&err) == -1) { ++ errno = err; ++ nbdkit_error ("delay: %m"); ++ return NULL; ++ } ++ ++ if (next (nxdata, readonly, exportname) == -1) ++ return NULL; ++ ++ return NBDKIT_HANDLE_NOT_NEEDED; ++} ++ ++/* Close connection. */ ++static void ++delay_close (void *handle) ++{ ++ int err; ++ ++ close_delay (&err); ++} ++ + /* Read data. */ + static int + delay_pread (nbdkit_next *next, +@@ -294,6 +350,8 @@ static struct nbdkit_filter filter = { + .config = delay_config, + .config_help = delay_config_help, + .can_fast_zero = delay_can_fast_zero, ++ .open = delay_open, ++ .close = delay_close, + .pread = delay_pread, + .pwrite = delay_pwrite, + .zero = delay_zero, +diff --git a/filters/delay/nbdkit-delay-filter.pod b/filters/delay/nbdkit-delay-filter.pod +index d6961a9e..11ae544b 100644 +--- a/filters/delay/nbdkit-delay-filter.pod ++++ b/filters/delay/nbdkit-delay-filter.pod +@@ -9,10 +9,15 @@ nbdkit-delay-filter - nbdkit delay filter + nbdkit --filter=delay plugin rdelay=NNms wdelay=NNms [plugin-args...] + + nbdkit --filter=delay plugin [plugin-args ...] +- delay-read=(SECS|NNms) delay-write=(SECS|NNms) +- delay-zero=(SECS|NNms) delay-trim=(SECS|NNms) +- delay-extents=(SECS|NNms) delay-cache=(SECS|NNms) ++ delay-read=(SECS|NNms) ++ delay-write=(SECS|NNms) ++ delay-zero=(SECS|NNms) ++ delay-trim=(SECS|NNms) ++ delay-extents=(SECS|NNms) ++ delay-cache=(SECS|NNms) + delay-fast-zero=BOOL ++ delay-open=(SECS|NNms) ++ delay-close=(SECS|NNms) + + =head1 DESCRIPTION + +@@ -108,6 +113,20 @@ delay as any other zero request; but setting this parameter to false + instantly fails a fast zero response without waiting for or consulting + the plugin. + ++=item BSECS ++ ++=item BNNB ++ ++=item BSECS ++ ++=item BNNB ++ ++(nbdkit E 1.28) ++ ++Delay open and close operations by C seconds or C ++milliseconds. Open corresponds to client connection. Close may not ++be visible to clients if they abruptly disconnect. ++ + =back + + =head1 FILES +@@ -140,4 +159,4 @@ Richard W.M. Jones + + =head1 COPYRIGHT + +-Copyright (C) 2018 Red Hat Inc. ++Copyright (C) 2018-2021 Red Hat Inc. +-- +2.31.1 + diff --git a/copy-patches.sh b/copy-patches.sh index 2682a17..2aaf42c 100755 --- a/copy-patches.sh +++ b/copy-patches.sh @@ -6,7 +6,7 @@ set -e # directory. Use it like this: # ./copy-patches.sh -rhel_version=8.3 +rhel_version=9.0 # Check we're in the right directory. if [ ! -f nbdkit.spec ]; then diff --git a/nbdkit.spec b/nbdkit.spec index f4efeee..7670b29 100644 --- a/nbdkit.spec +++ b/nbdkit.spec @@ -51,7 +51,7 @@ ExclusiveArch: x86_64 Name: nbdkit Version: 1.26.2 -Release: 1%{?dist}.1 +Release: 2%{?dist} Summary: NBD server License: BSD @@ -72,14 +72,31 @@ Source2: libguestfs.keyring # Maintainer script which helps with handling patches. Source3: copy-patches.sh -# Patches in upstream stable-1.26 branch. -Patch0001: 0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch -Patch0002: 0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch -Patch0003: 0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch -Patch0004: 0004-ocaml-Rearrange-the-callbacks.patch -Patch0005: 0005-ocaml-Fix-comment-on-plugin-.pread-field.patch -Patch0006: 0006-docs-Correct-selinux-label-example.patch -Patch0007: 0007-cow-Fix-assert-failure-in-cow_extents.patch +# Patches come from the upstream repository: +# https://gitlab.com/nbdkit/nbdkit/-/commits/rhel-9.0/ + +# Patches. +Patch0001: 0001-ocaml-Call-caml_shutdown-when-unloading-the-plugin.patch +Patch0002: 0002-ocaml-Fix-valgrinding-by-only-ignoring-caml_stat_all.patch +Patch0003: 0003-ocaml-tests-Actually-call-.get_ready-method-in-test-.patch +Patch0004: 0004-ocaml-Rearrange-the-callbacks.patch +Patch0005: 0005-ocaml-Fix-comment-on-plugin-.pread-field.patch +Patch0006: 0006-docs-Correct-selinux-label-example.patch +Patch0007: 0007-cow-Fix-assert-failure-in-cow_extents.patch +Patch0008: 0008-cache-Fix-misleading-LRU-diagram-and-comment.patch +Patch0009: 0009-docs-Improve-documentation-of-.can_cache-and-.cache-.patch +Patch0010: 0010-cow-Improve-documentation-of-cow-on-cache-option.patch +Patch0011: 0011-tests-cache-Simplify-test-cache-on-read.sh.patch +Patch0012: 0012-cache-Reduce-verbosity-of-debugging.patch +Patch0013: 0013-cache-cow-Add-blk_read_multiple-function.patch +Patch0014: 0014-cache-cow-Use-full-pread-pwrite-operations.patch +Patch0015: 0015-cache-Implement-cache-on-read-PATH.patch +Patch0016: 0016-cache-Add-cache-min-block-size-parameter.patch +Patch0017: 0017-cache-cow-Use-a-64K-block-size-by-default.patch +Patch0018: 0018-cache-Refactor-printing-state-into-new-function.patch +Patch0019: 0019-tests-cache-Test-cache-on-read-option-really-caches.patch +Patch0020: 0020-cow-Implement-cow-on-read.patch +Patch0021: 0021-delay-Add-delay-open-and-delay-close.patch BuildRequires: make %if 0%{patches_touch_autotools} @@ -1250,8 +1267,15 @@ export LIBGUESTFS_TRACE=1 %changelog -* Mon Jul 26 2021 Richard W.M. Jones - 1.26.2-1.1 -- Add patches from upstream stable-1.26 branch, fixing a virt-v2v crash +* Fri Jul 30 2021 Richard W.M. Jones - 1.26.2-2 +- More efficient cache and cow filters. +- Add nbdkit-cow-filter cow-on-read option. +- Add nbdkit-cache-filter cache-on-read=/PATH. +- Add nbdkit-cache-filter cache-min-block-size option. +- Add nbdkit-delay-filter delay-open and delay-close options. +- Reduce verbosity of debugging from virt-v2v. +- Miscellaneous bugfixes + resolves: rhbz#1950632 * Mon Jul 05 2021 Richard W.M. Jones - 1.26.2-1 - New upstream stable version 1.26.2.