]> arthur.barton.de Git - netatalk.git/blob - bin/cnid/cnid_maint.in
MFH:
[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.4.2.3 2002-02-11 21:21:30 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 if ( $< != 0 ) {
78     die "You must be root to run this script.\n";
79 }
80
81 print "Beginning run of CNID DB Maintanence script at "
82   . scalar(localtime) . ".\n\n";
83
84 if ( -f $LOCK_FILE ) {
85     error( 1, "Lock file $LOCK_FILE exists." );
86     end();
87 }
88
89 unless ( open( LOCK, ">" . $LOCK_FILE ) ) {
90     error( 2, "Unable to create $LOCK_FILE: $!" );
91 }
92 flock( LOCK, LOCK_EX );
93 $HOLDING_LOCK = 1;
94
95 # Check to see if the AppleVolumes.default file exists.  We will use this file
96 # to get a list of database environments to recover.  We will ignore users'
97 # home directories since that could be a monumental under taking.
98 if ( !-f $APPLE_VOLUMES_FILE ) {
99     error( 2, "Unable to locate $APPLE_VOLUMES_FILE" );
100 }
101
102 # Use ps to get a list of running afpds.  We will track all afpd PIDs that are
103 # running as root.
104 unless ( open( PS, $PS_CMD . " | $GREP afpd | $GREP -v grep |" ) ) {
105     error( 2, "Unable to open a pipe to ps: $!" );
106 }
107
108 my $children  = 0;
109 my $processes = 0;
110 while (<PS>) {
111     chomp;
112     $processes++;
113     my ( $user, $pid, $ppid, $command ) = split (/\s+/);
114     if ( ( $user eq "root" && $ppid != 1 ) || ( $user ne "root" ) ) {
115         $children++;
116     }
117 }
118
119 close(PS);
120
121 if ( $children > 1 ) {
122
123     # We have some children.  We cannot run recovery.
124     error( 1,
125 "Clients are still connected.  Database recovery will not be run at this time."
126     );
127     end();
128 }
129
130 if ($processes) {
131
132     # Shutdown the running afpds.
133     $START_NETATALK = 1;
134     error( 0, "Shutting down afpd process..." );
135     error( 2, "Failed to shutdown afpd" )
136       if system( $STOP_CMD . ">/dev/null 2>&1" );
137 }
138
139 # Now, we parse AppleVolumes.default to get a list of volumes to run recovery
140 # on.
141 unless ( open( VOLS, $APPLE_VOLUMES_FILE ) ) {
142     error( 2, "Unable to open $APPLE_VOLUMES_FILE: $!" );
143 }
144
145 my @paths = ();
146 while (<VOLS>) {
147     s/#.*//;
148     s/^\s+//;
149     s/\s+$//;
150     next unless length;
151     my ( $path, @options ) = split ( /\s+/, $_ );
152     next if ( $path =~ /^~/ );
153     my $option = "";
154     foreach $option (@options) {
155
156         # We need to check for the dbpath option on each volume.  If that
157         # option is present, we should use its path instead of the actual
158         # volume path.
159         if ( $option =~ /^dbpath:/ ) {
160             push @paths, $';
161         }
162         else {
163             push @paths, $path;
164         }
165     }
166 }
167
168 close(VOLS);
169
170 my $path = "";
171 foreach $path (@paths) {
172     my $dbpath = $path . "/.AppleDB";
173     if ( !-d $dbpath ) {
174         error( 1, "Database environment $dbpath does not exist" );
175         next;
176     }
177     if ($extra_safe) {
178         error( 0,
179             "Checking database environment $dbpath for open connections..." );
180         unless ( open( STAT, $DB_STAT . " -h $dbpath -e |" ) ) {
181             error( 1, "Failed to open a pipe to $DB_STAT: $!" );
182             next;
183         }
184
185         # Now, check each DB environment for any open connections (db_stat calls 
186         # them as references).  If a volume has no references, we can do
187         # recovery on it.  Only check this option if the user wants to play 
188         # things extra safe.
189         my $connections = 0;
190         while (<STAT>) {
191             chomp;
192             s/\s//g;
193             if (/References\.$/) {
194                 $connections = $`;
195                 last;
196             }
197         }
198
199         close(STAT);
200
201         # Print out two different skip messages.  This is just for anality.
202         if ( $connections == 1 ) {
203             error( 1, "Skipping $dbpath since it has one active connection" );
204             next;
205         }
206
207         if ( $connections > 0 ) {
208             error( 1,
209                 "Skipping $dbpath since it has $connections active connections"
210             );
211             next;
212         }
213     }
214
215     # Run the db_recover command on the environment.
216     error( 0, "Running db_recover on $dbpath" );
217     if ( system( $DB_RECOVER . " -h $dbpath >/dev/null 2>&1" ) ) {
218         error( 1, "Failed to run db_recover on $dbpath" );
219         next;
220     }
221
222     if ($do_verify) {
223         error( 0, "Verifying $dbpath/cnid.db" );
224         if ( system( $DB_VERIFY . " -q -h $dbpath cnid.db" ) ) {
225             error( 1, "Verification of $dbpath/cnid.db failed" );
226             next;
227         }
228
229         error( 0, "Verifying $dbpath/devino.db" );
230         if ( system( $DB_VERIFY . " -q -h $dbpath devino.db" ) ) {
231             error( 1, "Verification of $dbpath/devino.db failed" );
232             next;
233         }
234
235         error( 0, "Verifying $dbpath/didname.db" );
236         if ( system( $DB_VERIFY . " -q -h $dbpath didname.db" ) ) {
237             error( 1, "Verification of $dbpath/didname.db failed" );
238             next;
239         }
240     }
241
242     if ($remove_logs) {
243
244         # Remove the log files if told to do so.
245         unless ( opendir( DIR, $dbpath ) ) {
246             error( 1, "Failed to open $dbpath for browsing: $!" );
247             next;
248         }
249
250         my $file = "";
251         while ( defined( $file = readdir(DIR) ) ) {
252             if ( $file =~ /^log\.\d+$/ ) {
253                 error( 0, "Removing $dbpath/$file" );
254                 unless ( unlink( $dbpath . "/" . $file ) ) {
255                     error( 1, "Failed to remove $dbpath/$file: $!" );
256                     next;
257                 }
258             }
259         }
260
261         closedir(DIR);
262     }
263
264 }
265
266 end();
267
268 sub tmpdir {
269     my $tmpdir;
270
271     foreach ( $ENV{TMPDIR}, "/tmp" ) {
272         next unless defined && -d && -w _;
273         $tmpdir = $_;
274         last;
275     }
276     $tmpdir = '' unless defined $tmpdir;
277     return $tmpdir;
278 }
279
280 sub error {
281     my ( $code, $msg ) = @_;
282
283     my $err_types = {
284         0 => "INFO",
285         1 => "WARNING",
286         2 => "ERROR",
287     };
288
289     print $err_types->{$code} . ": " . $msg . "\n";
290
291     end() if ( $code == 2 );
292 }
293
294 sub end {
295     if ($START_NETATALK) {
296         error( 0, "Restarting Netatalk" );
297         if ( system( $START_CMD . " >/dev/null 2>&1" ) ) {
298             print "ERROR: Failed to restart Netatalk\n";
299         }
300     }
301     if ($HOLDING_LOCK) {
302         close(LOCK);
303         unlink($LOCK_FILE);
304     }
305     print "\nRun of CNID DB Maintenance script ended at "
306       . scalar(localtime) . ".\n";
307     exit(0);
308 }
309
310 sub version {
311     print "$0 version $VERSION\n";
312 }
313
314 sub help {
315     print "usage: $0 [-hlsvV]\n";
316     print "\t-h   view this message\n";
317     print "\t-l   remove transaction logs after running recovery\n";
318     print
319       "\t-s   be extra safe in verifying there are no open DB connections\n";
320     print "\t-v   print version and exit\n";
321     print "\t-V   run a verification on all database files after recovery\n";
322 }