Compare commits

..

No commits in common. "594ea3c72e7af26b680d2bfb696c0271839b731d" and "2767b051bc0f27c22388aef1ca5f160f57282340" have entirely different histories.

2 changed files with 70 additions and 239 deletions

View file

@ -42,7 +42,6 @@ use Carp;
use Term::ANSIColor; use Term::ANSIColor;
use Socket; use Socket;
use Time::HiRes; use Time::HiRes;
use Math::BigInt;
use version; use version;
## no critic (InputOutput::RequireBriefOpen) ## no critic (InputOutput::RequireBriefOpen)
@ -176,25 +175,6 @@ sub error {
} }
} }
# The encoding of dev_t is MMMM Mmmm mmmM MMmm, where M is a hex digit of
# the major number and m is a hex digit of the minor number.
sub major {
my $rdev = shift;
my $right
= Math::BigInt->from_hex("0x00000000000fff00")->band($rdev)->brsft(8);
my $left
= Math::BigInt->from_hex("0xfffff00000000000")->band($rdev)->brsft(32);
return $right->bior($left);
}
sub minor {
my $rdev = shift;
my $right = Math::BigInt->from_hex("0x00000000000000ff")->band($rdev);
my $left
= Math::BigInt->from_hex("0x00000ffffff00000")->band($rdev)->brsft(12);
return $right->bior($left);
}
# check whether a directory is mounted by comparing the device number of the # check whether a directory is mounted by comparing the device number of the
# directory itself with its parent # directory itself with its parent
sub is_mountpoint { sub is_mountpoint {
@ -241,7 +221,7 @@ sub get_tar_compressor {
} elsif ($filename =~ /\.(xz|txz)$/) { } elsif ($filename =~ /\.(xz|txz)$/) {
return ['xz', '--threads=0']; return ['xz', '--threads=0'];
} elsif ($filename =~ /\.zst$/) { } elsif ($filename =~ /\.zst$/) {
return ['zstd', '--threads=0']; return ['zstd'];
} }
return; return;
} }
@ -857,23 +837,7 @@ sub run_apt_progress {
$line_has_error = sub { $line_has_error = sub {
# apt-get doesn't report a non-zero exit if the update failed. # apt-get doesn't report a non-zero exit if the update failed.
# Thus, we have to parse its output. See #778357, #776152, #696335 # Thus, we have to parse its output. See #778357, #776152, #696335
# and #745735 for the parsing bugs as well as #594813, #696335, # and #745735
# #776152, #778357 and #953726 for non-zero exit on transient
# network errors.
#
# For example, we want to fail with the following warning:
# W: Some index files failed to download. They have been ignored,
# or old ones used instead.
# But since this message is meant for human consumption it is not
# guaranteed to be stable across different apt versions and may
# change arbitrarily in the future. Thus, we error out on any W:
# lines as well. The downside is, that apt also unconditionally
# and by design prints a warning for unsigned repositories, even
# if they were allowed with Acquire::AllowInsecureRepositories "1"
# or with trusted=yes.
#
# A workaround was introduced by apt 2.1.16 with the --error-on=any
# option to apt-get update.
if ($_[0] =~ /^(W: |Err:)/) { if ($_[0] =~ /^(W: |Err:)/) {
return 1; return 1;
} }
@ -1972,30 +1936,12 @@ sub run_setup() {
sub run_update() { sub run_update() {
my $options = shift; my $options = shift;
my $aptversion = version->new(0); info "running apt-get update...";
{ run_apt_progress({
my $pid = open my $fh, '-|', 'apt-get',
'--version' // error "failed to fork(): $!";
chomp(my $firstline = <$fh>);
close $fh;
if ( $? == 0
and $firstline =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/)
{
$aptversion = version->new($1);
}
}
my $aptopts = {
ARGV => ['apt-get', 'update'], ARGV => ['apt-get', 'update'],
CHDIR => $options->{root}, CHDIR => $options->{root},
}; FIND_APT_WARNINGS => 1
if ($aptversion < "2.1.16") { });
$aptopts->{FIND_APT_WARNINGS} = 1;
} else {
push @{ $aptopts->{ARGV} }, '--error-on=any';
}
info "running apt-get update...";
run_apt_progress($aptopts);
# check if anything was downloaded at all # check if anything was downloaded at all
{ {
@ -3049,16 +2995,7 @@ sub run_cleanup() {
if (any { $_ eq 'cleanup/apt' } @{ $options->{skip} }) { if (any { $_ eq 'cleanup/apt' } @{ $options->{skip} }) {
info "skipping cleanup/apt as requested"; info "skipping cleanup/apt as requested";
} else { } else {
if ( none { $_ eq 'cleanup/apt/lists' } @{ $options->{skip} }
and none { $_ eq 'cleanup/apt/cache' } @{ $options->{skip} }) {
info "cleaning package lists and apt cache..."; info "cleaning package lists and apt cache...";
}
if (any { $_ eq 'cleanup/apt/lists' } @{ $options->{skip} }) {
info "skipping cleanup/apt/lists as requested";
} else {
if (any { $_ eq 'cleanup/apt/cache' } @{ $options->{skip} }) {
info "cleaning package lists...";
}
run_apt_progress({ run_apt_progress({
ARGV => [ ARGV => [
'apt-get', '--option', 'apt-get', '--option',
@ -3067,16 +3004,8 @@ sub run_cleanup() {
], ],
CHDIR => $options->{root}, CHDIR => $options->{root},
}); });
}
if (any { $_ eq 'cleanup/apt/cache' } @{ $options->{skip} }) {
info "skipping cleanup/apt/cache as requested";
} else {
if (any { $_ eq 'cleanup/apt/lists' } @{ $options->{skip} }) {
info "cleaning apt cache...";
}
run_apt_progress( run_apt_progress(
{ ARGV => ['apt-get', 'clean'], CHDIR => $options->{root} }); { ARGV => ['apt-get', 'clean'], CHDIR => $options->{root} });
}
# apt since 1.6 creates the auxfiles directory. If apt inside the # apt since 1.6 creates the auxfiles directory. If apt inside the
# chroot is older than that, then it will not know how to clean it. # chroot is older than that, then it will not know how to clean it.
@ -5008,37 +4937,30 @@ sub main() {
. " signed-by value"; . " signed-by value";
last; last;
} }
# initialize gpg trustdb with empty one
{
`@gpgcmd --update-trustdb >/dev/null 2>/dev/null`;
$? == 0 or error "gpg failed to initialize trustdb: $?";
}
# find all the fingerprints of the keys apt currently # find all the fingerprints of the keys apt currently
# knows about # knows about
my @keyrings = (); my @keyringopts = ();
opendir my $dh, "$options->{apttrustedparts}" opendir my $dh, "$options->{apttrustedparts}"
or error "cannot read $options->{apttrustedparts}"; or error "cannot read $options->{apttrustedparts}";
while (my $filename = readdir $dh) { while (my $filename = readdir $dh) {
if ($filename !~ /\.(asc|gpg)$/) { if ($filename !~ /\.(asc|gpg)$/) {
next; next;
} }
$filename = "$options->{apttrustedparts}/$filename"; push @keyringopts, '--keyring',
# skip empty keyrings "$options->{apttrustedparts}/$filename";
-s "$filename" || next;
push @keyrings, "$filename";
} }
closedir $dh; closedir $dh;
if (-s $options->{apttrusted}) { if (-e $options->{apttrusted}) {
push @keyrings, $options->{apttrusted}; push @keyringopts, '--keyring', $options->{apttrusted};
} }
my @aptfingerprints = (); my @aptfingerprints = ();
if (scalar @keyrings == 0) { if (scalar @keyringopts == 0) {
$signedby = " [signed-by=\"$keyring\"]"; $signedby = " [signed-by=\"$keyring\"]";
last; last;
} }
{ {
open(my $fh, '-|', @gpgcmd, '--with-colons', '--show-keys', open my $fh, '-|', @gpgcmd, @keyringopts, '--with-colons',
@keyrings) // error "failed to fork(): $!"; '--list-keys' // error "failed to fork(): $!";
while (my $line = <$fh>) { while (my $line = <$fh>) {
if ($line !~ /^fpr:::::::::([^:]+):/) { if ($line !~ /^fpr:::::::::([^:]+):/) {
next; next;
@ -5059,8 +4981,9 @@ sub main() {
# the case # the case
my @suitefingerprints = (); my @suitefingerprints = ();
{ {
open(my $fh, '-|', @gpgcmd, '--with-colons', '--show-keys', open my $fh, '-|', @gpgcmd, '--keyring', $keyring,
$keyring) // error "failed to fork(): $!"; '--with-colons',
'--list-keys' // error "failed to fork(): $!";
while (my $line = <$fh>) { while (my $line = <$fh>) {
if ($line !~ /^fpr:::::::::([^:]+):/) { if ($line !~ /^fpr:::::::::([^:]+):/) {
next; next;
@ -5220,22 +5143,7 @@ sub main() {
# figure out the right format # figure out the right format
if ($format eq 'auto') { if ($format eq 'auto') {
# (stat(...))[6] is the device identifier which contains the major and if ($options->{target} eq '/dev/null') {
# minor numbers for character special files
# major 1 and minor 3 is /dev/null on Linux
if ( $options->{target} eq '/dev/null'
and $OSNAME eq 'linux'
and -c '/dev/null'
and major((stat("/dev/null"))[6]) == 1
and minor((stat("/dev/null"))[6]) == 3) {
$format = 'null';
} elsif ($options->{target} eq '-'
and $OSNAME eq 'linux'
and major((stat(STDOUT))[6]) == 1
and minor((stat(STDOUT))[6]) == 3) {
# by checking the major and minor number of the STDOUT fd we also
# can detect redirections to /dev/null and choose the null format
# accordingly
$format = 'null'; $format = 'null';
} elsif ($options->{target} ne '-' and -d $options->{target}) { } elsif ($options->{target} ne '-' and -d $options->{target}) {
$format = 'directory'; $format = 'directory';
@ -5312,11 +5220,6 @@ sub main() {
error "the $format format is unable to write to standard output"; error "the $format format is unable to write to standard output";
} }
if ($format eq 'null'
and none { $_ eq $options->{target} } ('-', '/dev/null')) {
info "ignoring target $options->{target} with null format";
}
if (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) { if (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) {
if ($format ne 'null') { if ($format ne 'null') {
if ( any { $_ eq $options->{variant} } ('extract', 'custom') if ( any { $_ eq $options->{variant} } ('extract', 'custom')
@ -5514,9 +5417,7 @@ sub main() {
); );
# tar2sqfs and genext2fs do not support extended attributes # tar2sqfs and genext2fs do not support extended attributes
if ($format eq "squashfs") { if ($format eq "squashfs") {
warning("tar2sqfs does not support extended attributes" warning "tar2sqfs does not support extended attributes";
. " from the 'system' namespace");
push @taropts, '--xattrs', '--xattrs-exclude=system.*';
} elsif ($format eq "ext2") { } elsif ($format eq "ext2") {
warning "genext2fs does not support extended attributes"; warning "genext2fs does not support extended attributes";
} else { } else {
@ -5870,9 +5771,9 @@ sub main() {
# change signal handler message # change signal handler message
$waiting_for = "cleanup"; $waiting_for = "cleanup";
if (any { $_ eq $format } ('directory')) { if (any { $_ eq $format } ('directory', 'null')) {
# nothing to do # nothing to do
} elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) { } elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2')) {
if (!-e $options->{root}) { if (!-e $options->{root}) {
error "$options->{root} does not exist"; error "$options->{root} does not exist";
} }
@ -6224,14 +6125,11 @@ Example: Setup chroot for installing a sub-essential busybox-based chroot with
--setup-hook='mkdir -p "$1/bin"' --setup-hook='mkdir -p "$1/bin"'
--setup-hook='for p in awk cat chmod chown cp diff echo env grep less ln --setup-hook='for p in awk cat chmod chown cp diff echo env grep less ln
mkdir mount rm rmdir sed sh sleep sort touch uname mktemp; do mkdir mount rm rmdir sed sh sleep sort touch uname; do
ln -s busybox "$1/bin/$p"; done' ln -s busybox "$1/bin/$p"; done'
--setup-hook='echo root:x:0:0:root:/root:/bin/sh > "$1/etc/passwd"' --setup-hook='echo root:x:0:0:root:/root:/bin/sh > "$1/etc/passwd"'
--setup-hook='printf "root:x:0:\nmail:x:8:\nutmp:x:43:\n" > "$1/etc/group"' --setup-hook='printf "root:x:0:\nmail:x:8:\nutmp:x:43:\n" > "$1/etc/group"'
For a more elegant way to setup merged-/usr via symlinks and for setting up a
sub-essential busybox-based chroot, see the B<--hook-dir> option below.
=item B<--extract-hook>=I<command> =item B<--extract-hook>=I<command>
Execute arbitrary I<command>s after the Essential:yes packages have been Execute arbitrary I<command>s after the Essential:yes packages have been
@ -6303,14 +6201,10 @@ if the scripts in two directories depend upon each other, the scripts must be
placed into a common directory and be named such that they get added in the placed into a common directory and be named such that they get added in the
correct order. correct order.
Example 1: Run mmdebstrap with eatmydata Example: Run mmdebstrap with eatmydata
--hook-dir=/usr/share/mmdebstrap/hooks/eatmydata --hook-dir=/usr/share/mmdebstrap/hooks/eatmydata
Example 2: Setup chroot for installing a sub-essential busybox-based chroot
--hook-dir=/usr/share/mmdebstrap/hooks/busybox
=item B<--skip>=I<stage>[,I<stage>,...] =item B<--skip>=I<stage>[,I<stage>,...]
B<mmdebstrap> tries hard to implement sensible defaults and will try to stop B<mmdebstrap> tries hard to implement sensible defaults and will try to stop
@ -6505,17 +6399,16 @@ Without that option the default format is I<auto>. The following formats exist:
When selecting this format (the default), the actual format will be inferred When selecting this format (the default), the actual format will be inferred
from the I<TARGET> positional argument. If I<TARGET> was not specified, then from the I<TARGET> positional argument. If I<TARGET> was not specified, then
the B<tar> format will be chosen. If I<TARGET> happens to be F</dev/null> or if the B<tar> format will be chosen. If I<TARGET> happens to be F</dev/null>, then
standard output is F</dev/null>, then the B<null> format will be chosen. If the B<null> format will be chosen. If I<TARGET> is an existing directory, and
I<TARGET> is an existing directory, and does not equal to C<->, then the does not equal to C<->, then the B<directory> format will be chosen. If
B<directory> format will be chosen. If I<TARGET> ends with C<.tar> or with one I<TARGET> ends with C<.tar> or with one of the filename extensions listed in
of the filename extensions listed in the section B<COMPRESSION>, or if the section B<COMPRESSION>, or if I<TARGET> equals C<->, or if I<TARGET> is a
I<TARGET> equals C<->, or if I<TARGET> is a named pipe (fifo) or if I<TARGET> named pipe (fifo) or if I<TARGET> is a character special file like
is a character special file, then the B<tar> format will be chosen. If F</dev/null>, then the B<tar> format will be chosen. If I<TARGET> ends with
I<TARGET> ends with C<.squashfs> or C<.sqfs>, then the B<squashfs> format will C<.squashfs> or C<.sqfs>, then the B<squashfs> format will be chosen. If
be chosen. If <TARGET> ends with C<.ext2> then the B<ext2> format will be <TARGET> ends with C<.ext2> then the B<ext2> format will be chosen. If none of
chosen. If none of these conditions apply, the B<directory> format will be these conditions apply, the B<directory> format will be chosen.
chosen.
=item B<directory>, B<dir> =item B<directory>, B<dir>
@ -6547,11 +6440,8 @@ C<tar2sqfs> utility, which will create an xz compressed squashfs image with a
blocksize of 1048576 bytes in I<TARGET>. The special I<TARGET> C<-> does not blocksize of 1048576 bytes in I<TARGET>. The special I<TARGET> C<-> does not
work with this format because C<tar2sqfs> can only write to a regular file. If work with this format because C<tar2sqfs> can only write to a regular file. If
you need your squashfs image be named C<->, then just explicitly pass the you need your squashfs image be named C<->, then just explicitly pass the
relative path to it like F<./->. The C<tar2sqfs> tool only supports a limited relative path to it like F<./->. Since C<tar2sqfs> does not support extended
set of extended attribute prefixes. Therefore, extended attributes are disabled attributes, the resulting image will not contain them.
in the resulting image. If you need them, create a tarball first and remove the
extended attributes from its pax headers. Refer to the B<EXAMPLES> section for
how to achieve this.
=item B<ext2> =item B<ext2>
@ -6572,8 +6462,7 @@ A temporary chroot directory will be created in C<$TMPDIR> or F</tmp> if
C<$TMPDIR> is not set. After the bootstrap is complete, the temporary chroot C<$TMPDIR> is not set. After the bootstrap is complete, the temporary chroot
will be deleted without being part of the output. This is most useful when the will be deleted without being part of the output. This is most useful when the
desired artifact is generated inside the chroot and it is transferred using desired artifact is generated inside the chroot and it is transferred using
special hooks such as B<sync-out>. It is also useful in situations where only special hooks such as B<sync-out>.
the exit code or stdout or stderr of a process run in a hook is of interest.
=back =back
@ -6786,7 +6675,7 @@ Performs cleanup tasks like:
=over 4 =over 4
=item * Removes the package lists (unless B<--skip=cleanup/apt/lists>) and apt cache (unless B<--skip=cleanup/apt/cache>). Both removals can be disabled by using B<--skip=cleanup/apt>. =item * Removes the package lists and apt cache. This can be disabled using B<--skip=cleanup/apt>.
=item * Remove all files that were put into the chroot for setup purposes, like F</etc/apt/apt.conf.d/00mmdebstrap>, the temporary apt config and the qemu-user-static binary. This can be disabled using B<--skip=cleanup/mmdebstrap>. =item * Remove all files that were put into the chroot for setup purposes, like F</etc/apt/apt.conf.d/00mmdebstrap>, the temporary apt config and the qemu-user-static binary. This can be disabled using B<--skip=cleanup/mmdebstrap>.
@ -6830,16 +6719,7 @@ Instead of a tarball, a squashfs image can be created:
By default, B<mmdebstrap> runs B<tar2sqfs> with C<--no-skip --exportable By default, B<mmdebstrap> runs B<tar2sqfs> with C<--no-skip --exportable
--compressor xz --block-size 1048576>. To choose a different set of options, --compressor xz --block-size 1048576>. To choose a different set of options,
and to filter out all extended attributes not supported by B<tar2sqfs>, pipe pipe the output of B<mmdebstrap> into B<tar2sqfs> manually.
the output of B<mmdebstrap> into B<tar2sqfs> manually like so:
$ mmdebstrap unstable \
| mmtarfilter --pax-exclude='*' \
--pax-include='SCHILY.xattr.user.*' \
--pax-include='SCHILY.xattr.trusted.*' \
--pax-include='SCHILY.xattr.security.*' \
| tar2sqfs --quiet --no-skip --force --exportable --compressor xz \
--block-size 1048576 unstable-chroot.squashfs
By default, debootstrapping a stable distribution will add mirrors for security By default, debootstrapping a stable distribution will add mirrors for security
and updates to the sources.list. and updates to the sources.list.
@ -7063,7 +6943,7 @@ https://gitlab.mister-muffin.de/josch/mmdebstrap/issues
https://bugs.debian.org/src:mmdebstrap https://bugs.debian.org/src:mmdebstrap
As of version 1.20.9, dpkg does not provide facilities preventing it from As of version 1.19.5, dpkg does not provide facilities preventing it from
reading the dpkg configuration of the machine running B<mmdebstrap>. reading the dpkg configuration of the machine running B<mmdebstrap>.
Therefore, until this dpkg limitation is fixed, a default dpkg configuration is Therefore, until this dpkg limitation is fixed, a default dpkg configuration is
recommended on machines running B<mmdebstrap>. If you are using B<mmdebstrap> recommended on machines running B<mmdebstrap>. If you are using B<mmdebstrap>
@ -7071,13 +6951,12 @@ as the non-root user, then as a workaround you could run C<chmod 600
/etc/dpkg/dpkg.cfg.d/*> so that the config files are only accessible by the /etc/dpkg/dpkg.cfg.d/*> so that the config files are only accessible by the
root user. root user.
With apt versions before 2.1.16, setting C<[trusted=yes]> or Setting [trusted=yes] to allow signed archives without a known public key will
C<Acquire::AllowInsecureRepositories "1"> to allow signed archives without a fail because of a gpg warning in the apt output. Since apt does not
known public key or unsigned archives will fail because of a gpg warning in the communicate its status via any other means than human readable strings,
apt output. Since apt does not communicate its status via any other means than B<mmdebstrap> treats any warning from "apt-get update" as an error. Fixing
human readable strings, and because B<mmdebstrap> wants to treat transient this will require apt to provide a machine readable status interface. See
network errors as errors, B<mmdebstrap> treats any warning from "apt-get Debian bugs #778357, #776152, #696335, and #745735.
update" as an error.
=head1 SEE ALSO =head1 SEE ALSO

View file

@ -25,20 +25,12 @@ import fnmatch
import re import re
class PathFilterAction(argparse.Action): class FilterAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, "pathfilter", []) items = getattr(namespace, "filter", [])
regex = re.compile(fnmatch.translate(values)) regex = re.compile(fnmatch.translate(values))
items.append((self.dest, regex)) items.append((self.dest, regex))
setattr(namespace, "pathfilter", items) setattr(namespace, "filter", items)
class PaxFilterAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, "paxfilter", [])
regex = re.compile(fnmatch.translate(values))
items.append((self.dest, regex))
setattr(namespace, "paxfilter", items)
def main(): def main():
@ -47,46 +39,22 @@ def main():
Filters a tarball on standard input by the same rules as the dpkg --path-exclude Filters a tarball on standard input by the same rules as the dpkg --path-exclude
and --path-include options and writes resulting tarball to standard output. See and --path-include options and writes resulting tarball to standard output. See
dpkg(1) for information on how these two options work in detail. dpkg(1) for information on how these two options work in detail.
Similarly, filter out unwanted pax extended headers. This is useful in cases
where a tool only accepts certain xattr prefixes. For example tar2sqfs only
supports SCHILY.xattr.user.*, SCHILY.xattr.trusted.* and
SCHILY.xattr.security.* but not SCHILY.xattr.system.posix_acl_default.*.
Both types of options use Unix shell-style wildcards:
* matches everything
? matches any single character
[seq] matches any character in seq
[!seq] matches any character not in seq
""" """
) )
parser.add_argument( parser.add_argument(
"--path-exclude", "--path-exclude",
metavar="pattern", metavar="pattern",
action=PathFilterAction, action=FilterAction,
help="Exclude path matching the given shell pattern.", help="Exclude path matching the given shell pattern.",
) )
parser.add_argument( parser.add_argument(
"--path-include", "--path-include",
metavar="pattern", metavar="pattern",
action=PathFilterAction, action=FilterAction,
help="Re-include a pattern after a previous exclusion.", help="Re-include a pattern after a previous exclusion.",
) )
parser.add_argument(
"--pax-exclude",
metavar="pattern",
action=PaxFilterAction,
help="Exclude pax header matching the given globbing pattern.",
)
parser.add_argument(
"--pax-include",
metavar="pattern",
action=PaxFilterAction,
help="Re-include a pax header after a previous exclusion.",
)
args = parser.parse_args() args = parser.parse_args()
if not hasattr(args, "pathfilter") and not hasattr(args, "paxfilter"): if not hasattr(args, "filter"):
from shutil import copyfileobj from shutil import copyfileobj
copyfileobj(sys.stdin.buffer, sys.stdout.buffer) copyfileobj(sys.stdin.buffer, sys.stdout.buffer)
@ -95,52 +63,36 @@ Both types of options use Unix shell-style wildcards:
# same logic as in dpkg/src/filters.c/filter_should_skip() # same logic as in dpkg/src/filters.c/filter_should_skip()
prefix_prog = re.compile(r"^([^*?[\\]*).*") prefix_prog = re.compile(r"^([^*?[\\]*).*")
def path_filter_should_skip(member): def filter_should_skip(member):
skip = False skip = False
if not hasattr(args, "pathfilter"): if not args.filter:
return False return False
for (t, r) in args.pathfilter: for (t, r) in args.filter:
if r.match(member.name[1:]) is not None: if r.match(member.name[1:]) is not None:
if t == "path_include": if t == "path_include":
skip = False skip = False
else: else:
skip = True skip = True
if skip and (member.isdir() or member.issym()): if skip and (member.isdir() or member.issym()):
for (t, r) in args.pathfilter: for (t, r) in args.filter:
if t != "path_include": if t != "path_include":
continue continue
prefix = prefix_prog.sub(r"\1", r.pattern) prefix = prefix_prog.sub(r"\1", r.pattern)
prefix = prefix.rstrip("/") prefix = prefix.rstrip("/")
if member.name[1:].startswith(prefix): if member.name[1:].startswith(prefix):
if member.name == "./usr/share/doc/doc-debian":
print("foo", prefix, "bar", file=sys.stderr)
return False return False
return skip return skip
def pax_filter_should_skip(header):
if not hasattr(args, "paxfilter"):
return False
skip = False
for (t, r) in args.paxfilter:
if r.match(header) is None:
continue
if t == "pax_include":
skip = False
else:
skip = True
return skip
# starting with Python 3.8, the default format became PAX_FORMAT, so this # starting with Python 3.8, the default format became PAX_FORMAT, so this
# is only for compatibility with older versions of Python 3 # is only for compatibility with older versions of Python 3
with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as in_tar, tarfile.open( with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as in_tar, tarfile.open(
fileobj=sys.stdout.buffer, mode="w|", format=tarfile.PAX_FORMAT fileobj=sys.stdout.buffer, mode="w|", format=tarfile.PAX_FORMAT
) as out_tar: ) as out_tar:
for member in in_tar: for member in in_tar:
if path_filter_should_skip(member): if filter_should_skip(member):
continue continue
member.pax_headers = {
k: v
for k, v in member.pax_headers.items()
if not pax_filter_should_skip(k)
}
if member.isfile(): if member.isfile():
with in_tar.extractfile(member) as file: with in_tar.extractfile(member) as file:
out_tar.addfile(member, file) out_tar.addfile(member, file)