#!/usr/bin/perl -w

# STAMOS 0.3 Some Things About My Own Server

# written 04/02/1999-04/10/1999 by Knut Grahlmann <Knut.Grahlmann@bigfoot.com>

# this script reads from various sources information about the system and puts it
# into a HTML-file

# this script is published under the terms of the GPL, see LICENSE for further information


use Getopt::Long;

$STAMOS_version="0.3";

$date=`date`; 		# the output of the date command
$number_of_hds=0;

# declare these up here as empty, so that they don't produce
# "Use of uninitialized value" when I test if they are declared
$uptimed_path="";
$output_file="";
$config_file="";
$hostname="";
$CPU="";
$MEM="";
$OS="";
$own_body="";
$own_start="";
$own_end="";
$own_style="";
$own_table="";
$manufacturer="";
$model="";
$overwrite=0;
$quiet=0;
$verbose=0;
$help=0;
$NFS_occured=0;

# gets commandline parameters
&parameters;

# is the name of a configfile provided? is there one in /etc?
# if so, read it and get the parameters out of it
if ($config_file ne "") {&configfile;}
else {
	if (-e "/etc/stamos.conf") {
		$config_file="/etc/stamos.conf";
		&configfile;
	}
}


# and now, the commercial :-)
if ($quiet!=1) {
	print "STAMOS $STAMOS_version  Some Things About My Own Server                    by Knut Grahlmann\n\n";
}


# without these two parameters, the program can't work
if ($uptimed_path eq "") {
	&help;
	die "You have to provide the path of the uptimed programs. See README if you have no clue what uptimed is.\n";
}
if ($output_file eq "") {
	&help;
	die "You have to provide the name and the full path of the output file.\n";
}

# some users aren't very smart
if ($quiet==1 && $verbose==1) {
	print "You said that I should be quiet and verbose. I guess, I should be quiet after\n";
	print "this message.\n";
	$verbose=0;
}

# I don't want a path looking like /opt/uptimed//uprecords; naw g
if (substr($uptimed_path,length($uptimed_path),1) eq "/") {
	$uprecords=$uptimed_path."uprecords";
}
else {
	$uprecords=$uptimed_path."/uprecords";
}

# finds most of the stuff (/proc-files)
&find_specs;

# a bit more complicated stuff, let's give it its own sub
&disks;

# same for the uptime
&uptime;

################TODO#########################
# is a language_file provided, yes: read it, no define it yourself
################TODO#########################

# generates the HTML output
&output;

if ($quiet!=1) {
	print "Generated $output_file.\n";
	print "Have a nice day...\n";
}
exit 0;
################# end of the program


# reads the configfile
sub configfile {
# list of all valid options
# and don't ask me why it needs to be in alphabetical order!
@option_list=qw(CPU hostname HTML_body HTML_end HTML_start HTML_style HTML_table manufacturer model OS output_file overwrite quiet RAM uptimed_path verbose);

# file present
if (!(-e $config_file)) {
	die "The configuration file doesn't exist.";
}

open (CONFIGFILE,$config_file) || die "Could not open $config_file: $!";

while (<CONFIGFILE>) {
	# we don't need empty lines and such starting with #
	if (substr($_,0,1) eq "#" || length($_)==1) { # empty lines contain \n, so 1
		next;
	}

	# no \n, get everything until the first space and kill spaces
	chomp;
	$position_space=index($_," ");
	$option=substr($_,0,$position_space);
	$option=~ tr/ //d;

	# get the real option value
	$value=substr($_,$position_space+1);

	# checks if empty parameter
	if ($value eq "") { next;}

	# converts yes/no into 1/0
	if ($value eq "yes") {$value=1;}
	if ($value eq "no") {$value=0;}

	$length_array=@option_list;

	# +1 so that I can check if an invalid option was provided
	# (if the option would be valid, num_option can't become
	# bigger that @options
	for ($num_option=0;$num_option<=($length_array-1);++$num_option) {
		if ($option eq $option_list[$num_option]) {
			if ($num_option==0) {$CPU=$value;last;}
			if ($num_option==1) {$hostname=$value;last;}
			if ($num_option==2) {$own_body=$value;last;}
			if ($num_option==3) {$own_end=$value;last;}
			if ($num_option==4) {$own_start=$value;last;}
			if ($num_option==5) {$own_style=$value;last;}
			if ($num_option==6) {$own_table=$value;last;}
			if ($num_option==7) {$manufacturer=$value;last;}
			if ($num_option==8) {$model=$value;last;}
			if ($num_option==9) {$OS=$value;last;}
			# some options can be overridden with
			# commandline parameters
			if ($num_option==10) {
				if ($output_file eq "") { $output_file=$value;}
				last;
			}
			if ($num_option==11) {
				if ($overwrite==0) {$overwrite=$value;}
				last;
			}
			if ($num_option==12) {
				if ($quiet==0) {$quiet=$value;}
				last;
			}
			if ($num_option==13) {$MEM=$value;last;}
			if ($num_option==14) {
				if ($uptimed_path eq "") {$uptimed_path=$value;}
				last;
			}
			if ($num_option==15) {
				if ($verbose==0) {$verbose=$value;}
				last;
			}
			last;
		}
	}
	
	# check if option valid
	if ($num_option==$length_array-1 && $verbose ne $value) {
		close CONFIGFILE;
		die "Unknown option: $option in $config_file";
	}
} # while

close CONFIGFILE;
}


# gets and checks command line parameters
sub parameters {
	&GetOptions("uptimed=s" => \$uptimed_path, "verbose" => \$verbose, "output=s" => \$output_file,"overwrite" =>\$overwrite,"quiet" =>\$quiet,"help" =>\$help,"config=s" =>\$config_file);
	if ($help==1) {
		&help;
		exit 1;
	}
}


# prints the help screen
sub help {
	if ($quiet==1) {
		print "STAMOS $STAMOS_version  Some Things About My Own Server                    by Knut Grahlmann\n\n";
	}
	print "STAMOS gathers several information (CPU, RAM, uptime etc.) about the computer it\n";
	print "is running on and generates an HTML file with these information.\n\n";
	print "usage: stamos options \n";
	print "Some options are mandatory, some aren't.\n";
	print "\nmandatory options:\n";
	print "--output\t\tthe name and the full path of the file where STAMOS\n";
	print "\t\t\tshould write to\n";
	print "--uptimed\t\tthe path to the folder with the uprecords program in it\n";
	print "\nnon-mandatory options:\n";
	print "--config\t\tthe path of the configfile, overrides the standard file\n";
	print "\t\t\t/etc/stamos.conf\n";
	print "--help\t\t\tthis screen\n";
	print "--overwrite\t\tIn case the output file exists, it is overwritten.\n";
	print "--quiet\t\t\tSTAMOS produces (unless there are errors) NO screen\n";
	print "\t\t\toutput.\n";
	print "--verbose\t\tMakes STAMOS a bit more talkative during the creation\n";
	print "\t\t\tof the the output file.\n";
	print "\n";
	print "Example:\n";
	print "stamos --output=/usr/local/httpd/htdocs/about.html --uptimed=/opt/uptimed --verbose\n";
}


# generates the HTML output file
sub output {
	# outputfile already existing?
	if ($overwrite!=1) {
		if (-e $output_file) {
			die "The output file already exists. Please delete it or use the --overwrite option.";
		}
	}
	
	# OF=OutputFile
	open (OF,">$output_file");
	
	print OF "<html><head><title>$hostname - specifications</title>$own_style</head>\n";

	if ($own_body ne "") {print OF "$own_body\n";}
	else {print OF "<body>\n";}
	print OF "<center><h2>specifications of <b>$hostname</b></h2>\n";
	print OF "<b>as of $date</b><br>\n";
	print OF "current uptime: <i>$uptime_days days $uptime_hms</i> (# $place), running since <i>$start_time</i>\n";

	if ($own_start ne "") {print OF "$own_start\n";}
	else {print OF "<hr>\n";}

	if ($own_table ne "") {print OF "<p><table width=50% $own_table>\n";}
	else {	print OF "<p><table border=1 width=50%>\n";}

	if ($manufacturer ne "") {print OF "<tr width=100%><td width=50%>manufacturer</td><td width=50%>$manufacturer</td></tr>\n";}
	if ($model ne "") {print OF "<tr width=100%><td width=50%>model</td><td width=50%>$model</td></tr>\n";}
	print OF "<tr width=100%><td width=50%>processor</td><td width=50%>$CPU</td></tr>\n";	
	print OF "<tr width=100%><td width=50%>&#160&#160 -bogomips</td><td width=50%>$bogomips</td></tr>\n";
	print OF "<tr width=100%><td width=50%>load averages</td><td width=50%>&#160</td></tr>\n";
	print OF "<tr width=100%><td width=50%>&#160&#160 -last minute</td><td width=50%>$loadavg_1</td></tr>\n";
	print OF "<tr width=100%><td width=50%>&#160&#160 -last 5 minutes</td><td width=50%>$loadavg_5</td></tr>\n";
	print OF "<tr width=100%><td width=50%>&#160&#160 -last 15 minutes</td><td width=50%>$loadavg_15</td></tr>\n";
	print OF "<tr width=100%><td width=50%>RAM</td><td width=50%>$MEM</td></tr>\n";
	print OF "</table>\n";

	if ($own_table ne "") {print OF "<table width=50% $own_table>\n";}
	else {print OF "<table width=50% border=1>\n";}

	print OF "<tr width=100%><td width=100%>hard drives:</td></tr>\n";
	print OF "<tr width=100%><td width=20%>";

	if ($NFS_occured==1) {print OF "device/<br>mount-point";}
	else { print OF "device";}
	
	print OF "</td><td width=20%>size (MB)</td><td width=20%>used (MB)</td><td width=20%>free (MB)</td><td width=20%>% used</td></tr>\n";
	for ($counter=0;$counter<=($number_of_hds-1);++$counter) {
		print OF "<tr width=100%><td width=20%>$HD_names[$counter]</td><td width=20%>$HD_sizes[$counter]</td><td width=20%>$HD_used[$counter]</td><td width=20%>$HD_free[$counter]</td><td width=20%>$HD_percentage[$counter]</td></tr>\n";
	}
	print OF "</table>\n";
	
	if ($own_table ne "") {print OF "<table width=50% $own_table>\n";}
	else {print OF "<table width=50% border=1>\n";}
	
	print OF "<tr width=100%><td width=50%> </td><td width=50%></td> </tr>\n";
	print OF "<tr width=100%><td width=50%>OS version</td><td width=50%>$OS</td></tr>\n";
	print OF "<tr width=100%><td width=50%>best uptime</td><td width=50%>$uptime_days_best days $uptime_hms_best</td></tr>\n";
	print OF "<tr width=100%><td width=50%>&#160&#160 -started</td><td width=50%>$start_time_best</td></tr>\n";
	print OF "</table>";
	print OF "<p>generated by <a href=\"http://privat.schlund.de/K/Knut_Grahlmann/english/stamos.html\"><b>STAMOS $STAMOS_version</b></a></center>";

	if ($own_end ne "") {print OF "<p>$own_end\n";}	
	print OF "</body></html>";

	close OF;
}


# "cat"s df into $_ and extracts, which HDs are used, how big they are how much
# space is used, and how much is free (what is surprisingly not size-used)
sub disks {

open (DISKS,"df|") || die "Could not read from df: $!";

	my $line_counter=-1;
	my $NFS=0;

	WHILE: while (<DISKS>) {

		# the first line is not necessary
		if ($line_counter==-1) {
			++$line_counter;
			next WHILE;
		}
		chomp;

		# disk drives are not the real thrill either
		if (substr($_,0,7) eq "/dev/fd") {
			next WHILE;
		}

		# if you have an NFS-drive, the output of df is like this:
		# IP:/mount-point
		#                size etc.
		if (length($_) < 50) {
			$pos_of_slash=index($_,":/")+1;
			chomp($HD_names[$line_counter]=substr($_,$pos_of_slash));
			$HD_names[$line_counter]=~ tr/ //d;
			$NFS=1;
			$NFS_occured=1;
			next WHILE;
		}

		if ($NFS!=1) {
			$HD_names[$line_counter]=substr($_,0,10);
			$HD_names[$line_counter]=~ tr/ //d;
		}
		else {$NFS=0;}

		# code courtesy of Peter Kovacs <kovacsp@kova.cx>
		($HD_sizes[$line_counter],$HD_used[$line_counter],$HD_free[$line_counter],$HD_percentage[$line_counter])=($_ =~ /\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);


		if ($verbose==1) {print "$HD_names[$line_counter]\t$HD_sizes[$line_counter]\t$HD_used[$line_counter]\t$HD_free[$line_counter]\t$HD_percentage[$line_counter]\n";}
		
		++$line_counter;
	}
	close DISKS;

	# so that I can make a for-to loop in &output to print all harddrives
	$number_of_hds=$line_counter;
}


# "cat"s several /proc files into $_
# does it need more comments?
sub find_specs {

	# already declared in conf file?
	if ($hostname eq "") {
		$hostname=$ENV{"HOSTNAME"};
		if ($verbose==1) {print "hostname: $hostname\n";}
	}

	# already declared in conf file?	
	if ($OS eq "") {
		open (OS,"(cat /proc/version)|") || die "Could not read /proc/version: $!";;
		while (<OS>) {
			chomp;
			my $old_pos_in_line=index($_," ");
			$OS=substr($_,0,$old_pos_in_line);
			my $pos_in_line=index($_," ",$old_pos_in_line+1);
			$old_pos_in_line=index($_," ",$pos_in_line+1);
			$OS=$OS." ".substr($_,$pos_in_line+1,$old_pos_in_line-$pos_in_line-1);
		}
		close OS;
		
		if ($verbose==1) {print "OS version: $OS\n";}
	}

	open (CPU,"(cat /proc/cpuinfo)|") || die "Could not read /proc/cpuinfo: $!";;
	while (<CPU>) {
		chomp;
	
		# already declared in conf file?
		if (substr($_,0,5) eq "model" && $CPU eq "") {
			$CPU=substr($_,9);
		}
		if (substr($_,0,8) eq "bogomips") {
			$bogomips=substr($_,11);
		}	
	}
	close CPU;
		
	if ($verbose==1) {print "CPU, bogomips: $CPU, $bogomips \n";}

	# already declared in conf file?
	if ($MEM eq "") {
		open (MEM,"(cat /proc/meminfo)|") || die "Could not read /proc/meminfo: $!";;
		while (<MEM>) {
			chomp;
			if (substr($_,0,8) eq "MemTotal") {
				$MEM=substr($_,12,10);
				$MEM=~ tr/ //d;
			}
		}
		close MEM;
	
		if ($verbose==1) {print "RAM: $MEM\n";}
	}

	# load averages can't be found out in a conf file
	open (LOAD,"(cat /proc/loadavg)|") || die "Could not read /proc/loadavg: $!";;
	while (<LOAD>) {
		chomp;
		$loadavg_1=substr($_,0,4);
		$loadavg_5=substr($_,5,4);
		$loadavg_15=substr($_,10,4);
	}
	close LOAD;
	
	if ($verbose==1) {print "load averages 1,5,15: $loadavg_1, $loadavg_5, $loadavg_15\n";}
}


# "cat"s the output of uprecords into $_and extracts the following values:
# the current uptime (in days and hour:minutes:seconds) and the record
sub uptime {
	open (UPTIME,"$uprecords|") || die "Could not read $uprecords: $!";
	my $line_counter=1;
		
	WHILE: while (<UPTIME>) {
		# the first line in the file is a "header line"; some spaces first
		# The thing is that a regular line starts with spaces, too, so that
		# one can't tell the difference
		if ($line_counter == 1) {
			++$line_counter;
			next WHILE;
		}

		# lines that start with these chracters are not usable
		if ((substr($_,0,1) eq "-") || (substr($_,0,1) eq "n") || (substr($_,0,1) eq "1")) {
			next WHILE;
		}
		
		chomp;
		$actual_line=$_;

		# line 2 is the line with the best uptime
		if ($line_counter==2) {

			# if the current uptime happens to be the best uptime
			if (substr($actual_line,5,1) eq '>') {
				$uptime_days=substr($actual_line,12,4);
				$uptime_days=~ tr/ //d;
				$uptime_days_best=$uptime_days;
				$uptime_hms=substr($actual_line,23,8);
				$uptime_hms_best=$uptime_hms;
				$start_time=substr($actual_line,66,25);
				$start_time_best=$start_time;
				$place=1;
				# exists the while loop because we already found
				# everything
				last WHILE;
			}
			
			# this is just the regular best uptime
			$uptime_days_best=substr($actual_line,8,4);
			$uptime_days_best=~ tr/ //d;
			$uptime_hms_best=substr($actual_line,19,8);
			$start_time_best=substr($actual_line,54,25);

			++$line_counter;
		}

		# the current uptime has a -> in front of it
		if (substr($actual_line,5,1) eq '>') {
			$place=substr($actual_line,8,2);
			$place=~ tr/ //d;
			$uptime_days=substr($actual_line,12,4);
			$uptime_days=~ tr/ //d;
			$uptime_hms=substr($actual_line,23,8);		
			$start_time=substr($actual_line,66,25);
			last WHILE;
		}
		
	}
	close UPTIME;

	if ($verbose==1) {print "uptime: $uptime_days days $uptime_hms\n\n";}
}
