#!/opt/local/bin/perl # This script facilitates adding and removing 'ignore' lines from swatch.conf # V Who When What # --------------------------------------------------------------------------- # 1.1.1 skendric 04-28-2008 Handle case where silence section is empty # 1.1.0 skendric 07-27-2007 More error handling # 1.0.2 skendric 05-30-2006 Add \n to inserted line # 1.0.1 skendric 05-19-2006 Disable Sudo code # 1.0.0 skendric 05-18-2006 First version # # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/monitor/swatch # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # # This script takes the following approach: # -Accepts a string on the command-line # -Opens swatch.conf and looks for the string in the Silence Section # -If the script finds the string, it removes the string # -If the script does not find the string, it adds it # -If the script has performed either action, it stops and restarts # swatch # # # Requirements: # -The script must have file system access to the swatch directory # # -PERL modules: FHCRC::VDOPS::Utilities # # # Assumptions: # # # Tested on: # -perl-5.8.7 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Try it out # # # Caveats: # # # Known Bugs: # # # To do: # # # Begin script # Load modules use strict; use warnings; use English; use Fcntl qw(:DEFAULT :flock); use File::Copy 'cp'; use File::Stat; use File::Temp; use Getopt::Std; use Net::Syslog; use Perl6::Say; use Proc::Reliable; use FHCRC::Netops::Utilities 1.1.7; use FHCRC::Netops::NetopsData 1.1.4; # Declare global variables my $backup_dir; # Directory where backups of swatch.conf files live my $backup_file; # Name of file to which we backup # $swatch_conf prior to making changes my $flavor; # 'insert' or 'remove' my $lock_file; # Limits this script to a single instance my $rcswatch; # Location of the rcswatch file my $section_begin; # String demarcing beginning of Silence Section my $section_end; # String demarcing end of Silence Section my $silence_string; # String containing the text to be inserted/ # removed from swatch.conf my $swatch_conf; # Location of swatch.conf my $swatch_dir; # Directory holding swatch.conf my $swatch_filename; # Same as $swatch_conf but with path stripped my $swatch_group; # Group which owns the running swatch process my $swatch_conf_fh; # File handle pointing to swatch.conf my $swatch_user; # Username which owns the running swatch process my $username; # The username of the UID running this script # Define global variables $backup_dir = '/opt/local/etc/swatch/backup'; $debug = 0; $lock_file = '/opt/local/etc/swatch/silence-swatch.lock'; $program_name = 'silence-swatch'; $rcswatch = '/etc/init.d/swatch'; $section_begin = 'Begin Silence Section'; $section_end = 'End Silence Section'; $swatch_conf = '/opt/local/etc/swatch/swatch.conf'; $swatch_dir = '/opt/local/etc/swatch'; $swatch_filename = 'swatch.conf'; $swatch_group = 'local'; $swatch_user = 'tocops'; $usage = 'Usage: silence-swatch {string}'; $version = '1.1.1'; $Getopt::Std::STANDARD_HELP_VERSION = 1; # Grab arguments getopts('h', \%option); die "$usage\n" if defined $option{h}; # Install sig handlers #$SIG{__DIE__} = \&shut_down("Died unexpectedly"); ### Begin Main Program ############################################### { sanity_check(); # Look for errors in globals and args prep_run(); # Change UID, lock swatch_conf determine_flavor(); # Find out whether we are inserting or removing do_the_work(); # Gag or ungag this line clean_up(); # Erase temporary files exec "$rcswatch restart"; # Restart swatch } ##### End Main Program ################################################# ######################################################################## # Backup the current swatch.conf file ######################################################################## sub backup_swatch_conf { # Debug trace trace_location('begin') if $debug; # Define name of backup file my ($date, $time) = get_date_and_time(); $backup_file = 'swatch.conf' . $DOT . $username . $DOT . $date . $DOT . $time; # Create the backup file open my $touch, '>', "$backup_dir/$backup_file" or shut_down("Cannot create $backup_dir/$backup_file: $!"); chmod 0644, "$backup_dir/$backup_file"; close $touch or warn "Cannot close $backup_dir/$backup_file\n"; # Copy current swatch.conf to backup file cp ($swatch_conf, "$backup_dir/$backup_file") or shut_down("Cannot backup $swatch_conf to $backup_file: $!"); # Check for errors die "Unable to backup $swatch_conf to $backup_dir/$backup_file\n" unless (-s "$backup_dir/$backup_file" > 0); # Notify operator say "Copied previous $swatch_filename to $backup_file" if $debug; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Change owner uid if necessary ######################################################################## sub change_uid { my $newuser = shift; my $name; my $gid; my $uid; # Debug trace trace_location('begin') if $debug; # Find UID/GID of newuser $uid = getpwnam($newuser); $gid = (getpwnam($newuser))[3]; # If we are already running as newuser, do nothing if ($uid == $UID and $gid = $GID) { # Do nothing; } # If we are running as root, use rootly powers to change ourselves to newuser elsif ($UID == 0) { ($UID, $GID) = ($uid, $gid); ($EUID, $EGID) = ($uid, $gid); } # Double-check work unless ($uid == $UID and $gid = $GID) { shut_down("Failed to change uid/gid, $PROGRAM_NAME must run as $newuser, bailing"); } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Clean up ######################################################################## sub clean_up { my $error = "Cannot delete $lock_file; this means that no one can run $PROGRAM_NAME until $lock_file has been removed\n"; # Debug trace trace_location('begin') if $debug; # Remove lock file if (-e $lock_file) { unlink $lock_file or warn $error; } # Close swatch.conf if (defined $swatch_conf_fh) { if (defined fileno($swatch_conf_fh)) { close $swatch_conf_fh or warn "Cannot close $swatch_conf: $!"; } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Figure out whether we are inserting or removing ######################################################################## sub determine_flavor { my $found_begin = 0; my $found_end = 0; my $found_string = 0; # Debug trace trace_location('begin') if $debug; # Do the work LINE: while (my $line = <$swatch_conf_fh>) { # If we found the beginning of the Silence Section, record the event if ($line =~ /$section_begin/) { $found_begin = 1; next LINE; } # If we found the end of the Silence Section, bail if ($line =~ /$section_end/) { $found_end = 1; last LINE; } # If we haven't found the beginning of the Silence Section yet, skip ahead next LINE unless $found_begin; # We have found the Silence Section: record success if we find the string $found_string++ if $line =~ /$silence_string/; } # Check for errors shut_down("Malformed Silence Section in $swatch_conf") unless ($found_begin and $found_end); shut_down("Multiple strings found") if $found_string > 1; # Set flavor $flavor = $found_string ? 'remove' : 'insert'; # Debug info say "Flavor = $flavor" if $debug; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Insert string ######################################################################## sub do_the_work { my $tmpfh; my $found_begin = 0; my $found_end = 0; my $tmpfile; my $handled_string = 0; # Debug trace trace_location('begin') if $debug; # Create temporary file $tmpfh = File::Temp->new( UNLINK => 1 ); $tmpfile = $tmpfh->filename; # Do the work seek($swatch_conf_fh, 0, 0); LINE: while (my $line = <$swatch_conf_fh>) { # If we have already inserted/removed $silence_string, write the current # line and skip ahead if ($handled_string) { print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); next LINE; } # If this line is the beginning of the Silence Section, record the event if ($line =~ /$section_begin/) { $found_begin = 1; say ' Found beginning of silence section' if $debug; print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); next LINE; } # If this line is the end of the Silence Section, record the event if ($line =~ /$section_end/) { $found_end = 1; say ' Found ending of silence section' if $debug; print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); next LINE; } # If we are processing the Silence Section, either insert or remove if ($found_begin and not $found_end and $flavor eq 'insert') { say " Writing $silence_string" if $debug; print $tmpfh "ignore=/$silence_string/\n" or shut_down("Cannot write to $tmpfile: $!"); print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); $handled_string = 1; } elsif ($found_begin and not $found_end and $flavor eq 'remove') { if ($line =~ /$silence_string/) { say " Erasing $silence_string" if $debug; # Don't bother writing the line $handled_string = 1; } else { # We are walking the Silence Section but haven't yet found # the silence string say ' Walking silence section' if $debug; print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); } } else { print $tmpfh $line or shut_down("Cannot write to $tmpfile: $!"); } } # Close swatch.conf close $swatch_conf_fh or warn "Cannot close $swatch_conf: $!"; # Flush data to tmpfile close $tmpfh or warn "Cannot close $tmpfile: $!"; # Check for errors shut_down("Made no changes to $swatch_conf") unless $handled_string; # Backup current swatch_conf file backup_swatch_conf(); # Overwrite swatch.conf with temporary file cp ($tmpfile, $swatch_conf) or shut_down("Cannot copy $tmpfile to $swatch_conf: $!"); # Check for errors unless (-s $swatch_conf > 0) { say "New $swatch_conf is empty, attempting to recover"; cp ($backup_dir/$backup_file, $swatch_conf) or shut_down("Unable to recover $swatch_conf from $backup_file"); shut_down("Attempted to recover $swatch_conf but failed") unless (-s $swatch_conf > 0); shut_down("Recovered $swatch_conf but unable to silence/unsilence"); } # Notify operator if ($flavor eq 'insert') { say "Inserted 'ignore $silence_string' in swatch.conf"; log_it("$username inserted $silence_string into swatch_conf"); } elsif ($flavor eq 'remove') { say "Removed 'ignore $silence_string' from swatch.conf"; log_it("$username removed ignore $silence_string from swatch_conf"); } # Clean up unlink $tmpfile or warn "Cannot delete $tmpfile: $!"; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Perform set-up work ######################################################################## sub prep_run { # Debug trace trace_location('begin') if $debug; # Save current owner $username = getpwuid($UID); # Change UID change_uid($swatch_user); # Create lock file open my $TOUCH, '>', $lock_file or shut_down("Cannot create $lock_file: $!"); close $TOUCH or warn "Cannot close $lock_file: $!"; # Lock swatch.conf open $swatch_conf_fh, '<', $swatch_conf or shut_down("Cannot open $swatch_conf: $!"); unless (flock ($swatch_conf_fh, LOCK_EX|LOCK_NB) ) { warn "Cannot immediately write-lock $swatch_conf ($!), blocking ..."; sleep 5; unless (flock($swatch_conf_fh, LOCK_EX)) { shut_down("Cannot get write-lock on $swatch_conf: $!"); } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Restart swatch (re-read its config file) # This isn't working yet. --sk ######################################################################## sub restart_swatch { my ($p, $output, $error, $status, $msg); # Debug trace trace_location('begin') if $debug; # Do it $p = Proc::Reliable->new('num_tries' => 0, 'time_per_try' => 5, 'time_btw_tries' => 2); ($output, $error, $status, $msg) = $p->run("$rcswatch restart"); # Print the results say $output; say $error if defined $error; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Check for various error conditions ######################################################################## sub sanity_check { # Debug trace trace_location('begin') if $debug; # Check arguments $silence_string = join($SPACE, @ARGV); die "$usage\n" unless defined $silence_string; die "$usage\n" if $silence_string eq $EMPTY_STR; die "$usage\n" if $silence_string =~ /^\s{1,10}$/; die "$usage\n" if length($silence_string) > 50; # Check swatch.conf die "$swatch_conf doesn't exist\n" unless -e $swatch_conf; die "$swatch_conf isn't readable\n" unless -r $swatch_conf; die "$swatch_conf isn't writeable\n" unless -w $swatch_conf; # Check rcswatch die "$rcswatch doesn't exist\n" unless -e $rcswatch; die "$rcswatch isn't executable\n" unless -x $rcswatch; # Check directories die "$backup_dir doesn't exist\n" unless -e $backup_dir; die "$backup_dir isn't readable\n" unless -r $backup_dir; die "$backup_dir isn't writeable\n" unless -w $backup_dir; die "$backup_dir isn't executable\n" unless -x $backup_dir; # One user at a time if (-e $lock_file) { my ($owner, $stat); $stat = File::Stat->new($lock_file); $owner = getpwuid($stat->uid); die "$owner is running $PROGRAM_NAME; see $lock_file\n" } # Is anyone editing swatch.conf? my $vi_lock = $swatch_dir . '/' . '.' . $swatch_filename. '.swp'; if (-e $vi_lock) { my ($owner, $stat); $stat = File::Stat->new($vi_lock); $owner = getpwuid($stat->uid); die "$owner is editing $swatch_conf\n" } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Handle die or kills ######################################################################## sub shut_down { my $msg = shift; clean_up(); die "$msg\n"; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { say "$usage\n"; say " where {string} is the string which swatch will ignore or, if"; say " already present in swatch.conf, the string which swatch will"; say " quit ignoring."; return 1; } ######################################################################## # Output version ######################################################################## sub VERSION_MESSAGE { say "$program_name v$version"; return 1; }