123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- #! /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 3, 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 <[email protected]>
- #
- # 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
|