File Coverage

blib/lib/Archive/Tar/Stream.pm
Criterion Covered Total %
statement 21 228 9.2
branch 0 84 0.0
condition 0 16 0.0
subroutine 7 26 26.9
pod 19 19 100.0
total 47 373 12.6


line stmt bran cond sub pod time code
1             package Archive::Tar::Stream;
2              
3 1     1   46836 use 5.006;
  1         5  
  1         51  
4 1     1   6 use strict;
  1         1  
  1         36  
5 1     1   5 use warnings;
  1         13  
  1         37  
6              
7             # this is pretty fixed by the format!
8 1     1   6 use constant BLOCKSIZE => 512;
  1         1  
  1         1385  
9              
10             # dependencies
11 1     1   2443 use IO::File;
  1         32283  
  1         1239  
12 1     1   10 use IO::Handle;
  1         2  
  1         53  
13 1     1   2479 use File::Temp;
  1         34081  
  1         6387  
14              
15             # XXX - make this an OO attribute
16             our $VERBOSE = 0;
17              
18             =head1 NAME
19              
20             Archive::Tar::Stream - pure perl IO-friendly tar file management
21              
22             =head1 VERSION
23              
24             Version 0.02
25              
26             =cut
27              
28             our $VERSION = '0.02';
29              
30              
31             =head1 SYNOPSIS
32              
33             Archive::Tar::Stream grew from a requirement to process very large
34             archives containing email backups, where the IO hit for unpacking
35             a tar file, repacking parts of it, and then unlinking all the files
36             was prohibitive.
37              
38             Archive::Tar::Stream takes two file handles, one purely for reads,
39             one purely for writes. It does no seeking, it just unpacks
40             individual records from the input filehandle, and packs records
41             to the output filehandle.
42              
43             This module does not attempt to do any file handle management or
44             compression for you. External zcat and gzip are quite fast and
45             use separate cores.
46              
47             use Archive::Tar::Stream;
48              
49             my $ts = Archive::Tar::Stream->new(outfh => $fh);
50             $ts->AddFile($name, -s $fh, $fh);
51              
52             # remove large non-jpeg files from a tar.gz
53             my $infh = IO::File->new("zcat $infile |") || die "oops";
54             my $outfh = IO::File->new("| gzip > $outfile") || die "double oops";
55             my $ts = Archive::Tar::Stream->new(infh => $infh, outfh => $outfh);
56             $ts->StreamCopy(sub {
57             my ($header, $outpos, $fh) = @_;
58              
59             # we want all small files
60             return 'KEEP' if $header->{size} < 64 * 1024;
61             # and any other jpegs
62             return 'KEEP' if $header->{name} =~ m/\.jpg$/i;
63              
64             # no, seriously
65             return 'EDIT' unless $fh;
66              
67             return 'KEEP' if mimetype_of_filehandle($fh) eq 'image/jpeg';
68              
69             # ok, we don't want other big files
70             return 'SKIP';
71             });
72              
73              
74             =head1 SUBROUTINES/METHODS
75              
76             =head2 new
77              
78             my $ts = Archive::Tar::Stream->new(%args);
79              
80             Args:
81             infh - filehandle to read from
82             outfh - filehandle to write to
83             inpos - initial offset in infh
84             outpos - initial offset in outfh
85             safe_copy - boolean.
86              
87             Offsets are for informational purposes only, but can be
88             useful if you are tracking offsets of items within your
89             tar files separately. All read and write functions
90             update these offsets. If you don't provide offsets, they
91             will default to zero.
92              
93             Safe Copy is the default - you have to explicitly turn it
94             off. If Safe Copy is set, every file is first extracted
95             from the input filehandle and stored in a temporary file
96             before appending to the output filehandle. This uses
97             slightly more IO, but guarantees that a truncated input
98             file will not corrupt the output file.
99              
100             =cut
101              
102             sub new {
103 0     0 1   my $class = shift;
104 0           my %args = @_;
105              
106 0 0 0       die "Useless to stream without a filehandle"
107             unless ($args{infh} or $args{outfh});
108              
109 0   0       my $Self = bless {
110             # defaults
111             safe_copy => 1,
112             inpos => 0,
113             outpos => 0,
114             %args
115             }, ref($class) || $class;
116              
117 0           return $Self;
118             }
119              
120             =head2 SafeCopy
121              
122             $ts->SafeCopy(0);
123              
124             Toggle the "safe_copy" field mentioned above.
125              
126             =cut
127              
128             sub SafeCopy {
129 0     0 1   my $Self = shift;
130 0 0         if (@_) {
131 0           $Self->{safe_copy} = shift;
132             }
133 0           return $Self->{safe_copy};
134             }
135              
136             =head2 InPos
137              
138             =head2 OutPos
139              
140             Read only accessors for the internal position trackers for
141             the two tar streams.
142              
143             =cut
144              
145             sub InPos {
146 0     0 1   my $Self = shift;
147 0           return $Self->{inpos};
148             }
149              
150             sub OutPos {
151 0     0 1   my $Self = shift;
152 0           return $Self->{outpos};
153             }
154              
155             =head2 AddFile
156              
157             Adds a file to the output filehandle, adding sensible
158             defaults for all the extra header fields.
159              
160             Requires: outfh
161              
162             my $header = $ts->AddFile($name, $size, $fh, %extra);
163              
164             See TARHEADER for documentation of the header fields.
165              
166             You must provide 'size' due to the non-seeking nature of
167             this library, but "-s $fh" is usually fine.
168              
169             Returns the complete header that was written.
170              
171             =cut
172              
173             sub AddFile {
174 0     0 1   my $Self = shift;
175 0           my $name = shift;
176 0           my $size = shift;
177 0           my $fh = shift;
178              
179 0           my $header = $Self->BlankHeader(@_);
180 0           $header->{name} = $name;
181 0           $header->{size} = $size;
182              
183 0           my $fullheader = $Self->WriteHeader($header);
184 0           $Self->CopyFromFh($fh, $size);
185              
186 0           return $fullheader;
187             }
188              
189             =head2 AddLink
190              
191             my $header = $ts->AddLink($name, $linkname, %extra);
192              
193             Adds a symlink to the output filehandle.
194              
195             See TARHEADER for documentation of the header fields.
196              
197             Returns the complete header that was written.
198              
199             =cut
200              
201             sub AddLink {
202 0     0 1   my $Self = shift;
203 0           my $name = shift;
204 0           my $linkname = shift;
205              
206 0           my $header = $Self->BlankHeader(@_);
207 0           $header->{name} = $name;
208 0           $header->{linkname} = $linkname;
209 0           $header->{typeflag} = 2;
210              
211 0           return $Self->WriteHeader($header);
212             }
213              
214             =head2 StreamCopy
215              
216             Streams all records from the input filehandle and provides
217             an easy way to write them to the output filehandle.
218              
219             Requires: infh
220             Optional: outfh - required if you return 'KEEP'
221              
222             $ts->StreamCopy(sub {
223             my ($header, $outpos, $fh) = @_;
224             # ...
225             return 'KEEP';
226             });
227              
228             The chooser function can either return a single 'action' or
229             a tuple of action and a new header.
230              
231             The action can be:
232             KEEP - copy this file as is (possibly changed header) to output tar
233             EDIT - re-call $Chooser with filehandle
234             SKIP - skip over the file and call $Chooser on the next one
235             EXIT - skip and also stop further processing
236              
237             EDIT mode:
238              
239             the file will be copied to a temporary file and the filehandle passed to
240             $Chooser. It can truncate, rewrite, edit - whatever. So long as it updates
241             $header->{size} and returns it as $newheader it's all good.
242              
243             you don't have to change the file of course, it's also good just as a way to
244             view the contents of some files as you stream them.
245              
246             A standard usage pattern looks like this:
247              
248             $ts->StreamCopy(sub {
249             my ($header, $outpos, $fs) = @_;
250              
251             # simple checks
252             return 'KEEP' if do_want($header);
253             return 'SKIP' if dont_want($header);
254              
255             return 'EDIT' unless $fh;
256              
257             # checks that require a filehandle
258             });
259              
260             =cut
261              
262             sub StreamCopy {
263 0     0 1   my $Self = shift;
264 0           my $Chooser = shift;
265              
266 0           while (my $header = $Self->ReadHeader()) {
267 0           my $pos = $header->{_pos};
268 0           my $oldsize = $header->{size};
269 0 0         if ($Chooser) {
270 0           my ($rc, $newheader) = $Chooser->($header, $Self->{outpos}, undef);
271              
272 0           my $TempFile;
273             my $Edited;
274              
275             # positive code means read the file
276 0 0         if ($rc eq 'EDIT') {
277 0           $Edited = 1;
278 0           $TempFile = $Self->CopyToTempFile($header->{size});
279             # call chooser again with the contents
280 0   0       ($rc, $newheader) = $Chooser->($newheader || $header, $Self->{outpos}, $TempFile);
281 0           seek($TempFile, 0, 0);
282             }
283              
284             # short circuit exit code
285 0 0         return if $rc eq 'EXIT';
286              
287             # NOTE: even the size could have been changed if it's an edit!
288 0 0         $header = $newheader if $newheader;
289              
290 0 0         if ($rc eq 'KEEP') {
    0          
291 0 0         print "KEEP $header->{name} $pos/$Self->{outpos}\n" if $VERBOSE;
292 0 0 0       if ($TempFile) {
    0          
293 0           $Self->WriteHeader($header);
294 0           $Self->CopyFromFh($TempFile, $header->{size});
295             }
296             # if the sizes don't match we just do a tempfile too
297             elsif ($Self->{safe_copy} or ($oldsize != $header->{size})) {
298             # guarantee safety by getting everything into a temporary file first
299 0           $TempFile = $Self->CopyToTempFile($oldsize);
300 0           $Self->WriteHeader($header);
301 0           $Self->CopyFromFh($TempFile, $header->{size});
302             }
303             else {
304 0           $Self->WriteHeader($header);
305 0           $Self->CopyBytes($header->{size});
306             }
307             }
308              
309             # anything else means discard it
310             elsif ($rc eq 'SKIP') {
311 0 0         if ($TempFile) {
312 0 0         print "LATE REJECT $header->{name} $pos/$Self->{outpos}\n" if $VERBOSE;
313             # $TempFile already contains the bytes
314             }
315             else {
316 0 0         print "DISCARD $header->{name} $pos/$Self->{outpos}\n" if $VERBOSE;
317 0           $Self->DumpBytes($header->{size});
318             }
319             }
320              
321             else {
322 0           die "Bogus response $rc from callback\n";
323             }
324             }
325             else {
326 0 0         print "PASSTHROUGH $header->{name} $Self->{outpos}\n" if $VERBOSE;
327              
328 0 0         if ($Self->{safe_copy}) {
329 0           my $TempFile = $Self->CopyToTempFile($header->{size});
330 0           $Self->WriteHeader($header);
331 0           $Self->CopyFromFh($TempFile, $header->{size});
332             }
333             else {
334 0           $Self->WriteHeader($header);
335 0           $Self->CopyBytes($header->{size});
336             }
337             }
338             }
339             }
340              
341             =head2 ReadBlocks
342              
343             Requires: infh
344              
345             my $raw = $ts->ReadBlocks($nblocks);
346              
347             Reads 'n' blocks of 512 bytes from the input filehandle
348             and returns them as single scalar.
349              
350             Returns undef at EOF on the input filehandle. Any further
351             calls after undef is returned will die. This is to avoid
352             naive programmers creating infinite loops.
353              
354             nblocks is optional, and defaults to 1.
355              
356             =cut
357              
358             sub ReadBlocks {
359 0     0 1   my $Self = shift;
360 0   0       my $nblocks = shift || 1;
361 0 0         unless ($Self->{infh}) {
362 0           die "Attempt to read without input filehandle";
363             }
364 0           my $bytes = BLOCKSIZE * $nblocks;
365 0           my $buf;
366             my @result;
367 0           while ($bytes > 0) {
368 0           my $n = sysread($Self->{infh}, $buf, $bytes);
369 0 0         unless ($n) {
370 0           delete $Self->{infh};
371 0 0         return if ($bytes == BLOCKSIZE * $nblocks); # nothing at EOF
372 0           die "Failed to read full block at $Self->{inpos}";
373             }
374 0           $bytes -= $n;
375 0           $Self->{inpos} += $n;
376 0           push @result, $buf;
377             }
378 0           return join('', @result);
379             }
380              
381             =head2 WriteBlocks
382              
383             Requires: outfh
384              
385             my $pos = $ts->WriteBlocks($buffer, $nblocks);
386              
387             Write blocks to the output filehandle. If the buffer is too
388             short, it will be padded with zero bytes. If it's too long,
389             it will be truncated.
390              
391             nblocks is optional, and defaults to 1.
392              
393             Returns the position of the header in the output stream.
394              
395             =cut
396              
397             sub WriteBlocks {
398 0     0 1   my $Self = shift;
399 0           my $string = shift;
400 0   0       my $nblocks = shift || 1;
401              
402 0           my $bytes = BLOCKSIZE * $nblocks;
403              
404 0 0         unless ($Self->{outfh}) {
405 0           die "Attempt to write without output filehandle";
406             }
407 0           my $pos = $Self->{outpos};
408              
409             # make sure we've got $nblocks times BLOCKSIZE bytes to write
410 0 0         if (length($string) > $bytes) {
    0          
411 0           $string = substr($string, 0, $bytes);
412             }
413             elsif (length($string) < $bytes) {
414 0           $string .= "\0" x ($bytes - length($string));
415             }
416              
417 0           while ($bytes > 0) {
418 0           my $n = syswrite($Self->{outfh}, $string, $bytes, (BLOCKSIZE * $nblocks) - $bytes);
419 0 0         unless ($n) {
420 0           delete $Self->{outfh};
421 0           die "Failed to write full block at $Self->{outpos}";
422             }
423 0           $bytes -= $n;
424 0           $Self->{outpos} += $n;
425             }
426              
427 0           return $pos;
428             }
429              
430             =head2 ReadHeader
431              
432             Requires: infh
433              
434             my $header = $ts->ReadHeader(%Opts);
435              
436             Read a single 512 byte header off the input filehandle and
437             convert it to a TARHEADER format hashref. Returns undef
438             at the end of the file.
439              
440             If the option (SkipInvalid => 1) is passed, it will skip
441             over blocks which fail to pass the checksum test.
442              
443             =cut
444              
445             sub ReadHeader {
446 0     0 1   my $Self = shift;
447 0           my %Opts = @_;
448              
449 0           my ($pos, $header, $skipped) = (0, undef, 0);
450              
451 0           my $initialpos = $Self->{inpos};
452 0           while (not $header) {
453 0           $pos = $Self->{inpos};
454 0           my $block = $Self->ReadBlocks();
455 0 0         last unless $block;
456 0           $header = $Self->ParseHeader($block);
457 0 0         last if $header;
458 0 0         last unless $Opts{SkipInvalid};
459 0           $skipped++;
460             }
461              
462 0 0         return unless $header;
463              
464 0 0         if ($skipped) {
465 0           warn "Skipped $skipped blocks - invalid headers at $initialpos\n";
466             }
467              
468 0           $header->{_pos} = $pos;
469 0           $Self->{last_header} = $header;
470              
471 0           return $header;
472             }
473              
474             =head2 WriteHeader
475              
476             Requires: outfh
477              
478             my $newheader = $ts->WriteHeader($header);
479              
480             Read a single 512 byte header off the input filehandle.
481              
482             If the option (SkipInvalid => 1) is passed, it will skip
483             over blocks which fail to pass the checksum test.
484              
485             Returns a copy of the header with _pos set to the position
486             in the output file.
487              
488             =cut
489              
490             sub WriteHeader {
491 0     0 1   my $Self = shift;
492 0           my $header = shift;
493              
494 0           my $block = $Self->CreateHeader($header);
495 0           my $pos = $Self->WriteBlocks($block);
496 0           return( {%$header, _pos => $pos} );
497             }
498              
499             =head2 ParseHeader
500              
501             my $header = $ts->ParseHeader($block);
502              
503             Parse a single block of raw bytes into a TARHEADER
504             format header. $block must be exactly 512 bytes.
505              
506             Returns undef if the block fails the checksum test.
507              
508             =cut
509              
510             sub ParseHeader {
511 0     0 1   my $Self = shift;
512 0           my $block = shift;
513              
514             # enforce length
515 0 0         return unless(512 == length($block));
516              
517             # skip empty blocks
518 0 0         return if substr($block, 0, 1) eq "\0";
519              
520             # unpack exactly 15 items from the block
521 0           my @items = unpack("a100a8a8a8a12a12a8a1a100a8a32a32a8a8a155", $block);
522 0 0         return unless (15 == @items);
523              
524 0           for (@items) {
525 0           s/\0.*//; # strip from first null
526             }
527              
528 0           my $chksum = oct($items[6]);
529             # do checksum
530 0           substr($block, 148, 8) = " ";
531 0 0         unless (unpack("%16C*", $block) == $chksum) {
532 0           return;
533             }
534              
535 0           my %header = (
536             name => $items[0],
537             mode => oct($items[1]),
538             uid => oct($items[2]),
539             gid => oct($items[3]),
540             size => oct($items[4]),
541             mtime => oct($items[5]),
542             # checksum
543             typeflag => $items[7],
544             linkname => $items[8],
545             # magic
546             uname => $items[10],
547             gname => $items[11],
548             devmajor => oct($items[12]),
549             devminor => oct($items[13]),
550             prefix => $items[14],
551             );
552              
553 0           return \%header;
554             }
555              
556             =head2 BlankHeader
557              
558             my $header = $ts->BlankHeader(%extra);
559              
560             Create a header with sensible defaults. That means
561             time() for mtime, 0777 for mode, etc.
562              
563             It then applies any 'extra' fields from %extra to
564             generate a final header. Also validates the keys
565             in %extra to make sure they're all known keys.
566              
567             =cut
568              
569             sub BlankHeader {
570 0     0 1   my $Self = shift;
571              
572 0           my %hash = (
573             name => '',
574             mode => 0777,
575             uid => 0,
576             gid => 0,
577             size => 0,
578             mtime => time(),
579             typeflag => '0', # this is actually the STANDARD plain file format, phooey. Not 'f' like Tar writes
580             linkname => '',
581             uname => '',
582             gname => '',
583             devmajor => 0,
584             devminor => 0,
585             prefix => '',
586             );
587              
588 0           my %overrides = @_;
589 0           foreach my $key (keys %overrides) {
590 0 0         if (exists $hash{$key}) {
591 0           $hash{$key} = $overrides{$key};
592             }
593             else {
594 0           warn "invalid key $key for tar header\n";
595             }
596             }
597 0           return \%hash;
598             }
599              
600             =head2 CreateHeader
601              
602             my $block = $ts->CreateHeader($header);
603              
604             Creates a 512 byte block from the TARHEADER format header.
605              
606             =cut
607              
608             sub CreateHeader {
609 0     0 1   my $Self = shift;
610 0           my $header = shift;
611              
612 0           my $block = pack("a100a8a8a8a12a12a8a1a100a8a32a32a8a8a155",
613             $header->{name},
614             sprintf("%07o", $header->{mode}),
615             sprintf("%07o", $header->{uid}),
616             sprintf("%07o", $header->{gid}),
617             sprintf("%011o", $header->{size}),
618             sprintf("%011o", $header->{mtime}),
619             " ", # chksum
620             $header->{typeflag},
621             $header->{linkname},
622             "ustar \0", # magic
623             $header->{uname},
624             $header->{gname},
625             sprintf("%07o", $header->{devmajor}),
626             sprintf("%07o", $header->{devminor}),
627             $header->{prefix},
628             );
629              
630             # calculate checksum
631 0           my $checksum = sprintf("%06o", unpack("%16C*", $block));
632 0           substr($block, 148, 8) = $checksum . "\0 ";
633              
634             # pad out to BLOCKSIZE characters
635 0 0         if (length($block) < BLOCKSIZE) {
    0          
636 0           $block .= "\0" x (BLOCKSIZE - length($block));
637             }
638             elsif (length($block) > BLOCKSIZE) {
639 0           $block = substr($block, 0, BLOCKSIZE);
640             }
641              
642 0           return $block;
643             }
644              
645             =head2 CopyBytes
646              
647             $ts->CopyBytes($bytes);
648              
649             Copies bytes from input to output filehandle, rounded up to
650             block size, so only whole blocks are actually copied.
651              
652             =cut
653              
654             sub CopyBytes {
655 0     0 1   my $Self = shift;
656 0           my $bytes = shift;
657 0           my $buf;
658 0           while ($bytes > 0) {
659 0           my $n = int($bytes / BLOCKSIZE);
660 0 0         $n = 16 if $n > 16;
661 0           my $dump = $Self->ReadBlocks($n);
662 0           $Self->WriteBlocks($dump, $n);
663 0           $bytes -= length($dump);
664             }
665             }
666              
667             =head2 DumpBytes
668              
669             $ts->DumpBytes($bytes);
670              
671             Just like CopyBytes, but it doesn't write anywhere.
672             Reads full blocks off the input filehandle, rounding
673             up to block size.
674              
675             =cut
676              
677             sub DumpBytes {
678 0     0 1   my $Self = shift;
679 0           my $bytes = shift;
680 0           while ($bytes > 0) {
681 0           my $n = int($bytes / BLOCKSIZE);
682 0 0         $n = 16 if $n > 16;
683 0           my $dump = $Self->ReadBlocks($n);
684 0           $bytes -= length($dump);
685             }
686             }
687              
688             =head2 FinishTar
689              
690             $ts->FinishTar();
691              
692             Writes 5 blocks of zero bytes to the output file, which makes
693             gnu tar happy that it's found the end of the file.
694              
695             Don't use this if you're planning on concatenating multiple
696             files together.
697              
698             =cut
699              
700             sub FinishTar {
701 0     0 1   my $Self = shift;
702 0           $Self->WriteBlocks("\0" x 512);
703 0           $Self->WriteBlocks("\0" x 512);
704 0           $Self->WriteBlocks("\0" x 512);
705 0           $Self->WriteBlocks("\0" x 512);
706 0           $Self->WriteBlocks("\0" x 512);
707             }
708              
709             =head2 CopyToTempFile
710              
711             my $fh = $ts->CopyToTempFile($header->{size});
712              
713             Creates a temporary file (with File::Temp) and fills it with
714             the contents of the file on the input stream. It reads
715             entire blocks, and discards the padding.
716              
717             =cut
718              
719             sub CopyToTempFile {
720 0     0 1   my $Self = shift;
721 0           my $bytes = shift;
722              
723 0           my $TempFile = File::Temp->new();
724 0           while ($bytes > 0) {
725 0           my $n = 1 + int(($bytes - 1) / BLOCKSIZE);
726 0 0         $n = 16 if $n > 16;
727 0           my $dump = $Self->ReadBlocks($n);
728 0 0         $dump = substr($dump, 0, $bytes) if length($dump) > $bytes;
729 0           $TempFile->print($dump);
730 0           $bytes -= length($dump);
731             }
732 0           seek($TempFile, 0, 0);
733              
734 0           return $TempFile;
735             }
736              
737             =head2 CopyFromFh
738              
739             $ts->CopyFromFh($fh, $header->{size});
740              
741             Copies the contents of the filehandle to the output stream,
742             padding out to block size.
743              
744             =cut
745              
746             sub CopyFromFh {
747 0     0 1   my $Self = shift;
748 0           my $Fh = shift;
749 0           my $bytes = shift;
750              
751 0           my $buf;
752 0           while ($bytes > 0) {
753 0 0         my $thistime = $bytes > BLOCKSIZE ? BLOCKSIZE : $bytes;
754 0           my $block = '';
755 0           while ($thistime) {
756 0           my $n = sysread($Fh, $buf, $thistime);
757 0 0         unless ($n) {
758 0           die "Failed to read entire file, doh ($bytes remaining)!\n";
759             }
760 0           $thistime -= $n;
761 0           $block .= $buf;
762             }
763 0 0         if (length($block) < BLOCKSIZE) {
764 0           $block .= "\0" x (BLOCKSIZE - length($block));
765             }
766 0           $Self->WriteBlocks($block);
767 0           $bytes -= BLOCKSIZE;
768             }
769             }
770              
771             =head1 TARHEADER format
772              
773             This is the "BlankHeader" output, which includes all the fields
774             in a standard tar header:
775              
776             my %hash = (
777             name => '',
778             mode => 0777,
779             uid => 0,
780             gid => 0,
781             size => 0,
782             mtime => time(),
783             typeflag => '0', # this is actually the STANDARD plain file format, phooey. Not 'f' like Tar writes
784             linkname => '',
785             uname => '',
786             gname => '',
787             devmajor => 0,
788             devminor => 0,
789             prefix => '',
790             );
791              
792             You can read more about the tar header format produced by this
793             module on wikipedia:
794             L
795             or here: L
796              
797             Type flags:
798              
799             '0' Normal file
800             (ASCII NUL) Normal file (now obsolete)
801             '1' Hard link
802             '2' Symbolic link
803             '3' Character special
804             '4' Block special
805             '5' Directory
806             '6' FIFO
807             '7' Contiguous file
808              
809             Obviously some module wrote 'f' as the type - I must have found
810             that during original testing. That's bogus though.
811              
812             =head1 AUTHOR
813              
814             Bron Gondwana, C<< >>
815              
816             =head1 BUGS
817              
818             Please report any bugs or feature requests to C
819             at rt.cpan.org>, or through the web interface at
820             L.
821             I will be notified, and then you'll automatically be notified of progress
822             on your bug as I make changes.
823              
824              
825             =head1 SUPPORT
826              
827             You can find documentation for this module with the perldoc command.
828              
829             perldoc Archive::Tar::Stream
830              
831              
832             You can also look for information at:
833              
834             =over 4
835              
836             =item * RT: CPAN's request tracker (report bugs here)
837              
838             L
839              
840             =item * AnnoCPAN: Annotated CPAN documentation
841              
842             L
843              
844             =item * CPAN Ratings
845              
846             L
847              
848             =item * Search CPAN
849              
850             L
851              
852             =back
853              
854              
855             =head1 LATEST COPY
856              
857             The latest copy of this code, including development branches,
858             can be found at
859              
860             http://github.com/brong/Archive-Tar-Stream/
861              
862              
863             =head1 LICENSE AND COPYRIGHT
864              
865             Copyright 2011 Opera Software Australia Pty Limited
866              
867             This program is free software; you can redistribute it and/or modify it
868             under the terms of either: the GNU General Public License as published
869             by the Free Software Foundation; or the Artistic License.
870              
871             See http://dev.perl.org/licenses/ for more information.
872              
873              
874             =cut
875              
876             1; # End of Archive::Tar::Stream