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   1115888 use strict;
  11         101  
  11         324  
4 11     11   61 use warnings;
  11         21  
  11         300  
5 11     11   3038 use File::Temp qw(tempdir);
  11         88817  
  11         621  
6 11     11   4262 use Log::Log4perl qw(:easy);
  11         235995  
  11         71  
7 11     11   14048 use File::Spec::Functions;
  11         9462  
  11         820  
8 11     11   83 use File::Spec;
  11         24  
  11         207  
9 11     11   51 use File::Path;
  11         21  
  11         695  
10 11     11   5386 use File::Copy;
  11         26584  
  11         654  
11 11     11   82 use File::Find;
  11         31  
  11         560  
12 11     11   92 use File::Basename;
  11         38  
  11         566  
13 11     11   4653 use File::Which qw(which);
  11         10059  
  11         641  
14 11     11   10290 use IPC::Run qw(run);
  11         359177  
  11         611  
15 11     11   95 use Cwd;
  11         25  
  11         574  
16 11     11   70 use Config;
  11         23  
  11         450  
17 11     11   5282 use IPC::Open3;
  11         30691  
  11         872  
18 11     11   87 use Symbol 'gensym';
  11         27  
  11         462  
19 11     11   68 use Carp;
  11         24  
  11         47751  
20              
21             our $VERSION = '0.36';
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 is an API wrapper around the C 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 in two ways:
75              
76             =over 4
77              
78             =item *
79              
80             B almost doesn't hold anything in memory (see C method),
81             instead using disk as storage.
82              
83             =item *
84              
85             B is 100% compliant with the platform's C
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 wrapper class. Finds the C executable
97             by searching C 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 creates temporary directories to store
103             C 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
109             section for details.
110              
111             Additional options can be passed to the C command by using the
112             C and C parameters. Example:
113              
114             my $arch = Archive::Tar::Wrapper->new(
115             tar_read_options => 'p'
116             );
117              
118             will use C to extract the tarball instead of just
119             C. 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 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 utility internally like
134              
135             tar cf tarfile --exclude=foo ...
136              
137             when the C method gets called.
138              
139             By default, the C functions will return only file entries:
140             directories will be suppressed. To have C 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 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 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:
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 28259 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     1609 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         102 bless $self, $class;
239              
240 20 100       110 unless ( defined $self->{tar} ) {
241              
242 19 0 33     90 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       91 if ( $self->{osname} eq 'MSWin32' ) {
247 0         0 $self->_setup_mswin();
248             }
249             else {
250 19   33     159 $self->{tar} = which('tar') || which('gtar');
251             }
252              
253 19 50       4419 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         129 $self->_acquire_tar_info();
268              
269 20 50       100 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       419 tempdir( $self->{tmpdir} ? ( DIR => $self->{tmpdir} ) : () );
279             }
280              
281 20         15238 $self->{tardir} = File::Spec->catfile( $self->{tmpdir}, 'tar' );
282             mkpath [ $self->{tardir} ], 0, oct(755)
283 20 50       4539 or LOGDIE 'Cannot create the path ' . $self->{tardir} . ": $!";
284             $logger->debug( 'tardir location: ' . $self->{tardir} )
285 20 50       371 if ( $logger->is_debug );
286 20         772 $self->{objdir} = tempdir();
287              
288 20         6395 return $self;
289             }
290              
291             =head2 read
292              
293             $arch->read("archive.tgz");
294              
295             C opens the given tarball, expands it into a temporary directory
296             and returns 1 on success or C on failure.
297             The temporary directory holding the tar data gets cleaned up when C<$arch>
298             goes out of scope.
299              
300             C 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 command, make sure
310             that the file paths match exactly what's in the tarball, otherwise
311             C will fail.
312              
313             =cut
314              
315             sub _is_openbsd {
316 57     57   176 my $self = shift;
317 57         790 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 1057 my ( $self, $tarfile, @files ) = @_;
343              
344 14         180 my $cwd = getcwd();
345              
346 14 100       227 unless ( File::Spec::Functions::file_name_is_absolute($tarfile) ) {
347 10         259 $tarfile = File::Spec::Functions::rel2abs( $tarfile, $cwd );
348             }
349              
350             chdir $self->{tardir}
351 14 50       688 or LOGDIE "Cannot chdir to $self->{tardir}";
352              
353 14         77 my $compr_opt = ''; # sane value
354 14         122 $compr_opt = $self->is_compressed($tarfile);
355              
356 14         43 my @cmd;
357              
358 14 50       51 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         58 @{ $self->{tar_gnu_read_options} },
  14         60  
366             );
367             }
368              
369 14         101 push( @cmd, '-f', $tarfile, @files );
370              
371 14 50       89 $logger->debug("Running @cmd") if ( $logger->is_debug );
372 14         293 my $error_code = run( \@cmd, \my ( $in, $out, $err ) );
373              
374 14 100       164886 unless ($error_code) {
375 1         36 ERROR "@cmd failed: $err";
376 1 50       37 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
377 1         42 return;
378             }
379              
380 13 50 33     271 $logger->warn($err) if ( $logger->is_warn and $err );
381 13 50       660 chdir $cwd or LOGDIE "Cannot chdir to $cwd: $!";
382 13 50       601 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.
390              
391             =cut
392              
393             sub list_reset {
394 7     7 1 1892 my ($self) = @_;
395              
396             #TODO: keep the file list as a fixed attribute of the instance
397 7         202 my $list_file = File::Spec->catfile( $self->{objdir}, 'list' );
398 7         107 my $cwd = getcwd();
399 7 50       106 chdir $self->{tardir} or LOGDIE "Can't chdir to $self->{tardir}: $!";
400 7 50       676 open( my $fh, '>', $list_file ) or LOGDIE "Can't open $list_file: $!";
401              
402 7 50       71 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   2059 my $entry = $File::Find::name;
409 35         151 $entry =~ s#^\./##o;
410 35 50       590 my $type = (
    100          
411             -d $_ ? 'd'
412             : -l $_ ? 'l'
413             : 'f'
414             );
415 35         229 print $fh "$type $entry\n";
416 35 50       143 $logger->debug("$type $entry") if ( $logger->is_debug );
417             },
418 7         1145 '.'
419             );
420              
421 7 50       283 $logger->debug('All entries listed') if ( $logger->is_debug );
422 7         330 close($fh);
423 7 50       109 chdir $cwd or LOGDIE "Can't chdir to $cwd: $!";
424 7         57 $self->_offset(0);
425 7         24 return 1;
426             }
427              
428             sub _read_from_tar {
429 21     21   47 my $self = shift;
430 21         194 my ( $wtr, $rdr, $err ) = ( gensym, gensym, gensym );
431 21         1066 my $pid = open3( $wtr, $rdr, $err, "$self->{tar} --version" );
432 21         80332 my ( $output, $error );
433              
434             {
435 21         147 local $/ = undef;
  21         403  
436 21         27454 $output = <$rdr>;
437 21         836 $error = <$err>;
438             }
439              
440 21         375 close($rdr);
441 21         272 close($err);
442 21         279 close($wtr);
443 21         474 waitpid( $pid, 0 );
444 21         123 chomp $error;
445 21         147 chomp $output;
446 21         120 $self->{tar_error_msg} = $error;
447 21         126 $self->{version_info} = $output;
448 21         259 $self->{tar_exit_code} = $? >> 8;
449 21         413 return 1;
450             }
451              
452             sub _acquire_tar_info {
453 24     24   102 my ( $self, $skip ) = @_;
454 24 100       142 $self->_read_from_tar() unless ($skip);
455 24         513 my $bsd_regex = qr/bsd/i;
456 24         101 $self->{is_gnu} = 0;
457 24         122 $self->{is_bsd} = 0;
458              
459 24 50 66     272 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         2 $self->{is_bsd} = 1;
471             }
472              
473             $self->{version_info} = 'Information not available. Search for errors'
474 24 100       185 unless ( $self->{tar_exit_code} == 0 );
475 24 100       328 $self->{is_gnu} = 1 if ( $self->{version_info} =~ /GNU/ );
476 24         127 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 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 926 my ( $self, $tarfile ) = @_;
537              
538 19 100       290 return 'z' if $tarfile =~ $self->{gzip_regex};
539 14 100       198 return 'j' if $tarfile =~ $self->{bzip2_regex};
540              
541             # Sloppy check for gzip files
542 12 50       716 open( my $fh, '<', $tarfile ) or croak("Cannot open $tarfile: $!");
543 12         82 binmode($fh);
544 12 50       188 my $read = sysread( $fh, my $two, 2, 0 )
545             or croak("Cannot sysread $tarfile: $!");
546 12         131 close($fh);
547              
548 12 100 66     150 return 'z'
549             if ( ( ( ord( substr( $two, 0, 1 ) ) ) == 0x1F )
550             and ( ( ord( substr( $two, 1, 1 ) ) ) == 0x8B ) );
551              
552 11         166 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 created to manipulate the
562             tarball on disk.
563              
564             =cut
565              
566             sub locate {
567 17     17 1 3932 my ( $self, $rel_path ) = @_;
568              
569 17         300 my $real_path = File::Spec->catfile( $self->{tardir}, $rel_path );
570              
571 17 100       860 if ( -e $real_path ) {
572 14 50       96 $logger->debug("$real_path exists") if ( $logger->is_debug );
573 14         253 return $real_path;
574             }
575             else {
576 3 50       26 $logger->warn("$rel_path not found in tarball") if ( $logger->is_warn );
577 3         46 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
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 1085 my ( $self, $rel_path, $path_or_stringref, $opts ) = @_;
609              
610 16 100       77 if ($opts) {
611 2 50 33     32 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         41 my ( $perm, $uid, $gid, $binmode );
617 16 100       49 $perm = $opts->{perm} if defined $opts->{perm};
618 16 50       37 $uid = $opts->{uid} if defined $opts->{uid};
619 16 50       45 $gid = $opts->{gid} if defined $opts->{gid};
620 16 100       44 $binmode = $opts->{binmode} if defined $opts->{binmode};
621              
622 16         185 my $target = File::Spec->catfile( $self->{tardir}, $rel_path );
623 16         582 my $target_dir = dirname($target);
624              
625 16 100       286 unless ( -d $target_dir ) {
626 9 100       49 if ( ref($path_or_stringref) ) {
627 1         69 $self->add( dirname($rel_path), dirname($target_dir) );
628             }
629             else {
630 8         331 $self->add( dirname($rel_path), dirname($path_or_stringref) );
631             }
632             }
633              
634 16 100       214 if ( ref($path_or_stringref) ) {
    100          
635 2 50       150 open my $fh, '>', $target or LOGDIE "Can't open $target: $!";
636 2 100       11 if ( defined $binmode ) {
637 1         7 binmode $fh, $binmode;
638             }
639 2         38 print $fh $$path_or_stringref;
640 2         94 close $fh;
641             }
642             elsif ( -d $path_or_stringref ) {
643              
644             # perms will be fixed further down
645 9 50       1162 mkpath( $target, 0, oct(755) ) unless -d $target;
646             }
647             else {
648 5 50       88 copy $path_or_stringref, $target
649             or LOGDIE "Can't copy $path_or_stringref to $target ($!)";
650             }
651              
652 16 50       1859 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       44 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       37 if ( defined $perm ) {
663 1 50       23 chmod $perm, $target
664             or LOGDIE "Can't chmod $target to $perm ($!)";
665             }
666              
667 16 100 33     204 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       58 perm_cp( $path_or_stringref, $target )
673             or LOGDIE "Can't perm_cp $path_or_stringref to $target ($!)";
674             }
675              
676 16         80 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 43 my ( $source, $target ) = @_;
703 13         48 perm_set( $target, perm_get($source) );
704 13         47 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.
714              
715             =cut
716              
717             sub perm_get {
718 13     13 1 35 my ($filename) = @_;
719 13 50       219 my @stats = ( stat $filename )[ 2, 4, 5 ]
720             or LOGDIE "Cannot stat $filename ($!)";
721 13         87 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)
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 32 my ( $filename, $perms ) = @_;
750 13         237 chown( $perms->[1], $perms->[2], $filename );
751 13 50       199 chmod( $perms->[0] & oct(777), $filename )
752             or LOGDIE "Cannot chmod $filename ($!)";
753 13         38 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 and
790             C instead of C.
791              
792             =cut
793              
794             sub list_all {
795 5     5 1 85 my ($self) = @_;
796 5         28 my @entries = ();
797 5         60 $self->list_reset();
798              
799 5         24 while ( my $entry = $self->list_next() ) {
800 13         57 push @entries, $entry;
801             }
802              
803 5         57 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 won't display directories, unless the C
815             parameter is set when running the constructor.
816              
817             =cut
818              
819             sub list_next {
820 27     27 1 135 my ($self) = @_;
821 27         204 my $offset = $self->_offset();
822 27         399 my $list_file = File::Spec->catfile( $self->{objdir}, 'list' );
823 27 50       855 open my $fh, '<', $list_file or LOGDIE "Can't open $list_file: $!";
824 27         251 seek $fh, $offset, 0;
825 27         85 my $data;
826              
827             REDO: {
828 27         75 my $line = <$fh>;
  42         287  
829              
830 42 100       122 unless ( defined($line) ) {
831 7         58 close($fh);
832             }
833             else {
834 35         62 chomp $line;
835 35         125 my ( $type, $entry ) = split / /, $line, 2;
836 35 100 100     192 redo if ( ( $type eq 'd' ) and ( not $self->{dirs} ) );
837 20         84 $self->_offset( tell $fh );
838 20         191 close($fh);
839             $data =
840 20         403 [ $entry, File::Spec->catfile( $self->{tardir}, $entry ), $type ];
841             }
842             }
843              
844 27         163 return $data;
845             }
846              
847             sub _offset {
848 54     54   128 my ( $self, $new_offset ) = @_;
849 54         1583 my $offset_file = File::Spec->catfile( $self->{objdir}, "offset" );
850              
851 54 100       184 if ( defined $new_offset ) {
852 27 50       10712 open my $fh, '>', $offset_file or LOGDIE "Can't open $offset_file: $!";
853 27         323 print $fh "$new_offset\n";
854 27         1659 close $fh;
855             }
856              
857 54 50       2463 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         644 my $offset = <$fh>;
861 54         153 chomp $offset;
862 54         397 close $fh;
863 54         326 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 2489 my ( $self, $tarfile, $compress ) = @_;
878              
879 5         62 my $cwd = getcwd();
880 5 50       102 chdir $self->{tardir} or LOGDIE "Can't chdir to $self->{tardir}: $!";
881              
882 5 50       70 unless ( File::Spec::Functions::file_name_is_absolute($tarfile) ) {
883 0         0 $tarfile = File::Spec::Functions::rel2abs( $tarfile, $cwd );
884             }
885              
886 5         160 my $compr_opt = '';
887 5 100       30 $compr_opt = 'z' if $compress;
888              
889 5 50       256 opendir( my $dir, '.' ) or LOGDIE "Cannot open $self->{tardir}: $!";
890 5         146 my @top_entries = readdir($dir);
891 5         88 closedir($dir);
892              
893 5         53 $self->_rem_dots( \@top_entries );
894              
895             my $cmd = [
896             $self->{tar}, "${compr_opt}cf$self->{tar_write_options}",
897 5         21 $tarfile, @{ $self->{tar_gnu_write_options} }
  5         30  
898             ];
899              
900 5 50       38 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         22 push @$cmd, @top_entries;
914             }
915              
916 5 50       48 $logger->debug("Running @$cmd") if ( $logger->is_debug );
917 5         123 my $rc = run( $cmd, \my ( $in, $out, $err ) );
918              
919 5 100       53352 unless ($rc) {
920 1         38 ERROR "@$cmd failed: $err";
921 1 50       44 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
922 1         36 return;
923             }
924              
925 4 50       44 WARN $err if $err;
926 4 50       121 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
927 4         278 return 1;
928             }
929              
930             sub _rem_dots {
931 10     10   4120 my ( $self, $entries_ref ) = @_;
932 10         35 my ( $first, $second );
933 10         25 my $index = 0;
934 10         20 my $found = 0;
935              
936 10         21 for ( @{$entries_ref} ) {
  10         38  
937              
938 33 100 66     217 if ( ( length($_) <= 2 )
      66        
939             and ( ( $_ eq '.' ) or ( $_ eq '..' ) ) )
940             {
941 20 100       59 if ( $found < 1 ) {
942 10         23 $first = $index;
943 10         26 $found++;
944 10         20 $index++;
945 10         36 next;
946             }
947             else {
948 10         19 $second = $index;
949 10         30 last;
950             }
951              
952             }
953             else {
954 13         29 $index++;
955             }
956             }
957              
958 10         24 splice( @{$entries_ref}, $first, 1 );
  10         40  
959              
960             # array length is now shortened by one
961 10         30 splice( @{$entries_ref}, ( $second - 1 ), 1 );
  10         35  
962 10         29 return 1;
963              
964             }
965              
966             sub DESTROY {
967 20     20   9439 my ($self) = @_;
968 20 50       142 $self->ramdisk_unmount() if defined $self->{ramdisk};
969 20 50       7491 rmtree( $self->{objdir} ) if defined $self->{objdir};
970 20 50       12426 rmtree( $self->{tmpdir} ) if defined $self->{tmpdir};
971 20         2358 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 3145 return shift->{is_gnu};
987             }
988              
989             =head2 is_bsd
990              
991             $arch->is_bsd();
992              
993             Same as C, but for BSD.
994              
995             =cut
996              
997             sub is_bsd {
998 6     6 1 1727 return shift->{is_bsd};
999             }
1000              
1001             =head2 ramdisk_mount
1002              
1003             Mounts a RAM disk.
1004              
1005             It executes the C 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)
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 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.
1079              
1080             Don't expect parameters and returns 1 if everything goes fine.
1081              
1082             Be sure to check the L 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__