Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
tar: Rewrite the tar plugin in Python.
Thanks: Nir Soffer.
  • Loading branch information
rwmjones committed Jun 23, 2020
1 parent 5801faf commit 2d15e79
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 149 deletions.
8 changes: 5 additions & 3 deletions README
Expand Up @@ -120,22 +120,24 @@ of the test suite:

- libnbd >= 0.9.8

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:
For the Python and tar plugins:

- python interpreter (version 3 only)

- python development libraries

- python unittest to run the test suite

- python tarfile.py (part of standard library)

For the OCaml plugin:

- OCaml >= 4.02.2
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Expand Up @@ -1203,7 +1203,7 @@ feature "nbd .................................... " \
feature "ssh .................................... " \
test "x$HAVE_SSH_TRUE" = "x"
feature "tar .................................... " \
test "x$HAVE_PERL_TRUE" = "x"
test "x$HAVE_PYTHON_TRUE" = "x"
feature "vddk ................................... " \
test "x$HAVE_VDDK_TRUE" = "x"

Expand Down
13 changes: 6 additions & 7 deletions plugins/tar/Makefile.am
Expand Up @@ -31,19 +31,18 @@

include $(top_srcdir)/common-rules.mk

source = tar.pl

EXTRA_DIST = \
$(source) \
nbdkit-tar-plugin.pod \
tar.py \
$(NULL)

if HAVE_PERL
if HAVE_PYTHON

plugin_SCRIPTS = nbdkit-tar-plugin
CLEANFILES += nbdkit-tar-plugin

# We have to do the rewriting here to avoid stupid exec_prefix.
nbdkit-tar-plugin: $(source)
nbdkit-tar-plugin: tar.py
rm -f $@ $@-t
sed 's,\@sbindir\@,${sbindir},g' < $< > $@-t
mv $@-t $@
Expand All @@ -54,8 +53,8 @@ if HAVE_POD
man_MANS = nbdkit-tar-plugin.1
CLEANFILES += $(man_MANS)

nbdkit-tar-plugin.1: $(source)
$(PODWRAPPER) --section=1 --name nbdkit-tar-plugin --man $@ \
nbdkit-tar-plugin.1: nbdkit-tar-plugin.pod
$(PODWRAPPER) --section=1 --man $@ \
--html $(top_builddir)/html/$@.html \
$<

Expand Down
119 changes: 1 addition & 118 deletions plugins/tar/tar.pl → plugins/tar/nbdkit-tar-plugin.pod
@@ -1,8 +1,3 @@
#!@sbindir@/nbdkit perl
# -*- perl -*-

=pod
=head1 NAME

nbdkit-tar-plugin - read and write files inside tar files without unpacking
Expand Down Expand Up @@ -94,11 +89,10 @@ =head1 VERSION

=head1 SEE ALSO

L<https://github.com/libguestfs/nbdkit/blob/master/plugins/tar/tar.pl>,
L<nbdkit(1)>,
L<nbdkit-offset-filter(1)>,
L<nbdkit-plugin(3)>,
L<nbdkit-perl-plugin(3)>,
L<nbdkit-python-plugin(3)>,
L<nbdkit-xz-filter(1)>,
L<tar(1)>.

Expand All @@ -111,114 +105,3 @@ =head1 AUTHORS
=head1 COPYRIGHT

Copyright (C) 2017-2020 Red Hat Inc.
=cut

use strict;

use Cwd qw(abs_path);
use IO::File;

my $tar; # Tar file.
my $file; # File within the tar file.
my $offset; # Offset within tar file.
my $size; # Size of disk image within tar file.

sub config
{
my $k = shift;
my $v = shift;

if ($k eq "tar") {
$tar = abs_path ($v);
}
elsif ($k eq "file") {
$file = $v;
}
else {
die "unknown parameter $k";
}
}

# Check all the config parameters were set.
sub config_complete
{
die "tar or file parameter was not set\n"
unless defined $tar && defined $file;

die "$tar: file not found\n"
unless -f $tar;
}

# Find the extent of the file within the tar file.
sub get_ready
{
open (my $pipe, "-|", "tar", "--no-auto-compress", "-tRvf", $tar, $file)
or die "$tar: could not open or parse tar file, see errors above";
while (<$pipe>) {
if (/^block\s(\d+):\s\S+\s\S+\s(\d+)/) {
# Add one for the tar header, and multiply by the block size.
$offset = ($1 + 1) * 512;
$size = $2;
Nbdkit::debug ("tar: file: $file offset: $offset size: $size")
}
}
close ($pipe);

die "offset or size could not be parsed. Probably the tar file is not a tar file or the file does not exist in the tar file. See any errors above.\n"
unless defined $offset && defined $size;
}

# Accept a connection from a client, create and return the handle
# which is passed back to other calls.
sub open
{
my $readonly = shift;
my $mode = "<";
$mode = "+<" unless $readonly;
my $fh = IO::File->new;
$fh->open ($tar, $mode) or die "$tar: open: $!";
$fh->binmode;
my $h = { fh => $fh, readonly => $readonly };
return $h;
}

# Close the connection.
sub close
{
my $h = shift;
my $fh = $h->{fh};
$fh->close;
}

# Return the size.
sub get_size
{
my $h = shift;
return $size;
}

# Read.
sub pread
{
my $h = shift;
my $fh = $h->{fh};
my $count = shift;
my $offs = shift;
$fh->seek ($offset + $offs, 0) or die "seek: $!";
my $r;
$fh->read ($r, $count) or die "read: $!";
return $r;
}

# Write.
sub pwrite
{
my $h = shift;
my $fh = $h->{fh};
my $buf = shift;
my $count = length ($buf);
my $offs = shift;
$fh->seek ($offset + $offs, 0) or die "seek: $!";
print $fh ($buf);
}
104 changes: 104 additions & 0 deletions plugins/tar/tar.py
@@ -0,0 +1,104 @@
#!@sbindir@/nbdkit python
# -*- python -*-
# 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.

# Note this uses API v1 since we may wish to copy it and use it with
# older versions of nbdkit. Also it doesn't use get_ready() for the
# same reason.

import builtins
import os.path
import tarfile
import nbdkit

tar = None # Tar file.
f = None # File within the tar file.
offset = None # Offset of file within the tar file.
size = None # Size of file within the tar file.

def config(k, v):
global tar, f

if k == "tar":
tar = os.path.abspath(v)
elif k == "file":
f = v
else:
raise RuntimeError("unknown parameter: %s" % key)

# Check all the config parameters were set.
def config_complete():
global tar, f, offset, size

if tar is None or f is None:
raise RuntimeError("tar or file parameter was not set")
if not os.path.exists(tar):
raise RuntimeError("$s: file not found" % tar)

# Find the extent of the file within the tar file.
for m in tarfile.open(tar, mode='r:'):
if m.name == f:
offset = m.offset_data
size = m.size
if offset is None or size is None:
raise RuntimeError("offset or size could not be parsed. Probably the tar file is not a tar file or the file does not exist in the tar file.")

# Accept a connection from a client, create and return the handle
# which is passed back to other calls.
def open(readonly):
global tar
if readonly:
mode = 'rb'
else:
mode = 'r+b'
return { 'fh': builtins.open(tar, mode) }

# Close the connection.
def close(h):
h['fh'].close()

# Return the size.
def get_size(h):
global size
return size

# Read.
#
# Python plugin thread model is always
# NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS so seeking here is fine.
def pread(h, count, offs):
global offset
h['fh'].seek(offset + offs)
return h['fh'].read(count)

# Write.
def pwrite(h, buf, offs):
global offset
h['fh'].seek(offset + offs)
h['fh'].write(buf)
6 changes: 3 additions & 3 deletions tests/Makefile.am
Expand Up @@ -828,10 +828,10 @@ test_streaming_SOURCES = test-streaming.c
test_streaming_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS)
test_streaming_LDADD = $(LIBNBD_LIBS)

# tar plugin test (written in perl).
if HAVE_PERL
# tar plugin test (written in python).
if HAVE_PYTHON
TESTS += test-tar.sh
endif HAVE_PERL
endif HAVE_PYTHON
EXTRA_DIST += test-tar.sh

# tmpdisk plugin test.
Expand Down
4 changes: 2 additions & 2 deletions tests/test-dump-plugin.sh
Expand Up @@ -43,10 +43,10 @@ do_test ()
{
vg=; [ "$NBDKIT_VALGRIND" = "1" ] && vg="-valgrind"
case "$1$vg" in
python-valgrind | ruby-valgrind | tcl-valgrind)
python-valgrind | ruby-valgrind | tar-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
Expand Down
4 changes: 2 additions & 2 deletions tests/test-help-plugin.sh
Expand Up @@ -43,10 +43,10 @@ do_test ()
{
vg=; [ "$NBDKIT_VALGRIND" = "1" ] && vg="-valgrind"
case "$1$vg" in
python-valgrind | ruby-valgrind | tcl-valgrind)
python-valgrind | ruby-valgrind | tar-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
Expand Down
26 changes: 19 additions & 7 deletions tests/test-tar.sh
Expand Up @@ -34,14 +34,18 @@ source ./functions.sh
set -e
set -x

# Python scripts break valgrind.
if [ "$NBDKIT_VALGRIND" = "1" ]; then
echo "$0: skipping Python test under valgrind."
exit 77
fi

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
# The tar plugin is written in Python and uses the tarfile module.
requires python --version
requires python -c 'import tarfile'

sock=`mktemp -u`
files="tar.pid tar.tar $sock"
Expand All @@ -54,7 +58,15 @@ tar cf tar.tar disk
# 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
# Check for existing file.
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

0 comments on commit 2d15e79

Please sign in to comment.