#!/usr/bin/perl
#========================================================================
# autonice ver 1.0p1 by Ken Haste Andersen (kenand@nbi.dk).
#
# TODO: 
# o  A jiffy should be defined by HZ, found in /usr/include/asm/param.h.
#    On intel machines a jiffy is 1/100 sec, on alphas 1/1024 sec.
# o  It should write into a std.logfile instead of its own logfile.
# o  The functionality of the script should be included into the (as of
#    now useless) nicetool or (better) into the quota system.
# o  It should detect and remove dead processes that use full CPU-time.
#
# 1198.
#
#========================================================================

#
# This script should only be run by root:
#
if ($< ne 0) {
    print "autonice should only be run by root.\n";
    exit;
}
#
# Global declarations:
#
$conffile = "/etc/autonice.conf"; # Default location of the configuration file.
$maxcputime = 180000; # half an hour (intel), alpha: 1843200
$minnice = 10; # The nice value for long processes.
$message = "/etc/autonice.message"; # The Message of Punishment.
$ccto = root; # Carbon copy of message
$logfile = "/var/log/autonice";
#
# Read configuration for evt. override of the above defaults:
#
if (open(infile, $conffile)) {
    @lines = <infile>;
    for ($i = -1; $i<$#lines; $i++) {
	chop($lines[$i]);
	($command, $value) = split(" ",$lines[$i]);
	if (($command ne "#") && ($command ne "")) { # ignore hashed lines
	    if ($command eq "maxcputime") {$maxcputime = $value }
	    elsif ($command eq "minnice") {$minnice = $value }
	    elsif ($command eq "message") {$message = $value }
	    elsif ($command eq "ccto") {$ccto = $value }
	    elsif ($command eq "logfile") {$logfile = $value }
	    else { 
		print "The command $command in $conffile line $i is not supported.\n";
	    }
	}
    }
    close(infile);
}
#
# Parse command-line:
#
if ($#ARGV == -1) { $argument=""; } # If no argument is given default is "check".
if ($#ARGV eq 0) { $argument=$ARGV[0]; }

if (($argument ne "start")&&($argument ne "check")&&($argument ne "stop")&&($argument ne "")) {
    print "Usage: autonice [start|check|stop]\n";
    exit;
}
#======================================================
# Add autonice to /etc/crontab
#======================================================
if ($argument eq "start") {
print "test\n";
    `echo "# Run autonice:" >> /etc/crontab`;
    `echo "* * * * * root /usr/sbin/autonice >> $logfile" >> /etc/crontab`;
    `echo "" >> /etc/crontab`;
    print "Autonice added to /etc/crontab.\n";
}
#======================================================
# Remove autonice from /etc/crontab
#======================================================
elsif ($argument eq "stop") {
    @lines = `cat /etc/crontab`;
    `mv /etc/crontab /etc/crontab.bak`;
    for ($i = -1; $i<$#lines; $i++) {
	($first) = split(":", $lines[$i]);
	if ($first eq "# Run autonice") {
	    print "Removed the lines:\n$lines[$i]$lines[$i+1]from /etc/crontab.\n";
	    $i = $i + 1; }
	else {
	    chop($lines[$i]);
	    `echo '$lines[$i]' >> /etc/crontab`;
	}
    }	    
    print "\nAutonice have been removed from /etc/crontab.\n";
    print "Old /etc/crontab is in /etc/crontab.bak.\n";
}
else {
#======================================================
# Check:
#======================================================

#
# Read the contents of /proc:
#
@proc = `ls /proc`;
$renice = 0; # flag to see if anything have been done in the end.
 
for ($i = -1; $i<$#proc; $i++) {
    chop($proc[$i]);
    if ($proc[$i] =~ /(\d+?)/) {
	#
	# Get status-file of the process:
	#
	if (open(infile, "/proc/$proc[$i]/status")) {
	    @lines = <infile>;
	    ($temp, $temp, $uid) = split(" ", $lines[4]);
	    if ($uid > 100) {
		#
		# Get stat-file of the process:
		#
		if (open(statfile, "/proc/$proc[$i]/stat")) {
		    $lines = <statfile>;
		    ($pid, $comm, $state, $ppid, $pgrp, $session, $tty, $tpgid, $flags, $minflt, $cminflt, $majflt, $cmajflt, $utime, $stime, $cutime, $cstime, $counter, $priority, $timeout, $itrealvalue, $starttime, $vsize, $rss, $rlim, $startcode, $endcode, $startstack, $kstkesp, $signal, $blocked, $sigignore, $sigcatch, $wchan) = split(" ",$lines);
		    #
		    # Check if the user is an abuser of CPU time:
		    #
		    if (($utime+$stime > $maxcputime) && ($priority < $minnice)) {
			# print STDERR "Filnavn: $comm pid: $pid uid: $uid time $utime (in 1/1000 secs) nice $priority\n";
			#
			# Get username from /etc/passwd:
			#
			@passwd = `cat /etc/passwd`;
			$j = -1;
			do {
			    $j = $j + 1;
			    chop($passwd[$j]);
			    ($name, $pass, $passwduid) = split(":", $passwd[$j]);
			} until (($passwduid==$uid)||($j==$#passwd));
			if ($passwduid ne $uid) {
			    print "Username for uid: $uid not found.";
			    $name = root;
			}
			#
			# Send mail to user and cc to root:
			#
			$hostname = `cat /etc/HOSTNAME`;
			chop($hostname);
			`sed -e "s/USERNAME/$name/" -e "s/PROCNAME/$comm/" -e "s/PID/$pid/" -e "s/HOSTNAME/$hostname/" $message |  mail -s "Process reniced" -c $ccto $name`; #-c $ccto $name`;
			`renice $minnice $pid`;
			$date = `date`;
			chop($date);
			print "$date: Reniced $comm for $name.\n";
			$renice = 1;
		    }
		}
	    }
	    close(infile);
	}
    }
}
# If autonice was called with the check option, print out a message, even
# if nothing has been done.
if (($argument eq "check") && ($renice eq 0)) {
    print "Nothing to renice.\n";
}
}





















