4
0

tar-snapshot-edit 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #! /usr/bin/perl -w
  2. # Display and edit the 'dev' field in tar's snapshots
  3. # Copyright (C) 2007 Free Software Foundation, Inc.
  4. #
  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)
  8. # any later version.
  9. #
  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.
  14. #
  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
  18. # 02110-1301, USA.
  19. #
  20. # Author: Dustin J. Mitchell <[email protected]>
  21. #
  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.
  27. use Getopt::Std;
  28. ## reading
  29. sub read_incr_db ($) {
  30. my $filename = shift;
  31. open(my $file, "<$filename") || die "Could not open '$filename' for reading";
  32. my $header_str = <$file>;
  33. my $file_version;
  34. if ($header_str =~ /^GNU tar-[^-]*-([0-9]+)\n$/) {
  35. $file_version = $1+0;
  36. } else {
  37. $file_version = 0;
  38. }
  39. print "file version $file_version\n";
  40. if ($file_version == 0) {
  41. return read_incr_db_0($file, $header_str);
  42. } elsif ($file_version == 1) {
  43. return read_incr_db_1($file);
  44. } elsif ($file_version == 2) {
  45. return read_incr_db_2($file);
  46. } else {
  47. die "Unrecognized snapshot version in header '$header_str'";
  48. }
  49. }
  50. sub read_incr_db_0 ($$) {
  51. my $file = shift;
  52. my $header_str = shift;
  53. my $hdr_timestamp_sec = $header_str;
  54. chop $hdr_timestamp_sec;
  55. my $hdr_timestamp_nsec = ''; # not present in file format 0
  56. my @dirs;
  57. while (<$file>) {
  58. /^([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
  59. push @dirs, { dev=>$1,
  60. ino=>$2,
  61. name=>$3 };
  62. }
  63. close($file);
  64. # file version, timestamp, timestamp, dir list
  65. return [ 0, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
  66. }
  67. sub read_incr_db_1 ($) {
  68. my $file = shift;
  69. my $timestamp = <$file>; # "sec nsec"
  70. my ($hdr_timestamp_sec, $hdr_timestamp_nsec) = ($timestamp =~ /([0-9]*) ([0-9]*)/);
  71. my @dirs;
  72. while (<$file>) {
  73. /^([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*) (.*)\n$/ || die("Bad snapshot line $_");
  74. push @dirs, { timestamp_sec=>$1,
  75. timestamp_nsec=>$2,
  76. dev=>$3,
  77. ino=>$4,
  78. name=>$5 };
  79. }
  80. close($file);
  81. # file version, timestamp, timestamp, dir list
  82. return [ 1, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
  83. }
  84. sub read_incr_db_2 ($) {
  85. my $file = shift;
  86. $/="\0"; # $INPUT_RECORD_SEPARATOR
  87. my $hdr_timestamp_sec = <$file>;
  88. chop $hdr_timestamp_sec;
  89. my $hdr_timestamp_nsec = <$file>;
  90. chop $hdr_timestamp_nsec;
  91. my @dirs;
  92. while (1) {
  93. last if eof($file);
  94. my $nfs = <$file>;
  95. my $timestamp_sec = <$file>;
  96. my $timestamp_nsec = <$file>;
  97. my $dev = <$file>;
  98. my $ino = <$file>;
  99. my $name = <$file>;
  100. # get rid of trailing NULs
  101. chop $nfs;
  102. chop $timestamp_sec;
  103. chop $timestamp_nsec;
  104. chop $dev;
  105. chop $ino;
  106. chop $name;
  107. my @dirents;
  108. while (my $dirent = <$file>) {
  109. chop $dirent;
  110. push @dirents, $dirent;
  111. last if ($dirent eq "");
  112. }
  113. die "missing terminator" unless (<$file> eq "\0");
  114. push @dirs, { nfs=>$nfs,
  115. timestamp_sec=>$timestamp_sec,
  116. timestamp_nsec=>$timestamp_nsec,
  117. dev=>$dev,
  118. ino=>$ino,
  119. name=>$name,
  120. dirents=>\@dirents };
  121. }
  122. close($file);
  123. $/ = "\n"; # reset to normal
  124. # file version, timestamp, timestamp, dir list
  125. return [ 2, $hdr_timestamp_sec, $hdr_timestamp_nsec, \@dirs ];
  126. }
  127. ## display
  128. sub show_device_counts ($$) {
  129. my $info = shift;
  130. my $filename = shift;
  131. my %devices;
  132. foreach my $dir (@{${@$info}[3]}) {
  133. my $dev = ${%$dir}{'dev'};
  134. $devices{$dev}++;
  135. }
  136. foreach $dev (sort keys %devices) {
  137. printf "$filename: Device 0x%04x occurs $devices{$dev} times.\n", $dev;
  138. }
  139. }
  140. ## editing
  141. sub replace_device_number ($@) {
  142. my $info = shift(@_);
  143. my @repl = @_;
  144. foreach my $dir (@{${@$info}[3]}) {
  145. foreach $x (@repl) {
  146. if (${%$dir}{'dev'} eq $$x[0]) {
  147. ${%$dir}{'dev'} = $$x[1];
  148. last;
  149. }
  150. }
  151. }
  152. }
  153. ## writing
  154. sub write_incr_db ($$) {
  155. my $info = shift;
  156. my $filename = shift;
  157. my $file_version = $$info[0];
  158. open($file, ">$filename") || die "Could not open '$filename' for writing";
  159. if ($file_version == 0) {
  160. write_incr_db_0($info, $file);
  161. } elsif ($file_version == 1) {
  162. write_incr_db_1($info, $file);
  163. } elsif ($file_version == 2) {
  164. write_incr_db_2($info, $file);
  165. } else {
  166. die "Unknown file version $file_version.";
  167. }
  168. close($file);
  169. }
  170. sub write_incr_db_0 ($$) {
  171. my $info = shift;
  172. my $file = shift;
  173. my $timestamp_sec = $info->[1];
  174. print $file "$timestamp_sec\n";
  175. foreach my $dir (@{${@$info}[3]}) {
  176. print $file "${%$dir}{'dev'} ";
  177. print $file "${%$dir}{'ino'} ";
  178. print $file "${%$dir}{'name'}\n";
  179. }
  180. }
  181. sub write_incr_db_1 ($$) {
  182. my $info = shift;
  183. my $file = shift;
  184. print $file "GNU tar-1.15-1\n";
  185. my $timestamp_sec = $info->[1];
  186. my $timestamp_nsec = $info->[2];
  187. print $file "$timestamp_sec $timestamp_nsec\n";
  188. foreach my $dir (@{${@$info}[3]}) {
  189. print $file "${%$dir}{'timestamp_sec'} ";
  190. print $file "${%$dir}{'timestamp_nsec'} ";
  191. print $file "${%$dir}{'dev'} ";
  192. print $file "${%$dir}{'ino'} ";
  193. print $file "${%$dir}{'name'}\n";
  194. }
  195. }
  196. sub write_incr_db_2 ($$) {
  197. my $info = shift;
  198. my $file = shift;
  199. print $file "GNU tar-1.16-2\n";
  200. my $timestamp_sec = $info->[1];
  201. my $timestamp_nsec = $info->[2];
  202. print $file $timestamp_sec . "\0";
  203. print $file $timestamp_nsec . "\0";
  204. foreach my $dir (@{${@$info}[3]}) {
  205. print $file ${%$dir}{'nfs'} . "\0";
  206. print $file ${%$dir}{'timestamp_sec'} . "\0";
  207. print $file ${%$dir}{'timestamp_nsec'} . "\0";
  208. print $file ${%$dir}{'dev'} . "\0";
  209. print $file ${%$dir}{'ino'} . "\0";
  210. print $file ${%$dir}{'name'} . "\0";
  211. foreach my $dirent (@{${%$dir}{'dirents'}}) {
  212. print $file $dirent . "\0";
  213. }
  214. print $file "\0";
  215. }
  216. }
  217. ## main
  218. sub main {
  219. our ($opt_b, $opt_r, $opt_h);
  220. getopts('br:h');
  221. HELP_MESSAGE() if ($opt_h || $#ARGV == -1 || ($opt_b && !$opt_r));
  222. my @repl;
  223. if ($opt_r) {
  224. foreach my $spec (split(/,/, $opt_r)) {
  225. ($spec =~ /^([^-]+)-([^-]+)/) || die "Invalid replacement specification '$opt_r'";
  226. push @repl, [interpret_dev($1), interpret_dev($2)];
  227. }
  228. }
  229. foreach my $snapfile (@ARGV) {
  230. my $info = read_incr_db($snapfile);
  231. if ($opt_r ) {
  232. if ($opt_b) {
  233. rename($snapfile, $snapfile . "~") || die "Could not rename '$snapfile' to backup";
  234. }
  235. replace_device_number($info, @repl);
  236. write_incr_db($info, $snapfile);
  237. } else {
  238. show_device_counts($info, $snapfile);
  239. }
  240. }
  241. }
  242. sub HELP_MESSAGE {
  243. print "Usage: tar-snapshot-edit.pl [-r 'DEV1-DEV2[,DEV3-DEV4...]' [-b]] SNAPFILE [SNAPFILE [..]]\n";
  244. print "\n";
  245. print " Without -r, summarize the 'device' values in each SNAPFILE.\n";
  246. print "\n";
  247. print " With -r, replace occurrences of DEV1 with DEV2 in each SNAPFILE.\n";
  248. print " DEV1 and DEV2 may be specified in hex (e.g., 0xfe01), decimal (e.g.,\n";
  249. print " 65025), or MAJ:MIN (e.g., 254:1). To replace multiple occurrences,\n";
  250. print " separate them with commas. If -b is also specified, backup\n";
  251. print " files (ending with '~') will be created.\n";
  252. exit 1;
  253. }
  254. sub interpret_dev ($) {
  255. my $dev = shift;
  256. if ($dev =~ /^([0-9]+):([0-9]+)$/) {
  257. return $1 * 256 + $2;
  258. } elsif ($dev =~ /^0x[0-9a-fA-F]+$/) {
  259. return oct $dev;
  260. } elsif ($dev =~ /^[0-9]+$/) {
  261. return $dev+0;
  262. } else {
  263. die "Invalid device specification '$dev'";
  264. }
  265. }
  266. main