#!/usr/bin/perl5

#++
# NAME
#	ramhash 1
# SUMMARY
#	compute per-block MD5 hashes for physical memory
# SYNOPSIS
# .ad
# .fi
#	\fBramhash\fR [\fB-b \fIblock_size\fR] [\fB-h \fRhash_size\fR]
#	[\fB-t \fItimeout\fR]
# DESCRIPTION
#	The \fBramhash\fR command computes MD5 hashes for successive blocks
#	of physical memory. The result is written to standard output.
#	See the OUTPUT FORMAT section below for output format details.
# .PP
#	Arguments:
# .IP "\fB-b \fIblock_size\fR"
#	The number of bytes over which an MD5 checksum is computed.
#	The default value is set with the \fB$hash_block_size\fR parameter
#	in the \fBcoroner.cf\fR file.
# .IP "\fB-h \fIhash_size\fR"
#	The number of bits of each MD5 checksum that are written to output.
#	This must be a multiple of 8.	The default value is set with the
#	\fB$hash_sum_size\fR parameter in the \fBcoroner.cf\fR file.
# .IP "\fB-t \fItimeout\fR"
#	Time limit. The default value is specified in the \fBcoroner.cf\fR
#	file with the \fB$timeouts{$BLOCKSUM}\fR parameter.
# OUTPUT FORMAT
# .ad
# .fi
#	The output format is in time machine format, as described in 
#	tm-format(5).
#
#	Each result begins with a two-line header that describes the data
#	origin and hash block size.
#
#	The information is followed by a one-line header that lists the names
#	of the data attributes that make up the remainder of the output:
# .IP \fBhash\fR
#	The MD5 checksum.
# BUGS
#	SunOS 4.x has no reasonable /dev/mem implementation. For example, on
#	the SparcSTATION SLC with 16MBytes installed (that's all it can have),
#	reading /dev/mem fails after the first 4 MBytes.
# FILES
#	/dev/mem, physical memory.
# SEE ALSO
#	tm-format(5), time machine file format.
#	blocksum(1), per-block MD5 checksums
# LICENSE
#	This software is distributed under the IBM Public License.
# AUTHOR(S)
#	Wietse Venema
#	IBM T.J. Watson Research
#	P.O. Box 704
#	Yorktown Heights, NY 10598, USA
#--

#
# Initialize. Allow TCT_HOME to be specified via the environment.
# This requires some ugly code layout so that the reconfig utility
# will do the right thing. Turn off scaffolding in library routines.
#
if ($ENV{'TCT_HOME'}) { $TCT_HOME = $ENV{'TCT_HOME'}; } else {
$TCT_HOME = "";
}

$debug = 0;
$verbose = 0;
$running_under_grave_robber = 1;

#
# Read configuration info and load support routines.
#
require "$TCT_HOME/conf/coroner.cf";
require "logger.pl";
require "tm_misc.pl";
require "paths.pl";
require "getopts.pl";
require "hostname.pl";
require "ostype.pl";

#
# Initialize the logging before running any of the above code.
#
&log_init_path($logfile);

#
# Parse JCL. Take defaults from the coroner.cf file.
#
chop($hostname = &hostname());
$opt_b = $hash_block_size;
$opt_h = $hash_sum_size;
$opt_t = $timeouts{$BLOCKSUM};

$usage = "Usage: $0 [options]\
    -b block_size: block size for hashing (default: $opt_b)\
    -h hash_size: bits per hash (default: $opt_h)\
    -t timeout: per-swapfile time limit (default: $opt_t)\n";

&Getopts("b:h:t:v") || die $usage;

die "bad block size: $opt_b" unless $opt_b > 0;
die "bad hash size: $opt_h" unless $opt_h > 0;
$verbose = $opt_v;
$timeouts{$BLOCKSUM} = $opt_t;

#
# Find out the size of the physical memory file. This is system dependent.
#
# We don't need to determine the memory size ahead of time for Solaris
# 5.x and for Linux, because their /dev/mem implementation properly
# produces an EOF condition.
#
&determine_os();

if ($OS =~ /FREEBSD[2-4]/ || $OS eq "OPENBSD2" || $OS =~ /BSDI[2-4]/) {
    die "No sysctl command configured!\n"
	unless $SYSCTL && -x $SYSCTL;
    ($junk, $memsize) =
	split(/[^a-z0-9.]+/, &command_to_string($SYSCTL, "hw.physmem"));
} elsif ($OS eq "SUNOS5") {
    $memsize = "auto";
#    die "No prtconf command configured!\n"
#	unless $PRTCONF && -x $PRTCONF;
#    open(PRTCONF, "$PRTCONF|")
#	|| die "cannot run $PRTCONF: $!\n";
#    while (<PRTCONF>) {
#	if (/Memory size: ([0-9]+) Megabytes/) {
#	    $memsize = $1 * 1024 * 1024;
#	    last;
#	}
#    }
#    close(PRTCONF);
} elsif ($OS eq "LINUX2") {
    $memsize = "auto";
#    die "Cannot stat /proc/kcore: $!\n"
#	unless ($junk,$junk,$junk,$junk,$junk,$junk,$junk,$memsize,
#	    $junk,$junk,$junk,$junk,$junk) = stat("/proc/kcore");
#    $memsize -= 4096;
} else {
    die "Don't know how to get the memory size for $OS\n";
}
die "Cannot find out the memory size!\n"
    unless $memsize > 0 || $memsize eq "auto";

print "memory size = $memsize\n" if $verbose;

#
# Emit the time machine header info.
#
&tm_print("class", "host", "start_time", "block_size");
&tm_print("ramhash", $hostname, time(), $opt_b);
&tm_print("hash");

#
# Compute the hashes for each block. We could avoid the copy loop
# below, but that requires that we flush stdout before running the
# blocksum command.
#
if ($memsize eq "auto") {
    pipe_command(BLOCKSUM, "$BLOCKSUM", "-b", "$opt_b", "-h", "$opt_h",
	"/dev/mem", "-|");
} else {
    pipe_command(BLOCKSUM, "$BLOCKSUM", "-b", "$opt_b", "-h", "$opt_h",
	"-l", "$memsize", "/dev/mem", "-|");
}
while (<BLOCKSUM>) {
    print $_;
}
close(BLOCKSUM)
    || warn "$BLOCKSUM failed: status $! $?\n";
exit 0;
