--- /dev/null
+#!@SHELL@
+#
+# Exit Status:
+# 0 Alles Ok
+# 1 Illegal Request
+# 2 Fatal Error
+#
+# Contributed by Eric DOUTRELEAU <Eric.Doutreleau@int-evry.fr>
+# This is supposed to work with Zubkoff/Dandelion version of mtx
+#
+# Modified by Joe Rhett <jrhett@isite.net>
+# to work with MTX 1.2.9 by Eric Lee Green http://mtx.sourceforge.net
+#
+# Modified by Jason Hollinden <jhollind@sammg.com> on 13-Feb-2001
+# to work with MTX 1.2.10, >9 slots, has barcode support, and works with
+# multiple configs at once.
+# NOTE: Only tested the 2 additions with an ADIC Scalar 100.
+
+################################################################################
+# Here are the things you need to do and know to configure this script:
+#
+# * Figure out what the robot device name is and what the tape drive
+# device name is. They will be different!
+#
+# You cannot send robot commands to a tape drive and vice versa.
+# Both should respond to "mtx -f /dev/... inquiry". Hopefully,
+# that output will make it obvious which is which.
+#
+# For instance, here is what mtx has to say about my current robot:
+#
+# Product Type: Medium Changer
+# Vendor ID: 'ATL '
+# Product ID: 'ACL2640 206 '
+# Revision: '2A5A'
+# Attached Changer: No
+#
+# and here is what it says about a tape drive:
+#
+# Product Type: Tape Drive
+# Vendor ID: 'Quantum '
+# Product ID: 'DLT4000 '
+# Revision: 'CD50'
+# Attached Changer: No
+#
+# Note the "Product Type" value makes it clear which is which.
+#
+# If it is not obvious, "mf -f /dev/... rewind" should be happy when
+# talking to a (loaded) tape drive but the changer should give some
+# kind of error. Similarly, "mtx -f /dev/... status" should show good
+# results with the changer but fail with a tape drive device name.
+#
+# Once you have this figured out, set "changerdev" in amanda.conf
+# to the changer device and "tapedev" to the tape device.
+#
+# * Find out what the first and last storage slots are. Running
+# "mtx -f /dev/... status" should give you something like this
+# (although the output will vary widely based on the version of mtx
+# and the specifics of your robot):
+#
+# Storage Changer /dev/changer:1 Drives, 9 Slots ( 0 Import/Export )
+# Data Transfer Element 0:Empty
+# Storage Element 1:Full :VolumeTag=SR0001
+# Storage Element 2:Full :VolumeTag=SR0002
+# Storage Element 3:Full :VolumeTag=SR0003
+# Storage Element 4:Full :VolumeTag=SR0004
+# Storage Element 5:Full :VolumeTag=SR0005
+# Storage Element 6:Full :VolumeTag=SR0006
+# Storage Element 7:Full :VolumeTag=SR0007
+# Storage Element 8:Full :VolumeTag=SR0008
+# Storage Element 9:Full :VolumeTag=SR0009
+# Storage Element 10 IMPORT/EXPORT:Full :VolumeTag=SR0009
+#
+# This says the first storage slot (element) is "1" and the last
+# is "9". If you allocate the entire robot to Amanda, you do not need
+# to set the "firstslot" or "lastslot" configuration file variables --
+# the script will compute these values for you.
+#
+# You do not have to allocate all of the slots for Amanda use,
+# but whatever slots you use must be contiguous (i.e. 4 through 9
+# in the above would be OK but 1, 2, 5, 6, 9 would not). The one
+# exception to this is that if one of the slots contains a cleaning
+# cartridge, it may be in any slot (Amanda will just skip over it if
+# it is between firstslot and lastslot).
+#
+# * Speaking of cleaning cartridges, if you have a storage slot dedicated
+# to one, figure out what slot it is in. That slot number will go in
+# the "cleanslot" variable.
+#
+# Also, decide if you want the changer script to automatically run
+# the cleaning tape through the drive after every so many mounts,
+# and how many mounts you want to do between cleanings. If you
+# want the script to do this, set the "autoclean" variable to 1 and
+# the "autocleancount" to the number of mounts between cleanings.
+# If you do not want to do automatic cleanings (including not having
+# a cleaning cartridge in the robot), set "autoclean" to 0.
+#
+# Note that only a count of mounts is used to determine when it is
+# time to clean. The script does not try to detect if the drive is
+# requesting cleaning, or how much the drive was used on a given
+# mount.
+#
+# * If you tell Amanda about a cleaning cartridge, whether for automatic
+# operation or manual (amtape <config> clean), you must also tell
+# the script how long it takes to run the cleaning cycle. It is
+# impossible for the script to determine when the cleaning operation
+# is done, so the "cleancycle" variable is the number of seconds
+# the longest cleaning operation takes (you'll just have to figure
+# this out by watching it a few times, or maybe finding it in a tape
+# drive hardware manual). The script will sleep for this length of
+# time whenever the cleaning tape is referenced. The default is 120
+# seconds (two minutes).
+#
+# * Figure out the drive slot number. By default, it is set to 0.
+# In the example above, the tape drive ("Data Transfer Element")
+# is in slot 0. If your drive slot is not 0, you
+# need to set the drive slot number with the "driveslot" variable.
+#
+# * Figure out whether your robot has a barcode reader and whether
+# your version of mtx supports it. If you see "VolumeTag" entries
+# in the "mtx -f /dev/xxx status" output you did above, you have
+# a reader and mtx can work with it, so you may set the "havereader"
+# variable to 1. The default is 0 (do not use a reader).
+#
+# * Pick any tape to load and then determine if the robot can put it
+# away directly or whether an "offline" must be done first.
+#
+# With the tape still mounted and ready, try to put the tape away
+# with "mtx". If you get some kind of error, which is the most
+# common response, try "mt -f /dev/... offline", wait for the drive
+# to unload and make sure the robot takes no action on its own to
+# store the tape. Assuming it does not, try the "mtx" command again
+# to store the tape.
+#
+# If you had to issue the "mt -f /dev/... offline" before you could
+# use "mtx" to store the tape, set the "offline_before_unload"
+# variable to 1. If "mtx" unloaded the drive and put the tape away
+# all by itself, set it to 0.
+#
+# * Some drives and robots require a small delay between unloading the
+# tape and instructing the robot to move it back to storage.
+# For instance, if you try to grab the tape too soon on an ATL robot
+# with DLT tape drives, it will rip the leader out of the drive and
+# require sincerely painful hardware maintenance.
+#
+# If you need a little delay, set the "unloadpause" variable to
+# the number of seconds to wait before trying to take a tape from
+# a drive back to storage. The default is 0.
+#
+# * Some drives also require a short pause after loading, or the drive
+# will return an I/O error during a test to see if it's online (which
+# this script uses "mt rewind" to test). My drives don't recover from
+# this, and must be reloaded before they will come online after failing
+# such a test. For this reason there is an "initial_poll_delay"
+# variable which will pause for a certain number of seconds before
+# looping through the online test for the first time. The default is 0.
+####
+
+####
+# Now you are ready to set up the variables in the changer configuration
+# file.
+#
+# All variables are in "changerfile".conf where "changerfile" is set
+# in amanda.conf. For example, if amanda.conf has:
+#
+# changerfile="/etc/amanda/Dailyset1/CHANGER"
+# or changerfile="/etc/amanda/Dailyset1/CHANGER.conf"
+#
+# the variables must be in "/etc/amanda/Dailyset1/CHANGER.conf".
+# The ".conf" is appended only if it's not there".
+#
+# If "changerfile" is a relative path, it is relative to the directory
+# that contains amanda.conf. That also happens to be the directory Amanda
+# makes current before running this script.
+#
+# Here is a commented out example file with all the variables and showing
+# their default value (if any):
+####
+# firstslot=? #### First storage slot (element) -- required
+# lastslot=? #### Last storage slot (element) -- required
+# cleanslot=-1 #### Slot with cleaner tape -- default is "-1"
+# #### Set negative to indicate no cleaner available
+# driveslot=0 #### Drive slot number. Defaults to 0
+# #### Use the 'Data Transfer Element' you want
+#
+# # Do you want to clean the drive after a certain number of accesses?
+# # NOTE - This is unreliable, since 'accesses' aren't 'uses', and we
+# # have no reliable way to count this. A single amcheck could
+# # generate as many accesses as slots you have, plus 1.
+# # ALSO NOTE - many modern tape loaders handle this automatically.
+#
+# autoclean=0 #### Set to '1' or greater to enable
+#
+# autocleancount=99 #### Number of access before a clean.
+#
+# havereader=0 #### If you have a barcode reader, set to 1.
+#
+# offline_before_unload=0 #### Does your robot require an
+# #### 'mt offline' before mtx unload?
+#
+# poll_drive_ready=NN #### Time (seconds) between tests to see if
+# #### the tape drive has gone ready (default: 3).
+#
+# max_drive_wait=NN #### Maximum time (seconds) to wait for the
+# #### tape drive to become ready (default: 120).
+#
+# initial_poll_delay=NN #### initial delay after load before polling for
+# #### readiness
+#
+####
+
+####
+# Now it is time to test the setup. Do all of the following in the
+# directory that contains the amanda.conf file, and do all of it as
+# the Amanda user.
+#
+# * Run this:
+#
+# .../chg-zd-mtx -info
+# echo $? #### (or "echo $status" if you use csh/tcsh)
+#
+# You should get a single line from the script like this (the actual
+# numbers will vary):
+#
+# 5 9 1 1
+#
+# The first number (5) is the "current" slot. This may or may not be
+# the slot actually loaded at the moment (if any). It is the slot
+# Amanda will try to use next.
+#
+# The second number (9) is the number of slots.
+#
+# The third number will always be "1" and indicates the changer is
+# capable of going backward.
+#
+# The fourth number is optional. If you set $havereader to 1, it
+# will be "1", otherwise it will not be present.
+#
+# The exit code ($? or $status) should be zero.
+#
+# * Run this:
+#
+# .../chg-zd-mtx -reset
+# echo $?
+#
+# The script should output a line like this:
+#
+# 1 /dev/rmt/0mn
+#
+# The number at the first should match $firstslot. The device name
+# after that should be your tape device.
+#
+# The exit code ($? or $status) should be zero.
+#
+# * Run this:
+#
+# .../chg-zd-mtx -slot next
+# echo $?
+#
+# The script should output a line like this:
+#
+# 2 /dev/rmt/0mn
+#
+# The number at the first should be one higher than $firstslot.
+# The device name after that should be your tape device.
+#
+# The exit code ($? or $status) should be zero.
+#
+# * Run this:
+#
+# .../chg-zd-mtx -slot current
+# echo $?
+#
+# Assuming the tape is still loaded from the previous test, the
+# robot should not move and the script should report the same thing
+# the previous command did.
+#
+# * If you continue to run "-slot next" commands, the robot should load
+# each tape in turn then wrap back around to the first when it
+# reaches $lasttape. If $cleanslot is within the $firstslot to
+# $lastslot range, the script will skip over that entry.
+#
+# * Finally, try some of the amtape commands and make sure they work:
+#
+# amtape <config> reset
+# amtape <config> slot next
+# amtape <config> slot current
+#
+# * If you set $havereader non-zero, now would be a good time to create
+# the initial barcode database:
+#
+# amtape <config> update
+####
+
+################################################################################
+# To debug this script, first look in @AMANDA_DBGDIR@. The script
+# uses one of two log files there, depending on what version of Amanda
+# is calling it. It may be chg-zd-mtx.YYYYMMDD*.debug, or it may be
+# changer.debug.driveN where 'N' is the drive number.
+#
+# If the log file does not help, try running the script, **as the Amanda
+# user**, in the amanda.conf directory with whatever set of args the log
+# said were used when you had a problem. If nothing else useful shows up
+# in the output, try running the script with the DEBUG environment variable
+# set non-null, e.g.:
+#
+# env DEBUG=yes .../chg-zd-mtx ...
+################################################################################
+
+# source utility functions and values from configure
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+amlibexecdir=@amlibexecdir@
+. ${amlibexecdir}/chg-lib.sh
+
+test -n "$DEBUG" && set -x
+TMPDIR="@AMANDA_TMPDIR@"
+DBGDIR="@AMANDA_DBGDIR@"
+
+argv0=$0
+myname=`expr "$argv0" : '.*/\(.*\)'`
+
+config=`pwd 2>/dev/null`
+config=`expr "$config" : '.*/\(.*\)'`
+
+###
+# Functions to write a new log file entry and append more log information.
+###
+
+ds=`date '+%H:%M:%S' 2>/dev/null`
+if [ $? -eq 0 -a -n "$ds" ]; then
+ logprefix=`echo "$ds" | sed 's/./ /g'`
+else
+ logprefix=""
+fi
+
+LogAppend() {
+ if [ -z "$logprefix" ]; then
+ echo "$@" >> $DBGFILE
+ else
+ echo "$logprefix" "$@" >> $DBGFILE
+ fi
+}
+
+Log() {
+ if [ -z "$logprefix" ]; then
+ echo "===" "`date`" "===" >> $DBGFILE
+ echo "$@" >> $DBGFILE
+ else
+ ds=`date '+%H:%M:%S' 2>/dev/null`
+ echo "$ds" "$@" >> $DBGFILE
+ fi
+}
+
+###
+# Common exit function.
+#
+# $1 = exit code
+# $2 = slot result
+# $3 = additional information (error message, tape devive, etc)
+###
+
+internal_call=0
+Exit() {
+ if [ $internal_call -gt 0 ]; then
+ call_type=Return
+ else
+ call_type=Exit
+ fi
+ code=$1
+ shift
+ exit_slot=$1
+ shift
+ exit_answer="$@"
+ Log $call_type "($code) -> $exit_slot $@"
+ echo "$exit_slot" "$@"
+ if [ $call_type = Return ]; then
+ return $code
+ fi
+ amgetconf dbclose.$argv0:$DBGFILE > /dev/null 2>&1
+ exit $code
+}
+
+###
+# Function to run another command and log it.
+###
+
+Run() {
+ Log `_ 'Running: %s' "$@"`
+ rm -f $stdout $stderr
+ "$@" > $stdout 2> $stderr
+ exitcode=$?
+ Log `_ 'Exit code: %s' "$exitcode"`
+ if [ -s $stdout ]
+ then
+ LogAppend Stdout:
+ cat $stdout >> $DBGFILE
+ fi
+ if [ -s $stderr ]
+ then
+ LogAppend Stderr:
+ cat $stderr >> $DBGFILE
+ fi
+ cat $stdout
+ cat $stderr 1>&2
+ return $exitcode
+}
+
+###
+# Return success if the arg is numeric.
+###
+
+IsNumeric() {
+ test -z "$1" && return 1
+ x="`expr "$1" : '\([-0-9][0-9]*\)' 2>/dev/null`"
+ return `expr X"$1" != X"$x"`
+}
+
+###
+# Run $MTX status unless the previous output is still valid.
+###
+
+mtx_status_valid=0
+get_mtx_status() {
+ test -n "$DEBUG" && set -x
+ if [ $mtx_status_valid -ne 0 ]; then
+ return 0
+ fi
+ rm -f $mtx_status
+ Run $MTX status > $mtx_status 2>&1
+ status=$?
+ if [ $status -eq 0 ]; then
+ mtx_status_valid=1
+ fi
+ return $status
+}
+
+###
+# Determine the slot currently loaded. Set $loadedslot to the slot
+# currently loaded, or "-1", and $loadedbarcode to the corresponding
+# barcode (or nothing).
+###
+
+get_loaded_info() {
+ test -n "$DEBUG" && set -x
+ get_mtx_status
+
+ set x `sed -n '
+/^Data Transfer Element:Empty/ {
+ s/.*/-1/p
+ q
+}
+/^Data Transfer Element '$driveslot':Empty/ {
+ s/.*/-1/p
+ q
+}
+/^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/ {
+ s/.*(Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/\1 \2/p
+ q
+}
+/^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/ {
+ s/.*(Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/\1 \2/p
+ q
+}
+/^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded):VolumeTag *= *\([^ ]*\)/ {
+ s/.*:VolumeTag *= *\([^ ]*\)/-2 \1/p
+ q
+}
+/^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
+ s/.*(Storage Element \([0-9][0-9]*\) Loaded).*/\1/p
+ q
+}
+/^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
+ s/.*Storage Element \([0-9][0-9]*\) Loaded.*/\1/p
+ q
+}
+/^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded)/ {
+ s/.*/-2/p
+ q
+}
+' < $mtx_status 2>&1`
+ shift # get rid of the "x"
+ loadedslot=$1
+ loadedbarcode=$2
+ if [ -z "$loadedslot" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "could not determine current slot, are you sure your drive slot is $driveslot"
+ return $? # in case we are internal
+ fi
+
+ #Use the current slot if it's empty and we don't know which slot is loaded'
+ if [ $loadedslot -eq -2 ]; then
+ set x `sed -n '
+{
+ /^.*Storage Element '$currentslot':Empty/ {
+ s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
+ q
+ }
+ /^.*Storage Element '$currentslot':Full/ {
+ s/.*Storage Element \([0-9][0-9]*\):Full/-2/p
+ q
+ }
+ /^.*Storage Element '$currentslot' IMPORT\/EXPORT:Empty/ {
+ s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/\1/p
+ q
+ }
+ /^.*Storage Element '$currentslot' IMPORT\/EXPORT:Full/ {
+ s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full/-2/p
+ q
+ }
+}
+' < $mtx_status 2>& 1`
+ shift # get rid of the "x"
+ loadedslotx=$1
+ if [ ! -z $loadedslotx ]; then
+ loadedslot=$loadedslotx
+ fi
+ fi
+
+ #Use the first empty slot if we don't know which slot is loaded'
+ if [ $loadedslot -eq -2 ]; then
+ set x `sed -n '
+{
+ /^.*Storage Element \([0-9][0-9]*\):Empty/ {
+ s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
+ q
+ }
+ /^.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/ {
+ s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/\1/p
+ q
+ }
+}
+' < $mtx_status 2>& 1`
+ shift # get rid of the "x"
+ loadedslot=$1
+ fi
+
+ if IsNumeric "$loadedslot" ; then
+ :
+ else
+ Exit 2 \
+ `_ '<none>'` \
+ "currently loaded slot ($loadedslot) not numeric"
+ return $? # in case we are internal
+ fi
+ Log `_ 'STATUS -> currently loaded slot = %s' "$loadedslot"`
+ LogAppend `_ ' -> currently loaded barcode = "%s"' "$loadedbarcode"`
+}
+
+###
+# Get a list of slots between $firstslot and $lastslot, if they are set.
+# If they are not set, set them to the first and last slot seen on the
+# assumption the entire robot is to be used (???).
+###
+
+slot_list=
+get_slot_list() {
+ test -n "$DEBUG" && set -x
+ if [ -n "$slot_list" ]; then
+ return
+ fi
+ get_mtx_status
+ slot_list=`sed -n '
+/^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
+ s/.*(Storage Element \([0-9][0-9]*\) Loaded).*/\1/p
+}
+/^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
+ s/.*Storage Element \([0-9][0-9]*\) Loaded.*/\1/p
+}
+/^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded)/ {
+ : loop
+ n
+ /^.*Storage Element \([0-9][0-9]*\):Full/ {
+ s/.*Storage Element \([0-9][0-9]*\):Full.*/\1/p
+ b loop
+ }
+ /^.*Storage Element \([0-9][0-9]*\):Empty/ {
+ s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
+ }
+}
+/^.*Storage Element \([0-9][0-9]*\):Full/ {
+ s/.*Storage Element \([0-9][0-9]*\):Full.*/\1/p
+}
+/^.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full/ {
+ s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full.*/\1/p
+}
+' < $mtx_status 2>&1 | grep -v "^${cleanslot}\$" | sort -n`
+ slot_list=`echo $slot_list` # remove the newlines
+ if [ $firstslot -lt 0 -o $lastslot -lt 0 ]; then
+ last=$lastslot
+ for slot in $slot_list; do
+ if [ $firstslot -lt 0 ]; then
+ Log `_ 'SLOTLIST -> firstslot set to %s' "$slot"`
+ firstslot=$slot
+ fi
+ if [ $lastslot -lt 0 ]; then
+ last=$slot
+ fi
+ done
+ if [ $lastslot -lt 0 -a $last -ge 0 ]; then
+ Log `_ 'SLOTLIST -> lastslot set to %s' "$last"`
+ lastslot=$last
+ fi
+ if [ $firstslot -lt 0 ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'cannot determine first slot'`
+ return $? # in case we are internal
+ elif [ $lastslot -lt 0 ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'cannot determine last slot'`
+ return $? # in case we are internal
+ fi
+ fi
+ amanda_slot_list=
+ for slot in $slot_list; do
+ if [ $slot -ge $firstslot -a $slot -le $lastslot ]; then
+ amanda_slot_list="$amanda_slot_list $slot"
+ fi
+ done
+ if [ -z "$amanda_slot_list" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "no slots available"
+ return $? # in case we are internal
+ fi
+ slot_list="$amanda_slot_list"
+}
+
+DBGFILE=`amgetconf dbopen.$argv0 2>/dev/null`
+if [ -z "$DBGFILE" ]
+then
+ DBGFILE=/dev/null # will try this again below
+fi
+
+changerfile=`amgetconf changerfile 2>/dev/null`
+if [ -z "$changerfile" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "changerfile must be specified in amanda.conf"
+fi
+
+rawtape=`amgetconf tapedev 2>/dev/null`
+if [ -z "$rawtape" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "tapedev may not be empty"
+fi
+tape=`tape_device_filename "$rawtape"`
+if [ -z "$tape" ]; then
+ Exit 2 \
+ ` _ '<none>'` \
+ "tapedev $rawtape is not a tape device."
+elif [ $tape = "/dev/null" -o `expr "$tape" : 'null:'` -eq 5 ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "tapedev ($tape) may not be the null device"
+fi
+# Confusingly, TAPE is the name of the changer device...
+TAPE=`amgetconf changerdev 2>/dev/null`
+if [ -z "$TAPE" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "changerdev may not be empty"
+elif [ $TAPE = "/dev/null" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "changerdev ($TAPE) may not be the null device"
+fi
+export TAPE # for mtx command
+
+CHANGER=$TAPE
+export CHANGER # for mtx command
+
+#### Set up the various config files.
+
+conf_match=`expr "$changerfile" : .\*\.conf\$`
+if [ $conf_match -ge 6 ]; then
+ configfile=$changerfile
+ changerfile=`echo $changerfile | sed 's/.conf$//g'`
+else
+ configfile=$changerfile.conf
+fi
+
+if [ ! -e $configfile ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "configuration file \"$configfile\" doesn't exist"
+fi
+if [ ! -f $configfile ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ "configuration file \"$configfile\" is not a file"
+fi
+
+cleanfile=$changerfile-clean
+accessfile=$changerfile-access
+slotfile=$changerfile-slot
+labelfile=$changerfile-barcodes
+[ ! -s $cleanfile ] && echo 0 > $cleanfile
+[ ! -s $accessfile ] && echo 0 > $accessfile
+[ ! -s $slotfile ] && echo -1 > $slotfile
+[ ! -f $labelfile ] && > $labelfile
+cleancount=`cat $cleanfile`
+accesscount=`cat $accessfile`
+
+#### Dig out of the config file what is needed
+
+varlist=
+varlist="$varlist firstslot"
+varlist="$varlist lastslot"
+varlist="$varlist cleanslot"
+varlist="$varlist cleancycle"
+varlist="$varlist OFFLINE_BEFORE_UNLOAD" # old name
+varlist="$varlist offline_before_unload"
+varlist="$varlist unloadpause"
+varlist="$varlist AUTOCLEAN" # old name
+varlist="$varlist autoclean"
+varlist="$varlist autocleancount"
+varlist="$varlist havereader"
+varlist="$varlist driveslot"
+varlist="$varlist poll_drive_ready"
+varlist="$varlist initial_poll_delay"
+varlist="$varlist max_drive_wait"
+
+for var in $varlist
+do
+ val="`cat $configfile 2>/dev/null | sed -n '
+# Ignore comment lines (anything starting with a #).
+/^[ ]*#/d
+# Find the first var=val line in the file, print the value and quit.
+/^[ ]*'$var'[ ]*=[ ]*\([^ ][^ ]*\).*/ {
+ s/^[ ]*'$var'[ ]*=[ ]*\([^ ][^ ]*\).*/\1/p
+ q
+}
+'`"
+ eval $var=\"$val\"
+done
+
+# Deal with driveslot first so we can get DBGFILE set if we are still
+# using the old amgetconf.
+
+if [ -z "$driveslot" ]; then
+ driveslot=0;
+fi
+
+# Get DBGFILE set if it is not already.
+
+if [ $DBGFILE = /dev/null ]; then
+ if [ -d "$DBGDIR" ]; then
+ DBGFILE=$DBGDIR/changer.debug.drive$driveslot
+ else
+ DBGFILE=/dev/null
+ fi
+ Log `_ '=== Start %s ===' "\`date\`"`
+fi
+
+stdout=$TMPDIR/$myname.1.$$
+stderr=$TMPDIR/$myname.2.$$
+mtx_status=$TMPDIR/$myname.status.$$
+trap "rm -f $stdout $stderr $mtx_status" 0 # exit cleanup
+
+Log `_ 'Using config file %s' "$configfile"`
+
+# Log the argument list.
+
+Log `_ "Arg info:"`
+LogAppend "\$# = $#"
+i=0
+LogAppend "\$$i = \"$argv0\""
+for arg in "$@"; do
+ i=`expr $i + 1`
+ LogAppend "\$$i = \"$arg\""
+done
+
+# Set the default config values for those not in the file. Log the
+# results and make sure each is valid (numeric).
+
+firstslot=${firstslot:-'-1'} # default: mtx status
+lastslot=${lastslot:-'-1'} # default: mtx status
+cleanslot=${cleanslot:-'-1'} # default: -1
+cleancycle=${cleancycle:-'120'} # default: two minutes
+if [ -z "$offline_before_unload" -a -n "$OFFLINE_BEFORE_UNLOAD" ]; then
+ offline_before_unload=$OFFLINE_BEFORE_UNLOAD # (old name)
+fi
+offline_before_unload=${offline_before_unload:-'0'} # default: 0
+unloadpause=${unloadpause:-'0'} # default: 0
+if [ -z "$autoclean" -a -n "$AUTOCLEAN" ]; then
+ autoclean=$AUTOCLEAN # (old name)
+fi
+autoclean=${autoclean:-'0'} # default: 0
+autocleancount=${autocleancount:-'99'} # default: 99
+havereader=${havereader:-'0'} # default: 0
+poll_drive_ready=${poll_drive_ready:-'3'} # default: three seconds
+initial_poll_delay=${initial_poll_delay:-'0'} # default: zero zeconds
+max_drive_wait=${max_drive_wait:-'120'} # default: two minutes
+
+# check MT and MTX for sanity
+if test "${MTX:0:1}" = "/"; then
+ if ! test -f "${MTX}"; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ "mtx binary at '%s' not found" "$MTX"`
+ fi
+ if ! test -x "${MTX}"; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ "mtx binary at '%s' is not executable" "$MTX"`
+ fi
+else
+ # try running it to see if the shell can find it
+ "$MTX" >/dev/null 2>/dev/null
+ if test $? -eq 127 -o $? -eq 126; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ "Could not run mtx binary at '%s'" "$MTX"`
+ fi
+fi
+
+error=`try_find_mt`
+if test $? -ne 0; then
+ Exit 2 '<none>' $error
+fi
+
+get_slot_list
+
+Log `_ "Config info:"`
+for var in $varlist; do
+ if [ $var = "OFFLINE_BEFORE_UNLOAD" ]; then
+ continue # old name
+ elif [ $var = "AUTOCLEAN" ]; then
+ continue # old name
+ fi
+ eval val=\"'$'$var\"
+ if [ -z "$val" ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ '%s missing in %s' "$var" "$configfile"`
+ fi
+ if IsNumeric "$val" ; then
+ :
+ else
+ Exit 2 \
+ `_ '<none>'` \
+ `_ '%s (%s) not numeric in %s' "$var" "$val" "$configfile"`
+ fi
+ LogAppend $var = \"$val\"
+done
+
+# Run the rest of the config file sanity checks.
+
+if [ $firstslot -gt $lastslot ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'firstslot (%s) greater than lastslot (%s) in %s' "$firstslot" "$lastslot" "$configfile"`
+fi
+if [ $autoclean -ne 0 -a $cleanslot -lt 0 ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'autoclean set but cleanslot not valid (%s)' "$cleanslot"`
+fi
+
+# Set up the current slot
+
+currentslot=`cat $slotfile`
+if IsNumeric "$currentslot" ; then
+ if [ $currentslot -lt $firstslot ]; then
+ Log `_ 'SETUP -> current slot %s less than %s ... resetting to %s' "$currentslot" "$firstslot" "$firstslot"`
+ currentslot=$firstslot
+ elif [ $currentslot -gt $lastslot ]; then
+ Log `_ 'SETUP -> current slot %s greater than %s ... resetting to %s' "$currentslot" "$lastslot" "$lastslot"`
+ currentslot=$lastslot
+ fi
+else
+ Log `_ 'SETUP -> contents of %s (%s) invalid, setting current slot to first slot (%s)' "$slotfile" "$currentslot" "$firstslot"`
+ currentslot=$firstslot
+fi
+
+found_current=0
+first_slot_in_list=-1
+next_slot_after_current=-1
+for slot in $slot_list; do
+ if [ $first_slot_in_list -lt 0 ]; then
+ first_slot_in_list=$slot # in case $firstslot is missing
+ fi
+ if [ $slot -eq $currentslot ]; then
+ found_current=1
+ break
+ elif [ $slot -gt $currentslot ]; then
+ next_slot_after_current=$slot # $currentslot is missing
+ break
+ fi
+done
+if [ $found_current -eq 0 ]; then
+ if [ $next_slot_after_current -lt 0 ]; then
+ new_currentslot=$first_slot_in_list
+ else
+ new_currentslot=$next_slot_after_current
+ fi
+ Log `_ 'WARNING -> current slot %s not available, setting current slot to next slot (%s)' "$currentslot" "$new_currentslot"`
+ currentslot=$new_currentslot
+fi
+
+# More routines.
+
+###
+# Eject the current tape and put it away.
+###
+
+eject() {
+ test -n "$DEBUG" && set -x
+ Log `_ 'EJECT -> ejecting tape from %s' "$tape"`
+ get_loaded_info
+ if [ $loadedslot -gt 0 ]; then
+ Log `_ 'EJECT -> moving tape from drive %s to storage slot %s' "$driveslot" "$loadedslot"`
+ if [ $offline_before_unload -ne 0 ]; then
+ Run try_eject_device $tape
+ fi
+ sleep $unloadpause
+ result=`Run $MTX unload $loadedslot $driveslot 2>&1`
+ status=$?
+ Log `_ ' -> status %s, result "%s"' "$status" "$result"`
+ mtx_status_valid=0
+ if [ $status -ne 0 ]; then
+ answer="$result"
+ code=2
+ else
+ answer="$rawtape"
+ code=0
+ fi
+ else
+ answer=`_ 'Drive was not loaded'`
+ code=1
+ fi
+ Exit $code "$loadedslot" "$answer"
+ return $? # in case we are internal
+}
+
+###
+# Reset the robot back to the first slot.
+###
+
+reset() {
+ test -n "$DEBUG" && set -x
+ Log `_ 'RESET -> loading tape from slot %s to drive %s (%s)' "$firstslot" "$driveslot" "$tape"`
+ # Call loadslot without doing it as an internal and let it finish
+ # things up.
+ loadslot $firstslot
+ # NOTREACHED
+ Exit 2 `_ '<none>'` `_ 'reset: should not get here'`
+ return $? # in case we are internal
+}
+
+###
+# Unload the current tape (if necessary) and load a new one (unless
+# "advance"). If no tape is loaded, get the value of "current" from
+# $slotfile.
+###
+
+loadslot() {
+ test -n "$DEBUG" && set -x
+ if [ $# -lt 1 ]; then
+ Exit 2 `_ '<none>'` `_ 'Missing -slot argument'`
+ return $? # in case we are internal
+ fi
+ whichslot=$1
+ Log `_ 'LOADSLOT -> load drive %s (%s) from slot %s' "$driveslot" "$tape" "$whichslot"`
+
+ numeric=`echo $whichslot | sed 's/[^0-9]//g'`
+ case $whichslot in
+ current|prev|next|advance)
+ find_slot=$currentslot
+ ;;
+ first)
+ find_slot=$firstslot
+ ;;
+ last)
+ find_slot=$lastslot
+ ;;
+ $numeric)
+ find_slot=$numeric
+ ;;
+ clean)
+ find_slot=$cleanslot
+ ;;
+ *)
+ Exit 2 `_ '<none>'` `_ 'Illegal slot: "%s"' "$whichslot"`
+ return $? # in case we are internal
+ ;;
+ esac
+
+ # Find the requested slot in the slot list. By loading the "set"
+ # command with multiple copies, we guarantee that if the slot is
+ # found, we can look both forward and backward without running
+ # off the end. Putting $cleanslot at the end allows us to find
+ # that slot since it is not in $slot_list.
+ get_slot_list
+ set x $slot_list $slot_list $slot_list $cleanslot
+ shift # get rid of the "x"
+ prev_slot=$1
+ shift
+ while [ $# -gt 0 ]; do
+ if [ $1 -eq $find_slot ]; then
+ break
+ fi
+ prev_slot=$1
+ shift
+ done
+ if [ $# -le 0 ]; then
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'Cannot find slot %s in slot list (%s)' "$find_slot " "$slot_list"`
+ return $? # in case we are internal
+ fi
+
+ # Determine the slot to load.
+ case $whichslot in
+ next|advance)
+ shift
+ loadslot=$1
+ ;;
+ prev)
+ loadslot=$prev_slot
+ ;;
+ *)
+ loadslot=$find_slot
+ esac
+
+ # If the desired slot is already loaded, we are done. Only update
+ # current slot if this is not the cleaning slot.
+ get_loaded_info
+ if [ $loadslot = $loadedslot ]; then
+ if [ $loadslot -ne $cleanslot ]; then
+ rm -f $slotfile
+ echo $loadslot > $slotfile
+ fi
+ Exit 0 "$loadedslot" "$rawtape"
+ return $? # in case we are internal
+ fi
+ if [ $loadedslot -eq -2 ]; then
+ Exit 0 "$loadedslot" "$rawtape"
+ return $? # in case we are internal
+ fi
+
+ # If we are loading the cleaning tape, bump the cleaning count
+ # and reset the access count. Otherwise, bump the access count
+ # and see if it is time to do a cleaning.
+ if [ $loadslot = $cleanslot ]; then
+ rm -f $cleanfile $accessfile
+ expr $cleancount + 1 > $cleanfile
+ echo 0 > $accessfile
+ else
+ rm -f $accessfile
+ expr $accesscount + 1 > $accessfile
+ if [ $autoclean -ne 0 -a $accesscount -gt $autocleancount ]
+ then
+ internal_call=`expr $internal_call + 1`
+ loadslot clean > /dev/null 2>&1
+ status=$?
+ internal_call=`expr $internal_call - 1`
+ if [ $status -ne 0 ]; then
+ Exit $status "$loadslot" "$exit_answer"
+ return $? # in case we are internal
+ fi
+
+ # Slot $cleanslot might contain an ordinary tape
+ # rather than a cleaning tape. A cleaning tape
+ # *MIGHT* auto-eject; an ordinary tape does not.
+ # We therefore have to read the status again to
+ # check what actually happened.
+ mtx_status_valid=0
+ get_loaded_info
+ fi
+ fi
+
+ # Unload whatever tape is in the drive.
+ internal_call=`expr $internal_call + 1`
+ eject > /dev/null 2>&1
+ status=$?
+ internal_call=`expr $internal_call - 1`
+ if [ $status -gt 1 ]; then
+ Exit $status "$exit_slot" "$exit_answer"
+ return $? # in case we are internal
+ fi
+
+ # If we were doing an "advance", we are done.
+ if [ $whichslot = advance ]; then
+ if [ $loadslot -ne $cleanslot ]; then
+ rm -f $slotfile
+ echo $loadslot > $slotfile
+ fi
+ Exit 0 "$loadslot" "/dev/null"
+ return $? # in case we are internal
+ fi
+
+ # Load the tape, finally!
+ Log `_ "LOADSLOT -> loading tape from slot %s to drive %s (%s)" "$loadslot" "$driveslot" "$tape"`
+ result=`Run $MTX load $loadslot $driveslot 2>&1`
+ status=$?
+ Log `_ ' -> status %s, result "%s"' "$status" "$result"`
+ mtx_status_valid=0
+ if [ $status -ne 0 ]; then
+ Exit 2 "$loadslot" "$result"
+ return $? # in case we are internal
+ fi
+
+ ###
+ # Cleaning tapes never go "ready", so instead we just sit here
+ # for "long enough" (as determined empirically by the user),
+ # then return success.
+ ###
+ if [ $loadslot -eq $cleanslot ]; then
+ Run sleep $cleancycle
+ Exit 0 "$loadslot" "$rawtape"
+ return $? # in case we are internal
+ fi
+
+ ###
+ # Wait for the drive to go online.
+ ###
+ waittime=0
+ ready=0
+ sleep $initial_poll_delay
+ while [ $waittime -lt $max_drive_wait ]; do
+ amdevcheck_status $tape
+ if [ $? -eq 0 ]; then
+ ready=1
+ break
+ fi
+ sleep $poll_drive_ready
+ waittime=`expr $waittime + $poll_drive_ready`
+ done
+ if [ $ready -eq 0 ]; then
+ Exit 2 "$loadslot" `_ 'Drive not ready after %s seconds, rewind said "%s"' "$max_drive_wait" "$result"`
+ return $? # in case we are internal
+ fi
+
+ if [ $loadslot -ne $cleanslot ]; then
+ rm -f $slotfile
+ echo $loadslot > $slotfile
+ fi
+ Exit 0 "$loadslot" "$rawtape"
+ return $? # in case we are internal
+}
+
+###
+# Return information about how the changer is configured and the current
+# state of the robot.
+###
+
+info() {
+ test -n "$DEBUG" && set -x
+ get_loaded_info
+ get_slot_list
+ Log `_ 'INFO -> first slot: %s' "$firstslot"`
+ LogAppend `_ ' -> current slot: %s' "$currentslot"`
+ LogAppend `_ ' -> loaded slot: %s' "$loadedslot"`
+ LogAppend `_ ' -> last slot: %s' "$lastslot"`
+ LogAppend `_ ' -> slot list: %s' "$slot_list"`
+ LogAppend `_ ' -> can go backwards: 1'`
+ LogAppend `_ ' -> havereader: %s' "$havereader"`
+
+ ###
+ # Check if a barcode reader is configured or not. If so, it
+ # passes the 4th item in the echo back to amtape signifying it
+ # can search based on barcodes.
+ ###
+ reader=
+ if [ $havereader -eq 1 ]; then
+ reader=1
+ fi
+
+ if [ $currentslot -lt $firstslot -o $currentslot -gt $lastslot ]; then
+ currentslot=$firstslot # what "current" will get
+ fi
+ numslots=`expr $lastslot - $firstslot + 1`
+ Exit 0 "$currentslot" "$numslots 1 $reader"
+ return $? # in case we are internal
+}
+
+###
+# Read the labelfile and scan for a particular entry.
+###
+
+read_labelfile() {
+ labelfile_entry_found=0
+ labelfile_label=
+ labelfile_barcode=
+
+ lbl_search=$1
+ bc_search=$2
+
+ line=0
+ while read lbl bc junk; do
+ line=`expr $line + 1`
+ if [ -z "$lbl" -o -z "$bc" -o -n "$junk" ]; then
+ Log `_ 'ERROR -> Line %s malformed: %s %s %s' "$line" "$lbl" "$bc" "$junk"`
+ LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'Line %s malformed in %s: %s %s %s' "$line" "$labelfile" "$lbl" "$bc" "$junk"`
+ return $? # in case we are internal
+ fi
+ if [ $lbl = "$lbl_search" -o $bc = "$bc_search" ]; then
+ if [ $labelfile_entry_found -ne 0 ]; then
+ Log `_ 'ERROR -> Duplicate entries: %s line %s' "$labelfile" "$line"`
+ LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'Duplicate entries: %s line %s' "$labelfile" "$line"`
+ return $? # in case we are internal
+ fi
+ labelfile_entry_found=1
+ labelfile_label=$lbl
+ labelfile_barcode=$bc
+ fi
+ done
+}
+
+###
+# Adds the label and barcode for the currently loaded tape to the
+# barcode file. Return an error if the database is messed up.
+###
+
+addlabel() {
+ test -n "$DEBUG" && set -x
+ if [ $# -lt 1 ]; then
+ Exit 2 `_ '<none>'` `_ 'Missing -label argument'`
+ return $? # in case we are internal
+ fi
+ tapelabel=$1
+ if [ $havereader -eq 0 ]; then
+ Exit 2 `_ '<none>'` `_ 'Not configured with barcode reader'`
+ return $? # in case we are internal
+ fi
+ get_loaded_info
+ if [ $loadedslot -lt 0 ]; then
+ Exit 1 `_ '<none>'` `_ 'No tape currently loaded'`
+ return $? # in case we are internal
+ fi
+ if [ -z "$loadedbarcode" ]; then
+ Exit 1 `_ '<none>'` `_ 'No barcode found for tape %s.' $tapelabel`
+ return $? # in case we are internal
+ fi
+ Log `_ 'LABEL -> Adding label "%s" with barcode "%s" for slot %s into %s' "$tapelabel" "$loadedbarcode" "$loadedslot" "$labelfile"`
+ read_labelfile "$tapelabel" "$loadedbarcode" < $labelfile
+ if [ $labelfile_entry_found -ne 0 ]; then
+ lf_val=
+ if [ "$labelfile_barcode" != "$loadedbarcode" ]; then
+ lf_type=label
+ lf_val=$tapelabel
+ val_type=barcode
+ old_val=$labelfile_barcode
+ new_val=$loadedbarcode
+ elif [ "$labelfile_label" != "$tapelabel" ]; then
+ lf_type=barcode
+ lf_val=$loadedbarcode
+ val_type=label
+ old_val=$labelfile_label
+ new_val=$tapelabel
+ fi
+ if [ -n "$lf_val" ]; then
+ LogAppend `_ 'ERROR -> !!! Label database corrupted !!!'`
+ LogAppend `_ ' -> "%s" conflicts with new %s "%s" for %s "%s"' "$old_val" "$val_type" "$new_val" "$lf_type" "$lf_val"`
+ Exit 2 \
+ `_ '<none>'` \
+ `_ '%s: "%s" conflicts with new %s "%s" for %s "%s"' "$tapelabel" "$old_val" "$val_type" "$new_val" "$lf_type" "$lf_val"`
+ return $? # in case we are internal
+ fi
+ LogAppend `_ " -> already synced"`
+ else
+ echo "$tapelabel $loadedbarcode" >> $labelfile
+ LogAppend `_ ' -> appended %s entry: %s %s' "$labelfile" "$tapelabel" "$loadedbarcode"`
+ fi
+ Exit 0 "$loadedslot" "$rawtape"
+ return $? # in case we are internal
+}
+
+###
+# Look for a label in the barcode file. If found, locate the slot it's
+# in by looking for the barcode in the mtx output, then load that tape.
+###
+
+searchtape() {
+ test -n "$DEBUG" && set -x
+ if [ $# -lt 1 ]; then
+ Exit 2 `_ '<none>'` `_ 'Missing -search argument'`
+ return $? # in case we are internal
+ fi
+ tapelabel=$1
+ if [ $havereader -eq 0 ]; then
+ Exit 2 `_ '<none>'` `_ 'Not configured with barcode reader'`
+ return $? # in case we are internal
+ fi
+ Log `_ 'SEARCH -> Hunting for label "%s"' "$tapelabel"`
+ read_labelfile "$tapelabel" "" < $labelfile
+ if [ $labelfile_entry_found -eq 0 ]; then
+ LogAppend `_ ' -> !!! label "%s" not found in %s !!!' "$tapelabel" "$labelfile"`
+ LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
+ Exit 2 \
+ `_ '<none>'` \
+ `_ '%s: label "%s" not found in %s' "$tapelabel" "$tapelabel" "$labelfile"`
+ return $? # in case we are internal
+ fi
+ LogAppend `_ ' -> barcode is "%s"' "$labelfile_barcode"`
+ get_mtx_status
+ foundslot=`sed -n '
+/VolumeTag *= *'$labelfile_barcode' *$/ {
+ s/.*Storage Element \([0-9][0-9]*\).*/\1/p
+ q
+}
+' < $mtx_status`
+ LogAppend `_ ' -> foundslot is %s' "$foundslot"`
+ if [ -z "$foundslot" ]; then
+ LogAppend `_ 'ERROR -> !!! Could not find slot for barcode "%s"!!!' "$labelfile_barcode"`
+ LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
+ Exit 2 \
+ `_ '<none>'` \
+ `_ 'barcode "%s" not found in mtx status output' "$labelfile_barcode"`
+ return $? # in case we are internal
+ fi
+ # Call loadslot without doing it as an internal and let it finish
+ # things up.
+ loadslot $foundslot
+ # NOTREACHED
+ Exit 2 `_ '<none>'` `_ 'searchtape: should not get here'`
+ return $? # in case we are internal
+}
+
+###
+# Program invocation begins here
+###
+
+if [ $# -lt 1 ]; then
+ Exit 2 `_ '<none>'` `_ 'Usage: %s -command args' "$myname"`
+fi
+cmd=$1
+shift
+case "$cmd" in
+-slot)
+ loadslot "$@"
+ ;;
+-info)
+ info "$@"
+ ;;
+-reset)
+ reset "$@"
+ ;;
+-eject)
+ eject "$@"
+ ;;
+-label)
+ addlabel "$@"
+ ;;
+-search)
+ searchtape "$@"
+ ;;
+-clean)
+ loadslot clean
+ ;;
+*)
+ Exit 2 `_ '<none>'` `_ 'unknown option: %s' "$cmd"`
+ ;;
+esac
+
+Exit 2 `_ '<none>'` `_ '%s: should not get here' "$myname"`