#!/bin/sh # # Bacula interface to virtual autoloader using disk storage # # Written by Kern Sibbald # # Bacula(R) - The Network Backup Solution # # Copyright (C) 2000-2016 Kern Sibbald # # The original author of Bacula is Kern Sibbald, with contributions # from many others, a complete list can be found in the file AUTHORS. # # You may use this file and others of this release according to the # license defined in the LICENSE file, which includes the Affero General # Public License, v3.0 ("AGPLv3") and some additional permissions and # terms pursuant to its AGPLv3 Section 7. # # This notice must be preserved when any source code is # conveyed and/or propagated. # # Bacula(R) is a registered trademark of Kern Sibbald. # If you set in your Device resource # # Changer Command = "path-to-this-script/disk-changer %c %o %S %a %d" # you will have the following input to this script: # # So Bacula will always call with all the following arguments, even though # in come cases, not all are used. Note, the Volume name is not always # included. # # disk-changer "changer-device" "command" "slot" "archive-device" "drive-index" "volume" # $1 $2 $3 $4 $5 $6 # # By default the autochanger has 10 Volumes and 1 Drive. # # Note: For this script to work, you *must" specify # Device Type = File # in each of the Devices associated with your AutoChanger resource. # # changer-device is the name of a file that overrides the default # volumes and drives. It may have: # maxslot=n where n is one based (default 10) # maxdrive=m where m is zero based (default 1 -- i.e. 2 drives) # # This code can also simulate barcodes. You simply put # a list of the slots and barcodes in the "base" directory/barcodes. # See below for the base directory definition. Example of a # barcodes file: # /var/bacula/barcodes # 1:Vol001 # 2:Vol002 # ... # # archive-device is the name of the base directory where you want the # Volumes stored appended with /drive0 for the first drive; /drive1 # for the second drive, ... For example, you might use # /var/bacula/drive0 Note: you must not have a trailing slash, and # the string (e.g. /drive0) must be unique, and it must not match # any other part of the directory name. These restrictions could be # easily removed by any clever script jockey. # # Full example: disk-changer /var/bacula/conf load 1 /var/bacula/drive0 0 TestVol001 # # The Volumes will be created with names slot1, slot2, slot3, ... maxslot in the # base directory. In the above example the base directory is /var/bacula. # However, as with tapes, their Bacula Volume names will be stored inside the # Volume label. In addition to the Volumes (e.g. /var/bacula/slot1, # /var/bacula/slot3, ...) this script will create a /var/bacula/loadedn # file to keep track of what Slot is loaded. You should not change this file. # # Modified 8 June 2010 to accept Volume names from the calling program as arg 6. # In this case, rather than storing the data in slotn, it is stored in the # Volume name. Note: for this to work, Volume names may not include spaces. # wd=@working_dir@ # # log whats done # # to turn on logging, uncomment the following line #touch $wd/disk-changer.log # dbgfile="$wd/disk-changer.log" debug() { if test -f $dbgfile; then echo "`date +\"%Y%m%d-%H:%M:%S\"` $*" >> $dbgfile fi } # # Create a temporary file # make_temp_file() { TMPFILE=`mktemp -t mtx.XXXXXXXXXX` if test x${TMPFILE} = x; then TMPFILE="$wd/disk-changer.$$" if test -f ${TMPFILE}; then echo "Temp file security problem on: ${TMPFILE}" exit 1 fi fi } # check parameter count on commandline # check_parm_count() { pCount=$1 pCountNeed=$2 if test $pCount -lt $pCountNeed; then echo "usage: disk-changer ctl-device command [slot archive-device drive-index]" echo " Insufficient number of arguments arguments given." if test $pCount -lt 2; then echo " Mimimum usage is first two arguments ..." else echo " Command expected $pCountNeed arguments" fi exit 1 fi } # # Strip off the final name in order to get the Directory ($dir) # that we are dealing with. # get_dir() { bn=`basename $device` dir=`echo "$device" | sed -e s%/$bn%%g` if [ ! -d $dir ]; then echo "ERROR: Autochanger directory \"$dir\" does not exist." echo " You must create it." exit 1 fi } # # Get the Volume name from the call line, or directly from # the volslotn information. # get_vol() { havevol=0 debug "vol=$volume" if test "x$volume" != x && test "x$volume" != "x*NONE*" ; then debug "touching $dir/$volume" touch $dir/$volume echo "$volume" >$dir/volslot${slot} havevol=1 elif [ -f $dir/volslot${slot} ]; then volume=`cat $dir/volslot${slot}` havevol=1 fi } # Setup arguments ctl=$1 cmd="$2" slot=$3 device=$4 drive=$5 volume=$6 # set defaults maxdrive=1 maxslot=10 # Pull in conf file if [ -f $ctl ]; then . $ctl fi # Check for special cases where only 2 arguments are needed, # all others are a minimum of 5 # case $2 in list|listall) check_parm_count $# 2 ;; slots) check_parm_count $# 2 ;; transfer) check_parm_count $# 4 if [ $slot -gt $maxslot ]; then echo "Slot ($slot) out of range (1-$maxslot)" debug "Error: Slot ($slot) out of range (1-$maxslot)" exit 1 fi ;; *) check_parm_count $# 5 if [ $drive -gt $maxdrive ]; then echo "Drive ($drive) out of range (0-$maxdrive)" debug "Error: Drive ($drive) out of range (0-$maxdrive)" exit 1 fi if [ $slot -gt $maxslot ]; then echo "Slot ($slot) out of range (1-$maxslot)" debug "Error: Slot ($slot) out of range (1-$maxslot)" exit 1 fi ;; esac debug "Parms: $ctl $cmd $slot $device $drive $volume $havevol" case $cmd in unload) debug "Doing disk -f $ctl unload $slot $device $drive $volume" get_dir if [ -f $dir/loaded${drive} ]; then ld=`cat $dir/loaded${drive}` else echo "Storage Element $slot is Already Full" debug "Unload error: $dir/loaded${drive} is already unloaded" exit 1 fi if [ $slot -eq $ld ]; then echo "0" >$dir/loaded${drive} unlink $device 2>/dev/null >/dev/null unlink ${device}.add 2>/dev/null >/dev/null rm -f ${device} ${device}.add else echo "Storage Element $slot is Already Full" debug "Unload error: $dir/loaded${drive} slot=$ld is already unloaded" exit 1 fi ;; load) debug "Doing disk $ctl load $slot $device $drive $volume" get_dir i=0 # Check if slot already in a drive while [ $i -le $maxdrive ]; do if [ -f $dir/loaded${i} ]; then ld=`cat $dir/loaded${i}` else ld=0 fi if [ $ld -eq $slot ]; then echo "Drive ${i} Full (Storage element ${ld} loaded)" debug "Load error: Cannot load Slot=${ld} in drive=$drive. Already in drive=${i}" exit 1 fi i=`expr $i + 1` done # Check if we have a Volume name get_vol if [ $havevol -eq 0 ]; then # check if slot exists if [ ! -f $dir/slot${slot} ] ; then echo "source Element Address $slot is Empty" debug "Load error: source Element Address $slot is Empty" exit 1 fi fi if [ -f $dir/loaded${drive} ]; then ld=`cat $dir/loaded${drive}` else ld=0 fi if [ $ld -ne 0 ]; then echo "Drive ${drive} Full (Storage element ${ld} loaded)" echo "Load error: Drive ${drive} Full (Storage element ${ld} loaded)" exit 1 fi echo "0" >$dir/loaded${drive} unlink $device 2>/dev/null >/dev/null unlink ${device}.add 2>/dev/null >/dev/null rm -f ${device} ${device}.add if [ $havevol -ne 0 ]; then ln -s $dir/$volume $device ln -s $dir/${volume}.add ${device}.add rtn=$? else ln -s $dir/slot${slot} $device ln -s $dir/slot${slot}.add ${device}.add rtn=$? fi if [ $rtn -eq 0 ]; then echo $slot >$dir/loaded${drive} fi exit $rtn ;; list) debug "Doing disk -f $ctl -- to list volumes" get_dir if [ -f $dir/barcodes ]; then cat $dir/barcodes else i=1 while [ $i -le $maxslot ]; do slot=$i volume= get_vol if [ $havevol -eq 0 ]; then echo "$i:" else echo "$i:$volume" fi i=`expr $i + 1` done fi exit 0 ;; listall) # ***FIXME*** must add new Volume stuff make_temp_file debug "Doing disk -f $ctl -- to list volumes" get_dir if [ ! -f $dir/barcodes ]; then exit 0 fi # we print drive content seen by autochanger # and we also remove loaded media from the barcode list i=0 while [ $i -le $maxdrive ]; do if [ -f $dir/loaded${i} ]; then ld=`cat $dir/loaded${i}` v=`awk -F: "/^$ld:/"' { print $2 }' $dir/barcodes` echo "D:$i:F:$ld:$v" echo "^$ld:" >> $TMPFILE fi i=`expr $i + 1` done # Empty slots are not in barcodes file # When we detect a gap, we print missing rows as empty # At the end, we fill the gap between the last entry and maxslot grep -v -f $TMPFILE $dir/barcodes | sort -n | \ perl -ne 'BEGIN { $cur=1 } if (/(\d+):(.+)?/) { if ($cur == $1) { print "S:$1:F:$2\n" } else { while ($cur < $1) { print "S:$cur:E\n"; $cur++; } } $cur++; } END { while ($cur < '"$maxslot"') { print "S:$cur:E\n"; $cur++; } } ' rm -f $TMPFILE exit 0 ;; transfer) # ***FIXME*** must add new Volume stuff get_dir make_temp_file slotdest=$device if [ -f $dir/slot{$slotdest} ]; then echo "destination Element Address $slot is Full" exit 1 fi if [ ! -f $dir/slot${slot} ] ; then echo "source Element Address $slot is Empty" exit 1 fi echo "Transfering $slot to $slotdest" mv $dir/slot${slot} $dir/slot{$slotdest} mv $dir/slot${slot}.add $dir/slot{$slotdest}.add if [ -f $dir/barcodes ]; then sed "s/^$slot:/$slotdest:/" > $TMPFILE sort -n $TMPFILE > $dir/barcodes fi exit 0 ;; loaded) debug "Doing disk -f $ctl $drive -- to find what is loaded" get_dir if [ -f $dir/loaded${drive} ]; then a=`cat $dir/loaded${drive}` else a="0" fi debug "Loaded: drive=$drive is $a" echo $a exit ;; slots) debug "Doing disk -f $ctl -- to get count of slots" echo $maxslot ;; esac