398 lines
10 KiB
Bash
398 lines
10 KiB
Bash
#!/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=/var/lib/bacula
|
|
|
|
#
|
|
# 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
|