File Coverage

blib/lib/Archive/Tar/Wrapper.pm
Criterion Covered Total %
statement 275 356 77.2
branch 110 204 53.9
condition 25 41 60.9
subroutine 38 44 86.3
pod 18 18 100.0
total 466 663 70.2


line stmt bran cond sub pod time code
1             package Archive::Tar::Wrapper;
2              
3 11     11   1518418 use strict;
  11         123  
  11         339  
4 11     11   60 use warnings;
  11         26  
  11         326  
5 11     11   3223 use File::Temp qw(tempdir);
  11         91628  
  11         614  
6 11     11   4254 use Log::Log4perl qw(:easy);
  11         240817  
  11         86  
7 11     11   15358 use File::Spec::Functions;
  11         9841  
  11         826  
8 11     11   91 use File::Spec;
  11         26  
  11         217  
9 11     11   51 use File::Path;
  11         25  
  11         775  
10 11     11   5809 use File::Copy;
  11         27330  
  11         729  
11 11     11   86 use File::Find;
  11         27  
  11         639  
12 11     11   92 use File::Basename;
  11         26  
  11         644  
13 11     11   5008 use File::Which qw(which);
  11         10466  
  11         680  
14 11     11   11518 use IPC::Run qw(run);
  11         306048  
  11         610  
15 11     11   114 use Cwd;
  11         29  
  11         601  
16 11     11   68 use Config;
  11         25  
  11         404  
17 11     11   5525 use IPC::Open3;
  11         31699  
  11         647  
18 11     11   91 use Symbol 'gensym';
  11         28  
  11         420  
19 11     11   67 use Carp;
  11         24  
  11         49982  
20              
21             our $VERSION = '0.38';
22             my $logger = get_logger();
23              
24             =pod
25              
26             =head1 NAME
27              
28             Archive::Tar::Wrapper - API wrapper around the 'tar' utility
29              
30             =head1 SYNOPSIS
31              
32             use Archive::Tar::Wrapper;
33              
34             my $arch = Archive::Tar::Wrapper->new();
35              
36             # Open a tarball, expand it into a temporary directory
37             $arch->read("archive.tgz");
38              
39             # Iterate over all entries in the archive
40             $arch->list_reset(); # Reset Iterator
41              
42             # Iterate through archive
43             while(my $entry = $arch->list_next()) {
44             my($tar_path, $phys_path) = @$entry;
45             print "$tar_path\n";
46             }
47              
48             # Get a huge list with all entries
49             for my $entry (@{$arch->list_all()}) {
50             my($tar_path, $real_path) = @$entry;
51             print "Tarpath: $tar_path Tempfile: $real_path\n";
52             }
53              
54             # Add a new entry
55             $arch->add($logic_path, $file_or_stringref);
56              
57             # Remove an entry
58             $arch->remove($logic_path);
59              
60             # Find the physical location of a temporary file
61             my($tmp_path) = $arch->locate($tar_path);
62              
63             # Create a tarball
64             $arch->write($tarfile, $compress);
65              
66             =head1 DESCRIPTION
67              
68             B<Archive::Tar::Wrapper> is an API wrapper around the C<tar> command line
69             program. It never stores anything in memory, but works on temporary
70             directory structures on disk instead. It provides a mapping between
71             the logical paths in the tarball and the 'real' files in the temporary
72             directory on disk.
73              
74             It differs from L<Archive::Tar> in two ways:
75              
76             =over 4
77              
78             =item *
79              
80             B<Archive::Tar::Wrapper> almost doesn't hold anything in memory (see C<write> method),
81             instead using disk as storage.
82              
83             =item *
84              
85             B<Archive::Tar::Wrapper> is 100% compliant with the platform's C<tar>
86             utility because it uses it internally.
87              
88             =back
89              
90             =head1 METHODS
91              
92             =head2 new
93              
94             my $arch = Archive::Tar::Wrapper->new();
95              
96             Constructor for the C<tar> wrapper class. Finds the C<tar> executable
97             by searching C<PATH> and returning the first hit. In case you want
98             to use a different tar executable, you can specify it as a parameter:
99              
100             my $arch = Archive::Tar::Wrapper->new(tar => '/path/to/tar');
101              
102             Since B<Archive::Tar::Wrapper> creates temporary directories to store
103             C<tar> data, the location of the temporary directory can be specified:
104              
105             my $arch = Archive::Tar::Wrapper->new(tmpdir => '/path/to/tmpdir');
106              
107             Tremendous performance increases can be achieved if the temporary
108             directory is located on a RAM disk. Check the L<Archive::Tar::Wrapper/Using RAM Disks>
109             section for details.
110              
111             Additional options can be passed to the C<tar> command by using the
112             C<tar_read_options> and C<tar_write_options> parameters. Example:
113              
114             my $arch = Archive::Tar::Wrapper->new(
115             tar_read_options => 'p'
116             );
117              
118             will use C<tar xfp archive.tgz> to extract the tarball instead of just
119             C<tar xf archive.tgz>. GNU tar supports even more options, these can
120             be passed in via
121              
122             my $arch = Archive::Tar::Wrapper->new(
123             tar_gnu_read_options => ["--numeric-owner"],
124             );
125              
126             Similarly, C<tar_gnu_write_options> can be used to provide additional
127             options for GNU tar implementations. For example, the tar object
128              
129             my $tar = Archive::Tar::Wrapper->new(
130             tar_gnu_write_options => ["--exclude=foo"],
131             );
132              
133             will call the C<tar> utility internally like
134              
135             tar cf tarfile --exclude=foo ...
136              
137             when the C<write> method gets called.
138              
139             By default, the C<list_*()> functions will return only file entries:
140             directories will be suppressed. To have C<list_*()> return directories
141             as well, use
142              
143             my $arch = Archive::Tar::Wrapper->new(
144             dirs => 1
145             );
146              
147             If more files are added to a tarball than the command line can handle,
148             B<Archive::Tar::Wrapper> will switch from using the command
149              
150             tar cfv tarfile file1 file2 file3 ...
151              
152             to
153              
154             tar cfv tarfile -T filelist
155              
156             where C<filelist> is a file containing all file to be added. The default
157             for this switch is 512, but it can be changed by setting the parameter
158             C<max_cmd_line_args>:
159              
160             my $arch = Archive::Tar::Wrapper->new(
161             max_cmd_line_args => 1024
162             );
163              
164             The expectable parameters are:
165              
166             =over
167              
168             =item *
169              
170             tar
171              
172             =item *
173              
174             tmpdir
175              
176             =item *
177              
178             tar_read_options
179              
180             =item *
181              
182             tar_write_options
183              
184             =item *
185              
186             tar_gnu_read_options
187              
188             =item *
189              
190             tar_gnu_write_options
191              
192             =item *
193              
194             max_cmd_line_args: defaults to 512
195              
196             =item *
197              
198             ramdisk
199              
200             =back
201              
202             Returns a new instance of the class.
203              
204             =cut
205              
206             sub new {
207 20     20 1 33933 my ( $class, %options ) = @_;
208              
209             my $self = {
210             tar => delete $options{tar} || undef,
211             tmpdir => undef,
212             tar_read_options => '',
213             tar_write_options => '',
214             tar_error_msg => undef,
215             tar_gnu_read_options => [],
216             tar_gnu_write_options => [],
217             dirs => 0,
218             max_cmd_line_args => 512,
219             ramdisk => undef,
220             _os_names => { openbsd => 'openbsd', mswin => 'MSWin32' },
221              
222             # hack used to enable unit testing
223             osname => delete $options{osname} || $Config{osname},
224 20   100     1684 bzip2_regex => qr/\.bz2$/ix,
      66        
225             gzip_regex => qr/\.t? # an optional t for matching tgz
226             gz$ # ending with gz, which means compressed by gzip
227             /ix,
228             tar_error_msg => undef,
229             version_info => undef,
230             tar_exit_code => undef,
231             is_gnu => undef,
232             is_bsd => undef,
233             version_info => undef,
234             tar_exit_code => undef,
235             %options,
236             };
237              
238 20         151 bless $self, $class;
239              
240 20 100       115 unless ( defined $self->{tar} ) {
241              
242 19 0 33     73 if ( ( $self->_is_openbsd ) and ( $self->{tar_read_options} ) ) {
243 0         0 $self->{tar_read_options} = '-' . $self->{tar_read_options};
244             }
245              
246 19 50       93 if ( $self->{osname} eq 'MSWin32' ) {
247 0         0 $self->_setup_mswin();
248             }
249             else {
250 19   33     149 $self->{tar} = which('tar') || which('gtar');
251             }
252              
253 19 50       4773 unless ( defined $self->{tar} ) {
254              
255             # this is specific for testing under MS Windows smokers without tar installed
256             # "OS unsupported" will mark the testing as NA instead of failure as convention.
257 0 0       0 if ( $self->{osname} eq 'MSWin32' ) {
258 0         0 LOGDIE 'tar not found in PATH, OS unsupported';
259             }
260             else {
261 0         0 LOGDIE 'tar not found in PATH, please specify location';
262             }
263             }
264              
265             }
266              
267 20         178 $self->_acquire_tar_info();
268              
269 20 50       149 if ( defined $self->{ramdisk} ) {
270 0         0 my $rc = $self->ramdisk_mount( %{ $self->{ramdisk} } );
  0         0  
271 0 0       0 unless ($rc) {
272 0         0 LOGDIE "Mounting ramdisk failed";
273             }
274 0         0 $self->{tmpdir} = $self->{ramdisk}->{tmpdir};
275             }
276             else {
277             $self->{tmpdir} =
278 20 100       811 tempdir( $self->{tmpdir} ? ( DIR => $self->{tmpdir} ) : () );
279             }
280              
281 20         15716 $self->{tardir} = File::Spec->catfile( $self->{tmpdir}, 'tar' );
282             mkpath [ $self->{tardir} ], 0, oct(755)
283 20 50       6827 or LOGDIE 'Cannot create the path ' . $self->{tardir} . ": $!";
284             $logger->debug( 'tardir location: ' . $self->{tardir} )
285 20 50       383 if ( $logger->is_debug );
286 20         742 $self->{objdir} = tempdir();
287              
288 20         6648 return $self;
289             }
290              
291             =head2 read
292              
293             $arch->read("archive.tgz");
294              
295             C<read()> opens the given tarball, expands it into a temporary directory
296             and returns 1 on success or C<undef> on failure.
297             The temporary directory holding the tar data gets cleaned up when C<$arch>
298             goes out of scope.
299              
300             C<read> handles both compressed and uncompressed files. To find out if
301             a file is compressed or uncompressed, it tries to guess by extension,
302             then by checking the first couple of bytes in the tarfile.
303              
304             If only a limited number of files is needed from a tarball, they
305             can be specified after the tarball name:
306              
307             $arch->read("archive.tgz", "path/file.dat", "path/sub/another.txt");
308              
309             The file names are passed unmodified to the C<tar> command, make sure
310             that the file paths match exactly what's in the tarball, otherwise
311             C<read()> will fail.
312              
313             =cut
314              
315             sub _is_openbsd {
316 57     57   236 my $self = shift;
317 57         917 return ( $self->{osname} eq $self->{_os_names}->{openbsd} );
318             }
319              
320             sub _read_openbsd_opts {
321 0     0   0 my ( $self, $compress_opt ) = @_;
322 0         0 my @cmd;
323 0         0 push( @cmd, $self->{tar} );
324              
325 0 0       0 if ($compress_opt) {
326              
327             # actually, prepending '-' would work with any modern GNU tar
328 0         0 $compress_opt = '-' . $compress_opt;
329 0         0 push( @cmd, $compress_opt );
330             }
331              
332 0         0 push( @cmd, '-x' );
333             push( @cmd, $self->{tar_read_options} )
334 0 0       0 if ( $self->{tar_read_options} ne '' );
335 0         0 push( @cmd, @{ $self->{tar_gnu_read_options} } )
336 0 0       0 if ( scalar( @{ $self->{tar_gnu_read_options} } ) > 0 );
  0         0  
337 0         0 return \@cmd;
338              
339             }
340              
341             sub read { ## no critic (ProhibitBuiltinHomonyms)
342 14     14 1 1706 my ( $self, $tarfile, @files ) = @_;
343              
344 14         200 my $cwd = getcwd();
345              
346 14 100       221 unless ( File::Spec::Functions::file_name_is_absolute($tarfile) ) {
347 10         318 $tarfile = File::Spec::Functions::rel2abs( $tarfile, $cwd );
348             }
349              
350             chdir $self->{tardir}
351 14 50       796 or LOGDIE "Cannot chdir to $self->{tardir}";
352              
353 14         112 my $compr_opt = ''; # sane value
354 14         123 $compr_opt = $self->is_compressed($tarfile);
355              
356 14         59 my @cmd;
357              
358 14 50       66 if ( $self->_is_openbsd ) {
359 0         0 @cmd = @{ $self->_read_openbsd_opts($compr_opt) };
  0         0  
360             }
361             else {
362             @cmd = (
363             $self->{tar},
364             "${compr_opt}x$self->{tar_read_options}",
365 14         71 @{ $self->{tar_gnu_read_options} },
  14         79  
366             );
367             }
368              
369 14         130 push( @cmd, '-f', $tarfile, @files );
370              
371 14 50       113 $logger->debug("Running @cmd") if ( $logger->is_debug );
372 14         320 my $error_code = run( \@cmd, \my ( $in, $out, $err ) );
373              
374 14 100       164907 unless ($error_code) {
375 1         46 ERROR "@cmd failed: $err";
376 1 50       46 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
377 1         57 return;
378             }
379              
380 13 50 33     262 $logger->warn($err) if ( $logger->is_warn and $err );
381 13 50       700 chdir $cwd or LOGDIE "Cannot chdir to $cwd: $!";
382 13 50       649 return ( $error_code == 0 ) ? undef : $error_code;
383             }
384              
385             =head2 list_reset
386              
387             $arch->list_reset()
388              
389             Resets the list iterator. To be used before the first call to C<list_next()>.
390              
391             =cut
392              
393             sub list_reset {
394 7     7 1 2667 my ($self) = @_;
395              
396             #TODO: keep the file list as a fixed attribute of the instance
397 7         178 my $list_file = File::Spec->catfile( $self->{objdir}, 'list' );
398 7         102 my $cwd = getcwd();
399 7 50       124 chdir $self->{tardir} or LOGDIE "Can't chdir to $self->{tardir}: $!";
400 7 50       786 open( my $fh, '>', $list_file ) or LOGDIE "Can't open $list_file: $!";
401              
402 7 50       89 if ( $logger->is_debug ) {
403 0         0 $logger->debug('List of all files identified inside the tar file');
404             }
405              
406             find(
407             sub {
408 35     35   2073 my $entry = $File::Find::name;
409 35         187 $entry =~ s#^\./##o;
410 35 50       642 my $type = (
    100          
411             -d $_ ? 'd'
412             : -l $_ ? 'l'
413             : 'f'
414             );
415 35         224 print $fh "$type $entry\n";
416 35 50       134 $logger->debug("$type $entry") if ( $logger->is_debug );
417             },
418 7         1308 '.'
419             );
420              
421 7 50       280 $logger->debug('All entries listed') if ( $logger->is_debug );
422 7         357 close($fh);
423 7 50       111 chdir $cwd or LOGDIE "Can't chdir to $cwd: $!";
424 7         90 $self->_offset(0);
425 7         21 return 1;
426             }
427              
428             sub _read_from_tar {
429 21     21   52 my $self = shift;
430 21         212 my ( $wtr, $rdr, $err ) = ( gensym, gensym, gensym );
431 21         1249 my $pid = open3( $wtr, $rdr, $err, "$self->{tar} --version" );
432 21         82083 my ( $output, $error );
433              
434             {
435 21         172 local $/ = undef;
  21         472  
436 21         30018 $output = <$rdr>;
437 21         910 $error = <$err>;
438             }
439              
440 21         400 close($rdr);
441 21         301 close($err);
442 21         313 close($wtr);
443 21         552 waitpid( $pid, 0 );
444 21         137 chomp $error;
445 21         119 chomp $output;
446 21         134 $self->{tar_error_msg} = $error;
447 21         94 $self->{version_info} = $output;
448 21         274 $self->{tar_exit_code} = $? >> 8;
449 21         396 return 1;
450             }
451              
452             sub _acquire_tar_info {
453 24     24   104 my ( $self, $skip ) = @_;
454 24 100       160 $self->_read_from_tar() unless ($skip);
455 24         509 my $bsd_regex = qr/bsd/i;
456 24         116 $self->{is_gnu} = 0;
457 24         106 $self->{is_bsd} = 0;
458              
459 24 50 66     306 if ( $self->_is_openbsd() ) {
    100          
460              
461             # there is no way to acquire version information from default tar program on OpenBSD
462 0         0 $self->{version_info} = "Information not available on $Config{osname}";
463 0         0 $self->{tar_exit_code} = 0;
464 0         0 $self->{is_bsd} = 1;
465             }
466             elsif ( ( $self->{tar} =~ $bsd_regex ) and ( $self->{tar_exit_code} == 1 ) )
467             {
468             # bsdtar exit code is 1 when asking for version, forcing to zero since is not an error
469 1         4 $self->{tar_exit_code} = 0;
470 1         3 $self->{is_bsd} = 1;
471             }
472              
473             $self->{version_info} = 'Information not available. Search for errors'
474 24 100       192 unless ( $self->{tar_exit_code} == 0 );
475 24 100       379 $self->{is_gnu} = 1 if ( $self->{version_info} =~ /GNU/ );
476 24         143 return 1;
477             }
478              
479             sub _setup_mswin {
480 0     0   0 my $self = shift;
481              
482             # bsdtar is always preferred on Windows
483 0         0 my $tar_path = which('bsdtar');
484 0 0       0 $tar_path = which('tar') unless ( defined($tar_path) );
485              
486 0 0       0 if ( $tar_path =~ /\s/ ) {
487              
488             # double quoting will be required is there is a space
489 0         0 $tar_path = qq($tar_path);
490             }
491 0         0 $self->{tar} = $tar_path;
492             }
493              
494             =head2 tardir
495              
496             $arch->tardir();
497              
498             Return the directory the tarball was unpacked in. This is sometimes useful
499             to play dirty tricks on B<Archive::Tar::Wrapper> by mass-manipulating
500             unpacked files before wrapping them back up into the tarball.
501              
502             =cut
503              
504             sub tardir {
505 0     0 1 0 my ($self) = @_;
506 0         0 return $self->{tardir};
507             }
508              
509             =head2 is_compressed
510              
511             Returns a string to identify if the tarball is compressed or not.
512              
513             Expect as parameter a string with the path to the tarball.
514              
515             Returns:
516              
517             =over
518              
519             =item *
520              
521             a "z" character if the file is compressed with gzip.
522              
523             =item *
524              
525             a "j" character if the file is compressed with bzip2.
526              
527             =item *
528              
529             a "" character if the file is not compressed at all.
530              
531             =back
532              
533             =cut
534              
535             sub is_compressed {
536 19     19 1 1032 my ( $self, $tarfile ) = @_;
537              
538 19 100       316 return 'z' if $tarfile =~ $self->{gzip_regex};
539 14 100       193 return 'j' if $tarfile =~ $self->{bzip2_regex};
540              
541             # Sloppy check for gzip files
542 12 50       680 open( my $fh, '<', $tarfile ) or croak("Cannot open $tarfile: $!");
543 12         72 binmode($fh);
544 12 50       905 my $read = sysread( $fh, my $two, 2, 0 )
545             or croak("Cannot sysread $tarfile: $!");
546 12         137 close($fh);
547              
548 12 100 66     139 return 'z'
549             if ( ( ( ord( substr( $two, 0, 1 ) ) ) == 0x1F )
550             and ( ( ord( substr( $two, 1, 1 ) ) ) == 0x8B ) );
551              
552 11         152 return q{};
553             }
554              
555             =head2 locate
556              
557             $arch->locate($logic_path);
558              
559             Finds the physical location of a file, specified by C<$logic_path>, which
560             is the virtual path of the file within the tarball. Returns a path to
561             the temporary file B<Archive::Tar::Wrapper> created to manipulate the
562             tarball on disk.
563              
564             =cut
565              
566             sub locate {
567 17     17 1 4475 my ( $self, $rel_path ) = @_;
568              
569 17         286 my $real_path = File::Spec->catfile( $self->{tardir}, $rel_path );
570              
571 17 100       397 if ( -e $real_path ) {
572 14 50       143 $logger->debug("$real_path exists") if ( $logger->is_debug );
573 14         241 return $real_path;
574             }
575             else {
576 3 50       32 $logger->warn("$rel_path not found in tarball") if ( $logger->is_warn );
577 3         53 return;
578             }
579             }
580              
581             =head2 add
582              
583             $arch->add($logic_path, $file_or_stringref, [$options]);
584              
585             Add a new file to the tarball. C<$logic_path> is the virtual path
586             of the file within the tarball. C<$file_or_stringref> is either
587             a scalar, in which case it holds the physical path of a file
588             on disk to be transferred (i.e. copied) to the tarball, or it is
589             a reference to a scalar, in which case its content is interpreted
590             to be the data of the file.
591              
592             If no additional parameters are given, permissions and user/group
593             id settings of a file to be added are copied. If you want different
594             settings, specify them in the options hash:
595              
596             $arch->add($logic_path, $stringref,
597             { perm => 0755, uid => 123, gid => 10 });
598              
599             If $file_or_stringref is a reference to a Unicode string, the C<binmode>
600             option has to be set to make sure the string gets written as proper UTF-8
601             into the tarfile:
602              
603             $arch->add($logic_path, $stringref, { binmode => ":utf8" });
604              
605             =cut
606              
607             sub add {
608 16     16 1 1176 my ( $self, $rel_path, $path_or_stringref, $opts ) = @_;
609              
610 16 100       86 if ($opts) {
611 2 50 33     59 unless ( ( ref($opts) ) and ( ref($opts) eq 'HASH' ) ) {
612 0         0 LOGDIE "Option parameter given to add() not a hashref.";
613             }
614             }
615              
616 16         34 my ( $perm, $uid, $gid, $binmode );
617 16 100       52 $perm = $opts->{perm} if defined $opts->{perm};
618 16 50       56 $uid = $opts->{uid} if defined $opts->{uid};
619 16 50       48 $gid = $opts->{gid} if defined $opts->{gid};
620 16 100       49 $binmode = $opts->{binmode} if defined $opts->{binmode};
621              
622 16         181 my $target = File::Spec->catfile( $self->{tardir}, $rel_path );
623 16         553 my $target_dir = dirname($target);
624              
625 16 100       319 unless ( -d $target_dir ) {
626 9 100       61 if ( ref($path_or_stringref) ) {
627 1         62 $self->add( dirname($rel_path), dirname($target_dir) );
628             }
629             else {
630 8         363 $self->add( dirname($rel_path), dirname($path_or_stringref) );
631             }
632             }
633              
634 16 100       203 if ( ref($path_or_stringref) ) {
    100          
635 2 50       181 open my $fh, '>', $target or LOGDIE "Can't open $target: $!";
636 2 100       17 if ( defined $binmode ) {
637 1         15 binmode $fh, $binmode;
638             }
639 2         35 print $fh $$path_or_stringref;
640 2         105 close $fh;
641             }
642             elsif ( -d $path_or_stringref ) {
643              
644             # perms will be fixed further down
645 9 50       1172 mkpath( $target, 0, oct(755) ) unless -d $target;
646             }
647             else {
648 5 50       82 copy $path_or_stringref, $target
649             or LOGDIE "Can't copy $path_or_stringref to $target ($!)";
650             }
651              
652 16 50       1944 if ( defined $uid ) {
653 0 0       0 chown $uid, -1, $target
654             or LOGDIE "Can't chown $target uid to $uid ($!)";
655             }
656              
657 16 50       52 if ( defined $gid ) {
658 0 0       0 chown -1, $gid, $target
659             or LOGDIE "Can't chown $target gid to $gid ($!)";
660             }
661              
662 16 100       56 if ( defined $perm ) {
663 1 50       21 chmod $perm, $target
664             or LOGDIE "Can't chmod $target to $perm ($!)";
665             }
666              
667 16 100 33     158 if ( not defined $uid
      66        
      100        
668             and not defined $gid
669             and not defined $perm
670             and not ref($path_or_stringref) )
671             {
672 13 50       59 perm_cp( $path_or_stringref, $target )
673             or LOGDIE "Can't perm_cp $path_or_stringref to $target ($!)";
674             }
675              
676 16         84 return 1;
677             }
678              
679             =head2 perm_cp
680              
681             Copies the permissions from a file to another.
682              
683             Expects as parameters:
684              
685             =over
686              
687             =item 1.
688              
689             string of the path to the file which permissions will be copied from.
690              
691             =item 2.
692              
693             string of the path to the file which permissions will be copied to.
694              
695             =back
696              
697             Returns 1 if everything works as expected.
698              
699             =cut
700              
701             sub perm_cp {
702 13     13 1 46 my ( $source, $target ) = @_;
703 13         61 perm_set( $target, perm_get($source) );
704 13         45 return 1;
705             }
706              
707             =head2 perm_get
708              
709             Gets the permissions from a file.
710              
711             Expects as parameter the path to the source file.
712              
713             Returns an array reference with only the permissions values, as returned by C<stat>.
714              
715             =cut
716              
717             sub perm_get {
718 13     13 1 37 my ($filename) = @_;
719 13 50       237 my @stats = ( stat $filename )[ 2, 4, 5 ]
720             or LOGDIE "Cannot stat $filename ($!)";
721 13         99 return \@stats;
722             }
723              
724             =head2 perm_set
725              
726             Sets the permission on a file.
727              
728             Expects as parameters:
729              
730             =over
731              
732             =item 1.
733              
734             The path to the file where the permissions should be applied to.
735              
736             =item 2.
737              
738             An array reference with the permissions (see C<perm_set>)
739              
740             =back
741              
742             Returns 1 if everything goes fine.
743              
744             Ignore errors here, as we can't change uid/gid unless we're the superuser (see LIMITATIONS section).
745              
746             =cut
747              
748             sub perm_set {
749 13     13 1 30 my ( $filename, $perms ) = @_;
750 13         238 chown( $perms->[1], $perms->[2], $filename );
751 13 50       213 chmod( $perms->[0] & oct(777), $filename )
752             or LOGDIE "Cannot chmod $filename ($!)";
753 13         47 return 1;
754             }
755              
756             =head2 remove
757              
758             $arch->remove($logic_path);
759              
760             Removes a file from the tarball. C<$logic_path> is the virtual path
761             of the file within the tarball.
762              
763             =cut
764              
765             sub remove {
766 0     0 1 0 my ( $self, $rel_path ) = @_;
767 0         0 my $target = File::Spec->catfile( $self->{tardir}, $rel_path );
768 0 0       0 rmtree($target) or LOGDIE "Can't rmtree $target: $!";
769 0         0 return 1;
770             }
771              
772             =head2 list_all
773              
774             my $items = $arch->list_all();
775              
776             Returns a reference to a (possibly huge) array of items in the
777             tarfile. Each item is a reference to an array, containing two
778             elements: the relative path of the item in the tarfile and the
779             physical path to the unpacked file or directory on disk.
780              
781             To iterate over the list, the following construct can be used:
782              
783             # Get a huge list with all entries
784             for my $entry (@{$arch->list_all()}) {
785             my($tar_path, $real_path) = @$entry;
786             print "Tarpath: $tar_path Tempfile: $real_path\n";
787             }
788              
789             If the list of items in the tarfile is big, use C<list_reset()> and
790             C<list_next()> instead of C<list_all>.
791              
792             =cut
793              
794             sub list_all {
795 5     5 1 73 my ($self) = @_;
796 5         28 my @entries = ();
797 5         55 $self->list_reset();
798              
799 5         36 while ( my $entry = $self->list_next() ) {
800 13         50 push @entries, $entry;
801             }
802              
803 5         61 return \@entries;
804             }
805              
806             =head2 list_next
807              
808             my ($tar_path, $phys_path, $type) = $arch->list_next();
809              
810             Returns the next item in the tarfile. It returns a list of three scalars:
811             the relative path of the item in the tarfile, the physical path
812             to the unpacked file or directory on disk, and the type of the entry
813             (f=file, d=directory, l=symlink). Note that by default,
814             B<Archive::Tar::Wrapper> won't display directories, unless the C<dirs>
815             parameter is set when running the constructor.
816              
817             =cut
818              
819             sub list_next {
820 27     27 1 129 my ($self) = @_;
821 27         123 my $offset = $self->_offset();
822 27         317 my $list_file = File::Spec->catfile( $self->{objdir}, 'list' );
823 27 50       766 open my $fh, '<', $list_file or LOGDIE "Can't open $list_file: $!";
824 27         240 seek $fh, $offset, 0;
825 27         89 my $data;
826              
827             REDO: {
828 27         58 my $line = <$fh>;
  42         308  
829              
830 42 100       137 unless ( defined($line) ) {
831 7         65 close($fh);
832             }
833             else {
834 35         61 chomp $line;
835 35         131 my ( $type, $entry ) = split / /, $line, 2;
836 35 100 100     219 redo if ( ( $type eq 'd' ) and ( not $self->{dirs} ) );
837 20         106 $self->_offset( tell $fh );
838 20         196 close($fh);
839             $data =
840 20         394 [ $entry, File::Spec->catfile( $self->{tardir}, $entry ), $type ];
841             }
842             }
843              
844 27         160 return $data;
845             }
846              
847             sub _offset {
848 54     54   144 my ( $self, $new_offset ) = @_;
849 54         523 my $offset_file = File::Spec->catfile( $self->{objdir}, "offset" );
850              
851 54 100       185 if ( defined $new_offset ) {
852 27 50       11584 open my $fh, '>', $offset_file or LOGDIE "Can't open $offset_file: $!";
853 27         353 print $fh "$new_offset\n";
854 27         1720 close $fh;
855             }
856              
857 54 50       1861 open my $fh, '<', $offset_file
858             or LOGDIE
859             "Can't open $offset_file: $! (Did you call list_next() without a previous list_reset()?)";
860 54         642 my $offset = <$fh>;
861 54         157 chomp $offset;
862 54         455 close $fh;
863 54         279 return $offset;
864             }
865              
866             =head2 write
867              
868             $arch->write($tarfile, $compress);
869              
870             Write out the tarball by tarring up all temporary files and directories
871             and store it in C<$tarfile> on disk. If C<$compress> holds a true value,
872             compression is used.
873              
874             =cut
875              
876             sub write { ## no critic (ProhibitBuiltinHomonyms)
877 5     5 1 2569 my ( $self, $tarfile, $compress ) = @_;
878              
879 5         57 my $cwd = getcwd();
880 5 50       122 chdir $self->{tardir} or LOGDIE "Can't chdir to $self->{tardir}: $!";
881              
882 5 50       64 unless ( File::Spec::Functions::file_name_is_absolute($tarfile) ) {
883 0         0 $tarfile = File::Spec::Functions::rel2abs( $tarfile, $cwd );
884             }
885              
886 5         176 my $compr_opt = '';
887 5 100       36 $compr_opt = 'z' if $compress;
888              
889 5 50       265 opendir( my $dir, '.' ) or LOGDIE "Cannot open $self->{tardir}: $!";
890 5         154 my @top_entries = readdir($dir);
891 5         91 closedir($dir);
892              
893 5         69 $self->_rem_dots( \@top_entries );
894              
895             my $cmd = [
896             $self->{tar}, "${compr_opt}cf$self->{tar_write_options}",
897 5         24 $tarfile, @{ $self->{tar_gnu_write_options} }
  5         29  
898             ];
899              
900 5 50       36 if ( @top_entries > $self->{max_cmd_line_args} ) {
901 0         0 my $filelist_file = $self->{tmpdir} . "/file-list";
902 0 0       0 open( my $fh, '>', $filelist_file )
903             or LOGDIE "Cannot write to $filelist_file: $!";
904              
905 0         0 for my $entry (@top_entries) {
906 0         0 print $fh "$entry\n";
907             }
908              
909 0         0 close($fh);
910 0         0 push @$cmd, "-T", $filelist_file;
911             }
912             else {
913 5         20 push @$cmd, @top_entries;
914             }
915              
916 5 50       79 $logger->debug("Running @$cmd") if ( $logger->is_debug );
917 5         136 my $rc = run( $cmd, \my ( $in, $out, $err ) );
918              
919 5 100       55424 unless ($rc) {
920 1         30 ERROR "@$cmd failed: $err";
921 1 50       53 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
922 1         51 return;
923             }
924              
925 4 50       45 WARN $err if $err;
926 4 50       156 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
927 4         291 return 1;
928             }
929              
930             sub _rem_dots {
931 10     10   3564 my ( $self, $entries_ref ) = @_;
932 10         20 my ( $first, $second );
933 10         33 my $index = 0;
934 10         20 my $found = 0;
935              
936 10         17 for ( @{$entries_ref} ) {
  10         34  
937              
938 33 100 66     198 if ( ( length($_) <= 2 )
      66        
939             and ( ( $_ eq '.' ) or ( $_ eq '..' ) ) )
940             {
941 20 100       62 if ( $found < 1 ) {
942 10         21 $first = $index;
943 10         15 $found++;
944 10         18 $index++;
945 10         23 next;
946             }
947             else {
948 10         22 $second = $index;
949 10         23 last;
950             }
951              
952             }
953             else {
954 13         23 $index++;
955             }
956             }
957              
958 10         30 splice( @{$entries_ref}, $first, 1 );
  10         31  
959              
960             # array length is now shortened by one
961 10         22 splice( @{$entries_ref}, ( $second - 1 ), 1 );
  10         30  
962 10         26 return 1;
963              
964             }
965              
966             sub DESTROY {
967 20     20   9641 my ($self) = @_;
968 20 50       146 $self->ramdisk_unmount() if defined $self->{ramdisk};
969 20 50       8466 rmtree( $self->{objdir} ) if defined $self->{objdir};
970 20 50       13317 rmtree( $self->{tmpdir} ) if defined $self->{tmpdir};
971 20         2402 return 1;
972             }
973              
974             =head2 is_gnu
975              
976             $arch->is_gnu();
977              
978             Checks if the tar executable is a GNU tar by running 'tar --version'
979             and parsing the output for "GNU".
980              
981             Returns true or false (in Perl terms).
982              
983             =cut
984              
985             sub is_gnu {
986 7     7 1 3943 return shift->{is_gnu};
987             }
988              
989             =head2 is_bsd
990              
991             $arch->is_bsd();
992              
993             Same as C<is_gnu()>, but for BSD.
994              
995             =cut
996              
997             sub is_bsd {
998 6     6 1 1767 return shift->{is_bsd};
999             }
1000              
1001             =head2 ramdisk_mount
1002              
1003             Mounts a RAM disk.
1004              
1005             It executes the C<mount> program under the hood to mount a RAM disk.
1006              
1007             Expects as parameter a hash with options to mount the RAM disk, like:
1008              
1009             =over
1010              
1011             =item *
1012              
1013             size
1014              
1015             =item *
1016              
1017             type (most probably C<tmpfs>)
1018              
1019             =item *
1020              
1021             tmpdir
1022              
1023             =back
1024              
1025             Returns 1 if everything goes fine.
1026              
1027             Be sure to check the L<Archive::Tar::Wrapper/Using RAM Disks> for full details on using RAM disks.
1028              
1029             =cut
1030              
1031             sub ramdisk_mount {
1032 0     0 1   my ( $self, %options ) = @_;
1033              
1034             # mkdir -p /mnt/myramdisk
1035             # mount -t tmpfs -o size=20m tmpfs /mnt/myramdisk
1036              
1037 0 0         $self->{mount} = which("mount") unless $self->{mount};
1038 0 0         $self->{umount} = which("umount") unless $self->{umount};
1039              
1040 0           for (qw(mount umount)) {
1041 0 0         unless ( defined $self->{$_} ) {
1042 0           LOGWARN "No $_ command found in PATH";
1043 0           return;
1044             }
1045             }
1046              
1047 0           $self->{ramdisk} = {%options};
1048             $self->{ramdisk}->{size} = "100m"
1049 0 0         unless defined $self->{ramdisk}->{size};
1050              
1051 0 0         if ( !defined $self->{ramdisk}->{tmpdir} ) {
1052 0           $self->{ramdisk}->{tmpdir} = tempdir( CLEANUP => 1 );
1053             }
1054              
1055             my @cmd = (
1056             $self->{mount}, "-t", "tmpfs", "-o", "size=$self->{ramdisk}->{size}",
1057             "tmpfs", $self->{ramdisk}->{tmpdir}
1058 0           );
1059              
1060 0           INFO "Mounting ramdisk: @cmd";
1061 0           my $rc = system(@cmd);
1062              
1063 0 0         if ($rc) {
1064              
1065 0 0         if ( $logger->is_info ) {
1066 0           $logger->info("Mount command '@cmd' failed: $?");
1067 0           $logger->info('Note that this only works on Linux and as root');
1068             }
1069 0           return;
1070             }
1071              
1072 0           $self->{ramdisk}->{mounted} = 1;
1073 0           return 1;
1074             }
1075              
1076             =head2 ramdisk_unmount
1077              
1078             Unmounts the RAM disk already mounted with C<ramdisk_mount>.
1079              
1080             Don't expect parameters and returns 1 if everything goes fine.
1081              
1082             Be sure to check the L<Archive::Tar::Wrapper/Using RAM Disks> for full details on using RAM disks.
1083              
1084             =cut
1085              
1086             sub ramdisk_unmount {
1087 0     0 1   my ($self) = @_;
1088              
1089 0 0         return unless ( exists $self->{ramdisk}->{mounted} );
1090 0           my @cmd = ( $self->{umount}, $self->{ramdisk}->{tmpdir} );
1091 0 0         $logger->info("Unmounting ramdisk: @cmd") if ( $logger->is_info );
1092 0           my $rc = system(@cmd);
1093              
1094 0 0         if ($rc) {
1095 0           LOGWARN "Unmount command '@cmd' failed: $?";
1096 0           return;
1097             }
1098              
1099 0           delete $self->{ramdisk};
1100 0           return 1;
1101             }
1102              
1103             1;
1104              
1105             __END__
1106              
1107              
1108             =head1 Using RAM Disks
1109              
1110             On Linux, it's quite easy to create a RAM disk and achieve tremendous
1111             speedups while untarring or modifying a tarball. You can either
1112             create the RAM disk by hand by running
1113              
1114             # mkdir -p /mnt/myramdisk
1115             # mount -t tmpfs -o size=20m tmpfs /mnt/myramdisk
1116              
1117             and then feeding the ramdisk as a temporary directory to
1118             B<Archive::Tar::Wrapper>, like
1119              
1120             my $tar = Archive::Tar::Wrapper->new( tmpdir => '/mnt/myramdisk' );
1121              
1122             or using B<Archive::Tar::Wrapper>'s built-in option C<ramdisk>:
1123              
1124             my $tar = Archive::Tar::Wrapper->new(
1125             ramdisk => {
1126             type => 'tmpfs',
1127             size => '20m', # 20 MB
1128             },
1129             );
1130              
1131             Only drawback with the latter option is that creating the RAM disk needs
1132             to be performed as root, which often isn't desirable for security reasons.
1133             For this reason, B<Archive::Tar::Wrapper> offers a utility functions that
1134             mounts the RAM disk and returns the temporary directory it's located in:
1135              
1136             # Create new ramdisk (as root):
1137             my $tmpdir = Archive::Tar::Wrapper->ramdisk_mount(
1138             type => 'tmpfs',
1139             size => '20m', # 20 MB
1140             );
1141              
1142             # Delete a ramdisk (as root):
1143             Archive::Tar::Wrapper->ramdisk_unmount();
1144              
1145             Optionally, the C<ramdisk_mount()> command accepts a C<tmpdir> parameter
1146             pointing to a temporary directory for the RAM disk if you wish to set it
1147             yourself instead of letting B<Archive::Tar::Wrapper> create it automatically.
1148              
1149             =head1 KNOWN LIMITATIONS
1150              
1151             =over
1152              
1153             =item *
1154              
1155             Currently, only C<tar> programs supporting the C<z> option (for
1156             compressing/decompressing) are supported. Future version will use
1157             C<gzip> alternatively.
1158              
1159             =item *
1160              
1161             Currently, you can't add empty directories to a tarball directly.
1162             You could add a temporary file within a directory, and then
1163             C<remove()> the file.
1164              
1165             =item *
1166              
1167             If you delete a file, the empty directories it was located in
1168             stay in the tarball. You could try to C<locate()> them and delete
1169             them. This will be fixed, though.
1170              
1171             =item *
1172              
1173             Filenames containing newlines are causing problems with the list
1174             iterators. To be fixed.
1175              
1176             =item *
1177              
1178             If you ask B<Archive::Tar::Wrapper> to add a file to a tarball, it copies it
1179             into a temporary directory and then calls the system tar to wrap up that
1180             directory into a tarball.
1181              
1182             This approach has limitations when it comes to file permissions: If the file to
1183             be added belongs to a different user/group, B<Archive::Tar::Wrapper> will adjust
1184             the uid/gid/permissions of the target file in the temporary directory to
1185             reflect the original file's settings, to make sure the system tar will add it
1186             like that to the tarball, just like a regular tar run on the original file
1187             would. But this will fail of course if the original file's uid is different
1188             from the current user's, unless the script is running with superuser rights.
1189             The tar program by itself (without B<Archive::Tar::Wrapper>) works differently:
1190             It'll just make a note of a file's uid/gid/permissions in the tarball (which it
1191             can do without superuser rights) and upon extraction, it'll adjust the
1192             permissions of newly generated files if the -p option is given (default for
1193             superuser).
1194              
1195             =back
1196              
1197             =head1 BUGS
1198              
1199             B<Archive::Tar::Wrapper> doesn't currently handle filenames with embedded
1200             newlines.
1201              
1202             =head2 Microsoft Windows support
1203              
1204             Support on Microsoft Windows is limited.
1205              
1206             Versions below Windows 10 will not be supported for desktops, and for servers
1207             only Windows 2012 and above.
1208              
1209             The GNU C<tar.exe> program doesn't work properly with the current interface of
1210             B<Archive::Tar::Wrapper>.
1211              
1212             You must use the C<bsdtar.exe> and make sure it appears first in the C<PATH>
1213             environment variable than the GNU tar (if it is installed). See
1214             L<http://libarchive.org/> for details about how to download and
1215             install C<bsdtar.exe>, or go to L<http://gnuwin32.sourceforge.net/packages.html>
1216             for a direct download. Be sure to look for the C<bzip2> program package to
1217             install it as well.
1218              
1219             Windows 10 might come already with C<bsdtar> program already installed. Please
1220             search for that on the appropriate page (Microsoft keeps changing the link to
1221             keep track of it here).
1222              
1223             Having spaces in the path string to the tar program might be an issue too.
1224             Although there is some effort in terms of workaround it, you best might avoid it
1225             completely by installing in a different path than C<C:\Program Files>.
1226             Installing both C<bsdtar> and C<bzip2> in C<C:\GnuWin32> will probably be enough
1227             when running the installers.
1228              
1229             =head1 LEGALESE
1230              
1231             This software is copyright (c) 2005 of Mike Schilli.
1232              
1233             This program is free software: you can redistribute it and/or modify it under
1234             the terms of the GNU General Public License as published by the Free Software
1235             Foundation, either version 3 of the License, or (at your option) any later
1236             version.
1237              
1238             This program is distributed in the hope that it will be useful, but WITHOUT ANY
1239             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
1240             PARTICULAR PURPOSE. See the GNU General Public License for more details.
1241              
1242             You should have received a copy of the GNU General Public License along with
1243             Archive-Tar-Wrapper. If not, see L<http://www.gnu.org/licenses/>.
1244              
1245             =head1 SEE ALSO
1246              
1247             =over
1248              
1249             =item *
1250              
1251             Linux Gazette article from Ben Okopnik, L<issue 87|https://linuxgazette.net/87/okopnik.html>.
1252              
1253             =back
1254              
1255             =head1 AUTHOR
1256              
1257             2005, Mike Schilli <cpan@perlmeister.com>
1258              
1259             =head1 MAINTAINER
1260              
1261             2018, Alceu Rodrigues de Freitas Junior <arfreitas@cpan.org>
1262              
1263             =cut