]> arthur.barton.de Git - netatalk.git/blob - bin/cnid/cnid_maint.in
Automatically detect the temp directory. The tmpdir() code is taken from
[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 2002-02-07 21:45:07 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 ($extra_safe) {
170         error( 0,
171             "Checking database environment $dbpath for open connections..." );
172         unless ( open( STAT, $DB_STAT . " -h $dbpath -e |" ) ) {
173             error( 1, "Failed to open a pipe to $DB_STAT: $!" );
174             next;
175         }
176
177         # Now, check each DB environment for any open connections (db_stat calls 
178         # them as references).  If a volume has no references, we can do
179         # recovery on it.  Only check this option if the user wants to play 
180         # things extra safe.
181         my $connections = 0;
182         while (<STAT>) {
183             chomp;
184             s/\s//g;
185             if (/References\.$/) {
186                 $connections = $`;
187                 last;
188             }
189         }
190
191         close(STAT);
192
193         # Print out two different skip messages.  This is just for anality.
194         if ( $connections == 1 ) {
195             error( 1, "Skipping $dbpath since it has one active connection" );
196             next;
197         }
198
199         if ( $connections > 0 ) {
200             error( 1,
201                 "Skipping $dbpath since it has $connections active connections"
202             );
203             next;
204         }
205     }
206
207     # Run the db_recover command on the environment.
208     error( 0, "Running db_recover on $dbpath" );
209     if ( system( $DB_RECOVER . " -h $dbpath >/dev/null 2>&1" ) ) {
210         error( 1, "Failed to run db_recover on $dbpath" );
211         next;
212     }
213
214     if ($do_verify) {
215         error( 0, "Verifying $dbpath/cnid.db" );
216         if ( system( $DB_VERIFY . " -q -h $dbpath cnid.db" ) ) {
217             error( 1, "Verification of $dbpath/cnid.db failed" );
218             next;
219         }
220
221         error( 0, "Verifying $dbpath/devino.db" );
222         if ( system( $DB_VERIFY . " -q -h $dbpath devino.db" ) ) {
223             error( 1, "Verification of $dbpath/devino.db failed" );
224             next;
225         }
226
227         error( 0, "Verifying $dbpath/didname.db" );
228         if ( system( $DB_VERIFY . " -q -h $dbpath didname.db" ) ) {
229             error( 1, "Verification of $dbpath/didname.db failed" );
230             next;
231         }
232     }
233
234     if ($remove_logs) {
235
236         # Remove the log files if told to do so.
237         unless ( opendir( DIR, $dbpath ) ) {
238             error( 1, "Failed to open $dbpath for browsing: $!" );
239             next;
240         }
241
242         my $file = "";
243         while ( defined( $file = readdir(DIR) ) ) {
244             if ( $file =~ /^log\.\d+$/ ) {
245                 error( 0, "Removing $dbpath/$file" );
246                 unless ( unlink( $dbpath . "/" . $file ) ) {
247                     error( 1, "Failed to remove $dbpath/$file: $!" );
248                     next;
249                 }
250             }
251         }
252
253         closedir(DIR);
254     }
255
256 }
257
258 end();
259
260 sub tmpdir {
261     my $tmpdir;
262
263     foreach ( $ENV{TMPDIR}, "/tmp" ) {
264         next unless defined && -d && -w _;
265         $tmpdir = $_;
266         last;
267     }
268     $tmpdir = '' unless defined $tmpdir;
269     return $tmpdir;
270 }
271
272 sub error {
273     my ( $code, $msg ) = @_;
274
275     my $err_types = {
276         0 => "INFO",
277         1 => "WARNING",
278         2 => "ERROR",
279     };
280
281     print $err_types->{$code} . ": " . $msg . "\n";
282
283     end() if ( $code == 2 );
284 }
285
286 sub end {
287     if ($START_NETATALK) {
288         error( 0, "Restarting Netatalk" );
289         if ( system( $START_CMD . " >/dev/null 2>&1" ) ) {
290             print "ERROR: Failed to restart Netatalk\n";
291         }
292     }
293     if ($HOLDING_LOCK) {
294         close(LOCK);
295         unlink($LOCK_FILE);
296     }
297     print "\nRun of CNID DB Maintenance script ended at "
298       . scalar(localtime) . ".\n";
299     exit(0);
300 }
301
302 sub version {
303     print "cnid_maint.pl version $VERSION\n";
304 }
305
306 sub help {
307     print "usage: cnid_maint.pl [-hlsvV]\n";
308     print "\t-h   view this message\n";
309     print "\t-l   remove transaction logs after running recovery\n";
310     print
311       "\t-s   be extra safe in verifying there are no open DB connections\n";
312     print "\t-v   print version and exit\n";
313     print "\t-V   run a verification on all database files after recovery\n";
314 }