File Coverage

blib/lib/Filesys/POSIX/Userland/Tar/Header.pm
Criterion Covered Total %
statement 106 156 67.9
branch 32 40 80.0
condition 5 9 55.5
subroutine 14 27 51.8
pod 0 21 0.0
total 157 253 62.0


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::Header;
9              
10 7     7   28 use strict;
  7         7  
  7         197  
11 7     7   22 use warnings;
  7         7  
  7         125  
12              
13 7     7   21 use Filesys::POSIX::Bits;
  7         8  
  7         1814  
14 7     7   31 use Filesys::POSIX::Path ();
  7         7  
  7         70  
15              
16 7     7   21 use Carp ();
  7         7  
  7         10692  
17              
18             our $BLOCK_SIZE = 512;
19              
20             my %TYPES = (
21             0 => $S_IFREG,
22             2 => $S_IFLNK,
23             3 => $S_IFCHR,
24             4 => $S_IFBLK,
25             5 => $S_IFDIR,
26             6 => $S_IFIFO
27             );
28              
29             sub inode_linktype {
30 84     84 0 102 my ($inode) = @_;
31              
32 84         330 foreach ( keys %TYPES ) {
33 385 100       882 return $_ if ( $inode->{'mode'} & $S_IFMT ) == $TYPES{$_};
34             }
35              
36 0         0 return 0;
37             }
38              
39             sub from_inode {
40 84     84 0 131 my ( $class, $inode, $path ) = @_;
41              
42 84         239 my $parts = Filesys::POSIX::Path->new($path);
43 84         170 my $cleanpath = $parts->full;
44 84 100       188 $cleanpath .= '/' if $inode->dir;
45              
46 84         242 my $path_components = split_path_components( $parts, $inode );
47 84 100       262 my $size = $inode->file ? $inode->{'size'} : 0;
48              
49 84         87 my $major = 0;
50 84         79 my $minor = 0;
51              
52 84 50 33     185 if ( $inode->char || $inode->block ) {
53 0         0 $major = $inode->major;
54 0         0 $minor = $inode->minor;
55             }
56              
57 84 100       263 return bless {
58             'path' => $cleanpath,
59             'prefix' => $path_components->{'prefix'},
60             'suffix' => $path_components->{'suffix'},
61             'truncated' => $path_components->{'truncated'},
62             'mode' => $inode->{'mode'},
63             'uid' => $inode->{'uid'},
64             'gid' => $inode->{'gid'},
65             'size' => $size,
66             'mtime' => $inode->{'mtime'},
67             'linktype' => inode_linktype($inode),
68             'linkdest' => $inode->link ? $inode->readlink : '',
69             'user' => '',
70             'group' => '',
71             'major' => $major,
72             'minor' => $minor
73             }, $class;
74             }
75              
76             sub decode {
77 0     0 0 0 my ( $class, $block ) = @_;
78              
79 0         0 my $suffix = read_str( $block, 0, 100 );
80 0         0 my $prefix = read_str( $block, 345, 155 );
81 0         0 my $checksum = read_oct( $block, 148, 8 );
82              
83 0         0 validate_block( $block, $checksum );
84              
85 0         0 return bless {
86             'suffix' => $suffix,
87             'mode' => read_oct( $block, 100, 8 ),
88             'uid' => read_oct( $block, 108, 8 ),
89             'gid' => read_oct( $block, 116, 8 ),
90             'size' => read_oct( $block, 124, 12 ),
91             'mtime' => read_oct( $block, 136, 12 ),
92             'linktype' => read_oct( $block, 156, 1 ),
93             'linkdest' => read_str( $block, 157, 100 ),
94             'user' => read_str( $block, 265, 32 ),
95             'group' => read_str( $block, 297, 32 ),
96             'major' => read_oct( $block, 329, 8 ),
97             'minor' => read_oct( $block, 337, 8 ),
98             'prefix' => $prefix
99             }, $class;
100             }
101              
102             sub encode_longlink {
103 2     2 0 485 my ($self) = @_;
104              
105 2         5 my $pathlen = length $self->{'path'};
106              
107 2         21 my $longlink_header = bless {
108             'prefix' => '',
109             'suffix' => '././@LongLink',
110             'mode' => 0,
111             'uid' => 0,
112             'gid' => 0,
113             'size' => $pathlen,
114             'mtime' => 0,
115             'linktype' => 'L',
116             'linkdest' => '',
117             'user' => '',
118             'group' => '',
119             'major' => 0,
120             'minor' => 0
121             },
122             ref $self;
123              
124 2         13 my $path_blocks = "\x00" x ( $pathlen + $BLOCK_SIZE - ( $pathlen % $BLOCK_SIZE ) );
125 2         6 substr( $path_blocks, 0, $pathlen ) = $self->{'path'};
126              
127 2         26 return $longlink_header->encode . $path_blocks;
128             }
129              
130             sub _compute_posix_header {
131 6     6   14009 my ( $self, $key, $value ) = @_;
132 6         18 my $header = " $key=$value\n";
133 6         10 my $len = length $header;
134 6         7 my $hdrlen = length($len) + $len;
135 6         5 my $curlen = length($hdrlen);
136              
137             # The length field includes everything up to and including the newline and
138             # the length field itself. Compute the proper value if adding the length
139             # would push us to a larger number of digits.
140 6 100       16 $hdrlen = $curlen + $len if $curlen > length($len);
141              
142 6         14 return "$hdrlen$header";
143             }
144              
145             sub encode_posix {
146 0     0 0 0 my ($self) = @_;
147              
148 0         0 my $linklen = length $self->{'linkdest'};
149 0         0 my $encoded = $self->_compute_posix_header( 'path', $self->{'path'} );
150 0 0       0 $encoded .= $self->_compute_posix_header( 'linkpath', $self->{'linkdest'} ) if $linklen;
151              
152 0         0 my $encodedlen = length $encoded;
153              
154 0         0 my $posix_header = bless {
155             'prefix' => "./PaxHeaders.$$",
156             'suffix' => substr( $self->{'path'}, 0, 100 ),
157             'mode' => 0,
158             'uid' => 0,
159             'gid' => 0,
160             'size' => $encodedlen,
161             'mtime' => 0,
162             'linktype' => 'x',
163             'linkdest' => '',
164             'user' => '',
165             'group' => '',
166             'major' => 0,
167             'minor' => 0
168             },
169             ref $self;
170              
171 0         0 my $path_blocks = "\x00" x ( $encodedlen + $BLOCK_SIZE - ( $encodedlen % $BLOCK_SIZE ) );
172 0         0 substr( $path_blocks, 0, $encodedlen ) = $encoded;
173              
174 0         0 return $posix_header->encode . $path_blocks;
175             }
176              
177             sub encode {
178 83     83 0 93 my ($self) = @_;
179 83         178 my $block = "\x00" x $BLOCK_SIZE;
180              
181 83         179 write_str( $block, 0, 100, $self->{'suffix'} );
182 83         190 write_oct( $block, 100, 8, $self->{'mode'} & $S_IPERM, 7 );
183 83         130 write_oct( $block, 108, 8, $self->{'uid'}, 7 );
184 83         131 write_oct( $block, 116, 8, $self->{'gid'}, 7 );
185 83         125 write_oct( $block, 124, 12, $self->{'size'}, 11 );
186 83         119 write_oct( $block, 136, 12, $self->{'mtime'}, 11 );
187 83         116 write_str( $block, 148, 8, ' ' );
188              
189 83 100       366 if ( $self->{'linktype'} =~ /^[0-9]$/ ) {
190 81         137 write_oct( $block, 156, 1, $self->{'linktype'}, 1 );
191             }
192             else {
193 2         6 write_str( $block, 156, 1, $self->{'linktype'} );
194             }
195              
196 83         135 write_str( $block, 157, 100, $self->{'linkdest'} );
197 83         104 write_str( $block, 257, 6, 'ustar' );
198 83         107 write_str( $block, 263, 2, '00' );
199 83         126 write_str( $block, 265, 32, $self->{'user'} );
200 83         114 write_str( $block, 297, 32, $self->{'group'} );
201              
202 83 50 33     371 if ( $self->{'major'} || $self->{'minor'} ) {
203 0         0 write_oct( $block, 329, 8, $self->{'major'}, 7 );
204 0         0 write_oct( $block, 337, 8, $self->{'minor'}, 7 );
205             }
206              
207 83         133 write_str( $block, 345, 155, $self->{'prefix'} );
208              
209 83         128 my $checksum = checksum($block);
210              
211 83         142 write_oct( $block, 148, 8, $checksum, 7 );
212              
213 83         253 return $block;
214             }
215              
216             sub split_path_components {
217 91     91 0 108 my ( $parts, $inode ) = @_;
218              
219 91         72 my $truncated = 0;
220              
221 91 100       178 $parts->[-1] .= '/' if $inode->dir;
222              
223 91         93 my $got = 0;
224 91         91 my ( @prefix_items, @suffix_items );
225              
226 91         81 while ( @{$parts} ) {
  499         684  
227 408         278 my $item = pop @{$parts};
  408         353  
228 408         353 my $len = length $item;
229              
230             #
231             # If the first item found is greater than 100 characters in length,
232             # truncate it so that it may fit in the standard tar path header field.
233             #
234 408 100 100     833 if ( $got == 0 && $len > 100 ) {
235 3 100       12 my $truncated_len = $inode->dir ? 99 : 100;
236              
237 3         8 $item = substr( $item, 0, $truncated_len );
238 3 100       184 $item .= '/' if $inode->dir;
239              
240 3         3 $len = 100;
241 3         6 $truncated = 1;
242             }
243              
244 408 100       535 $got++ if $got;
245 408         294 $got += $len;
246              
247 408 100       517 if ( $got <= 100 ) {
    50          
248 302         486 push @suffix_items, $item;
249             }
250             elsif ( $got > 100 ) {
251 106         98 push @prefix_items, $item;
252             }
253             }
254              
255 91         139 my $prefix = join( '/', reverse @prefix_items );
256 91         163 my $suffix = join( '/', reverse @suffix_items );
257              
258 91 50       145 if ( length($prefix) > 155 ) {
259 0         0 $prefix = substr( $prefix, 0, 155 );
260 0         0 $truncated = 1;
261             }
262              
263             return {
264 91         404 'prefix' => $prefix,
265             'suffix' => $suffix,
266             'truncated' => $truncated
267             };
268             }
269              
270             sub read_str {
271 0     0 0 0 my ( $block, $offset, $len ) = @_;
272 0         0 my $template = "Z$len";
273              
274 0         0 return unpack( $template, substr( $block, $offset, $len ) );
275             }
276              
277             sub write_str {
278 666     666 0 686 my ( $block, $offset, $len, $string ) = @_;
279              
280 666 100       726 if ( length($string) == $len ) {
281 169         154 substr( $_[0], $offset, $len ) = $string;
282             }
283             else {
284 497         837 substr( $_[0], $offset, $len ) = pack( "Z$len", $string );
285             }
286              
287 666         515 return;
288             }
289              
290             sub read_oct {
291 0     0 0 0 my ( $block, $offset, $len ) = @_;
292 0         0 my $template = "Z$len";
293              
294 0         0 return oct( unpack( $template, substr( $block, $offset, $len ) ) );
295             }
296              
297             sub write_oct {
298 579     579 0 594 my ( $block, $offset, $len, $value, $digits ) = @_;
299 579         1000 my $string = sprintf( "%.${digits}o", $value );
300 579         521 my $sub_offset = length($string) - $digits;
301 579         513 my $substring = substr( $string, $sub_offset, $digits );
302              
303 579 100       660 if ( $len == $digits ) {
304 81         81 substr( $_[0], $offset, $len ) = $substring;
305             }
306             else {
307 498         696 substr( $_[0], $offset, $len ) = pack( "Z$len", $substring );
308             }
309              
310 579         522 return;
311             }
312              
313             sub checksum {
314 83     83 0 81 my ($block) = @_;
315 83         62 my $sum = 0;
316              
317 83         1435 foreach ( unpack 'C*', $block ) {
318 42496         29368 $sum += $_;
319             }
320              
321 83         764 return $sum;
322             }
323              
324             sub validate_block {
325 0     0 0   my ( $block, $checksum ) = @_;
326 0           my $copy = "$block";
327              
328 0           write_str( $block, 148, 8, ' ' x 8 );
329              
330 0           my $calculated_checksum = checksum($copy);
331              
332 0 0         Carp::confess('Invalid block') unless $calculated_checksum == $checksum;
333              
334 0           return;
335             }
336              
337             sub file {
338 0     0 0   my ($self) = @_;
339              
340 0           return $TYPES{ $self->{'linktype'} } == $S_IFREG;
341             }
342              
343             sub link {
344 0     0 0   my ($self) = @_;
345              
346 0           return $self->{'linktype'} == 1;
347             }
348              
349             sub symlink {
350 0     0 0   my ($self) = @_;
351              
352 0           return $TYPES{ $self->{'linktype'} } == $S_IFLNK;
353             }
354              
355             sub char {
356 0     0 0   my ($self) = @_;
357              
358 0           return $TYPES{ $self->{'linktype'} } == $S_IFCHR;
359             }
360              
361             sub block {
362 0     0 0   my ($self) = @_;
363              
364 0           return $TYPES{ $self->{'linktype'} } == $S_IFBLK;
365             }
366              
367             sub dir {
368 0     0 0   my ($self) = @_;
369              
370 0           return $TYPES{ $self->{'linktype'} } == $S_IFDIR;
371             }
372              
373             sub fifo {
374 0     0 0   my ($self) = @_;
375              
376 0           return $TYPES{ $self->{'linktype'} } == $S_IFIFO;
377             }
378              
379             sub contig {
380 0     0 0   my ($self) = @_;
381              
382 0           return $self->{'linktype'} == 7;
383             }
384              
385             1;