#!/bin/bash

# Begin dd_rhelp

# Copyright (C) 2007 LAB Valentin <vaab@free.fr>
#  
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#  
# 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 General Public License for more details.
#  
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#  

#!- Library include
function include() {

case "$1" in


"common")

# Begin libcommon.sh

include color


function gnu_options()
{
    # Quite basic
    for __i in $* ;do
	if [ "$__i"  = '--help' ];then
	    print_help
	    exit 0;
	fi
	if [ "$__i"  = '--version' ];then
	    print_version
	    exit 0;
	fi

    done
};

function print_version()
{
    echo "$exname ver. $version";
};

function print_help()
{
    print_version
    echo "$help";
};

function print_exit()
{
    echo $@;
    exit 1;
};

function print_syntax_error()
{
    [ "$*" ] ||	print_syntax_error "$FUNCNAME: no arguments" 
    print_exit "${ERROR}script error:${NORMAL} $@" >&2 
};

function print_syntax_warning()
{
    [ "$*" ] || print_syntax_error "$FUNCNAME: no arguments."
    [ "$exname" ] || print_syntax_error "$FUNCNAME: 'exname' var is null or not defined.";
    echo "$exname: ${WARNING}script warning:${NORMAL} $@" >&2;
};

function print_error() {
    [ "$*" ] || print_syntax_warning "$FUNCNAME: no arguments." 
    [ "$exname" ] || print_exit "$FUNCNAME: 'exname' var is null or not defined." >&2;
    print_exit "$exname: ${ERROR}error:${NORMAL} $@" >&2
};

function die() {
    [ "$*" ] || print_syntax_warning "$FUNCNAME: no arguments." 
    [ "$exname" ] || print_exit "$FUNCNAME: 'exname' var is null or not defined." >&2;
    print_exit "$exname: ${ERROR}error:${NORMAL} $@" >&2
};

function print_warning()
{
    [ "$*" ] || print_syntax_warning "$FUNCNAME: no arguments." 
    [ "$exname" ] || print_syntax_error "$FUNCNAME: 'exname' var is null or not defined.";
    echo "$exname: ${WARNING}warning:${NORMAL} $@" >&2
};

function print_usage()
{
    [ "$usage" ] || print_error "$FUNCNAME: 'usage' variable is not set or empty."
    echo "usage: $usage"

#    if [ "$_options" != "" ]
#    then	
	

#    fi
}

function invert_list()
{
    newlist=" "
    for i in $*
    do
      newlist=" $i${newlist}"
    done
    echo $newlist;
};

function get_path()
{
    type="$(type -t "$1")"
    case $type in 
	("file")
	    type -p "$1"
	    return 0
	    ;;
	("function" | "builtin" )
	    echo "$1"
	    return 0
	    ;;
    esac
    return 1

};


function depends() {


    tr=$(get_path "tr")
    if test -z "$tr"; then 
	print_error "dependency check : couldn't find '$i' command."
    fi

    for i in $@
    do
# 	if ! type $i > /dev/null 2>&1
# 	then
# 	   print_error "dependency check : couldn't find '$i' command."
# 	else
# 	    path=$(get_path $i)
# 	    echo "'$i' --> '$?:$path'"
	    
# 	    if ! test -z "$path" ; then
# 		export $i=$path
# 	    fi
# 	fi

      if ! path=$(get_path $i);then
	  if [ "$(echo $i | "$tr" '_' '-')" != "$i" ]; then
	     depends "$(echo $i | "$tr" '_' '-')";
	  else   
	     print_error "dependency check : couldn't find '$i' command."
	  fi
      else
	  if ! test -z "$path" ; then
	      export "$(echo $i | "$tr" '-' '_')"=$path
	  fi
      fi
      
    done
}

function require()
{
    for i in $@
    do

      if ! path=$(get_path $i);then
	   return 1;
      else
	  if ! test -z "$path" ; then
	      export $i=$path
	  fi
      fi
      
    done
}

function check()
{
    for i in $@
    do
      [ "$(type -t "check_$i")" == "function" ] &&
      "check_$i" && continue
      
      print_error "dependency check : couldn't find 'check_$i' function."
    done
}

function check_ls_timestyle()
{
    depends ls 

    #  Checking a special option of "ls"
    #     -ls does accept the --time-style ?

    if ! "$ls" --time-style=+date:%Y%m%d%H%M.%S / >/dev/null 2>&1; then
	print_error "'$ls' doesn't support the --time-style argument, please upgrade your coreutils tools."
    fi
};



function print_octets ()
{
    [ "$*" ] || print_syntax_error "$FUNCNAME: no arguments.";
    [ "$2" ] && print_syntax_error "$FUNCNAME: too much arguments.";

    [ "$( echo "$1 < 1024" | bc )" == "1" ] && { echo -n "$1 octets"; return 0;}

    kbytes=$(echo "$1 / 1024" | bc );
    [ "$( echo "$kbytes < 1024" | bc)" == "1" ] && { echo -n "$kbytes Ko" ; return 0; }

    mbytes=$(echo "$kbytes / 1024" | bc );
    [ "$( echo "$mbytes < 1024" | bc)" == "1" ] && { echo -n "$mbytes Mo" ; return 0; }
    gbytes=$(echo "$mbytes / 1024" | bc );
    [ "$( echo "$gbytes < 1024" | bc )" == "1" ] && { echo -n "$gbytes Go" ; return 0; }
    tbytes=$(echo "$gbytes / 1024" | bc );
    echo -n "$gbytes To"

}


function is_set() {
    
    for i in $*; do
	val=$(eval echo -n \$$i)
#	echo "valeur '$i' : '$val'"
	if test -z "$val"; then 
	    print_error "Variable \$$i is not set."
	fi
    done
    return 0

};

function checkfile () {

    [ "$*" ] || print_syntax_error "$FUNCNAME: no arguments.";
    [ "$3" ] && print_syntax_error "$FUNCNAME: too much arguments.";
    
    separate=$(echo "$1" | "$sed" 's/\(.\)/ \1/g')

    for i in $(echo $1 | "$sed" 's/\(.\)/ \1/g'); do
	case "$i" in
		"")
			:
		;;
                "e")
                        if ! [ -e "$2" ]
			then 
	                        echo "'$2' is not found."
        	                return 1
			fi;;
		"f")
			if ! [ -f "$2" ]
			then
				echo "'$2' is not a regular file."
				return 1
			fi;;
		"d")
			if ! [ -d "$2" ]
			then
				echo "'$2' is not a directory."
				return 1
			fi;;
		"r")
	                if ! [ -r "$2" ]
			then
	                        echo "'$2' is not readable."
	                        return 1
			fi;;
                "w")
			if ! [ -w "$2" ]
			then
	                        echo "'$2' is not writable."
	                        return 1
			fi;;
                "x")
                        if ! [ -x "$2" ]
			then
	                        echo "'$2' is not executable/openable."
	                        return 1
			fi;;
		"l")
			if ! [ -L "$2" ]
			then
				echo "'$2' is not a symbolic link."
				return 1
			fi;;
	esac
    done

    return 0;
};


function matches()
{
    [ "$*" ] || print_syntax_error "$FUNCNAME: no arguments.";
    [ "$3" ] && print_syntax_error "$FUNCNAME: too much arguments.";
    
#    echo "$2" | grep "^\/.*\/\$" >/dev/null 2>&1 || print_syntax_error "$FUNCNAME: second argument should begin and end with '/'."

#     rest="$(echo "$1" | sed "s${2}X/g")"
    
#     if [ "$rest" != "X" ] ;then
# 	return 1
#     else
# 	return 0
#     fi
     echo "$1" | "$grep" "^$2\$" >/dev/null 2>&1
}


# TODO : make a better search configurability with check-file style option.
function find_conf_file()
{
    [ "$*" ] || print_syntax_error "$FUNCNAME: no arguments.";
    [ "$2" ] && print_syntax_error "$FUNCNAME: too much arguments.";

    poss="~/.$1 ~/.vlfs/$1 "

    [ -d "$VLFS_CONF_DIR" ] && poss="$VLFS_CONF_DIR/$1 $poss"
    [ -d "$VLFS_PREFIX" ] && poss="$VLFS_PREFIX/etc/$1 $poss"

    poss="/etc/$1 /usr/etc/$1 /usr/local/etc/$1 "

    for i in $poss ;do
	n=$(eval echo "$i")
	if [ -f "$n" -a -r "$n" ]; then
	    echo "$n"
	    return 0
	fi
    done

    # return first choice
    for i in $poss ;do
	n=$(eval echo "$i")
	echo "$n"
	return 1
    done
}

function str_is_uint()
{
    matches "$1" "[0-9]\+"
}

function str_is_sint()
{
    matches "$1" '\(-\|+\)\?[0-9]\+'
}

function str_is_sreal()
{
    matches "$1" '\(-\|+\)\?[0-9]\+\(\.[0-9]\+\)\?'
}

function str_is_ipv4()
{
    # TODO : this is not perfect could match = 929.267829872.2.129782
    matches "$1" '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+'
}


function sed_compat {

    if test "$BSD_SED"; then 
        ## BSD sed
	"$cat" - | "$sed" -E "$1"
	return 0;
    fi
    
    ## GNU sed
    "$cat" - | "$sed" -r "$1"
    return 0;

}

function md5_compat {

    if test "$BSD_SED"; then
        ## BSD sed
	depends md5
	"$cat" - | "$md5"
	return 0;
    fi

    ## GNU sed
    depends md5sum
    "$cat" - | "$md5sum" | "$cut" -c -32
    return 0;

}


function get_perm() {
    if test "$BSD_STAT"; then
	"$stat" -f %OLp "$1"
	return 0;
    fi

    "$stat" "$1" -c %a
}

function check_perm()
{
    [ "$(get_perm "$1")" == "$2" ]
}

function same_contents() {

    "$diff" "$1" "$2" >/dev/null 2>&1
}

function is_set() {
    "$print_env" "$1" >/dev/null 2>&1
}

function pause() {
    read -sn1 key
}


depends basename

[ -n "$exname" ] || exname=$("$basename" $0)
[ -n "$fullexname" ] || fullexname=$0

depends grep sed stat cut diff df


if ! is_set BSD_SED; then
    "$sed" --version > /dev/null 2>&1 || BSD_SED=1
fi

if ! is_set BSD_STAT; then
    "$stat" --version > /dev/null 2>&1 || BSD_STAT=1
fi

if ! is_set BSD_DF; then
    "$df" --version > /dev/null 2>&1 || BSD_DF=1
fi

    

# End libcommon.sh

;;

"color")

#!/bin/bash
# Begin libcolor.sh

# If COLUMNS hasn't been set yet (bash sets it but not when called as
# sh), do it ourself

if [ -z "$COLUMNS" ]
then
    # Get the console device if we don't have it already
    # This is ok by the FHS as there is a fallback if
    # /usr/bin/tty isn't available, for example at bootup.

    test -x /usr/bin/tty && CONSOLE=`/usr/bin/tty`
    test -z "$CONSOLE" && CONSOLE=/dev/console

    # Get the console size (rows columns)

    stty size > /dev/null 2>&1
    if [ "$?" == 0 ]
    then
	[ "$CONSOLE" == "/dev/console" ] && SIZE=$(stty size < $CONSOLE) \
                                         || SIZE=$(stty size)

        # Strip off the rows leaving the columns

        COLUMNS=${SIZE#*\ }
	LINES=${SIZE%\ *}  
    else
	COLUMNS=80
	LINES=24
    fi

fi

SIZE_LINE=$COLUMNS
SIZE_ELT=$[$COLUMNS - 30 - 4]
SIZE_INFO=20


COL=$[$COLUMNS - 10]
WCOL=$[$COLUMNS - 30]
SCOL=$[$COLUMNS - 4]
LCOL=$[$COLUMNS - 1]


function ascii_color()
{
    if [ "$1" != "no" ]; then

	SET_COL=$(echo -en "\033[${COL}G")
	SET_SCOL=$(echo -en "\033[${SCOL}G")
	SET_WCOL=$(echo -en "\033[${WCOL}G")
	SET_LCOL=$(echo -en "\033[${LCOL}G")
	
	SET_BEGINCOL=$(echo -en "\033[0G")
	
	UP=$(echo -en "\033[1A")
	DOWN=$(echo -en "\033[1B")
	LEFT=$(echo -en "\033[1D")
	RIGHT=$(echo -en "\033[1C")
	
	SAVE=$(echo -en "\0337")
	RESTORE=$(echo -en "\0338")
	
	
	NORMAL=$(echo -en "\033[0;37m")
	RED=$(echo -en "\033[1;31m")
	GREEN=$(echo -en "\033[1;32m")
	YELLOW=$(echo -en "\033[1;33m")
	BLUE=$(echo -en "\033[1;34m")
	GRAY=$(echo -en "\033[1;30m")
	WHITE=$(echo -en "\033[1;37m")
	
	SUCCESS=$GREEN
	WARNING=$YELLOW
	FAILURE=$RED
	NOOP=$BLUE
	ON=$SUCCESS
	OFF=$FAILURE
	ERROR=$FAILURE
	
	ascii_color="yes"
    else

	SET_COL=
	SET_SCOL=
	SET_WCOL=
	SET_LCOL=
	
	SET_BEGINCOL=
	
	
	
	NORMAL=
	RED=
	GREEN=
	YELLOW=
	BLUE=
	GRAY=
	WHITE=
	
	SUCCESS=
	WARNING=
	FAILURE=
	NOOP=
	ON=
	OFF=
	ERROR=

	ascii_color="no"
	
    fi

}

ascii_color "$ascii_color"



# End libcolor.sh

;; 
*)
   echo "$FUNCNAME: '$1' not found."
   exit 1;;
esac

}

#!- 


# === VARS :
#
# Feel free to change them.

max_err_min=5     # number of contiguous error needed to jump to a new location
                  #  DO NOT SET TO "1"...

max_chunks=300   # number of maximum chunks allowed
min_chunks=15    # number of minimum chunks to start modifying max_err


min_bs=512    # min block size (see dd_rescue's help)
max_bs=16384  # max block size (see dd_rescue's help)
bar_lines=15  # nb of lines for bar drawing.

default_log_paths=". ~ /tmp" # if default log path is in a small filesystem
                             #   or a virtual filesystem (as /dev or /proc)
                             #   then these paths will be tried.


# Meta data description

email="vaab@free.fr"
version="0.1.2"
state="beta"
author="LAB Valentin"



include common

depends sed grep cat cut tr head touch wc bc tail stat dirname basename df

usage="$exname {filename|device} {output-file} [{info}]
    or $exname --help
    or $exname --version"

help="
Options:
  --help            Print this message
  --version         Print version information
  {filename|device} The source file (it can be a block device)
  {output-file}     The destination file
  info              Specifying \"info\" as third argument will display 
                      summary informations on ongoing recovery and
		      exit without taking any actions.

Note:
  A log file will be created, and named '<output-file>.log'. This is a
dd_rescue log file (which is human readable). This log file is important
as dd_rhelp feeds itself with its contents to manage correctly dd_rescue.

  Without this log file, dd_rescue won't support resume capability.

Send bug reports, or comments to $email."


## 
# Is $1 numeric ?
#
# @param $1 string Will be tested as numeric
#
# @return-errorlevel 0/1 if $1 is numeric 0 else 1
# @return-stdout nothing
#
function is_num ()
{
    rest=$(echo $1 | "${sed}" 's/^\([0-9]\+\)\?\(\.\([0-9]\+\)\)\?$/X/g')
    
    if [ -z "$(echo $1 | "${grep}" '^\([0-9]\+\)\?\(\.\([0-9]\+\)\)\?$')" ] ;then
	return 1
    else
	return 0
    fi
}

## 
# Handles BC conveniently
# 
# @param $1 integer scale (number of digit in result after '.')
# @param $2+ string expression to be parsed by bc
#
# @return-errorlevel 0 if no error occured
#                    1 if bc failed
# @return-stdout string output of bc 
#                       or debug string output
#
function bc_calc ()
{
    scale=$1;

    if ! is_num "$scale" && test "$scale" != "no"; then
	echo "bc_calc: Wrong first argument " >&2
	echo "'$scale' is not a number." >&2
	exit 1
    fi

    test "$scale" == "no" && scale=""

    shift
    exp=$(test "$scale" && echo "scale=$scale;";
	test "$*" && echo "$*" || "${cat}" - )
    
    ans=$(echo "$exp" | "${bc}" 2>&1)

    if ! is_num "$ans"; then
	echo "bc failed on this expression : " >&2
	echo "$exp" >&2
	echo "bc returned :" >&2
	echo "$ans" >&2
	exit 1;
    else
	echo "$ans"
	return 0
    fi    
}


#
# === Argument checking...
#

gnu_options $*

if [ "$#" -le "1" ]; then
    echo "Need 2 arguments..."
    print_usage
    exit 1
fi

if test -z "$1"; then
    print_error "First argument is empty !!";
fi

if test -z "$2"; then
    print_error "Second argument is empty !!";
fi

if [ "$#" == "3" ] &&  test -z "$3"; then
    print_error "third argument is empty !!";
fi

if [ "$3" != "" ] && [ "$3" != "info" ]; then
    shift;shift
    echo "too much argument... : '$*' is beyond limit. "
    print_usage
    exit 1;
fi

if [ "$3" == "info" ]
then
  opt="info"
fi

infile="$1"
outfile="$2"
logfile="$2.log"

#
# === Files checking
#


if ! checkfile er "$infile"; then
    # No need of msg, checkfile will output them
    print_error "Please specify a usable file as first argument."
    exit 1
fi


#if ! LC_ALL=C "${stat}" -c %F "$infile" | "$grep" "special file" >/dev/null 2>&1 ; then
#
#   if [ "$(LC_ALL=C "${stat}" -c %s "$infile")" -le 100 ]; then
#	print_error "dd_rhelp does not support input files < 100 bytes, and is quite useless in these cases."
#    fi
#fi

if ! checkfile erw "$outfile" > /dev/null 2>&1
then

    if ! "${touch}" "$outfile" > /dev/null 2>&1
    then
	print_error "'$outfile' is not accessible/could not be created..."
    else
	[ "$DEBUG" == "on" ] && 
	   echo "- file '$outfile' was successfully touched..."
    fi
fi


if ! checkfile edr "$("$dirname" "$logfile")" ; then
    # No need of msg, checkfile will output them
    exit 1
fi

## XXXVaab : should check available disk space ...

function check_logfilediskspace() {
    logfile_diskspace="$("$df" --sync -k "$("$dirname" "$logfile")" | "$tail" -n 1 |  "$sed" "s/ \+/ /g" | "$cut" -f 2 -d " ")"
    if [ "$(bc_calc 2 "$logfile_diskspace < 10000")" == "1" ]; then
	print_warning "The available disk space on the location \
'$(dirname "$logfile")' seems to be less than 10 MB which could \
not be enough to store all information the log file will need. \
$exname won't use '$logfile' as log file, but instead will \
use another location."
	return 1
    fi
}

function check_logfilefs() {
    logfile_fs="$("$stat" -f "$("$dirname" "$logfile")" -c %T)"
    if [ "$logfile_fs" == "tmpfs" ] ||
	[ "$logfile_fs" == "usbfs" ] ||
	[ "$logfile_fs" == "devpts" ] ||
	[ "$logfile_fs" == "proc" ] ||
	[ "$logfile_fs" == "sysfs" ]; then
	print_warning "Your default logging directory seems to be \
'$(dirname "$logfile")' which is on a '$logfile_fs' filesystem. \
As we shouldn't mess with log files in these type of filesystem \
$exname won't use '$logfile' as log file, but instead will \
use another location."
	return 1
    fi
}


for i in "$("$dirname" "$2")" $default_log_paths __end__; do 

    if [ "$i" == "__end__" ]; then
	print_error "Could not find any location that could hold conveniently the log file... "
	exit 1
    fi 
    echo -n ">> Probing logfile location : "
    logfile="$(eval "echo $i")/$("$basename" "$logfile" .log).log"
    echo "'$logfile'";

    check_logfilefs || continue
    check_logfilediskspace || continue
    if checkfile e "$logfile" > /dev/null 2>&1 ; then
	echo "Log file exists already."
	if ! checkfile r "$logfile" ;then
	    continue
	fi

	if ! "${touch}" "$logfile" > /dev/null 2>&1 ;then
	    print_warning "'$logfile' is not writable... Only 'info' option will be \
permitted. No parsing will be allowed. Please solve the permissions issue, or remove this logfile from '$logfile' to start a brand new parsing of your device."
	fi

    fi

    break;
done

echo ">> Found convenient logfile location '$logfile'"


if ! checkfile erw "$logfile" > /dev/null 2>&1 ;then

    if [ "$opt" == "info" ] && ! [ -r "$logfile" ]
    then
	"${cat}" <<EOF
No info available since there's no readable '$logfile'.

'info' option outputs information on current rescuing state by parsing this 
log file that would have been created by a precedent use of dd_rhelp or 
dd_rescue. Since there's no log file, it has nothing to display.

This happens if you haven't launched a $exname before.
EOF
	exit 0 ;
    fi
  


    if ! "${touch}" "$logfile" > /dev/null 2>&1
    then
	print_error "'$logfile' is not accessible/could not be created..."
    else
	[ "$DEBUG" == "on" ] && 
	   echo "- file '$logfile' was successfully touched..."
    fi

else

    if [ "$opt" == "info" ] && [ "$(${cat} "$logfile" | "${grep}" -v ^\$ )" == "" ] ; then
	"${cat}" <<EOF
No info available in '$logfile' : it is empty !

'info' option outputs information on current rescuing state by parsing this 
log file that would have been created by a precedent use of dd_rhelp or 
dd_rescue. Since there's no content in log file, it has nothing to display.

This could happens if you haven't launched a $exname before.
EOF
	exit 0 ;
    fi

fi


#
# === Some vars, do not touch unless you know what you are doing...
#

# regexp for parsing the log file.

string="^Summary for $infile -> $outfile:"
infoline="^dd_rescue: (info):"
eofstring="$infoline $infile ([0-9]\+\.[0-9]k): EOF\$"
nb_stars=$[ $bar_lines * $COLUMNS ] # nb of char to display progress bar...

#
# === Functions
#

# Variable that holds chunks info in a list of
# lines of nb1-nb2...
chunk=""



##
# Fetch a valid dd_rescue version in PATH and in current dir.
#
#
#
#
function get_valid_dd_rescue() {

    [ "$DEBUG" == "on" ] && echo "Finding dd_rescue binary" >&2

    path=$(get_path dd_rescue)
    
    if test -z "$path"; then 
	    echo -e "dd_rescue not found ! You must have dd_rescue in your PATH or in the current directory.">&2
	    echo -e "Your PATH is : " >&2
	    echo -e $PATH >&2
    	exit 1;
    fi
    
    version=$("$path" -V 2>&1 | "${grep}" "dd_rescue Version" | cut -f 3 -d " " |
    cut -f 1 -d ",")
    
    [ "$DEBUG" == "on" ] && echo -n "Trying '$path' : gives this version : '$version'..." >&2

    if is_num "$version" && [ "$(bc_calc 2 "$version < 1.03")" == "0" ]; then
        [ "$DEBUG" == "on" ] && echo "OK !" >&2
		echo "$path"
		return 0
    else
		[ "$DEBUG" == "on" ] && echo "BAD !" >&2
    fi
	
    path="$(dirname $(type -ap "$0" | "${tail}" -n -1))/dd_rescue"
    
    if [ -x "$path" ] ;then
		version=$("$path" -V 2>&1 | "${grep}" "dd_rescue Version" | cut -f 3 -d " " |
	    cut -f 1 -d ",")
		[ "$DEBUG" == "on" ] && echo -n "Trying '$path' : gives this version : '$version'..." >&2
		if is_num "$version" && [ "$(bc_calc 2 "$version < 1.03")" == "0" ];then
		    [ "$DEBUG" == "on" ] && echo "OK !" >&2
		    echo "$path"
		    return 0
		else
		    [ "$DEBUG" == "on" ] && echo "BAD !" >&2
		fi
	
    fi
    
    echo "Bad version of dd_rescue ! you must have >= 1.03">&2
    exit 1    
};



##
# Go fetch EOF information in log to get a good approximation
#
# no args
# Looks in logfile to compute EOF
# @return-output nothing
# @return-errorlevel allways 0
#
# changes $eof to "nothing" if no EOF is found,
#              or 'nb' where nb is best EOF found
function get_eof()
{

  eoflines="$("${cat}" "$logfile" | "${tr}" -d "\\r" | "${grep}" "$eofstring" | "${sed}" 's/^dd_rescue: (info): .* (\([0-9\.]\+\)k): EOF$/\1/g')"

  eof="nothing"

  for i in $eoflines
  do
    if [ "$eof" == "nothing" ]; then
	eof=$i
	continue
    fi

    if [ "$(bc_calc 1 "$eof > $i")" == "1" ];then
	eof=$i
	continue
    fi
  done
}


##
# 
# Will mark chunk as beiing completed
# Depends on valid '$chunk'
#
# @param $1 string in the form "nb1-nb2" with nb1<nb2
#
# warning : this function modifies '$chunk'.
#
function add_chunk() {

    arg_start=$(echo "$1" | "${cut}" -f 1 -d "-")
    arg_stop=$(echo "$1" | "${cut}" -f 2 -d "-")

    if ! is_num "$arg_start" ||
	! is_num "$arg_stop"; then
	print_error "add_chunk : invalid argument '$1' (is not correctly formatted as number:number)"
    fi

    if test "$(bc_calc 1 "$arg_start < $arg_stop")" == "0"; then
	print_error "add_chunk : invalid argument '$1' (these are not logical values)"
    fi

    overlap="no"

    goodchunk=""
    parsechunk="$chunk"

    while test "$parsechunk"
    do

      # get first chunk already marked.
      i="$(echo "$parsechunk" | "${head}" -n 1 )"

      # pull the two bounds
      i_start="$(echo "$i" | "${cut}" -f 1 -d "-")"
      i_stop="$(echo "$i" | "${cut}" -f 2 -d "-")"


      # new chunk begins after current chunk end ?
      as_gt_ie="$(bc_calc 1 "$arg_start > $i_stop" )"

      if [ "$as_gt_ie" == "1" ]
      then
          # new chunk doesn't overlap with current chunk
	  # Iterate, put current chunk in $goodchunk.
	  goodchunk="$(echo -en "$goodchunk\n$i")"
	  parsechunk="$(echo "$parsechunk" | "${tail}" -n +2)"
	  continue
      fi

      # new chunk ends before current chunk start ?
      ae_gt_is="$(bc_calc 1 "$arg_stop < $i_start")"

      
      if [ "$ae_gt_is" == "1" ]
      then
          # new chunk doesn't overlap with current chunk but is before
	  # we have found where to put our chunk

	  break; # we can break because chunk are sorted
      fi
      
      # if we come here, that means that new chunk overlap with current.
      
      # have we new chunk's start located IN current chunk ?
      as_int="$(bc_calc 1 "$arg_start >= $i_start && $arg_start <= $i_stop")"

      # have we new chunk's end located IN current chunk ?
      ae_int="$(bc_calc 1 "$arg_stop >= $i_start && $arg_stop <= $i_stop ")"

      # new chunk is contained entirely in current chunk
      if [ "$as_int" == "1" ] && [ "$ae_int" == "1" ]
      then
	  # no need to do anything
	  overlap="yes"
	  break;
      fi

      # new chunk contains entirely current chunk
      if [ "$as_int" == "0" ] && [ "$ae_int" == "0" ]
      then
	  # we forget about current chunk, and iterate.
	  parsechunk=$(echo "$parsechunk" | "${tail}" -n +2)
	  continue
      fi
      
      # new chunk overlap on its end with beginning of current chunk
      if [ "$as_int" == "0" ] && [ "$ae_int" == "1" ]
      then
	  # grow new chunk to englobe current chunk.
	  arg_stop=$i_stop
	  parsechunk=$(echo "$parsechunk" | "${tail}" -n +2)

	  break; # we can break because chunk are sorted.
      fi

      # new chunk overlap on its beginning with end of current chunk
      if [ "$as_int" == "1" ] && [ "$ae_int" == "0" ]
      then
	  # grow new chunk to englobe current chunk.
	  arg_start=$i_start
	  parsechunk=$(echo "$parsechunk" | "${tail}" -n +2)
	  continue; # new chunk might overlap more chunks
      fi

    done

    # Overlapping occurs only if new chunk is contained in already marked
    # chunk. In this case, we musn't change $chunk.
    if [ "$overlap" == "no" ]
    then
	chunk="$(echo -en "$goodchunk\n$arg_start-$arg_stop\n$parsechunk" |
	 "${grep}" -v ^\$)"
    fi
}


# get_next_pos will found the next offset to jump at to launch dd_rescue
# No args
# depends on $eof
# returns offset:long  (offset in start location, long is how much bytes
#                         to retrieve from location both reverse and forth)
function get_next_pos()
{
   if [ "$eof" == "nothing" ] || test -z "$eof" 
   then

	# finding last's chunk end.

	if test "$chunk" ;then
	   last_chunk=$(echo "$chunk" | "${tail}" -n -1 )
	   max_stop=$(echo "$last_chunk" | "${cut}" -f 2 -d "-")
        else		   
           max_stop=0
	fi
	
	echo "$(bc_calc 1 "($max_stop * 2)"):$max_stop";
	   
    else

	# find biggest hole.
	pos=0
	size=0
	cursize=0
	start=0
	next=0

	# Get biggest hole between chunks
	for i in $chunk "$eof-$eof"
        do

	  # collect start of chunk
	  next=$(echo "$i" | "${cut}" -f 1 -d "-")

	  if [ "$next" != "$start" ]
	  then
	      cursize="$(bc_calc 1 "($next - $start)")"
	      if [ "$(bc_calc 1 "($size < $cursize)")" == "1" ]
	      then
		      size=$cursize
		      pos=$start
	      fi
	  fi
	  start=$(echo "$i" | "${cut}" -f 2 -d "-")
	done

	size="$(bc_calc 0 "(($size + 1) / 2)")"
	echo "$(bc_calc 1 "($pos + $size)"):$size"
    fi
}


# Get info with last summary produced by dd_rescue call.
# no args
# depends on content of log file
# changes $logcontent, $chunk, $eof 
function swallow_last_summary()
{
  # last summary of log (4 lines output by printreport())
  last_logcontent=$("${cat}" "$logfile" | "$tr" -d "\r" | "${grep}" "$string" -A 3 | "${tail}" -n -4)

  if test -z "$last_logcontent" ;then 
	print_error "Didn't found any summary in log file. These info are normally produced by dd_rescue, and have to be parsed by dd_rhelp."
  fi	

  #echo last_logcontent: "$last_logcontent"

  process_log "$last_logcontent"

  get_eof

  save_log
  
}



function get_last_chunk() {
    if test "$chunk"; then
	last_chunk="$(echo "$chunk" | "${tail}" -n -1 )"
	echo "$last_chunk" | "${cut}" -f 2 -d "-"
    else
	echo 0
    fi
};


# Display a neat bar in ascii art(?!) which shows completion of dd_rescue.
# 
#
#
function show_bar()
{

    echo "=== BAR === [ 'x' parsed, '*' next jump point, '|' '.' not parsed ]"

    if [ "$eof" == "nothing" ] || test -z "$eof"
    then
	eof_limit="$(get_last_chunk)"
	next_pos="$(get_next_pos | "${cut}" -f 1 -d ":")"
	if [ "$(bc_calc 1 "$eof_limit < $next_pos")" == "1" ]; then
	    eof_limit=$next_pos
	fi
    else
	eof_limit="$eof";
    fi

    if ! is_num "$nb_stars";then
	nb_stars=80
    fi
	

    if [ "$eof_limit" != "0" ]
    then
	
	# c_res is nb of Kb represented by one char.
	c_res="$(bc_calc 10 "$eof_limit / $nb_stars")";

	# next_pos is place of next jump in chars.
	next_pos="$(bc_calc 0 "$(get_next_pos | "${cut}" -f 1 -d ":") / $c_res")";    
        

#	echo -n "[";

	curchar=0
	start=0
	next=0
	ct=0

	for i in $chunk $eof_limit-$eof_limit
	  do

	  start=$(echo "$i" | "${cut}" -f 1 -d "-")
	  
	  if [ "$next" != "$start" ]
	  then

	      # This is start of hole
	      startchar="$(bc_calc 0 "$next / $c_res")"
	
	      # This is end of hole
	      curchar="$(bc_calc 0 "$start / $c_res")"
	      	      
	      # draw completed chars up to start of hole.
	      while [ "$ct" -lt "$startchar" ] ;do
		echo -n "x"
		ct=$[$ct+1] ;
	      done

	      # our current tracker ($ct) is now at : $ct==$startchar
	      # OR is $ct = $startchar + 1 ONLY if precedent hole finished
	      #    in the same char this hole begins !!
	      
	      # as rounding occurs, we might have $startchar == $curchar
	      # but original hole is not null ! We must show that there's
	      # a hole in this char.

              # hole is bigger than 1 char 
	      if [ "$startchar" -lt "$curchar" ] ; then
		  # if current drawing position ($ct) is on startchar
		  if [ "$ct" == "$startchar" ] ;then
		      # draw the beginning of hole.
		      [ "$ct" != "$next_pos" ] && echo -n "|" || echo -n "*"
		      ct=$[$ct + 1] 
		  fi
		  
		  # mark char between startchar and curchar as hole.
		  while [ "$ct" -lt "$curchar" ] ; do
		    [ "$ct" != "$next_pos" ] && echo -n "." || echo -n "*"
		    ct=$[$ct + 1]
		  done

		  # current tracker is now equal to curchar.

		  # draw the end of hole.
		  if [ "$nb_stars" -gt "$curchar" ] ; then
		      [ "$ct" != "$next_pos" ] && echo -n "|" || echo -n "*"
		      ct=$[$ct + 1]
		  fi

	      else
	      
	      # the only remaining possibility is that $startchar=$curchar
	      # this is the rounding possibility.

		  # if [ "$startchar" == "$curchar" ] ; then
		      if [ "$nb_stars" -gt "$curchar" ] ; then
			  if [ "$ct" == "$next_pos" ] ; then
			      [ "$ct" != "$next_pos" ] && echo -n "|" || 
			      echo -n "*"
			      ct=$[$ct + 1]
			  fi
		      fi
		  # fi
		      
	      fi
	  else
	      if [ "$start" == "$eof_limit" ]; then
		  while [ "$ct" -lt "$nb_stars" ] ;do
		      echo -n "x"
		      ct=$[$ct+1] ;
		  done
	      fi
	  fi
	  next=$(echo "$i" | "${cut}" -f 2 -d "-")
	  
	done

    else
	echo -n "[ No Bar available the first launch ]"
    fi

    last_chunk="$(get_last_chunk)"

    if [ "$eof_limit" != "$last_chunk" ]
    then
	echo "=== Bar was drawn from 0 to hypothetic end : $eof_limit"
    else
	echo "=== Bar was drawn from 0 to $eof_limit"
    fi
}



function show_info()
{
    echo "=== dd_rhelp INFO -" $(echo "$chunk" | "${wc}" -l) "parsed chunks...";


    if [ "$eof" == "nothing" ]
    then
# 	echo "- max file size : no limit found"
	:
    else
#	echo "- max file size : $eof"
	
	echo -e "- Biggest unparsed chunk : " "$(bc_calc 1 "$(get_next_pos | "${cut}" -f 2 -d ":") * 2")" "k "
    fi
    
    parsing="$logcontent"
    total_errxfer="0";
    total_succxfer="0";
    total_xferd="0";
    
    while test "$parsing" 
    do
	firstline="$(echo "$parsing" | "${head}" -n 1)"
	parsing="$(echo "$parsing" | "${tail}" -n +2)"
	
	xferd="$(echo "$firstline" | "${cut}" -f 2 -d ":" | "${cut}" -f 2 -d "=")"
	errxfer="$(echo "$firstline" | "${cut}" -f 4 -d ":" | "${cut}" -f 2 -d "=")"
	succxfer="$(echo "$firstline" | "${cut}" -f 5 -d ":" | "${cut}" -f 2 -d "=")"
	
	total_errxfer="$(bc_calc 1 "$total_errxfer + $errxfer")"
	total_succxfer="$(bc_calc 1 "$total_succxfer + $succxfer")"
	total_xferd="$(bc_calc 1 "$total_xferd + $xferd")"
	
    done
    

    size=0
    cursize=0
    start=0
    next=0
    for i in $chunk
    do
	next=$(echo "$i" | "${cut}" -f 1 -d "-")
	if [ "$next" != "$start" ]
	then
	    cursize="$(bc_calc 1 "$next - $start")"
	    size="$(bc_calc 1 "$size + $cursize")"
	fi
	start=$(echo "$i" | "${cut}" -f 2 -d "-")
    done
    #echo -e "- total unparsed : ${size}k"
    #echo -e ""

    eof_limit=$(get_last_chunk)

    echo -e "- xferd(succ/err) : ${total_xferd}k(${total_succxfer}k/${total_errxfer}k)"


    # if there are some data parsed
    if [ "$eof_limit" != 0 ]; then 
	[ "$eof" == "nothing" ] && 
	echo -e "    less than       <$(bc_calc 0 "(100 * $total_xferd) / ${eof_limit}")%(<$(bc_calc 0 "(100 * $total_succxfer) / ${eof_limit}")%/<$(bc_calc 0 "(100 * $total_errxfer) / ${eof_limit}")%)"

	[ "$eof" != "nothing" ] && [ "$eof" != "$eof_limit" ] && 
	echo -e "    between         $(bc_calc 1 "(100 * $total_xferd) / ${eof}")-$(bc_calc 1 "(100 * $total_xferd) / ${eof_limit}")%($(bc_calc 1 "(100 * $total_succxfer) / ${eof}")-$(bc_calc 1 "(100 * $total_succxfer) / ${eof_limit}")%/$(bc_calc 2 "(100 * $total_errxfer) / ${eof}")-$(bc_calc 2 "(100 * $total_errxfer) / ${eof_limit}")%)"
	
	[ "$eof" != "nothing" ] && [ "$eof" == "$eof_limit" ] && 
	echo -e "                    $(bc_calc 2 "(100 * $total_xferd) / ${eof}")%($(bc_calc 2 "(100 * $total_succxfer) / ${eof}")%/$(bc_calc 2 "(100 * $total_errxfer) / ${eof}")%)"
	
	[ "$eof" != "nothing" ] &&  [ "$eof" != "$eof_limit" ] && 
	echo -e "    left to parse : between $(bc_calc 1 "$eof - $total_xferd")k and $(bc_calc 1 "$eof_limit - $total_xferd")k"
	[ "$eof" != "nothing" ] &&  [ "$eof" == "$eof_limit" ] && 
	echo -e "    left to parse : $(bc_calc 1 "$eof - $total_xferd")k"

    
	echo -en "- EOF "
	
	if  [ "$eof" != "nothing" ] && 
	    [ "$eof" == "$eof_limit" ];then
	    
	    echo "is found and is at ${eof}k."
	    
	    
	else
	    if [ "$eof" != "nothing" ]; then
		echo "is not found, but between ${eof_limit}k and ${eof}k."
	    else
		echo "is not found, but greater than ${eof_limit}k"
	    fi
	fi
    fi
    

    jump=$(get_next_pos | "${cut}" -f 1 -d ":")

    [ "$jump" != "0" ] && echo -n "- Jump pos : $(get_next_pos | "${cut}" -f 1 -d ":") "

    if [ "$size" == "0" ] && [ "$eof" != "nothing" ] && 
	[ "$eof" == "$eof_limit" ];then
	
	return 0
    else
	return 1
    fi
}


function process_log() {


    data="$1"
    #echo "DATA: $data"

    test -z "$data" && return 0
	
    [ "$DEBUG" == "on" ] && echo -n "- cleaning data ["
    # XXXVaab : bad if file is less that 0.1Ko length
    data=$(echo "$data" | "${grep}" -v "xferd: \+0.0k$")
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${grep}" "$infoline" -A 1 | "${cut}" -c 12-)
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${sed}" 's/^(info): ipos: \+//g')
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${sed}" 's/^ \+errs: \+/NR:/g')
    [ "$DEBUG" == "on" ] && echo -n "."

    data=$(echo "$data" | "${sed}" 's/^ \+- \+errs: \+/RE:/g')
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${sed}" 's/^\([0-9\.]\+\)k, opos:.\+xferd: \+\([0-9\.]\+\)k$/ipos=\1:xferd=\2:/g')
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${sed}" 's/^\(RE\|NR\):[0-9]\+, errxfer: \+\([0-9\.]\+\)k, succxfer: \+\([0-9\.]\+\)k$/\1:errxfer=\2:succxfer=\3;/g')
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${tr}" -d "\n")
    [ "$DEBUG" == "on" ] && echo -n "."
    data=$(echo "$data" | "${tr}" ";" "\n")
    [ "$DEBUG" == "on" ] && echo ".]"
    
    # All info now take one line per entry, and field are separated by ":"

    [ "$DEBUG" == "on" ] && echo -n "- processing data ["
	
  # finding start of chunks
    
    parsing="$data"
    # chunk=""
    
    while test "$parsing" ;do
	#echo "parsing: $parsing"
	firstline="$(echo "$parsing" | "${head}" -n 1)"
	#echo "firstline: $firstline"
	parsing="$(echo "$parsing" | "${tail}" -n +2)"
	
	ipos="$(echo $firstline | "${cut}" -f 1 -d ":" | "${cut}" -f 2 -d "=")"
	xferd="$(echo $firstline | "${cut}" -f 2 -d ":" | "${cut}" -f 2 -d "=")"
	rev="$(echo $firstline | "${cut}" -f 3 -d ":")"
	errxfer="$(echo $firstline | "${cut}" -f 4 -d ":" | "${cut}" -f 2 -d "=")"
	succxfer="$(echo $firstline | "${cut}" -f 5 -d ":" | "${cut}" -f 2 -d "=")"
	
	if [ "$rev" == "RE" ] ; then
	    start="$ipos"
	    stop="$(bc_calc 1 "$ipos + $xferd")"
	else
	    start="$(bc_calc 1 "$ipos - $xferd")"
	    stop="$ipos"
	fi

	chunkline="$start-$stop"
	add_chunk $chunkline

	[ "$DEBUG" == "on" ] && echo -n "."
    done

    [ "$DEBUG" == "on" ] && echo "]";

    if test "$logcontent";then
	logcontent="$(echo -en "$logcontent\n$data")"
    else
	logcontent="$data";
    fi
}


##
# Load log info in variables
#
#
#
#
function load_log() {

    # Complete last save log entry
    lnb_save=$("${cat}" -n "$logfile" | "${tr}" -d "\\r" | "${grep}" "chunk:" -A 2 | "${tail}" -n -3)

    if test "$lnb_save" ;then

	# Get Chunk info in Last log entry
	lnb_save=$(echo $lnb_save | "${head}" -n 1 | cut -f 1 -d " ")


	end_log="$(cat "$logfile" | "$tr" -d "\\r" | "${tail}" -n "+$lnb_save")"

	last_lines=$(echo "$end_log" | "${grep}" "chunk:" -A 2 | "${tail}" -n -3)
	
	log=$(echo "$last_lines" | "${grep}" "chunk" | "${tail}" -n -1 )
	log1=$(echo "$last_lines" | "${grep}" "logcontent" | "${tail}" -n -1 )
	log2=$(echo "$last_lines" | "${grep}" "eof" | "${tail}" -n -1 )
	
	if test "$log" && test "$log1" && test "$log2" ;then
	    chunk="$(echo "$log" | "${cut}" -f 2- -d ":" | "${tr}" ":" "\n")"

	    logcontent="$(echo "$log1" | "${cut}" -f 2- -d : | "${tr}" ";" "\n")"
	    eof="$(echo "$log2" | "${cut}" -f 2 -d ":")"
	    
	    log=$(echo "$end_log" | "${grep}" "$string" -A 3 )
	    
	    process_log "$log"
	    return 0
	else
	    echo "Bad log format !!! Fallback to slow mode..."
	fi

    fi
	
    # select all summary info of dd_rescue
    log=$("${cat}" "$logfile" | "$tr" -d "\\r" | "${grep}" "$string" -A 3 )

    # Set EOF with log.
    get_eof

    # Sets logcontent AND chunk
    process_log "$log"

}

function save_log() {
    echo "=== COMPUTED VERSION OF LOG :" >> "$logfile"
    echo "chunk:$(echo -n "$chunk" | "${tr}" "\n" : )" >> "$logfile"
    echo "logcontent:$(echo -n "$logcontent" | "${tr}" "\n" ";" )" >> "$logfile"
    echo "eof:$eof" >> "$logfile"
}



# === beginning of real code

#echo load
load_log

# Save computed version of log for next time.
#echo save
save_log

if [ "$opt" == "info" ] && test -z "$logcontent"
then
    echo "No Info found in log..."
    exit 0;
fi


info="$(show_info)"
if [ "$?" == "0" ] ; then 
    [ "$opt" == "info" ] && echo "$info"
    echo "All your data has been dd_rescued."
else 
    if [ "$opt" == "info" ];then
	echo "$info";
	show_bar
    fi
fi

[ "$opt" == "info" ] && exit 0

DD_RESCUE="$(get_valid_dd_rescue)" || exit 1;

while [ "$(echo "$chunk" | "${wc}" -l)" -gt "1" ] ||
      [ "$(get_last_chunk)" != "$eof" ] ||
      [ "$eof" == "nothing" ]; do

  info="$(show_info)" 
  if [ "$?" == "0" ] ; then 
      echo "$info"
      echo -n "All your data has been dd_rescued !! Please note that it doesn't mean that it has been RECOVERED : dd_rescue parsed all your drive and copied what could be copied. It's fairly sure that you cannot get more from your drive. You should now use with caution a filesystem recovering tool specific to your filesystem type to rebuild meta-data information. Then you should be able to mount partitions if needed."
      echo 
      # show_bar
      exit 0
  fi
  
  if [ "$logcontent" != "" ] ;then
      echo "$info";
      show_bar
  fi


  [ "$DEBUG" == "on" ] && [ "$opt" == "info" ] && [ "$chunk" != "" ] && echo -en "Chunks that were dd_rescued (in k):\
 \n$chunk\n"
  [ "$opt" == "info" ] && exit 1;

  getnextpos_content="$(get_next_pos)"
  next_pos="$(echo "$getnextpos_content" | "${cut}" -f 1 -d ":")"
  count="$(echo "$getnextpos_content" | "${cut}" -f 2 -d ":")"
  nbchunks="$(echo "$chunk" | "${wc}" -l)"

  if [ "$nbchunks" -le "$min_chunks" ]; then
      max_err="$max_err_min";
  else
      if [ "$nbchunks" -ge "$max_chunks" ]; then
	  max_err="$(bc_calc 0 "$count * 2")";
      else
	  ## XXXVaab : must keep a high scale (ie 5) to avoid high
	  ##  simplification due to the 0..1 result of the division.
	  max_err="$(bc_calc 5 "$max_err_min + ((($nbchunks - $min_chunks)/($max_chunks - $nbchunks)) * ((${count} * 2) - $max_err_min))" | cut -f 1 -d ".")"
      fi
  fi

  max_err_k="$(bc_calc 1 "$max_err / 2")";

  if [ "$next_pos" != "0" ]
  then
      logline="=== parsing at ${next_pos}k, for ${count}k, max continuous err: ${max_err_k}k <<< ===";
      echo "$logline" >> "$logfile"
      echo "$logline"
      ${DD_RESCUE} -r -s "$next_pos"k -l "$logfile" -e "$max_err" -B "$min_bs" -b "$max_bs" -m "$count"k "$infile" "$outfile"
      swallow_last_summary
  fi

  if [ "$next_pos" != "${eof}" ]
  then
      if [ "$eof" == "nothing" ]
      then
	  count=0;
      fi

      logline="=== parsing at ${next_pos}k, for ${count}k, max continuous err: ${max_err_k}k >>> ===";
      echo "$logline" >> "$logfile"
      echo "$logline"
      ${DD_RESCUE} -s "$next_pos"k -l "$logfile" -e "$max_err" -B "$min_bs" -b "$max_bs" -m "$count"k "$infile" "$outfile"
      swallow_last_summary
  fi
 
done

# End dd_rhelp
