1 # Copyright © 2008-2010 Raphaël Hertzog <hertzog@debian.org>
2 # Copyright © 2012-2014 Guillem Jover <guillem@debian.org>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 package Dpkg::Compression::FileHandle;
22 our $VERSION = '1.01';
24 use POSIX qw(:signal_h :sys_wait_h);
27 use Dpkg::Compression;
28 use Dpkg::Compression::Process;
30 use Dpkg::ErrorHandling;
32 use parent qw(IO::File Tie::Handle);
34 # Useful reference to understand some kludges required to
35 # have the object behave like a filehandle
36 # http://blog.woobling.org/2009/10/are-filehandles-objects.html
42 Dpkg::Compression::FileHandle - object dealing transparently with file compression
46 use Dpkg::Compression::FileHandle;
50 $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
51 print $fh "Something\n";
54 $fh = Dpkg::Compression::FileHandle->new();
55 open($fh, '>', 'sample.bz2');
56 print $fh "Something\n";
59 $fh = Dpkg::Compression::FileHandle->new();
60 $fh->open('sample.xz', 'w');
61 $fh->print("Something\n");
64 $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
68 $fh = Dpkg::Compression::FileHandle->new();
69 open($fh, '<', 'sample.bz2');
73 $fh = Dpkg::Compression::FileHandle->new();
74 $fh->open('sample.xz', 'r');
75 @lines = $fh->getlines();
80 Dpkg::Compression::FileHandle is an object that can be used
81 like any filehandle and that deals transparently with compressed
82 files. By default, the compression scheme is guessed from the filename
83 but you can override this behaviour with the method C<set_compression>.
85 If you don't open the file explicitly, it will be auto-opened on the
86 first read or write operation based on the filename set at creation time
87 (or later with the C<set_filename> method).
89 Once a file has been opened, the filehandle must be closed before being
90 able to open another file.
92 =head1 STANDARD FUNCTIONS
94 The standard functions acting on filehandles should accept a
95 Dpkg::Compression::FileHandle object transparently including
96 C<open> (only when using the variant with 3 parameters), C<close>,
97 C<binmode>, C<eof>, C<fileno>, C<getc>, C<print>, C<printf>, C<read>,
98 C<sysread>, C<say>, C<write>, C<syswrite>, C<seek>, C<sysseek>, C<tell>.
100 Note however that C<seek> and C<sysseek> will only work on uncompressed
101 files as compressed files are really pipes to the compressor programs
102 and you can't seek on a pipe.
104 =head1 FileHandle METHODS
106 The object inherits from IO::File so all methods that work on this
107 object should work for Dpkg::Compression::FileHandle too. There
108 may be exceptions though.
110 =head1 PUBLIC METHODS
114 =item $fh = Dpkg::Compression::FileHandle->new(%opts)
116 Creates a new filehandle supporting on-the-fly compression/decompression.
117 Supported options are "filename", "compression", "compression_level" (see
118 respective set_* functions) and "add_comp_ext". If "add_comp_ext"
119 evaluates to true, then the extension corresponding to the selected
120 compression scheme is automatically added to the recorded filename. It's
121 obviously incompatible with automatic detection of the compression method.
127 my ($this, %args) = @_;
128 my $class = ref($this) || $this;
129 my $self = IO::File->new();
130 # Tying is required to overload the open functions and to auto-open
131 # the file on first read/write operation
132 tie *$self, $class, $self;
135 *$self->{compression} = 'auto';
136 *$self->{compressor} = Dpkg::Compression::Process->new();
137 *$self->{add_comp_ext} = $args{add_compression_extension} ||
138 $args{add_comp_ext} || 0;
139 *$self->{allow_sigpipe} = 0;
140 if (exists $args{filename}) {
141 $self->set_filename($args{filename});
143 if (exists $args{compression}) {
144 $self->set_compression($args{compression});
146 if (exists $args{compression_level}) {
147 $self->set_compression_level($args{compression_level});
152 =item $fh->ensure_open($mode, %opts)
154 Ensure the file is opened in the requested mode ("r" for read and "w" for
155 write). The options are passed down to the compressor's spawn() call, if one
156 is used. Opens the file with the recorded filename if needed. If the file
157 is already open but not in the requested mode, then it errors out.
162 my ($self, $mode, %opts) = @_;
163 if (exists *$self->{mode}) {
164 return if *$self->{mode} eq $mode;
165 croak "ensure_open requested incompatible mode: $mode";
168 delete $opts{from_pipe};
169 delete $opts{from_file};
170 delete $opts{to_pipe};
171 delete $opts{to_file};
174 $self->_open_for_write(%opts);
175 } elsif ($mode eq 'r') {
176 $self->_open_for_read(%opts);
178 croak "invalid mode in ensure_open: $mode";
184 ## METHODS FOR TIED HANDLE
187 my ($class, $self) = @_;
192 my ($self, $scalar, $length, $offset) = @_;
193 $self->ensure_open('w');
194 return *$self->{file}->write($scalar, $length, $offset);
198 my ($self, $scalar, $length, $offset) = @_;
199 $self->ensure_open('r');
200 return *$self->{file}->read($scalar, $length, $offset);
205 $self->ensure_open('r');
206 return *$self->{file}->getlines() if wantarray;
207 return *$self->{file}->getline();
212 if (scalar(@_) == 2) {
213 my ($mode, $filename) = @_;
214 $self->set_filename($filename);
216 $self->_open_for_write();
217 } elsif ($mode eq '<') {
218 $self->_open_for_read();
220 croak 'Dpkg::Compression::FileHandle does not support ' .
224 croak 'Dpkg::Compression::FileHandle only supports open() ' .
227 return 1; # Always works (otherwise errors out)
233 if (defined *$self->{file}) {
234 $ret = *$self->{file}->close(@_) if *$self->{file}->opened();
244 return *$self->{file}->fileno(@_) if defined *$self->{file};
249 # Since perl 5.12, an integer parameter is passed describing how the
250 # function got called, just ignore it.
251 my ($self, $param) = (shift, shift);
252 return *$self->{file}->eof(@_) if defined *$self->{file};
258 return *$self->{file}->seek(@_) if defined *$self->{file};
264 return *$self->{file}->tell(@_) if defined *$self->{file};
270 return *$self->{file}->binmode(@_) if defined *$self->{file};
278 =item $fh->set_compression($comp)
280 Defines the compression method used. $comp should one of the methods supported by
281 B<Dpkg::Compression> or "none" or "auto". "none" indicates that the file is
282 uncompressed and "auto" indicates that the method must be guessed based
283 on the filename extension used.
287 sub set_compression {
288 my ($self, $method) = @_;
289 if ($method ne 'none' and $method ne 'auto') {
290 *$self->{compressor}->set_compression($method);
292 *$self->{compression} = $method;
295 =item $fh->set_compression_level($level)
297 Indicate the desired compression level. It should be a value accepted
298 by the function C<compression_is_valid_level> of B<Dpkg::Compression>.
302 sub set_compression_level {
303 my ($self, $level) = @_;
304 *$self->{compressor}->set_compression_level($level);
307 =item $fh->set_filename($name, [$add_comp_ext])
309 Use $name as filename when the file must be opened/created. If
310 $add_comp_ext is passed, it indicates whether the default extension
311 of the compression method must be automatically added to the filename
317 my ($self, $filename, $add_comp_ext) = @_;
318 *$self->{filename} = $filename;
319 # Automatically add compression extension to filename
320 if (defined($add_comp_ext)) {
321 *$self->{add_comp_ext} = $add_comp_ext;
323 my $comp_ext_regex = compression_get_file_extension_regex();
324 if (*$self->{add_comp_ext} and $filename =~ /\.$comp_ext_regex$/) {
325 warning('filename %s already has an extension of a compressed file ' .
326 'and add_comp_ext is active', $filename);
330 =item $file = $fh->get_filename()
332 Returns the filename that would be used when the filehandle must
333 be opened (both in read and write mode). This function errors out
334 if "add_comp_ext" is enabled while the compression method is set
335 to "auto". The returned filename includes the extension of the compression
336 method if "add_comp_ext" is enabled.
342 my $comp = *$self->{compression};
343 if (*$self->{add_comp_ext}) {
344 if ($comp eq 'auto') {
345 croak 'automatic detection of compression is ' .
346 'incompatible with add_comp_ext';
347 } elsif ($comp eq 'none') {
348 return *$self->{filename};
350 return *$self->{filename} . '.' .
351 compression_get_property($comp, 'file_ext');
354 return *$self->{filename};
358 =item $ret = $fh->use_compression()
360 Returns "0" if no compression is used and the compression method used
361 otherwise. If the compression is set to "auto", the value returned
362 depends on the extension of the filename obtained with the B<get_filename>
367 sub use_compression {
369 my $comp = *$self->{compression};
370 if ($comp eq 'none') {
372 } elsif ($comp eq 'auto') {
373 $comp = compression_guess_from_filename($self->get_filename());
374 *$self->{compressor}->set_compression($comp) if $comp;
379 =item $real_fh = $fh->get_filehandle()
381 Returns the real underlying filehandle. Useful if you want to pass it
382 along in a derived object.
388 return *$self->{file} if exists *$self->{file};
393 sub _open_for_write {
394 my ($self, %opts) = @_;
397 croak 'cannot reopen an already opened compressed file'
398 if exists *$self->{mode};
400 if ($self->use_compression()) {
401 *$self->{compressor}->compress(from_pipe => \$filehandle,
402 to_file => $self->get_filename(), %opts);
404 CORE::open($filehandle, '>', $self->get_filename)
405 or syserr(g_('cannot write %s'), $self->get_filename());
407 *$self->{mode} = 'w';
408 *$self->{file} = $filehandle;
412 my ($self, %opts) = @_;
415 croak 'cannot reopen an already opened compressed file'
416 if exists *$self->{mode};
418 if ($self->use_compression()) {
419 *$self->{compressor}->uncompress(to_pipe => \$filehandle,
420 from_file => $self->get_filename(), %opts);
421 *$self->{allow_sigpipe} = 1;
423 CORE::open($filehandle, '<', $self->get_filename)
424 or syserr(g_('cannot read %s'), $self->get_filename());
426 *$self->{mode} = 'r';
427 *$self->{file} = $filehandle;
432 my $cmdline = *$self->{compressor}{cmdline} // '';
433 *$self->{compressor}->wait_end_process(nocheck => *$self->{allow_sigpipe});
434 if (*$self->{allow_sigpipe}) {
435 unless (($? == 0) || (WIFSIGNALED($?) && (WTERMSIG($?) == SIGPIPE))) {
436 subprocerr($cmdline);
438 *$self->{allow_sigpipe} = 0;
440 delete *$self->{mode};
441 delete *$self->{file};
446 =head1 DERIVED OBJECTS
448 If you want to create an object that inherits from
449 Dpkg::Compression::FileHandle you must be aware that
450 the object is a reference to a GLOB that is returned by Symbol::gensym()
451 and as such it's not a HASH.
453 You can store internal data in a hash but you have to use
454 C<*$self->{...}> to access the associated hash like in the example below:
457 my ($self, $value) = @_;
458 *$self->{option} = $value;
463 =head2 Version 1.01 (dpkg 1.17.11)
465 New argument: $fh->ensure_open() accepts an %opts argument.
467 =head2 Version 1.00 (dpkg 1.15.6)
469 Mark the module as public.