The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/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