[Libguestfs] [PATCH nbdkit] New filter: tar.

Richard W.M. Jones rjones at redhat.com
Tue Jul 7 17:36:42 UTC 2020


This filter can be used to open tar files.  It uses the technique
first suggested by Eric Blake here:
https://www.redhat.com/archives/libguestfs/2020-July/msg00017.html

We suggest that nbdkit-tar-plugin is deprecated in nbdkit 1.26, but it
might happen later.
---
 docs/nbdkit-captive.pod                 |   4 +-
 filters/offset/nbdkit-offset-filter.pod |   2 +-
 filters/tar/nbdkit-tar-filter.pod       | 108 +++++++
 plugins/tar/nbdkit-tar-plugin.pod       |  10 +
 configure.ac                            |   2 +
 filters/tar/Makefile.am                 |  67 ++++
 tests/Makefile.am                       |  20 +-
 filters/tar/tar.c                       | 395 ++++++++++++++++++++++++
 tests/test-tar-info.sh                  |   4 +-
 tests/test-tar.sh                       |   2 +-
 TODO                                    |   8 +-
 11 files changed, 603 insertions(+), 19 deletions(-)

diff --git a/docs/nbdkit-captive.pod b/docs/nbdkit-captive.pod
index 390c2191..09628367 100644
--- a/docs/nbdkit-captive.pod
+++ b/docs/nbdkit-captive.pod
@@ -106,10 +106,10 @@ If the source suffers from temporary network failures
 L<nbdkit-retry-filter(1)> may help.
 
 To overwrite a file inside an uncompressed tar file (the file being
-overwritten must be the same size), use L<nbdkit-tar-plugin(1)> like
+overwritten must be the same size), use L<nbdkit-tar-filter(1)> like
 this:
 
- nbdkit -U - tar tar=data.tar file=disk.img \
+ nbdkit -U - file data.tar --filter=tar tar-entry=disk.img \
    --run 'qemu-img convert -n disk.img $nbd'
 
 =head1 EXIT WITH PARENT
diff --git a/filters/offset/nbdkit-offset-filter.pod b/filters/offset/nbdkit-offset-filter.pod
index 0fd2761e..19df1742 100644
--- a/filters/offset/nbdkit-offset-filter.pod
+++ b/filters/offset/nbdkit-offset-filter.pod
@@ -84,7 +84,7 @@ L<nbdkit(1)>,
 L<nbdkit-file-plugin(1)>,
 L<nbdkit-filter(3)>,
 L<nbdkit-partition-filter(1)>,
-L<nbdkit-tar-plugin(1)>,
+L<nbdkit-tar-filter(1)>,
 L<nbdkit-truncate-filter(1)>.
 
 =head1 AUTHORS
diff --git a/filters/tar/nbdkit-tar-filter.pod b/filters/tar/nbdkit-tar-filter.pod
new file mode 100644
index 00000000..85968668
--- /dev/null
+++ b/filters/tar/nbdkit-tar-filter.pod
@@ -0,0 +1,108 @@
+=head1 NAME
+
+nbdkit-tar-filter - read and write files inside tar files without unpacking
+
+=head1 SYNOPSIS
+
+ nbdkit file FILENAME.tar --filter=tar tar-entry=PATH_INSIDE_TAR
+
+=head1 EXAMPLES
+
+=head2 Serve a single file inside a tarball
+
+ nbdkit file file.tar --filter=tar tar-entry=some/disk.img
+ guestfish --format=raw -a nbd://localhost
+
+=head2 Opening a disk image inside an OVA file
+
+The popular "Open Virtual Appliance" (OVA) format is really an
+uncompressed tar file containing (usually) VMDK-format files, so you
+could access one file in an OVA like this:
+
+ $ tar tf rhel.ova
+ rhel.ovf
+ rhel-disk1.vmdk
+ rhel.mf
+ $ nbdkit -r file rhel.ova --filter=tar tar-entry=rhel-disk1.vmdk
+ $ guestfish --ro --format=vmdk -a nbd://localhost
+
+In this case the tarball is opened readonly (I<-r> option).  The
+plugin supports write access, but writing to the VMDK file in the
+tarball does not change data checksums stored in other files (the
+C<rhel.mf> file in this example), and as these will become incorrect
+you probably won't be able to open the file with another tool
+afterwards.
+
+=head2 Open a disk image inside a remote tar file
+
+You can use other plugins apart from L<nbdkit-file-plugin(1)> to
+provide the tar file.  For example if the tar file is located on a web
+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)
+
+This filter cannot handle compressed tar files itself, but you can
+combine it with L<nbdkit-xz-filter(1)>:
+
+ nbdkit file filename.tar.xz --filter=tar --filter=xz tar-entry=disk.img
+
+=head1 DESCRIPTION
+
+C<nbdkit-tar-filter> is a filter which can read and writes files
+inside an uncompressed tar file without unpacking the tar file.
+
+The tar file is provided by the underlying plugin.  You must tell the
+filter which entry in the tar file you wish to read and write using
+the C<tar-entry> parameter.  The C<tar-entry> must exactly match file
+name in the tar index.  Use C<tar tf filename.tar> to list the index
+of a tar file.
+
+This filter will B<not> work directly on compressed tar files.  You
+have to combine it with another filter as shown in the example above.
+
+Use the nbdkit I<-r> flag to open the file readonly.  This is the
+safest option because it guarantees that the tar file will not be
+modified.  Without I<-r> writes will modify the tar file.
+
+The disk image cannot be resized.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item [B<tar-entry=>]PATH_INSIDE_TAR
+
+The path of the file inside the tarball to serve.  This parameter is
+required.  It must exactly match the name stored in the tarball, so
+use S<C<tar tf filename.tar>>
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-tar-filter> first appeared in nbdkit 1.22.  It is derived
+from C<nbdkit-tar-plugin> which first appeared in nbdkit 1.2.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-curl-plugin(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-offset-filter(1)>,
+L<nbdkit-plugin(3)>,
+L<nbdkit-ssh-plugin(1)>,
+L<nbdkit-xz-filter(1)>,
+L<tar(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones.
+
+Based on the virt-v2v OVA importer written by Tomáš Golembiovský.
+
+=head1 COPYRIGHT
+
+Copyright (C) 2017-2020 Red Hat Inc.
diff --git a/plugins/tar/nbdkit-tar-plugin.pod b/plugins/tar/nbdkit-tar-plugin.pod
index 589e114b..b576d568 100644
--- a/plugins/tar/nbdkit-tar-plugin.pod
+++ b/plugins/tar/nbdkit-tar-plugin.pod
@@ -6,6 +6,15 @@ nbdkit-tar-plugin - read and write files inside tar files without unpacking
 
  nbdkit tar [tar=]FILENAME.tar file=PATH_INSIDE_TAR
 
+=head1 DEPRECATED
+
+B<The tar plugin is deprecated in S<nbdkit E<ge> 1.22.16> and will be
+removed in S<nbdkit 1.26>>.  It has been replaced with a filter with
+the same functionality, see L<nbdkit-tar-filter(1)>.  You can use the
+filter like this:
+
+ nbdkit file FILENAME.tar --filter=tar tar-entry=PATH_INSIDE_TAR
+
 =head1 EXAMPLES
 
 =head2 Serve a single file inside a tarball
@@ -113,6 +122,7 @@ C<nbdkit-tar-plugin> first appeared in nbdkit 1.2.
 L<nbdkit(1)>,
 L<nbdkit-offset-filter(1)>,
 L<nbdkit-plugin(3)>,
+L<nbdkit-tar-filter(1)>,
 L<nbdkit-xz-filter(1)>,
 L<tar(1)>.
 
diff --git a/configure.ac b/configure.ac
index 899a9dcc..b360d863 100644
--- a/configure.ac
+++ b/configure.ac
@@ -122,6 +122,7 @@ filters="\
         retry \
         stats \
         swab \
+        tar \
         truncate \
         xz \
         "
@@ -1161,6 +1162,7 @@ AC_CONFIG_FILES([Makefile
                  filters/retry/Makefile
                  filters/stats/Makefile
                  filters/swab/Makefile
+                 filters/tar/Makefile
                  filters/truncate/Makefile
                  filters/xz/Makefile
                  fuzzing/Makefile
diff --git a/filters/tar/Makefile.am b/filters/tar/Makefile.am
new file mode 100644
index 00000000..43446c88
--- /dev/null
+++ b/filters/tar/Makefile.am
@@ -0,0 +1,67 @@
+# 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-tar-filter.pod
+
+filter_LTLIBRARIES = nbdkit-tar-filter.la
+
+nbdkit_tar_filter_la_SOURCES = \
+	tar.c \
+	$(top_srcdir)/include/nbdkit-filter.h \
+	$(NULL)
+
+nbdkit_tar_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+nbdkit_tar_filter_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_tar_filter_la_LDFLAGS = \
+	-module -avoid-version -shared $(SHARED_LDFLAGS) \
+	-Wl,--version-script=$(top_srcdir)/filters/filters.syms \
+	$(NULL)
+nbdkit_tar_filter_la_LIBADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-tar-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-tar-filter.1: nbdkit-tar-filter.pod
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 062ade84..2bc10d70 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -830,16 +830,6 @@ test_streaming_SOURCES = test-streaming.c
 test_streaming_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS)
 test_streaming_LDADD = $(LIBNBD_LIBS)
 
-# tar plugin test.
-TESTS += \
-	test-tar.sh \
-	test-tar-info.sh \
-	$(NULL)
-EXTRA_DIST += \
-	test-tar.sh \
-	test-tar-info.sh \
-	$(NULL)
-
 # tmpdisk plugin test.
 LIBGUESTFS_TESTS += test-tmpdisk
 TESTS += test-tmpdisk-command.sh
@@ -1379,6 +1369,16 @@ EXTRA_DIST += \
 	test-swab-64w.sh \
 	$(NULL)
 
+# tar filter test.
+TESTS += \
+	test-tar.sh \
+	test-tar-info.sh \
+	$(NULL)
+EXTRA_DIST += \
+	test-tar.sh \
+	test-tar-info.sh \
+	$(NULL)
+
 # truncate filter tests.
 TESTS += \
 	test-truncate1.sh \
diff --git a/filters/tar/tar.c b/filters/tar/tar.c
new file mode 100644
index 00000000..8d6ced04
--- /dev/null
+++ b/filters/tar/tar.c
@@ -0,0 +1,395 @@
+/* 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 <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <pthread.h>
+
+#include <nbdkit-filter.h>
+
+#include "cleanup.h"
+#include "minmax.h"
+#include "utils.h"
+
+static const char *entry;       /* File within tar (tar-entry=...) */
+
+/* Offset and size within tarball.
+ *
+ * These are calculated once in the first connection that calls
+ * tar_prepare.  They are protected by the lock.
+ */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static bool offset_initialized = false;
+static uint64_t offset, size;
+
+static int
+tar_config (nbdkit_next_config *next, void *nxdata,
+            const char *key, const char *value)
+{
+  if (strcmp (key, "tar-entry") == 0) {
+    if (entry) {
+      nbdkit_error ("only one tar-entry parameter can be given");
+      return -1;
+    }
+    entry = value;
+    return 0;
+  }
+
+  return next (nxdata, key, value);
+}
+
+static int
+tar_config_complete (nbdkit_next_config_complete *next, void *nxdata)
+{
+  if (entry == NULL) {
+    nbdkit_error ("you must supply the tar-entry=<FILENAME> parameter");
+    return -1;
+  }
+
+  return next (nxdata);
+}
+
+#define tar_config_help \
+  "tar-entry=<FILENAME> (required) The path inside the tar file to serve."
+
+static int
+tar_thread_model (void)
+{
+  return NBDKIT_THREAD_MODEL_PARALLEL;
+}
+
+struct handle {
+  /* These are copied from the globals during tar_prepare, so that we
+   * don't have to keep grabbing the lock on each request.
+   */
+  uint64_t offset, size;
+};
+
+static void *
+tar_open (nbdkit_next_open *next, nbdkit_backend *nxdata, int readonly)
+{
+  struct handle *h;
+
+  if (next (nxdata, readonly) == -1)
+    return NULL;
+
+  h = calloc (1, sizeof *h);
+  if (h == NULL) {
+    nbdkit_error ("calloc: %m");
+    return NULL;
+  }
+  return h;
+}
+
+static void
+tar_close (void *handle)
+{
+  free (handle);
+}
+
+/* Calculate the offset of the entry within the tarball.  This is
+ * called with the lock held.  The method used is described here:
+ * https://www.redhat.com/archives/libguestfs/2020-July/msg00017.html
+ */
+static int
+calculate_offset_of_entry (struct nbdkit_next_ops *next_ops, void *nxdata)
+{
+  const size_t bufsize = 65536;
+  char output[] = "/tmp/tarXXXXXX";
+  int fd;
+  FILE *fp;
+  CLEANUP_FREE char *cmd = NULL;
+  size_t cmdlen = 0;
+  CLEANUP_FREE char *buf = NULL;
+  int64_t i, copysize;
+  bool scanned_ok = false;
+
+  assert (entry);
+
+  /* Temporary file to capture the output from the tar command. */
+  fd = mkstemp (output);
+  if (fd == -1) {
+    nbdkit_error ("mkstemp: %m");
+    return -1;
+  }
+  close (fd);
+
+  /* Construct the tar command to examine the tar file. */
+  fp = open_memstream (&cmd, &cmdlen);
+  if (fp == NULL) {
+    nbdkit_error ("open_memstream: %m");
+    return -1;
+  }
+  fprintf (fp, "LANG=C tar --no-auto-compress -tRvf - ");
+  shell_quote (entry, fp);
+  fprintf (fp, " > ");
+  shell_quote (output, fp);
+  if (fclose (fp) == EOF) {
+    nbdkit_error ("memstream failed: %m");
+    return -1;
+  }
+
+  /* Prepare the copy buffer and copy size. */
+  buf = malloc (bufsize);
+  if (buf == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+  copysize = next_ops->get_size (nxdata);
+  if (copysize == -1)
+    return -1;
+
+  /* Run the tar command. */
+  nbdkit_debug ("%s", cmd);
+  fp = popen (cmd, "w");
+  if (fp == NULL) {
+    nbdkit_error ("tar: %m");
+    return -1;
+  }
+
+  /* Now loop, writing data from the plugin (the tar file) until we
+   * detect that tar has written something to the output file or we
+   * run out of plugin.  We're making the assumption that the plugin
+   * is not going to be sparse, which is probably true of most tar
+   * files.
+   */
+  for (i = 0; i < copysize; i += bufsize) {
+    int err, r;
+    const int64_t count = MIN (bufsize, copysize-i);
+    int64_t j;
+    struct stat statbuf;
+
+    r = next_ops->pread (nxdata, buf, count, i, 0, &err);
+    if (r == -1) {
+      errno = err;
+      nbdkit_error ("pread: %m");
+      pclose (fp);
+      return -1;
+    }
+    for (j = 0; j < count;) {
+      size_t written = fwrite (&buf[j], 1, count-j, fp);
+      if (written == 0) {
+        nbdkit_error ("tar: error writing to subprocess");
+        pclose (fp);
+        return -1;
+      }
+      j += written;
+    }
+
+    /* Did we get something in the output file yet? */
+    if (stat (output, &statbuf) == 0 && statbuf.st_size > 0)
+      break;
+  }
+  pclose (fp);
+
+  /* Open the tar output and try to parse it. */
+  fp = fopen (output, "r");
+  if (fp == NULL) {
+    nbdkit_error ("%s: %m", output);
+    return -1;
+  }
+  scanned_ok = fscanf (fp, "block %" SCNu64 ": %*s %*s %" SCNu64,
+                       &offset, &size) == 2;
+  if (fclose (fp) != 0) {
+    nbdkit_error ("tar subcommand failed, "
+                  "check that the file really exists in the tarball");
+    return -1;
+  }
+
+  unlink (output);
+
+  if (!scanned_ok) {
+    nbdkit_error ("unexpected output from the tar subcommand");
+    return -1;
+  }
+
+  /* Adjust the offset: Add 1 for the tar header, then multiply by the
+   * block size.
+   */
+  offset = (offset+1) * 512;
+
+  nbdkit_debug ("tar: offset %" PRIu64 ", size %" PRIu64, offset, size);
+
+  /* Check it looks sensible.  XXX We ought to check it doesn't exceed
+   * the size of the tar file.
+   */
+  if (offset >= INT64_MAX || size >= INT64_MAX) {
+    nbdkit_error ("internal error: calculated offset and size are wrong");
+    return -1;
+  }
+
+  offset_initialized = true;
+
+  return 0;
+}
+
+static int
+tar_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle, int readonly)
+{
+  struct handle *h = handle;
+  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+
+  if (!offset_initialized) {
+    if (calculate_offset_of_entry (next_ops, nxdata) == -1)
+      return -1;
+  }
+
+  assert (offset_initialized);
+  assert (offset > 0);
+  h->offset = offset;
+  h->size = size;
+  return 0;
+}
+
+/* Get the file size. */
+static int64_t
+tar_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
+              void *handle)
+{
+  struct handle *h = handle;
+  return h->size;
+}
+
+/* Read data from the file. */
+static int
+tar_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+           void *handle, void *buf, uint32_t count, uint64_t offs,
+           uint32_t flags, int *err)
+{
+  struct handle *h = handle;
+  return next_ops->pread (nxdata, buf, count, offs + h->offset, flags, err);
+}
+
+/* Write data to the file. */
+static int
+tar_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, const void *buf, uint32_t count, uint64_t offs,
+            uint32_t flags, int *err)
+{
+  struct handle *h = handle;
+  return next_ops->pwrite (nxdata, buf, count, offs + h->offset, flags, err);
+}
+
+/* Trim data. */
+static int
+tar_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
+          void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+          int *err)
+{
+  struct handle *h = handle;
+  return next_ops->trim (nxdata, count, offs + h->offset, flags, err);
+}
+
+/* Zero data. */
+static int
+tar_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+          void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+          int *err)
+{
+  struct handle *h = handle;
+  return next_ops->zero (nxdata, count, offs + h->offset, flags, err);
+}
+
+/* Extents. */
+static int
+tar_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+             struct nbdkit_extents *extents, int *err)
+{
+  struct handle *h = handle;
+  size_t i;
+  CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL;
+  struct nbdkit_extent e;
+
+  extents2 = nbdkit_extents_new (offs + h->offset, h->offset + h->size);
+  if (extents2 == NULL) {
+    *err = errno;
+    return -1;
+  }
+  if (next_ops->extents (nxdata, count, offs + h->offset,
+                         flags, extents2, err) == -1)
+    return -1;
+
+  for (i = 0; i < nbdkit_extents_count (extents2); ++i) {
+    e = nbdkit_get_extent (extents2, i);
+    e.offset -= h->offset;
+    if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
+      *err = errno;
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/* Cache data. */
+static int
+tar_cache (struct nbdkit_next_ops *next_ops, void *nxdata,
+           void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+           int *err)
+{
+  struct handle *h = handle;
+  return next_ops->cache (nxdata, count, offs + h->offset, flags, err);
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "tar",
+  .longname          = "nbdkit tar filter",
+  .config            = tar_config,
+  .config_complete   = tar_config_complete,
+  .config_help       = tar_config_help,
+  .thread_model      = tar_thread_model,
+  .open              = tar_open,
+  .close             = tar_close,
+  .prepare           = tar_prepare,
+  .get_size          = tar_get_size,
+  .pread             = tar_pread,
+  .pwrite            = tar_pwrite,
+  .trim              = tar_trim,
+  .zero              = tar_zero,
+  .extents           = tar_extents,
+  .cache             = tar_cache,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/tests/test-tar-info.sh b/tests/test-tar-info.sh
index efaa8ec8..459e5d6f 100755
--- a/tests/test-tar-info.sh
+++ b/tests/test-tar-info.sh
@@ -56,7 +56,9 @@ qemu-img convert -f raw disk -O qcow2 $disk
 tar cf $tar $disk
 
 # Run nbdkit.
-nbdkit -U - tar $tar file=$disk --run 'qemu-img info --output=json $nbd' > $out
+nbdkit -U - file $tar \
+       --filter=tar tar-entry=$disk \
+       --run 'qemu-img info --output=json $nbd' > $out
 cat $out
 
 # Check various fields in the input.
diff --git a/tests/test-tar.sh b/tests/test-tar.sh
index 3164b826..8f6422cf 100755
--- a/tests/test-tar.sh
+++ b/tests/test-tar.sh
@@ -49,7 +49,7 @@ tar cf tar.tar test-tar.sh Makefile disk Makefile.am
 tar tvvf tar.tar
 
 # Run nbdkit.
-start_nbdkit -P tar.pid -U $sock tar tar=tar.tar file=disk
+start_nbdkit -P tar.pid -U $sock file tar.tar --filter=tar tar-entry=disk
 
 # Now see if we can open, read and write the disk from the tar file.
 guestfish -x --format=raw -a "nbd://?socket=$sock" -m /dev/sda1 <<EOF
diff --git a/TODO b/TODO
index d96b11e7..7332a9eb 100644
--- a/TODO
+++ b/TODO
@@ -169,12 +169,8 @@ Rust:
 Suggestions for filters
 -----------------------
 
-* tar plugin should really be a filter
-
 * gzip plugin should really be a filter
 
-* libarchive could be used to implement a general tar/zip filter
-
 * LUKS encrypt/decrypt filter, bonus points if compatible with qemu
   LUKS-encrypted disk images
 
@@ -202,6 +198,10 @@ Suggestions for filters
 
 * nbdkit-swab-filter should be able to swap 32 and 64 bits.
 
+* nbdkit-tar-filter should let you specify the tar command, eg.  for
+  platforms like FreeBSD where tar != GNU tar but GNU tar can be
+  installed under another name.
+
 nbdkit-rate-filter:
 
 * allow other kinds of traffic shaping such as VBR
-- 
2.27.0




More information about the Libguestfs mailing list