From 01cf5c53151cb0c4166dbc061551b79c173d13f5 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 16 Jun 2007 12:09:59 +0000 Subject: [PATCH] New file --- doc/tar-snapshot-edit.texi | 58 +++++++ scripts/tar-snapshot-edit | 328 +++++++++++++++++++++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 doc/tar-snapshot-edit.texi create mode 100755 scripts/tar-snapshot-edit diff --git a/doc/tar-snapshot-edit.texi b/doc/tar-snapshot-edit.texi new file mode 100644 index 0000000..9c01a4a --- /dev/null +++ b/doc/tar-snapshot-edit.texi @@ -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 index 0000000..81b4b2b --- /dev/null +++ b/scripts/tar-snapshot-edit @@ -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 +# +# 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 -- 2.45.2