#!/usr/bin/perl -w
#
# (c) 2001-2005  Rudolf Cejka <cejkar@fit.vutbr.cz>
#
# $Id: ez17,v 1.5 2005/06/21 14:27:27 bacula Exp $
#

use strict;
use Math::BigInt;
use Getopt::Std;

sub DEFAULTDEV () { "ch0" }

sub CAMCONTROL () { "/sbin/camcontrol" }

sub TRUE () { 1 }
sub FALSE () { 0 }

sub LOG_SENSE_10 () { 1 }
sub MODE_SENSE_6 () { 2 }

my $prog;
($prog = $0) =~ s/.*\///;

sub usage()
{
	printf STDERR <<"END", $prog, DEFAULTDEV;
Usage: %s [-d device] [-p pages]
    -d device   Queried device (default "%s")
    -p pages    Print supported pages (p), system statistics (y), state (s),
                history of events (h), element statistics (l), element
                positions (e), drive counters (c) (default: pyshelc - all)
END
}

sub error($)
{
	die "$prog: $_[0]\n";
}

sub encode_shell($)
{
	my $str = $_[0];
	if (defined($str)) {
		$str =~ s/([`"\$\\])/\\$1/g;
	} else {
		$str = "";
	}
	return "\"" . $str . "\"";
}

sub cdb_mode_sense_6($$$)
{
	my ($pagecode, $pc, $size) = @_;
	return sprintf "1a 8 %x 0 %x 0",
	    (($pc & 0x03) << 6) | ($pagecode & 0x3F),
	    $size & 0xFF;
}

sub cdb_log_sense_10($$$)
{
	my ($pagecode, $pc, $size) = @_;
	return sprintf "4d 0 %x 0 0 0 0 %x %x 0",
	    (($pc & 0x03) << 6) | ($pagecode & 0x3F),
	    ($size >> 8) & 0xFF, $size & 0xFF;
}

sub WD () { 32 }	# Item width: C => 8, n => 16, N => 32
sub BL () { 512 }	# Block read size

sub call($)
{
	my $r = "";
	open(FH, $_[0] . " |")
	    or error("open(): Execute error");
	while (read(FH, $r, BL, length($r))) { }
	close(FH)
	    or error("close(): Execute error");
	return unpack("N*", $r);
}

sub query($$$$)
{
	my ($device, $type, $pagecode, $pc) = @_;
	my ($cdb, $size);
	if ($type eq LOG_SENSE_10) {
		$size = 511;
		$cdb = cdb_log_sense_10($pagecode, $pc, $size);
	} elsif ($type eq MODE_SENSE_6) {
		$size = 255;
		$cdb = cdb_mode_sense_6($pagecode, $pc, $size);
	} else {
		return ();
	}
	return call(CAMCONTROL . " cmd " . encode_shell($device)
	    . " -v -c \"" . $cdb . "\" -i " . $size . " -");
}

sub get_mask($)
{
	return (($_[0] < WD) ? 1 << $_[0] : 0) - 1;
}

sub get_bits($$$$)
{
	my ($data, $ofs, $len, $signed) = @_;
	my ($i, $j, $f, $l, $r, $tmp);
	
	if ($ofs < 0 || $len < 0 || $len > 64
	    || $ofs + $len > scalar(@$data) * WD) {
		return undef;
	}
	$f = TRUE;
	while ($len > 0) {
		$l = WD - ($ofs % WD);
		if ($l > $len) {
			$r = $l - $len;
			$l = $len;
		} else {
			$r = 0;
		}
		if ($f) {
			$i = new Math::BigInt(($signed && ((@$data[$ofs / WD]
			    >> ($r + $l - 1)) & 1) != 0) ? -1 : 0);
			$f = FALSE;
		}
		$i *= 2 ** $l;
		$tmp = (@$data[$ofs / WD] >> $r) & get_mask($l);
		$i += $tmp;
		$len -= $l;
		$ofs += $l;
	}
	($i = "$i") =~ s/^\+//;
	return $i;
}

sub get_value($$$@)
{
	my ($data, $pos, $code, @items) = @_;
	my ($p, $c, $l, $i, $s, @r);
	$p = $$pos * 8;
	$c = get_bits($data, $p, 16, FALSE);
	$l = get_bits($data, $p + 24, 8, FALSE);
	$p += 32;
	@r = ();
	$s = 0;
	if (scalar(@items) < 1) {
		@items = ($l);
	}
	while (defined($i = shift(@items))) {
		if ($code == -1) {
			push(@r, $c, $i, get_bits($data, $p, $i * 8, FALSE));
		} elsif ($code == $c) {
			push(@r, $i, get_bits($data, $p, $i * 8, FALSE));
		} else {
			push(@r, $i, "Unexpected code $c instead of $code");
		}
		$p += $i * 8;
		$s += $i;
	}
	if ($s != $l) {
		$i = ($code == -1) ? 2 : 1;
		while ($i < scalar(@r)) {
			$r[$i] = "Unexpected length $s instead of $l";
			$i += ($code == -1) ? 3 : 2;
		}
	}
	$$pos += $l + 4;
	return @r;
}

sub make_printable($)
{
	my $str = $_[0];
	if (defined($str)) {
		$str =~ s/[\x00-\x1F\x7F-\xFF]/./g;
	} else {
		$str = "";
	}
	return $str;
}

sub get_string($$$)
{
	my ($data, $pos, $code) = @_;
	my ($p, $c, $l, $i, $v, $r);
	$p = $$pos * 8;
	$c = get_bits($data, $p, 16, TRUE);
	$l = get_bits($data, $p + 24, 8, FALSE);
	$p += 32;
	$r = "";
	for ($i = 0; $i < $l; $i++) {
		$v = get_bits($data, $p, 8, FALSE);
		if ($v > 0) {
			$r .= chr($v);
		}
		$p += 8;
	}
	$$pos += $l + 4;
	return ($l, ($code == $c) ? make_printable($r)
	    : "Unexpected code $c instead of $code");
}

sub get_log_sense_page_code($)
{
	my ($data) = @_;
	return get_bits($data, 2, 6, FALSE);
}

sub get_mode_sense_page_code($)
{
	my ($data) = @_;
	return get_bits($data, 34, 6, FALSE);
}

sub get_log_sense_length($)
{
	my ($data) = @_;
	my $l_data = get_bits($data, 16, 16, FALSE);
	if (defined($l_data)) {
		return 4 + $l_data;
	} else {
		return 0;
	}
}

sub get_mode_sense_length($)
{
	my ($data) = @_;
	my $l_data = get_bits($data, 0, 8, FALSE);
	if (defined($l_data)) {
		return 1 + $l_data;
	} else {
		return 0;
	}
}

sub print_log_sense_binary(@)
{
	my ($l_data, $n, $c, $b);
	printf "\nBinary dump (%02Xh):\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	$l_data = get_log_sense_length(\@_);
	$c = 0;
	for ($n = 0; $n < $l_data; $n++) {
		$b = get_bits(\@_, $n * 8, 8, FALSE);
		printf "%s%02x",
		    ($c == 0) ? "" : (($c % 8 == 0) ? "\n" : " "), $b;
		$c++;
	}
	if ($c > 0) {
		print "\n";
	}
}

sub print_mode_sense_binary(@)
{
	my ($l_data, $n, $c, $b);
	printf "\nBinary dump (%02Xh):\n",
	    get_mode_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	$l_data = get_mode_sense_length(\@_);
	$c = 0;
	for ($n = 0; $n < $l_data; $n++) {
		$b = get_bits(\@_, $n * 8, 8, FALSE);
		printf "%s%02x",
		    ($c == 0) ? "" : (($c % 8 == 0) ? "\n" : " "), $b;
		$c++;
	}
	if ($c > 0) {
		print "\n";
	}
}

sub get_pages(@)
{
	my ($l_data, $n, @result);
	$l_data = get_log_sense_length(\@_);
	for ($n = 4; $n < $l_data; $n++) {
		push(@result, get_bits(\@_, $n * 8, 8, FALSE));
	}
	return @result;
}

sub print_log_sense_pages(@)
{
	my ($c, $b);
	printf "\nSupported pages (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	printf "Supported pages (%d):\t\t\t", scalar(@_);
	$c = 0;
	foreach $b (@_) {
		printf "%s%02Xh",
		    ($c == 0) ? "" : (($c % 16 == 0) ? "\n" : " "), $b;
		$c++;
	}
	if ($c > 0) {
		print "\n";
	}
}

sub print_log_sense_system_statistics(@)
{
	my $p = 4;
	my (@x, $i);
	printf "\nSystem Statistics Log Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	printf "Total Number of Moves (%d):\t\t%s moves\n",
	    get_value(\@_, \$p, 0);
	printf "Total Number of Pick Retries (%d):\t%s retries\n",
	    get_value(\@_, \$p, 1);
	printf "Total Number of Put Retries (%d):\t%s retries\n",
	    get_value(\@_, \$p, 2);
	printf "Total Number of Theta Retries (%d):\t%s retries\n",
	    get_value(\@_, \$p, 3);
	foreach $i (4 .. 7) {
		if ((@x = get_value(\@_, \$p, $i))[1] ne "0") {
			printf "Reserved (%d):\t\t\t\t%s\n", @x;
		}
	}
}

sub print_log_sense_state(@)
{
	my $p = 4;
	my (@x, $i);
	printf "\nState Log Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	printf "Magazine Present (%d):\t\t\t%s\n",
	    get_value(\@_, \$p, 0);
	printf "Cartridge Ejected (%d):\t\t\t%s\n",
	    get_value(\@_, \$p, 1);
	printf "Theta Home (%d):\t\t\t\t%s\n",
	    get_value(\@_, \$p, 2);
	foreach $i (3 .. 7) {
		if ((@x = get_value(\@_, \$p, $i))[1] ne "0") {
			printf "Reserved (%d):\t\t\t\t%s\n", @x;
		}
	}
	printf "Temperature (%d):\t\t\t%s *C\n",
	    get_value(\@_, \$p, 100);
	foreach $i (101 .. 103) {
		if ((@x = get_value(\@_, \$p, $i))[1] ne "0") {
			printf "Reserved (%d):\t\t\t\t%s\n", @x;
		}
	}
}

sub print_log_sense_history(@)
{
	my $p = 4;
	my $i;
	printf "\nHistory of Events Log Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
# XXX: Printing is very slow
# XXX: Print more history logs (300 instead of 5) (maximum is 250 per request)
	for ($i = 0; $i >= -5; $i--) {
		printf "%s\n", (get_string(\@_, \$p, $i))[1];
	}
}

sub print_log_sense_element_statistics(@)
{
	my $p = 4;
	my $mp;
	printf "\nElement Statistics Log Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	$mp = get_log_sense_length(\@_);
	while ($p < $mp) {
		printf "Element %2d Total Puts (%d):\t\t%s puts\n" .
		    "Element %2d Total Put Retries (%d):\t%s retries\n" .
		    "Element %2d Total Pick Retries (%d):\t%s retries\n",
		    get_value(\@_, \$p, -1, 4, 2, 2);
	}
}

sub print_log_sense_element_position(@)
{
	my $p = 4;
	my (@x, $mp);
	printf "\nElement Position Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	$mp = get_log_sense_length(\@_);
	while ($p < $mp) {
		@x = get_value(\@_, \$p, -1, 2, 4);
		printf "Element %2d Theta Axis Position (%d):\t%s steps\n"
		    . (($x[5] ne "0") ? "Element %2d Reserved (%d):\t\t%s\n"
		    : ""), @x;
	}
}

sub print_log_sense_drive_counters(@)
{
	my $p = 4;
	my (@x, $mp);
	printf "\nDrive Counters Page (%02Xh)\n",
	    get_log_sense_page_code(\@_);
	print "=" x 40 . "\n\n";
	$mp = get_log_sense_length(\@_);
	while ($p < $mp) {
		@x = get_value(\@_, \$p, -1, 4, 4, 4, 4, 4);
		printf "Element %2d Total Loads (%d):\t\t%s loads\n" .
		    "Element %2d Total Reloads (%d):\t\t%s reloads\n" .
		    "Element %2d Total Pluck Retries (%d):\t%s retries\n" .
		    "Element %2d Total Short Reloads (%d):\t%s reloads\n" .
		    (($x[14] ne "0") ? "Element %2d Reserved (%d):\t\t%s\n"
		    : ""), @x;
	}
}

sub is($$)
{
	my ($optlist, $opt) = @_;
	return !defined($optlist) || index($optlist, $opt) >= 0;
}

# main()

my (@pages, $dev, %o);

if (!getopts("?hd:p:", \%o)) {
	usage();
	exit;
}

$dev = (defined($o{"d"})) ? $o{"d"} : DEFAULTDEV;

if (defined($o{"?"}) || defined($o{"h"})) {
	usage();
	exit;
}

@pages = get_pages(query($dev, LOG_SENSE_10, 0, 1));
if (is($o{"p"}, "p")) {
	print_log_sense_pages(@pages);
}
if (grep($_ == 48, @pages) > 0 && is($o{"p"}, "y")) {
	print_log_sense_system_statistics(query($dev, LOG_SENSE_10, 48, 1));
}
if (grep($_ == 49, @pages) > 0 && is($o{"p"}, "s")) {
	print_log_sense_state(query($dev, LOG_SENSE_10, 49, 1));
}
if (grep($_ == 50, @pages) > 0 && is($o{"p"}, "h")) {
	print_log_sense_history(query($dev, LOG_SENSE_10, 50, 1));
}
if (grep($_ == 51, @pages) > 0 && is($o{"p"}, "l")) {
	print_log_sense_element_statistics(query($dev, LOG_SENSE_10, 51, 1));
}
if (grep($_ == 53, @pages) > 0 && is($o{"p"}, "e")) {
	print_log_sense_element_position(query($dev, LOG_SENSE_10, 53, 1));
}
if (grep($_ == 54, @pages) > 0 && is($o{"p"}, "c")) {
	print_log_sense_drive_counters(query($dev, LOG_SENSE_10, 54, 1));
}
print "\n";

