#! /usr/bin/perl -w

# Catch for sh/csh on systems without #! ability.
eval '(exit $?0)' && eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        & eval 'exec /usr/bin/perl -S $0 $argv:q'
                if 0;

# 
# This changer script controls tape libraries on operating systems that have a
# chgio program
#	DSL 7000 on FreeBSD is an example
#
# The changer being used is a n tape juke, that can be used with 1, n-1 or n
# tapes in the juke. The special slot is slot n. The script does not
# make assumptions about the number of slots, except that the special slot
# is the highest number. The slot is special in the sense that it contains the
# the only tape if the juke contains 1 tape and contains no tape if the juke
# contains n-1 tapes. See getCurrentTape.
#
# Furthermore, the script uses drive 0 and assumes that the device is able to
# figure itself how to move a type from slot m to drive 0 if asked to do so and
# multiple pickers are present.
#
# The numbering of the slots is by the way from 1 to n with slots. The chio
# program returns the slot numbers numbered from 0 to n-1 however.
# 
# This script is built up out of bits and pieces of the other scripts
# and no credits are claimed. Most notably the chg-rth.pl script was used. That
# script was written by Erik Frederick, <edf@tyrell.mc.duke.edu>.
# 
# Permission to freely use and distribute is granted (by me and was granted by
# the original authors).
#
# Nick Hibma - nick.hibma@jrc.it
#

require 5.001;

($progname = $0) =~ s#/.*/##;

use English;
use Getopt::Long;

$| = 1;

if (-d "/tmp/amanda") {
	$logfile = "/tmp/amanda/changer.debug";
} else {
	$logfile = "/dev/null";
}
die "$progname: cannot open $logfile: $ERRNO\n"
	unless (open (LOG, ">> $logfile"));

#
# get the information from the configuration file
#

$prefix="/usr/local";
$prefix=$prefix;		# avoid warnings about possible typo
$exec_prefix="${prefix}";
$exec_prefix=$exec_prefix;	# Ditto
$sbindir="${exec_prefix}/sbin";
if ( "no" eq "yes" ) {
    $SUF = "-2.4.5p1";
} else {
    $SUF = "";
}

chomp ($tapeDevice = `$sbindir/amgetconf$SUF tapedev 2>&1`);
die "tapedev not found in amanda.conf"
	if !$tapeDevice or $tapeDevice =~ m/BUGGY/;
chomp ($changerDevice = `$sbindir/amgetconf$SUF changerdev 2>&1`);
chomp $changerDevice;
die "changerdev not found in amanda.conf"
	if !$changerDevice or $changerDevice =~ m/BUGGY/;

#
# Initialise a few global variables
#

@slots = ();
@drives = ();
$max_slot = 0;
$max_drive = 0;
$nr_tapes = 0;

@dow = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
@moy = ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec");

sub do_time {
	my (@t);
	my ($r);

	###
	# Get the local time for the value.
	###

	@t = localtime (time ());

	###
	# Return the result.
	###

	$r = sprintf "%s %s %2d %2d:%02d:%02d %4d",
	  $dow[$t[6]],
	  $moy[$t[4]],
	  $t[3],
	  $t[2], $t[1], $t[0],
	  1900 + $t[5];

	return $r;
}

sub getCurrentTape {
	print LOG &do_time(), ": enter: getCurrentTape\n";

	#
	# Determines the slot number for the tape that is currently in the
	# drive. getTapeParams and getTapeStatus should have been called.
	# If there is no tape in the drive, no current tape, 0 is returned.
	#

	my($slot, $i);

	if ( !$drives[0] ) {		# drive empty
		$i = 0;
	} elsif ( $nr_tapes == 1 ) {	# one tape -> comes from slot max_slot
		$i = $max_slot;
	} else {			# find first empty slot
		$i = 0;
		while ( $i < $#slots and $slots[$i] ) {
			$i++
		}
		$i++;
	}

	print LOG &do_time(), ": leave: getCurrentTape: $i\n";
	return $i;
}

sub getTapeStatus {
	print LOG &do_time(), ": enter: getTapeStatus\n";

	#
	# Sets $nr_tapes, @slots, @drives, $current_tape
	#

	my($type,$num,$status);

	print LOG &do_time(), ": running: /bin/chio -f $changerDevice status\n";
	if ( !(open(FH,"/bin/chio -f $changerDevice status|")) ) {
		print "$progname: '/bin/chio -f $changerDevice status' failed, $!\n";
		exit(2);
	}

	#
	# This routine requires the format of the output of 'chio status' to 
	# be as follows:
	#   picker 0: 
	#   slot 0: <ACCESS>
	#   slot 1: <ACCESS,FULL>
	#   slot 2: <ACCESS,FULL>
	#   (etc.)
	#   drive 0: <ACCESS,FULL>


	@slots=();
	@drives=();

	while( defined ($line = <FH>) ) {
		chomp( $line );
		print LOG &do_time(), ": $line\n";
		next unless $line =~ m/(\w+)\s+(\d+):\s*<([^>]+)>/;
		($type,$num,$status) = ($1,$2,$3);
		if ( $type =~ m/slot/i ) {
			$slots[$num] = ( $status =~ m/full/i ) ? 1 : 0;
			if ($slots[ $num ]) { $nr_tapes++ }
		} elsif ( $type =~ m/drive/i ) {
			$drives[$num] = 0;
			if (  $status =~ m/full/i ) {
				$drives[$num] = 1;
				$nr_tapes++;
			}
		} else {
			# ignore 'picker', empty ones, etc...
		}
	}
	close(FH);

	if ( $nr_tapes == 0 ) {
		print "$progname: No tapes in changer!\n";
		exit(2);
	}

	$currentTape = &getCurrentTape(); 

	print LOG &do_time(), ": leave: getTapeStatus: $nr_tapes\n";
	return($nr_tapes);
}

sub getTapeParams {
	print LOG &do_time(), ": enter: getTapeParams\n";
  
	#
	# Requests information on the number of slots, pickers and drives
	# from the changer.
	#

	my($max_slot,$max_drive,$max_picker);
  
	print LOG &do_time(), ": running: /bin/chio -f $changerDevice params\n";
	if ( !open(FH,"/bin/chio -f $changerDevice params|") ) {
		print "$progname: '/bin/chio -f $changerDevice params' failed, $!\n";
		exit(2);
	}
  
	#
	# the format of the output of 'chio params' should be
	#  /dev/ch0: 8 slots, 1 drive, 1 picker
	#  /dev/ch0: current picker: 0
	#

	$max_slot = 0;
	$max_picker = -1;
	$max_drive = 0;

	while( defined ($line = <FH>) ) {
		chomp $line;
		print LOG &do_time(), ": $line\n";
		$max_slot 	= $1 if $line =~ m/(\d+) slot/i;
		$max_drive	= $1 if $line =~ m/(\d+) drive/i;
		$max_picker 	= $1 if $line =~ m/(\d+) picker/i;

	}
	close(FH);
	if ( $max_drive == 0 or $max_picker == -1 ) {
		print "$progname: No drive or picker ? ($max_drive/$max_picker)\n";
		exit(2);
	}

	print LOG &do_time(), ": leave: getTapeParams: $max_slot, $max_drive, $max_picker\n";
	return ($max_slot, $max_drive, $max_picker);
}

sub testTape {
	my($tape) = @_;

	#
	# Check a few parameters to avoid the most serious problems
	#

	return
		if $currentTape == $tape;

	if( $slots[$tape-1] == 0 ) {
		print "<none> $progname: no tape in slot requested\n";
		exit(1);
	}
	if( $tape > $max_slot ) {
		print $tape," $progname: requested a tape > $max_slot\n";
		exit(2);
	}
	if( $tape < 1 ) {
		print $tape," $progname: requested a tape < 1\n";
		exit(2);
	}
	return;
}

sub Load {
	my($tape) = @_;
	print LOG &do_time(), ": enter: Load: $tape\n";

	#
	# Load tape $tape into drive 0
	#

	print LOG &do_time(), ": running: /bin/chio -f $changerDevice move slot ", $tape - 1, " drive 0\n";
	if ( system("/bin/chio -f $changerDevice move slot ".($tape-1)." drive 0") ) {
		print "$progname: cannot '/bin/chio -f $changerDevice move' tape $tape into drive 0\n";
		exit(2);
	}
	print LOG &do_time(), ": leave: Load\n";
}

sub Unload {
	my($tape) = @_;
	print LOG &do_time(), ": enter: Unload: $tape\n";

	#
	# Unload the tape from drive 0 and put it into the slot specified by
	# $tape.
	#

	#
	# Ecrix AutoPAK devices (based on the Spectra Logics 215 changer)
	# can lock up if you try to move a tape from a drive to an open slot
	# without first rewinding and ejecting the tape.  This appears to
	# occur when the operation times out and the ch driver sends a device
	# or bus reset. Ecrix claims this is about to be fixed with a new
	# firmware rev but for now it's safest to just explicitly eject
	# the tape before moving the cartridge.
	#
	if ( system ("/bin/mt -f $tapeDevice offline") ) {
		print "$progname: Warning, failed to eject the tape with '/bin/mt -f $tapeDevice offline'\n";
		# NB: not fatal; let chio try it's thing
	}

	if ( system("/bin/chio -f $changerDevice move drive 0 slot ".($tape-1)." ") ) {
		print "$progname: cannot '/bin/chio -f $changerDevice move' tape $tape from drive 0\n";
		exit(2);
	}
	print LOG &do_time(), ": leave: Unload\n";
}

sub changeTape {
	my($tape) = @_;
	print LOG &do_time(), ": enter: changeTape: $tape\n";

	#
	# Unload current tape and load a new tape from slot $tape.
	#

	if ($tape != $currentTape) {

		&testTape($tape);

		if( $currentTape != 0 ) {
			&Unload($currentTape);
		}
		&Load($tape);
		$currentTape = $tape;
	}
	print LOG &do_time(), ": leave: changeTape\n";
}


#
# Main program
#

#
# Initialise
#

($max_slot, $max_drive) = &getTapeParams();

$opt_slot = 0;					# perl -w fodder
$opt_info = 0;					# perl -w fodder
$opt_reset = 0;					# perl -w fodder
$opt_eject = 0;					# perl -w fodder

GetOptions("slot=s", "info", "reset", "eject"); 

$nr_tapes = &getTapeStatus();

#
# Before we do anything with the tape changer we'll have to rewind the tape
#

if (-x "$sbindir/ammt$SUF") {
	$MT="$sbindir/ammt$SUF";
	$MTF="-f";
} elsif (-x "/bin/mt") {
	$MT="/bin/mt";
	$MTF="-f";
} else {
	print LOG &do_time(), ": mt program not found\n";
	print "<none> mt program not found\n";
	exit(1);
}
print LOG &do_time(), "MT -> $MT $MTF\n";

system ("$MT $MTF $tapeDevice rewind")
	unless $currentTape == 0;


if ( $opt_slot ) {
	if ( $opt_slot =~ /first/ ) {
		&changeTape(1);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /last/ ) {
		&changeTape($max_slot);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /current/ ) {
		&changeTape($currentTape);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /next/ ) {
		$tape = $currentTape+1;
		if ( $tape > $max_slot ) {
			$tape = 1;
		}
		while ( $slots[$tape-1] == 0 ) {        # there is at least 1 
			if ( ++$tape > $max_slot ) {
				$tape = 1;
			}
		}
		&changeTape($tape);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /prev/ ) {
		$tape = $currentTape-1;
		if ( $tape < 1 ) {
			$tape = $max_slot;
		}
		while ( $slots[$tape-1] == 0 ) {        # there is at least 1
			if ( --$tape < 1 ) {
				$tape = $max_slot;
			}
		}
		&changeTape($tape);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /^\d+$/ ) {
		&changeTape($opt_slot);
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape $tapeDevice\n";
	}
	if ( $opt_slot =~ /advance/ ) {
		$tape=$currentTape+1;
		if ( $tape > $max_slot ) {
			$tape = 1;
		}
		if ( $currentTape ) { 
			&Unload($currentTape);
		}
		print LOG &do_time(), ": $currentTape $tapeDevice\n";
		print "$currentTape , /dev/null\n";
	}

	exit 0;
}

if ( $opt_info ) {
	if ( $currentTape == 0 ) {
		&Load(1);			# load random tape
		$currentTape = 1;
	}

	print LOG &do_time(), ": $currentTape $max_slot 1\n";
	print "$currentTape $max_slot 1\n";
	exit 0;
}

if ( $opt_reset ) {
	&changeTape(1);
	print LOG &do_time(), ": $currentTape $tapeDevice\n";
	print "$currentTape $tapeDevice\n";
	exit 0;
}

if ( $opt_eject ) {
	if ( $currentTape ) { 
		&Unload($currentTape);
		print "0 $tapeDevice\n";
		exit 0;
	} else {
		print "$progname: drive was not loaded\n";
		exit 1;
	}
}

print "$progname: No command was received.  Exiting.\n";
exit 1;
