/[wait]/trunk/script/wait_admin
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/script/wait_admin

Parent Directory Parent Directory | Revision Log Revision Log


Revision 99 - (show annotations)
Thu May 27 22:04:30 2004 UTC (19 years, 11 months ago) by dpavlin
File size: 8101 byte(s)
new tool to perform maintenance on catalog and meta database files

1 #!/usr/bin/perl -w
2
3 =head1 NAME
4
5 wait_admin - perform administrative tasks on WAIT catalog and meta files
6
7 =head1 SYNOPSIS
8
9 B<wait_admin>
10 [B<-database> I<dbname>]
11 [B<-dir> I<directory>]
12 [B<-table> I<table name>]
13 [B<-verbose>]
14 [B<-debug>]
15
16 =head1 OPTIONS
17
18 =over 5
19
20 =item B<-database> I<dbname>
21
22 Specify database name. Default is F<DB>.
23
24 =item B<-dir> I<directory>
25
26 Alternate directory where databases are located. Default is the
27 directory specified during configuration of WAIT.
28
29 =item B<-table> I<table name>
30
31 Specify just one table to check. By default, all tables are checked.
32
33 =item B<-quiet>
34
35 Supress C<ok:> messages from output.
36
37 =item B<-verbose>
38
39 Output also informational messages prefixed by C<info:>.
40
41 =item B<-debug>
42
43 Very chatty output (mostly for debugging) to STDERR. Includes verbose
44 messages which will go to STDOUT.
45
46 =back
47
48 =head1 DESCRIPTION
49
50 This script will check your database catalog and meta files and interactivly
51 try to recover from following cases:
52
53 =over 5
54
55 =item * removing database directory without droping tables first
56
57 =item * out of sync catalog and meta file data
58
59 =back
60
61 This will be done performing following steps:
62
63 =over 5
64
65 =cut
66
67 BEGIN {require WAIT::Config;}
68
69 use strict;
70 use Getopt::Long;
71 use Data::Dumper;
72 use Storable qw(store retrieve dclone); # freeze thaw
73 use Text::Diff;
74 use Acme::Damn qw(unbless);
75 use Scalar::Util qw(blessed); # Acme::Holly?
76 use IO::File;
77
78 my %OPT = (
79 database => 'DB',
80 dir => $WAIT::Config->{WAIT_home} || '/tmp',
81 verbose => 0,
82 debug => 0, ## TODO change to 0 before release!
83 quiet => 0,
84 );
85
86 GetOptions(\%OPT,
87 'database=s',
88 'dir=s',
89 'verbose!',
90 'debug!',
91 'quiet!',
92 );
93
94 my $catalog_file = "$OPT{dir}/$OPT{database}/catalog";
95 my $meta_file = "$OPT{dir}/$OPT{database}/meta";
96
97 # logging functions
98 sub log_info {
99 print "info: ",join("",@_),"\n" if ($OPT{'verbose'} || $OPT{'debug'});
100 }
101 sub log_ok {
102 print "ok: ",join("",@_),"\n" if (!$OPT{'quiet'} || $OPT{'verbose'} || $OPT{'debug'});
103 }
104 sub log_warning {
105 print "WARNING: ",join("",@_),"\n";
106 }
107 sub log_error {
108 print "ERROR: ",join("",@_),"\n";
109 }
110 sub log_die {
111 die "FATAL: ",join("",@_),"\n";
112 }
113 sub log_debug {
114 print STDERR "debug: ",join("",@_),"\n" if ($OPT{'debug'});
115 }
116 # user interaction
117 sub ask {
118 print join("",@_);
119 my $ans = <STDIN>;
120 chomp($ans);
121 return $ans;
122 }
123 sub do_fix {
124 my $ans = ask(( @_ , " [Y/n]: "));
125 $ans ||= 'y'; # default
126 if (lc($ans) =~ m/^y/) {
127 return 1;
128 }
129 return 0;
130 }
131
132 # compress tabs to single space
133 sub compress_tabs {
134 my ($p,$t) = @_;
135 return $p.(" " x (length($t)/8));
136 }
137
138 sub compDumper {
139 my $d = Dumper(shift);
140 $d =~ s/^()(\s+)/compress_tabs($1,$2)/gem;
141 return $d;
142 }
143
144
145 =item * check existence of catalog and meta files
146
147 =cut
148
149 if (-r $catalog_file) {
150 log_info "using catalog '$catalog_file'";
151 } else {
152 log_die "can't find catalog '$catalog_file': $!";
153 }
154
155 if (-r $meta_file) {
156 log_info "using meta '$meta_file'";
157 } else {
158 log_die "can't find meta '$meta_file': $!";
159 }
160
161 =item * read catalog and meta files
162
163 =cut
164
165 my ($catalog,$meta);
166
167 if ($catalog = retrieve($catalog_file)) {
168 log_ok "catalog opened with Storable";
169 } else {
170 log_die "catalog unreadable by Storable!";
171 }
172
173 if ($meta = do $meta_file) {
174 log_ok "meta read with do";
175 } else {
176 log_warning "do meta failed... trying with cat...";
177 $meta = eval `cat $meta_file`;
178 if ($meta) {
179 log_ok "meta read with cat (why did do failed?)";
180 } else {
181 log_die "can't cat or do meta file!";
182 }
183 }
184
185 =item * compare content of meta and catalog files
186
187 Data might differ, depending on ordering of variables in Storable and
188 Data::Dumper structures and ability to store structure correctly (it seems that there is always difference in one variable).
189
190 If differences are found, you will be offered to select master copy (catalog
191 or meta). You can also accept default none which will skip synchronization
192 because of differences.
193
194 =cut
195
196 print STDERR compDumper($catalog,$meta) if ($OPT{'debug'});
197
198 my $cc = 0; # number of changes in catalog
199
200 if (Dumper($catalog) ne Dumper($meta)) {
201 log_warning "catalog and meta are different!";
202 my $diff = diff(\Dumper($catalog),\Dumper($meta), { STYLE => "Unified" });
203 $diff =~ s/^([\s\+\-])(\s+)/compress_tabs($1,$2)/gem;
204
205 print STDERR "diff -u catalog meta\n$diff" if ($OPT{'debug'});
206
207 foreach my $d (split(/@@ [+-]\d+,\d+ [+-]\d+,\d+ @@/, $diff)) {
208
209 if ($d =~ m/^\-(.+)$/m && $d =~ m/^\+\Q$1\E/m) {
210 log_debug("false alarm! structure ordering different");
211 } elsif ($d) {
212 log_info("here is diff between catalog and meta:\n",$d);
213 }
214 }
215
216 if ($diff) {
217 my $ans=ask("Select master repository to sync to [meta/catalog/NONE]: ");
218 if ($ans =~ m/^c/i) {
219 $meta = $catalog;
220 log_info("copied catalog to meta");
221 $cc++;
222 } elsif ($ans =~ m/^m/i) {
223 $catalog = $meta;
224 log_info("copied meta to catalog");
225 $cc++;
226 } else {
227 log_info("meta and catalog still out of sync!");
228 }
229 }
230 } else {
231 log_ok "meta and catalog are same\n";
232 }
233
234 # catalog and meta unblessed
235 my $c = dclone($catalog);
236 my $m = dclone($meta);
237
238 if (my $class = blessed $c) {
239 if ($class eq "WAIT::Database") {
240 log_ok "top class $class";
241 unbless($c);
242 } else {
243 log_die "unknown top class $class";
244 }
245 }
246
247 =item * check for existence of database directory
248
249 =cut
250
251 my $db_dir = $c->{'file'} ||
252 log_die("no database directory in c!");
253 if (-d $db_dir) {
254 log_ok("database directory: '$db_dir' (exists)");
255 } else {
256 log_die("database directory '$db_dir' doesn't exist!");
257 }
258
259 =item * print info about database
260
261 This will check if database name and mode is defined, and
262 report if unique attributes restrction is in effect.
263
264 =cut
265
266 if ($c->{'name'}) {
267 log_info("database name: ",$c->{'name'});
268 } else {
269 log_error("database name is not defined");
270 }
271
272 if ($db_dir =~ m/\/$c->{'name'}$/) {
273 log_ok("database directory contains database name");
274 }
275
276 if ($c->{'mode'}) {
277 log_info("catalog mode: ",$c->{'mode'});
278 } else {
279 log_error("catalog mode is not defined");
280 }
281
282 my $unique_att = $c->{'uniqueatt'} &&
283 log_info("using unique attributes restriction");
284
285 =item * list tables in database and check them
286
287 If option B<-table> is used, just one table will be checked.
288
289 Varous tests on tables will be performed, including correct class blessing,
290 and testing for table directories.
291
292 =cut
293
294 my @tables = keys %{$c->{'tables'}};
295 @tables = qw/$OPT{table}/ if ($OPT{table});
296
297 foreach my $t (@tables) {
298 log_info "checking table '$t'";
299 my $tc = blessed $c->{'tables'}->{$t};
300 if ($tc) {
301 if ($tc eq "WAIT::Table") {
302 log_ok("table '$t' is $tc");
303
304 if (-d "$db_dir/$t") {
305 log_info("table directory exists");
306 } else {
307 log_error("can't find directory for table '$t', it should be '$db_dir/$t'");
308 if (do_fix("Do you want to remove table '$t' from database?")) {
309 delete $catalog->{'tables'}->{$t} || die "can't delete table $t from catalog!";
310 $cc++;
311 log_info("table '$t' removed from database $OPT{database}");
312 }
313 }
314
315 } else {
316 log_error("table '$t' should be blessed to WAIT::Table and not to $tc!");
317 }
318 } else {
319 log_error("table '$t' isn't blessed");
320 }
321 }
322
323 print STDERR compDumper($catalog) if ($OPT{'debug'});
324
325 if ($cc && do_fix("Commit $cc ",($cc == 1 ? "change" : "changes")," to catalog and meta?")) {
326 eval { store($catalog, $catalog_file) } ||
327 log_die("can't store new catalog in '$catalog_file': $!");
328
329 my $fh = new IO::File "> $meta_file";
330 if (! $fh) {
331 log_error("can't open meta file '$meta_file' for writing: $!");
332 log_die("you should probably re-run this tool to make catalog and meta in-sync!");
333 }
334 my $dd = new Data::Dumper [$catalog],['self'];
335 $fh->print('my ');
336 $fh->print($dd->Dumpxs) || log_die("problem dumping meta file: $1 (re-run this tool)");
337 $fh->close;
338 }
339
340 $WAIT::Config = $WAIT::Config; # make perl -w happy
341
342 =head1 WARNING
343
344 This script doesn't use locks. So you beter don't run it on live database or
345 expect that your data might get trashed (so, think again do you want to copy
346 database first before running this script on it!).
347
348 =head1 TODO
349
350 Check various conditions and improve recovery.
351
352 =head1 AUTHOR
353
354 Dobrica Pavlinusic E<lt>F<dpavlin@rot13.org>E<gt>
355
356 =cut

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26