#!/bin/bash

# For information about the usage of this script type
# sh undel-ext3.sh --help
#
# This script is for people who have deleted something
# on their ext3 partition and have no backup, like me :)
#
# Basically i started work on this script as i deleted
# something on my ext3 partition and for recovering files
# from Virtuozzo partitions which are broken.
#
# Author: Maik Broemme <mbroemme@plusserver.de>
#
# $Id: undel-ext3.sh,v 1.2 2004/05/27 03:20:27 mbroemme Exp $

SCRIPTNAME="undel-ext3.sh"
OPTION_DEVICE="FALSE"
OPTION_DEVICE_TYPE="FILE"
OPTION_HELP="FALSE"
OPTION_OUTPUT="TRUE"
OPTION_BLOCKSIZE="0"
OPTION_BLOCKSIZE_VALUE="0"
OPTION_DIRECTORY="0"
OPTION_DIRECTORY_VALUE="0"

# reading argument list and save given values.
for i in $@ ; do
	if [ "$OPTION_BLOCKSIZE" == "1" ]; then
		if [ `expr match $i "[0-9]*"` -ne 0 ]; then    
			OPTION_BLOCKSIZE_VALUE="$i"
		fi
		OPTION_BLOCKSIZE="0"
		continue
	fi
	if [ "$OPTION_DIRECTORY" == "1" ]; then
		OPTION_DIRECTORY_VALUE="$i"
		OPTION_DIRECTORY="0"
		continue
	fi
	if [ "$i" == "-h" ] || [ "$i" == "--help" ]; then
		OPTION_HELP="TRUE"
		continue
	fi
	if [ "$i" == "-s" ] || [ "$i" == "--silent" ]; then
		OPTION_OUTPUT="FALSE"
		continue
	fi
	if [ -b "$i" ]; then
		OPTION_DEVICE_TYPE="BLOCK"
		OPTION_DEVICE="$i"
		continue
	fi
	if [ -f "$i" ]; then
		OPTION_DEVICE_TYPE="FILE"
		OPTION_DEVICE="$i"
		continue
	fi
	if [ "$i" == "-b" ] || [ "$i" == "--blocksize" ]; then
		OPTION_BLOCKSIZE="1"
		continue
	fi
	if [ "$i" == "-o" ] || [ "$i" == "--output" ]; then
		OPTION_DIRECTORY="1"
		continue
	fi
done

# do some useful argument checking. 
if [ "$OPTION_DEVICE" == "FALSE" ] && [ "$OPTION_HELP" == "FALSE" ]
then
	echo "No device given"
	echo "Try \`$SCRIPTNAME --help' for more information."
	exit 1
fi
if [ "$OPTION_BLOCKSIZE_VALUE" == "0" ] && [ "$OPTION_HELP" == "FALSE" ]
then
	echo "No blocksize given"
	echo "Try \`$SCRIPTNAME --help' for more information."
	exit 1
fi
if [ "$OPTION_DIRECTORY_VALUE" == "0" ] && [ "$OPTION_HELP" == "FALSE" ]
then
	echo "No output directory given"
	echo "Try \`$SCRIPTNAME --help' for more information."
	exit 1
fi

# help for the options.
if [ "$1" == "" ] || [ "$OPTION_HELP" == "TRUE" ]
then
	echo "Usage: $SCRIPTNAME [DEVICE] [PATH]..."
	echo "Undeletes files from ext3 devices. (Example: sh $SCRIPTNAME -b 4096 /dev/hda5 -o /tmp/hda5)"
	echo
	echo "Mandatory arguments to long options are mandatory for short options too."
	echo "  -h, --help		shows you this help screen."
	echo "  -b, --blocksize		specifies the ext3 blocksize."
	echo "  -o, --output		directory to store files."
	echo "  -s, --silent		shows you nothing."
	echo
	echo "By default, a normal verbose mode is enabled to show some statistics."
	echo
	echo "Report bugs <mbroemme@plusserver.de>"
	exit 0
fi

# shows the given text on STDOUT
function undel_ext3_printf() {
	local text="${1}"
	local stderr="${2}"
	if [ "$OPTION_OUTPUT" == "TRUE" ] || [ "$stderr" == "STDERR" ]; then
		echo -e "$text"
	fi
}

# search all needed binaries
function undel_ext3_check() {
	check_bin=`which "${1}" 2>&1`
	if [ "$?" != "0" ]; then
		undel_ext3_printf "ERROR: ${1} is missing on your system" "STDERR"
		exit 1
	else
		undel_ext3_printf "Using binary: $check_bin"
	fi
}

# shows the given parameters
function undel_ext3_device() {
	local device_type="${1}"
	local device_path="${2}"
	local device_bs="${3}"
	local device_output="${4}"

	undel_ext3_printf "Device: $device_path"

	# check what type of device we handle
	if [ "$device_type" == "BLOCK" ]; then
		undel_ext3_printf "Type: block device"
	fi
	if [ "$device_type" == "FILE" ]; then
		undel_ext3_printf "Type: image file"
	fi

	undel_ext3_printf "Blocksize: $device_bs"
	undel_ext3_printf "Output directory: $device_output"
}

# creates output directory
function undel_ext3_output() {
	local device_output="${1}"

	undel_ext3_printf "Creating output directory..."
	mkdir -p $device_output 2> /dev/null
}

# the undelete god :)
function undel_ext3_undelete() {
	local device_path="${1}"
	local device_bs="${2}"
	local device_output="${3}"
	local i=0

	# threshold
	local delta=4030

	# calculate the number of blocks in
	local blocks=$(echo $(du -b $device_path | awk '{print $1}' ) / $device_bs | bc)

	# create temporary file
	local temp=`mktemp`

	while [ "$i" -lt "$blocks" ]; do {
		dd if=$device_path bs=$device_bs count=1 skip=$i 2>/dev/null > $temp
		[ "$(dd if=$temp bs=128 skip=31 2>/dev/null)" = "" ] && \
		[ "$(dd if=$temp bs=1 skip=7 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" == "$(dd if=$temp bs=1 skip=3 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" ] && \
		[ "$(dd if=$temp bs=1 skip=15 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" == "$(dd if=$temp bs=1 skip=11 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" ] && \
		i=$(echo "$i+1" | bc)

		# get filetype
		local dir=$(dd if=$device_path bs=$device_bs count=1 skip=$i  2>/dev/null | file -b -)
		local file_type="$dir"
		local dir=$device_output/$(echo $dir | awk '{print $2}')/$(echo $dir | awk '{print $1}')

		# check if directory already exists, if not create the directory with filetype retrieved from file
		test -d $dir || mkdir -p $dir

		# our filename with the filenumber
		local file=$dir/file$i.raw
		local file_name="file$i.raw"

		dd if=$device_path bs=$device_bs count=1 skip=$i 2>/dev/null > $file
		local end=$(dd if=$file count=$device_bs skip=$delta bs=1 2> /dev/null)

		# copy until we think eof comes, notice eof must be a multiple of blocksize
		while test -n "$end"; do {
			i=$(echo "$i+1"|bc)
			dd if=$device_path bs=$device_bs count=1 skip=$i 2>/dev/null > $temp
			[ "" == "$(dd if=$temp bs=128 skip=31 2>/dev/null)" ] && \
			[ "$(dd if=$temp bs=1 skip=7 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" == "$(dd if=$temp bs=1 skip=3 count=1 2>/dev/null | hexdump -b | awk '{print $2}' )" ] &&
			[ "$(dd if=$temp bs=1 skip=15 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" == "$(dd if=$temp bs=1 skip=11 count=1 2>/dev/null | hexdump -b | awk '{print $2}')" ] && \
			i=$(echo "$i+1" | bc) && \
			dd if=$device_path bs=$device_bs count=1 skip=$i 2>/dev/null > $temp

			local end=$(dd if=$temp count=66 skip=$delta bs=1 2> /dev/null)
			cat $temp >> $file
		}; done

		# shows the file and type on which script is working
		undel_ext3_printf "File: $file_name from type $file_type"
		i=$(echo "$i+1" | bc)
	}; done
}

# main() starts here
for i in awk bc cat expr dd file hexdump mkdir mktemp ; do
	undel_ext3_check "$i"
done
undel_ext3_device "$OPTION_DEVICE_TYPE" "$OPTION_DEVICE" "$OPTION_BLOCKSIZE_VALUE" "$OPTION_DIRECTORY_VALUE"
undel_ext3_output "$OPTION_DIRECTORY_VALUE"
undel_ext3_undelete "$OPTION_DEVICE" "$OPTION_BLOCKSIZE_VALUE" "$OPTION_DIRECTORY_VALUE"

