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