8 # Contributed by Eric DOUTRELEAU <Eric.Doutreleau@int-evry.fr>
9 # This is supposed to work with Zubkoff/Dandelion version of mtx
11 # Modified by Joe Rhett <jrhett@isite.net>
12 # to work with MTX 1.2.9 by Eric Lee Green http://mtx.sourceforge.net
14 # Modified by Jason Hollinden <jhollind@sammg.com> on 13-Feb-2001
15 # to work with MTX 1.2.10, >9 slots, has barcode support, and works with
16 # multiple configs at once.
17 # NOTE: Only tested the 2 additions with an ADIC Scalar 100.
19 ################################################################################
20 # Here are the things you need to do and know to configure this script:
22 # * Figure out what the robot device name is and what the tape drive
23 # device name is. They will be different!
25 # You cannot send robot commands to a tape drive and vice versa.
26 # Both should respond to "mtx -f /dev/... inquiry". Hopefully,
27 # that output will make it obvious which is which.
29 # For instance, here is what mtx has to say about my current robot:
31 # Product Type: Medium Changer
33 # Product ID: 'ACL2640 206 '
35 # Attached Changer: No
37 # and here is what it says about a tape drive:
39 # Product Type: Tape Drive
40 # Vendor ID: 'Quantum '
41 # Product ID: 'DLT4000 '
43 # Attached Changer: No
45 # Note the "Product Type" value makes it clear which is which.
47 # If it is not obvious, "mf -f /dev/... rewind" should be happy when
48 # talking to a (loaded) tape drive but the changer should give some
49 # kind of error. Similarly, "mtx -f /dev/... status" should show good
50 # results with the changer but fail with a tape drive device name.
52 # Once you have this figured out, set "changerdev" in amanda.conf
53 # to the changer device and "tapedev" to the tape device.
55 # * Find out what the first and last storage slots are. Running
56 # "mtx -f /dev/... status" should give you something like this
57 # (although the output will vary widely based on the version of mtx
58 # and the specifics of your robot):
60 # Storage Changer /dev/changer:1 Drives, 9 Slots ( 0 Import/Export )
61 # Data Transfer Element 0:Empty
62 # Storage Element 1:Full :VolumeTag=SR0001
63 # Storage Element 2:Full :VolumeTag=SR0002
64 # Storage Element 3:Full :VolumeTag=SR0003
65 # Storage Element 4:Full :VolumeTag=SR0004
66 # Storage Element 5:Full :VolumeTag=SR0005
67 # Storage Element 6:Full :VolumeTag=SR0006
68 # Storage Element 7:Full :VolumeTag=SR0007
69 # Storage Element 8:Full :VolumeTag=SR0008
70 # Storage Element 9:Full :VolumeTag=SR0009
71 # Storage Element 10 IMPORT/EXPORT:Full :VolumeTag=SR0009
73 # This says the first storage slot (element) is "1" and the last
74 # is "9". If you allocate the entire robot to Amanda, you do not need
75 # to set the "firstslot" or "lastslot" configuration file variables --
76 # the script will compute these values for you.
78 # You do not have to allocate all of the slots for Amanda use,
79 # but whatever slots you use must be contiguous (i.e. 4 through 9
80 # in the above would be OK but 1, 2, 5, 6, 9 would not). The one
81 # exception to this is that if one of the slots contains a cleaning
82 # cartridge, it may be in any slot (Amanda will just skip over it if
83 # it is between firstslot and lastslot).
85 # * Speaking of cleaning cartridges, if you have a storage slot dedicated
86 # to one, figure out what slot it is in. That slot number will go in
87 # the "cleanslot" variable.
89 # Also, decide if you want the changer script to automatically run
90 # the cleaning tape through the drive after every so many mounts,
91 # and how many mounts you want to do between cleanings. If you
92 # want the script to do this, set the "autoclean" variable to 1 and
93 # the "autocleancount" to the number of mounts between cleanings.
94 # If you do not want to do automatic cleanings (including not having
95 # a cleaning cartridge in the robot), set "autoclean" to 0.
97 # Note that only a count of mounts is used to determine when it is
98 # time to clean. The script does not try to detect if the drive is
99 # requesting cleaning, or how much the drive was used on a given
102 # * If you tell Amanda about a cleaning cartridge, whether for automatic
103 # operation or manual (amtape <config> clean), you must also tell
104 # the script how long it takes to run the cleaning cycle. It is
105 # impossible for the script to determine when the cleaning operation
106 # is done, so the "cleancycle" variable is the number of seconds
107 # the longest cleaning operation takes (you'll just have to figure
108 # this out by watching it a few times, or maybe finding it in a tape
109 # drive hardware manual). The script will sleep for this length of
110 # time whenever the cleaning tape is referenced. The default is 120
111 # seconds (two minutes).
113 # * Figure out the drive slot number. By default, it is set to 0.
114 # In the example above, the tape drive ("Data Transfer Element")
115 # is in slot 0. If your drive slot is not 0, you
116 # need to set the drive slot number with the "driveslot" variable.
118 # * Figure out whether your robot has a barcode reader and whether
119 # your version of mtx supports it. If you see "VolumeTag" entries
120 # in the "mtx -f /dev/xxx status" output you did above, you have
121 # a reader and mtx can work with it, so you may set the "havereader"
122 # variable to 1. The default is 0 (do not use a reader).
124 # * Pick any tape to load and then determine if the robot can put it
125 # away directly or whether an "offline" must be done first.
127 # With the tape still mounted and ready, try to put the tape away
128 # with "mtx". If you get some kind of error, which is the most
129 # common response, try "mt -f /dev/... offline", wait for the drive
130 # to unload and make sure the robot takes no action on its own to
131 # store the tape. Assuming it does not, try the "mtx" command again
134 # If you had to issue the "mt -f /dev/... offline" before you could
135 # use "mtx" to store the tape, set the "offline_before_unload"
136 # variable to 1. If "mtx" unloaded the drive and put the tape away
137 # all by itself, set it to 0.
139 # * Some drives and robots require a small delay between unloading the
140 # tape and instructing the robot to move it back to storage.
141 # For instance, if you try to grab the tape too soon on an ATL robot
142 # with DLT tape drives, it will rip the leader out of the drive and
143 # require sincerely painful hardware maintenance.
145 # If you need a little delay, set the "unloadpause" variable to
146 # the number of seconds to wait before trying to take a tape from
147 # a drive back to storage. The default is 0.
149 # * Some drives also require a short pause after loading, or the drive
150 # will return an I/O error during a test to see if it's online (which
151 # this script uses "mt rewind" to test). My drives don't recover from
152 # this, and must be reloaded before they will come online after failing
153 # such a test. For this reason there is an "initial_poll_delay"
154 # variable which will pause for a certain number of seconds before
155 # looping through the online test for the first time. The default is 0.
159 # Now you are ready to set up the variables in the changer configuration
162 # All variables are in "changerfile".conf where "changerfile" is set
163 # in amanda.conf. For example, if amanda.conf has:
165 # changerfile="/etc/amanda/Dailyset1/CHANGER"
166 # or changerfile="/etc/amanda/Dailyset1/CHANGER.conf"
168 # the variables must be in "/etc/amanda/Dailyset1/CHANGER.conf".
169 # The ".conf" is appended only if it's not there".
171 # If "changerfile" is a relative path, it is relative to the directory
172 # that contains amanda.conf. That also happens to be the directory Amanda
173 # makes current before running this script.
175 # Here is a commented out example file with all the variables and showing
176 # their default value (if any):
178 # firstslot=? #### First storage slot (element) -- required
179 # lastslot=? #### Last storage slot (element) -- required
180 # cleanslot=-1 #### Slot with cleaner tape -- default is "-1"
181 # #### Set negative to indicate no cleaner available
182 # driveslot=0 #### Drive slot number. Defaults to 0
183 # #### Use the 'Data Transfer Element' you want
185 # # Do you want to clean the drive after a certain number of accesses?
186 # # NOTE - This is unreliable, since 'accesses' aren't 'uses', and we
187 # # have no reliable way to count this. A single amcheck could
188 # # generate as many accesses as slots you have, plus 1.
189 # # ALSO NOTE - many modern tape loaders handle this automatically.
191 # autoclean=0 #### Set to '1' or greater to enable
193 # autocleancount=99 #### Number of access before a clean.
195 # havereader=0 #### If you have a barcode reader, set to 1.
197 # offline_before_unload=0 #### Does your robot require an
198 # #### 'mt offline' before mtx unload?
200 # poll_drive_ready=NN #### Time (seconds) between tests to see if
201 # #### the tape drive has gone ready (default: 3).
203 # max_drive_wait=NN #### Maximum time (seconds) to wait for the
204 # #### tape drive to become ready (default: 120).
206 # initial_poll_delay=NN #### initial delay after load before polling for
209 # slotinfofile=FILENAME #### record slot information to this file, in
210 # #### the line-based format "SLOT LABEL\n"
215 # Now it is time to test the setup. Do all of the following in the
216 # directory that contains the amanda.conf file, and do all of it as
221 # .../chg-zd-mtx -info
222 # echo $? #### (or "echo $status" if you use csh/tcsh)
224 # You should get a single line from the script like this (the actual
225 # numbers will vary):
229 # The first number (5) is the "current" slot. This may or may not be
230 # the slot actually loaded at the moment (if any). It is the slot
231 # Amanda will try to use next.
233 # The second number (9) is the number of slots.
235 # The third number will always be "1" and indicates the changer is
236 # capable of going backward.
238 # The fourth number is optional. If you set $havereader to 1, it
239 # will be "1", otherwise it will not be present.
241 # The exit code ($? or $status) should be zero.
245 # .../chg-zd-mtx -reset
248 # The script should output a line like this:
252 # The number at the first should match $firstslot. The device name
253 # after that should be your tape device.
255 # The exit code ($? or $status) should be zero.
259 # .../chg-zd-mtx -slot next
262 # The script should output a line like this:
266 # The number at the first should be one higher than $firstslot.
267 # The device name after that should be your tape device.
269 # The exit code ($? or $status) should be zero.
273 # .../chg-zd-mtx -slot current
276 # Assuming the tape is still loaded from the previous test, the
277 # robot should not move and the script should report the same thing
278 # the previous command did.
280 # * If you continue to run "-slot next" commands, the robot should load
281 # each tape in turn then wrap back around to the first when it
282 # reaches $lasttape. If $cleanslot is within the $firstslot to
283 # $lastslot range, the script will skip over that entry.
285 # * Finally, try some of the amtape commands and make sure they work:
287 # amtape <config> reset
288 # amtape <config> slot next
289 # amtape <config> slot current
291 # * If you set $havereader non-zero, now would be a good time to create
292 # the initial barcode database:
294 # amtape <config> update
297 ################################################################################
298 # To debug this script, first look in @AMANDA_DBGDIR@. The script
299 # uses one of two log files there, depending on what version of Amanda
300 # is calling it. It may be chg-zd-mtx.YYYYMMDD*.debug, or it may be
301 # changer.debug.driveN where 'N' is the drive number.
303 # If the log file does not help, try running the script, **as the Amanda
304 # user**, in the amanda.conf directory with whatever set of args the log
305 # said were used when you had a problem. If nothing else useful shows up
306 # in the output, try running the script with the DEBUG environment variable
307 # set non-null, e.g.:
309 # env DEBUG=yes .../chg-zd-mtx ...
310 ################################################################################
312 # source utility functions and values from configure
314 exec_prefix=@exec_prefix@
315 amlibexecdir=@amlibexecdir@
316 . ${amlibexecdir}/chg-lib.sh
318 test -n "$DEBUG" && set -x
319 TMPDIR="@AMANDA_TMPDIR@"
320 DBGDIR="@AMANDA_DBGDIR@"
323 myname=`expr "$argv0" : '.*/\(.*\)'`
325 config=`pwd 2>/dev/null`
326 config=`expr "$config" : '.*/\(.*\)'`
329 # Functions to write a new log file entry and append more log information.
332 ds=`date '+%H:%M:%S' 2>/dev/null`
333 if [ $? -eq 0 -a -n "$ds" ]; then
334 logprefix=`echo "$ds" | sed 's/./ /g'`
340 if [ -z "$logprefix" ]; then
341 echo "$@" >> $DBGFILE
343 echo "$logprefix" "$@" >> $DBGFILE
348 if [ -z "$logprefix" ]; then
349 echo "===" "`date`" "===" >> $DBGFILE
350 echo "$@" >> $DBGFILE
352 ds=`date '+%H:%M:%S' 2>/dev/null`
353 echo "$ds" "$@" >> $DBGFILE
358 # Common exit function.
362 # $3 = additional information (error message, tape devive, etc)
367 if [ $internal_call -gt 0 ]; then
377 Log $call_type "($code) -> $exit_slot $@"
378 echo "$exit_slot" "$@"
379 if [ $call_type = Return ]; then
382 amgetconf dbclose.$myname:$DBGFILE > /dev/null 2>&1
387 # Function to run another command and log it.
391 Log `_ 'Running: %s' "$*"`
392 rm -f $stdout $stderr
393 "$@" > $stdout 2> $stderr
395 Log `_ 'Exit code: %s' "$exitcode"`
399 cat $stdout >> $DBGFILE
404 cat $stderr >> $DBGFILE
412 # Return success if the arg is numeric.
416 test -z "$1" && return 1
417 x="`expr -- "$1" : "\([-0-9][0-9]*\)" 2>/dev/null`"
418 return `expr X"$1" != X"$x"`
422 # Run $MTX status unless the previous output is still valid.
427 test -n "$DEBUG" && set -x
428 if [ $mtx_status_valid -ne 0 ]; then
432 Run $MTX status > $mtx_status 2>&1
434 if [ $status -eq 0 ]; then
438 # shim this in here so that we get a completely new slotinfofile
439 # every time we run mtx status
440 regenerate_slotinfo_from_mtx
446 # Determine the slot currently loaded. Set $loadedslot to the slot
447 # currently loaded, or "-1", and $loadedbarcode to the corresponding
448 # barcode (or nothing).
452 test -n "$DEBUG" && set -x
454 if [ $mtx_status_valid -eq 0 ]; then
457 `head -1 $mtx_status`
462 /^Data Transfer Element:Empty/ {
466 /^Data Transfer Element '$driveslot':Empty/ {
470 /^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/ {
471 s/.*(Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/\1 \2/p
474 /^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/ {
475 s/.*(Storage Element \([0-9][0-9]*\) Loaded):VolumeTag *= *\([^ ]*\)/\1 \2/p
478 /^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded):VolumeTag *= *\([^ ]*\)/ {
479 s/.*:VolumeTag *= *\([^ ]*\)/-2 \1/p
482 /^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
483 s/.*(Storage Element \([0-9][0-9]*\) Loaded).*/\1/p
486 /^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
487 s/.*Storage Element \([0-9][0-9]*\) Loaded.*/\1/p
490 /^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded)/ {
494 ' < $mtx_status 2>&1`
495 shift # get rid of the "x"
498 if [ -z "$loadedslot" ]; then
501 "could not determine current slot, are you sure your drive slot is $driveslot"
502 return $? # in case we are internal
505 #Use the current slot if it's empty and we don't know which slot is loaded'
506 if [ $loadedslot -eq -2 ]; then
509 /^.*Storage Element '$currentslot':Empty/ {
510 s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
513 /^.*Storage Element '$currentslot':Full/ {
514 s/.*Storage Element \([0-9][0-9]*\):Full/-2/p
517 /^.*Storage Element '$currentslot' IMPORT\/EXPORT:Empty/ {
518 s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/\1/p
521 /^.*Storage Element '$currentslot' IMPORT\/EXPORT:Full/ {
522 s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full/-2/p
526 ' < $mtx_status 2>& 1`
527 shift # get rid of the "x"
529 if [ ! -z $loadedslotx ]; then
530 loadedslot=$loadedslotx
534 #Use the first empty slot if we don't know which slot is loaded'
535 if [ $loadedslot -eq -2 ]; then
538 /^.*Storage Element \([0-9][0-9]*\):Empty/ {
539 s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
542 /^.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/ {
543 s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Empty/\1/p
547 ' < $mtx_status 2>& 1`
548 shift # get rid of the "x"
552 if IsNumeric "$loadedslot" ; then
557 "currently loaded slot ($loadedslot) not numeric"
558 return $? # in case we are internal
560 Log `_ 'STATUS -> currently loaded slot = %s' "$loadedslot"`
561 LogAppend `_ ' -> currently loaded barcode = "%s"' "$loadedbarcode"`
565 # Get a list of slots between $firstslot and $lastslot, if they are set.
566 # If they are not set, set them to the first and last slot seen on the
567 # assumption the entire robot is to be used (???).
572 test -n "$DEBUG" && set -x
573 if [ -n "$slot_list" ]; then
577 if [ $mtx_status_valid -eq 0 ]; then
580 `head -1 $mtx_status`
584 /^Data Transfer Element:Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
585 s/.*(Storage Element \([0-9][0-9]*\) Loaded).*/\1/p
587 /^Data Transfer Element '$driveslot':Full (Storage Element \([0-9][0-9]*\) Loaded)/ {
588 s/.*Storage Element \([0-9][0-9]*\) Loaded.*/\1/p
590 /^Data Transfer Element '$driveslot':Full (Unknown Storage Element Loaded)/ {
593 /^.*Storage Element \([0-9][0-9]*\):Full/ {
594 s/.*Storage Element \([0-9][0-9]*\):Full.*/\1/p
597 /^.*Storage Element \([0-9][0-9]*\):Empty/ {
598 s/.*Storage Element \([0-9][0-9]*\):Empty/\1/p
601 /^.*Storage Element \([0-9][0-9]*\):Full/ {
602 s/.*Storage Element \([0-9][0-9]*\):Full.*/\1/p
604 /^.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full/ {
605 s/.*Storage Element \([0-9][0-9]*\) IMPORT\/EXPORT:Full.*/\1/p
607 ' < $mtx_status 2>&1 | grep -v "^${cleanslot}\$" | sort -n`
608 slot_list=`echo $slot_list` # remove the newlines
609 if [ $firstslot -lt 0 -o $lastslot -lt 0 ]; then
611 for slot in $slot_list; do
612 if [ $firstslot -lt 0 ]; then
613 Log `_ 'SLOTLIST -> firstslot set to %s' "$slot"`
616 if [ $lastslot -lt 0 ]; then
620 if [ $lastslot -lt 0 -a $last -ge 0 ]; then
621 Log `_ 'SLOTLIST -> lastslot set to %s' "$last"`
624 if [ $firstslot -lt 0 ]; then
627 `_ 'cannot determine first slot'`
628 return $? # in case we are internal
629 elif [ $lastslot -lt 0 ]; then
632 `_ 'cannot determine last slot'`
633 return $? # in case we are internal
637 for slot in $slot_list; do
638 if [ $slot -ge $firstslot -a $slot -le $lastslot ]; then
639 amanda_slot_list="$amanda_slot_list $slot"
642 if [ -z "$amanda_slot_list" ]; then
646 return $? # in case we are internal
648 slot_list="$amanda_slot_list"
652 # Read the labelfile and scan for a particular entry.
656 labelfile_entry_found=0
664 while read lbl bc junk; do
665 line=`expr $line + 1`
666 if [ -z "$lbl" -o -z "$bc" -o -n "$junk" ]; then
667 Log `_ 'ERROR -> Line %s malformed: %s %s %s' "$line" "$lbl" "$bc" "$junk"`
668 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
671 `_ 'Line %s malformed in %s: %s %s %s' "$line" "$labelfile" "$lbl" "$bc" "$junk"`
672 return $? # in case we are internal
674 if [ $lbl = "$lbl_search" -o $bc = "$bc_search" ]; then
675 if [ $labelfile_entry_found -ne 0 ]; then
676 Log `_ 'ERROR -> Duplicate entries: %s line %s' "$labelfile" "$line"`
677 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
680 `_ 'Duplicate entries: %s line %s' "$labelfile" "$line"`
681 return $? # in case we are internal
683 labelfile_entry_found=1
685 labelfile_barcode=$bc
690 lookup_label_by_barcode() {
691 [ -z "$1" ] && return
692 read_labelfile "" "$1" < $labelfile
693 echo "$labelfile_label"
696 lookup_barcode_by_label() {
697 [ -z "$1" ] && return
698 read_labelfile "$1" "" < $labelfile
699 echo "$labelfile_barcode"
702 remove_from_labelfile() {
707 internal_remove_from_labelfile "$lbl_search" "$bc_search" < $labelfile >$labelfile.new
708 if [ $labelfile_entry_found -ne 0 ]; then
709 mv -f $labelfile.new $labelfile
710 LogAppend `_ 'Removed Entry "%s %s" from barcode database' "$labelfile_label" "$labelfile_barcode"`
714 internal_remove_from_labelfile() {
715 labelfile_entry_found=0
723 while read lbl bc junk; do
724 line=`expr $line + 1`
725 if [ -z "$lbl" -o -z "$bc" -o -n "$junk" ]; then
726 Log `_ 'ERROR -> Line %s malformed: %s %s %s' "$line" "$lbl" "$bc" "$junk"`
727 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
730 `_ 'Line %s malformed in %s: %s %s %s' "$line" "$labelfile" "$lbl" "$bc" "$junk"`
731 return $? # in case we are internal
733 if [ $lbl = "$lbl_search" -o $bc = "$bc_search" ]; then
734 if [ $labelfile_entry_found -ne 0 ]; then
735 Log `_ 'ERROR -> Duplicate entries: %s line %s' "$labelfile" "$line"`
736 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
739 `_ 'Duplicate entries: %s line %s' "$labelfile" "$line"`
740 return $? # in case we are internal
742 labelfile_entry_found=1
744 labelfile_barcode=$bc
752 # Add a new slot -> label correspondance to the slotinfo file, removing any previous
753 # information about that slot.
756 record_label_in_slot() {
757 [ -z "$slotinfofile" ] && return
762 if [ -f "$slotinfofile" ]; then
763 grep -v "^$newslot " < "$slotinfofile"
765 echo "$newslot $newlabel"
767 mv "$slotinfofile~" "$slotinfofile"
771 # Remove a slot from the slotinfo file
774 remove_slot_from_slotinfo() {
775 [ -z "$slotinfofile" ] && return
779 if [ -f "$slotinfofile" ]; then
780 grep -v "^$emptyslot " < "$slotinfofile"
783 mv "$slotinfofile~" "$slotinfofile"
787 # Assuming get_mtx_status has been run,
788 # - if we have barcodes, regenerate the slotinfo file completely by
789 # mapping barcodes in the status into labels using the labelfile
790 # - otherwise, remove all empty slots from the slotinfo file
793 regenerate_slotinfo_from_mtx() {
794 [ -z "$slotinfofile" ] && return
795 [ "$mtx_status_valid" = "1" ] || return
797 if [ "$havereader" = "1" ]; then
798 # rewrite slotinfo entirely based on the status, since it has barcodes
800 sed -n '/.*Storage Element \([0-9][0-9]*\).*VolumeTag *= *\([^ ]*\) *$/{
801 s/.*Storage Element \([0-9][0-9]*\).*VolumeTag *= *\([^ ]*\) *$/\1 \2/
803 }' < $mtx_status | while read newslot newbarcode; do
804 newlabel=`lookup_label_by_barcode "$newbarcode"`
805 if [ -n "$newlabel" ]; then
806 echo "$newslot $newlabel" >> "$slotinfofile~"
809 mv "$slotinfofile~" "$slotinfofile"
811 # just remove empty slots from slotinfo
813 # first determine which slots are not really empty, but are
814 # loaded into a data transfer element
815 loadedslots=`sed -n '/.*(Storage Element \([0-9][0-9]*\) Loaded).*/{
816 s/.*(Storage Element \([0-9][0-9]*\) Loaded).*/\1/g
820 # now look for any slots which are empty, but which aren't
821 # in the set of loaded slots
822 sed -n '/.*Storage Element \([0-9][0-9]*\): *Empty.*/{
823 s/.*Storage Element \([0-9][0-9]*\): *Empty.*/\1/g
825 }' < $mtx_status | while read emptyslot; do
827 if [ -n "$loadedslots" ]; then
828 for loadedslot in $loadedslots; do
829 [ "$loadedslot" = "$emptyslot" ] && reallyempty=0
832 if [ "$reallyempty" = "1" ]; then
833 remove_slot_from_slotinfo "$emptyslot"
839 DBGFILE=`amgetconf dbopen.$myname 2>/dev/null`
842 DBGFILE=/dev/null # will try this again below
845 changerfile=`amgetconf changerfile 2>/dev/null`
846 if [ -z "$changerfile" ]; then
849 "changerfile must be specified in amanda.conf"
852 rawtape=`amgetconf tapedev 2>/dev/null`
853 if [ -z "$rawtape" ]; then
856 "tapedev may not be empty"
858 tape=`tape_device_filename "$rawtape"`
859 if [ -z "$tape" ]; then
862 "tapedev $rawtape is not a tape device."
863 elif [ $tape = "/dev/null" -o `expr "$tape" : 'null:'` -eq 5 ]; then
866 "tapedev ($tape) may not be the null device"
868 # Confusingly, TAPE is the name of the changer device...
869 TAPE=`amgetconf changerdev 2>/dev/null`
870 if [ -z "$TAPE" ]; then
873 "changerdev may not be empty"
874 elif [ $TAPE = "/dev/null" ]; then
877 "changerdev ($TAPE) may not be the null device"
879 export TAPE # for mtx command
882 export CHANGER # for mtx command
884 #### Set up the various config files.
886 conf_match=`expr "$changerfile" : .\*\.conf\$`
887 if [ $conf_match -ge 6 ]; then
888 configfile=$changerfile
889 changerfile=`echo $changerfile | sed 's/.conf$//g'`
891 configfile=$changerfile.conf
894 if [ ! -e $configfile ]; then
897 "configuration file \"$configfile\" doesn't exist"
899 if [ ! -f $configfile ]; then
902 "configuration file \"$configfile\" is not a file"
905 cleanfile=$changerfile-clean
906 accessfile=$changerfile-access
907 slotfile=$changerfile-slot
908 labelfile=$changerfile-barcodes
910 [ ! -s $cleanfile ] && echo 0 > $cleanfile
911 [ ! -s $accessfile ] && echo 0 > $accessfile
912 [ ! -s $slotfile ] && echo -1 > $slotfile
913 [ ! -f $labelfile ] && > $labelfile
914 cleancount=`cat $cleanfile`
915 accesscount=`cat $accessfile`
917 #### Dig out of the config file what is needed
920 varlist="$varlist firstslot"
921 varlist="$varlist lastslot"
922 varlist="$varlist cleanslot"
923 varlist="$varlist cleancycle"
924 varlist="$varlist OFFLINE_BEFORE_UNLOAD" # old name
925 varlist="$varlist offline_before_unload"
926 varlist="$varlist unloadpause"
927 varlist="$varlist AUTOCLEAN" # old name
928 varlist="$varlist autoclean"
929 varlist="$varlist autocleancount"
930 varlist="$varlist havereader"
931 varlist="$varlist driveslot"
932 varlist="$varlist poll_drive_ready"
933 varlist="$varlist initial_poll_delay"
934 varlist="$varlist max_drive_wait"
935 varlist="$varlist slotinfofile"
939 val="`cat $configfile 2>/dev/null | sed -n '
940 # Ignore comment lines (anything starting with a #).
942 # Find the first var=val line in the file, print the value and quit.
943 /^[ ]*'$var'[ ]*=[ ]*\([^ ][^ ]*\).*/ {
944 s/^[ ]*'$var'[ ]*=[ ]*\([^ ][^ ]*\).*/\1/p
951 # Deal with driveslot first so we can get DBGFILE set if we are still
952 # using the old amgetconf.
954 if [ -z "$driveslot" ]; then
958 # Get DBGFILE set if it is not already.
960 if [ $DBGFILE = /dev/null ]; then
961 if [ -d "$DBGDIR" ]; then
962 DBGFILE=$DBGDIR/changer.debug.drive$driveslot
966 Log `_ '=== Start %s ===' "\`date\`"`
969 stdout=$TMPDIR/$myname.1.$$
970 stderr=$TMPDIR/$myname.2.$$
971 mtx_status=$TMPDIR/$myname.status.$$
972 trap "rm -f $stdout $stderr $mtx_status" 0 # exit cleanup
974 Log `_ 'Using config file %s' "$configfile"`
976 # Log the argument list.
981 LogAppend "\$$i = \"$argv0\""
984 LogAppend "\$$i = \"$arg\""
987 # Set the default config values for those not in the file. Log the
988 # results and make sure each is valid (numeric).
990 firstslot=${firstslot:-'-1'} # default: mtx status
991 lastslot=${lastslot:-'-1'} # default: mtx status
992 cleanslot=${cleanslot:-'-1'} # default: -1
993 cleancycle=${cleancycle:-'120'} # default: two minutes
994 if [ -z "$offline_before_unload" -a -n "$OFFLINE_BEFORE_UNLOAD" ]; then
995 offline_before_unload=$OFFLINE_BEFORE_UNLOAD # (old name)
997 offline_before_unload=${offline_before_unload:-'0'} # default: 0
998 unloadpause=${unloadpause:-'0'} # default: 0
999 if [ -z "$autoclean" -a -n "$AUTOCLEAN" ]; then
1000 autoclean=$AUTOCLEAN # (old name)
1002 autoclean=${autoclean:-'0'} # default: 0
1003 autocleancount=${autocleancount:-'99'} # default: 99
1004 havereader=${havereader:-'0'} # default: 0
1005 poll_drive_ready=${poll_drive_ready:-'3'} # default: three seconds
1006 initial_poll_delay=${initial_poll_delay:-'0'} # default: zero zeconds
1007 max_drive_wait=${max_drive_wait:-'120'} # default: two minutes
1009 # check MT and MTX for sanity
1010 if test "${MTX%${MTX#?}}" = "/"; then
1011 if ! test -f "${MTX}"; then
1014 `_ "mtx binary at '%s' not found" "$MTX"`
1016 if ! test -x "${MTX}"; then
1019 `_ "mtx binary at '%s' is not executable" "$MTX"`
1022 # try running it to see if the shell can find it
1023 "$MTX" >/dev/null 2>/dev/null
1024 if test $? -eq 127 -o $? -eq 126; then
1027 `_ "Could not run mtx binary at '%s'" "$MTX"`
1032 if test $? -ne 0; then
1033 Exit 2 '<none>' $error
1038 Log `_ "Config info:"`
1039 for var in $varlist; do
1040 if [ $var = "OFFLINE_BEFORE_UNLOAD" ]; then
1042 elif [ $var = "AUTOCLEAN" ]; then
1044 elif [ $var = "slotinfofile" ]; then
1045 continue # not numeric
1047 eval val=\"'$'$var\"
1048 if [ -z "$val" ]; then
1051 `_ '%s missing in %s' "$var" "$configfile"`
1053 if IsNumeric "$val" ; then
1058 `_ '%s (%s) not numeric in %s' "$var" "$val" "$configfile"`
1060 LogAppend $var = \"$val\"
1063 # Run the rest of the config file sanity checks.
1065 if [ $firstslot -gt $lastslot ]; then
1068 `_ 'firstslot (%s) greater than lastslot (%s) in %s' "$firstslot" "$lastslot" "$configfile"`
1070 if [ $autoclean -ne 0 -a $cleanslot -lt 0 ]; then
1073 `_ 'autoclean set but cleanslot not valid (%s)' "$cleanslot"`
1076 # Set up the current slot
1078 currentslot=`cat $slotfile`
1079 if IsNumeric "$currentslot" ; then
1080 if [ $currentslot -lt $firstslot ]; then
1081 Log `_ 'SETUP -> current slot %s less than %s ... resetting to %s' "$currentslot" "$firstslot" "$firstslot"`
1082 currentslot=$firstslot
1083 elif [ $currentslot -gt $lastslot ]; then
1084 Log `_ 'SETUP -> current slot %s greater than %s ... resetting to %s' "$currentslot" "$lastslot" "$lastslot"`
1085 currentslot=$lastslot
1088 Log `_ 'SETUP -> contents of %s (%s) invalid, setting current slot to first slot (%s)' "$slotfile" "$currentslot" "$firstslot"`
1089 currentslot=$firstslot
1093 first_slot_in_list=-1
1094 next_slot_after_current=-1
1095 for slot in $slot_list; do
1096 if [ $first_slot_in_list -lt 0 ]; then
1097 first_slot_in_list=$slot # in case $firstslot is missing
1099 if [ $slot -eq $currentslot ]; then
1102 elif [ $slot -gt $currentslot ]; then
1103 next_slot_after_current=$slot # $currentslot is missing
1107 if [ $found_current -eq 0 ]; then
1108 if [ $next_slot_after_current -lt 0 ]; then
1109 new_currentslot=$first_slot_in_list
1111 new_currentslot=$next_slot_after_current
1113 Log `_ 'WARNING -> current slot %s not available, setting current slot to next slot (%s)' "$currentslot" "$new_currentslot"`
1114 currentslot=$new_currentslot
1120 # Eject the current tape and put it away.
1124 test -n "$DEBUG" && set -x
1125 Log `_ 'EJECT -> ejecting tape from %s' "$tape"`
1127 if [ $loadedslot -gt 0 ]; then
1128 Log `_ 'EJECT -> moving tape from drive %s to storage slot %s' "$driveslot" "$loadedslot"`
1129 if [ $offline_before_unload -ne 0 ]; then
1130 Run try_eject_device $tape
1133 result=`Run $MTX unload $loadedslot $driveslot 2>&1`
1135 Log `_ ' -> status %s, result "%s"' "$status" "$result"`
1137 if [ $status -ne 0 ]; then
1145 answer=`_ 'Drive was not loaded'`
1148 Exit $code "$loadedslot" "$answer"
1149 return $? # in case we are internal
1153 # Reset the robot back to the first slot.
1157 test -n "$DEBUG" && set -x
1158 Log `_ 'RESET -> loading tape from slot %s to drive %s (%s)' "$firstslot" "$driveslot" "$tape"`
1159 # Call loadslot without doing it as an internal and let it finish
1163 Exit 2 `_ '<none>'` `_ 'reset: should not get here'`
1164 return $? # in case we are internal
1168 # Unload the current tape (if necessary) and load a new one (unless
1169 # "advance"). If no tape is loaded, get the value of "current" from
1174 test -n "$DEBUG" && set -x
1175 if [ $# -lt 1 ]; then
1176 Exit 2 `_ '<none>'` `_ 'Missing -slot argument'`
1177 return $? # in case we are internal
1180 Log `_ 'LOADSLOT -> load drive %s (%s) from slot %s' "$driveslot" "$tape" "$whichslot"`
1182 numeric=`echo $whichslot | sed 's/[^0-9]//g'`
1184 current|prev|next|advance)
1185 find_slot=$currentslot
1188 find_slot=$firstslot
1197 find_slot=$cleanslot
1200 Exit 2 `_ '<none>'` `_ 'Illegal slot: "%s"' "$whichslot"`
1201 return $? # in case we are internal
1205 # Find the requested slot in the slot list. By loading the "set"
1206 # command with multiple copies, we guarantee that if the slot is
1207 # found, we can look both forward and backward without running
1208 # off the end. Putting $cleanslot at the end allows us to find
1209 # that slot since it is not in $slot_list.
1211 set x $slot_list $slot_list $slot_list $cleanslot
1212 shift # get rid of the "x"
1215 while [ $# -gt 0 ]; do
1216 if [ $1 -eq $find_slot ]; then
1222 if [ $# -le 0 ]; then
1225 `_ 'Cannot find slot %s in slot list (%s)' "$find_slot " "$slot_list"`
1226 return $? # in case we are internal
1229 # Determine the slot to load.
1242 # If the desired slot is already loaded, we are done. Only update
1243 # current slot if this is not the cleaning slot.
1245 if [ $loadslot = $loadedslot ]; then
1246 if [ $loadslot -ne $cleanslot ]; then
1248 echo $loadslot > $slotfile
1250 Exit 0 "$loadedslot" "$rawtape"
1251 return $? # in case we are internal
1253 if [ $loadedslot -eq -2 ]; then
1254 Exit 0 "$loadedslot" "$rawtape"
1255 return $? # in case we are internal
1258 # If we are loading the cleaning tape, bump the cleaning count
1259 # and reset the access count. Otherwise, bump the access count
1260 # and see if it is time to do a cleaning.
1261 if [ $loadslot = $cleanslot ]; then
1262 rm -f $cleanfile $accessfile
1263 expr $cleancount + 1 > $cleanfile
1264 echo 0 > $accessfile
1267 expr $accesscount + 1 > $accessfile
1268 if [ $autoclean -ne 0 -a $accesscount -gt $autocleancount ]
1270 internal_call=`expr $internal_call + 1`
1271 loadslot clean > /dev/null 2>&1
1273 internal_call=`expr $internal_call - 1`
1274 if [ $status -ne 0 ]; then
1275 Exit $status "$loadslot" "$exit_answer"
1276 return $? # in case we are internal
1279 # Slot $cleanslot might contain an ordinary tape
1280 # rather than a cleaning tape. A cleaning tape
1281 # *MIGHT* auto-eject; an ordinary tape does not.
1282 # We therefore have to read the status again to
1283 # check what actually happened.
1289 # Unload whatever tape is in the drive.
1290 internal_call=`expr $internal_call + 1`
1291 eject > /dev/null 2>&1
1293 internal_call=`expr $internal_call - 1`
1294 if [ $status -gt 1 ]; then
1295 Exit $status "$exit_slot" "$exit_answer"
1296 return $? # in case we are internal
1299 # If we were doing an "advance", we are done.
1300 if [ $whichslot = advance ]; then
1301 if [ $loadslot -ne $cleanslot ]; then
1303 echo $loadslot > $slotfile
1305 Exit 0 "$loadslot" "/dev/null"
1306 return $? # in case we are internal
1309 # Load the tape, finally!
1310 Log `_ "LOADSLOT -> loading tape from slot %s to drive %s (%s)" "$loadslot" "$driveslot" "$tape"`
1311 result=`Run $MTX load $loadslot $driveslot 2>&1`
1313 Log `_ ' -> status %s, result "%s"' "$status" "$result"`
1315 if [ $status -ne 0 ]; then
1316 Exit 2 "$loadslot" "$result"
1317 return $? # in case we are internal
1321 # Cleaning tapes never go "ready", so instead we just sit here
1322 # for "long enough" (as determined empirically by the user),
1323 # then return success.
1325 if [ $loadslot -eq $cleanslot ]; then
1326 Run sleep $cleancycle
1327 Exit 0 "$loadslot" "$rawtape"
1328 return $? # in case we are internal
1332 # Wait for the drive to go online.
1336 sleep $initial_poll_delay
1337 while [ $waittime -lt $max_drive_wait ]; do
1338 amdevcheck_status $tape
1339 if [ $? -eq 0 ]; then
1343 sleep $poll_drive_ready
1344 waittime=`expr $waittime + $poll_drive_ready`
1346 if [ $ready -eq 0 ]; then
1347 Exit 2 "$loadslot" `_ 'Drive not ready after %s seconds: %s' "$max_drive_wait" "$amdevcheck_message"`
1348 return $? # in case we are internal
1351 if [ $loadslot -ne $cleanslot ]; then
1353 echo $loadslot > $slotfile
1355 Exit 0 "$loadslot" "$rawtape"
1356 return $? # in case we are internal
1360 # Return information about how the changer is configured and the current
1361 # state of the robot.
1365 test -n "$DEBUG" && set -x
1368 Log `_ 'INFO -> first slot: %s' "$firstslot"`
1369 LogAppend `_ ' -> current slot: %s' "$currentslot"`
1370 LogAppend `_ ' -> loaded slot: %s' "$loadedslot"`
1371 LogAppend `_ ' -> last slot: %s' "$lastslot"`
1372 LogAppend `_ ' -> slot list: %s' "$slot_list"`
1373 LogAppend `_ ' -> can go backwards: 1'`
1374 LogAppend `_ ' -> havereader: %s' "$havereader"`
1377 # Check if a barcode reader is configured or not. If so, it
1378 # passes the 4th item in the echo back to amtape signifying it
1379 # can search based on barcodes.
1382 if [ $havereader -eq 1 ]; then
1386 if [ $currentslot -lt $firstslot -o $currentslot -gt $lastslot ]; then
1387 currentslot=$firstslot # what "current" will get
1389 numslots=`expr $lastslot - $firstslot + 1`
1390 Exit 0 "$currentslot" "$numslots 1 $reader"
1391 return $? # in case we are internal
1395 # Adds the label and barcode for the currently loaded tape to the
1396 # barcode file. Return an error if the database is messed up.
1400 test -n "$DEBUG" && set -x
1401 if [ $# -lt 1 ]; then
1402 Exit 2 `_ '<none>'` `_ 'Missing -label argument'`
1403 return $? # in case we are internal
1407 if [ $loadedslot -lt 0 ]; then
1408 Exit 1 `_ '<none>'` `_ 'No tape currently loaded'`
1409 return $? # in case we are internal
1411 record_label_in_slot "$tapelabel" "$loadedslot"
1412 if [ $havereader -eq 0 ]; then
1413 Exit 0 "$loadedslot" "$rawtape" # that's all we needed
1414 return $? # in case we are internal
1416 if [ -z "$loadedbarcode" ]; then
1417 Exit 1 `_ '<none>'` `_ 'No barcode found for tape %s.' $tapelabel`
1418 return $? # in case we are internal
1420 Log `_ 'LABEL -> Adding label "%s" with barcode "%s" for slot %s into %s' "$tapelabel" "$loadedbarcode" "$loadedslot" "$labelfile"`
1421 read_labelfile "$tapelabel" "$loadedbarcode" < $labelfile
1422 if [ $labelfile_entry_found -ne 0 ]; then
1424 if [ "$labelfile_barcode" != "$loadedbarcode" ]; then
1428 old_val=$labelfile_barcode
1429 new_val=$loadedbarcode
1430 elif [ "$labelfile_label" != "$tapelabel" ]; then
1432 lf_val=$loadedbarcode
1434 old_val=$labelfile_label
1437 if [ -n "$lf_val" ]; then
1438 if [ "$val_type" = "barcode" ]; then
1439 remove_from_labelfile $labelfile "" "$old_val"
1441 remove_from_labelfile $labelfile "$old_val" ""
1443 echo "$tapelabel $loadedbarcode" >> $labelfile
1444 LogAppend `_ ' -> appended %s entry: %s %s' "$labelfile" "$tapelabel" "$loadedbarcode"`
1446 LogAppend `_ " -> already synced"`
1449 echo "$tapelabel $loadedbarcode" >> $labelfile
1450 LogAppend `_ ' -> appended %s entry: %s %s' "$labelfile" "$tapelabel" "$loadedbarcode"`
1452 Exit 0 "$loadedslot" "$rawtape"
1453 return $? # in case we are internal
1457 # Look for a label in the barcode file. If found, locate the slot it's
1458 # in by looking for the barcode in the mtx output, then load that tape.
1462 test -n "$DEBUG" && set -x
1463 if [ $# -lt 1 ]; then
1464 Exit 2 `_ '<none>'` `_ 'Missing -search argument'`
1465 return $? # in case we are internal
1468 if [ $havereader -eq 0 ]; then
1469 Exit 2 `_ '<none>'` `_ 'Not configured with barcode reader'`
1470 return $? # in case we are internal
1472 Log `_ 'SEARCH -> Hunting for label "%s"' "$tapelabel"`
1473 read_labelfile "$tapelabel" "" < $labelfile
1474 if [ $labelfile_entry_found -eq 0 ]; then
1475 LogAppend `_ ' -> !!! label "%s" not found in %s !!!' "$tapelabel" "$labelfile"`
1476 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
1479 `_ '%s: label "%s" not found in %s' "$tapelabel" "$tapelabel" "$labelfile"`
1480 return $? # in case we are internal
1482 LogAppend `_ ' -> barcode is "%s"' "$labelfile_barcode"`
1484 if [ $mtx_status_valid -eq 0 ]; then
1487 `head -1 $mtx_status`
1491 /VolumeTag *= *'$labelfile_barcode' *$/ {
1492 s/.*Storage Element \([0-9][0-9]*\).*/\1/p
1496 LogAppend `_ ' -> foundslot is %s' "$foundslot"`
1497 if [ -z "$foundslot" ]; then
1498 LogAppend `_ 'ERROR -> !!! Could not find slot for barcode "%s"!!!' "$labelfile_barcode"`
1499 LogAppend `_ ' -> Remove %s and run "%s %s update"' "$labelfile" "$sbindir/amtape" "$config"`
1502 `_ 'barcode "%s" not found in mtx status output' "$labelfile_barcode"`
1503 return $? # in case we are internal
1505 # Call loadslot without doing it as an internal and let it finish
1509 Exit 2 `_ '<none>'` `_ 'searchtape: should not get here'`
1510 return $? # in case we are internal
1514 # Program invocation begins here
1517 if [ $# -lt 1 ]; then
1518 Exit 2 `_ '<none>'` `_ 'Usage: %s -command args' "$myname"`
1545 Exit 2 `_ '<none>'` `_ 'unknown option: %s' "$cmd"`
1549 Exit 2 `_ '<none>'` `_ '%s: should not get here' "$myname"`