#!/usr/bin/perl =begin metadata Name: tar Description: manipulate tape archives Author: License: =end metadata =cut use 5.004; # use strict; use Getopt::Std; use IO::File; use vars qw($opt); BEGIN { $opt = 'ctxvmf:'; eval { require Compress::Zlib }; if ($@) { warn "No decompression available:$@\n"; } else { Compress::Zlib->import; $opt .= 'zZ'; } } my %opt; getopts($opt,\%opt); sub read_header { my $read = shift; my $buf = ''; my $err = &$read($buf,512); die "Cannot read:$err" if $err; if (length($buf) == 512) { return undef if $buf =~ /^\0{512}/; my %info; ($info{'archname'}, $info{'mode'}, $info{'uid'}, $info{'gid'}, $info{'size'}, $info{'mtime'}, $info{'chksum'}, $info{'linkflag'}, $info{'arch_linkname'}, $info{'magic'}, $info{'uname'}, $info{'gname'}, $info{'devmajor'}, $info{'devminor'}) = unpack('A100A8A8A8A12A12A8A1A100A8A32A32A8A8',$buf); foreach my $key (qw(archname arch_linkname magic uname gname)) { $info{$key} =~ s/\0(?:.|\n)*$//; } foreach my $key (qw(mode uid gid size mtime chksum)) { my $val = $info{$key}; if ($val =~ /^\s*([0-7]+)$/) { $info{$key} = oct($1); } else { $val =~ s/([^\x20-\x7f])/sprintf('\%03o',unpack('C',$1))/eg; warn "$key is '$val'\n"; } } return \%info; } else { die "size is ".length($buf)." not 512" if (length($buf)); } return undef; } sub read_data { my ($read,$hdr,$fh) = @_; my $size = $hdr->{'size'}; my $blocks = int(($size+511)/512); # print "$size => $blocks\n"; my $first = 1; while ($blocks--) { my $buf = ''; my $err = &$read($buf,512); die "Cannot read:$err" if ($err); my $len = length($buf); if ($len != 512) { die "Size is $len not 512:$!"; } if ($fh) { $buf = substr($buf,0,$size) if ($size < 512); if ($first) { if ($buf =~ /([^\r\n\s!-~])/) { warn "Binary due to $1 (".ord($1).")\n"; binmode($fh) } $first = 0; } print $fh $buf; $size -= length($buf); } } } sub skip_entry { my ($read,$hdr) = @_; read_data($read,$hdr,undef); } sub make_dir { my $name = shift; make_dir($1) if ($name =~ m#^(.*)/[^/]+#); unless (-d $name) { mkdir($name,0777) || die "Cannot mkdir($name):$!"; warn "mkdir $name\n" if ($opt{'v'}); } } sub extract_entry { my ($read,$hdr) = @_; my $name = $hdr->{'archname'}; if ($opt{'m'}) { $name =~ s/([A-Z])/_\l$1/g; } make_dir($1) if ($name =~ m#^(.*)/[^/]+#); my $typ = $hdr->{'mode'} >> 9; if ($typ != 0100) { if ($typ != 040) { printf "%o $name\n",$hdr->{'mode'}; } read_data($read,$hdr,undef); return; } if (-f $name && !-w $name) { chmod(0666,$name); unlink($name) } my $fh = IO::File->new(">$name") unless ($name =~ m#/$#); warn "Cannot open $name:$!" unless ($fh); read_data($read,$hdr,$fh); if ($fh) { my $t = $hdr->{'mtime'}; $fh->close; utime($t,$t,$name); chmod($hdr->{'mode'} & 0777,$name); } } sub mode_str { my $mode = shift; my $str = ''; $str .= ($mode & 4) ? 'r' : '-'; $str .= ($mode & 2) ? 'w' : '-'; $str .= ($mode & 1) ? 'x' : '-'; } sub list_entry { my $hdr = shift; my $mode = $hdr->{'mode'}; my $str = '-'; # Needs to be 'd', 'l', 'c', 'b' etc. $str .= mode_str(($mode >> 6) & 7); $str .= mode_str(($mode >> 3) & 7); $str .= mode_str(($mode >> 0) & 7); $str .= sprintf(" %d/%d %12d ",$hdr->{'uid'},$hdr->{'gid'},$hdr->{'size'}); my $t = localtime($hdr->{'mtime'}); $t =~ s/^\w+\s//; $t =~ s/(\d+:\d+):\d+/$1/; $str .= $t; $str .= ' '; $str .= $hdr->{'archname'}; return $str; } if ($opt{'c'}) { die "-c not implemeted\n"; } else { my $hdr; $| = 1; if ($opt{'f'}) { open(STDIN, '<', $opt{'f'}) || die "Cannot open $opt{'f'}:$!"; } binmode(STDIN); my $read; if ($opt{'z'} || $opt{'Z'}) { # quick and dirty till we sort out Compress::Zlib my $gz = gzopen(\*STDIN, "rb"); die "Cannot gzopen:$gzerrno" unless ($gz); $read = sub { $gz->gzread($_[0],$_[1]) < 0 ? $gzerrno : 0 }; } else { $read = sub { read(\*STDIN,$_[0], $_[1]) < 0 ? $! : 0 }; } while ($hdr = read_header($read)) { my $dh; if ($opt{'x'}) { extract_entry($read,$hdr); } else { skip_entry($read,$hdr); } if ($opt{'v'}) { print list_entry($hdr),"\n" } elsif ($opt{'t'}) { print $hdr->{'archname'},"\n"; } #last; } } __END__ =encoding utf8 =head1 NAME tar - manipulate tape archives