#!/bin/bash
#
# zfs-transfer-clean
#
# Delete old dataset snapshots
#
#



FECHA="20231014"
VERSION="0.0.1"

[[ "$DEBUG" ]] || DEBUG=0
if [[ "$MONDIR" ]]; then
   USA_MONDIR=1
   MONFILE=${MONDIR}/`basename $0`
else
   USA_MONDIR=0
fi

######################################################
#                  Funciones utiles                  #
######################################################


logdebug () {
   local msg="$1"
   local level="$2"
   [[ "$level" ]] || level=1
   if [[ $DEBUG -ge $level ]]; then
      echo "DEBUG[$level]: $msg" >&2
   fi
}

termina_error() {
   # Registramos el suceso en MONDIR
   if [ $USA_MONDIR -gt 0 ]; then
      [ -d "$MONDIR" ] || mkdir -p "$MONDIR"
      if [ $? -gt 0 ]; then
         echo "*** Error al crear $MONDIR" >&2
      else
         echo ----- `date` ----- >> $MONFILE
         echo "*** error: $1" >> "$MONFILE"
         if [ -r $ERRFILE ]; then
            cat $ERRFILE >> "$MONFILE"
         fi
      fi
   fi


   # Informamos via stderr
   echo "[$(hostname)] *** error: $1" >&2
   if [ -r $ERRFILE ]; then
      cat $ERRFILE >&2
   fi

   rm -f $ERRFILE
   if [ "$2" ]; then
      exit $2
   else
      exit 1
   fi
}


despliega_uso() {
cat << EOF

Delete old dataset snapshots.

Usage: zfs-transfer-clean [options] <dataset>

Options:

  -m <min>    Minimum amount of snapshots to preserve (Default: $MINSNAPS).
  -x <max>    Maximum amount of snapshots to preserve (Default: $MAXSNAPS).
  -p <prefix> Only consider datasets whose name starts with <prefix>.
              (Default: $SNAP_PREFIX)
  -n          Do not actually delete anything, only show what would be done.
  -c <config> Config file path (Default: $CONFIGFILE) 

zfs-transfer-clean will remove the oldest snapshots present for <dataset>
so only <max> snapshots remain.

If less than <min> snapshots would remain then remove only the amount of
snapshots that will keep <min> snapshots active for the named dataset.

EOF
}


crea_temp() {
   # Archivo temporal
   local retfile=`mktemp ${TMPBASE}/$(basename $0).XXXXXX`
   if [ $? -gt 0 ]; then
      termina_error "Error al crear archivo temporal en $TMPBASE"
      exit 1
   fi
   
   echo $retfile
}

crea_temp_dir() {
   # Directorio temporal
   local retdir=`mktemp -d ${TMPBASE}/$(basename $0).XXXXXX`
   if [ $? -gt 0 ]; then
      termina_error "Error al crear directorio temporal en $TMPBASE"
      exit 1
   fi
   
   echo $retdir
}

#
# get_envdir $envdir
#
# For earch file f in the directory $envdir add a variable named f to the
# environment with the contents of file f as a value.
#

get_envdir() {
   local envdir=$1

   if [[ -d "$envdir" ]]; then
      for v in $('ls' -1 $envdir); do
         if [[ -s $v ]]; then
            [[ $v =~ ^\. || $v =~ ^= ]] || read -d"" -r "$v" < "${envdir}/${v}"
         else
            unset $v
         fi
      done
   fi
}


snap_list () {
   "${ZFS}" list -t snap -H "$dataset" | cut -f1 | egrep "^${dataset}@${SNAP_PREFIX}"
}

colon_period () {
   local c
   [[ "$1" ]] || c=0
   if [[ $c -eq 0 ]]; then
      echo .
   else
      echo :
   fi
}

#######################################################
#                      Defaults                       #
#######################################################

# Direccion del administrador del sistema
[ "$ADMIN" ] || ADMIN="monitor@asic-linux.com.mx"

# Archivo de configuracion
[ "$CONFIGFILE" ] || CONFIGFILE="/etc/zfs-transfer/$(basename $0).conf"

# Ruta para archivos temporales
[ "$TMPBASE" ] || TMPBASE="/tmp"


# Ruta al ejecutable zfs
[[ "$ZFS" ]] || ZFS="/sbin/zfs"

# Prefijo para los snapshots
[[ "$SNAP_PREFIX" ]] || SNAP_PREFIX=""

# Minimum amount of snapshots to keep
[[ "$MINSNAPS" ]] || MINSNAPS=2

# Maximum amount of snapshots to keep
[[ "$MAXSNAPS" ]] || MAXSNAPS=2

# Simulate only?
[[ "$SIMULATE" ]] || SIMULATE=0



#######################################################
#              Procesamos las opciones                #
#######################################################

while getopts ":m:x:p:nvVch" opt; do
  case $opt in
    m)
      MINSNAPS="$OPTARG"
      ;;
    x)
      MAXSNAPS="$OPTARG"
      ;;
    p)
      SNAP_PREFIX="$OPTARG"
      ;;
    n)
      SIMULATE=1
      ;;
    v)
      VERBOSE=1
      ;;
    V)
      echo "$(basename $0) $VERSION, $FECHA"
      exit 0
      ;;
    c)
      CONFIGFILE="$OPTARG"
      ;;
    h)
      despliega_uso
      exit 0
      ;;
    \?)
      echo "$(basename $0): Opcion invalida: -$OPTARG" >&2
      despliega_uso
      exit 1
      ;;
    :)
      echo "$(basename $0): Opcion -$OPTARG requiere un argumento" >&2
      exit 1
      ;;
  esac
done

shift $(($OPTIND-1))

# Log de errores
ERRFILE=$( crea_temp ) || termina_error "Error al crear el archivo ERRFILE"

# Leemos el archivo de configuracion
if [ -f "$CONFIGFILE" ]; then
   source "$CONFIGFILE" 2>>$ERRFILE || termina_error "Leyendo el archivo de configuracion $CONFIGFILE"
fi

#####################################################
#             Argumentos posicionales               #
#####################################################

dataset="$1"

#####################################################
#            Verificaciones de sanidad              #
#####################################################

# ZFS es ejecutable?
[[ -x "$ZFS" ]]  || termina_error "$ZFS no es ejecutable."

[[ $MAXSNAPS -lt $MINSNAPS ]] && termina_error "You asked for no less than $MINSNAPS but want no more than $MAXSNAPS. I'm afraid I can't do that, Dave." 

if [[ -z "$dataset" ]]; then
   despliega_uso
   termina_error "The <dataset> argument is required" 111
fi

#####################################################
#                 Bucle principal                   #
#####################################################

main () {

snap_count=$(snap_list | wc -l)

logdebug "snap_count: $snap_count"
if [[ $DEBUG -ge 2 ]]; then
   logdebug "snap_list:" 2
   snap_list | while read l; do
      logdebug "$l" 2
   done
fi

logdebug "MAXSNAPS: $MAXSNAPS"
logdebug "MINSNAPS: $MINSNAPS"

# If we already have MINSNAPS or less there's nothing to do.
if [[ $snap_count -le $MINSNAPS ]]; then
   [[ VERBOSE -gt 0 ]] && echo "$dataset already has $MINSNAPS or less snapshots with prefix '$SNAP_PREFIX'. There's nothing to do." >&2
   exit 0
fi

# Calculate how many snapshots should we destroy
snap_count_diff=$(( $snap_count - $MAXSNAPS ))
if [[ $snap_count_diff -lt 0 ]]; then
   snap_count_diff=0
fi

logdebug "snap_count_diff: snap_count ($snap_count) - MAXSNAPS ($MAXSNAPS) | 0 = $snap_count_diff"

# Destroy $snap_count_diff snaps
vflag=""
[[ $VERBOSE -gt 0 ]] && vflag="-v"
nflag=""
will_would="will"
if [[ $SIMULATE -gt 0 ]]; then
   nflag="-n" 
   will_would="would"
fi

keep_count=$(( $snap_count - $snap_count_diff )) 

logdebug "keep_count: snap_count ($snap_count) - snap_count_diff ($snap_count_diff) = $keep_count"

echo
echo "I ${will_would} keep ${keep_count} snapshots."
echo


# List the snaps to be kept
if [[ $SIMULATE -gt 0 ]]; then
   snap_list | tail -n ${keep_count} | while read ksnap; do
      echo "${will_would} keep ${ksnap}"
   done
fi

echo
echo "I ${will_would} destroy ${snap_count_diff} snapshots."
echo

deleted=0
snap_list | while read snap; do
   [[ $deleted -eq $snap_count_diff ]] && break
   "${ZFS}" destroy $vflag $nflag "$snap"
   deleted=$(( deleted + 1 ))
done

} 

main
rm -f $ERRFILE
exit $retval
