]>
Dogcows Code - chaz/tar/blob - scripts/tar-snapshot-edit
56c24a1a3008e7bee25927da179220f0c90b5749
2 # Display and edit the 'dev' field in tar's snapshots
3 # Copyright 2007, 2011, 2013 Free Software Foundation, Inc.
5 # This file is part of GNU tar.
7 # GNU tar is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # GNU tar is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
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",
32 # "Numerical result out of range", or "Invalid argument" error
33 # if tar were run using that snapshot file as input. (See the
34 # comments included in the definition of the check_field_values
35 # routine for more detailed information regarding these checks.)
39 # Author: Dustin J. Mitchell <dustin@zmanda.com>
41 # Modified Aug 25, 2011 by Nathan Stratton Treadway <nathanst AT ontko.com>:
42 # * update Perl syntax to work correctly with more recent versions of
43 # Perl. (The original code worked with in the v5.8 timeframe but
44 # not with Perl v5.10.1 and later.)
45 # * added a "-c" option to check the snapshot file for invalid field values.
46 # * handle NFS indicator character ("+") in version 0 and 1 files
47 # * preserve the original header/version line when editing version 1
49 # * tweak output formatting
51 # Modified March 13, 2013 by Nathan Stratton Treadway <nathanst AT ontko.com>:
52 # * configure field ranges used for -c option based on the system
53 # architecture (in response to the December 2012 update to GNU tar
54 # enabling support for systems with signed dev_t values).
55 # * when printing the list of device ids found in the snapshot file
56 # (when run in the default mode), print the raw device id values
57 # instead of the hex-string version in those cases where they
58 # can't be converted successfully.
63 my %snapshot_field_ranges; # used in check_field_values function
67 sub read_incr_db
($) {
69 open(my $file, "<$filename") || die "Could not open '$filename' for reading";
71 my $header_str = <$file>;
73 if ($header_str =~ /^GNU tar-[^-]*-([0-9]+)\n$/) {
79 print "\nFile: $filename\n";
80 print " Detected snapshot file version: $file_version\n\n";
82 if ($file_version == 0) {
83 return read_incr_db_0
($file, $header_str);
84 } elsif ($file_version == 1) {
85 return read_incr_db_1
($file, $header_str);
86 } elsif ($file_version == 2) {
87 return read_incr_db_2
($file, $header_str);
89 die "Unrecognized snapshot version in header '$header_str'";
93 sub read_incr_db_0
($$) {
95 my $header_str = shift;
97 my $hdr_timestamp_sec = $header_str;
98 chop $hdr_timestamp_sec;
99 my $hdr_timestamp_nsec = ''; # not present in file format 0
105 /^(\+?)([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
112 push @dirs, { nfs
=>$nfs,
120 # file version, timestamp, timestamp, dir list, file header line
121 return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, ""];
124 sub read_incr_db_1
($$) {
126 my $header_str = shift;
129 my $timestamp = <$file>; # "sec nsec"
130 my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
136 /^(\+?)([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
144 push @dirs, { nfs
=>$nfs,
154 # file version, timestamp, timestamp, dir list, file header line
155 return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, $header_str ];
158 sub read_incr_db_2
($$) {
160 my $header_str = shift;
162 $/="\0"; # $INPUT_RECORD_SEPARATOR
163 my $hdr_timestamp_sec = <$file>;
164 chop $hdr_timestamp_sec;
165 my $hdr_timestamp_nsec = <$file>;
166 chop $hdr_timestamp_nsec;
173 my $timestamp_sec = <$file>;
174 my $timestamp_nsec = <$file>;
179 # get rid of trailing NULs
182 chop $timestamp_nsec;
188 while (my $dirent = <$file>) {
190 push @dirents, $dirent;
191 last if ($dirent eq "");
193 die "missing terminator" unless (<$file> eq "\0");
195 push @dirs, { nfs
=>$nfs,
196 timestamp_sec
=>$timestamp_sec,
197 timestamp_nsec
=>$timestamp_nsec,
201 dirents
=>\
@dirents };
205 $/ = "\n"; # reset to normal
207 # file version, timestamp, timestamp, dir list, file header line
208 return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \
@dirs, $header_str];
213 sub show_device_counts
($) {
216 foreach my $dir (@{$info->[3]}) {
217 my $dev = $dir->{'dev'};
222 foreach $dev (sort {$a <=> $b} keys %devices) {
223 $devstr = sprintf ("0x%04x", $dev);
224 if ( $dev > 0xffffffff or $dev < 0 or hex($devstr) != $dev ) {
225 # sprintf "%x" will not return a useful value for device ids
226 # that are negative or which overflow the integer size on this
227 # instance of Perl, so we convert the hex string back to a
228 # number, and if it doesn't (numerically) equal the original
229 # device id value, we know the hex conversion hasn't worked.
231 # Unfortunately, since we're running in "-w" mode, Perl will
232 # also print a warning message if the hex() routine is called
233 # on anything larger than "0xffffffff", even in 64-bit Perl
234 # where such values are actually supported... so we have to
235 # avoid calling hex() at all if the device id is too large or
236 # negative. (If it's negative, the conversion to an unsigned
237 # integer for the "%x" specifier will mean the result will
238 # always trigger hex()'s warning on a 64-bit machine.)
240 # These situations don't seem to occur very often, so for now
241 # when they do occur, we simply print the original text value
242 # that was read from the snapshot file; it will look a bit
243 # funny next to the values that do print in hex, but that's
244 # preferable to printing values that aren't actually correct.
247 printf " Device %s occurs $devices{$dev} times.\n", $devstr;
251 ## check field values
253 # initializes the global %snapshot_field_ranges hash, based on the "-a"
254 # command-line option if given, otherwise based on the "archname" of
255 # the current system.
257 # Each value in the hash is a two-element array containing the minimum
258 # and maximum allowed values, respectively, for that field in the snapshot
259 # file. GNU tar's allowed values for each architecture are determined
260 # in the incremen.c source file, where the TYPE_MIN and TYPE_MAX
261 # pre-processor expressions are used to determine the range that can be
262 # expressed by the C data type used for each field; the values in the
263 # array defined below should match those calculations.
265 sub choose_architecture
($) {
268 my $arch = $opt_a ? $opt_a : $Config{'archname'};
270 # These ranges apply to Linux 2.4/2.6 on iX86 systems, but are used
271 # by default on unrecognized/unsupported systems, too.
272 %iX86_linux_field_ranges = (
273 timestamp_sec
=> [ -2147483648, 2147483647 ], # min/max of time_t
274 timestamp_nsec
=> [ 0, 999999999 ], # 0 to BILLION-1
276 dev
=> [ 0, 18446744073709551615 ], # min/max of dev_t
277 ino
=> [ 0, 4294967295 ], # min/max of ino_t
281 if ( $arch =~ m/^i[\dxX]86-linux/i ) {
282 %snapshot_field_ranges = %iX86_linux_field_ranges;
283 print "Checking snapshot field values using \"iX86-linux\" ranges.\n\n";
284 } elsif ( $arch =~ m/^x86_64-linux/i ) {
285 %snapshot_field_ranges = (
286 timestamp_sec
=> [ -9223372036854775808, 9223372036854775807 ],
287 timestamp_nsec
=> [ 0, 999999999 ],
289 dev
=> [ 0, 18446744073709551615 ],
290 ino
=> [ 0, 18446744073709551615 ],
292 print "Checking snapshot field values using \"x86_64-linux\" ranges.\n\n";
293 } elsif ( $arch =~ m/^IA64.ARCHREV_0/i ) {
294 # HP/UX running on Itanium/ia64 architecture
295 %snapshot_field_ranges = (
296 timestamp_sec
=> [ -2147483648, 2147483647 ],
297 timestamp_nsec
=> [ 0, 999999999 ],
299 dev
=> [ -2147483648, 2147483647 ],
300 ino
=> [ 0, 4294967295 ],
302 print "Checking snapshot field values using \"IA64.ARCHREV_0\" (HP/UX) ranges.\n\n";
304 %snapshot_field_ranges = %iX86_linux_field_ranges;
305 print "Unrecognized architecture \"$arch\"; defaulting to \"iX86-linux\".\n";
306 print "(Use -a option to override.)\n" unless $opt_a;
310 if ( ref(1) ne "" ) {
311 print "(\"bignum\" mode is in effect; skipping 64-bit-integer check.)\n\n"
313 # find the largest max value in the current set of ranges
315 for $v (values %snapshot_field_ranges ) {
316 $maxmax = $v->[1] if ($v->[1] > $maxmax);
319 # "~0" translates into a platform-native integer with all bits turned
320 # on -- that is, the largest value that can be represented as
321 # an integer. We print a warning if our $maxmax value is greater
322 # than that largest integer, since in that case Perl will switch
323 # to using floats for those large max values. The wording of
324 # the message assumes that the only way this situation can exist
325 # is that the platform uses 32-bit integers but some of the
326 # snapshot-file fields have 64-bit values.
327 if ( ~0 < $maxmax ) {
329 Note: this version of Perl uses 32-bit integers, which means that it
330 will switch to using floating-point numbers when checking the ranges
331 for 64-bit snapshot-file fields. This normally will work fine, but
332 might fail to detect cases where the value in the input field value is
333 only slightly out of range. (For example, a "9223372036854775808"
334 might not be recognized as being larger than 9223372036854775807.)
335 If you suspect you are experiencing this problem, you can try running
336 the program using the "-Mbignum" option, as in
337 \$ perl $0 -Mbignum -c [FILES]
338 (but doing so will make the program run *much* slower).
347 # returns a warning message if $field_value isn't a valid string
348 # representation of an integer, or if the resulting integer is out of range
349 # defined by the two-element array retrieved using up the $field_name key in
350 # the global %snapshot_field_ranges hash.
351 sub validate_integer_field
($$) {
352 my $field_value = shift;
353 my $field_name = shift;
355 my ($min, $max) = @{$snapshot_field_ranges{$field_name}};
359 if ( not $field_value =~ /^-?\d+$/ ) {
360 $msg = " $field_name value contains invalid characters: \"$field_value\"\n";
362 if ( $field_value < $min ) {
363 $msg = " $field_name value too low: \"$field_value\" < $min \n";
364 } elsif ( $field_value > $max ) {
365 $msg = " $field_name value too high: \"$field_value\" > $max \n";
372 # This routine loops through each directory entry in the $info data
373 # structure and prints a warning message if tar would abort with an
374 # "Unexpected field value in snapshot file", "Numerical result out of
375 # range", or "Invalid argument" error upon reading this snapshot file.
377 # (Note that the "Unexpected field value in snapshot file" error message
378 # was introduced along with the change to snapshot file format "2",
379 # starting with tar v1.16 [or, more precisely, v1.15.91], while the
380 # other two were introduced in v1.27.)
382 # The checks here are intended to match those found in the incremen.c
383 # source file. See the choose_architecture() function (above) for more
384 # information on how to configure the range of values considered valid
387 # (Note: the checks here are taken from the code that processes
388 # version 2 snapshot files, but to keep things simple we apply those
389 # same checks to files having earlier versions -- but only for
390 # the fields that actually exist in those input files.)
392 sub check_field_values
($) {
398 print " Checking field values in snapshot file...\n";
400 $snapver = $info->[0];
403 $msg .= validate_integer_field
($info->[1], 'timestamp_sec');
405 $msg .= validate_integer_field
($info->[2], 'timestamp_nsec');
409 print "\n shapshot file header:\n";
414 foreach my $dir (@{$info->[3]}) {
418 $msg .= validate_integer_field
($dir->{'nfs'}, 'nfs');
420 $msg .= validate_integer_field
($dir->{'timestamp_sec'}, 'timestamp_sec');
421 $msg .= validate_integer_field
($dir->{'timestamp_nsec'}, 'timestamp_nsec');
423 $msg .= validate_integer_field
($dir->{'dev'}, 'dev');
424 $msg .= validate_integer_field
($dir->{'ino'}, 'ino');
428 print "\n directory: $dir->{'name'}\n";
433 print "\n Snapshot field value check complete" ,
434 $error_found ? "" : ", no errors found" ,
440 sub replace_device_number
($@) {
441 my $info = shift(@_);
446 foreach my $dir (@{$info->[3]}) {
448 if ($dir->{'dev'} eq $$x[0]) {
449 $dir->{'dev'} = $$x[1];
455 print " Updated $count records.\n"
460 sub write_incr_db
($$) {
462 my $filename = shift;
463 my $file_version = $$info[0];
465 open($file, ">$filename") || die "Could not open '$filename' for writing";
467 if ($file_version == 0) {
468 write_incr_db_0
($info, $file);
469 } elsif ($file_version == 1) {
470 write_incr_db_1
($info, $file);
471 } elsif ($file_version == 2) {
472 write_incr_db_2
($info, $file);
474 die "Unknown file version $file_version.";
480 sub write_incr_db_0
($$) {
484 my $timestamp_sec = $info->[1];
485 print $file "$timestamp_sec\n";
487 foreach my $dir (@{$info->[3]}) {
491 print $file "$dir->{'dev'} ";
492 print $file "$dir->{'ino'} ";
493 print $file "$dir->{'name'}\n";
498 sub write_incr_db_1
($$) {
502 print $file $info->[4];
504 my $timestamp_sec = $info->[1];
505 my $timestamp_nsec = $info->[2];
506 print $file "$timestamp_sec $timestamp_nsec\n";
508 foreach my $dir (@{$info->[3]}) {
512 print $file "$dir->{'timestamp_sec'} ";
513 print $file "$dir->{'timestamp_nsec'} ";
514 print $file "$dir->{'dev'} ";
515 print $file "$dir->{'ino'} ";
516 print $file "$dir->{'name'}\n";
521 sub write_incr_db_2
($$) {
525 print $file $info->[4];
527 my $timestamp_sec = $info->[1];
528 my $timestamp_nsec = $info->[2];
529 print $file $timestamp_sec . "\0";
530 print $file $timestamp_nsec . "\0";
532 foreach my $dir (@{$info->[3]}) {
533 print $file $dir->{'nfs'} . "\0";
534 print $file $dir->{'timestamp_sec'} . "\0";
535 print $file $dir->{'timestamp_nsec'} . "\0";
536 print $file $dir->{'dev'} . "\0";
537 print $file $dir->{'ino'} . "\0";
538 print $file $dir->{'name'} . "\0";
539 foreach my $dirent (@{$dir->{'dirents'}}) {
540 print $file $dirent . "\0";
549 our ($opt_b, $opt_r, $opt_h, $opt_c, $opt_a);
551 HELP_MESSAGE
() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r) ||
552 ($opt_a && !$opt_c) || ($opt_r && $opt_c) );
556 foreach my $spec (split(/,/, $opt_r)) {
557 ($spec =~ /^([^-]+)-([^-]+)/) || die "Invalid replacement specification '$opt_r'";
558 push @repl, [interpret_dev
($1), interpret_dev
($2)];
562 choose_architecture
($opt_a) if ($opt_c);
564 foreach my $snapfile (@ARGV) {
565 my $info = read_incr_db
($snapfile);
568 rename($snapfile, $snapfile . "~") || die "Could not rename '$snapfile' to backup";
571 replace_device_number
($info, @repl);
572 write_incr_db
($info, $snapfile);
574 check_field_values
($info);
576 show_device_counts
($info);
585 tar-snapshot-edit SNAPFILE [SNAPFILE [...]]
586 tar-snapshot-edit -r 'DEV1-DEV2[,DEV3-DEV4...]' [-b] SNAPFILE [SNAPFILE [...]]
587 tar-snapshot-edit -c [-aARCH] SNAPFILE [SNAPFILE [...]]
589 With no options specified: print a summary of the 'device' values
590 found in each SNAPFILE.
592 With -r: replace occurrences of DEV1 with DEV2 in each SNAPFILE.
593 DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,
594 65025), or MAJ:MIN (e.g., 254:1). To replace multiple occurrences,
595 separate them with commas. If -b is also specified, backup files
596 (ending with '~') will be created.
598 With -c: Check the field values in each SNAPFILE and print warning
599 messages if any invalid values are found. (An invalid value is one
600 that would cause \"tar\" to abort with an error message such as
601 Unexpected field value in snapshot file
602 Numerical result out of range
605 as it processed the snapshot file.)
607 Normally the program automatically chooses the valid ranges for
608 the fields based on the current system's architecture, but the
609 -a option can be used to override the selection, e.g. in order
610 to validate a snapshot file generated on a some other system.
611 (Currently only three architectures are supported, "iX86-linux",
612 "x86_64-linux", and "IA64.ARCHREV_0" [HP/UX running on Itanium/ia64],
613 and if the current system isn't recognized, then the iX86-linux
614 values are used by default.)
620 sub interpret_dev
($) {
623 if ($dev =~ /^([0-9]+):([0-9]+)$/) {
624 return $1 * 256 + $2;
625 } elsif ($dev =~ /^0x[0-9a-fA-F]+$/) {
627 } elsif ($dev =~ /^[0-9]+$/) {
630 die "Invalid device specification '$dev'";
This page took 0.073691 seconds and 4 git commands to generate.