1#!/bin/sh 2# 3# Bacula interface to virtual autoloader using disk storage 4# 5# Written by Kern Sibbald 6# 7# Bacula(R) - The Network Backup Solution 8# 9# Copyright (C) 2000-2016 Kern Sibbald 10# 11# The original author of Bacula is Kern Sibbald, with contributions 12# from many others, a complete list can be found in the file AUTHORS. 13# 14# You may use this file and others of this release according to the 15# license defined in the LICENSE file, which includes the Affero General 16# Public License, v3.0 ("AGPLv3") and some additional permissions and 17# terms pursuant to its AGPLv3 Section 7. 18# 19# This notice must be preserved when any source code is 20# conveyed and/or propagated. 21# 22# Bacula(R) is a registered trademark of Kern Sibbald. 23# If you set in your Device resource 24# 25# Changer Command = "path-to-this-script/disk-changer %c %o %S %a %d" 26# you will have the following input to this script: 27# 28# So Bacula will always call with all the following arguments, even though 29# in come cases, not all are used. Note, the Volume name is not always 30# included. 31# 32# disk-changer "changer-device" "command" "slot" "archive-device" "drive-index" "volume" 33# $1 $2 $3 $4 $5 $6 34# 35# By default the autochanger has 10 Volumes and 1 Drive. 36# 37# Note: For this script to work, you *must" specify 38# Device Type = File 39# in each of the Devices associated with your AutoChanger resource. 40# 41# changer-device is the name of a file that overrides the default 42# volumes and drives. It may have: 43# maxslot=n where n is one based (default 10) 44# maxdrive=m where m is zero based (default 1 -- i.e. 2 drives) 45# 46# This code can also simulate barcodes. You simply put 47# a list of the slots and barcodes in the "base" directory/barcodes. 48# See below for the base directory definition. Example of a 49# barcodes file: 50# /var/bacula/barcodes 51# 1:Vol001 52# 2:Vol002 53# ... 54# 55# archive-device is the name of the base directory where you want the 56# Volumes stored appended with /drive0 for the first drive; /drive1 57# for the second drive, ... For example, you might use 58# /var/bacula/drive0 Note: you must not have a trailing slash, and 59# the string (e.g. /drive0) must be unique, and it must not match 60# any other part of the directory name. These restrictions could be 61# easily removed by any clever script jockey. 62# 63# Full example: disk-changer /var/bacula/conf load 1 /var/bacula/drive0 0 TestVol001 64# 65# The Volumes will be created with names slot1, slot2, slot3, ... maxslot in the 66# base directory. In the above example the base directory is /var/bacula. 67# However, as with tapes, their Bacula Volume names will be stored inside the 68# Volume label. In addition to the Volumes (e.g. /var/bacula/slot1, 69# /var/bacula/slot3, ...) this script will create a /var/bacula/loadedn 70# file to keep track of what Slot is loaded. You should not change this file. 71# 72# Modified 8 June 2010 to accept Volume names from the calling program as arg 6. 73# In this case, rather than storing the data in slotn, it is stored in the 74# Volume name. Note: for this to work, Volume names may not include spaces. 75# 76 77wd=@working_dir@ 78 79# 80# log whats done 81# 82# to turn on logging, uncomment the following line 83#touch $wd/disk-changer.log 84# 85dbgfile="$wd/disk-changer.log" 86debug() { 87 if test -f $dbgfile; then 88 echo "`date +\"%Y%m%d-%H:%M:%S\"` $*" >> $dbgfile 89 fi 90} 91 92 93# 94# Create a temporary file 95# 96make_temp_file() { 97 TMPFILE=`mktemp -t mtx.XXXXXXXXXX` 98 if test x${TMPFILE} = x; then 99 TMPFILE="$wd/disk-changer.$$" 100 if test -f ${TMPFILE}; then 101 echo "Temp file security problem on: ${TMPFILE}" 102 exit 1 103 fi 104 fi 105} 106 107# check parameter count on commandline 108# 109check_parm_count() { 110 pCount=$1 111 pCountNeed=$2 112 if test $pCount -lt $pCountNeed; then 113 echo "usage: disk-changer ctl-device command [slot archive-device drive-index]" 114 echo " Insufficient number of arguments arguments given." 115 if test $pCount -lt 2; then 116 echo " Mimimum usage is first two arguments ..." 117 else 118 echo " Command expected $pCountNeed arguments" 119 fi 120 exit 1 121 fi 122} 123 124# 125# Strip off the final name in order to get the Directory ($dir) 126# that we are dealing with. 127# 128get_dir() { 129 bn=`basename $device` 130 dir=`echo "$device" | sed -e s%/$bn%%g` 131 if [ ! -d $dir ]; then 132 echo "ERROR: Autochanger directory \"$dir\" does not exist." 133 echo " You must create it." 134 exit 1 135 fi 136} 137 138# 139# Get the Volume name from the call line, or directly from 140# the volslotn information. 141# 142get_vol() { 143 havevol=0 144 debug "vol=$volume" 145 if test "x$volume" != x && test "x$volume" != "x*NONE*" ; then 146 debug "touching $dir/$volume" 147 touch $dir/$volume 148 echo "$volume" >$dir/volslot${slot} 149 havevol=1 150 elif [ -f $dir/volslot${slot} ]; then 151 volume=`cat $dir/volslot${slot}` 152 havevol=1 153 fi 154} 155 156 157# Setup arguments 158ctl=$1 159cmd="$2" 160slot=$3 161device=$4 162drive=$5 163volume=$6 164 165# set defaults 166maxdrive=1 167maxslot=10 168 169# Pull in conf file 170if [ -f $ctl ]; then 171 . $ctl 172fi 173 174 175# Check for special cases where only 2 arguments are needed, 176# all others are a minimum of 5 177# 178case $2 in 179 list|listall) 180 check_parm_count $# 2 181 ;; 182 slots) 183 check_parm_count $# 2 184 ;; 185 transfer) 186 check_parm_count $# 4 187 if [ $slot -gt $maxslot ]; then 188 echo "Slot ($slot) out of range (1-$maxslot)" 189 debug "Error: Slot ($slot) out of range (1-$maxslot)" 190 exit 1 191 fi 192 ;; 193 *) 194 check_parm_count $# 5 195 if [ $drive -gt $maxdrive ]; then 196 echo "Drive ($drive) out of range (0-$maxdrive)" 197 debug "Error: Drive ($drive) out of range (0-$maxdrive)" 198 exit 1 199 fi 200 if [ $slot -gt $maxslot ]; then 201 echo "Slot ($slot) out of range (1-$maxslot)" 202 debug "Error: Slot ($slot) out of range (1-$maxslot)" 203 exit 1 204 fi 205 ;; 206esac 207 208 209debug "Parms: $ctl $cmd $slot $device $drive $volume $havevol" 210 211case $cmd in 212 unload) 213 debug "Doing disk -f $ctl unload $slot $device $drive $volume" 214 get_dir 215 if [ -f $dir/loaded${drive} ]; then 216 ld=`cat $dir/loaded${drive}` 217 else 218 echo "Storage Element $slot is Already Full" 219 debug "Unload error: $dir/loaded${drive} is already unloaded" 220 exit 1 221 fi 222 if [ $slot -eq $ld ]; then 223 echo "0" >$dir/loaded${drive} 224 unlink $device 2>/dev/null >/dev/null 225 unlink ${device}.add 2>/dev/null >/dev/null 226 rm -f ${device} ${device}.add 227 else 228 echo "Storage Element $slot is Already Full" 229 debug "Unload error: $dir/loaded${drive} slot=$ld is already unloaded" 230 exit 1 231 fi 232 ;; 233 234 load) 235 debug "Doing disk $ctl load $slot $device $drive $volume" 236 get_dir 237 i=0 238 # Check if slot already in a drive 239 while [ $i -le $maxdrive ]; do 240 if [ -f $dir/loaded${i} ]; then 241 ld=`cat $dir/loaded${i}` 242 else 243 ld=0 244 fi 245 if [ $ld -eq $slot ]; then 246 echo "Drive ${i} Full (Storage element ${ld} loaded)" 247 debug "Load error: Cannot load Slot=${ld} in drive=$drive. Already in drive=${i}" 248 exit 1 249 fi 250 i=`expr $i + 1` 251 done 252 # Check if we have a Volume name 253 get_vol 254 if [ $havevol -eq 0 ]; then 255 # check if slot exists 256 if [ ! -f $dir/slot${slot} ] ; then 257 echo "source Element Address $slot is Empty" 258 debug "Load error: source Element Address $slot is Empty" 259 exit 1 260 fi 261 fi 262 if [ -f $dir/loaded${drive} ]; then 263 ld=`cat $dir/loaded${drive}` 264 else 265 ld=0 266 fi 267 if [ $ld -ne 0 ]; then 268 echo "Drive ${drive} Full (Storage element ${ld} loaded)" 269 echo "Load error: Drive ${drive} Full (Storage element ${ld} loaded)" 270 exit 1 271 fi 272 echo "0" >$dir/loaded${drive} 273 unlink $device 2>/dev/null >/dev/null 274 unlink ${device}.add 2>/dev/null >/dev/null 275 rm -f ${device} ${device}.add 276 if [ $havevol -ne 0 ]; then 277 ln -s $dir/$volume $device 278 ln -s $dir/${volume}.add ${device}.add 279 rtn=$? 280 else 281 ln -s $dir/slot${slot} $device 282 ln -s $dir/slot${slot}.add ${device}.add 283 rtn=$? 284 fi 285 if [ $rtn -eq 0 ]; then 286 echo $slot >$dir/loaded${drive} 287 fi 288 exit $rtn 289 ;; 290 291 list) 292 debug "Doing disk -f $ctl -- to list volumes" 293 get_dir 294 if [ -f $dir/barcodes ]; then 295 cat $dir/barcodes 296 else 297 i=1 298 while [ $i -le $maxslot ]; do 299 slot=$i 300 volume= 301 get_vol 302 if [ $havevol -eq 0 ]; then 303 echo "$i:" 304 else 305 echo "$i:$volume" 306 fi 307 i=`expr $i + 1` 308 done 309 fi 310 exit 0 311 ;; 312 313 listall) 314 # ***FIXME*** must add new Volume stuff 315 make_temp_file 316 debug "Doing disk -f $ctl -- to list volumes" 317 get_dir 318 if [ ! -f $dir/barcodes ]; then 319 exit 0 320 fi 321 322 # we print drive content seen by autochanger 323 # and we also remove loaded media from the barcode list 324 i=0 325 while [ $i -le $maxdrive ]; do 326 if [ -f $dir/loaded${i} ]; then 327 ld=`cat $dir/loaded${i}` 328 v=`awk -F: "/^$ld:/"' { print $2 }' $dir/barcodes` 329 echo "D:$i:F:$ld:$v" 330 echo "^$ld:" >> $TMPFILE 331 fi 332 i=`expr $i + 1` 333 done 334 335 # Empty slots are not in barcodes file 336 # When we detect a gap, we print missing rows as empty 337 # At the end, we fill the gap between the last entry and maxslot 338 grep -v -f $TMPFILE $dir/barcodes | sort -n | \ 339 perl -ne 'BEGIN { $cur=1 } 340 if (/(\d+):(.+)?/) { 341 if ($cur == $1) { 342 print "S:$1:F:$2\n" 343 } else { 344 while ($cur < $1) { 345 print "S:$cur:E\n"; 346 $cur++; 347 } 348 } 349 $cur++; 350 } 351 END { while ($cur < '"$maxslot"') { print "S:$cur:E\n"; $cur++; } } ' 352 353 rm -f $TMPFILE 354 exit 0 355 ;; 356 transfer) 357 # ***FIXME*** must add new Volume stuff 358 get_dir 359 make_temp_file 360 slotdest=$device 361 if [ -f $dir/slot{$slotdest} ]; then 362 echo "destination Element Address $slot is Full" 363 exit 1 364 fi 365 if [ ! -f $dir/slot${slot} ] ; then 366 echo "source Element Address $slot is Empty" 367 exit 1 368 fi 369 370 echo "Transfering $slot to $slotdest" 371 mv $dir/slot${slot} $dir/slot{$slotdest} 372 mv $dir/slot${slot}.add $dir/slot{$slotdest}.add 373 374 if [ -f $dir/barcodes ]; then 375 sed "s/^$slot:/$slotdest:/" > $TMPFILE 376 sort -n $TMPFILE > $dir/barcodes 377 fi 378 exit 0 379 ;; 380 loaded) 381 debug "Doing disk -f $ctl $drive -- to find what is loaded" 382 get_dir 383 if [ -f $dir/loaded${drive} ]; then 384 a=`cat $dir/loaded${drive}` 385 else 386 a="0" 387 fi 388 debug "Loaded: drive=$drive is $a" 389 echo $a 390 exit 391 ;; 392 393 slots) 394 debug "Doing disk -f $ctl -- to get count of slots" 395 echo $maxslot 396 ;; 397esac 398