#!/bin/ksh
#______________________________________________________________________________________________
#
# "rzync" is a simple ksh script using rsync to synchronize a file/directory with a remote one 
# using the SSH channel. It enables user to define different configurations and prepare SSH keys, 
# and also supports batch mode and logging option to trace the output.
#
#______________________________________________________________________________________________

sshpath=`which ssh`
sshkeygen=`which ssh-keygen`
rsyncpath=`which rsync`
rm=/bin/rm
mkdir=/bin/mkdir
ls=/bin/ls
sh=/bin/sh
touch=touch
hostname=/bin/hostname
cut=`which cut`
sed=`which sed`
chmod=/bin/chmod
date="/bin/date  +%Y%m%d%H%M"
cat=/bin/cat
tee=`which tee`

#______________________________________________________________________________________________

Rhost=mbalma1-2.lsu.edu                 # remote host
Lhost=`$hostname | $cut -d'.' -f1`      # local hostname
Ruser=$USER                             # remote user
Luser=$USER                             # local user
Local_path=/tmp/s/                      # local path 
Remote_path=/tmp/d                      # remote path
quick=false                             # quick mode (no connection check, no locking ) !!!
                                        # the option "quick" ignores all controls
                                        # lock, unlock, check_connections will return immediately
                                        
batchon=false                           # batch mode 
                                        # if quick==true, also bacth=true 
logoutput=true                          # logging ?

#______________________________________________________________________________________________

timethreshold=2
progname="rzync"
confdir="$HOME/."$progname              # configuration directory
confile=$progname                       # configuration file
configfile="$confdir/$confile"

IdentityFile="$HOME/.ssh/id_$progname"  # SSH identityFile

ExcludeFile="$confdir/$confile""-exclude-from"  # rsync exclude/include files
IncludeFile="$confdir/$confile""-include-from"
# example: rzync-exclude-from
#######.*
#######Desktop/*
#######Documents/*
#######Library
#######Movies/*
#######Music/*
#######Pictures/*
#######Public/*
#######Send Registration/*
#######Sites/*
#######inMac/*
#######config/*
# 

Timeout=5600        # rsync timeout --timeout

logfile=$confdir/log/$progname"_"`$date`".log"  # LOG FILE

#______________________________________________________________________________________________

dsleep=7        # time to wait if there is a lock
dsleepinc=5     # waiting time increment by dsleepinc

# list of all provided functions
cmds_all="update lock unlock help readconfig configure readargs help check_conn createkeys checktime checkpaths put get setlockfiles printconfig"

# allowed_cmds=$cmds_all
# options - "rzync" 
allowed_cmds="configure unlock printconfig help createkeys get put update"  


Lpath=""        # rsync paths
Rpath=""        

configuring=false

#______________________________________________________________________________________________
# set lock files

setlockfiles(){
    #local_lock_file="/tmp"/"$Luser"-locallock_file
    #remote_lock_file="/tmp"/"$Ruser"-remotelock_file
    
    slpath=`echo $Local_path | $sed 's/\/$//' | sed 's/\//_/g'` 
    srpath=`echo $Remote_path | $sed 's/\/$//' | sed 's/\//_/g'`    

    local_lock_file="/tmp"/"$slpath"_LOCK
    remote_lock_file="/tmp"/"$srpath"_LOCK

}

#______________________________________________________________________________________________
# perform lock operation
lock(){

    if $quick ; then 
      return 
    fi

    echo "lock :" $@
    check_conn      # check SSH connection
    setlockfiles
                                                                                                 
    dsleeptmp=$dsleep
                                                                                                 
    while :
    do
        if ! ( $ls $local_lock_file >/dev/null 2>&1  )
        then
           if  ! ( $SSH $Rhost -l $Ruser $sh -c  \'$ls $remote_lock_file  \' \
           >/dev/null 2>&1 )
           then
                $touch $local_lock_file
                if (( $? != 0 )) ; then
                    echo "[$progname] Error : can not create local lock file $local_lock_file) ..."
                    exit 4
                    fi
                                                                                                
                $SSH $Rhost -l $Ruser $sh -c  \' $touch $remote_lock_file \'  \
                >/dev/null 2>&1
                if (( $? != 0 )) ; then
                    echo "[$progname] Error : can not create remote lock file ($Ruser@$Rhost:$remote_lock_file) ..."
                    exit 4
                fi
                break
           fi
        fi
                                                                                                 
          sleep $dsleeptmp
          dsleeptmp=$(( $dsleeptmp + $dsleepinc ))
        done


    echo "... lock files : ($local_lock_file) and ($Rhost:$remote_lock_file)"
}

#______________________________________________________________________________________________
# unlock operation

unlock(){

      if $quick ; then 
          return 
      fi

        echo "unlock :" $@
        
        check_conn  # check connection
        
        setlockfiles                                                                                         
        
        $rm -f $local_lock_file
        if (( $? != 0 )) ; then
            echo "[$progname] Error: can not remove local lock file ($local_lock_file) ..."
            exit 4
        fi
                                                                                                 
        $SSH $Rhost -l $Ruser $sh -c  \' $rm -f $remote_lock_file \' \
        >/dev/null 2>&1
        if (( $? != 0 )) ; then
            echo "[$progname] Error: can not remove remote lock file ($Ruser@$Rhost:$remote_lock_file) ..."
            exit 4
        fi
}

#______________________________________________________________________________________________
# update operation
# does not delete any file
# rsync local to remote
# rsync remote to local 

update(){
        echo "rsync : update "
        
        check_conn
        checkpaths

        
        RSYNC="$rsyncpath -auxvz  --timeout=$Timeout"
        NRSYNC="$rsyncpath -nauxvz   --timeout=$Timeout" #  --dry-run show what would have been transferred

                
        # if exclude/include lists exits, set RSYNC command accordingly
        
        if [ -r $ExcludeFile ] ; then 
                 RSYNC=$RSYNC" --exclude-from=$ExcludeFile"  
                     NRSYNC=$NRSYNC" --exclude-from=$ExcludeFile"
        fi
        if [ -r $IncludeFile ] ; then 
                 RSYNC=$RSYNC" --exclude-from=$IncludeFile"  
                     NRSYNC=$NRSYNC" --exclude-from=$IncludeFile"
        fi

        # if batch mode, does not show what will be transfered 
        # else shows what will be transfered
         
        if ! $batchon ; then 
            tmp="" ; echo
            $NRSYNC -e "$SSH" $Lpath $Ruser@$Rhost:$Rpath 
            echo $RSYNC -e "$SSH"  $Lpath $Ruser@$Rhost:$Rpath
            echo "Continue? [y/n]" ;  read tmp
            if [ "x$tmp" != "xy" ]; then 
            exit 0
            fi
        
            echo
            $NRSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
            echo $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
            echo "Continue? [y/n]" ; read tmp
            if [ "x$tmp" != "xy" ]; then
                exit 0
            fi

        fi
        
        lock 
        
        echo ; echo "Running :"
        echo $RSYNC -e "$SSH"  $Lpath $Ruser@$Rhost:$Rpath
        $RSYNC -e "$SSH" $Lpath $Ruser@$Rhost:$Rpath 
# >/dev/null 2>&1
        if (( $? != 0 )) ; then
                echo "[$progname] Error: rsync  FAILED ..."
                unlock
                exit 3
        fi

        echo ; echo "Running :"
        echo $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
        $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath 
# >/dev/null 2>&1
        if (( $? != 0 )) ; then
                echo "[$progname] Error : rsync FAILED ..."
                unlock
                exit 3
        fi
        
        unlock
                                                                                                 
}

#______________________________________________________________________________________________
# rsync from local to remote
# with --delete option

put(){
        echo "rsync : put "
        check_conn
        checkpaths

        RSYNC="$rsyncpath -axvz  --delete --timeout=$Timeout"
        NRSYNC="$rsyncpath -naxvz  --delete --timeout=$Timeout"
                
        if [ -r $ExcludeFile ] ; then 
                 RSYNC=$RSYNC" --exclude-from=$ExcludeFile"  
                 NRSYNC=$NRSYNC" --exclude-from=$ExcludeFile"
        fi
        if [ -r $IncludeFile ] ; then 
                 RSYNC=$RSYNC" --exclude-from=$IncludeFile"  
                 NRSYNC=$NRSYNC" --exclude-from=$IncludeFile"
        fi

       if ! $batchon ; then 
            tmp="" ; echo
            $NRSYNC -e "$SSH" $Lpath $Ruser@$Rhost:$Rpath 
            echo $RSYNC -e "$SSH"  $Lpath $Ruser@$Rhost:$Rpath
            echo "Continue? [y/n]" ;  read tmp
            if [ "x$tmp" != "xy" ]; then 
                exit 0
            fi
        fi
        
        lock
        
        echo ; echo "Running :"
        echo $RSYNC -e "$SSH"  $Lpath $Ruser@$Rhost:$Rpath
        $RSYNC -e "$SSH" $Lpath $Ruser@$Rhost:$Rpath 
# >/dev/null 2>&1
        if (( $? != 0 )) ; then
                echo "[$progname] Error: rsync  FAILED ..."
                unlock
                exit 3
        fi
    
    unlock
}

#______________________________________________________________________________________________
# rsync from remote to local
# with --delete option

get(){
        echo "rsync : get "
        check_conn
        checkpaths

        RSYNC="$rsyncpath -axvz  --delete --timeout=$Timeout"
        NRSYNC="$rsyncpath -naxvz  --delete --timeout=$Timeout"

        if [ -r $ExcludeFile ] ; then 
           RSYNC=$RSYNC" --exclude-from=$ExcludeFile"  
           NRSYNC=$NRSYNC" --exclude-from=$ExcludeFile" 
        fi
        if [ -r $IncludeFile ] ; then
           RSYNC=$RSYNC" --include-from=$IncludeFile"  
           NRSYNC=$NRSYNC" --include-from=$IncludeFile" 
        fi
                                                                                                 

        if ! $batchon ; then
            tmp="" ;    echo
            echo $NRSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
            $NRSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath 
            echo $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
            echo "Continue? [y/n]"
            read tmp
            if [ "x$tmp" != "xy" ]; then
                exit 0
            fi
        fi
                                                                                                 
        lock 
        
        echo ; echo "Running :"
        echo $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath
        $RSYNC -e "$SSH" $Ruser@$Rhost:$Rpath $Lpath 
# >/dev/null 2>&1
        if (( $? != 0 )) ; then
                echo "[$progname] Error : rsync FAILED ..."
                unlock
                exit 3
        fi
    
        unlock
                                                                                                 
}

#______________________________________________________________________________________________

checkpaths(){
         Lpath=$Local_path
         Rpath=$Remote_path
                                                                                                 
        if [ "x$Lpath" = "x" ] ; then
                echo "Error : no source is specified!"
                exit 4
        fi
        if [ "x$Rpath" = "x" ] ; then
                echo "Error : no destination is  specified!"
                exit 4
        fi
        if [ -d $Lpath ] ; then
                Lpath="$Lpath"/
        fi
                                                                                                 
        $SSH $Ruser@$Rhost $sh -c \' [ -d $Rpath ]  \' >/dev/null 2>&1 
        if (( $? == 0 )) ; then
                Rpath="$Rpath"/
        fi
}

#______________________________________________________________________________________________

readconfig(){
    
    $mkdir -p $confdir
    
    if (( $? != 0 )) ; then
         echo "[$progname] Error : can not create configuration directory ($confdir)"
         exit 2
    fi
    if $logoutput ; then 
        $mkdir -p $confdir/log
        if (( $? != 0 )) ; then 
            echo "[$progname] Error : can not create log directory ($confdir/log)"
            exit 2
        fi
    fi
    #echo "using configuration file ($configfile)"
    [ -r $configfile ] &&  . $configfile
}

#______________________________________________________________________________________________


printconfig(){
    echo
    echo "printing configuration ($configfile):"
    echo "     local path      :"$Local_path
    echo "     remote path    :"$Remote_path
    echo "     remore host     :"$Rhost
    echo "     local host      :"$Lhost
    echo "     remote user     :"$Ruser
    echo "     local user      :"$Luser
    echo "     rsync exclude file      :"$ExcludeFile
    echo "     rsync include file      :"$IncludeFile
    echo "     quick option    :"$quick
    echo "     batch mode      :"$batchon
    echo "     log output      :"$logoutput
    echo
}

#______________________________________________________________________________________________

configure(){

    configuring=true

    if $batchon ; then
        echo "[$progname] Error : command (configure) can not run on batch mode ..."
        exit 1
    fi

    tmp="";     echo "generating configuration file for $progname ..."

    echo -n "   remote path      [ $Remote_path ] :"
    read tmp
    [ "x$tmp" != "x" ] && Remote_path=$tmp
    echo -n "    local path      [ $Local_path ] :"
    read tmp
    [ "x$tmp" != "x" ] && Local_path=$tmp
    echo -n "    remote server   [ $Rhost ] :"
        read tmp
        [ "x$tmp" != "x" ] && Rhost=$tmp
    echo -n "    remote user     [ $Ruser ] :"
        read tmp
        [ "x$tmp" != "x" ] && Ruser=$tmp
    echo -n "    file in which exclude patterns are listed \
                [ $ExcludeFile ] :"
        read tmp
        [ "x$tmp" != "x" ] && ExcludeFile=$tmp
    echo -n "    file in which include-from patterns are listed \
                [ $IncludeFile ] :"
        read tmp
        [ "x$tmp" != "x" ] && IncludeFile=$tmp
    

    echo "writing into ($configfile) ...."

    ( echo "Local_path="$Local_path
      echo "Remote_path="$Remote_path
      echo "Rhost="$Rhost
      echo "Lhost="$Lhost
      echo "Ruser="$Ruser
      echo "Luser="$Luser 
      echo "ExcludeFile="$ExcludeFile
      echo "IncludeFile="$IncludeFile ) > $configfile
    
    touch $ExcludeFile
    touch $IncludeFile

    check_conn

    configuring=false
}
#______________________________________________________________________________________________

createkeys(){

    if $batchon ; then
                echo "[$progname] Error : command (createkeys) can not run on batch mode ..."
                exit 1
        fi

    tmp="";    echo "Configure SSH keys for automatic authentication."
    echo -n "Continue ? [y/n]"
    read tmp
    if [ "x$tmp" != "xy" ] ; then      
            return
    fi
    $rm -f $IdentityFile "$IdentityFile".pub
    $sshkeygen -t dsa -N '' -f "$IdentityFile"
    if (( $? != 0 )) ; then 
        echo "[$progname] Error : can not generate SSH keys ..."
        exit 2
    fi  

    echo "please enter password for $Ruser@$Rhost"
    ( $cat "$IdentityFile".pub  | $sshpath -o IdentityFile=$IdentityFile  $Ruser@$Rhost $sh -c \' $mkdir -p .ssh \; $chmod 0700 .ssh \; $touch .ssh/authorized_keys \; $chmod 0600 .ssh/authorized_keys \; $cat >> .ssh/authorized_keys \' )

    echo "... checking SSH connection (IdentityFile=$IdentityFile)"
    $sshpath -o IdentityFile=$IdentityFile  $Ruser@$Rhost true >/dev/null 2>&1
    if (( $? != 0 )) ; then
        echo "[$progname] Error : unable to connect to remote host ..."
        exit 2
    fi
    SSH="$sshpath -o IdentityFile=$IdentityFile"
    
    checktime
}

#______________________________________________________________________________________________

checktime(){
                                                                                                 
        echo "checking time synchronization ..."
        localdate=`$date`
        remotedate=`$SSH $Ruser@$Rhost $sh -c \'$date\' `
        if (( $? != 0 )) ; then
                echo "[$progname] Error : can not check time on remote server ($Rhost) ..."
                exit 2
        fi
                                                                                                 
        diffstr=$(( $localdate - $remotedate ))
        diff=`echo $diffstr | $sed 's/-//g'`
                                                                                                 
        if (( $diff > $timethreshold )) ; then
                echo "[$progname] Error :  time not synchronized ..."
                exit 2
        fi
}

#______________________________________________________________________________________________


check_conn(){
    
    if $quick ; then
        return
    fi
    
    SSH="$sshpath  -o BatchMode=yes "

    echo  "checking SSH connection ..."

    $SSH $Ruser@$Rhost true  >/dev/null 2>&1
    if (( $? == 0 )) ; then
       
	checktime
       	
	if $configuring ; then
		echo "SSH=\" $SSH \" " >> $configfile
	fi

	return
    fi

    SSH="$SSH -o IdentityFile=$IdentityFile "

    $SSH $Ruser@$Rhost true >/dev/null 2>&1
    if (( $? == 0 )) ; then
        
	checktime
	
	if $configuring ; then
		echo "SSH=\" $SSH \" " >> $configfile
	fi

        return
    fi

    echo " FAILED"
    if ! $batchon ; then 
        createkeys
    else
       echo "[$progname] Error : can not connect to remote host ($Rhost) ..."
       exit 2
    fi

    if $configuring ; then 
	echo "SSH=\" $SSH \" " >> $configfile
    fi

}

#______________________________________________________________________________________________

readargs(){
    set -A arguments -- "$@"
    i=1
    while (( $i <  ${#arguments[@]} ))
    do
        if [ ${arguments[i]} = "--quick" ]; then 
            quick=true
            batchon=true
        else if [ ${arguments[i]} = "--batchmode" ]; then 
            batchon=true
        else if [ ${arguments[i]} = "--config" ]; then 
            i=$(( $i + 1 ))
                configfile="$confdir/$confile-"${arguments[i]}
        else if [ ${arguments[i]} = "--nologging" ]; then
                        logoutput=false
        else 
            echo "[$progname] Error : invalid argument for (${arguments[0]}) ... "
            exit 1
        fi
        fi
        fi
	fi
        i=$(( $i + 1 ))
    done
}

#______________________________________________________________________________________________

help(){
    echo $progname" :"
    echo  "    commands    : "$allowed_cmds
    echo  "    options    : --quick, --batchmode, --config <conf_name>, --nologging"
    echo
    echo "ex : $progname --update --nologging --config test1 --batchmode"
    echo 
    echo "commands :
    
        configure:      configure rzync. writes the following variables to configuration file.
                        remote server, remote path, remote user, 
                        local path, exclude file, include/exclude files
                
        unlock:         remove lock files. if lock files exist, it will wait till they are removed.
        
        printconfig :       print configuration information.
        
        createkeys:          create SSH keys in order to connect without a password
        
        update:              rsync from local to remote AND rsync from remote to local. without --delete option
        
        get:                 rsync from remote to local . with --delete option.
        
        put:                 rsync from local to remote. with --delete option.
        
    
    options:
    
	    quick:                 ignores all controls. lock, unlock, check_connections will return immediately.
    
	    batchmode:              if batch mode, does not show what will be transfered, else first shows what will be transfered
        
            nologging:      	    disable logging
        
            confile <filename>:     use the given configuration file instead of the default one
        
        
    "
}

#______________________________________________________________________________________________

main() {

   set -A args -- "$@"
   cmd=${args[0]};  args[0]=" " 
   cmd=`echo $cmd | $sed 's/-//g'`

   if echo $allowed_cmds | grep $cmd >/dev/null 2>&1 
   then
    readargs "$cmd ${args[@]}"
    readconfig ${args[@]}

    if $logoutput ; then
        ( printconfig ${args[@]}
              $cmd ${args[@]} ) | $tee -a  $logfile 2>&1
    else
	( printconfig ${args[@]}
              $cmd ${args[@]} ) 
     fi
   
   else 
    if [ "x$cmd" != "x" ] ; then 
    echo "[$progname] Error : command \"$cmd\" not found"
        exit 1
    else 
      $cmd help
    fi
   fi 
}

#______________________________________________________________________________________________

main $@

