Mail::Toaster::Utility - a collection of utility subroutines for sysadmin tasks
use Mail::Toaster::Utility; my $utility = Mail::Toaster::Utility->new;
$utility->file_write($file, @lines);
This is just one of the many handy little methods I have amassed here. Rather than try to remember all of the best ways to code certain functions and then attempt to remember them, I have consolidated years of experience and countless references from Learning Perl, Programming Perl, Perl Best Practices, and many other sources into these subroutines.
This Mail::Toaster::Utility package is my most frequently used one. Peruse through the list of methods and surely you too can find something of use. Each method has its own documentation but in general, all methods accept as input a hashref with at least one required argument and a number of optional arguments.
All methods set and return error codes (0 = fail, 1 = success) unless otherwise stated.
Unless otherwise mentioned, all methods accept two additional parameters:
debug - to print status and verbose error messages, set debug=>1. fatal - die on errors. This is the default, set fatal=>0 to override.
Perl. Scalar::Util - built-in as of perl 5.8
Almost nothing else. A few of the methods do require certian things, like archive_expand requires tar and file. But in general, this package (Mail::Toaster::Utility) should run flawlessly on any UNIX-like system. Because I recycle this package in other places (not just Mail::Toaster), I avoid creating dependencies here.
############################################ # Usage : use Mail::Toaster::Utility; # : my $utility = Mail::Toaster::Utility->new; # Purpose : create a new Mail::Toaster::Utility object # Returns : a bona fide object # Parameters : none ############################################
############################################ # Usage : my $answer = $utility->answer( # question => "Would you like fries with that", # default => "SuperSized!", # timeout => 30 # ); # Purpose : prompt the user for information # # Returns : S - the users response (if not empty) or # : S - the default answer or # : S - an empty string # # Parameters # Required : S - question - what to ask # : S - q - a programmer friendly alias for question # Optional : S - default - a default answer # : I - timeout - how long to wait for a response # Throws : no exceptions # See Also : yes_or_no
############### archive_expand ################## # Usage : $utility->archive_expand( # : archive => 'example.tar.bz2' ); # Purpose : test the archiver, determine its contents, and then # use the best available means to expand it. # Returns : 0 - failure, 1 - success # Parameters : S - archive - a bz2, gz, or tgz file to decompress
############ chdir_source_dir ################### # Usage : $utility->chdir_source_dir( dir=>"/usr/local/src" ); # Purpose : prepare a location to build source files in # Returns : 0 - failure, 1 - success # Parameters : S - dir - a directory to build programs in
######### check_homedir_ownership ############ # Usage : $utility->check_homedir_ownership(); # Purpose : repair user homedir ownership # Returns : 0 - failure, 1 - success # Parameters : # Optional : I - auto - no prompts, just fix everything # See Also : sysadmin
Comments: Auto mode should be run with great caution. Run it first to see the results and then, if everything looks good, run in auto mode to do the actual repairs.
$utility->file_chmod( file_or_dir => '/etc/resolv.conf', mode => '0755', sudo => $sudo )
arguments required: file_or_dir - a file or directory to alter permission on mode - the permissions (numeric)
arguments optional: sudo - the output of $utility->sudo fatal - die on errors? (default: on) debug
result: 0 - failure 1 - success
############### chown_system ################# # Usage : $utility->chown_system( dir=>"/tmp/example", user=>'matt' ); # Purpose : change the ownership of a file or directory # Returns : 0 - failure, 1 - success # Parameters : S - dir - the directory to chown # : S - user - a system username # Optional : S - group - a sytem group name # : I - recurse - include all files/folders in directory? # Comments : Uses the system chown binary # See Also : n/a
############## clean_tmp_dir ################ # Usage : $utility->clean_tmp_dir( dir=>$dir ); # Purpose : clean up old build stuff before rebuilding # Returns : 0 - failure, 1 - success # Parameters : S - $dir - a directory or file. # Throws : no exceptions # Comments : Running this will delete its contents. Be careful!
############# drives_get_mounted ############ # Usage : my $mounts = $utility->drives_get_mounted(); # Purpose : Uses mount to fetch a list of mounted drive/partitions # Returns : a hashref of mounted slices and their mount points.
############### file_archive ################# # Purpose : Make a backup copy of a file by copying the file to $file.timestamp. # Usage : my $archived_file = $utility->file_archive( file=>$file ); # Returns : the filename of the backup file, or 0 on failure. # Parameters : S - file - the filname to be backed up # Comments : none
$utility->file_chown( file_or_dir => '/etc/resolv.conf', uid => 'root', gid => 'wheel', sudo => 1 );
arguments required: file_or_dir - a file or directory to alter permission on uid - the uid or user name gid - the gid or group name
arguments optional: file - alias for file_or_dir dir - alias for file_or_dir sudo - the output of $utility->sudo fatal - die on errors? (default: on) debug
result: 0 - failure 1 - success
############################################ # Usage : $utility->file_delete( file=>$file ); # Purpose : Deletes a file. # Returns : 0 - failure, 1 - success # Parameters # Required : file - a file path # Comments : none # See Also :
Uses unlink if we have appropriate permissions, otherwise uses a system rm call, using sudo if it is not being run as root. This sub will try very hard to delete the file!
$utility->file_get( url=>$url, debug=>1 );
Use the standard URL fetching utility (fetch, curl, wget) for your OS to download a file from the $url handed to us.
arguments required: url - the fully qualified URL
arguments optional: timeout - the maximum amount of time to try fatal debug
result: 1 - success 0 - failure
my @lines = $utility->file_read( file=>$file, max_lines=>100 )
arguments required: file - the file to read in
arguments optional: max_lines - integer - max number of lines max_length - integer - maximum length of a line fatal debug
result: 0 - failure success - returns an array with the files contents, one line per array element
usage: my @lines = "1", "2", "3"; # named array $utility->file_write ( file=>"/tmp/foo", lines=>\@lines ); or $utility->file_write ( file=>"/tmp/foo", lines=>['1','2','3'] ); # anon arrayref
required arguments: file - the file path you want to write to lines - an arrayref. Each array element will be a line in the file
arguments optional: fatal debug
result: 0 - failure 1 - success
$utility->files_diff( f1=>$file1,f2=>$file2,type=>'text',debug=>1 );
if ( $utility->files_diff( f1=>"foo", f2=>"bar" ) ) { print "different!\n"; };
required arguments: f1 - the first file to compare f2 - the second file to compare
arguments optional: type - the type of file (text or binary) fatal debug
result: 0 - files are the same 1 - files are different -1 - error.
If the etc dir and file name are provided and the file exists, use it.
If that fails, then go prowling around the drive and look in all the usual places, in order of preference:
/opt/local/etc/ /usr/local/etc/ /etc
Finally, if none of those work, then check the working directory for the named .conf file, or a .conf-dist.
Example: my $twconf = $utility->find_config ( file => 'toaster-watcher.conf', etcdir => '/usr/local/etc', )
arguments required: file - the .conf file to read in
arguments optional: etcdir - the etc directory to prefer debug fatal
result: 0 - failure the path to $file
$utility->find_the_bin( program=>'dos2unix', dir=>'/opt/local/bin' );
Example:
my $apachectl = $utility->find_the_bin( program=>"apachectl", dir=>"/usr/local/sbin" );
arguments required: program - the name of the program (its filename)
arguments optional: dir - a directory to check first fatal debug
results: 0 - failure success will return the full path to the binary.
$utility->is_process_running($process) ? print "yes" : print "no";
$process is the name as it would appear in the process table.
############################################ # Usage : $utility->is_readable( file=>$file ); # Purpose : ???? # Returns : 0 = no (not reabable), 1 = yes # Parameters : S - file - a path name to a file # Throws : no exceptions # Comments : none # See Also : n/a
result: 0 - no (file is not readable) 1 - yes (file is readable)
############################################ # Usage : $utility->is_writable(file =>"/tmp/boogers"); # Purpose : make sure a file is writable # Returns : 0 - no (not writable), 1 - yes (is writeable) # Parameters : S - file - a path name to a file # Throws : no exceptions
############ fstab_list ################### # Usage : $utility->fstab_list; # Purpose : Fetch a list of drives that are mountable from /etc/fstab. # Returns : an arrayref # Comments : used in backup.pl # See Also : n/a
$utility->get_dir_files( dir=>$dir, debug=>1 )
required arguments: dir - a directory
optional arguments: fatal debug
result: an array of files names contained in that directory. 0 - failure
$utility->get_the_date( bump=>$bump, debug=>$debug )
required arguments: none
optional arguments: bump - the offset (in days) to subtract from the date. debug
result: (array with the following elements) $dd = day $mm = month $yy = year $lm = last month $hh = hours $mn = minutes $ss = seconds
my ($dd, $mm, $yy, $lm, $hh, $mn, $ss) = $utility->get_the_date();
$utility->install_if_changed( newfile => '/etc/resolv.conf.new'; existing => '/etc/resolv.conf'; mode => '0755', uid => 89, gid => 89, );
arguments required newfile existing
arguments optional uid - gid - mode - file permissions mode (numeric: 0755) debug - clean - int - delete the newfile after installing it? notify- int - send notification upon updates? email - email address to send notifications (default: root)
results: 0 = error (failure) 1 = success 2 = success, no update required
usage:
$utility->install_from_source( package => 'simscan-1.07', site => 'http://www.inter7.com', url => '/simscan/', targets => ['./configure', 'make', 'make install'], patches => '', debug => 1, );
Downloads and installs a program from sources.
required arguments: conf - hashref - mail-toaster.conf settings. site - url - package -
optional arguments: targets - arrayref - defaults to [./configure, make, make install]. patches - arrayref - patch(es) to apply to the sources before compiling patch_args - source_sub_dir - a subdirectory within the sources build directory bintest - check the usual places for an executable binary. If found, it will assume the software is already installed and require confirmation before re-installing. debug fatal
result: 1 - success 0 - failure
$utility->is_arrayref($testme, $debug);
Enable debugging to see helpful error messages.
$utility->is_hashref($hashref, $debug);
$utility->logfile_append( file=>$file, lines=>\@lines )
Pass a filename and an array ref and it will append a timestamp and the array contents to the file. Here's a working example:
$utility->logfile_append( file=>$file, prog=>"proggy", lines=>["Starting up", "Shutting down"] )
That will append a line like this to the log file:
2004-11-12 23:20:06 proggy Starting up 2004-11-12 23:20:06 proggy Shutting down
arguments required: file - the log file to append to prog - the name of the application lines - arrayref - elements are events to log.
arguments optional: fatal debug
result: 1 - success 0 - failure
$utility->mailtoaster();
Downloads and installs Mail::Toaster.
my ($up1dir, $userdir) = $utility->path_parse($dir)
Takes a path like ``/usr/home/matt'' and returns ``/usr/home'' and ``matt''
You (and I) should be using File::Basename instead as it is more portable.
Example: my $tconf = $utility->parse_config( file=>'toaster.conf' );
required parameters: file - a configuration file to load settings from etcdir - where to look for $file - defaults to /usr/local/etc also checks the current working directory.
optional parameters: debug fatal
result: a hashref with the key/value pairs. 0 - failure
$pidfile = $utility->pidfile_check( pidfile=>"/var/run/program.pid" );
The above example is all you need to do to add process checking (avoiding multiple daemons running at the same time) to a program or script. This is used in toaster-watcher.pl and rrdutil. toaster-watcher normally completes a run in a few seconds and is run every 5 minutes.
However, toaster-watcher can be configured to do things like expire old messages from maildirs and feed spam through a processor like sa-learn. This can take a long time on a large mail system so we don't want multiple instances of toaster-watcher running.
result: the path to the pidfile (on success).
Example:
my $pidfile = $utility->pidfile_check( pidfile=>"/var/run/changeme.pid" ); unless ($pidfile) { warn "WARNING: couldn't create a process id file!: $!\n"; exit 0; };
do_a_bunch_of_cool_stuff; unlink $pidfile;
Example: $utility->regexp_test( exp => 'toast', string => 'mailtoaster rocks', );
arguments required: exp - the regular expression string - the string you are applying the regexp to
result: printed string highlighting the regexp match
Usage:
$utility->source_warning( package => "Mail-Toaster-4.10", clean => 1, src => "/usr/local/src" );
arguments required: package - the name of the packages directory
arguments optional: src - the source directory to build in (/usr/local/src) clean - do we try removing the existing sources? (enabled) timeout - how long to wait for an answer (60 seconds)
result: 1 - removed 0 - failure, package exists and needs to be removed.
usage: $self->sources_get( conf => $conf, package => 'simscan-1.07', site => 'http://www.inter7.com', url => '/simscan/', )
arguments required: package - the software package name site - the host to fetch it from url - the path to the package on $site
arguments optional: conf - hashref - values from toaster-watcher.conf debug
This sub proved quite useful during 2005 as many packages began to be distributed in bzip format instead of the traditional gzip.
my $sudo = $utility->sudo();
$utility->syscmd( command=>"$sudo rm /etc/root-owned-file" );
Often you want to run a script as an unprivileged user. However, the script may need elevated privileges for a plethora of reasons. Rather than running the script suid, or as root, configure sudo allowing the script to run system commands with appropriate permissions.
If sudo is not installed and you're running as root, it'll offer to install sudo for you. This is recommended, as is properly configuring sudo.
arguments required:
arguments optional: debug
result: 0 - failure on success, the full path to the sudo binary
Just a little wrapper around system calls, that returns any failure codes and prints out the error(s) if present. A bit of sanity testing is also done to make sure the command to execute is safe.
my $r = $utility->syscmd( command=>"gzip /tmp/example.txt" ); $r ? print "ok!\n" : print "not ok.\n";
arguments required: command - the command to execute cmd - alias of command
arguments optional: debug fatal
result the exit status of the program you called.
my $r = $utility->yes_or_no( question => "Would you like fries with that?", timeout => 30 );
$r ? print "fries are in the bag\n" : print "no fries!\n";
arguments required: none.
arguments optional: question - the question to ask timeout - how long to wait for an answer (in seconds)
result: 0 - negative (or null) 1 - success (affirmative)
Matt Simerson (matt@tnpi.net)
None known. Report any to author.
make all errors raise exceptions write test cases for every method comments. always needs more comments.
The following are all man/perldoc pages:
Mail::Toaster Mail::Toaster::Conf toaster.conf toaster-watcher.conf
http://mail-toaster.org/
Copyright (c) 2003-2006, The Network People, Inc. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the The Network People, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.