Commit 85c6d7cd authored by Kevin Locke's avatar Kevin Locke
Browse files

pristine-xz: Detect pixz compression

There are at least 2 cases where `pixz` produces output which differs
from `xz --check=crc32 -T0`:

- When `pixz` compresses a `.tar` file and `-t` is not given it produces
  an extra xz data block which contains an index of the tar contents.

- For blocks where the uncompressed size is significantly less than the
  expected block size, `xz` overestimates the block heading size, which
  results in >= 4 bytes of padding while `pixz` does not.  Examples:
  * https://mirrors.edge.kernel.org/pub/software/network/iw/iw-4.14.tar.xz
  * https://mirrors.edge.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.26.tar.xz
  * http://snapshot.debian.org/archive/debian/20180220T215051Z/pool/main/k/kexec-tools/kexec-tools_2.0.16.orig.tar.xz


  All are compressed using `pixz` and have a 16-byte block header.
  Recompressing with `xz` gives a 20-byte block header with 4 pad bytes.

Due to these differences, use of `pixz` instead of `xz` is sometimes
necessary, which is why this commit introduces these checks.

Closes: #892029
Closes: #913218

Signed-off-by: Kevin Locke's avatarKevin Locke <kevin@kevinlocke.name>
parent c1982c5d
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
The delta file is a compressed tarball, containing the following files:

type
	Type of file this is a delta for ("tar", "gz", or "bz2").
	Type of file this is a delta for ("tar", "gz", "bz2", or "xz").
version
	The meaning depends on the "type" value.
sha256sum
@@ -64,6 +64,11 @@ For xz files, the wrapper contains:
version
	Currently only "2.0".
params
	Typically, only the compression level is needed.
	Typically, only the compression level is needed.  May also contain
	integrity check type, block size, and/or pixz tarball mode.  Also
	multi-threading, which affects xz block header flags and block size.
program
	Program used to compress. Almost every time, it is xz.
	Program used to compress. Almost every time, it is xz (or another
	implementation producing bit-identical results).  pixz may also be
	used in a mode which differs from xz (e.g. due to -t or block header
	padding differences).
+58 −37
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ use Pristine::Tar::Formats;
use File::Basename qw/basename/;
use IO::Handle;

my @supported_xz_programs = qw(xz);
my @supported_xz_programs = qw(xz pixz);

my $try = 0;

@@ -177,9 +177,8 @@ sub scan_xz_lvv_robot {
}

sub predict_xz_args {
  my ($xz)       = @_;
  my ($xz, $program) = @_;
  my $presets    = undef;
  my $block_list = undef;
  my $blocks     = $xz->{blocks};
  if (scalar(@$blocks)) {
    # There is at least one block. We assume the same compression
@@ -208,24 +207,37 @@ sub predict_xz_args {
      die "Unknown dict size: $dict_size\n"
        if (!defined($presets));
    }
    if (scalar(@$blocks) > 1) {
      # Gather the block uncompressed sizes
      $block_list = join(',', map { $_->{uncompressed_size} } @$blocks);
  }
  my $check_name = $xz->{stream}->{check_name};
  my $common = [$program];
  if ($program eq 'pixz') {
    # pixz only supports CRC32
    return [] if ($check_name ne 'CRC32');
    # pixz writes sizes in all block headers except tar index block
    # Note: tar index also recognizable by PIXZ_INDEX_MAGIC in block data
    my $last_block = $blocks->[-1];
    my @data_blocks = @$blocks;
    if (scalar(@$blocks) > 1 and $last_block->{size_present_flags} ne 'cu') {
      # last block is probably a tar index
      pop @data_blocks;
    } else {
      # last block is not a tar index, force non-tarball mode
      push @$common, '-t';
    }
    return [] if (grep { $_->{size_present_flags} ne 'cu' } @data_blocks);
  } else {
    my %check_kwd_of = (
      None      => 'none',
      CRC32     => 'crc32',
      CRC64     => 'crc64',
      'SHA-256' => 'sha256',
    );
  my $check_name = $xz->{stream}->{check_name};
    my $check_kwd  = $check_kwd_of{$check_name};
    die "Unknown xz check: $check_name\n" if (!defined($check_kwd));

  my $possible_args = [];
  my $common = [ "--check=$check_kwd", "-z" ];
  if (defined($block_list)) {
    push @$common, "--check=$check_kwd", '-z';
    if (scalar(@$blocks) > 1) {
      # Gather the block uncompressed sizes
      my $block_list = join(',', map { $_->{uncompressed_size} } @$blocks);
      unshift @$common, "--block-list=$block_list";
    }
    # xz writes sizes in block headers if and only if run with multiple threads
@@ -234,6 +246,8 @@ sub predict_xz_args {
      # Note: All values other than 1 produce the same output.  Use 0 for speed.
      push @$common, '-T0'
    }
  }
  my $possible_args = [];
  foreach my $preset (@$presets) {
    push @$possible_args, [ @$common, "-$preset" ];
    push @$possible_args, [ @$common, "-${preset}e" ];
@@ -253,7 +267,10 @@ sub readxz {
  # lzma2_presets_from_dict_size_of in predict_xz_args) or if --extreme
  # was used. We output possible args for each combination in this case.
  my $xz            = scan_xz_lvv_robot($filename);
  my $possible_args = predict_xz_args($xz);
  my $possible_args = [];
  foreach my $program (@supported_xz_programs) {
    push @$possible_args, @{ predict_xz_args($xz, $program) };
  }
  return $possible_args;
}

@@ -321,12 +338,11 @@ sub reproducexz {
  # If we get an error we fallback to guessing, otherwise, we should
  # succeed with one of the proposed combinations
  if (!$@) {
    foreach my $program (@supported_xz_programs) {
    foreach my $args (@$possible_args) {
      my $program = shift @$args;
      testvariant($orig, $tmpin, $program, @$args)
        && return $program, @$args;
    }
    }
  } else {
    # Fallback to guessing
    my ($possible_levels) = predictxzlevels($orig);
@@ -362,6 +378,8 @@ sub genxz {
    my $param = shift @params;

    next if $param =~ /^(-[0-9]e?)$/;

    if ($delta->{program} eq 'xz') {
      next if $param eq '-z';
      next if $param eq '--check=none';
      next if $param eq '--check=crc32';
@@ -369,6 +387,9 @@ sub genxz {
      next if $param eq '--check=sha256';
      next if $param =~ /^(--block-list=[0-9,]+)$/;
      next if $param =~ /^-T[0-9]+$/;
    } elsif ($delta->{program} eq 'pixz') {
      next if $param eq '-t';
    }
    die "paranoia check failed on params from delta ($param)";
  }
  @params = split(' ', $delta->{params});