On Sun, Jun 28, 2020 at 4:03 PM Richard W.M. Jones <rjones(a)redhat.com> wrote:
...
 +
 +static int
 +tar_get_ready (void)
 +{
 +  FILE *fp;
 +  CLEANUP_FREE char *cmd = NULL;
 +  size_t len = 0;
 +  bool scanned_ok;
 +  char s[256];
 +
 +  /* Construct the tar command to examine the tar file. */
 +  fp = open_memstream (&cmd, &len);
 +  if (fp == NULL) {
 +    nbdkit_error ("open_memstream: %m");
 +    return -1;
 +  }
 +  fprintf (fp, "LANG=C tar --no-auto-compress -tRvf "); 
Using -R is nice, but is block size documented?
Also --block-number would be nicer.
 +  shell_quote (tarfile, fp); 
This is questionable. Why use string and quote the string instead of using
argv directly?
 +  fputc (' ', fp);
 +  shell_quote (file, fp);
 +  if (fclose (fp) == EOF) {
 +    nbdkit_error ("memstream failed: %m");
 +    return -1;
 +  }
 +
 +  /* Run the command and read the first line of the output. */
 +  nbdkit_debug ("%s", cmd);
 +  fp = popen (cmd, "r");
 +  if (fp == NULL) {
 +    nbdkit_error ("tar: %m");
 +    return -1;
 +  }
 +  scanned_ok = fscanf (fp, "block %" SCNu64 ": %*s %*s %" SCNu64,
 +                       &offset, &size) == 2;
 +  /* We have to now read and discard the rest of the output until EOF. */
 +  while (fread (s, sizeof s, 1, fp) > 0)
 +    ;
 +  if (pclose (fp) != 0) {
 +    nbdkit_error ("tar subcommand failed, "
 +                  "check that the file really exists in the tarball");
 +    return -1;
 +  }
 +
 +  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;
 +  }
 +
 +  return 0;
 +}
 +
 +struct handle {
 +  int fd;
 +};
 +
 +static void *
 +tar_open (int readonly)
 +{
 +  struct handle *h;
 +
 +  assert (offset > 0);     /* Cannot be zero because of tar header. */
 +
 +  h = calloc (1, sizeof *h);
 +  if (h == NULL) {
 +    nbdkit_error ("calloc: %m");
 +    return NULL;
 +  }
 +  h->fd = open (tarfile, (readonly ? O_RDONLY : O_RDWR) | O_CLOEXEC);
 +  if (h->fd == -1) {
 +    nbdkit_error ("%s: %m", tarfile);
 +    free (h);
 +    return NULL;
 +  }
 +
 +  return h;
 +}
 +
 +#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
 +
 +/* Get the file size. */
 +static int64_t
 +tar_get_size (void *handle)
 +{
 +  return size;
 +}
 +
 +/* Serves the same data over multiple connections. */
 +static int
 +tar_can_multi_conn (void *handle)
 +{
 +  return 1;
 +}
 +
 +static int
 +tar_can_cache (void *handle)
 +{
 +  /* Let nbdkit call pread to populate the file system cache. */
 +  return NBDKIT_CACHE_EMULATE;
 +}
 +
 +/* Read data from the file. */
 +static int
 +tar_pread (void *handle, void *buf, uint32_t count, uint64_t offs) 
This should be identical to file plugin, on? can we reuse the same code
for reading files?
 +{
 +  struct handle *h = handle;
 +
 +  offs += offset;
 +
 +  while (count > 0) {
 +    ssize_t r = pread (h->fd, buf, count, offs);
 +    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;
 +    offs += r;
 +  }
 +
 +  return 0;
 +}
 +
 +/* Write data to the file. */
 +static int
 +tar_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offs)
 +{
 +  struct handle *h = handle;
 +
 +  offs += offset;
 +
 +  while (count > 0) {
 +    ssize_t r = pwrite (h->fd, buf, count, offs);
 +    if (r == -1) {
 +      nbdkit_error ("pwrite: %m");
 +      return -1;
 +    }
 +    buf += r;
 +    count -= r;
 +    offs += r;
 +  }
 +
 +  return 0;
 +}
 +
 +static struct nbdkit_plugin plugin = {
 +  .name              = "tar",
 +  .longname          = "nbdkit tar plugin",
 +  .version           = PACKAGE_VERSION,
 +  .unload            = tar_unload,
 +  .config            = tar_config,
 +  .config_complete   = tar_config_complete,
 +  .config_help       = tar_config_help,
 +  .magic_config_key  = "tar",
 +  .get_ready         = tar_get_ready,
 +  .open              = tar_open,
 +  .get_size          = tar_get_size,
 +  .can_multi_conn    = tar_can_multi_conn,
 +  .can_cache         = tar_can_cache,
 +  .pread             = tar_pread,
 +  .pwrite            = tar_pwrite,
 +  .errno_is_preserved = 1,
 +};
 +
 +NBDKIT_REGISTER_PLUGIN(plugin)
 diff --git a/tests/test-dump-plugin.sh b/tests/test-dump-plugin.sh
 index 0b4c1ce1..6eb25a65 100755
 --- a/tests/test-dump-plugin.sh
 +++ b/tests/test-dump-plugin.sh
 @@ -46,7 +46,7 @@ do_test ()
          python-valgrind | ruby-valgrind | tcl-valgrind)
              echo "$0: skipping $1$vg because this language doesn't support
valgrind"
              ;;
 -        example4* | tar*)
 +        example4*)
              # These tests are written in Perl so we have to check that
              # the Perl plugin was compiled.
              if nbdkit perl --version; then run_test $1; fi
 diff --git a/tests/test-help-plugin.sh b/tests/test-help-plugin.sh
 index 7dc26ece..f0dfa7df 100755
 --- a/tests/test-help-plugin.sh
 +++ b/tests/test-help-plugin.sh
 @@ -46,7 +46,7 @@ do_test ()
          python-valgrind | ruby-valgrind | tcl-valgrind)
              echo "$0: skipping $1$vg because this language doesn't support
valgrind"
              ;;
 -        example4* | tar*)
 +        example4*)
              # These tests are written in Perl so we have to check that
              # the Perl plugin was compiled.
              if nbdkit perl --version; then run_test $1; fi
 diff --git a/tests/test-tar-info.sh b/tests/test-tar-info.sh
 new file mode 100755
 index 00000000..efaa8ec8
 --- /dev/null
 +++ b/tests/test-tar-info.sh
 @@ -0,0 +1,67 @@
 +#!/usr/bin/env bash
 +# nbdkit
 +# Copyright (C) 2017-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.
 +
 +# Test that qemu-img info works on a qcow2 file in a tar file.
 +
 +source ./functions.sh
 +set -e
 +set -x
 +
 +requires test -f disk
 +requires guestfish --version
 +requires tar --version
 +requires qemu-img --version
 +requires qemu-img info --output=json /dev/null
 +requires jq --version
 +requires stat --version
 +
 +disk=tar-info-disk.qcow2
 +out=tar-info.out
 +tar=tar-info.tar
 +files="$disk $out $tar"
 +rm -f $files
 +cleanup_fn rm -f $files
 +
 +# Create a tar file containing a known qcow2 file.
 +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
 +cat $out
 +
 +# Check various fields in the input.
 +# Virtual size must be the same as the size of the original raw disk.
 +test "$( jq -r -c '.["virtual-size"]' $out )" -eq "$(
stat -c %s disk )"
 +
 +# Format must be qcow2.
 +test "$( jq -r -c '.["format"]' $out )" = "qcow2"
 diff --git a/tests/test-tar.sh b/tests/test-tar.sh
 index c6d726c4..3164b826 100755
 --- a/tests/test-tar.sh
 +++ b/tests/test-tar.sh
 @@ -38,23 +38,27 @@ requires test -f disk
  requires guestfish --version
  requires tar --version
 -# The tar plugin requires some Perl modules, this checks if they are
 -# installed.
 -requires perl -MCwd -e 1
 -requires perl -MIO::File -e 1
 -
  sock=`mktemp -u`
  files="tar.pid tar.tar $sock"
  rm -f $files
  cleanup_fn rm -f $files
 -# Create a tar file containing the disk image.
 -tar cf tar.tar disk
 +# Create a tar file containing the disk image plus some other random
 +# files that hopefully will be ignored.
 +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
 -# Now see if we can open the disk from the tar file.
 -guestfish -x --ro --format=raw -a "nbd://?socket=$sock" -m /dev/sda1
<<EOF
 +# 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
    cat /hello.txt
 +
 +  # Write a new file.
 +  write /test.txt "hello"
 +  cat /test.txt
  EOF
 +
 +# Check that the tar file isn't corrupt.
 +tar tvvf tar.tar
 diff --git a/tests/test-version-plugin.sh b/tests/test-version-plugin.sh
 index 7d2ab072..c397afc2 100755
 --- a/tests/test-version-plugin.sh
 +++ b/tests/test-version-plugin.sh
 @@ -46,7 +46,7 @@ do_test ()
          python-valgrind | ruby-valgrind | tcl-valgrind)
              echo "$0: skipping $1$vg because this language doesn't support
valgrind"
              ;;
 -        example4* | tar*)
 +        example4*)
              # These tests are written in Perl so we have to check that
              # the Perl plugin was compiled.
              if nbdkit perl --version; then run_test $1; fi
 diff --git a/wrapper.c b/wrapper.c
 index b1e2ce2f..c27afae0 100644
 --- a/wrapper.c
 +++ b/wrapper.c
 @@ -85,7 +85,7 @@ static size_t len;
  static bool
  is_perl_plugin (const char *name)
  {
 -  return strcmp (name, "example4") == 0 || strcmp (name, "tar") ==
0;
 +  return strcmp (name, "example4") == 0;
  }
  static void
 diff --git a/.gitignore b/.gitignore
 index f02d19dc..48a5302e 100644
 --- a/.gitignore
 +++ b/.gitignore
 @@ -79,7 +79,6 @@ plugins/*/*.3
  /plugins/ocaml/nbdkit-ocamlexample-plugin.so
  /plugins/rust/Cargo.lock
  /plugins/rust/target
 -/plugins/tar/nbdkit-tar-plugin
  /plugins/tmpdisk/default-command.c
  /podwrapper.pl
  /server/local/nbdkit.pc
 diff --git a/README b/README
 index 4f584828..7733761e 100644
 --- a/README
 +++ b/README
 @@ -124,13 +124,13 @@ For the bittorrent plugin:
   - libtorrent-rasterbar (
https://www.libtorrent.org)
 -For the Perl, example4 and tar plugins:
 +For the Perl and example4 plugins:
   - perl interpreter
   - perl development libraries
 - - perl modules ExtUtils::Embed, IO::File and Cwd
 + - perl modules ExtUtils::Embed
  For the Python plugin:
 --
 2.25.0