File: pristine-xz

package info (click to toggle)
pristine-tar 1.25%2Bdeb7u1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 668 kB
  • sloc: ansic: 9,370; perl: 1,275; makefile: 189
file content (222 lines) | stat: -rwxr-xr-x 5,077 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/perl

=head1 NAME

pristine-xz - regenerate pristine xz files

=head1 SYNOPSIS

B<pristine-xz> [-vdk] gendelta I<file.xz> I<delta>

B<pristine-xz> [-vdk] genxz I<delta> I<file>

=head1 DESCRIPTION

This is a complement to the pristine-tar(1) command. Normally you
don't need to run it by hand, since pristine-tar calls it as necessary
to handle .tar.xz files.

pristine-xz gendelta takes the specified I<xz> file, and generates a
small binary I<delta> file that can later be used by pristine-xz genxz
to recreate the original file.

pristine-xz genxz takes the specified I<delta> file, and compresses the
specified input I<file> (which must be identical to the contents of the
original xz file). The resulting file will be identical to
the original gz file used to create the delta.

The approach used to regenerate the original xz file is to figure out
how it was produced -- what compression level was used, etc. Currently
support is poor for xz files produced with unusual compression options.

If the delta filename is "-", pristine-xz reads or writes it to stdio.

=head1 OPTIONS

=over 4

=item -v

Verbose mode, show each command that is run.

=item -d

Debug mode.

=item -k

Don't clean up the temporary directory on exit.

=item -t

Try harder to determine how to generate deltas of difficult xz files.

=back

=head1 ENVIRONMENT

=over 4

=item B<TMPDIR>

Specifies a location to place temporary files, other than the default.

=back

=head1 AUTHOR

Joey Hess <joeyh@debian.org>,
Faidon Liambotis <paravoid@debian.org>,
Cyril Brulebois <cyril.brulebois@enst-bretagne.fr>

Licensed under the GPL, version 2.

=cut

use warnings;
use strict;
use Pristine::Tar;
use Pristine::Tar::Delta;
use Pristine::Tar::Formats;
use File::Basename qw/basename/;
use IO::Handle;

my @supported_xz_programs = qw(xz);

my $try=0;

dispatch(
	commands => {
		usage => [\&usage],
		genxz => [\&genxz, 2],
		gendelta => [\&gendelta, 2],
	},
	options => {
		"t|try!" => \$try,
	},
);

sub usage {
	print STDERR "Usage: pristine-xz [-vdkt] gendelta file.xz delta\n";
	print STDERR "       pristine-xz [-vdkt] genxz delta file\n";
}

sub readxz {
	my $filename = shift;

	if (! is_xz($filename)) {
		error "This is not a valid xz archive.";
	}

	# XXX We don't currently have a way to guess the level from the
	# file format, as this level only presets several other tunables.
	# Correct handling would involve finding as many preset values as
	# possible, and reconstructing the compression level from that.
	#
	# So far in the wild only these levels have been seen.
	# (Note that level 9 can use a lot of memory.)
	my $possible_levels = ["6", "9", "0", "6e", "9e", "0e"];

	return ($possible_levels);
}

sub predictxzargs {
	my ($possible_levels, $program) = @_;

	my @args;
	foreach my $level (@$possible_levels) {
		push @args, ["-z", "-$level"];
		push @args, ["-z", "-$level", "--check=crc32"];
		push @args, ["-z", "-$level", "--check=sha256"];
	}
	return @args;
}

sub testvariant {
	my ($old, $tmpin, $xz_program, @args) = @_;

	my $new=$tmpin.'.xz';
	unlink($new);

	# Note that file name, mode, mtime do not matter to xz.

	# try xz'ing with the arguments passed
	doit_redir($tmpin, $new, $xz_program, @args);

	unless (-e $new) {
		die("$xz_program failed, aborting");
	}

	# and compare the generated with the original
	return !comparefiles($old, $new);
}

sub reproducexz {
	my $orig=shift;

	my $wd=tempdir();
	
	my $tmpin="$wd/test";
	doit_redir($orig, $tmpin, "xz", "-dc");

	# read fields from xz headers
	my ($possible_levels) = readxz($orig);

	foreach my $program (@supported_xz_programs) {
		# try to guess the xz arguments that are needed by the
		# header information
		foreach my $args (predictxzargs($possible_levels, $program)) {
			testvariant($orig, $tmpin, $program, @$args)
				&& return $program, @$args;
		}
	}

	print STDERR "pristine-xz failed to reproduce build of $orig\n";
	print STDERR "(Please file a bug report.)\n";
	exit 1;
}

sub genxz {
	my $deltafile=shift;
	my $file=shift;

	my $delta=Pristine::Tar::Delta::read(Tarball => $deltafile);
	Pristine::Tar::Delta::assert($delta, type => "xz", maxversion => 2, 
		fields => [qw{params program}]);

	my @params=split(' ', $delta->{params});
	while (@params) {
		my $param=shift @params;

		next if $param=~/^(-[0-9]e?)$/;
		next if $param eq '-z';
		next if $param eq '--check=none';
		next if $param eq '--check=crc32';
		next if $param eq '--check=crc64';
		next if $param eq '--check=sha256';
		next if $param=~/^(--block-list=[0-9,]+)$/;
		die "paranoia check failed on params from delta (@params)";
	}
	@params=split(' ', $delta->{params});

	my $program=$delta->{program};
	if (! grep { $program eq $_ } @supported_xz_programs) {
		die "paranoia check failed on program from delta ($program)";
	}

	doit($program, @params, $file);
}

sub gendelta {
	my $xzfile=shift;
	my $deltafile=shift;

	my ($program, @params) = reproducexz($xzfile);

	Pristine::Tar::Delta::write(Tarball => $deltafile, {
		version => '2.0',
		type => 'xz',
		params => "@params",
		program => $program,
	});
}