Category: Utility
Author/Contact Info Dmitri Tikhonov, dtikhonov@yahoo.com
Description: Filter to convert tarballs from GNU tar format to ustar (POSIX) format. This script was implemented because of wild differences in the way the two formats support filenames that are longer than 100 characters.

See also ustar2tar.

#
# this program converts tar formats from old par to ustar on the fly.
# old tar is read from stdin and then, in ustar format, written to
# stdout.
#
# author: Dmitri
#
# $Id: tar2ustar,v 1.2 2001/03/15 13:19:39 dmitri Exp $
#
# $Log: tar2ustar,v $
# Revision 1.2  2001/03/15 13:19:39  dmitri
# Little clean-up.
#
# Revision 1.1  2001/03/14 17:33:53  dmitri
# Filter to conver tar archives from old format to the ustar format
# (POSIX 1003.1).
#

use integer;
use strict;

my $block;
my $blocksize = 512;
my $blknum = 0;

my @tar_field = (100, 8, 8, 8, 12, 12, 8, 1, 100);

my @tar_name = ('name', 'mode', 'uid', 'gid', 'size', 'mtime', 'chksum
+',
                'link', 'linkname');

sub checksum {
        my ($block) = @_;
        my $chksum = 0;

        substr $block, 148, 8, '        ';

        for (split '', $block) { $chksum += ord }
        $chksum = sprintf("%o%c ", $chksum, 0);
        $chksum = ' 'x(8 - length($chksum)) . $chksum;

        substr $block, 148, 8, $chksum;

        return $block;
}


sub parse_block {
        my ($block, $filename, $prefix) = @_;
        my %tar;
        
        my $off = 0;
        for (my $i = 0; $i < scalar @tar_field; ++$i) {
                ($tar{$tar_name[$i]} = substr $block, $off, $tar_field
+[$i]) =~
                        s/\000//g;
                $off += $tar_field[$i];
        }

        # this condition probably only applies at the end, where there
+'s a
        # trailer (don't feel like detecting it).
        ++$blknum && return $block unless $tar{name};

        if ($tar{'name'} =~ m|^\./\./\@LongLink| && $tar{'link'} eq "L
+") {
                read(STDIN, $block, $blocksize);
                $block =~ s/\000.*$//;
                print STDERR $block, "\n";
                my ($filename, $prefix, $index);
                $index = index $block, "/", length($block) - 100;
                $filename = substr $block, $index + 1;
                $prefix = substr $block, 0, $index;

                read(STDIN, $block, $blocksize);

                return parse_block($block, $filename, $prefix);
        }

        if ($filename && $prefix) {
                substr $block, 0, 100, sprintf("%c", 0) x 100;
                substr $block, 0, length $filename, $filename;
                substr $block, 345, length $prefix, $prefix;
        }

        # mode
        substr $block, 100, 7, "       ";
        substr $block, 101, length $tar{'mode'}, $tar{'mode'};

        substr $block, 108, 7, "     0 ";       # uid
        substr $block, 116, 7, "     0 ";       # gid

        # size
        $tar{'size'} =~ s/^0+//;
        substr $block, 124, 12, "            ";
        substr  $block,
                135 - length $tar{'size'},
                length $tar{'size'},
                $tar{'size'};

        # mtime
        $tar{'mtime'} =~ s/^0+//;
        substr $block, 136, 12, "            ";
        substr  $block,
                147 - length $tar{'mtime'},
                length $tar{'mtime'},
                $tar{'mtime'};

        substr $block, 257, 5, "ustar";         # magic
        substr $block, 329, 7, "     0 ";       # devmajor
        substr $block, 337, 7, "     0 ";       # devminor

        if (oct $tar{'size'} != 0) {
                $blknum += (oct $tar{'size'}) / $blocksize;
                ++$blknum if (oct $tar{'size'}) % $blocksize;
        }
        ++$blknum;

        return checksum $block;
}
        

for (my $i = 0; read(STDIN, $block, $blocksize); ++$i) {
        if ($blknum == $i) {
                # if header block, convert.
                print parse_block $block;
        } else {
                print $block;
        }
}