| Tar documentation | Contained in the Tar distribution. |
Tar - module for manipulation of tar archives.
use Tar;
$tar = Tar->new();
$tar->add_files("file/foo.c", "file/bar.c");
$tar->add_data("file/bar.c","This is the file contents");
$tar->write("files.tar");
This module is definitely tentative, and several things will be changed rather shortly. The exported routines will not be exported [done], all the calls to croak() should be replaced with returning undef() and putting error messages in a package global [done].
At the moment these methods are implemented:
new()Returns a new Tar object. If given a filename as an argument, it will try to load that as a tar file.
add_files(@filenamelist)Takes a list of filenames and adds them to the in-memory archive.
add_data($filename,$data,$opthashref)Takes a filename, a scalar full of data and optionally a reference to
a hash with specific options. Will add a file to the in-memory
archive, with name $filename and content $data. Specific options
can be set using $opthashref, which will be documented later.
remove(@filenamelist)Removes any entries with names matching any of the given filenames
from the in-memory archive. String comparisons are done with eq.
read('file.tar')Try to read the given tarfile into memory. Will replace any previous
content in $tar!
write('file.tar')Will write the in-memory archive to disk.
data()Returns the in-memory archive. This is a list of references to hashes, the internals of which is not currently documented.
extract(@filenames)Write files whose names are equivalent to any of the names in
@filenames to disk, creating subdirectories as neccesary. This
might not work too well under VMS and MacOS.
| Tar documentation | Contained in the Tar distribution. |
package Tar; use strict; use Carp; BEGIN { use Exporter (); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $symlinks); $VERSION = 0.04; @ISA = qw(Exporter); @EXPORT = qw (); %EXPORT_TAGS = (); @EXPORT_OK = (); $symlinks = 1; eval { $_ = readlink $0; }; # Pointless assigment to make -w shut up if ($@) { warn "Symbolic links not available.\n"; $symlinks = 0; } }
use vars qw(@EXPORT_OK $tar_unpack_header $tar_header_length $error); $tar_unpack_header ='A100 A8 A8 A8 A12 A12 A8 A1 A100 A6 A2 A32 A32 A8 A8 A155'; $tar_header_length = 512; sub format_tar_entry; ### ### Non-method functions ### sub drat {$error=$!;return undef} sub read_tar { my ($filename) = @_; my @tarfile = (); my $i = 0; my $head; open(TAR, $filename) or drat; while (read(TAR,$head,$tar_header_length)==$tar_header_length) { my ($name, # string $mode, # octal number $uid, # octal number $gid, # octal number $size, # octal number $mtime, # octal number $chksum, # octal number $typeflag, # character $linkname, # string $magic, # string $version, # two bytes $uname, # string $gname, # string $devmajor, # octal number $devminor, # octal number $prefix) = unpack($tar_unpack_header,$head); my ($data, $diff, $dummy); $mode = oct $mode; $uid = oct $uid; $gid = oct $gid; $size = oct $size; $mtime = oct $mtime; $chksum = oct $chksum; $devmajor = oct $devmajor; $devminor = oct $devminor; if (read(TAR,$data,$size)!=$size) { croak "Read error on tarfile.\n"; } $diff = $size%512; if ($diff!=0) { read(TAR,$dummy,512-$diff); # Padding, throw away } # Guard against tarfiles with garbage at the end return @tarfile if $name eq ''; $tarfile[$i++]={ name => $name, mode => $mode, uid => $uid, gid => $gid, size => $size, mtime => $mtime, chksum => $chksum, typeflag => $typeflag, linkname => $linkname, magic => $magic, version => $version, uname => $uname, gname => $gname, devmajor => $devmajor, devminor => $devminor, prefix => $prefix, data => $data}; } return @tarfile; } sub write_tar { my ($filename) = shift; my @tarfile = @_; my ($tmp); open(TAR, ">".$filename) or drat; foreach (@tarfile) { $tmp = format_tar_entry $_; syswrite(TAR,$tmp,length($tmp)); } $tmp = "\0" x 512; syswrite(TAR,$tmp,512); close(TAR) or carp "Failed to close $filename, data may be lost: $!\n"; } sub format_tar_entry { my ($ref) = shift; my ($tmp); $tmp = pack("a100a8a8a8a12a12a8a1a100", $ref->{name}, sprintf("%6o ",$ref->{mode}), sprintf("%6o ",$ref->{uid}), sprintf("%6o ",$ref->{gid}), sprintf("%11o ",$ref->{size}), sprintf("%11o ",$ref->{mtime}), " ", $ref->{typeflag}, $ref->{linkname}); $tmp .= pack("a6", $ref->{magic}); $tmp .= '00'; $tmp .= pack("a32",$ref->{uname}); $tmp .= pack("a32",$ref->{gname}); $tmp .= pack("a8",sprintf("%6o ",$ref->{devmajor})); $tmp .= pack("a8",sprintf("%6o ",$ref->{devminor})); $tmp .= pack("a155",$ref->{prefix}); substr($tmp,148,6) = sprintf("%6o", unpack("%16C*",$tmp)); substr($tmp,154,1) = "\0"; $tmp .= "\0" x ($tar_header_length-length($tmp)); $tmp .= $ref->{data}; if ($ref->{size}>0) { $tmp .= "\0" x (512 - ($ref->{size}%512)); } return $tmp; } ### ### Methods ### # Constructor. Reads tarfile if given an argument that's the name of a # readable file. sub new { my $class = shift; my $self = {}; bless $self, $class; $self->{_filename} = undef; if (!defined $_[0]) { return $self; } if (-r $_[0]) { $self->{_data} = [read_tar $_[0]]; $self->{_filename} = $_[0]; return $self; } if (-e $_[0]) { carp "File exists but is not readable: $_[0]\n"; } return $self; } # Return list with references to hashes representing the tar archive's # component files. sub data { my $self = shift; return @{$self->{_data}}; } # Read a tarfile. Returns number of component files. sub read { my $self = shift; my $file = $_[0]; $self->{_filename} = undef; if (! -e $file) { carp "$file does not exist.\n"; $self->{_data}=[]; return undef; } elsif (! -r $file) { carp "$file is not readable.\n"; $self->{_data}=[]; return undef; } else { $self->{_data}=[read_tar $file]; $self->{_filename} = $file; return scalar @{$self->{_data}}; } } # Write a tar archive to file sub write { my ($self) = shift @_; my ($file) = shift @_; unless ($file) { $file = $self->{_filename}; } write_tar $file, @{$self->{_data}}; } # Add files to the archive. Returns number of successfully added files. sub add_files { my ($self) = shift; my (@files) = @_; my $file; my ($mode,$uid,$gid,$rdev,$size,$mtime,$data,$typeflag,$linkname); my $counter = 0; local ($/); undef $/; foreach $file (@files) { if ((undef,undef,$mode,undef,$uid,$gid,$rdev,$size, undef,$mtime,undef,undef,undef) = stat($file)) { $data = ""; $linkname = ""; if (-f $file) { # Plain file $typeflag = 0; unless (open(FILE,$file)) { next; # Can't open file, for some reason. Try next one. } $data = <FILE>; close FILE; } elsif (-d $file) { # Directory $typeflag = 5; } elsif (-l $file) { # Symlink $typeflag = 1; $linkname = readlink $file if $symlinks; } elsif (-p $file) { # Named pipe $typeflag = 6; } elsif (-S $file) { # Socket $typeflag = 8; # Bogus value, POSIX doesn't believe in sockets } elsif (-b $file) { # Block special $typeflag = 4; } elsif (-c $file) { # Character special $typeflag = 3; } else { # Something else (like what?) $typeflag = 9; # Also bogus value. } push(@{$self->{_data}},{ name => $file, mode => $mode, uid => $uid, gid => $gid, size => length $data, mtime => $mtime, chksum => " ", typeflag => $typeflag, linkname => $linkname, magic => "ustar\0", version => "00", uname => (getpwuid($uid))[0], gname => (getgrgid($gid))[0], devmajor => 0, # We don't handle this yet devminor => 0, # We don't handle this yet prefix => "", # We don't handle this yet 'data' => $data, }); $counter++; # Successfully added file } else { next undef; # stat failed } } return $counter; } sub remove { my ($self) = shift; my (@files) = @_; my $file; foreach $file (@files) { @{$self->{_data}} = grep {$_->{name} ne $file} @{$self->{_data}}; } return $self; } # Add data as a file sub add_data { my ($self, $file, $data, $opt) = @_; my $ref = {}; my $key; $ref->{'data'}=$data; $ref->{name}=$file; $ref->{mode}=0666&(0777-umask); $ref->{uid}=$>; $ref->{gid}=(split(/ /,$)))[0]; # Yuck $ref->{size}=length $data; $ref->{mtime}=time; $ref->{chksum}=" "; # Utterly pointless $ref->{typeflag}=0; # Ordinary file $ref->{linkname}=""; $ref->{magic}="ustar\0"; $ref->{version}="00"; $ref->{uname}=(getpwuid($>))[0]; $ref->{gname}=(getgrgid($)))[0]; $ref->{devmajor}=0; $ref->{devminor}=0; $ref->{prefix}=""; if ($opt) { foreach $key (keys %$opt) { $ref->{$key} = $opt->{$key} } } push(@{$self->{_data}},$ref); return 1; } # Write a single (probably) file from the in-memory archive to disk sub extract { my $self = shift; my (@files) = @_; my ($file, $level, $filename, $dirname); foreach $file (@files) { foreach (@{$self->{_data}}) { if ($_->{name} eq $file) { # For the moment, we assume that all paths in tarfiles # are given according to Unix standards. $file =~ m|^(.*?)([^/]*)$|; $dirname = $1; $filename = $2; foreach (split(m|/|,$dirname)) { if (! -e $_) { mkdir $_,0777 or drat; } chdir $_; $level++; } if ($filename eq '') { ($filename) = reverse split(m|/|,$1); } if ($_->{typeflag}==0) { # Ordinary file open(FILE,">".$filename); print FILE $_->{'data'}; close FILE; } elsif ($_->{typeflag}==5) { # Directory if (-e $filename && ! -d $filename) { drat; } mkdir $filename,0777 unless -d $filename; } elsif ($_->{typeflag}==1) { symlink $file,$_->{linkname} if $symlinks; } elsif ($_->{typeflag}==6) { warn "Doesn't handle named pipes (yet).\n"; return 1; } elsif ($_->{typeflag}==4) { warn "Doesn't handle device files (yet).\n"; return 1; } elsif ($_->{typeflag}==3) { warn "Doesn't handle device files (yet).\n"; return 1; } else { $error = "unknown file type: $_->{typeflag}"; return undef; } chmod $_->{mode},$file; utime time, $_->{mtime}, $file; if ($>==0) { # We are root chown $_->{uid},$_->{gid},$file; } chdir ".." while $level-->0; } } } } ### Standard end of module :-) 1;