]> Dogcows Code - chaz/tar/commitdiff
New file
authorSergey Poznyakoff <gray@gnu.org.ua>
Sat, 16 Jun 2007 12:09:59 +0000 (12:09 +0000)
committerSergey Poznyakoff <gray@gnu.org.ua>
Sat, 16 Jun 2007 12:09:59 +0000 (12:09 +0000)
doc/tar-snapshot-edit.texi [new file with mode: 0644]
scripts/tar-snapshot-edit [new file with mode: 0755]

diff --git a/doc/tar-snapshot-edit.texi b/doc/tar-snapshot-edit.texi
new file mode 100644 (file)
index 0000000..9c01a4a
--- /dev/null
@@ -0,0 +1,58 @@
+@c This is part of the paxutils manual.
+@c Copyright (C) 2007 Free Software Foundation, Inc.
+@c This file is distributed under GFDL 1.1 or any later version
+@c published by the Free Software Foundation.
+
+@cindex Device numbers, changing
+@cindex snapshot files, editing
+@cindex snapshot files, fixing device numbers
+  Sometimes device numbers can change after upgrading your kernel
+version or recofiguring the harvare.  Reportedly this is the case with 
+some newer @i{Linux} kernels, when using @acronym{LVM}.  In majority of
+cases this change is unnoticed by the users.  However, it influences
+@command{tar} incremental backups: the device number is stored in tar
+snapshot files (@pxref{Snapshot Files}) and is used to determine whether
+the file has changed since the last backup.  If the device numbers
+change for some reason, the next backup you run will be a full backup.
+
+@pindex tar-snapshot-edit
+  To minimize the impact in these cases, GNU @command{tar} comes with
+the @command{tar-snapshot-edit} utility for inspecting and updating
+device numbers in snapshot files.  The utility, written by
+Dustin J.@: Mitchell, is available from
+@uref{http://www.gnu.org/@/software/@/tar/@/utils/@/tar-snapshot-edit.html,
+@GNUTAR{} home page}. 
+
+  To obtain the device numbers used in the snapshot file, run 
+
+@smallexample
+$ @kbd{tar-snapshot-edit @var{snapfile}}
+@end smallexample
+
+@noindent
+where @var{snapfile} is the name of the snapshot file (you can supply as many
+files as you wish in a single command line ).
+
+To update all occurrences of the given device number in the file, use
+@option{-r} option.  It takes a single argument of the form
+@samp{@var{olddev}-@var{newdev}},  where @var{olddev} is the device number
+used in the snapshot file, and @var{newdev} is the corresponding new device
+number.  Both numbers may be specified in hex (e.g., @samp{0xfe01}),
+decimal (e.g., @samp{65025}), or as a major:minor number pair (e.g.,
+@samp{254:1}).  To change several device numbers at once, specify them
+in a single comma-separated list, as in
+@option{-r 0x3060-0x4500,0x307-0x4600}.
+
+Before updating the snapshot file, it is a good idea to create a backup
+copy of it.  This is accomplished by @samp{-b} option.  The name of the
+backup file is obtained by appending @samp{~} to the original file name.
+
+An example session:
+@smallexample
+$ @kbd{tar-snapshot-edit /var/backup/snap.a}
+file version 2
+/tmp/snap: Device 0x0306 occurs 634 times.
+$ @kbd{tar-snapshot-edit -b -r 0x0306-0x4500 /var/backup/snap.a}
+file version 2
+@end smallexample
+
diff --git a/scripts/tar-snapshot-edit b/scripts/tar-snapshot-edit
new file mode 100755 (executable)
index 0000000..81b4b2b
--- /dev/null
@@ -0,0 +1,328 @@
+#! /usr/bin/perl -w
+# Display and edit the 'dev' field in tar's snapshots
+# Copyright (C) 2007 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# Author: Dustin J. Mitchell <dustin@zmanda.com>
+#
+# This script is capable of replacing values in the 'dev' field of an
+# incremental backup 'snapshot' file.  This is useful when the device
+# used to store files in a tar archive changes, without the files
+# themselves changing.  This may happen when, for example, a device
+# driver changes major or minor numbers.
+
+use Getopt::Std;
+
+## reading
+
+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 "file version $file_version\n";
+
+    if ($file_version == 0) {
+       return read_incr_db_0($file, $header_str);
+    } elsif ($file_version == 1) {
+       return read_incr_db_1($file);
+    } elsif ($file_version == 2) {
+       return read_incr_db_2($file);
+    } 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 = ''; # not present in file format 0
+
+    my @dirs;
+
+    while (<$file>) {
+       /^([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
+
+       push @dirs, { dev=>$1,
+                     ino=>$2,
+                     name=>$3 };
+    }
+
+    close($file);
+
+    # file version, timestamp, timestamp, dir list
+    return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
+}
+
+sub read_incr_db_1 ($) {
+    my $file = shift;
+
+    my $timestamp = <$file>; # "sec nsec"
+    my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
+
+    my @dirs;
+
+    while (<$file>) {
+       /^([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
+
+       push @dirs, { timestamp_sec=>$1,
+                     timestamp_nsec=>$2,
+                     dev=>$3,
+                     ino=>$4,
+                     name=>$5 };
+    }
+
+    close($file);
+
+    # file version, timestamp, timestamp, dir list
+    return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
+}
+
+sub read_incr_db_2 ($) {
+    my $file = shift;
+
+    $/="\0"; # $INPUT_RECORD_SEPARATOR
+    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>;
+
+       # get rid of trailing NULs
+       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"; # reset to normal
+
+    # file version, timestamp, timestamp, dir list
+    return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
+}
+
+## display
+
+sub show_device_counts ($$) {
+    my $info = shift;
+    my $filename = shift;
+    my %devices;
+    foreach my $dir (@{${@$info}[3]}) {
+       my $dev = ${%$dir}{'dev'};
+       $devices{$dev}++;
+    }
+
+    foreach $dev (sort keys %devices) {
+       printf "$filename: Device 0x%04x occurs $devices{$dev} times.\n", $dev;
+    }
+}
+
+## editing
+
+sub replace_device_number ($@) {
+    my $info = shift(@_);
+    my @repl = @_;
+
+    foreach my $dir (@{${@$info}[3]}) {
+        foreach $x (@repl) {
+           if (${%$dir}{'dev'} eq $$x[0]) {
+               ${%$dir}{'dev'} = $$x[1];
+                last;
+            }
+       }
+    }
+}
+
+## writing
+
+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]}) {
+       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 "GNU tar-1.15-1\n";
+
+    my $timestamp_sec = $info->[1];
+    my $timestamp_nsec = $info->[2];
+    print $file "$timestamp_sec $timestamp_nsec\n";
+
+    foreach my $dir (@{${@$info}[3]}) {
+       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 "GNU tar-1.16-2\n";
+
+    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";
+    }
+}
+
+## main
+
+sub main {
+    our ($opt_b, $opt_r, $opt_h);
+    getopts('br:h');
+    HELP_MESSAGE() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r));
+
+    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)];
+       }
+    }
+
+    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);
+       } else {
+           show_device_counts($info, $snapfile);
+       }
+    }
+}
+
+sub HELP_MESSAGE {
+    print "Usage: tar-snapshot-edit.pl [-r 'DEV1-DEV2[,DEV3-DEV4...]' [-b]] SNAPFILE [SNAPFILE [..]]\n";
+    print "\n";
+    print "  Without -r, summarize the 'device' values in each SNAPFILE.\n";
+    print "\n";
+    print "  With -r, replace occurrences of DEV1 with DEV2 in each SNAPFILE.\n";
+    print "  DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,\n";
+    print "  65025), or MAJ:MIN (e.g., 254:1).  To replace multiple occurrences,\n";
+    print "  separate them with commas.  If -b is also specified, backup\n";
+    print "  files (ending with '~') will be created.\n";
+    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
This page took 0.030732 seconds and 4 git commands to generate.