dpkg (1.18.25) stretch; urgency=medium
[dpkg] / scripts / Dpkg / Compression / FileHandle.pm
1 # Copyright © 2008-2010 Raphaël Hertzog <hertzog@debian.org>
2 # Copyright © 2012-2014 Guillem Jover <guillem@debian.org>
3 #
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.
8 #
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.
13 #
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/>.
16
17 package Dpkg::Compression::FileHandle;
18
19 use strict;
20 use warnings;
21
22 our $VERSION = '1.01';
23
24 use POSIX qw(:signal_h :sys_wait_h);
25 use Carp;
26
27 use Dpkg::Compression;
28 use Dpkg::Compression::Process;
29 use Dpkg::Gettext;
30 use Dpkg::ErrorHandling;
31
32 use parent qw(IO::File Tie::Handle);
33
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
37
38 =encoding utf8
39
40 =head1 NAME
41
42 Dpkg::Compression::FileHandle - object dealing transparently with file compression
43
44 =head1 SYNOPSIS
45
46 use Dpkg::Compression::FileHandle;
47
48 my ($fh, @lines);
49
50 $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
51 print $fh "Something\n";
52 close $fh;
53
54 $fh = Dpkg::Compression::FileHandle->new();
55 open($fh, '>', 'sample.bz2');
56 print $fh "Something\n";
57 close $fh;
58
59 $fh = Dpkg::Compression::FileHandle->new();
60 $fh->open('sample.xz', 'w');
61 $fh->print("Something\n");
62 $fh->close();
63
64 $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
65 @lines = <$fh>;
66 close $fh;
67
68 $fh = Dpkg::Compression::FileHandle->new();
69 open($fh, '<', 'sample.bz2');
70 @lines = <$fh>;
71 close $fh;
72
73 $fh = Dpkg::Compression::FileHandle->new();
74 $fh->open('sample.xz', 'r');
75 @lines = $fh->getlines();
76 $fh->close();
77
78 =head1 DESCRIPTION
79
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>.
84
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).
88
89 Once a file has been opened, the filehandle must be closed before being
90 able to open another file.
91
92 =head1 STANDARD FUNCTIONS
93
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>.
99
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.
103
104 =head1 FileHandle METHODS
105
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.
109
110 =head1 PUBLIC METHODS
111
112 =over 4
113
114 =item $fh = Dpkg::Compression::FileHandle->new(%opts)
115
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.
122
123 =cut
124
125 # Object methods
126 sub new {
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;
133 bless $self, $class;
134 # Initializations
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});
142 }
143 if (exists $args{compression}) {
144 $self->set_compression($args{compression});
145 }
146 if (exists $args{compression_level}) {
147 $self->set_compression_level($args{compression_level});
148 }
149 return $self;
150 }
151
152 =item $fh->ensure_open($mode, %opts)
153
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.
158
159 =cut
160
161 sub ensure_open {
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";
166 } else {
167 # Sanitize options.
168 delete $opts{from_pipe};
169 delete $opts{from_file};
170 delete $opts{to_pipe};
171 delete $opts{to_file};
172
173 if ($mode eq 'w') {
174 $self->_open_for_write(%opts);
175 } elsif ($mode eq 'r') {
176 $self->_open_for_read(%opts);
177 } else {
178 croak "invalid mode in ensure_open: $mode";
179 }
180 }
181 }
182
183 ##
184 ## METHODS FOR TIED HANDLE
185 ##
186 sub TIEHANDLE {
187 my ($class, $self) = @_;
188 return $self;
189 }
190
191 sub WRITE {
192 my ($self, $scalar, $length, $offset) = @_;
193 $self->ensure_open('w');
194 return *$self->{file}->write($scalar, $length, $offset);
195 }
196
197 sub READ {
198 my ($self, $scalar, $length, $offset) = @_;
199 $self->ensure_open('r');
200 return *$self->{file}->read($scalar, $length, $offset);
201 }
202
203 sub READLINE {
204 my ($self) = shift;
205 $self->ensure_open('r');
206 return *$self->{file}->getlines() if wantarray;
207 return *$self->{file}->getline();
208 }
209
210 sub OPEN {
211 my ($self) = shift;
212 if (scalar(@_) == 2) {
213 my ($mode, $filename) = @_;
214 $self->set_filename($filename);
215 if ($mode eq '>') {
216 $self->_open_for_write();
217 } elsif ($mode eq '<') {
218 $self->_open_for_read();
219 } else {
220 croak 'Dpkg::Compression::FileHandle does not support ' .
221 "open() mode $mode";
222 }
223 } else {
224 croak 'Dpkg::Compression::FileHandle only supports open() ' .
225 'with 3 parameters';
226 }
227 return 1; # Always works (otherwise errors out)
228 }
229
230 sub CLOSE {
231 my ($self) = shift;
232 my $ret = 1;
233 if (defined *$self->{file}) {
234 $ret = *$self->{file}->close(@_) if *$self->{file}->opened();
235 } else {
236 $ret = 0;
237 }
238 $self->_cleanup();
239 return $ret;
240 }
241
242 sub FILENO {
243 my ($self) = shift;
244 return *$self->{file}->fileno(@_) if defined *$self->{file};
245 return;
246 }
247
248 sub EOF {
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};
253 return 1;
254 }
255
256 sub SEEK {
257 my ($self) = shift;
258 return *$self->{file}->seek(@_) if defined *$self->{file};
259 return 0;
260 }
261
262 sub TELL {
263 my ($self) = shift;
264 return *$self->{file}->tell(@_) if defined *$self->{file};
265 return -1;
266 }
267
268 sub BINMODE {
269 my ($self) = shift;
270 return *$self->{file}->binmode(@_) if defined *$self->{file};
271 return;
272 }
273
274 ##
275 ## NORMAL METHODS
276 ##
277
278 =item $fh->set_compression($comp)
279
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.
284
285 =cut
286
287 sub set_compression {
288 my ($self, $method) = @_;
289 if ($method ne 'none' and $method ne 'auto') {
290 *$self->{compressor}->set_compression($method);
291 }
292 *$self->{compression} = $method;
293 }
294
295 =item $fh->set_compression_level($level)
296
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>.
299
300 =cut
301
302 sub set_compression_level {
303 my ($self, $level) = @_;
304 *$self->{compressor}->set_compression_level($level);
305 }
306
307 =item $fh->set_filename($name, [$add_comp_ext])
308
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
312 (or not).
313
314 =cut
315
316 sub set_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;
322 }
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);
327 }
328 }
329
330 =item $file = $fh->get_filename()
331
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.
337
338 =cut
339
340 sub get_filename {
341 my $self = shift;
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};
349 } else {
350 return *$self->{filename} . '.' .
351 compression_get_property($comp, 'file_ext');
352 }
353 } else {
354 return *$self->{filename};
355 }
356 }
357
358 =item $ret = $fh->use_compression()
359
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>
363 method.
364
365 =cut
366
367 sub use_compression {
368 my $self = shift;
369 my $comp = *$self->{compression};
370 if ($comp eq 'none') {
371 return 0;
372 } elsif ($comp eq 'auto') {
373 $comp = compression_guess_from_filename($self->get_filename());
374 *$self->{compressor}->set_compression($comp) if $comp;
375 }
376 return $comp;
377 }
378
379 =item $real_fh = $fh->get_filehandle()
380
381 Returns the real underlying filehandle. Useful if you want to pass it
382 along in a derived object.
383
384 =cut
385
386 sub get_filehandle {
387 my $self = shift;
388 return *$self->{file} if exists *$self->{file};
389 }
390
391 ## INTERNAL METHODS
392
393 sub _open_for_write {
394 my ($self, %opts) = @_;
395 my $filehandle;
396
397 croak 'cannot reopen an already opened compressed file'
398 if exists *$self->{mode};
399
400 if ($self->use_compression()) {
401 *$self->{compressor}->compress(from_pipe => \$filehandle,
402 to_file => $self->get_filename(), %opts);
403 } else {
404 CORE::open($filehandle, '>', $self->get_filename)
405 or syserr(g_('cannot write %s'), $self->get_filename());
406 }
407 *$self->{mode} = 'w';
408 *$self->{file} = $filehandle;
409 }
410
411 sub _open_for_read {
412 my ($self, %opts) = @_;
413 my $filehandle;
414
415 croak 'cannot reopen an already opened compressed file'
416 if exists *$self->{mode};
417
418 if ($self->use_compression()) {
419 *$self->{compressor}->uncompress(to_pipe => \$filehandle,
420 from_file => $self->get_filename(), %opts);
421 *$self->{allow_sigpipe} = 1;
422 } else {
423 CORE::open($filehandle, '<', $self->get_filename)
424 or syserr(g_('cannot read %s'), $self->get_filename());
425 }
426 *$self->{mode} = 'r';
427 *$self->{file} = $filehandle;
428 }
429
430 sub _cleanup {
431 my $self = shift;
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);
437 }
438 *$self->{allow_sigpipe} = 0;
439 }
440 delete *$self->{mode};
441 delete *$self->{file};
442 }
443
444 =back
445
446 =head1 DERIVED OBJECTS
447
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.
452
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:
455
456 sub set_option {
457 my ($self, $value) = @_;
458 *$self->{option} = $value;
459 }
460
461 =head1 CHANGES
462
463 =head2 Version 1.01 (dpkg 1.17.11)
464
465 New argument: $fh->ensure_open() accepts an %opts argument.
466
467 =head2 Version 1.00 (dpkg 1.15.6)
468
469 Mark the module as public.
470
471 =cut
472 1;