#!/bin/sh
#
# For debug: add -x on the first line above.

#------------------------------------------------------------------------------
#                                U&R Consultores                               
#                                www.uyr.com.ar 
# Archive  : /secure/saveit
# Creation : 19991016 - Francisco Mancardi - (FM)                      
# Revision : 
#
#            20001105    FM
#                           Adding init_messages
#                           Test the existence of traget to backup
#
#            20000729    FM
#                           Adding functions to improve code.
#                           Adding screen messages.   
#
#            20000710    FM
#                            Added option -c (Comment).
#                            You can add a comment line (why did I save this file)
#                            that will be added to the log file. 
#                            
#                            Added test:
#                            is $# -ne 0, AFTER processing options via getopt ?
#                            
#                            Added version string  
#
#                            Detected problems with 'who am i' and mc 
#                            Midnight Commander on Linux 6.0 if logged 
#                            as root.
#                            First try to fix it.  
#
#
#            20000703    SB  (Sean Boran)
#                            english notes, tested on Sol2.7, OpenBSD 2.6
#                            If file has been save today already, save with time.
#                            (We need more testing after these changes)
#                            To Do: handle devices, links, etc.
#            20000403-01 FM
#            detecto el S.O. para saber que usar como awk
#
#            19991019-02 FM
#            Descubro el siguiente error:
#            save /etc/rc?.d/KAK -> el archivo KAK no existe, luego lo
#                                   que el shell expande y me manda como
#                                   DIR_DESTINO resulta ser la lista :
#                                   /etc/rc0.d /etc/rc1.d ... /etc/rcS.d 
#                                   Esto causaba la falla de un test.
#            Sin embargo si funcionaba bien:
#            save /etc/rc?.d/README ya que existen tres de estos archivos
#
#            19991019-01 FM
#            - voy a controlar antes de salvar algo, si existe.
#            - hago siempre append en el archivo de lista de operaciones
#            19991017 - FM 
#            1) agrego como argumeno opcional el directorio de base donde 
#               hacer el backup.
#            2) Se puede indicar el pathname de lo que se desea salvar
#            3) Se puede indicar que salve un directorio completo.
#               Atencion!: 
#                          en este caso se lleva TODOS los subdirectorios
#                          que tenga dentro. 
#------------------------------------------------------------------------------

# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#                                  FUNCIONES AUXILIARES
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

# ........................................................................
# set the text of the messages
# ........................................................................
init_messages()
{

# Messages
MSG_NOT_FOUND="File/Dir doesn't exists";   export MSG_NOT_FOUND
MSG_CANT_COPY="Can't copy: ";           export MSG_CANT_COPY
MSG_COPYING="copying ";                 export MSG_COPYING
MSG_COPYING_FILE="copying file ";       export MSG_COPYING_FILE
MSG_COPYING_DIR="copying directory ";   export MSG_COPYING_DIR

MSG_I_D_NOT="options -i and -d not allowed together"; export MSG_I_D_NOT
MSG_I_O_NOT="options -i and -o not allowed together"; export MSG_I_D_NOT

MSG_DIR_LIST="Directory listing: ";     export MSG_DIR_LIST
}
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++



# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
debug()
{
echo $1
}

# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# Funcion: usage
#          Muestra en pantalla las lineas que indican como se usa y 
#          un mensaje adicional.
#
# Argumentos: mensaje adicional
#
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usage()
{

VERSION="2.4.1"

PROGNAME=`basename $0`

# aca levanto el argumento
MENSAJE=$1

#${MENSAJE}

cat << EOF
Simple Backup Utility - v$VERSION

usage
${PROGNAME} [-p] [-d backup_dir] Pathname

Options

-d root_backup_directory 
   Not allowed with option -i

-c comments to write into the log file

-o overwrite
   if the file exists in the backup area overwrite it.

-i backup in place
   creates a file/directory copy in the directory
   from where you are running ${PROGNAME}  
   The name of the save file/directory is created appending
   .YYYYMMDD.HHMM to the original name.
   Not allowed with option -d

-a plain.
   The file/directory to be saved is copied in the 
   Backup Directory without creating the standard YYYYMMDD 
   directory and without creating the target fullpath.

EOF

}
# ........................................................................


# ........................................................................
# makes the copy
# ........................................................................
make_copy()
{

# -----------------------------------------------------------------------
## Francisco
# Test Source existence before cp
if [ -f $TFILE -o -d $TFILE ] 
then
   cmd_status=0
else
   #echo $MSG_NOT_FOUND
   cmd_status=-1
   return $cmd_status
fi
# -----------------------------------------------------------------------

if test $save_over -eq 1
then
  DEST_FILE=$DIR_DESTINO/$TARGET_FILE
else
  DEST_FILE=$DIR_DESTINO/$TARGET_FILE.$SUFFIX
fi

#DEST_FILE=$DIR_DESTINO/$TARGET_FILE.$SUFFIX

## Sean
## If target exists already, save to target.SUFFIX
if [ -f $DIR_DESTINO/$TARGET_FILE ]; then

   # target is a FILE and exists, so add time to target
   echo $MSG_COPYING_FILE "$TFILE ===> $DEST_FILE"

   cp -Rp $TFILE $DEST_FILE 2>/dev/null

elif [ -d $DIR_DESTINO/$TARGET_FILE ]; then
   # target is a Directory and exists, so add time to target
   echo $MSG_COPYING_DIR "$TFILE ===> $DEST_FILE"

   cp -Rp $TFILE $DEST_FILE 2>/dev/null

elif [ -f $TFILE -a -f $DIR_DESTINO/$TARGET_FILE ]; then

   # source is a directory/file construction and target already exists
   echo $MSG_COPYING_FILE "$TFILE ===> $DEST_FILE"

   cp -Rp $TFILE $DEST_FILE 2>/dev/null

else
   echo $MSG_COPYING "$TFILE ===> $DIR_DESTINO/$TARGET_FILE"
   cp -Rp $TFILE $DIR_DESTINO 2>/dev/null
fi

cmd_status=$?
return $cmd_status
}
# ........................................................................

#
# 20001105
ls_2_log()
{

`ls -la $TFILE | $AWKNAME  'BEGIN {RIGHTS=1; USER=3; GROUP=4; F_NAME=9; 
                                                  ARROW=10; LINK=11} 
{

if( ($1 != "total") && (length($1) != 0) && 
    ($F_NAME != ".") && ($F_NAME != "..") ){

  if( index($1,"l") != 0 ){
    printf "%s %s/%s %s %s %s\n", $RIGHTS, $USER, $GROUP, 
                                     $F_NAME, $ARROW, $LINK
  }
  else {
    printf "%s %s/%s %s \n", $RIGHTS, $USER, $GROUP, $F_NAME
  }
}
}' >> $LISTA`

}
# ........................................................................


# ........................................................................
# list_2_log:
# make a listing resembling tar -t
# ........................................................................
list_2_log()
{

`ls -lAR $TFILE | $AWKNAME -v BASE_DIR=$TFILE 'BEGIN {RIGHTS=1; USER=3; GROUP=4; F_NAME=9; 
                                                  ARROW=10; LINK=11} 
{
if (NR == 1 ) {
   prefix=BASE_DIR
}

if( ($1 != "total") && (length($1) != 0) && 
    ($F_NAME != ".") && ($F_NAME != "..") ){

  if( index($1,":") != 0 ){
      no_colon=substr($1,1,(length($1)-1))
      prefix=""
      prefix=no_colon
  }
  else if( index($1,"l") != 0 ){
    printf "%s %s/%s %s/%s %s %s\n", $RIGHTS, $USER, $GROUP, prefix, 
                                     $F_NAME, $ARROW, $LINK
  }
  else if( index($1,"d") != 0 ){
    # Add final / to mark directory
    ### printf "%s/%s/\n", prefix, $F_NAME
  }
  else {
    printf "%s %s/%s %s/%s \n", $RIGHTS, $USER, $GROUP, prefix, $F_NAME
  }
}
}' >> $LISTA`

}
# ........................................................................


# ........................................................................
# strip ./ or ../ if the string begins with one of them.
# ........................................................................
strip_dot_slash()
{
_WHERE=$1

RET_dot_slash=`echo $_WHERE | 
               $AWKNAME '{ sub("^\./","",$0); sub("^\.\./","",$0); printf "%s", $0}'`

export RET_dot_slash 
}
# ........................................................................

# ........................................................................
# strip ./ or ../ if the string begins with one of them.
# ........................................................................
one_slash()
{

RET_one_slash=`echo $_WHERE | 
               $AWKNAME '{ sub("//","/",$0); printf "%s", $0}'`

export RET_one_slash 
}
# ........................................................................





# ........................................................................
my_dirname ()
{
_IN_ARG=`echo $1 | sed 's-/$--'`

RET_my_dirname=` echo $_IN_ARG |
                $AWKNAME 'BEGIN { FS="/" }
                { if( NF == 1 ){ 
                      if ( $1 == ".." ) { print ".." } else { print "." }
                  }
                  else if( NF == 2 ){ if( $1=="") { printf "/" } else { printf"%s",$1 }
                  }
                  else { for(idx=1; idx < (NF-1); idx++){ printf"%s/",$idx }
                         printf"%s", $(NF-1)
                  }
                }'`
export RET_my_dirname 
}
# ........................................................................

# ........................................................................
my_basename()
{
# suppress final / if exists 
_IN_ARG=`echo $1 | sed 's-/$--'`

RET_my_basename=`echo $_IN_ARG | $AWKNAME 'BEGIN {FS="/" } {print $NF}'`

export RET_my_basename 
}
# ........................................................................

# ........................................................................
#
# returns: .   if dir= .   or starts with ./
#          ..  if dir= ..  or starts with ../ 
#
is_relative()
{
#
#echo $1 > /tmp/saveit.2222

RET_is_relative=`echo $1 | $AWKNAME '{ if( index($1,"./")== 1 || $1==".") {
                                print "."
                           }
                           else if( index($1,"../") == 1 || $1=="..") {
                                print ".."
                           }
                           else {
                                print "*"
                           }
                           }'`

export RET_is_relative 
}
# ........................................................................

# ........................................................................
# set common variables
# ........................................................................
set_common_vars()
{

#
# XX="uu" ->OK  , XX ="uu" ->KO 
# 
FECHAHORA=`date '+%Y%m%d-%H%M'`; export FECHAHORA
FECHA=`date '+%Y%m%d'`;          export FECHA
DD_MM_AAAA=`date '+%d-%m-%Y'`;   export DD_MM_AAAA
HH_MM=`date '+%H:%M'`;           export HH_MM
CURRDIR=`pwd`;                   export CURRDIR
HOST=`hostname`;                 export HOST
PROGRAM_NAME=`basename $0`;      export PROGRAM_NAME
}
# ........................................................................


# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#                             FIN FUNCIONES AUXILIARES
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

# ********************************************************************************
#
#                                             MAIN
#
# ********************************************************************************

# set common used vars & messages
set_common_vars
init_messages


# ********************************************************************************
# Configurable options
# *****************************************************************************
# Directory under which all backups will be stored
DIR_ROOT_BACKUP="/Backup.d"; export DIR_ROOT_BACKUP

# Subdirectory format YYYYMMDD-HHMM
TAG_BACKUP_PARANOIC=${FECHAHORA}; export TAG_BACKUP_PARANOIC
# format YYYYMMDD
TAG_BACKUP=$FECHA;                export TAG_BACKUP

# Prefix for log file in backup directory
PREFIJO_LISTA="log";              export PREFIJO_LISTA
# ***** End of configurable options *******************************************



# Detect os and set awk path accordingly
# 20000710 - Detected problems with 'who am i' and mc 
#            Midnight Commander on Linux 6.0 if logged 
#            as root.
#
if [ -z "$MC_CONTROL_FILE" ]
then
# no mc
   WHOAMI="who am i" 
else
   WHOAMI="whoami" 
fi

OSNAME=`uname -s`;export OSNAME
if [ $OSNAME = "Linux" ] ; then
   AWKNAME="awk";export AWKNAME;

elif test $OSNAME = "SunOS" ; then 
   AWKNAME="nawk";export AWKNAME;
else
   # hope for the best
   AWKNAME="awk";export AWKNAME;
fi

# 20000710
COMMENT_LINE=""
HAVE_COMMENT=0
LOGIN_ID=`$WHOAMI | $AWKNAME '{ print $1 }'`; export LOGIN_ID 

# permisos
RWXR_XR_X=0755; export RWXR_XR_X



# suffix to add to identify different versions of the same file
SUFFIX=$HH_MM; export SUFFIX


# Control de argumentos.
# Si la cantidad es 0 => mostrar usage()
#
QTA_ARG=$#; export QTA_ARG
#
# -ne : solo para enteros
#
if test $QTA_ARG -eq 0 
then 
#
  usage " "
  exit 1
fi

# ----------------------------------------------------------------------
# parse command line
OPCIONES="d:c:aio";export OPCIONES

# 20000812
save_plain=0; export save_plain
save_inplace=0; export save_inplace
save_dir=0; export save_dir
save_over=0; export save_over

while getopts $OPCIONES opt
do
  case $opt in

       a) save_plain=1
          ;;

       i) save_inplace=1
          SUFFIX=$FECHAHORA; export SUFFIX
          ;;

       o) save_over=1
          ;;

       d) save_dir=1
          USER_ROOT_BACKUP=$OPTARG
          ;;

       c) COMMENT_LINE=$OPTARG
          HAVE_COMMENT=1
          ;;


       # aqui llega si se le pasa una opcion que no esta declarada
       \?)      #sleep 2 
                usage " "
                exit 2;;
  esac
done

# --------------------------------------------
# 20000812
if test $save_inplace -eq 1
then

  DIR_ROOT_BACKUP=$CURRDIR

  if test $save_dir -eq 1
  then
     echo $MSG_I_D_NOT
     exit
  fi


  if test $save_over -eq 1
  then
     echo $MSG_I_O_NOT
     exit
  fi

fi

if test $save_dir -eq 1
then
  DIR_ROOT_BACKUP=${USER_ROOT_BACKUP}
fi
# --------------------------------------------



# Archivo donde se deja la lista de los archivos que se resguardaron.
ARCHBKPED=$PREFIJO_LISTA"-"$TAG_BACKUP; export ARCHBKPED

# con esto va a parar a $1 el primer argumento luego de todas
# las opciones
#
shift `expr $OPTIND - 1`

# 20000710 - FM
# After processing all options, is there an argument ?
# if not -> K.O.

if test $# -eq 0 
then 
#
  usage " "
  exit 1
fi





# ----------------------------------------------------------------------
# me fijo si el directorio root de backup es relativo o absoluto.
# si es relativo trato de transformarlo en absoluto 
#
#
# returns: .   if dir= .   or starts with ./
#          ..  if dir= ..  or starts with ../ 
#
is_relative $DIR_ROOT_BACKUP

#echo "RETORNO IS REALTIVE"
#echo ""
#echo "${RET_is_relative}"
#exit


# throw away ./ and ../ if present
strip_dot_slash $DIR_ROOT_BACKUP 


case $RET_is_relative in

     .)DIR_ROOT_BACKUP=$CURRDIR"/"$RET_dot_slash;;

     ..) cd ..; DIR_ROOT_BACKUP=`pwd`"/"$RET_dot_slash; cd $CURRDIR;;

     *);;
esac

#echo $DIR_ROOT_BACKUP
#exit
# ----------------------------------------------------------------------


# ----------------------------------------------------------------------
# Ahora tengo que tratar de crear el directorio donde hacer el resguardo
# Controlo si tengo exito
#
# N.B.!!: el uso de mkdir -p tiene las siguienets ventajas:
#         1) crea directorios padres si no existen
#         2) no grita si el directorio que quiero crear existe.
#
# Al resguardar creo toda la estructura de directorio hasta llegar al
# directorio donde reside el archivo a resguardar.
# DEBO IR HASTA DONDE ESTA EL ARCHIVO DESEADO ya que uso pwd para saber
# donde estoy.
#

DIR_BASE_BACKUP=$DIR_ROOT_BACKUP

#debug "debug 1"
#debug  $DIR_BASE_BACKUP

if test $save_inplace -eq 1 
then
    DIR_BASE_BACKUP=$CURRDIR
elif test $save_plain -eq 1
then
    DIR_BASE_BACKUP=$DIR_ROOT_BACKUP
else
    DIR_BASE_BACKUP=$DIR_ROOT_BACKUP"/"$TAG_BACKUP
fi
export DIR_BASE_BACKUP



# Por ahora todo O.K.
#
# Armo el nombre del archivo donde dejar la lista de los que salve
LISTA=$DIR_ROOT_BACKUP"/"$ARCHBKPED; export LISTA

debug $LISTA
 

# check for backup dir and create if necessary
if [ ! -d ${DIR_ROOT_BACKUP} ]; then
   mkdir -m $RWXR_XR_X -p ${DIR_ROOT_BACKUP} 2> /dev/null
fi

# Controlo si lo pudo crear analizando el retorno ($?)
if [ $? -ne 0 ];then
   echo "Problems during creation of directory "$DIR_ROOT_BACKUP
   exit 3
fi


# ------------------------------------------------------------
cat >> $LISTA << EOF
--------------------------------------------------------------
Backup base directory $DIR_ROOT_BACKUP
Backup requested by   $LOGIN_ID 
Date (dd/mm/aaaa)     $DD_MM_AAAA
Time                  $HH_MM
EOF

# 20000710 - FM
if [ $HAVE_COMMENT -eq 1 ] 
then
   echo "   "          >> $LISTA
   echo "Details               "$COMMENT_LINE  >> $LISTA
fi
# ------------------------------------------------------------



# Ahora copio los archivos
#
# variables importantes de shell (Kernighan & Pike -  The UNIX Programming Environment)
#
# $#  cantidad de argumentos (sin contar el nombre del programa)
# $*  la lista de todos los argumentos
# $-  las opciones suministradas
# $?  valor del ultimo comando ejecutado  


for arg in $*
do
  # analizo para ver si en arg tiene directorio (distinto al ./)
  # esto sirve para hacer el backup sin tener que pararme en el
  # directorio que deseo.
  #
  # N.B.: dirname pepe     -> .
  #       dirname ./pepe   -> .
  #
  my_dirname $arg
  LISTA_DIR_ORIGEN=$RET_my_dirname; export LISTA_DIR_ORIGEN

  my_basename $arg
  TARGET_FILE=$RET_my_basename; export  TARGET_FILE


  # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  # 19991019-02
  # para que funcione algo como save /etc/rc?.d/AKA, que si no no anda 

  for DIR_ORIGEN in $LISTA_DIR_ORIGEN
  do

    # 20000729  ---> INICIO 
    #
    if test $DIR_ORIGEN = "."; then
       DIR_ORIGEN=$CURRDIR

    elif test $DIR_ORIGEN = ".."; then
       cd ..
       DIR_ORIGEN=`pwd`
       cd $CURRDIR
    fi



    if test $save_inplace -eq 1
    then
       DIR_DESTINO=${DIR_BASE_BACKUP} 
    else
       DIR_DESTINO=${DIR_BASE_BACKUP}${DIR_ORIGEN} 
    fi
    TFILE=$DIR_ORIGEN"/"$TARGET_FILE

    #debug DIR_BASE_BACKUP  
    #debug $DIR_BASE_BACKUP  
    #debug DIR_DESTINO
    #debug $DIR_DESTINO
    #debug TARGET_FILE
    #debug $TARGET_FILE


    if [ ! -d ${DIR_DESTINO} ]; then 
       mkdir -m $RWXR_XR_X -p $DIR_DESTINO 2> /dev/null
    fi

  # Controlo si lo pudo crear analizando el retorno ($?)
    status=$?; export status

    if [ $status -ne 0 ];then
       MENSAJE="Problems creating directory "$DIR_DESTINO
       echo $MENSAJE
       echo $MENSAJE >> $LISTA
    else
       # O.K. -> salvo
       # 0 -> exito
       make_copy
       status=$?

       #debug "resultado de make_copy"
       #debug status


       if [ $status -eq 0 ];then
         if [ -f $arg ]; then
             TFILE=$DIR_ORIGEN"/"$TARGET_FILE
             #MY=`find $DIR_ORIGEN -name $TARGET_FILE -exec ls -la {} \;`
             #MY2=`echo $MY | $AWKNAME -v TF=$TFILE '{pos=index($0, TF);
             #                ixs=substr($0, pos); print ixs}'`
             #echo $MY2     >> $LISTA



             # 20001105
             ls_2_log 

         else
            echo $MSG_DIR_LIST $TFILE >> $LISTA
            list_2_log 
         fi
       else
         if [ ! -d $arg ]; then

            MSG_AUX="$MSG_CANT_COPY $DIR_ORIGEN/$TARGET_FILE"
            echo $MSG_AUX >> $LISTA 
            echo $MSG_AUX 

         else
            MSG_AUX="$MSG_CANT_COPY $arg"
            echo $MSG_AUX >> $LISTA
            echo $MSG_AUX 
         fi
       fi
       # status del cp
    fi
    done
    # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
done

# end of saveit
