2 # Display and edit the 'dev' field in tar's snapshots
3 # Copyright (C) 2007 Free Software Foundation, Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3, or (at your option)
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 # Author: Dustin J. Mitchell <dustin@zmanda.com>
22 # This script is capable of replacing values in the 'dev' field of an
23 # incremental backup 'snapshot' file. This is useful when the device
24 # used to store files in a tar archive changes, without the files
25 # themselves changing. This may happen when, for example, a device
26 # driver changes major or minor numbers.
32 sub read_incr_db
($) {
34 open(my $file, "<$filename") || die "Could not open '$filename' for reading";
36 my $header_str = <$file>;
38 if ($header_str =~ /^GNU tar-[^-]*-([0-9]+)\n$/) {
44 print "file version $file_version\n";
46 if ($file_version == 0) {
47 return read_incr_db_0
($file, $header_str);
48 } elsif ($file_version == 1) {
49 return read_incr_db_1
($file);
50 } elsif ($file_version == 2) {
51 return read_incr_db_2
($file);
53 die "Unrecognized snapshot version in header '$header_str'";
57 sub read_incr_db_0
($$) {
59 my $header_str = shift;
61 my $hdr_timestamp_sec = $header_str;
62 chop $hdr_timestamp_sec;
63 my $hdr_timestamp_nsec = ''; # not present in file format 0
68 /^([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
70 push @dirs, { dev
=>$1,
77 # file version, timestamp, timestamp, dir list
78 return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs ];
81 sub read_incr_db_1
($) {
84 my $timestamp = <$file>; # "sec nsec"
85 my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
90 /^([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
92 push @dirs, { timestamp_sec
=>$1,
101 # file version, timestamp, timestamp, dir list
102 return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs ];
105 sub read_incr_db_2
($) {
108 $/="\0"; # $INPUT_RECORD_SEPARATOR
109 my $hdr_timestamp_sec = <$file>;
110 chop $hdr_timestamp_sec;
111 my $hdr_timestamp_nsec = <$file>;
112 chop $hdr_timestamp_nsec;
119 my $timestamp_sec = <$file>;
120 my $timestamp_nsec = <$file>;
125 # get rid of trailing NULs
128 chop $timestamp_nsec;
134 while (my $dirent = <$file>) {
136 push @dirents, $dirent;
137 last if ($dirent eq "");
139 die "missing terminator" unless (<$file> eq "\0");
141 push @dirs, { nfs
=>$nfs,
142 timestamp_sec
=>$timestamp_sec,
143 timestamp_nsec
=>$timestamp_nsec,
147 dirents
=>\
@dirents };
151 $/ = "\n"; # reset to normal
153 # file version, timestamp, timestamp, dir list
154 return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs ];
159 sub show_device_counts
($$) {
161 my $filename = shift;
163 foreach my $dir (@
{${@
$info}[3]}) {
164 my $dev = ${%$dir}{'dev'};
168 foreach $dev (sort keys %devices) {
169 printf "$filename: Device 0x%04x occurs $devices{$dev} times.\n", $dev;
175 sub replace_device_number
($@
) {
176 my $info = shift(@_);
179 foreach my $dir (@
{${@
$info}[3]}) {
181 if (${%$dir}{'dev'} eq $$x[0]) {
182 ${%$dir}{'dev'} = $$x[1];
191 sub write_incr_db
($$) {
193 my $filename = shift;
194 my $file_version = $$info[0];
196 open($file, ">$filename") || die "Could not open '$filename' for writing";
198 if ($file_version == 0) {
199 write_incr_db_0
($info, $file);
200 } elsif ($file_version == 1) {
201 write_incr_db_1
($info, $file);
202 } elsif ($file_version == 2) {
203 write_incr_db_2
($info, $file);
205 die "Unknown file version $file_version.";
211 sub write_incr_db_0
($$) {
215 my $timestamp_sec = $info->[1];
216 print $file "$timestamp_sec\n";
218 foreach my $dir (@
{${@
$info}[3]}) {
219 print $file "${%$dir}{'dev'} ";
220 print $file "${%$dir}{'ino'} ";
221 print $file "${%$dir}{'name'}\n";
226 sub write_incr_db_1
($$) {
230 print $file "GNU tar-1.15-1\n";
232 my $timestamp_sec = $info->[1];
233 my $timestamp_nsec = $info->[2];
234 print $file "$timestamp_sec $timestamp_nsec\n";
236 foreach my $dir (@
{${@
$info}[3]}) {
237 print $file "${%$dir}{'timestamp_sec'} ";
238 print $file "${%$dir}{'timestamp_nsec'} ";
239 print $file "${%$dir}{'dev'} ";
240 print $file "${%$dir}{'ino'} ";
241 print $file "${%$dir}{'name'}\n";
246 sub write_incr_db_2
($$) {
250 print $file "GNU tar-1.16-2\n";
252 my $timestamp_sec = $info->[1];
253 my $timestamp_nsec = $info->[2];
254 print $file $timestamp_sec . "\0";
255 print $file $timestamp_nsec . "\0";
257 foreach my $dir (@
{${@
$info}[3]}) {
258 print $file ${%$dir}{'nfs'} . "\0";
259 print $file ${%$dir}{'timestamp_sec'} . "\0";
260 print $file ${%$dir}{'timestamp_nsec'} . "\0";
261 print $file ${%$dir}{'dev'} . "\0";
262 print $file ${%$dir}{'ino'} . "\0";
263 print $file ${%$dir}{'name'} . "\0";
264 foreach my $dirent (@
{${%$dir}{'dirents'}}) {
265 print $file $dirent . "\0";
274 our ($opt_b, $opt_r, $opt_h);
276 HELP_MESSAGE
() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r));
280 foreach my $spec (split(/,/, $opt_r)) {
281 ($spec =~ /^([^-]+)-([^-]+)/) || die "Invalid replacement specification '$opt_r'";
282 push @repl, [interpret_dev
($1), interpret_dev
($2)];
286 foreach my $snapfile (@ARGV) {
287 my $info = read_incr_db
($snapfile);
290 rename($snapfile, $snapfile . "~") || die "Could not rename '$snapfile' to backup";
293 replace_device_number
($info, @repl);
294 write_incr_db
($info, $snapfile);
296 show_device_counts
($info, $snapfile);
302 print "Usage: tar-snapshot-edit.pl [-r 'DEV1-DEV2[,DEV3-DEV4...]' [-b]] SNAPFILE [SNAPFILE [..]]\n";
304 print " Without -r, summarize the 'device' values in each SNAPFILE.\n";
306 print " With -r, replace occurrences of DEV1 with DEV2 in each SNAPFILE.\n";
307 print " DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,\n";
308 print " 65025), or MAJ:MIN (e.g., 254:1). To replace multiple occurrences,\n";
309 print " separate them with commas. If -b is also specified, backup\n";
310 print " files (ending with '~') will be created.\n";
314 sub interpret_dev
($) {
317 if ($dev =~ /^([0-9]+):([0-9]+)$/) {
318 return $1 * 256 + $2;
319 } elsif ($dev =~ /^0x[0-9a-fA-F]+$/) {
321 } elsif ($dev =~ /^[0-9]+$/) {
324 die "Invalid device specification '$dev'";