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