#!/usr/bin/perl use strict; use warnings; use Getopt::Long; (my $ME = $0) =~ s|.*/||; (my $VERSION = '$Revision$ ') =~ tr/[0-9].//cd; use constant S_START => 1; use constant S_ADJUST_NULS => 2; use constant S_COPY_REMAINDER => 3; sub usage ($) { my ($exit_code) = @_; my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); if ($exit_code != 0) { print $STREAM "Try `$ME --help' for more information.\n"; } else { print $STREAM < /dev/null /bin/tar --sparse --format=posix -cf x.tar big $ME --field-name=GNU.sparse.numblocks --val=\$(echo 2^32|bc) z.tar # or $ME --field-name=GNU.sparse.size --val=\$(echo 2^32|bc) z.tar /bin/tar tf z.tar OPTIONS: --help display this help and exit --version output version information and exit --field-name=NAME FIXME --value=VAL FIXME EOF } exit $exit_code; } { my $field_string; my $new_val; GetOptions ( help => sub { usage 0 }, version => sub { print "$ME version $VERSION\n"; exit }, 'field-name=s' => \$field_string, 'value=s' => \$new_val, ) or usage 1; @ARGV == 0 or (warn "$ME: too many arguments\n"), usage 1; defined $field_string && defined $new_val or (warn "$ME: you must specify both a field name and a value\n"), usage 1; my $field_name_re = quotemeta $field_string; my $state = S_START; my $n_increase; foreach my $line (<>) { if ($state == S_START) { if ($line !~ /^(|.*\0)((\d+) ($field_name_re)=([\d.,]+))$/) { print $line; next; } my ($prefix, $rest, $line_len, $field_name, $val) = ($1, $2, $3, $4, $5); my $initial_length = length ($rest) + 1; my $new_line; my $n = 0; my $len = 0; while (1) { my $prev_len = $len; $new_line = "$prev_len $field_name=$new_val\n"; $len = length $new_line; $len == $prev_len and last; } $n_increase = length ($new_line) - $initial_length; $state = S_ADJUST_NULS; $line = "$prefix$new_line"; } elsif ($state == S_ADJUST_NULS) { if ($line =~ /\0/) { if (0 <= $n_increase) { # The new value is longer: remove some NULs to compensate. if ($line =~ /\0{$n_increase}/) { $line =~ s///; $state = S_COPY_REMAINDER; } } else { # The new value is shorter: add some NULs to compensate. $line =~ s/\0/"\0" x (1-$n_increase)/e; $state = S_COPY_REMAINDER; } } } print $line; } $state == S_COPY_REMAINDER or die "$ME: no field matches $field_string\n"; }