File Coverage

blib/lib/Filesys/POSIX/Userland/Tar.pm
Criterion Covered Total %
statement 80 87 91.9
branch 24 32 75.0
condition 5 6 83.3
subroutine 13 13 100.0
pod 1 1 100.0
total 123 139 88.4


line stmt bran cond sub pod time code
1             # Copyright (c) 2014, cPanel, Inc.
2             # All rights reserved.
3             # http://cpanel.net/
4             #
5             # This is free software; you can redistribute it and/or modify it under the same
6             # terms as Perl itself. See the LICENSE file for further details.
7              
8             package Filesys::POSIX::Userland::Tar;
9              
10 7     7   2583 use strict;
  7         12  
  7         193  
11 7     7   22 use warnings;
  7         8  
  7         123  
12              
13 7     7   23 use Filesys::POSIX::Bits;
  7         11  
  7         1650  
14 7     7   34 use Filesys::POSIX::Module ();
  7         32  
  7         114  
15              
16 7     7   24 use Filesys::POSIX::Path ();
  7         8  
  7         68  
17 7     7   2435 use Filesys::POSIX::Userland::Find ();
  7         13  
  7         106  
18 7     7   2628 use Filesys::POSIX::Userland::Tar::Header ();
  7         10  
  7         123  
19              
20 7     7   35 use Errno;
  7         7  
  7         198  
21 7     7   26 use Carp ();
  7         8  
  7         3863  
22              
23             my @METHODS = qw(tar);
24              
25             Filesys::POSIX::Module->export_methods( __PACKAGE__, @METHODS );
26              
27             =head1 NAME
28              
29             Filesys::POSIX::Userland::Tar - Generate ustar archives from L
30              
31             =head1 SYNOPSIS
32              
33             use Filesys::POSIX;
34             use Filesys::POSIX::Mem;
35             use Filesys::POSIX::IO::Handle;
36             use Filesys::POSIX::Userland::Tar;
37              
38             my $fs = Filesys::POSIX->new(Filesys::POSIX::Mem->new,
39             'noatime' => 1
40             );
41              
42             $fs->mkdir('foo');
43             $fs->touch('foo/bar');
44              
45             $fs->tar(Filesys::POSIX::IO::Handle->new(\*STDOUT), '.');
46              
47             =head1 DESCRIPTION
48              
49             This module provides an implementation of the ustar standard on top of the
50             virtual filesystem layer, a mechanism intended to take advantage of the many
51             possible mapping and manipulation capabilities inherent in this mechanism.
52             Internally, it uses the L module to perform
53             depth- last recursion to locate inodes for packaging.
54              
55             As mentioned, archives are written in the ustar format, with pathnames of the
56             extended maximum length of 256 characters, supporting file sizes up to 4GB.
57             Currently, only user and group IDs are stored; names are not resolved and
58             stored as of the time of this writing. All inode types are supported for
59             archival.
60              
61             =head1 USAGE
62              
63             =over
64              
65             =cut
66              
67             our $BLOCK_SIZE = 512;
68             our $BUF_MAX = 20 * $BLOCK_SIZE;
69              
70             #
71             # NOTE: I'm only using $inode->open() calls to avoid having to call stat().
72             # This is not necessarily something that should be done by end user software.
73             #
74             sub _write_file {
75 11     11   17 my ( $fh, $inode, $handle, $size ) = @_;
76              
77 11         16 my $total_written = 0;
78 11         8 my $remaining = $size;
79 11         30 my $modulo = $size % $BLOCK_SIZE;
80 11 100       30 my $padding = $modulo ? $BLOCK_SIZE - $modulo : 0;
81              
82 11         23 while ($remaining) {
83 4894         3319 my $readlen;
84             my $writelen;
85 0         0 my $buf;
86              
87 4894 100       6185 my $wanted = ( $remaining >= $BUF_MAX ) ? $BUF_MAX : $remaining;
88              
89 4894 100       9923 if ( ( $readlen = $fh->read( $buf, $wanted ) ) <= 0 ) {
90 1         9 last;
91             }
92              
93 4893 50       9723 if ( ( $writelen = $handle->write( $buf, $readlen ) ) < $readlen ) {
94 0         0 last;
95             }
96              
97 4893         5255 $total_written += $writelen;
98 4893         7646 $remaining -= $writelen;
99             }
100              
101 11 100       208 if ($remaining) {
    100          
102 1         25 warn sprintf(
103             "Only streamed %d of %d bytes of file",
104             $total_written, $size
105             );
106              
107 1         10 $padding += $remaining;
108             }
109             elsif ( !$fh->eof ) {
110 1         20 warn "File grew as we read it; truncated";
111             }
112              
113 3         26 $handle->write( "\x00" x $padding, $padding );
114              
115 3         15 $fh->close;
116              
117 3         5 $total_written += $padding;
118              
119 3         23 return $total_written;
120             }
121              
122             sub _archive {
123 78     78   104 my ( $inode, $handle, $path, $opts ) = @_;
124              
125 78         74 my $written = 0;
126              
127 78         458 my $header = Filesys::POSIX::Userland::Tar::Header->from_inode( $inode, $path );
128 78         129 my $blocks = '';
129              
130 78 50       185 if ( $header->{'truncated'} ) {
131              
132 0 0       0 if ( $opts->{'gnu_extensions'} ) {
    0          
133 0         0 $blocks .= $header->encode_longlink;
134             }
135             elsif ( $opts->{'posix_extensions'} ) {
136 0         0 $blocks .= $header->encode_posix;
137             }
138             else {
139 0         0 die('Filename too long');
140             }
141             }
142              
143 78         227 $blocks .= $header->encode;
144              
145 78         96 local $@;
146              
147 78         96 eval {
148             # Acquire the file handle before writing the header so we don't corrupt
149             # the tarball if the file is missing.
150 78         74 my $header_len = length $blocks;
151              
152 78 50       259 unless ( $handle->write( $blocks, $header_len ) == $header_len ) {
153 0         0 Carp::confess('Short write while dumping tar header to file handle');
154             }
155              
156 78 100 100     246 if ( $inode->file && $header->{'size'} > 0 ) {
157 13         114 my $fh = $inode->open( $O_RDONLY | $O_NONBLOCK ); # Case 82969: No block on pipes
158              
159 11         56 $written += _write_file( $fh, $inode, $handle, $header->{'size'} );
160             }
161              
162 68         113 $written += $header_len;
163             };
164              
165 78 100       2197 if ($!) {
166 2 100 66     13 if ( !$opts->{'ignore_missing'} || $! != &Errno::ENOENT ) {
167 1         10 die $@;
168             }
169 1 50       5 $opts->{'ignore_missing'}->($path)
170             if ref $opts->{'ignore_missing'} eq 'CODE';
171             }
172              
173 77         395 return $written;
174             }
175              
176             =item C<$fs-Etar($handle, @items)>
177              
178             =item C<$fs-Etar($handle, $opts, @items)>
179              
180             Locate files and directories in each path specified in the C<@items> array,
181             writing results to the I/O handle wrapper specified by C<$handle>, an instance
182             of L. When an anonymous HASH argument, C<$opts>, is
183             specified, the data is passed unmodified to L.
184             In this way, for instance, the behavior of following symlinks can be specified.
185              
186             In addition to options supported by L, the
187             following options are recognized uniquely by C<$fs-Etar()>:
188              
189             =over
190              
191             =item C
192              
193             When set, certain GNU extensions to the tar output format are enabled, namely
194             support for arbitrarily long filenames.
195              
196             =back
197              
198             =item C
199              
200             When set, ignore if a file is missing when writing it to the tarball. This can
201             happen if a file is removed between the time the find functionality finds it and
202             the time it is actually written to the output. If the value is a coderef, calls
203             that function with the name of the missing file.
204              
205             =back
206              
207             =cut
208              
209             sub tar {
210 12     12 1 369 my $self = shift;
211 12         68 my $handle = shift;
212 12 100       128 my $opts = ref $_[0] eq 'HASH' ? shift : {};
213 12         71 my @items = @_;
214 12         37 my $unpadded = 0;
215              
216             $self->find(
217             sub {
218 79     79   84 my ( $path, $inode ) = @_;
219              
220 79 100       307 return if $inode->sock;
221              
222 78         171 $unpadded += _archive( $inode, $handle, $path->full, $opts );
223 77         185 $unpadded %= $BUF_MAX;
224             },
225 12         395 $opts,
226             @items
227             );
228              
229 11         78 my $padlen = $BUF_MAX - ( $unpadded % $BUF_MAX );
230 11         119 $handle->write( "\x00" x $padlen, $padlen );
231              
232 11         80 return;
233             }
234              
235             =back
236              
237             =cut
238              
239             1;
240              
241             __END__