123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- use Getopt::Std;
- use Config;
- my %snapshot_field_ranges;
- sub read_incr_db ($) {
- my $filename = shift;
- open(my $file, "<$filename") || die "Could not open '$filename' for reading";
- my $header_str = <$file>;
- my $file_version;
- if ($header_str =~ /^GNU tar-[^-]*-([0-9]+)\n$/) {
- $file_version = $1+0;
- } else {
- $file_version = 0;
- }
- print "\nFile: $filename\n";
- print " Detected snapshot file version: $file_version\n\n";
- if ($file_version == 0) {
- return read_incr_db_0($file, $header_str);
- } elsif ($file_version == 1) {
- return read_incr_db_1($file, $header_str);
- } elsif ($file_version == 2) {
- return read_incr_db_2($file, $header_str);
- } else {
- die "Unrecognized snapshot version in header '$header_str'";
- }
- }
- sub read_incr_db_0 ($$) {
- my $file = shift;
- my $header_str = shift;
- my $hdr_timestamp_sec = $header_str;
- chop $hdr_timestamp_sec;
- my $hdr_timestamp_nsec = '';
- my $nfs;
- my @dirs;
- while (<$file>) {
- /^(\+?)([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
- if ( $1 eq "+" ) {
- $nfs="1";
- } else {
- $nfs="0";
- }
- push @dirs, { nfs=>$nfs,
- dev=>$2,
- ino=>$3,
- name=>$4 };
- }
- close($file);
-
- return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs, ""];
- }
- sub read_incr_db_1 ($$) {
- my $file = shift;
- my $header_str = shift;
- my $timestamp = <$file>;
- my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
- my $nfs;
- my @dirs;
- while (<$file>) {
- /^(\+?)([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
- if ( $1 eq "+" ) {
- $nfs="1";
- } else {
- $nfs="0";
- }
- push @dirs, { nfs=>$nfs,
- timestamp_sec=>$2,
- timestamp_nsec=>$3,
- dev=>$4,
- ino=>$5,
- name=>$6 };
- }
- close($file);
-
- return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs, $header_str ];
- }
- sub read_incr_db_2 ($$) {
- my $file = shift;
- my $header_str = shift;
- $/="\0";
- my $hdr_timestamp_sec = <$file>;
- chop $hdr_timestamp_sec;
- my $hdr_timestamp_nsec = <$file>;
- chop $hdr_timestamp_nsec;
- my @dirs;
- while (1) {
- last if eof($file);
- my $nfs = <$file>;
- my $timestamp_sec = <$file>;
- my $timestamp_nsec = <$file>;
- my $dev = <$file>;
- my $ino = <$file>;
- my $name = <$file>;
-
- chop $nfs;
- chop $timestamp_sec;
- chop $timestamp_nsec;
- chop $dev;
- chop $ino;
- chop $name;
- my @dirents;
- while (my $dirent = <$file>) {
- chop $dirent;
- push @dirents, $dirent;
- last if ($dirent eq "");
- }
- die "missing terminator" unless (<$file> eq "\0");
- push @dirs, { nfs=>$nfs,
- timestamp_sec=>$timestamp_sec,
- timestamp_nsec=>$timestamp_nsec,
- dev=>$dev,
- ino=>$ino,
- name=>$name,
- dirents=>\@dirents };
- }
- close($file);
- $/ = "\n";
-
- return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs, $header_str];
- }
- sub show_device_counts ($) {
- my $info = shift;
- my %devices;
- foreach my $dir (@{$info->[3]}) {
- my $dev = $dir->{'dev'};
- $devices{$dev}++;
- }
- my $devstr;
- foreach $dev (sort {$a <=> $b} keys %devices) {
- $devstr = sprintf ("0x%04x", $dev);
- if ( $dev > 0xffffffff or $dev < 0 or hex($devstr) != $dev ) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $devstr = $dev;
- }
- printf " Device %s occurs $devices{$dev} times.\n", $devstr;
- }
- }
-
- sub choose_architecture ($) {
- my $opt_a = shift;
- my $arch = $opt_a ? $opt_a : $Config{'archname'};
-
-
- %iX86_linux_field_ranges = (
- timestamp_sec => [ -2147483648, 2147483647 ],
- timestamp_nsec => [ 0, 999999999 ],
- nfs => [ 0, 1 ],
- dev => [ 0, 18446744073709551615 ],
- ino => [ 0, 4294967295 ],
- );
- if ( $arch =~ m/^i[\dxX]86-linux/i ) {
- %snapshot_field_ranges = %iX86_linux_field_ranges;
- print "Checking snapshot field values using \"iX86-linux\" ranges.\n\n";
- } elsif ( $arch =~ m/^x86_64-linux/i ) {
- %snapshot_field_ranges = (
- timestamp_sec => [ -9223372036854775808, 9223372036854775807 ],
- timestamp_nsec => [ 0, 999999999 ],
- nfs => [ 0, 1 ],
- dev => [ 0, 18446744073709551615 ],
- ino => [ 0, 18446744073709551615 ],
- );
- print "Checking snapshot field values using \"x86_64-linux\" ranges.\n\n";
- } elsif ( $arch =~ m/^IA64.ARCHREV_0/i ) {
-
- %snapshot_field_ranges = (
- timestamp_sec => [ -2147483648, 2147483647 ],
- timestamp_nsec => [ 0, 999999999 ],
- nfs => [ 0, 1 ],
- dev => [ -2147483648, 2147483647 ],
- ino => [ 0, 4294967295 ],
- );
- print "Checking snapshot field values using \"IA64.ARCHREV_0\" (HP/UX) ranges.\n\n";
- } else {
- %snapshot_field_ranges = %iX86_linux_field_ranges;
- print "Unrecognized architecture \"$arch\"; defaulting to \"iX86-linux\".\n";
- print "(Use -a option to override.)\n" unless $opt_a;
- print "\n";
- }
- if ( ref(1) ne "" ) {
- print "(\"bignum\" mode is in effect; skipping 64-bit-integer check.)\n\n"
- } else {
-
- my $maxmax = 0;
- for $v (values %snapshot_field_ranges ) {
- $maxmax = $v->[1] if ($v->[1] > $maxmax);
- }
-
-
-
-
-
-
-
-
-
- if ( ~0 < $maxmax ) {
- print <<EOF
- Note: this version of Perl uses 32-bit integers, which means that it
- will switch to using floating-point numbers when checking the ranges
- for 64-bit snapshot-file fields. This normally will work fine, but
- might fail to detect cases where the value in the input field value is
- only slightly out of range. (For example, a "9223372036854775808"
- might not be recognized as being larger than 9223372036854775807.)
- If you suspect you are experiencing this problem, you can try running
- the program using the "-Mbignum" option, as in
- \$ perl $0 -Mbignum -c [FILES]
- (but doing so will make the program run *much* slower).
- EOF
- }
- }
-
- }
- sub validate_integer_field ($$) {
- my $field_value = shift;
- my $field_name = shift;
- my ($min, $max) = @{$snapshot_field_ranges{$field_name}};
- my $msg = "";
- if ( not $field_value =~ /^-?\d+$/ ) {
- $msg = " $field_name value contains invalid characters: \"$field_value\"\n";
- } else {
- if ( $field_value < $min ) {
- $msg = " $field_name value too low: \"$field_value\" < $min \n";
- } elsif ( $field_value > $max ) {
- $msg = " $field_name value too high: \"$field_value\" > $max \n";
- }
- }
- return $msg;
- }
- sub check_field_values ($) {
- my $info = shift;
- my $msg;
- my $error_found = 0;
- print " Checking field values in snapshot file...\n";
- $snapver = $info->[0];
- $msg = "";
- $msg .= validate_integer_field($info->[1], 'timestamp_sec');
- if ($snapver >= 1) {
- $msg .= validate_integer_field($info->[2], 'timestamp_nsec');
- }
- if ( $msg ne "" ) {
- $error_found = 1;
- print "\n snapshot file header:\n";
- print $msg;
- }
- foreach my $dir (@{$info->[3]}) {
- $msg = "";
- $msg .= validate_integer_field($dir->{'nfs'}, 'nfs');
- if ($snapver >= 1) {
- $msg .= validate_integer_field($dir->{'timestamp_sec'}, 'timestamp_sec');
- $msg .= validate_integer_field($dir->{'timestamp_nsec'}, 'timestamp_nsec');
- }
- $msg .= validate_integer_field($dir->{'dev'}, 'dev');
- $msg .= validate_integer_field($dir->{'ino'}, 'ino');
- if ( $msg ne "" ) {
- $error_found = 1;
- print "\n directory: $dir->{'name'}\n";
- print $msg;
- }
- }
- print "\n Snapshot field value check complete" ,
- $error_found ? "" : ", no errors found" ,
- ".\n";
- }
- sub replace_device_number ($@) {
- my $info = shift(@_);
- my @repl = @_;
- my $count = 0;
- foreach my $dir (@{$info->[3]}) {
- foreach $x (@repl) {
- if ($dir->{'dev'} eq $$x[0]) {
- $dir->{'dev'} = $$x[1];
- $count++;
- last;
- }
- }
- }
- print " Updated $count records.\n"
- }
- sub write_incr_db ($$) {
- my $info = shift;
- my $filename = shift;
- my $file_version = $$info[0];
- open($file, ">$filename") || die "Could not open '$filename' for writing";
- if ($file_version == 0) {
- write_incr_db_0($info, $file);
- } elsif ($file_version == 1) {
- write_incr_db_1($info, $file);
- } elsif ($file_version == 2) {
- write_incr_db_2($info, $file);
- } else {
- die "Unknown file version $file_version.";
- }
- close($file);
- }
- sub write_incr_db_0 ($$) {
- my $info = shift;
- my $file = shift;
- my $timestamp_sec = $info->[1];
- print $file "$timestamp_sec\n";
- foreach my $dir (@{$info->[3]}) {
- if ($dir->{'nfs'}) {
- print $file '+'
- }
- print $file "$dir->{'dev'} ";
- print $file "$dir->{'ino'} ";
- print $file "$dir->{'name'}\n";
- }
- }
- sub write_incr_db_1 ($$) {
- my $info = shift;
- my $file = shift;
- print $file $info->[4];
- my $timestamp_sec = $info->[1];
- my $timestamp_nsec = $info->[2];
- print $file "$timestamp_sec $timestamp_nsec\n";
- foreach my $dir (@{$info->[3]}) {
- if ($dir->{'nfs'}) {
- print $file '+'
- }
- print $file "$dir->{'timestamp_sec'} ";
- print $file "$dir->{'timestamp_nsec'} ";
- print $file "$dir->{'dev'} ";
- print $file "$dir->{'ino'} ";
- print $file "$dir->{'name'}\n";
- }
- }
- sub write_incr_db_2 ($$) {
- my $info = shift;
- my $file = shift;
- print $file $info->[4];
- my $timestamp_sec = $info->[1];
- my $timestamp_nsec = $info->[2];
- print $file $timestamp_sec . "\0";
- print $file $timestamp_nsec . "\0";
- foreach my $dir (@{$info->[3]}) {
- print $file $dir->{'nfs'} . "\0";
- print $file $dir->{'timestamp_sec'} . "\0";
- print $file $dir->{'timestamp_nsec'} . "\0";
- print $file $dir->{'dev'} . "\0";
- print $file $dir->{'ino'} . "\0";
- print $file $dir->{'name'} . "\0";
- foreach my $dirent (@{$dir->{'dirents'}}) {
- print $file $dirent . "\0";
- }
- print $file "\0";
- }
- }
- sub main {
- our ($opt_b, $opt_r, $opt_h, $opt_c, $opt_a);
- getopts('br:hca:');
- HELP_MESSAGE() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r) ||
- ($opt_a && !$opt_c) || ($opt_r && $opt_c) );
- my @repl;
- if ($opt_r) {
- foreach my $spec (split(/,/, $opt_r)) {
- ($spec =~ /^([^-]+)-([^-]+)/) || die "Invalid replacement specification '$opt_r'";
- push @repl, [interpret_dev($1), interpret_dev($2)];
- }
- }
- choose_architecture($opt_a) if ($opt_c);
- foreach my $snapfile (@ARGV) {
- my $info = read_incr_db($snapfile);
- if ($opt_r) {
- if ($opt_b) {
- rename($snapfile, $snapfile . "~") || die "Could not rename '$snapfile' to backup";
- }
- replace_device_number($info, @repl);
- write_incr_db($info, $snapfile);
- } elsif ($opt_c) {
- check_field_values($info);
- } else {
- show_device_counts($info);
- }
- }
- }
- sub HELP_MESSAGE {
- print <<EOF;
- Usage:
- tar-snapshot-edit SNAPFILE [SNAPFILE [...]]
- tar-snapshot-edit -r 'DEV1-DEV2[,DEV3-DEV4...]' [-b] SNAPFILE [SNAPFILE [...]]
- tar-snapshot-edit -c [-aARCH] SNAPFILE [SNAPFILE [...]]
- With no options specified: print a summary of the 'device' values
- found in each SNAPFILE.
- With -r: replace occurrences of DEV1 with DEV2 in each SNAPFILE.
- DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,
- 65025), or MAJ:MIN (e.g., 254:1). To replace multiple occurrences,
- separate them with commas. If -b is also specified, backup files
- (ending with '~') will be created.
- With -c: Check the field values in each SNAPFILE and print warning
- messages if any invalid values are found. (An invalid value is one
- that would cause \"tar\" to abort with an error message such as
- Unexpected field value in snapshot file
- Numerical result out of range
- or
- Invalid argument
- as it processed the snapshot file.)
- Normally the program automatically chooses the valid ranges for
- the fields based on the current system's architecture, but the
- -a option can be used to override the selection, e.g. in order
- to validate a snapshot file generated on a some other system.
- (Currently only three architectures are supported, "iX86-linux",
- "x86_64-linux", and "IA64.ARCHREV_0" [HP/UX running on Itanium/ia64],
- and if the current system isn't recognized, then the iX86-linux
- values are used by default.)
- EOF
- exit 1;
- }
- sub interpret_dev ($) {
- my $dev = shift;
- if ($dev =~ /^([0-9]+):([0-9]+)$/) {
- return $1 * 256 + $2;
- } elsif ($dev =~ /^0x[0-9a-fA-F]+$/) {
- return oct $dev;
- } elsif ($dev =~ /^[0-9]+$/) {
- return $dev+0;
- } else {
- die "Invalid device specification '$dev'";
- }
- }
- main
|