[Libguestfs] [PATCH nbdkit] New filter: gzip

Richard W.M. Jones rjones at redhat.com
Fri Jul 10 11:50:55 UTC 2020


Turn the existing nbdkit-gzip-plugin into a filter so it can be
applied on top of files or other sources:

  nbdkit file --filter=gzip file.gz
  nbdkit curl --filter=gzip https://example.com/disk.gz

Because of the nature of the gzip format which is not blocked based
and thus not seekable, this filter caches the whole uncompressed file
in a hidden temporary file.  This is required in order to implement
.get_size.  See this link for a more detailed explanation:
https://stackoverflow.com/a/9213826

This commit deprecates nbdkit-gzip-plugin and suggests removal in
nbdkit 1.26.
---
 filters/gzip/nbdkit-gzip-filter.pod |  85 +++++++
 filters/tar/nbdkit-tar-filter.pod   |   7 +-
 plugins/gzip/nbdkit-gzip-plugin.pod |   9 +
 configure.ac                        |  10 +-
 filters/gzip/Makefile.am            |  75 ++++++
 tests/Makefile.am                   |  34 +--
 filters/gzip/gzip.c                 | 347 ++++++++++++++++++++++++++++
 tests/test-gzip.c                   |   4 +-
 TODO                                |   2 -
 9 files changed, 547 insertions(+), 26 deletions(-)

diff --git a/filters/gzip/nbdkit-gzip-filter.pod b/filters/gzip/nbdkit-gzip-filter.pod
new file mode 100644
index 00000000..da0cf626
--- /dev/null
+++ b/filters/gzip/nbdkit-gzip-filter.pod
@@ -0,0 +1,85 @@
+=head1 NAME
+
+nbdkit-gzip-filter - decompress a .gz file
+
+=head1 SYNOPSIS
+
+ nbdkit file --filter=gzip FILENAME.gz
+
+=head1 DESCRIPTION
+
+C<nbdkit-gzip-filter> is a filter for L<nbdkit(1)> which transparently
+decompresses a gzip-compressed file.  You can place this filter on top
+of L<nbdkit-file-plugin(1)> to decompress a local F<.gz> file, or on
+top of other plugins such as L<nbdkit-curl-plugin(1)>:
+
+ nbdkit curl --filter=gzip https://example.com/disk.gz
+
+With L<nbdkit-tar-filter(1)> it can be used to extract files from a
+compressed tar file:
+
+ nbdkit curl --filter=tar --filter=gzip \
+        https://example.com/file.tar.gz tar-entry=disk.img
+
+The filter only allows read-only connections.
+
+B<Note> that gzip files are not very good for random access in large
+files because seeking to a position in the gzip file involves
+decompressing all data before that point in the file.  A more
+practical method to compress large disk images is to use the L<xz(1)>
+format and L<nbdkit-xz-filter(1)>.
+
+To allow seeking this filter has to keep the contents of the complete
+uncompressed file, which it does in a hidden temporary file under
+C<$TMPDIR>.
+
+=head1 PARAMETERS
+
+There are no parameters specific to this plugin.
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item C<TMPDIR>
+
+Because the gzip format is not seekable, this filter has to store the
+complete contents of the compressed file in a temporary file located
+in F</var/tmp> by default.  You can override this location by setting
+the C<TMPDIR> environment variable before starting nbdkit.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$plugindir/nbdkit-gzip-plugin.so>
+
+The plugin.
+
+Use C<nbdkit --dump-config> to find the location of C<$plugindir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-gzip-filter> first appeared in nbdkit 1.22.  It is derived
+from C<nbdkit-gzip-plugin> which first appeared in nbdkit 1.0.
+
+=head1 SEE ALSO
+
+L<nbdkit-curl-plugin(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-tar-filter(1)>,
+L<nbdkit-xz-filter(1)>,
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2013-2020 Red Hat Inc.
diff --git a/filters/tar/nbdkit-tar-filter.pod b/filters/tar/nbdkit-tar-filter.pod
index 56d4cab1..0f0734c3 100644
--- a/filters/tar/nbdkit-tar-filter.pod
+++ b/filters/tar/nbdkit-tar-filter.pod
@@ -42,11 +42,13 @@ server use:
  nbdkit -r curl https://example.com/file.tar \
         --filter=tar tar-entry=disk.img
 
-=head2 Open an xz-compressed tar file (read-only)
+=head2 Open an gzip-compressed tar file (read-only)
 
 This filter cannot handle compressed tar files itself, but you can
-combine it with L<nbdkit-xz-filter(1)>:
+combine it with L<nbdkit-gzip-filter(1)> or L<nbdkit-xz-filter(1)>:
 
+ nbdkit file filename.tar.gz \
+        --filter=tar tar-entry=disk.img --filter=gzip
  nbdkit file filename.tar.xz \
         --filter=tar tar-entry=disk.img --filter=xz
 
@@ -100,6 +102,7 @@ from C<nbdkit-tar-plugin> which first appeared in nbdkit 1.2.
 L<nbdkit(1)>,
 L<nbdkit-curl-plugin(1)>,
 L<nbdkit-file-plugin(1)>,
+L<nbdkit-gzip-filter(1)>,
 L<nbdkit-offset-filter(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-ssh-plugin(1)>,
diff --git a/plugins/gzip/nbdkit-gzip-plugin.pod b/plugins/gzip/nbdkit-gzip-plugin.pod
index 1b090125..4cd91ede 100644
--- a/plugins/gzip/nbdkit-gzip-plugin.pod
+++ b/plugins/gzip/nbdkit-gzip-plugin.pod
@@ -6,6 +6,15 @@ nbdkit-gzip-plugin - nbdkit gzip plugin
 
  nbdkit gzip [file=]FILENAME.gz
 
+=head1 DEPRECATED
+
+B<The gzip plugin is deprecated in S<nbdkit E<ge> 1.22.17> and will be
+removed in S<nbdkit 1.26>>.  It has been replaced with a filter with
+the same functionality, see L<nbdkit-gzip-filter(1)>.  You can use the
+filter like this:
+
+ nbdkit file --filter=gzip FILENAME.gz
+
 =head1 DESCRIPTION
 
 C<nbdkit-gzip-plugin> is a file serving plugin for L<nbdkit(1)>.
diff --git a/configure.ac b/configure.ac
index b51b67b6..3c1f2e11 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,6 +105,7 @@ filters="\
         ext2 \
         extentlist \
         fua \
+        gzip \
         ip \
         limit \
         log \
@@ -899,10 +900,10 @@ AS_IF([test "$with_libvirt" != "no"],[
 ])
 AM_CONDITIONAL([HAVE_LIBVIRT],[test "x$LIBVIRT_LIBS" != "x"])
 
-dnl Check for zlib (only if you want to compile the gzip plugin).
+dnl Check for zlib (only if you want to compile the gzip filter).
 AC_ARG_WITH([zlib],
     [AS_HELP_STRING([--without-zlib],
-                    [disable gzip plugin @<:@default=check@:>@])],
+                    [disable gzip filter @<:@default=check@:>@])],
     [],
     [with_zlib=check])
 AS_IF([test "$with_zlib" != "no"],[
@@ -911,7 +912,7 @@ AS_IF([test "$with_zlib" != "no"],[
         AC_SUBST([ZLIB_LIBS])
         AC_DEFINE([HAVE_ZLIB],[1],[zlib found at compile time.])
     ],
-    [AC_MSG_WARN([zlib >= 1.2.3.5 not found, gzip plugin will be disabled])])
+    [AC_MSG_WARN([zlib >= 1.2.3.5 not found, gzip filter will be disabled])])
 ])
 AM_CONDITIONAL([HAVE_ZLIB],[test "x$ZLIB_LIBS" != "x"])
 
@@ -1144,6 +1145,7 @@ AC_CONFIG_FILES([Makefile
                  filters/ext2/Makefile
                  filters/extentlist/Makefile
                  filters/fua/Makefile
+                 filters/gzip/Makefile
                  filters/ip/Makefile
                  filters/limit/Makefile
                  filters/log/Makefile
@@ -1253,6 +1255,8 @@ echo "Optional filters:"
 echo
 feature "ext2 ................................... " \
         test "x$HAVE_EXT2_TRUE" = "x"
+feature "gzip ................................... " \
+        test "x$HAVE_ZLIB_TRUE" = "x"
 feature "xz ..................................... " \
         test "x$HAVE_LIBLZMA_TRUE" = "x"
 
diff --git a/filters/gzip/Makefile.am b/filters/gzip/Makefile.am
new file mode 100644
index 00000000..a329fab8
--- /dev/null
+++ b/filters/gzip/Makefile.am
@@ -0,0 +1,75 @@
+# nbdkit
+# Copyright (C) 2019-2020 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-gzip-filter.pod
+
+if HAVE_ZLIB
+
+filter_LTLIBRARIES = nbdkit-gzip-filter.la
+
+nbdkit_gzip_filter_la_SOURCES = \
+	gzip.c \
+	$(top_srcdir)/include/nbdkit-filter.h \
+	$(NULL)
+
+nbdkit_gzip_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+nbdkit_gzip_filter_la_CFLAGS = \
+	$(WARNINGS_CFLAGS) \
+	$(ZLIB_CFLAGS) \
+	$(NULL)
+nbdkit_gzip_filter_la_LIBADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(ZLIB_LIBS) \
+	$(NULL)
+nbdkit_gzip_filter_la_LDFLAGS = \
+	-module -avoid-version -shared $(SHARED_LDFLAGS) \
+	-Wl,--version-script=$(top_srcdir)/filters/filters.syms \
+	$(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-gzip-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-gzip-filter.1: nbdkit-gzip-filter.pod
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
+
+endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2b5737b8..77f21d79 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -557,23 +557,6 @@ EXTRA_DIST += test-floppy.sh
 TESTS += test-full.sh
 EXTRA_DIST += test-full.sh
 
-# gzip plugin test.
-if HAVE_MKE2FS_WITH_D
-if HAVE_ZLIB
-LIBGUESTFS_TESTS += test-gzip
-check_DATA += disk.gz
-CLEANFILES += disk.gz
-
-test_gzip_SOURCES = test-gzip.c test.h
-test_gzip_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
-test_gzip_LDADD = libtest.la $(LIBGUESTFS_LIBS)
-
-disk.gz: disk
-	rm -f $@
-	gzip -9 -c disk > $@
-endif HAVE_ZLIB
-endif HAVE_MKE2FS_WITH_D
-
 # info plugin test.
 TESTS += \
 	test-info-address.sh \
@@ -1253,6 +1236,23 @@ EXTRA_DIST += test-extentlist.sh
 TESTS += test-fua.sh
 EXTRA_DIST += test-fua.sh
 
+# gzip filter test.
+if HAVE_MKE2FS_WITH_D
+if HAVE_ZLIB
+LIBGUESTFS_TESTS += test-gzip
+check_DATA += disk.gz
+CLEANFILES += disk.gz
+
+test_gzip_SOURCES = test-gzip.c test.h
+test_gzip_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
+test_gzip_LDADD = libtest.la $(LIBGUESTFS_LIBS)
+
+disk.gz: disk
+	rm -f $@
+	gzip -9 -c disk > $@
+endif HAVE_ZLIB
+endif HAVE_MKE2FS_WITH_D
+
 # ip filter test.
 TESTS += test-ip-filter.sh
 EXTRA_DIST += test-ip-filter.sh
diff --git a/filters/gzip/gzip.c b/filters/gzip/gzip.c
new file mode 100644
index 00000000..582652cd
--- /dev/null
+++ b/filters/gzip/gzip.c
@@ -0,0 +1,347 @@
+/* nbdkit
+ * Copyright (C) 2018-2020 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <zlib.h>
+
+#include <nbdkit-filter.h>
+
+#include "cleanup.h"
+#include "minmax.h"
+
+/* The first thread to call gzip_prepare has to uncompress the whole
+ * plugin to the temporary file.  This lock prevents concurrent
+ * access.
+ */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Temporary file storing the uncompressed data. */
+static int fd = -1;
+
+/* Size of uncompressed data. */
+static int64_t size = -1;
+
+static void
+gzip_unload (void)
+{
+  if (fd >= 0)
+    close (fd);
+}
+
+static int
+gzip_thread_model (void)
+{
+  return NBDKIT_THREAD_MODEL_PARALLEL;
+}
+
+static void *
+gzip_open (nbdkit_next_open *next, nbdkit_backend *nxdata, int readonly)
+{
+  /* Always pass readonly=1 to the underlying plugin. */
+  if (next (nxdata, 1) == -1)
+    return NULL;
+
+  return NBDKIT_HANDLE_NOT_NEEDED;
+}
+
+/* Convert a zlib error (always negative) to an nbdkit error message,
+ * and return errno correctly.
+ */
+static void
+zerror (const char *op, const z_stream *strm, int zerr)
+{
+  if (zerr == Z_MEM_ERROR) {
+    errno = ENOMEM;
+    nbdkit_error ("gzip: %s: %m", op);
+  }
+  else {
+    errno = EIO;
+    if (strm->msg)
+      nbdkit_error ("gzip: %s: %s", op, strm->msg);
+    else
+      nbdkit_error ("gzip: %s: unknown error: %d", op, zerr);
+  }
+}
+
+/* Write a whole buffer to the temporary file or fail. */
+static int
+xwrite (const void *buf, size_t count)
+{
+  ssize_t r;
+
+  while (count > 0) {
+    r = write (fd, buf, count);
+    if (r == -1) {
+      nbdkit_error ("write: %m");
+      return -1;
+    }
+    buf += r;
+    count -= r;
+  }
+
+  return 0;
+}
+
+/* The first thread to call gzip_prepare uncompresses the whole plugin. */
+static int
+do_uncompress (struct nbdkit_next_ops *next_ops, void *nxdata)
+{
+  int64_t compressed_size;
+  z_stream strm;
+  int zerr;
+  const char *tmpdir;
+  size_t len;
+  char *template;
+  CLEANUP_FREE char *in_block = NULL, *out_block = NULL;
+
+  /* This was the same buffer size as used in the old plugin.  As far
+   * as I know it was chosen at random.
+   */
+  const size_t block_size = 128 * 1024;
+
+  assert (size == -1);
+
+  /* Get the size of the underlying plugin. */
+  compressed_size = next_ops->get_size (nxdata);
+  if (compressed_size == -1)
+    return -1;
+
+  /* Create the temporary file. */
+  tmpdir = getenv ("TMPDIR");
+  if (!tmpdir)
+    tmpdir = LARGE_TMPDIR;
+
+  len = strlen (tmpdir) + 8;
+  template = alloca (len);
+  snprintf (template, len, "%s/XXXXXX", tmpdir);
+
+#ifdef HAVE_MKOSTEMP
+  fd = mkostemp (template, O_CLOEXEC);
+#else
+  /* This is only invoked serially with the lock held, so this is safe. */
+  fd = mkstemp (template);
+  if (fd >= 0) {
+    fd = set_cloexec (fd);
+    if (fd < 0) {
+      int e = errno;
+      unlink (template);
+      errno = e;
+    }
+  }
+#endif
+  if (fd == -1) {
+    nbdkit_error ("mkostemp: %s: %m", tmpdir);
+    return -1;
+  }
+
+  unlink (template);
+
+  /* Uncompress the whole plugin.  This is REQUIRED in order to
+   * implement gzip_get_size.  See: https://stackoverflow.com/a/9213826
+   *
+   * For use of inflateInit2 on gzip streams see:
+   * https://stackoverflow.com/a/1838702
+   */
+  memset (&strm, 0, sizeof strm);
+  zerr = inflateInit2 (&strm, 16+MAX_WBITS);
+  if (zerr != Z_OK) {
+    zerror ("inflateInit2", &strm, zerr);
+    return -1;
+  }
+
+  in_block = malloc (block_size);
+  if (!in_block) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+  out_block = malloc (block_size);
+  if (!out_block) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+
+  for (;;) {
+    /* Do we need to read more from the plugin? */
+    if (strm.avail_in == 0 && strm.total_in < compressed_size) {
+      size_t n = MIN (block_size, compressed_size - strm.total_in);
+      int err = 0;
+
+      if (next_ops->pread (nxdata, in_block, (uint32_t) n, strm.total_in,
+                           0, &err) == -1) {
+        errno = err;
+        return -1;
+      }
+
+      strm.next_in = (void *) in_block;
+      strm.avail_in = n;
+    }
+
+    /* Inflate the next chunk of input. */
+    strm.next_out = (void *) out_block;
+    strm.avail_out = block_size;
+    zerr = inflate (&strm, Z_SYNC_FLUSH);
+    if (zerr < 0) {
+      zerror ("inflate", &strm, zerr);
+      return -1;
+    }
+
+    /* Write the output to the file. */
+    if (xwrite (out_block, (char *) strm.next_out - out_block) == -1)
+      return -1;
+
+    if (zerr == Z_STREAM_END)
+      break;
+  }
+
+  /* Set the size to the total uncompressed size. */
+  size = strm.total_out;
+  nbdkit_debug ("gzip: uncompressed size: %" PRIi64, size);
+
+  zerr = inflateEnd (&strm);
+  if (zerr != Z_OK) {
+    zerror ("inflateEnd", &strm, zerr);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int
+gzip_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle,
+              int readonly)
+{
+  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+
+  if (size >= 0)
+    return 0;
+  return do_uncompress (next_ops, nxdata);
+}
+
+/* Whatever the plugin says, this filter makes it read-only. */
+static int
+gzip_can_write (struct nbdkit_next_ops *next_ops, void *nxdata,
+                void *handle)
+{
+  return 0;
+}
+
+/* Similar to above, whatever the plugin says, extents are not
+ * supported.
+ */
+static int
+gzip_can_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+                  void *handle)
+{
+  return 0;
+}
+
+/* We are already operating as a cache regardless of the plugin's
+ * underlying .can_cache, but it's easiest to just rely on nbdkit's
+ * behavior of calling .pread for caching.
+ */
+static int
+gzip_can_cache (struct nbdkit_next_ops *next_ops, void *nxdata,
+                void *handle)
+{
+  return NBDKIT_CACHE_EMULATE;
+}
+
+/* Get the file size. */
+static int64_t
+gzip_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle)
+{
+  /* This must be true because gzip_prepare must have been called. */
+  assert (size >= 0);
+
+  /* We must call underlying get_size even though we don't use the
+   * result, because it caches the plugin size in server/backend.c.
+   */
+  if (next_ops->get_size (nxdata) == -1)
+    return -1;
+
+  return size;
+}
+
+/* Read data from the temporary file. */
+static int
+gzip_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, void *buf, uint32_t count, uint64_t offset,
+            uint32_t flags, int *err)
+{
+  /* This must be true because gzip_prepare must have been called. */
+  assert (fd >= 0);
+
+  while (count > 0) {
+    ssize_t r = pread (fd, buf, count, offset);
+    if (r == -1) {
+      nbdkit_error ("pread: %m");
+      return -1;
+    }
+    if (r == 0) {
+      nbdkit_error ("pread: unexpected end of file");
+      return -1;
+    }
+    buf += r;
+    count -= r;
+    offset += r;
+  }
+
+  return 0;
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "gzip",
+  .longname          = "nbdkit gzip filter",
+  .unload            = gzip_unload,
+  .thread_model      = gzip_thread_model,
+  .open              = gzip_open,
+  .prepare           = gzip_prepare,
+  .can_write         = gzip_can_write,
+  .can_extents       = gzip_can_extents,
+  .can_cache         = gzip_can_cache,
+  .prepare           = gzip_prepare,
+  .get_size          = gzip_get_size,
+  .pread             = gzip_pread,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/tests/test-gzip.c b/tests/test-gzip.c
index 9e1229e1..969d6d0e 100644
--- a/tests/test-gzip.c
+++ b/tests/test-gzip.c
@@ -1,5 +1,5 @@
 /* nbdkit
- * Copyright (C) 2013 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -50,7 +50,7 @@ main (int argc, char *argv[])
   int r;
   char *data;
 
-  if (test_start_nbdkit ("gzip", "-r", "file=disk.gz", NULL) == -1)
+  if (test_start_nbdkit ("file", "--filter=gzip", "disk.gz", NULL) == -1)
     exit (EXIT_FAILURE);
 
   g = guestfs_create ();
diff --git a/TODO b/TODO
index 28bcc952..addf8025 100644
--- a/TODO
+++ b/TODO
@@ -169,8 +169,6 @@ Rust:
 Suggestions for filters
 -----------------------
 
-* gzip plugin should really be a filter
-
 * LUKS encrypt/decrypt filter, bonus points if compatible with qemu
   LUKS-encrypted disk images
 
-- 
2.27.0




More information about the Libguestfs mailing list