#! @PERL@

# Catch for sh/csh on systems without #! ability.
eval '(exit $?0)' && eval 'exec @PERL@ -S $0 ${1+"$@"}'
        & eval 'exec @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;
$progname =~ s#/.*/##;

use Getopt::Long;

$| = 1;


#
# get the information from the configuration file
#

$libexecdir = "@libexecdir@";
if ( "@USE_VERSION_SUFFIXES@" eq "yes" ) {
    $SUF = "-@VERSION@";
} else {
    $SUF = "";
}

$tapeDevice = `$libexecdir/getconf$SUF tapedev`;
chomp $tapeDevice;
die "tapedev not found in amanda.conf"
	if !$tapeDevice or $tapeDevice =~ m/BUGGY/;
$changerDevice = `$libexecdir/getconf$SUF changerdev`;
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;


sub getCurrentTape {

	#
	# 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
		return(0);
	} elsif ( $nr_tapes == 1 ) {	# one tape -> comes from slot max_slot
		return ($max_slot);
	} else {			# find first empty slot
		$i = 0;
		while ( $i < $#slots and $slots[$i] ) {
			$i++
		}
		return ($i+1);
	}
}

sub getTapeStatus {
	#
	# Sets $nr_tapes, @slots, @drives, $current_tape
	#

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

	if ( !(open(FH,"@CHIO@ -f $changerDevice status|")) ) {
		print "$progname: '@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( $line = <FH> ) {
		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 ) {
			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(); 

	return($nr_tapes);
}

sub getTapeParams {
  
	#
	# Requests information on the number of slots, pickers and drives
	# from the changer.
	#

	my($max_slot,$max_drive,$max_picker);
  
	if ( !open(FH,"@CHIO@ -f $changerDevice params|") ) {
		print "$progname: '@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
	#

	$line = <FH>;
	chomp $line;
	if ($line =~ m/:\s+(\d+)\s+slots,\s+(\d+)\s+drive,\s+(\d+)\s+picker/i) {
		$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 == r or $max_picker == 0 ) {
			print "$progname: No drive or picker ? ($max_drive/$max_picker)\n";
			exit(2);
		}
	} else {
		print "$progname: @CHIO@ -f $changerDevice params failed: $line";
		exit(2);
	}

	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) = @_;

	#
	# Load tape $tape into drive 0
	#

	if ( system("@CHIO@ -f $changerDevice move slot ".($tape-1)." drive 0") ) {
		print "$progname: cannot '$chiocommand -f $changerDevice move' tape $tape into drive 0\n";
		exit(2);
	}
}

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

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

	if ( system("@CHIO@ -f $changerDevice move drive 0 slot ".($tape-1)." ") ) {
		print "$progname: cannot '@CHIO@ -f $changerDevice move' tape $tape from drive 0\n";
		exit(2);
	}
}

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

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

	return
		if $tape == $currentTape;

	&testTape($tape);

	if( $currentTape == 0 ) {
		&Load($tape);
		$currentTape = $tape;
		return;
	} else {
		Unload($currentTape);
		Load($tape);
		$currentTape=$tape;
	}
}


#
# Main program
#

#
# Initialise
#

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

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

$nr_tapes = &getTapeStatus();

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

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


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

	exit 0;
}

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

	print STDERR "$currentTape $max_slot 1\n";
	print "$currentTape $max_slot 1\n";
	exit 0;
}

if ( $opt_reset ) {
	&changeTape(1);
	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;
