#!/usr/local/bin/perl
;#
;# taro: tar organizer
;#	Copyright (c) 1991,1992 srekcah@sra.co.jp
;#	October 7 1991
;#
;#	Maintained by utashiro@sra.co.jp
;;	$rcsid = '$Id: taro,v 1.3 1992/04/13 18:30:37 utashiro Exp $';
;#
$usage = <<_;
Usage:
	taro list tar-file				# list
	taro pick tar-file pattern [ pattern ... ]	# pick up some files
	taro throw tar-file pattern [ pattern ... ]	# throw away some files
	taro cat tar-file [ name ... ]			# show file contents
	taro edit tar-file perl-script			# edit filename
	taro s/foo/bar/ tar-file			# short-cut for edit
_
;#
;# SEE ALSO
;#	tar(1), tar(5)
;#
push(@INC, 'c:/etc/perl');
require('ctime.pl');

$header_size = 512;
$header_format = "a100 a8 a8 a8 a12 a12 a8 a a100 a*";
$nullblock = "\0" x $header_size;
%mode=(1, '--x', 2, '-r-', 3, '-wx', 4, 'r--', 5, 'r-x', 6, 'rw-', 7, 'rwx');

# initialize header index
$i = 0;
for (split(/ / ,'name mode uid gid size mtime chksum linkflag linkname pad')) {
    &eval(sprintf('$%s_i = %d;', $_, $i++));
}

# option handling
$opts = 'dtv';
while ($_ = $ARGV[0], /^-/ && shift) {
    next unless ($car, $cdr) = /^-?(.)(.*)/;
    if (index($opts, "$car:") >= $[) {
	&eval("\$opt_$car = length(\$cdr) ? \$cdr : \@ARGV ? shift : &usage");
	next;
    }
    if (index($opts, $car) >= $[) {
	&eval("\$opt_$car++"); $_ = $cdr; redo;
    }
    &usage("Unknown option: $car\n\n");
}

$command = shift || &usage;	# 1st argument is command
$tarfile = shift || '-';	# 2nd argument is archive file
command: {
    if ($command eq 'list') {
	last;
    }
    if ($command eq 'pick' || $command eq 'throw') {
	@patterns = @ARGV;
	for (@patterns) {
	    $_ = &wildcard($_);
	}
	($command, $negative) = ('pick', '!') if ($command eq 'throw');
	$sub = "sub pickit { local(\$_) = \@_; $negative(/"
	    . join('/ || /', @patterns) . "/); }\n";
	print STDERR $sub if $opt_d;
	&eval($sub);
	last;
    }
    if ($command eq 'cat' || $command eq 'extract') {
	$catall++ if (@ARGV == 0);
	for (@ARGV) {
	    $cat{$_}++;
	    $cat_cnt++;
	}
	last;
    }
    if ($command eq 'edit') {
	$script = shift;
    }
    if ($command =~ m/^s/) {
	$script = $command;
	$command = 'edit';
    }
    if ($command eq 'edit') {
	$sub_edit = "sub edit { local(\$_)=\@_; $script; \$_; }\n";
	&eval($sub_edit);
	print STDERR $sub_edit if $opt_d;
    }
    else {
	print "Unkown command: $command\n";
	&usage;
    }
}

open(TAR, $tarfile) || die("$tarfile: $!\n");
open(TAR, '-|') || exec('zcat', $tarfile) || die("zcat: $!\n")
    if ($tarfile =~ /\.Z$/);
while (($s = read(TAR, $header, $header_size)) == $header_size) {
    $print_header = $print_body = 1;
    if ($header eq $nullblock) {
	print $header if ($command eq 'pick' || $command eq 'edit');
	last if (++$null_count == 2);
	next;
    }
    $null_count = 0;

    @header = unpack($header_format, $header);
    ($name = $header[$name_i]) =~ s/\0*$//;

    if ($command eq 'list') {
	$print_header = $print_body = 0;
	&show_header(@header);
    }
    elsif ($command eq 'pick') {
	$print_header = $print_body = &pickit($name);
    }
    elsif ($command eq 'cat' || $command eq 'extract') {
	$print_header = $print_body = 0;
	$catit = $catall || $cat{$name};
	$catit = 0 if ($header[$linkflag_i] =~ /[12]/);
    }
    elsif ($command eq 'edit') {
	$header[$name_i] = &edit($header[$name_i]);
	$header[$linkname_i] = &edit($header[$linkname_i])
	    if ($header[$linkflag_i] =~ /1/);
	$header = &make_header(@header);
	$print_header = $print_body = 0 if ($header[$name_i] =~ /^\0/);
    }

    print $header if $print_header;

    if ($catit && $command eq 'extract') {
	if ($name =~ m|/$|) {
	    chop($name);
	    mkdir($name, oct($header[$mode_i])) || warn("$name: $!\n");
	    next;
	} else {
	    open(OUT, ">$name") || warn("$name: $!\n");
	    select(OUT);
	}
    }
    $bufsize = 8192;
    $size = oct($header[$size_i]);
    $size = 0 if ($header[$linkflag_i] =~ /1/);
    while ($size > 0) {
	$bufsize = 512 if ($size < $bufsize);
	if (($s = read(TAR, $buf, $bufsize)) != $bufsize) {
	    print "bufsize = $bufsize, size = $size, s = $s\n" if $opt_d;
	    die "Illegal EOF!\n";
	}
	print substr($buf, 0, $size) if $catit;
	print $buf if $print_body;
	$size -= $bufsize;
    }
    exit if ($catit && !--$cat_cnt);
    if ($catit && $command eq 'extract') {
	select(STDOUT);
	close(OUT);
	chmod oct($header[$mode_i]), $name;
    }
}
close(TAR);

exit(0);

######################################################################

sub usage {
    print $usage;
    exit 1;
}

;#
;# make header block by reculculating checksum
;#
sub make_header {
    local(@header, $header) = @_;
    $header = pack($header_format, @header[0..5], ' ' x 8, @header[7..9]);
    $header[6] = sprintf("% 6o\0 ", unpack("%16C*", $header));
    pack($header_format, @header);
}

;#
;# parse header block
;#
sub parse_header {
    local(@h) = @_;
    $h[$name_i] =~ s/\0+$//;
    $h[$linkname_i] =~ s/\0+$//;
    for (2..6) {
	$h[$_] =~ s/ \0//g;
	$h[$_] = oct($h[$_]);
    }
    @h;
}

;#
;# show "tar tv" like information from header
;#
sub show_header {
    ($name, $mode, $uid, $gid, $size, $mtime, $chksum, $linkflag, $linkname)
	= &parse_header(@_);
    $ctime = &ctime($mtime);
    chop($ctime);

    # Sat Oct  5 10:46:00 PDT 1991
    # 0-->            16---->
    substr($ctime, 16, length($ctime) - 21) = '';
    substr($ctime, 0, 4) = '';
    $ctime =~ s/ (\d:\d\d)/0\1/;	# keep compatibility with tar

    printf("%s%3d/%d%7d %s ", &modeline($mode), $uid, $gid, $size, $ctime)
	unless $opt_t;		# terse

    print $name;

    if ($linkflag eq '1') {	# hard link
	print " linked to ", $linkname;
    }
    if ($linkflag eq '2') {	# symbolic link
	print " symbolic link to ", $linkname;
    }
    print " [chksum=$chksum]" if $opt_v;
    print "\n";
}

;#
;# make modeline like 'rw-r--r--'
;#
sub modeline {
    local($u, $g, $o) = $_[$[] =~ /(\d)(\d)(\d) \0$/;
    $mode{$u} . $mode{$g} . $mode{$o};
}

;#
;# wildcard to regex convert
;#
sub wildcard {
    local($_) = @_;
    s#\\?.#$_ = $&; s/\\?([_0-9A-Za-z])/$1/ || /\\./ || s/[*]/.*/ ||
	s/[|]/\$|^/ || tr/?{,}[]\-/.(|)[]\-/ || s/./\\$&/; $_;#ge;
    length($_) ? "(^|\\/)$_\(\\0|\$|\\/)" : undef;
}

;#
;# eval with error handling
;#
sub eval {
    local($exp) = @_;
    eval $exp;
    if ($@ ne '') {
	local($package, $filename, $line) = caller;
	warn "eval failed on line $line in file $filename\n";
	warn "exp = \"$exp\"\n";
	warn $@;
	exit 1;
    }
}
