#! /local/bin/perl # (C) Copyright 1996 Rahul Dhesi, All rights reserved. # Permission for copying and creation of derivative works is granted, # provided this copyright notice is preserved, to anybody who # does not discriminate against the copyright owner. # $Source: /local/undoc/RCS/watchdisk,v $ # $Id: watchdisk,v 1.16 1996/11/03 11:32:49 rdroot Exp $ # # Watch filesystems, and invoke a program if a filesystem is too full. # In typical use, this program will be periodically invoked from your # crontab. Give it as argument one or more directory names. Ideally # each specified directory will be the mount point of a filesystem. # It will read a .watchtab file in each directory and check for free # space as specified in lines in that file, taking any specified # actions depending on how low the disk space is. Sample line # in crontab: # # 20 * * * * /usr/local/bin/watchdisk / /usr /etc /home /var/spool # # The above line causes watchdisk to run once an hour and check # .watchtab files in /, /usr, /etc, /home, and /var/spool, looking # for filesystems low on disk space. See below for .watchtab file # syntax. # # Tested only with perl 4.036. # path used only for invoking programs from .watchtab $ENV{'PATH'} = '/local/scripts:/local/bin:/usr/ucb:/usr/bin:/local/undoc'; $myname = "watchdisk"; $RCSHEADER = '$Source: /local/undoc/RCS/watchdisk,v $' . "\n" . '$Id: watchdisk,v 1.16 1996/11/03 11:32:49 rdroot Exp $'; ## begin configuration section ## # (see also subroutine df for how df output is parsed) # name of watchtab file to be consulted $WATCHTAB = '.watchtab'; # how to invoke df $DF = '/local/bin/df'; ## end configuration section ## $usage = "usage: $myname [-vtx] directory ... (or -h for help)"; if ($ARGV[0] =~ "^-.+" ) { require "getopts.pl"; &Getopts("vtxh"); } $debug = $opt_x; $trace = $opt_t; $verbose = $debug || $trace || $opt_v; # suppresss perl warnings ($capacity && $device && $kbytes && $mounted && $opt_h && $opt_v && $opt_t && $opt_x && $used); if ($opt_h) { &givehelp(); exit(0); } (@ARGV < 1) && &usage_error; # program goes here for $dir (@ARGV) { &process($dir); } sub process { local($dir) = @_; $watchtab = "$dir/$WATCHTAB"; if (!open(F, $watchtab)) { warn "$myname: error: can't open $watchtab for reading: $!\n"; return; } # Read and parse watchtab file. A # in the first column begins # a comment. Non-comment lines are of the form: # free cmd # 'free' is the free space, in megabytes, any numeric format that # perl can use in expressions, e.g. integer or float. # 'cmd' is the command to be given to /bin/sh. Before execution # any instances of %FREE% will be replaced by the actual # free space and %DIR% will be replaced by the value of # $dir. Current directory is changed to $dir before executing # the command. # For each line in which actual free space is less than 'free', # the command is executed. However, once a line is skipped # all remaining lines are skipped. # Disk space is checked afresh before each command. So one command # line may clean up some space, and others may clean up more. # # Here is an example .watchtab file: # ### BEGIN .watchtab file ### # # if less than 100 megs free, clean up older files # 100 find . -mtime +3 -exec rm -f {} \; # # if still not sufficient free space, do a faster clean up # 100 find . -mtime +2 -exec rm -f {} \; # # Must have at least 50 megs free now, else page operator # 50 sendpage "URGENT: only %FREE% M free on %DIR%, cleanup failed" # ### END .watchtab file ### # Here is a real .watchtab file in use at a2i. We use this in a # filesystem that holds mostly log files. Various files are pruned # as needed to keep the filesystem from filling up. # ### BEGIN .watchtab file ### # 20 cd news.inn.log/OLD; rm -f log.*.Z # 20 cd var.log; rm -f *.[2-9] # 20 cd var.log; rm -f *.1 # 20 cd var.log; rm -f *.0 # 20 cd var.log; cp /dev/null syslog # 20 cd var.log; cp /dev/null messages # 10 cd news.inn.log; cp /dev/null log # 10 rsh bolero sendpage "hustle: panic: %DIR% has %FREE% M free: help" # ### END .watchtab file ### $debug && print "> reading $watchtab\n"; while () { /^#/ && next; /^\s*$/ && next; chop; ($free, $cmd) = split(' ', $_, 2); $debug && print "\n>> [$free] [$cmd]"; $megs = &df($dir); if ($megs < $free) { $debug && print ">> ${megs}M < $free, WILL EXECUTE COMMAND\n"; $cmd =~ s/%FREE%/$megs/g; $cmd =~ s/%DIR%/$dir/g; $verbose && print "$dir (${megs}M < $free) $cmd\n"; $trace || system('/bin/sh', '-c', "cd $dir; $cmd"); } else { $debug && print "> NO ACTION\n"; last; } } } # Get df output. This subroutine is system-specific. Rewrite it to # suit your system. # # $dir(input): a directory name on which to do 'df' # $DF(global, input): full pathname of df program # function value(output): megabytes free # # $debug is honored. sub df { local($dir) = @_; # get df output for $dir open(DF, "$DF $dir|"); $debug && print "\n> $DF $dir\n"; while () { /^Filesystem/ && next; ($device, $kbytes, $used, $avail, $capacity, $mounted) = split(' ', $_); $debug && print ">> $_"; last; } $megs = int(($avail / 1024) + 0.5); $debug && print ">> $megs megabytes free\n"; $megs; } sub usage_error { local($msg) = @_; if ($msg) { die "$msg\n"; } else { die "$usage\n"; } } sub givehelp { ## require 'local/page.pl'; ## &page(<