]> arthur.barton.de Git - netatalk.git/blob - bin/cnid/cnid_maint.in
Don't try to recover a DB environment that doesn't exist.
[netatalk.git] / bin / cnid / cnid_maint.in
1 #!@PERL@
2
3 #
4 # cnid_maint: A script to maintain the consistency of CNID databases.
5 #
6 # $Id: cnid_maint.in,v 1.6 2002-02-11 21:02:00 jmarcus Exp $
7 #
8
9 use strict;
10 use Getopt::Std;
11 use vars qw(
12   $APPLE_VOLUMES_FILE
13   $STOP_CMD
14   $START_CMD
15   $PS_CMD
16   $GREP
17   $DB_STAT
18   $DB_RECOVER
19   $DB_VERIFY
20   $VERSION
21   $START_NETATALK
22   $LOCK_FILE
23   $HOLDING_LOCK
24 );
25
26 ## Edit ME
27 $STOP_CMD  = '/usr/local/etc/rc.d/netatalk.sh stop';
28 $START_CMD = '/usr/local/etc/rc.d/netatalk.sh start';
29
30 # This ps command needs to output the following fields in the following order:
31 # USER,PID,PPID,COMMAND
32 # Below is the example of a BSD ps.  A SYSV example is:
33 # /bin/ps -eflouid,pid,ppid,comm
34 $PS_CMD             = '@PS@ -axouser,pid,ppid,command';
35 $DB_STAT            = '@DB3_PATH@bin/db_stat';
36 $DB_RECOVER         = '@DB3_PATH@bin/db_recover';
37 $DB_VERIFY          = '@DB3_PATH@bin/db_verify';
38 $APPLE_VOLUMES_FILE = '@PKGCONFDIR@/AppleVolumes.default';
39 ## End edit section
40
41 $VERSION = '1.0';
42 $GREP           = '@GREP@';
43 $START_NETATALK = 0;
44 $LOCK_FILE      = tmpdir() . '/cnid_maint.LOCK';
45 $HOLDING_LOCK   = 0;
46
47 sub LOCK_SH { 1 }
48 sub LOCK_EX { 2 }
49 sub LOCK_NB { 4 }
50 sub LOCK_UN { 8 }
51
52 my $opts        = {};
53 my $extra_safe  = 0;
54 my $do_verify   = 0;
55 my $remove_logs = 0;
56
57 getopts( 'hsvVl', $opts );
58
59 if ( $opts->{'v'} ) {
60     version();
61     exit(0);
62 }
63 if ( $opts->{'h'} ) {
64     help();
65     exit(0);
66 }
67 if ( $opts->{'s'} ) {
68     $extra_safe = 1;
69 }
70 if ( $opts->{'V'} ) {
71     $do_verify = 1;
72 }
73 if ( $opts->{'l'} ) {
74     $remove_logs = 1;
75 }
76
77 print "Beginning run of CNID DB Maintanence script at "
78   . scalar(localtime) . ".\n\n";
79
80 if ( -f $LOCK_FILE ) {
81     error( 1, "Lock file $LOCK_FILE exists." );
82     end();
83 }
84
85 unless ( open( LOCK, ">" . $LOCK_FILE ) ) {
86     error( 2, "Unable to create $LOCK_FILE: $!" );
87 }
88 flock( LOCK, LOCK_EX );
89 $HOLDING_LOCK = 1;
90
91 # Check to see if the AppleVolumes.default file exists.  We will use this file
92 # to get a list of database environments to recover.  We will ignore users'
93 # home directories since that could be a monumental under taking.
94 if ( !-f $APPLE_VOLUMES_FILE ) {
95     error( 2, "Unable to locate $APPLE_VOLUMES_FILE" );
96 }
97
98 # Use ps to get a list of running afpds.  We will track all afpd PIDs that are
99 # running as root.
100 unless ( open( PS, $PS_CMD . " | $GREP afpd | $GREP -v grep |" ) ) {
101     error( 2, "Unable to open a pipe to ps: $!" );
102 }
103
104 my $children  = 0;
105 my $processes = 0;
106 while (<PS>) {
107     chomp;
108     $processes++;
109     my ( $user, $pid, $ppid, $command ) = split (/\s+/);
110     if ( ( $user eq "root" && $ppid != 1 ) || ( $user ne "root" ) ) {
111         $children++;
112     }
113 }
114
115 close(PS);
116
117 if ( $children > 1 ) {
118
119     # We have some children.  We cannot run recovery.
120     error( 1,
121 "Clients are still connected.  Database recovery will not be run at this time."
122     );
123     end();
124 }
125
126 if ($processes) {
127
128     # Shutdown the running afpds.
129     $START_NETATALK = 1;
130     error( 0, "Shutting down afpd process..." );
131     error( 2, "Failed to shutdown afpd" )
132       if system( $STOP_CMD . ">/dev/null 2>&1" );
133 }
134
135 # Now, we parse AppleVolumes.default to get a list of volumes to run recovery
136 # on.
137 unless ( open( VOLS, $APPLE_VOLUMES_FILE ) ) {
138     error( 2, "Unable to open $APPLE_VOLUMES_FILE: $!" );
139 }
140
141 my @paths = ();
142 while (<VOLS>) {
143     s/#.*//;
144     s/^\s+//;
145     s/\s+$//;
146     next unless length;
147     my ( $path, @options ) = split ( /\s+/, $_ );
148     next if ( $path =~ /^~/ );
149     my $option = "";
150     foreach $option (@options) {
151
152         # We need to check for the dbpath option on each volume.  If that
153         # option is present, we should use its path instead of the actual
154         # volume path.
155         if ( $option =~ /^dbpath:/ ) {
156             push @paths, $';
157         }
158         else {
159             push @paths, $path;
160         }
161     }
162 }
163
164 close(VOLS);
165
166 my $path = "";
167 foreach $path (@paths) {
168     my $dbpath = $path . "/.AppleDB";
169     if ( !-d $dbpath ) {
170         error( 1, "Database environment $dbpath does not exist" );
171         next;
172     }
173     if ($extra_safe) {
174         error( 0,
175             "Checking database environment $dbpath for open connections..." );
176         unless ( open( STAT, $DB_STAT . " -h $dbpath -e |" ) ) {
177             error( 1, "Failed to open a pipe to $DB_STAT: $!" );
178             next;
179         }
180
181         # Now, check each DB environment for any open connections (db_stat calls 
182         # them as references).  If a volume has no references, we can do
183         # recovery on it.  Only check this option if the user wants to play 
184         # things extra safe.
185         my $connections = 0;
186         while (<STAT>) {
187             chomp;
188             s/\s//g;
189             if (/References\.$/) {
190                 $connections = $`;
191                 last;
192             }
193         }
194
195         close(STAT);
196
197         # Print out two different skip messages.  This is just for anality.
198         if ( $connections == 1 ) {
199             error( 1, "Skipping $dbpath since it has one active connection" );
200             next;
201         }
202
203         if ( $connections > 0 ) {
204             error( 1,
205                 "Skipping $dbpath since it has $connections active connections"
206             );
207             next;
208         }
209     }
210
211     # Run the db_recover command on the environment.
212     error( 0, "Running db_recover on $dbpath" );
213     if ( system( $DB_RECOVER . " -h $dbpath >/dev/null 2>&1" ) ) {
214         error( 1, "Failed to run db_recover on $dbpath" );
215         next;
216     }
217
218     if ($do_verify) {
219         error( 0, "Verifying $dbpath/cnid.db" );
220         if ( system( $DB_VERIFY . " -q -h $dbpath cnid.db" ) ) {
221             error( 1, "Verification of $dbpath/cnid.db failed" );
222             next;
223         }
224
225         error( 0, "Verifying $dbpath/devino.db" );
226         if ( system( $DB_VERIFY . " -q -h $dbpath devino.db" ) ) {
227             error( 1, "Verification of $dbpath/devino.db failed" );
228             next;
229         }
230
231         error( 0, "Verifying $dbpath/didname.db" );
232         if ( system( $DB_VERIFY . " -q -h $dbpath didname.db" ) ) {
233             error( 1, "Verification of $dbpath/didname.db failed" );
234             next;
235         }
236     }
237
238     if ($remove_logs) {
239
240         # Remove the log files if told to do so.
241         unless ( opendir( DIR, $dbpath ) ) {
242             error( 1, "Failed to open $dbpath for browsing: $!" );
243             next;
244         }
245
246         my $file = "";
247         while ( defined( $file = readdir(DIR) ) ) {
248             if ( $file =~ /^log\.\d+$/ ) {
249                 error( 0, "Removing $dbpath/$file" );
250                 unless ( unlink( $dbpath . "/" . $file ) ) {
251                     error( 1, "Failed to remove $dbpath/$file: $!" );
252                     next;
253                 }
254             }
255         }
256
257         closedir(DIR);
258     }
259
260 }
261
262 end();
263
264 sub tmpdir {
265     my $tmpdir;
266
267     foreach ( $ENV{TMPDIR}, "/tmp" ) {
268         next unless defined && -d && -w _;
269         $tmpdir = $_;
270         last;
271     }
272     $tmpdir = '' unless defined $tmpdir;
273     return $tmpdir;
274 }
275
276 sub error {
277     my ( $code, $msg ) = @_;
278
279     my $err_types = {
280         0 => "INFO",
281         1 => "WARNING",
282         2 => "ERROR",
283     };
284
285     print $err_types->{$code} . ": " . $msg . "\n";
286
287     end() if ( $code == 2 );
288 }
289
290 sub end {
291     if ($START_NETATALK) {
292         error( 0, "Restarting Netatalk" );
293         if ( system( $START_CMD . " >/dev/null 2>&1" ) ) {
294             print "ERROR: Failed to restart Netatalk\n";
295         }
296     }
297     if ($HOLDING_LOCK) {
298         close(LOCK);
299         unlink($LOCK_FILE);
300     }
301     print "\nRun of CNID DB Maintenance script ended at "
302       . scalar(localtime) . ".\n";
303     exit(0);
304 }
305
306 sub version {
307     print "cnid_maint.pl version $VERSION\n";
308 }
309
310 sub help {
311     print "usage: cnid_maint.pl [-hlsvV]\n";
312     print "\t-h   view this message\n";
313     print "\t-l   remove transaction logs after running recovery\n";
314     print
315       "\t-s   be extra safe in verifying there are no open DB connections\n";
316     print "\t-v   print version and exit\n";
317     print "\t-V   run a verification on all database files after recovery\n";
318 }