]>
Dogcows Code - chaz/tar/blob - scripts/tar-snapshot-edit
92741d3a03203b8127377fa8ffa6fd73623ea5cd
2 # Display and edit the 'dev' field in tar's snapshots
3 # Copyright (C) 2007,2011 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 2, 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
23 # This script is capable of replacing values in the 'dev' field of an
24 # incremental backup 'snapshot' file. This is useful when the device
25 # used to store files in a tar archive changes, without the files
26 # themselves changing. This may happen when, for example, a device
27 # driver changes major or minor numbers.
29 # It can also run a check on all the field values found in the
30 # snapshot file, printing out a detailed message when it finds values
31 # that would cause an "Unexpected field value in snapshot file" error
32 # if tar were run using that snapshot file as input. (See the
33 # comments included in the definition of the check_field_values
34 # routine for more detailed information regarding these checks.)
38 # Author: Dustin J. Mitchell <dustin@zmanda.com>
40 # Modified Aug 25, 2011 by Nathan Stratton Treadway <nathanst AT ontko.com>:
41 # * update Perl syntax to work correctly with more recent versions of
42 # Perl. (The original code worked with in the v5.8 timeframe but
43 # not with Perl v5.10.1 and later.)
44 # * added a "-c" option to check the snapshot file for invalid field values.
45 # * handle NFS indicator character ("+") in version 0 and 1 files
46 # * preserve the original header/version line when editing version 1
48 # * tweak output formatting
56 sub read_incr_db
($) {
58 open(my $file, "<$filename") || die "Could not open '$filename' for reading";
60 my $header_str = <$file>;
62 if ($header_str =~ /^GNU tar-[^-]*-([0-9]+)\n$/) {
68 print "\nFile: $filename\n";
69 print " Detected snapshot file version: $file_version\n\n";
71 if ($file_version == 0) {
72 return read_incr_db_0
($file, $header_str);
73 } elsif ($file_version == 1) {
74 return read_incr_db_1
($file, $header_str);
75 } elsif ($file_version == 2) {
76 return read_incr_db_2
($file, $header_str);
78 die "Unrecognized snapshot version in header '$header_str'";
82 sub read_incr_db_0
($$) {
84 my $header_str = shift;
86 my $hdr_timestamp_sec = $header_str;
87 chop $hdr_timestamp_sec;
88 my $hdr_timestamp_nsec = ''; # not present in file format 0
94 /^(\+?)([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
101 push @dirs, { nfs
=>$nfs,
109 # file version, timestamp, timestamp, dir list, file header line
110 return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, ""];
113 sub read_incr_db_1
($$) {
115 my $header_str = shift;
118 my $timestamp = <$file>; # "sec nsec"
119 my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
125 /^(\+?)([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
133 push @dirs, { nfs
=>$nfs,
143 # file version, timestamp, timestamp, dir list, file header line
144 return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, $header_str ];
147 sub read_incr_db_2
($$) {
149 my $header_str = shift;
151 $/="\0"; # $INPUT_RECORD_SEPARATOR
152 my $hdr_timestamp_sec = <$file>;
153 chop $hdr_timestamp_sec;
154 my $hdr_timestamp_nsec = <$file>;
155 chop $hdr_timestamp_nsec;
162 my $timestamp_sec = <$file>;
163 my $timestamp_nsec = <$file>;
168 # get rid of trailing NULs
171 chop $timestamp_nsec;
177 while (my $dirent = <$file>) {
179 push @dirents, $dirent;
180 last if ($dirent eq "");
182 die "missing terminator" unless (<$file> eq "\0");
184 push @dirs, { nfs
=>$nfs,
185 timestamp_sec
=>$timestamp_sec,
186 timestamp_nsec
=>$timestamp_nsec,
190 dirents
=>\
@dirents };
194 $/ = "\n"; # reset to normal
196 # file version, timestamp, timestamp, dir list, file header line
197 return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, $header_str];
202 sub show_device_counts
($) {
205 foreach my $dir (@{$info->[3]}) {
206 my $dev = $dir->{'dev'};
210 foreach $dev (sort {$a <=> $b} keys %devices) {
211 printf " Device 0x%04x occurs $devices{$dev} times.\n", $dev;
215 ## check field values
217 # returns a warning message if $field isn't a valid string representation
218 # of an integer, or if the resulting integer is out of the specified range
219 sub validate_integer_field
($$$$) {
221 my $field_name = shift;
227 if ( not $field =~ /^-?\d+$/ ) {
228 $msg = " $field_name value contains invalid characters: \"$field\"\n";
230 if ( $field < $min ) {
231 $msg = " $field_name value too low: \"$field\" < $min \n";
232 } elsif ( $field > $max ) {
233 $msg = " $field_name value too high: \"$field\" > $max \n";
240 # This routine loops through each directory entry in the $info data
241 # structure and prints a warning message if tar would abort with an
242 # "Unexpected field value in snapshot file" error upon reading this
245 # (Note that this specific error message was introduced along with the
246 # change to snapshot file format "2", starting with tar v1.16 [or,
247 # more precisely, v1.15.91].)
249 # The checks here are intended to match those found in the incremen.c
250 # source file (as of tar v1.16.1).
252 # In that code, the checks are done against pre-processor expressions,
253 # as defined in the C header files at compile time. In the routine
254 # below, a Perl variable is created for each expression used as part of
255 # one of these checks, assigned the value of the related pre-processor
256 # expression as found on a Linux 2.6.8/i386 system.
258 # It seems likely that these settings will catch most invalid
259 # field values found in actual snapshot files on all systems. However,
260 # if "tar" is erroring out on a snapshot file that this check routine
261 # does not complain about, that probably indicates that the values
262 # below need to be adjusted to match those used by "tar" in that
263 # particular environment.
265 # (Note: the checks here are taken from the code that processes
266 # version 2 snapshot files, but to keep things simple we apply those
267 # same checks to files having earlier versions -- but only for
268 # the fields that actually exist in those input files.)
270 sub check_field_values
($) {
273 # set up a variable with the value of each pre-processor
274 # expression used for field-value checks in incremen.c
275 # (these values here are from a Linux 2.6.8/i386 system)
276 my $BILLION = 1000000000; # BILLION
277 my $MIN_TIME_T = -2147483648; # TYPE_MINIMUM(time_t)
278 my $MAX_TIME_T = 2147483647; # TYPE_MAXIUMUM(time_t)
279 my $MAX_DEV_T = 4294967295; # TYPE_MAXIUMUM(dev_t)
280 my $MAX_INO_T = 4294967295; # TYPE_MAXIUMUM(ino_t)
286 print " Checking field values in snapshot file...\n";
288 $snapver = $info->[0];
291 $msg .= validate_integer_field
($info->[1],
292 'timestamp_sec', $MIN_TIME_T, $MAX_TIME_T);
294 $msg .= validate_integer_field
($info->[2],
295 'timestamp_nsec', 0, $BILLION-1);
299 print "\n shapshot file header:\n";
304 foreach my $dir (@{$info->[3]}) {
308 $msg .= validate_integer_field
($dir->{'nfs'}, 'nfs', 0, 1);
310 $msg .= validate_integer_field
($dir->{'timestamp_sec'},
311 'timestamp_sec', $MIN_TIME_T, $MAX_TIME_T);
312 $msg .= validate_integer_field
($dir->{'timestamp_nsec'},
313 'timestamp_nsec', 0, $BILLION-1);
315 $msg .= validate_integer_field
($dir->{'dev'}, 'dev', 0, $MAX_DEV_T);
316 $msg .= validate_integer_field
($dir->{'ino'}, 'ino', 0, $MAX_INO_T);
320 print "\n directory: $dir->{'name'}\n";
325 print "\n Snapshot field value check complete" ,
326 $error_found ? "" : ", no errors found" ,
332 sub replace_device_number
($@) {
333 my $info = shift(@_);
338 foreach my $dir (@{$info->[3]}) {
340 if ($dir->{'dev'} eq $$x[0]) {
341 $dir->{'dev'} = $$x[1];
347 print " Updated $count records.\n"
352 sub write_incr_db
($$) {
354 my $filename = shift;
355 my $file_version = $$info[0];
357 open($file, ">$filename") || die "Could not open '$filename' for writing";
359 if ($file_version == 0) {
360 write_incr_db_0
($info, $file);
361 } elsif ($file_version == 1) {
362 write_incr_db_1
($info, $file);
363 } elsif ($file_version == 2) {
364 write_incr_db_2
($info, $file);
366 die "Unknown file version $file_version.";
372 sub write_incr_db_0
($$) {
376 my $timestamp_sec = $info->[1];
377 print $file "$timestamp_sec\n";
379 foreach my $dir (@{$info->[3]}) {
383 print $file "$dir->{'dev'} ";
384 print $file "$dir->{'ino'} ";
385 print $file "$dir->{'name'}\n";
390 sub write_incr_db_1
($$) {
394 print $file $info->[4];
396 my $timestamp_sec = $info->[1];
397 my $timestamp_nsec = $info->[2];
398 print $file "$timestamp_sec $timestamp_nsec\n";
400 foreach my $dir (@{$info->[3]}) {
404 print $file "$dir->{'timestamp_sec'} ";
405 print $file "$dir->{'timestamp_nsec'} ";
406 print $file "$dir->{'dev'} ";
407 print $file "$dir->{'ino'} ";
408 print $file "$dir->{'name'}\n";
413 sub write_incr_db_2
($$) {
417 print $file $info->[4];
419 my $timestamp_sec = $info->[1];
420 my $timestamp_nsec = $info->[2];
421 print $file $timestamp_sec . "\0";
422 print $file $timestamp_nsec . "\0";
424 foreach my $dir (@{$info->[3]}) {
425 print $file $dir->{'nfs'} . "\0";
426 print $file $dir->{'timestamp_sec'} . "\0";
427 print $file $dir->{'timestamp_nsec'} . "\0";
428 print $file $dir->{'dev'} . "\0";
429 print $file $dir->{'ino'} . "\0";
430 print $file $dir->{'name'} . "\0";
431 foreach my $dirent (@{$dir->{'dirents'}}) {
432 print $file $dirent . "\0";
441 our ($opt_b, $opt_r, $opt_h, $opt_c);
443 HELP_MESSAGE
() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r) ||
444 ($opt_r && $opt_c) );
448 foreach my $spec (split(/,/, $opt_r)) {
449 ($spec =~ /^([^-]+)-([^-]+)/) || die "Invalid replacement specification '$opt_r'";
450 push @repl, [interpret_dev
($1), interpret_dev
($2)];
454 foreach my $snapfile (@ARGV) {
455 my $info = read_incr_db
($snapfile);
458 rename($snapfile, $snapfile . "~") || die "Could not rename '$snapfile' to backup";
461 replace_device_number
($info, @repl);
462 write_incr_db
($info, $snapfile);
464 check_field_values
($info);
466 show_device_counts
($info);
475 tar-snapshot-edit SNAPFILE [SNAPFILE [...]]
476 tar-snapshot-edit -r 'DEV1-DEV2[,DEV3-DEV4...]' [-b] SNAPFILE [SNAPFILE [...]]
477 tar-snapshot-edit -c SNAPFILE [SNAPFILE [...]]
479 With no options specified: print a summary of the 'device' values
480 found in each SNAPFILE.
482 With -r: replace occurrences of DEV1 with DEV2 in each SNAPFILE.
483 DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,
484 65025), or MAJ:MIN (e.g., 254:1). To replace multiple occurrences,
485 separate them with commas. If -b is also specified, backup files
486 (ending with '~') will be created.
488 With -c: Check the field values in each SNAPFILE and print warning
489 messages if any invalid values are found. (An invalid value is one
490 that would cause \"tar\" to generate an
491 Unexpected field value in snapshot file
492 error message as it processed the snapshot file.)
498 sub interpret_dev
($) {
501 if ($dev =~ /^([0-9]+):([0-9]+)$/) {
502 return $1 * 256 + $2;
503 } elsif ($dev =~ /^0x[0-9a-fA-F]+$/) {
505 } elsif ($dev =~ /^[0-9]+$/) {
508 die "Invalid device specification '$dev'";
This page took 0.070383 seconds and 4 git commands to generate.