3 # sd-tool - Tool to manipulate tapes and tape-changers
4 # Copyright (C) 2010, 2011 Dennis Leeuw
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 BACULA_PID_DIR="/opt/bacula/working"
21 MTX_DEV="/dev/tape/by-id/scsi-1ADIC_A0C0410012_LLA-changer"
22 DRIVES=(/dev/tape/by-id/scsi-3500308c0a08d1000-nst /dev/tape/by-id/scsi-3500308c0a08d1004-nst)
24 function set_dte_data() {
25 # This function sets the DTE_voltag and DTE_slots arrays
26 echo -n "See what's in the drives... "
28 for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
30 if [ "${STATUS_ARRAY[$I]#Data Transfer Element}" != "${STATUS_ARRAY[$I]}" ]
32 # Line looks like this:
33 # Data Transfer Element 1:Full (Storage Element 7 Loaded):VolumeTag = 000006
34 DTE_number=${STATUS_ARRAY[$I]#Data Transfer Element }
35 DTE_number=${DTE_number%%:*}
38 if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
40 # We now know we are a DTE and we have a tape
41 tmp=${STATUS_ARRAY[$I]#*Storage Element }
43 DTE_slots[$DTE_number]=${tmp}
44 tmp=${STATUS_ARRAY[$I]#*VolumeTag = }
45 DTE_voltag[$DTE_number]=`echo ${tmp}`
53 function set_ie_data() {
54 # This function sets the IE_voltag and IE_slots_empty and IE_slots_full arrays
55 echo -n "See what's in the Import/Export slots... "
59 for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
61 if [ "${STATUS_ARRAY[$I]# Storage Element}" != "${STATUS_ARRAY[$I]}" ] &&
62 [ "${STATUS_ARRAY[$I]#*IMPORT/EXPORT}" != "${STATUS_ARRAY[$I]}" ]
64 # Line looks like this:
65 # Storage Element 36 IMPORT/EXPORT:Empty:VolumeTag=
68 if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
70 # We now know we are a IE and we have a tape
71 tmp=${STATUS_ARRAY[$I]#*Storage Element }
73 IE_slots_full[$count_full]=${tmp}
74 tmp=${STATUS_ARRAY[$I]#*VolumeTag=}
75 IE_voltag[$count_full]=`echo ${tmp}`
76 count_full=$(($count_full+1))
79 tmp=${STATUS_ARRAY[$I]#*Storage Element }
81 IE_slots_empty[$count_empty]=${tmp}
82 count_empty=$(($count_empty+1))
90 function set_se_data() {
91 # This function sets the SE_voltag and SE_slots_empty and SE_slots_full arrays
92 echo -n "See what's in the slots... "
96 for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
98 if [ "${STATUS_ARRAY[$I]# Storage Element}" != "${STATUS_ARRAY[$I]}" ] &&
99 [ "${STATUS_ARRAY[$I]#*IMPORT/EXPORT}" = "${STATUS_ARRAY[$I]}" ]
101 # Line looks like this:
102 # Storage Element 30:Full :VolumeTag=000035
105 if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
107 # We now know we are a SE and we have a tape
108 tmp=${STATUS_ARRAY[$I]#*Storage Element }
110 SE_slots_full[$count_full]=${tmp}
111 tmp=${STATUS_ARRAY[$I]#*VolumeTag=}
112 SE_voltag[$count_full]=`echo ${tmp}`
113 count_full=$(($count_full+1))
116 tmp=${STATUS_ARRAY[$I]#*Storage Element }
119 # Test if tape is in a drive
121 for (( N=0; $N < ${#DTE_slots[*]}; N=$(($N+1)) ))
123 if [ "$tmp" = "${DTE_slots[$N]}" ]
125 SE_slots_full[$count_full]=${tmp}
126 SE_voltag[$count_full]=${DTE_voltag[$N]}
128 count_full=$(($count_full+1))
132 # If it is not in a drive mark it as empty`
135 SE_slots_empty[$count_empty]=${tmp}
136 count_empty=$(($count_empty+1))
146 function set_mtx_status() {
147 echo -n "Retrieving changer information... "
149 STATUS_ARRAY=(`mtx -f ${MTX_DEV} status`)
152 function set_mtx_data() {
156 # Set data from MTX status
163 function error_handler() {
165 if [ "$1" = "fatal" ]; then
169 function get_storage_from_pool() {
170 # Fetch the Storage name from the Pool resource
171 local storage=`echo "show pool=$1" | /opt/bacula/bin/bconsole | grep Storage:`
173 storage=${storage#*name=}
174 storage=${storage%% *}
178 function get_empty_drive() {
180 local my_storage=`get_storage_from_pool $pool`
182 # Find the device(s) without writers
184 local free_device=(`echo "status storage=${my_storage}" | /opt/bacula/bin/bconsole | grep -B1 writers=0| head -1`)
187 # We only want 1, so we take the first one:
188 free_device=${free_device[0]##*(}
189 free_device=${free_device%)*}
190 for (( I=0; $I < ${#DRIVES[*]}; I=$(($I+1)) )); do
191 if [ "${DRIVES[$I]}" = "${free_device}" ]; then
196 if [ "${drive_index}" = "-1" ]; then
197 echo "No free drive available"
203 function update_slots() {
204 if [ "$1" != "" ]; then
208 if [ "$pool" = "" ]; then
209 echo "update_slots: No 'pool' set"
212 local my_storage=`get_storage_from_pool $pool`
213 if [ "$my_storage" = "" ]; then
214 echo "update_slots: No storage found for $pool"
218 echo -n "Updating slots... "
219 echo "update storage=${my_storage} drive=0 slots" | /opt/bacula/bin/bconsole 2>/dev/null 1>/dev/null
224 # Continue after failed... should we?
227 function unload_drive() {
230 echo "mtx -f $MTX_DEV unload $slot $drive"
231 mtx -f $MTX_DEV unload $slot $drive
236 echo -n "Stopping bacula-sd... "
237 service bacula-sd stop 1>/dev/null 2>/dev/null
239 while([ -f ${BACULA_PID_DIR}/bacula-sd.*.pid ]); do
243 if [ $sleep = 10 ]; then
244 error_handler fatal "failed."
252 # Call all help functions
258 function help_unload_() {
259 echo "Syntax: $0 unload <volume(s)> from <pool>"
260 echo " unload <volumes> from <pool>"
261 echo " <volume(s)> a single volume or a space seperated list of volumes"
262 echo " <pool> the name of the pool the volumes should come from"
265 function _unload_() {
269 # Find volname in SE_voltag
270 # set org_slot from SE_slots
271 for (( N=0; $N < ${#SE_voltag[*]}; N=$(($N+1)) ))
273 if [ "$volname" = "${SE_voltag[$N]}" ]
275 org_slot="${SE_slots_full[$N]}"
279 # Set first (last) free export slot
280 dest_slot=${IE_slots_empty[${#IE_slots_empty[*]}-1]}
282 unset IE_slots_empty[${#IE_slots_empty[*]}-1]
284 echo -n "Unloading $volname from ${org_slot} to ${dest_slot}... "
285 mtx -f ${MTX_DEV} eepos 0 transfer ${org_slot} ${dest_slot}
293 # Make sure the catalog knows about it
299 # Make sure we have updated information
300 if [ "$pool" != "Scratch" ]; then
304 # If no slots are filled, do nothing
305 if [ "${#IE_voltag[*]}" = 0 ]; then
306 # Check MTX again, just to be sure
307 set_mtx_status 2>/dev/null 1>/dev/null
308 set_mtx_data 2>/dev/null 1>/dev/null
309 if [ "${#IE_voltag[*]}" = 0 ]; then
310 echo "There is nothing in the Import/Export slots."
315 # Per filled IE slot load the tape if there is room
317 for (( N=0; $N < ${#IE_voltag[*]}; N=$(($N+1)) ))
319 # Reset test variables
322 # Figure out our destination slot
323 if [ "${#SE_slots_empty}" = "0" ]; then
324 error_handler fatal "There are no more empty slots."
327 dest_slot=${SE_slots_empty[${#SE_slots_empty[*]}-1]}
329 unset SE_slots_empty[${#SE_slots_empty[*]}-1]
331 # Is the volume already known to the catalog
332 known_volume=`echo "list media pool=$pool" | /opt/bacula/bin/bconsole | sed -e 's/ */ /g' | grep "| ${IE_voltag[$N]} |"`
334 # Move tape from IE to slot
335 echo -n "Loading ${IE_voltag[$N]} from ${IE_slots_full[$N]} to ${dest_slot}... "
336 mtx -f ${MTX_DEV} eepos 0 transfer ${IE_slots_full[$N]} ${dest_slot}
339 if [ "$known_volume" = "" ]; then
340 slots_line="${slots_line},${dest_slot}"
347 # Let the catalog know
348 if [ "$pool" != "Scratch" ]; then
352 # If slots line is empty, we have only loaded tapes that already were in the catalog
353 # So they must have a label... oeps assumption!
354 if [ "$slots_line" != "" ]; then
356 # FIXME this only works with barcode labeling
357 local my_free_drive=`get_empty_drive`
360 echo "label storage=${my_storage} pool=${pool} slots=${slots_line#,} drive=${my_free_drive} barcodes"
361 error_handler fatal "No free drive found"
363 local my_storage=`get_storage_from_pool $pool`
365 echo "label storage=${my_storage} pool=${pool} slots=${slots_line#,} drive=${my_free_drive} barcodes"
366 error_handler fatal "No valid free drive found"
368 echo -n "Labeling new tapes, with: label storage=${my_storage} pool=${pool} slots=${slots_line#,} drive=${my_free_drive} barcodes"
369 echo -e "label storage=${my_storage} pool=${pool} slots=${slots_line#,} drive=${my_free_drive} barcodes\nyes" | /opt/bacula/bin/bconsole
370 #2>/dev/null 1>/dev/null
377 # Better safe then sorry
378 if [ "$pool" != "Scratch" ]; then
386 function help_load_() {
387 echo "Syntax: $0 load into <pool>"
388 echo " Load all tapes from the IE-slots into <pool>"
389 echo " <pool> the name of the pool the volumes should be loaded into"
392 function help_show_() {
396 function help_show_oldest() {
397 echo "Syntax: $0 show oldest [number] from <pool>"
398 echo " [number] amount of tapes to be unloaded, if no number is given then the amount of free IE slots is used"
399 echo " <pool> the name of the pool the volumes should come from"
401 function _show_oldest() {
402 # Provide amount of I/E slots we can use
403 # Script returns volumes
406 if [ "$number" = "" ]; then
407 free_slots=${#IE_slots_empty[*]}
413 if [ "$pool" = "" ]; then
414 error_handler fatal "No pool given"
420 # Make sure we get updated information
421 update_slots 1>/dev/null 2>/dev/null
423 # Get all tapes that can be ejected
425 for line in `echo "list media pool=${pool}" | /opt/bacula/bin/bconsole | egrep -e 'Full|Error|Used'`; do
426 # Check if tape is in the drive
427 # We only want to unload loaded tapes
428 tmp=`echo ${line} | cut -d'|' -f11`
429 if [ "${tmp:$((${#tmp}-2)):1}" = "1" ]; then
430 loadedtape_list[$N]=$line
435 # Sort on last written (get the oldest first)
437 for dateline in `for line in ${loadedtape_list[*]}
439 echo ${line} | cut -d'|' -f13
442 # Find the line with the date athand
443 for dataline in ${loadedtape_list[*]}; do
444 if [ ${dataline#*${dateline}} != ${dataline} ]; then
445 # Get volumes the tape is loaded in
446 my_vol=`echo $dataline | cut -d'|' -f3`
448 echo -n "${my_vol%% *} "
450 # If all free slots are filled exit
451 if [ "$C" = "$free_slots" ]; then
461 function help_remove_() {
466 function help_remove_oldest() {
467 echo "Syntax: $0 remove oldest [number] from <pool>"
468 echo " Unload oldest [number] of tapes from <pool> into the IE slots"
469 echo " [number] amount of tapes to be unloaded, if no number is given then the amount of free IE slots is used"
470 echo " <pool> the name of the pool the volumes should come from"
473 function help_remove_label() {
474 echo "Syntax: $0 remove label from <volume(s)>"
475 echo " Removes the label from a tape and thus destroys"
476 echo " all data on that tape (use with care!)."
477 echo " <volume(s)> a single volume or a space seperated list of volumes"
479 function _remove_oldest() {
480 volumes=`_show_oldest $number`
481 if [ "x$volumes" = "" ]; then
482 echo "No volumes to be unloaded"
487 function _remove_label() {
489 local current_slot=-1
490 local my_storage=`get_storage_from_pool $pool`
492 # No running jobs, so stop SD
493 if [ `echo "status storage=${my_pool}" | /opt/bacula/bin/bconsole | grep "No Jobs running"` = "No Jobs running" ]; then
496 echo "There are still jobs running, so can not destroy labels"
500 # No jobs are running... so fixed drive index
501 # and bring that drive offline
502 echo -n "Bring ${DRIVES[${drive_index}]} offline... "
504 mt -f ${DRIVES[${drive_index}]} offline
508 for rm_name in ${remove_volumes}; do
509 # See if the tape is already in the drive
510 for (( I=0; $I < ${#DTE_voltag[*]}; I=$(($I+1)) )); do
511 if [ "$rm_name" = "${DTE_voltag[$I]}" ]; then
516 echo -n "Make sure $rm_name is loaded... "
517 if [ $indrive = 0 ]; then
518 unload_drive ${DTE_slots[${drive_index}]} ${drive_index}
520 for (( I=0; $I < ${#SE_voltag[*]}; I=$(($I+1)) )); do
521 if [ "$rm_name" = "${SE_voltag[$I]}" ]; then
522 current_slot=${SE_slots_full[$I]}
523 mtx -f ${MTX_DEV} load ${current_slot} ${drive_index}
529 echo -n "Destroy label on ${rm_name}... "
531 mt -f ${DRIVES[${drive_index}]} rewind
533 error_handler fatal "rewind failed"
535 echo -n "rewind done, "
538 # Write EOF at the start of the tape
539 mt -f ${DRIVES[${my_free_drive}]} weof
541 error_handler fatal "EOF write failed"
547 unload_drive ${current_slot} ${drive_index}
550 # Restore to initial state
551 mtx -f ${MTX_DEV} load ${DTE_slots[${drive_index}]} ${drive_index}
552 service bacula-sd start
560 # If nothing is given... do help
561 if [ "$1" = "" ]; then
566 until [ "$1" = "" ]; do
567 if [ $1 = help ]; then
569 elif [ $1 = show ] || \
572 [ $1 = unload ]; then
575 elif [ $1 = label ] || \
580 [ $1 = unload ]; then
583 elif [ $1 = into ] || \
588 elif [ "${1//[0-9]/}" = "" ]; then
589 number="${number} $1"
599 if [ "$function" != "help" ]; then
604 ${function}_${do}_${what}