]> git.sur5r.net Git - bacula/bacula/blob - bacula/examples/sd-tool
Restore win32 dir from Branch-5.2 and update it
[bacula/bacula] / bacula / examples / sd-tool
1 #!/bin/bash
2
3 # sd-tool - Tool to manipulate tapes and tape-changers
4 # Copyright (C) 2010, 2011 Dennis Leeuw
5 #
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.
10 #
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.
15 #
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
19
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)
23
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... "
27         local tmp=""
28         for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
29                 do
30                 if [ "${STATUS_ARRAY[$I]#Data Transfer Element}" != "${STATUS_ARRAY[$I]}" ]
31                         then
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%%:*}
36
37                         # Check Full fist
38                         if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
39                                 then
40                                 # We now know we are a DTE and we have a tape
41                                 tmp=${STATUS_ARRAY[$I]#*Storage Element }
42                                 tmp=${tmp%% *}
43                                 DTE_slots[$DTE_number]=${tmp}
44                                 tmp=${STATUS_ARRAY[$I]#*VolumeTag = }
45                                 DTE_voltag[$DTE_number]=`echo ${tmp}`
46                         fi
47                 fi
48         done
49         echo "done."
50         return 0
51 }
52
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... "
56         local count_full=0
57         local count_empty=0
58         local tmp=""
59         for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
60                 do
61                 if [ "${STATUS_ARRAY[$I]#      Storage Element}" != "${STATUS_ARRAY[$I]}" ] &&
62                    [ "${STATUS_ARRAY[$I]#*IMPORT/EXPORT}" != "${STATUS_ARRAY[$I]}" ]
63                         then
64                         # Line looks like this:
65                         #      Storage Element 36 IMPORT/EXPORT:Empty:VolumeTag=
66
67                         # Check Full fist
68                         if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
69                                 then
70                                 # We now know we are a IE and we have a tape
71                                 tmp=${STATUS_ARRAY[$I]#*Storage Element }
72                                 tmp=${tmp%% *}
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))
77                         else
78                                 # Line is Empty
79                                 tmp=${STATUS_ARRAY[$I]#*Storage Element }
80                                 tmp=${tmp%% *}
81                                 IE_slots_empty[$count_empty]=${tmp}
82                                 count_empty=$(($count_empty+1))
83                         fi
84                 fi
85         done
86         echo "done."
87         return 0
88 }
89
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... "
93         local count_full=0
94         local count_empty=0
95         local tmp=""
96         for (( I=0; $I < ${#STATUS_ARRAY[*]}; I=$(($I+1)) ))
97                 do
98                 if [ "${STATUS_ARRAY[$I]#      Storage Element}" != "${STATUS_ARRAY[$I]}" ] &&
99                    [ "${STATUS_ARRAY[$I]#*IMPORT/EXPORT}" = "${STATUS_ARRAY[$I]}" ]
100                         then
101                         # Line looks like this:
102                         #       Storage Element 30:Full :VolumeTag=000035
103
104                         # Check Full fist
105                         if [ "${STATUS_ARRAY[$I]#*:Full}" != "${STATUS_ARRAY[$I]}" ]
106                                 then
107                                 # We now know we are a SE and we have a tape
108                                 tmp=${STATUS_ARRAY[$I]#*Storage Element }
109                                 tmp=${tmp%%:*}
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))
114                         else
115                                 # Line is Empty
116                                 tmp=${STATUS_ARRAY[$I]#*Storage Element }
117                                 tmp=${tmp%%:*}
118
119                                 # Test if tape is in a drive
120                                 local full=0
121                                 for (( N=0; $N < ${#DTE_slots[*]}; N=$(($N+1)) ))
122                                         do
123                                         if [ "$tmp" = "${DTE_slots[$N]}" ]
124                                                 then
125                                                 SE_slots_full[$count_full]=${tmp}
126                                                 SE_voltag[$count_full]=${DTE_voltag[$N]}
127                                                 full=1
128                                                 count_full=$(($count_full+1))
129                                         fi
130                                 done
131
132                                 # If it is not in a drive mark it as empty`
133                                 if [ "$full" = "0" ]
134                                         then
135                                         SE_slots_empty[$count_empty]=${tmp}
136                                         count_empty=$(($count_empty+1))
137                                 fi
138                         fi
139                         count=$(($count+1))
140                 fi
141         done
142         IE_slots=$count
143         echo "done."
144         return 0
145 }
146 function set_mtx_status() {
147         echo -n "Retrieving changer information... "
148         local IFS=$'\n'
149         STATUS_ARRAY=(`mtx -f ${MTX_DEV} status`)
150         echo "done."
151 }
152 function set_mtx_data() {
153         # Collect MTX data
154         set_mtx_status
155
156         # Set data from MTX status
157         set_dte_data
158         set_se_data
159         set_ie_data
160
161         return 0
162 }
163 function error_handler() {
164         echo $2
165         if [ "$1" = "fatal" ]; then
166                 exit 1
167         fi
168 }
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:`
172         local err=$?
173         storage=${storage#*name=}
174         storage=${storage%% *}
175         echo $storage
176         return $err
177 }
178 function get_empty_drive() {
179         local drive_index=-1
180         local my_storage=`get_storage_from_pool $pool`
181
182         # Find the device(s) without writers
183         local IFS=$'\n'
184         local free_device=(`echo "status storage=${my_storage}" | /opt/bacula/bin/bconsole | grep -B1 writers=0| head -1`)
185         local err=$?
186
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
192                         drive_index=$I
193                 fi
194         done
195
196         if [ "${drive_index}" = "-1" ]; then
197                 echo "No free drive available"
198                 return 1
199         fi
200         echo $drive_index
201         return $err
202 }
203 function update_slots() {
204         if [ "$1" != "" ]; then
205                 pool=$1
206         fi
207
208         if [ "$pool" = "" ]; then
209                 echo "update_slots: No 'pool' set"
210                 exit 1
211         fi
212         local my_storage=`get_storage_from_pool $pool`
213         if [ "$my_storage" = "" ]; then
214                 echo "update_slots: No storage found for $pool"
215                 exit 1
216         fi
217
218         echo -n "Updating slots... "
219         echo "update storage=${my_storage} drive=0 slots" | /opt/bacula/bin/bconsole 2>/dev/null 1>/dev/null
220         if [ $? = 0 ]; then
221                 echo "done."
222         else
223                 echo "failed."
224                 # Continue after failed... should we?
225         fi
226 }
227 function unload_drive() {
228         local slot=$1
229         local drive=$2
230         echo "mtx -f $MTX_DEV unload $slot $drive"
231         mtx -f $MTX_DEV unload $slot $drive
232         return $?
233 }
234 function stop_sd() {
235         local sleep=0
236         echo -n "Stopping bacula-sd... "
237         service bacula-sd stop 1>/dev/null 2>/dev/null
238
239         while([ -f ${BACULA_PID_DIR}/bacula-sd.*.pid ]); do
240                 sleep 2
241                 sleep=$(($sleep+2))
242
243                 if [ $sleep = 10 ]; then
244                         error_handler fatal "failed."
245                 fi
246         done
247
248         echo "done."
249         return 0
250 }
251 function help__() {
252         # Call all help functions
253         help_show_
254         help_remove_
255         help_load_
256         help_unload_
257 }
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"
263 }
264
265 function _unload_() {
266         for volname in $@
267                 do
268
269                 # Find volname in SE_voltag
270                 # set org_slot from SE_slots
271                 for (( N=0; $N < ${#SE_voltag[*]}; N=$(($N+1)) ))
272                         do
273                         if [ "$volname" = "${SE_voltag[$N]}" ]
274                                 then
275                                 org_slot="${SE_slots_full[$N]}"
276                         fi
277                 done
278
279                 # Set first (last) free export slot
280                 dest_slot=${IE_slots_empty[${#IE_slots_empty[*]}-1]}
281
282                 unset IE_slots_empty[${#IE_slots_empty[*]}-1]
283
284                 echo -n "Unloading $volname from ${org_slot} to ${dest_slot}... "
285                 mtx -f ${MTX_DEV} eepos 0 transfer ${org_slot} ${dest_slot}
286                 if [ $? = 0 ]; then
287                         echo "done."
288                 else
289                         echo "failed."
290                 fi
291         done
292
293         # Make sure the catalog knows about it
294         update_slots
295         return 0
296 }
297 ### LOAD ###
298 function _load_() {
299         # Make sure we have updated information
300         if [ "$pool" != "Scratch" ]; then
301                 update_slots
302         fi
303
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."
311                         exit 0
312                 fi
313         fi
314
315         # Per filled IE slot load the tape if there is room
316         slots_line=""
317         for (( N=0; $N < ${#IE_voltag[*]}; N=$(($N+1)) ))
318                 do
319                 # Reset test variables
320                 known_volume=""
321
322                 # Figure out our destination slot
323                 if [ "${#SE_slots_empty}" = "0" ]; then
324                         error_handler fatal "There are no more empty slots."
325                 fi
326                 
327                 dest_slot=${SE_slots_empty[${#SE_slots_empty[*]}-1]}
328
329                 unset SE_slots_empty[${#SE_slots_empty[*]}-1]
330
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]} |"`
333
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}
337                 if [ $? = 0 ]; then
338                         echo "done."
339                         if [ "$known_volume" = "" ]; then
340                                 slots_line="${slots_line},${dest_slot}"
341                         fi
342                 else
343                         echo "failed."
344                 fi
345         done
346
347         # Let the catalog know
348         if [ "$pool" != "Scratch" ]; then
349                 update_slots
350         fi
351
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
355                 # Label them tapes
356                 # FIXME this only works with barcode labeling
357                 local my_free_drive=`get_empty_drive`
358                 if [ $? != 0 ]; then
359                         echo
360                         echo "label storage=${my_storage} pool=${pool} slots=${slots_line#,} drive=${my_free_drive} barcodes"
361                         error_handler fatal "No free drive found"
362                 fi
363                 local my_storage=`get_storage_from_pool $pool`
364                 if [ $? != 0 ]; then
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"
367                 fi
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
371                 if [ $? = 0 ]; then
372                         echo "done."
373                 else
374                         echo "failed."
375                 fi
376
377                 # Better safe then sorry
378                 if [ "$pool" != "Scratch" ]; then
379                         update_slots
380                 fi
381         fi
382
383         return 0
384 }
385
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"
390 }
391 # END LOAD #
392 function help_show_() {
393         help_show_oldest
394 }
395
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"
400 }
401 function _show_oldest() {
402         # Provide amount of I/E slots we can use
403         # Script returns volumes
404
405         # Check number
406         if [ "$number" = "" ]; then
407                 free_slots=${#IE_slots_empty[*]}
408         else
409                 free_slots=$number
410         fi
411
412         # Check pool
413         if [ "$pool" = "" ]; then
414                 error_handler fatal "No pool given"
415         fi
416
417         # Global IFS setting
418         IFS=$'\n'
419
420         # Make sure we get updated information
421         update_slots 1>/dev/null 2>/dev/null
422
423         # Get all tapes that can be ejected
424         local N=0
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
431                         N=$(($N+1))
432                 fi
433         done
434
435         # Sort on last written (get the oldest first)
436         local C=0
437         for dateline in `for line in ${loadedtape_list[*]}
438                         do
439                                 echo ${line} | cut -d'|' -f13
440                         done | sort`; do
441         
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`
447                                 my_vol=${my_vol# }
448                                 echo -n "${my_vol%% *} "
449                                 C=$(($C+1))
450                                 # If all free slots are filled exit
451                                 if [ "$C" = "$free_slots" ]; then
452                                         echo
453                                         exit
454                                 fi
455                         fi
456                 done
457         done
458
459         echo
460 }
461 function help_remove_() {
462         help_remove_oldest
463         help_remove_label
464 }
465
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"
471 }
472
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"
478 }
479 function _remove_oldest() {
480         volumes=`_show_oldest $number`
481         if [ "x$volumes" = "" ]; then
482                 echo "No volumes to be unloaded"
483                 exit 0
484         fi
485         _unload_ $volumes
486 }
487 function _remove_label() {
488         local indrive=0
489         local current_slot=-1
490         local my_storage=`get_storage_from_pool $pool`
491
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
494                 sd_stop
495         else
496                 echo "There are still jobs running, so can not destroy labels"
497                 exit 255
498         fi
499
500         # No jobs are running... so fixed drive index
501         # and bring that drive offline
502         echo -n "Bring ${DRIVES[${drive_index}]} offline... "
503         local drive_index=0
504         mt -f ${DRIVES[${drive_index}]} offline
505         echo "done."
506
507         # Destroy label(s)
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
512                                 indrive=1
513                         fi
514                 done
515
516                 echo -n "Make sure $rm_name is loaded... "
517                 if [ $indrive = 0 ]; then
518                         unload_drive ${DTE_slots[${drive_index}]} ${drive_index}
519                 else
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}
524                                 fi
525                         done
526                 fi
527                 echo "done."
528
529                 echo -n "Destroy label on ${rm_name}... "
530                 # Rewind the tape
531                 mt -f ${DRIVES[${drive_index}]} rewind
532                 if [ $? != 0 ]; then
533                         error_handler fatal "rewind failed"
534                 else
535                         echo -n "rewind done, "
536                 fi
537
538                 # Write EOF at the start of the tape
539                 mt -f ${DRIVES[${my_free_drive}]} weof
540                 if [ $? != 0 ]; then
541                         error_handler fatal "EOF write failed"
542                 else
543                         echo "wrote EOF"
544                 fi
545
546                 # Empty slot
547                 unload_drive ${current_slot} ${drive_index}
548         done
549
550         # Restore to initial state
551         mtx -f ${MTX_DEV} load ${DTE_slots[${drive_index}]} ${drive_index}
552         service bacula-sd start
553
554         exit 0
555 }
556 #------#
557 # Main #
558 #------#
559
560 # If nothing is given... do help
561 if [ "$1" = "" ]; then
562         function=help
563 fi
564
565 # Parse command line
566 until [ "$1" = "" ]; do
567         if [ $1 = help ]; then
568                 function=$1
569         elif [ $1 = show ] || \
570            [ $1 = remove ] || \
571            [ $1 = load ] || \
572            [ $1 = unload ]; then
573                 # Do list
574                 do=$1
575         elif [ $1 = label ] || \
576              [ $1 = oldest ] || \
577              [ $1 = show ] || \
578              [ $1 = remove ] || \
579              [ $1 = load ] || \
580              [ $1 = unload ]; then
581                 # What list
582                 what=$1
583         elif [ $1 = into ] || \
584              [ $1 = from ]; then
585                 # Pool + extra shift
586                 pool=$2
587                 shift
588         elif [ "${1//[0-9]/}" = "" ]; then
589                 number="${number} $1"
590         fi
591
592         shift
593 done
594
595 # Clean up number
596 number=${number# }
597
598 # Set global vars
599 if [ "$function" != "help" ]; then
600         set_mtx_data
601 fi
602
603 # Do function
604 ${function}_${do}_${what}
605
606 #-----#
607 # END #
608