#!/usr/bin/sh
#
# BAREOS interface to virtual autoloader using disk storage
#
#  Written by Kern Sibbald
#
#  Copyright (C) 2000-2010 Free Software Foundation Europe e.V.
#
#  This program is Free Software; you can redistribute it and/or
#  modify it under the terms of version three of the GNU Affero General Public
#  License as published by the Free Software Foundation, which is
#  listed in the file LICENSE.
#
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#  Affero General Public License for more details.
#
#  You should have received a copy of the GNU Affero General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#  02110-1301, USA.
#
#  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 Bareos 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/bareos/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/bareos/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/bareos/conf load 1 /var/bareos/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/bareos.
#  However, as with tapes, their Bareos Volume names will be stored inside the
#  Volume label. In addition to the Volumes (e.g. /var/bareos/slot1,
#  /var/bareos/slot3, ...) this script will create a /var/bareos/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/bareos

#
# log what's 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 "  Minimum 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)"
      exit 1
    fi
    ;;
  *)
    check_parm_count $# 5
    if [ $drive -gt $maxdrive ]; then
      echo "Drive ($drive) out of range (0-$maxdrive)"
      exit 1
    fi
    if [ $slot -gt $maxslot ]; then
      echo "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"
      exit 1
    fi
    if [ $slot -eq $ld ]; then
      echo "0" >$dir/loaded${drive}
      unlink $device 2>/dev/null >/dev/null
      rm -f $device
    else
      echo "Storage Element $slot is Already Full"
      exit 1
    fi
    ;;

  load)
    debug "Doing disk $ctl load $slot $device $drive $volume"
    get_dir
    i=0
    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)"
        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"
        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)"
      exit 1
    fi
    echo "0" >$dir/loaded${drive}
    unlink $device 2>/dev/null >/dev/null
    rm -f $device
    if [ $havevol -ne 0 ]; then
      ln -s $dir/$volume $device
      rtn=$?
    else
      ln -s $dir/slot${slot} $device
      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 "Transferring $slot to $slotdest"
    mv $dir/slot${slot} $dir/slot{$slotdest}

    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
      cat $dir/loaded${drive}
    else
      echo "0"
    fi
    exit
    ;;

  slots)
    debug "Doing disk -f $ctl -- to get count of slots"
    echo $maxslot
    ;;
esac
