#!/bin/bash

# This script is part of anyfs-tools (anyfs-tools.sf.net)
# Written by Krivchenkov Nikolaj aka unDEFER <undefer@gmail.com>
# License GPL

SCRIPT="anyconvertfs"
VERSION="v0.84.12d"
if [ "${0:0:1}" != '/' ]; then F0=$PWD/$0; else F0=$0; fi
export TEXTDOMAIN="anyfs-tools"
export TEXTDOMAINDIR="${F0%%$SCRIPT}../share/locale"

( which "gettext" >/dev/null 2>&1 ) && GT="gettext " || GT="echo "

export PATH="$PATH:/usr/local/sbin:/usr/sbin:/sbin:."

PRINT()
{
	STRING="$1"
	STRING="${STRING//\{DEVICE\}/${BOLD}\"$DEVICE\"${CLEAR}}"
	STRING="${STRING//\{MOUNTPOINT\}/${BOLD}\"$MOUNTPOINT\"${CLEAR}}"
	STRING="${STRING//\{MOUNTEDIT\}/${BOLD}\"$MOUNTEDIT\"${CLEAR}}"
	STRING="${STRING//\{INODETABLE\}/${BOLD}\"$INODETABLE\"${CLEAR}}"
	STRING="${STRING//\{MOUNTEDITFILE\}/${BOLD}\"$MOUNTEDITFILE\"${CLEAR}}"
	STRING="${STRING//\{INODETABLEFILE\}/${BOLD}\"$INODETABLEFILE\"${CLEAR}}"
	STRING="${STRING//\{FROMFS\}/${BOLD}$FROMFS${CLEAR}}"
	STRING="${STRING//\{TOFS\}/${BOLD}$TOFS${CLEAR}}"
	STRING="${STRING//\{MOUNTEDFS\}/${BOLD}\"$MOUNTEDFS\"${CLEAR}}"
	STRING="${STRING//\{INODETABLE\}/${BOLD}\"$INODETABLE\"${CLEAR}}"
	STRING="${STRING//\{UTIL\}/${BOLD}\"$UTIL\"${CLEAR}}"
	STRING="${STRING//\{TMPDIR\}/${BOLD}\"$TMPDIR\"${CLEAR}}"
	STRING="${STRING//\{BUILD_FS\}/${BOLD}\"$BUILD_FS\"${CLEAR}}"
	STRING="${STRING//\{LABEL\}/${BOLD}\"$LABEL\"${CLEAR}}"
	
	STRING="${STRING//\{LOOPDEVICE\}/${BOLD}\"$LOOPDEVICE\"${CLEAR}}"
	STRING="${STRING//\{RESCUELIST\}/${BOLD}\"$RESCUELIST\"${CLEAR}}"
	STRING="${STRING//\{RESCUEDIR\}/${BOLD}\"$RESCUEDIR\"${CLEAR}}"

	STRING="${STRING//\{MESG\}/$MESG}"

	STRING="${STRING//\{MOUNTOPTIONS\}/${UNDER}\"$MOUNTOPTIONS\"${CLEAR}}"
	STRING="${STRING//\{ANYMOUNTPOINT\}/${UNDER}\"$ANYMOUNTPOINT\"${CLEAR}}"

	STRING="${STRING//\{FIRSTDESTROYSTEP\}/${UNDER}$FIRSTDESTROYSTEP${CLEAR}}"
	STRING="${STRING//\{SECS\}/${UNDER}$SECS${CLEAR}}"
	STRING="${STRING//\{OLDBLOCKSIZE\}/${UNDER}$OLDBLOCKSIZE${CLEAR}}"
	STRING="${STRING//\{BLOCKSIZE\}/${UNDER}$BLOCKSIZE${CLEAR}}"
	STRING="${STRING//\{MINBLOCKSIZE\}/${UNDER}$MINBLOCKSIZE${CLEAR}}"
	STRING="${STRING//\{MAXBLOCKSIZE\}/${UNDER}$MAXBLOCKSIZE${CLEAR}}"

	STRING="${STRING//\{MAXLABELLEN\}/${UNDER}$MAXLABELLEN${CLEAR}}"

       	STRING="${STRING//\{RED\}/${RED}}"
       	STRING="${STRING//\{GREEN\}/${GREEN}}"
       	STRING="${STRING//\{CYAN\}/${CYAN}}"
       	STRING="${STRING//\{BOLD\}/${BOLD}}"
       	STRING="${STRING//\{UNDER\}/${UNDER}}"
	STRING="${STRING//\{CLEAR\}/${CLEAR}}"

	echo -e "$STRING"
}

checkutilities ()
{
    MESG="$1"
    shift
    while [ $# != 0  ]; do
	UTIL="$1"
        shift
        if ( ! which "${UTIL}" >/dev/null 2>&1 ); then
            PRINT "`$GT"Cannot find {UTIL}. {MESG}"`"
            return 1
        fi
    done
}

checkutilities "`$GT"It is necessary to work."`" mount modprobe awk tr df build_it stat head reblock || exit 1

usage ()
{
    echo -e "${BOLD}$SCRIPT ${WHITE}$VERSION${CLEAR}"
    echo -e "${BOLD}${BROWN}`$GT"usage:"`${CLEAR}""\n$ ${BOLD}$SCRIPT [--tmpdir${CLEAR} ${UNDER}directory${CLEAR}${BOLD}] [--blocksize${CLEAR} ${UNDER}blocksize${CLEAR}${BOLD}] [-f] [--dry]"
    echo -e "\t[--inodetable${CLEAR} ${UNDER}inodetable${CLEAR}${BOLD}] [--color|--no-color] [--label${CLEAR} ${UNDER}label${CLEAR}${BOLD}]"
    echo -e "\t[--debug] [--dont-delete-it] [--success-delete-it]"
    echo -e "\t[--dontstop] [--pause${CLEAR} ${UNDER}seconds${CLEAR}${BOLD}] [--] "
    echo -e "\t ${CLEAR}${UNDER}device${CLEAR} ${BOLD}[${CLEAR}${UNDER}source_fs${CLEAR}${BOLD}] ${CLEAR}${UNDER}destination_fs${CLEAR}"

    exit 1
}

COLOR="N"
[[ "`stat -c "%T" -f /proc/$$/fd/1`" == "devpts" ]] && COLOR="Y"

FAST="DEFAULT"
HEADLEN="DEFAULT"
BLOCKSIZE="AUTO"
FORCE="N"
TMPDIR="/tmp"
DRYRUN="N"
LABEL="AUTO"
DONTSTOP="N"
PAUSE=7
DEBUG="N"
DELETE_IT="AUTO"

END="0"
while [[ "$END" == '0' ]]; do
	case "$1" in
		'--color')
			COLOR="Y"
			shift 1
			;;
		'--no-color')
			COLOR="N"
			shift 1
			;;
		'--tmpdir')
			TMPDIR="$2"
			shift 2
			;;
		'--blocksize')
			BLOCKSIZE="$2"
			shift 2
			;;
		'--debug')
			DEBUG="Y"
			shift 1
			;;
		'--dont-delete-it')
			DELETE_IT="NEVER"
			shift 1
			;;
		'--success-delete-it')
			DELETE_IT="SUCCESS"
			shift 1
			;;
		'-f')
			FORCE="Y"
			shift 1
			;;
		'--dry')
			DRYRUN="Y"
			shift 1
			;;
		'--inodetable')
			INODETABLE="$2"
			shift 2
			;;
		'--label')
			LABEL="$2"
			shift 2
			;;
		'--dontstop')
			DONTSTOP="Y"
			shift 1
			;;
		'--pause')
			PAUSE="$2"
			shift 2
			;;
		'--')
			END=1
			shift 1
			;;
		*)
			END=1
	esac
done

[[ "$COLOR" == "Y" ]] &&
{
	CLEAR="\x1B[0m"
	BOLD="\x1B[1m"
	UNDER="\x1B[4m"

	RED="\x1B[31m"
	GREEN="\x1B[32m"
	BROWN="\x1B[33m"
	BLUE="\x1B[34m"
	MAGENTA="\x1B[35m"
	CYAN="\x1B[36m"
	WHITE="\x1B[37m"
}

ENOSPC=28

[ -f "/usr/include/errno.h" ] &&
{
	( which "cpp" >/dev/null 2>&1 ) &&
		eval `cpp -dM /usr/include/errno.h | awk -- '($2 == "ENOSPC") {print $2 "=" $3; }'`
}

PAGESIZE=4096
[ -f "/usr/include/asm/page.h" ] &&
{
	( which "cpp" >/dev/null 2>&1 ) &&
	{
		eval `cpp -dM /usr/include/asm/page.h | awk -- '($2 == "PAGE_SHIFT") {print $2 "=" $3; }'`
		[ $PAGE_SHIFT ] &&
		{
			PAGESIZE=$[ 1 << $PAGE_SHIFT ]
		}
	}
}

[ -z "$4" ] || usage
[ -z "$2" ] && usage

grep -q "fuse" /proc/filesystems || modprobe fuse ||
{
	PRINT "`$GT"Sorry, seems {BOLD}fuse{CLEAR} kernel module hasn't installed"`"
	echo -e "${BOLD}${RED}`$GT"Exit"`${CLEAR}"
	exit 1
} 

which "anyfuse" >/dev/null 2>&1 ||
{
	PRINT "`$GT"Sorry, seems {BOLD}anyfuse{CLEAR} FUSE module hasn't installed"`"
	echo -e "${BOLD}${RED}`$GT"Exit"`${CLEAR}"
	exit 1
}

fullpath()
{
	F="$1"
	DIR="${F%/*}"
	NAME="${F##*/}"
	pushd "$DIR" 1>/dev/null 2>&1
	DIR="`pwd -P`"
	popd 1>/dev/null 2>&1
	echo "$DIR/$NAME"
}

DEVICE="$1"
[ -z "$3" ] &&
{
	FROMFS="AUTO"
	TOFS="$2"
} ||
{
	FROMFS="$2"
	TOFS="$3"
}

[ -b "$DEVICE" ] || [[ "$FORCE" == "Y" ]]  ||
{
	PRINT "`$GT"{DEVICE} is not block device. Use {UNDER}'-f'{CLEAR} option."`"
	exit 1
}

DEVICE="`fullpath "$DEVICE"`"

BUILD_FS="build_$TOFS"
BUILD_FS_OPS="-v"
BUILD_FS_NOOPERATION="-n"

AUTOBLOCKSIZE="4096"
MINBLOCKSIZE="512"
MAXBLOCKSIZE="$PAGESIZE"

MAXLABELLEN="16"
LABELOPT="-L"

[[ "$BLOCKSIZE" != "AUTO" ]] &&
{
	BLOCKLOG=9
	while (( $[1<<$BLOCKLOG] < $BLOCKSIZE )); do
		BLOCKLOG=$[$BLOCKLOG+1]
	done

	(( $[1<<$BLOCKLOG] == $BLOCKSIZE )) ||
	{
		PRINT "`$GT"{BLOCKSIZE} is bad blocksize. It must be power of 2 and not less than 512."`"
		exit 1
	}

	(( $BLOCKSIZE < $MINBLOCKSIZE )) &&
	{
		PRINT "`$GT"{BLOCKSIZE} is too small. Minimal blocksize in Linux is {MINBLOCKSIZE}"`"
		exit 1
	}

	(( $BLOCKSIZE > $MAXBLOCKSIZE )) &&
	{
		PRINT "`$GT"{BLOCKSIZE} is too big. Maximal blocksize in Linux is {MAXBLOCKSIZE}"`"
		exit 1
	}
}

case "$TOFS" in
	'ext2')
		BUILD_FS="build_e2fs"

		MINBLOCKSIZE="1024"
		[ -b "$DEVICE" ] || BUILD_FS_OPS="$BUILD_FS_OPS -F"
		;;
	'ext3')
		BUILD_FS="build_e2fs"
		BUILD_FS_OPS="-v -j"

		MINBLOCKSIZE="1024"
		[ -b "$DEVICE" ] || BUILD_FS_OPS="$BUILD_FS_OPS -F"
		;;
	'xfs')
		BUILD_FS_NOOPERATION="-N"
		BUILD_FS_OPS="$BUILD_FS_OPS -f"
		MAXLABELLEN="12"
		;;
	'any')
		BUILD_FS="build_it"
		LABELOPT=""
		MAXLABELLEN="0"
		;;
	*)
		PRINT "`$GT""\
"Not supported destination filesystem: {TOFS}
I can convert to ext2/ext3/xfs/any only"`"
		exit 1
		;;
esac

[ -z $LABELOPT ] && [[ "$LABEL" != "AUTO" ]] &&
{
	PRINT "`$GT"Sorry {TOFS} doesn't support labels."`"
	exit 1
}

[[ "$BLOCKSIZE" == "AUTO" ]] && BLOCKSIZE=$AUTOBLOCKSIZE

(( $BLOCKSIZE < $MINBLOCKSIZE )) &&
{
	PRINT "`$GT"{BLOCKSIZE} is too small for the filesystem. Minimal blocksize for {TOFS} is {MINBLOCKSIZE}."`"
	exit 1
}

(( $BLOCKSIZE > $MAXBLOCKSIZE )) &&
{
	PRINT "`$GT"{BLOCKSIZE} is too big for the filesystem. Maximal blocksize for {TOFS} is {MAXBLOCKSIZE}."`"
	exit 1
}

checkutilities "`$GT"It is neccessary for converting to"` $TOFS" $BUILD_FS || exit 1

[[ "$UID" != "0" ]] &&
{
    echo -e `$GT""\
"Sorry, you are not root.
But I would use mount."`
    exit 1
}

[ -d "$TMPDIR" ] ||
{
	PRINT "`$GT"{TMPDIR} is not directory"`"
	exit 1
}

TMPDIR="$TMPDIR/$SCRIPT$$"
mkdir $TMPDIR

[ -d "$TMPDIR" ] && [ -w "$TMPDIR" ] ||
{
	PRINT "`$GT"Access denied to {TMPDIR}."`"
	exit 1
}

TMPDIRDEVICE="`df -P "$TMPDIR" | awk -- '(NR==2) {print $1}'`"

[[ "$TMPDIRDEVICE" == "$DEVICE" ]] &&
{
	echo -e "`$GT"Temporary directory cannot be at convert device"`"
	exit 1
}

ANYMOUNTPOINT="$TMPDIR/anymountpoint"

MOUNTPOINT="$TMPDIR/mountpoint"

RESCUELIST="$TMPDIR/rescue.list"
RESCUEDIR="$TMPDIR/rescue"

WASMOUNTED="N"

[[ "$FROMFS" == "ntfs-3g" ]] &&
{
	checkutilities "`$GT"It is neccessary for converting from"` $FROMFS" ntfs-3g || exit 1
}

MOUNTEDLOOPDEVICE=""

mount | grep -q "$DEVICE" && WASMOUNTED="Y"

[ "$WASMOUNTED" == "Y" ] || [ -b "$DEVICE" ] ||
{
	LOOPDEVICES=`mount | losetup -j "$DEVICE" | awk -- '{sub(/:$/, "", $1); print $1}'`
	for dev in $LOOPDEVICES; do
		mount | grep -q "$dev" && 
		{
			WASMOUNTED="Y"
			MOUNTEDLOOPDEVICE="$dev"
			break
		}
	done
}

if [[ "$FROMFS" == "ntfs-3g" && $WASMOUNTED == "Y" ]]; then
	MOUNTEDDEVICE="$DEVICE"
	[ "$MOUNTEDLOOPDEVICE" ] && MOUNTEDDEVICE="$MOUNTEDLOOPDEVICE"
	MOUNTPOINT="`mount | awk -- '($1 == "'"$MOUNTEDDEVICE"'") {print $3; exit}'`"

	umount "$MOUNTEDDEVICE" ||
        {
		PRINT "`$GT""\
"{DEVICE} was mounted and I cannot umount it
for convert it from ntfs-3g"`"
		exit 1
	}	
fi

if [[ "$WASMOUNTED" == "Y" && "$FROMFS" != "ntfs-3g" ]]; then

	MOUNTEDDEVICE="$DEVICE"
	[ "$MOUNTEDLOOPDEVICE" ] && MOUNTEDDEVICE="$MOUNTEDLOOPDEVICE"
	MOUNTPOINT="`mount | awk -- '($1 == "'"$MOUNTEDDEVICE"'") {print $3; exit}'`"
	MOUNTOPTIONS="`mount | awk -- '($1 == "'"$MOUNTEDDEVICE"'") {sub (/^\(/, "", $6); sub (/\)$/, "", $6); print $6}'`"
	MOUNTEDFS="`mount | awk -- '($1 == "'"$MOUNTEDDEVICE"'") {print $5; exit}'`"

	[[ "$MOUNTEDFS" != "any" ]] && [ "$INODETABLE" ] &&
        {
		PRINT "`$GT""\
"{INODETABLE} inodetable was specified. But filesystem mounted 
as {MOUNTEDFS}, not {UNDER}anyfs{CLEAR}"`"
		exit 1
	}	

	[[ "$MOUNTEDFS" == "any" ]] &&
	{
		MOUNTEDIT="`echo $MOUNTOPTIONS | awk -- '{sub(/.+,inodetable=/, ""); sub(/,.*$/, ""); print}'`"
		MOUNTOPTIONS="`echo $MOUNTOPTIONS | awk -- '{sub(/inodetable='"${MOUNTEDIT//\//\\\/}"'/, ""); sub(/,,/, ","); sub(/,$/, ""); sub(/^,/, ""); print}'`"
		[[ "${MOUNTEDIT:0:1}" == "/" ]] ||
		{
			[ -z "$INODETABLE" ] &&
			{
				PRINT "`$GT""\
"anyfs mounted with specified relative path to inodetable: {MOUNTEDIT}
Please use {UNDER}--inodetable{CLEAR} option to specify inodetable"`"
				exit 1
			} ||
			{
				MOUNTEDITFILE=${MOUNTEDIT##*/}
				INODETABLEFILE=${INODETABLE##*/}
				[[ "$MOUNTEDITFILE" == "$INODETABLEFILE" ]] ||
				{
					PRINT "`$GT""\
"In options of mounted anyfs specified {MOUNTEDITFILE} inodetable file
And you specify {INODETABLEFILE}. I don't think that it is the same file."`"
					exit 1
				}
			}
		}

		[[ "${MOUNTEDIT:0:1}" == "/" ]] && [ "$INODETABLE" ] &&
		{
			IDENTMOUNTEDIT="`stat -c "%D %i" $MOUNTEDIT`"
			IDENTINODETABLE="`stat -c "%D %i" $INODETABLE`"

			[[ "$IDENTMOUNTEDIT" == "$IDENTINODETABLE" ]] ||
			{
				PRINT "`$GT""\
"anyfs was mounted with {MOUNTEDIT} inode table
But you specify {INODETABLE}"`"
				exit 1
			}
		}

		INODETABLE="$MOUNTEDIT"
	}

	[ "$INODETABLE" ] && MOUNTOPTIONS="$MOUNTOPTIONS,inodetable=$INODETABLE"

	[[ "$FROMFS" == "AUTO" ]] &&
	{
		FROMFS="$MOUNTEDFS"
	} ||
	{
		[[ "$FROMFS" == "$MOUNTEDFS" ]] &&
		{
			PRINT "`$GT""\
"{FROMFS} source filesystem was specified but {DEVICE}
mounted as {MOUNTEDFS} filesystem"`"
			exit 1
		}
	}

	[[ "$FROMFS" == "any" ]] &&
	{
		umount "$MOUNTPOINT" ||
		{
			echo -e `$GT"Error while unmount anyfs"`
			exit 1
		}
	} ||
	{
		mount -o remount,"$MOUNTOPTIONS",ro "$MOUNTPOINT" ||
		{
			echo -e `$GT"Error while remount device in read-only mode"`
			exit 1
		}
	}
else
	mkdir -p "$MOUNTPOINT"

	[[ "$FROMFS" == "any" ]] && [ -z "$INODETABLE" ] &&
	{
		PRINT "`$GT"Can't mount anyfs without inodetable. Use {BOLD}--inodetable{CLEAR} option"`"
		exit 1
	}

	[ "$INODETABLE" ] && 
	{
		[ -f "$INODETABLE" ] ||
		{
			PRINT "`$GT"{INODETABLE} is not regular file"`"
			exit 1
		}

		[[ "$FROMFS" == "AUTO" ]] &&
		FROMFS="any"

		[[ "$FROMFS" == "any" ]] ||
		{
			PRINT "`$GT""\
"{INODETABLE} inodetable and {FROMFS} source filesystem was specified.
But {UNDER}--inodetable{CLEAR} is option for converting from {UNDER}anyfs{CLEAR}"`"
			exit 1
		}
	}

	TYPEFSOPS=""
	[[ "$FROMFS" == "AUTO" ]] || TYPEFSOPS="-t $FROMFS"

	MOUNTOPTIONS="rw"

	if [[ "$FROMFS" == "ntfs-3g" ]]; then
		MOUNTOPTIONS="$MOUNTOPTIONS,blkdev"

		LOOPDEVICE=""
		[ -b "$DEVICE" ] || 
		{
			LOOPDEVICE="`losetup -f`" ||
			{
				PRINT "`$GT""\
"Error while search free loop device for mount ntfs-3g in blkdev mode"`"
				exit 1
			}

			losetup "$LOOPDEVICE" "$DEVICE" ||
			{
				PRINT "`$GT""\
"Error while setup loop device for mount ntfs-3g in blkdev mode"`"
				exit 1
			}
		}

		MOUNTDEVICE="$DEVICE"
		[ "$LOOPDEVICE" ] && MOUNTDEVICE="$LOOPDEVICE"

		ntfs-3g "$MOUNTDEVICE" "$MOUNTPOINT" -o "$MOUNTOPTIONS",ro
	else
		[ -b "$DEVICE" ] || MOUNTOPTIONS="rw,loop"
		[ "$INODETABLE" ] && MOUNTOPTIONS="$MOUNTOPTIONS,inodetable=$INODETABLE"

		[[ "$FROMFS" == "any" ]] ||
		mount $TYPEFSOPS -o "$MOUNTOPTIONS",ro "$DEVICE" "$MOUNTPOINT" ||
		{
			echo -e `$GT"Error while mount device in read-only mode"`
			exit 1
		}

		[ -b "$DEVICE" ] || 
		{
			LOOPDEVICES=`mount | losetup -j "$DEVICE" | awk -- '{sub(/:$/, "", $1); print $1}'`
			for dev in $LOOPDEVICES; do
				mount | grep -q "$dev" && 
				{
					MOUNTEDLOOPDEVICE="$dev"
					break
				}
			done
		}

		MOUNTDEVICE="$DEVICE"
		[ "$MOUNTEDLOOPDEVICE" ] && MOUNTDEVICE="$MOUNTEDLOOPDEVICE"

		[[ "$FROMFS" ==  "AUTO" ]] &&
			FROMFS="`mount | awk -- '($1 == "'"$MOUNTDEVICE"'") {print $5}'`"
	fi
fi

[ -z "$INODETABLE" ] && INODETABLE="$TMPDIR/inodetable"

[[ "$LABEL" == "AUTO" ]] &&
{
	LABEL="`mount | awk -- '($1 == "'"$DEVICE"'") {print $7; exit}'`"
	LABEL="${LABEL#[}"
	LABEL="${LABEL%]}"

	[ -z "$LABEL" ] &&
	{
		case "$FROMFS" in
			'ext2'|'ext3')
			( which "e2label" >/dev/null 2>&1 ) &&
			    LABEL="`e2label "$DEVICE"`"
			;;
			'xfs')
			( which "xfs_db" >/dev/null 2>&1 ) &&
			    LABEL="`xfs_db -fc "label" "$DEVICE" | awk -- '{sub(/^label = \"/, ""); sub(/\"$/, ""); print; }'`"
			;;
			'iso9660'|'udf')
			( which "isoinfo" >/dev/null 2>&1 ) &&
			    LABEL="`isoinfo -d -i "$DEVICE" | awk -- '/^Volume id: / {sub(/^Volume id: /, ""); print; exit;}'`"
			;;
			'jfs')
			( which "jfs_tune" >/dev/null 2>&1 ) &&
			    LABEL="`jfs_tune -l "$DEVICE" | awk -- '/^Volume label:/ {sub(/^Volume label:[ \t]*'\''/, ""); sub(/'\''$/, ""); print; exit;}'`"
			;;
			'reiserfs')
			( which "debugreiserfs" >/dev/null 2>&1 ) &&
			    LABEL="`debugreiserfs "$DEVICE" 2>/dev/null | awk -- '/^LABEL:/ {sub(/^LABEL: /, ""); print; exit;}'`"
			;;
			'ntfs-3g')
			( which "ntfslabel" >/dev/null 2>&1 ) &&
			    LABEL="`ntfslabel "$DEVICE"`"
			;;
		esac
	}
}

(( ${#LABEL} > $MAXLABELLEN )) &&
{
	[ $LABELOPT ] && PRINT "`$GT"{LABEL} label would be truncated to {MAXLABELLEN} length."`"
	LABEL="${LABEL:0:$MAXLABELLEN}"
}

LABEL="${LABEL// /_}"
[ -z "$LABEL" ] && LABELOPT=""

HANDLER()
{
	[ -z "$1" ] || echo -e "$1"
	echo -e "${BOLD}${RED}`$GT"Exit"`${CLEAR}"

	if [[ "$WASMOUNTED" == "Y" ]]; then
		if [[ "$FROMFS" == "any" ]]; then
			anyfuse "$INODETABLE" "$DEVICE" "$MOUNTPOINT"
		else
			mount -o remount,"$MOUNTOPTIONS" "$MOUNTPOINT"
		fi
	else
		umount "$MOUNTPOINT"
		rmdir "$MOUNTPOINT"
	fi

	[[ ("$DELETE_IT" == "AUTO" && "$RMINODETABLE" == "Y") || 
		"$DELETE_IT" == "SUCCESS" || 
		"$DELETE_IT" == "NEVER" ]] || RMINODETABLE="N"

	[[ "$RMINODETABLE" == "Y" ]] && 
		rm -f "$INODETABLE" ||
		PRINT "`$GT"Inode table was saved. See {INODETABLE}."`"

	[[ "$FROMFS" == "ntfs-3g" || "$FROMFS" == "fuseblk" ]] && [ -f "$RESCUELIST" ] ||
		PRINT "`$GT"See {RESCUELIST} for list of files not aligned with block boundaries."`"

	[ "$LOOPDEVICE" ] && [[ "$WASMOUNTED" == "N" ]] && losetup -d "$LOOPDEVICE"

	rmdir "$TMPDIR"

	exit 1
}

[[ "$FROMFS" != "any" ]] && fuser -m "$MOUNTPOINT" >/dev/null &&
{
	echo -e "`$GT"Device is busy."`"
	HANDLER
}

OLDBLOCKSIZE=`stat -c "%s" -f "$MOUNTPOINT"`
[[ "$FROMFS" == "any" ]] && 
{
	OLDBLOCKSIZE=$[16#`head -n 1 "$INODETABLE" | awk -- '{print $2}'`
									]
}

FIRSTDESTROYSTEP=5
[[ "$TOFS" == "any" ]]  && FIRSTDESTROYSTEP=`$GT"Never"`
(( $BLOCKSIZE > $OLDBLOCKSIZE )) && FIRSTDESTROYSTEP=3
[[ "$FROMFS" == "any" ]] && FIRSTDESTROYSTEP=`$GT"Never"`

echo
PRINT "`$GT"Converting of {DEVICE} from {FROMFS} to {TOFS}
New filesystem label: {LABEL}
Old filesystem blocksize: {OLDBLOCKSIZE}
Blocksize for new filesystem: {BLOCKSIZE}
Mount point: {MOUNTPOINT}
Mount options: {MOUNTOPTIONS}
Temporary directory: {TMPDIR}
Inode table: {INODETABLE}
First step which destroy old filesystem: {FIRSTDESTROYSTEP}"`"

[ "$LOOPDEVICE" ] &&
	PRINT "`$GT"Loop Device: {LOOPDEVICE}"`"

[[ "$FROMFS" == "ntfs-3g" || "$FROMFS" == "fuseblk" ]] &&
{
	PRINT "`$GT"Rescue List: {RESCUELIST}
Rescue Directory: {RESCUEDIR}"`"
}

RMINODETABLE="Y"
[[ "$FROMFS" == "any" ]] && RMINODETABLE="N"

[[ "$DRYRUN" == "Y" ]] && HANDLER "`$GT"Dry run {BOLD}{GREEN}successful{CLEAR}"`"

trap HANDLER 2

SLEEP()
{
	SECS="$1"
	(( "$SECS" <= 0 )) && return
	PRINT "`$GT"Sleep {SECS} seconds..."` $2"
	for i in `seq $SECS -1 1`; do
		echo -n "$i..."; sleep 1
	done
	echo "0...";
}

[[ "$DONTSTOP" == "N" ]] &&
{
	PRINT "`$GT"Please, {BOLD}attentively{CLEAR} check information above,
and then press {UNDER}Enter{CLEAR}"`"
	read
}

echo
PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}1{CLEAR}. {BOLD}{CYAN}Building inode table{CLEAR} (with {BOLD}build_it{CLEAR})."`"

if [[ "$FROMFS" == "any" ]]; then
	echo -e "`$GT"O.K. anyfs already has inode table"`"
	echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
else
	BUILD_IT_OPTIONS=""
	BUILD_IT_OUTPUT="/dev/stdout"

	[[ "$FROMFS" == "ntfs-3g" || "$FROMFS" == "fuseblk" ]] && 
	{
		BUILD_IT_OPTIONS="-s"
		BUILD_IT_OUTPUT="$RESCUELIST"
	}

	[[ "$DEBUG" == "Y" ]] &&
		echo "[DEBUG] build_it $BUILD_IT_OPTIONS \"$MOUNTPOINT\" \"$INODETABLE\" >\"$BUILD_IT_OUTPUT\""
	build_it $BUILD_IT_OPTIONS "$MOUNTPOINT" "$INODETABLE" >"$BUILD_IT_OUTPUT" ||
	{
		echo -e "`$GT"Error while building inode table"`"
		HANDLER
	}
	echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
fi

if [[ "$FROMFS" == "ntfs-3g" || "$FROMFS" == "fuseblk" ]]; then

	echo
	PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}1a{CLEAR}. {BOLD}{CYAN}Rescue (copy) files not aligned with block boundaries{CLEAR}."`"

	RESCUELIST_ENTRIES="`cat "$RESCUELIST" | wc -l`"
	if (( $RESCUELIST_ENTRIES )); then
		mkdir "$RESCUEDIR"

		RESCUE_SIZE="`cat "$RESCUELIST"| tr '\n' '\0' | xargs -0 du -bc | awk 'END {print $1}'`"
		RESCUE_SIZE=$[$RESCUE_SIZE/1024 + 4*$RESCUELIST_ENTRIES]

		TOTAL_SIZE="`df -kP "$MOUNTPOINT" | awk 'END {print $3}'`"
		DISK_FREE="`df -kP "$RESCUEDIR" | awk 'END {print $4}'`"

		if (( $RESCUE_SIZE > $TOTAL_SIZE*3/4 )); then
			PRINT "`$GT""\
"Seems more than 75% space on the filesystem is files not aligned 
with block boundaries. 
So seems it will easier to use cp && mkfs && cp for converting this filesystem.
{BOLD}Please, check if the filesystem is encrypted or compressed.{CLEAR}"`"
			
			HANDLER
		fi

		if (( $RESCUE_SIZE > $DISK_FREE )); then
			PRINT "`$GT""\
"Sorry, not enough space on filesystem with temporary directory to rescue 
all files not aligned with block boundaries. 
Use {UNDER}'--tmpdir'{CLEAR} option to point other temporary directory."`"

			if (( $RESCUE_SIZE > $TOTAL_SIZE/2 )); then
				PRINT "`$GT""\
"More than 50% space on the filesystem is files not aligned 
with block boundaries.
{BOLD}Please, check if the filesystem (or some files/directories) is 
	encrypted or compressed.{CLEAR}"`"
			elif (( $RESCUE_SIZE > $TOTAL_SIZE/4 )); then
				PRINT "`$GT""\
"More than 25% space on the filesystem is files not aligned 
with block boundaries.
{BOLD}Please, check if some files/directories is encrypted or compressed.{CLEAR}"`"
			fi

			HANDLER
		fi

		while read file; do
			RELPATH="${file#$MOUNTPOINT}"
			RELDIRPATH="${RELPATH%/*}"

			FULLRESCUEPATH="$RESCUEDIR/$RELPATH"
			FULLRESCUEDIRPATH="$RESCUEDIR/$RELDIRPATH"

			mkdir -p "$FULLRESCUEDIRPATH"
			cp -fp "$file" "$FULLRESCUEPATH" ||
			{
				echo -e "`$GT"Error while copy file"`"
				HANDLER
			}
		done < "$RESCUELIST"

		echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
	else
		echo -e "`$GT"O.K. Rescue list is empty"`"
		echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
	fi
fi

[[ "$TOFS" == "any" ]] && [[ "$RMINODETABLE" == "Y" ]] && RMINODETABLE="n"

[[ "$FROMFS" == "any" ]] || umount "$MOUNTPOINT" ||
{
	echo -e "`$GT"Error while unmount device"`"
	HANDLER
}

HANDLER()
{
	[ -z "$1" ] || echo "$1"
	echo -e "${BOLD}${RED}`$GT"Exit"`${CLEAR}"

	mount | grep -q "$MOUNTPOINT" && umount "$MOUNTPOINT"

	if [[ "$RMINODETABLE" == "Y" ]] && [[ "$WASMOUNTED" == "Y" ]]; then
		mount -t "$FROMFS" -o "$MOUNTOPTIONS" "$DEVICE" "$MOUNTPOINT"
	fi

	if [[ "$FROMFS" == "any" ]] && [[ "$WASMOUNTED" == "Y" ]]; then
		anyfuse "$INODETABLE" "$DEVICE" "$MOUNTPOINT"
	fi

	if [[ "$WASMOUNTED" == "N" ]]; then
		rmdir "$MOUNTPOINT"
	fi

	[[ "$DELETE_IT" == "AUTO" && "$RMINODETABLE" == "Y" ]] || RMINODETABLE="N"

	[[ "$RMINODETABLE" == "Y" ]] && rm -f "$INODETABLE" ||
		PRINT "`$GT"Inode table was saved. See {INODETABLE}."`"

	[ "$LOOPDEVICE" ] && [[ "$WASMOUNTED" == "N" ]] && losetup -d "$LOOPDEVICE"

	rmdir "$TMPDIR"

	exit 1
}

echo
PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}2{CLEAR}. {BOLD}{CYAN}Noops (check if enough space) reblock anyfs{CLEAR} (with {BOLD}reblock{CLEAR})."`"

FREESPACE()
{
	UTIL="$1"
	PRINT "`$GT""\
"{UTIL} returned {BOLD}{RED}Not Enough Space{CLEAR}
O.K. I try to be helpful and now.."`"

	echo -e "`$GT"Mounting anyfs..."`"

	MOUNTOPTIONS="rw"
	[ -b "$DEVICE" ] || MOUNTOPTIONS="rw,loop"

	anyfuse "$INODETABLE" "$DEVICE" "$MOUNTPOINT" ||
	{
		echo -e "`$GT"Error while mount anyfs filesystem"`"
		HANDLER
	}

	echo
	PRINT "`$GT""\
"So, device was mounted as anyfs to {MOUNTPOINT}
At anyfs you can remove and move files and directories,
create hard links, symbolic links, special files and directories.
You CANNOT create or copy regular files from other filesystems."`"
	echo
	PRINT "`$GT""\
"{BOLD}Now I would run 'bash' at this filesystem.
Please, remove not needed files, or move its to other filesystems.
So clear some space for system information of new FS.
When it would done, print 'exit'. And I try run {UTIL}{BOLD} again.{CLEAR}"`"
	PRINT "`$GT""\
"{UNDER}Do NOT close the console.
Do NOT unmount the anyfs filesystem.{CLEAR}"`"
	echo

	pushd "$MOUNTPOINT" >/dev/null
	bash
	popd >/dev/null

	echo -e "`$GT"Unmounting anyfs..."`"
	umount "$MOUNTPOINT" ||
	{
		echo -e "`$GT"Error while unmount anyfs filesystem"`"
		HANDLER
	}
	echo -e "${BOLD}${BROWN}`$GT"Retry"`${CLEAR}"
}

NOWBLOCKSIZE=$[16#`head -n 1 "$INODETABLE" | awk -- '{print $2}'`
								 ]

if (( $BLOCKSIZE == $NOWBLOCKSIZE )); then
	PRINT "`$GT"Good. Filesystem already has {BLOCKSIZE} blocksize"`"
	echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
else
	while true; do
		[[ "$DEBUG" == "Y" ]] &&
			echo "[DEBUG] reblock -n \"$INODETABLE\" \"$DEVICE\" \"$BLOCKSIZE\""

		reblock -n "$INODETABLE" "$DEVICE" "$BLOCKSIZE" ||
		{
			RETVAL=$?
			(( $RETVAL == $ENOSPC )) &&
			{
				FREESPACE "reblock"
				continue
			}

			echo
			echo -e "`$GT"Error while reblock (no operation)"`"
			HANDLER
		}
		break;
	done
	echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
fi

echo
PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}3{CLEAR}. {BOLD}{CYAN}Reblock anyfs{CLEAR} (with {BOLD}reblock{CLEAR})."`"

if (( $BLOCKSIZE == $NOWBLOCKSIZE )); then
	echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
else
	(( $BLOCKSIZE > $NOWBLOCKSIZE )) &&
	{
		[[ "$RMINODETABLE" == "N" ]] ||
			SLEEP "$PAUSE" "`$GT""\
"It's your {BOLD}last chance{CLEAR} to save the old filesystem.
Press {UNDER}Ctrl+C{CLEAR} to {BOLD}cancel{CLEAR} converting."`"

		RMINODETABLE="N"
	}

	[[ "$DEBUG" == "Y" ]] &&
		echo "[DEBUG] reblock \"$INODETABLE\" \"$DEVICE\" \"$BLOCKSIZE\""

	reblock "$INODETABLE" "$DEVICE" "$BLOCKSIZE" ||
	{
		echo
		echo -e "`$GT"Error while reblock"`"
		HANDLER
	}
	echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
fi

echo
PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}4{CLEAR}. {BOLD}{CYAN}Noops (check if enough space) build new filesystem{CLEAR} (with {BUILD_FS})."`"

if [[ "$TOFS" == "any" ]]; then
	echo -e "`$GT"O.K. Inode table already built"`"
	echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
else
	while true; do
		[[ "$DEBUG" == "Y" ]] &&
			echo "[DEBUG] $BUILD_FS $LABELOPT $LABEL $BUILD_FS_OPS $BUILD_FS_NOOPERATION \"$INODETABLE\" \"$DEVICE\""
		$BUILD_FS $LABELOPT $LABEL $BUILD_FS_OPS $BUILD_FS_NOOPERATION "$INODETABLE" "$DEVICE" ||
		{
			RETVAL=$?
			(( $RETVAL == $ENOSPC )) &&
			{
				FREESPACE "$BUILD_FS"
				continue
			}

			echo
			echo -e "`$GT"Error while build new fs (no operation)"`"
			HANDLER
		}
		break;
	done
	echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
fi

echo
PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}5{CLEAR}. {BOLD}{CYAN}Build new filesystem{CLEAR} (with {BUILD_FS})."`"

[[ "$RMINODETABLE" == "Y" ]] &&
	SLEEP "$PAUSE" "`$GT""\
"It's your {BOLD}last chance{CLEAR} to save the old filesystem.
Press {UNDER}Ctrl+C{CLEAR} to {BOLD}cancel{CLEAR} converting."`"

RMINODETABLE="N"

if [[ "$TOFS" == "any" ]]; then
	echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
else
	[[ "$DEBUG" == "Y" ]] &&
		echo "[DEBUG] $BUILD_FS $LABELOPT $LABEL $BUILD_FS_OPS \"$INODETABLE\" \"$DEVICE\""

	$BUILD_FS $LABELOPT $LABEL $BUILD_FS_OPS "$INODETABLE" "$DEVICE" ||
	{
		echo
		echo -e "`$GT"Error while build new fs"`"
		HANDLER
	}
	echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
fi

if [[ "$FROMFS" == "ntfs-3g" || "$FROMFS" == "fuseblk" ]]; then

	echo
	PRINT "`$GT"{BOLD}Step{CLEAR} {UNDER}5a{CLEAR}. {BOLD}{CYAN}Move rescued files to new filesystem{CLEAR}."`"

	if (( $RESCUELIST_ENTRIES )); then
		if [[ "$TOFS" == "any" ]]; then
			PRINT "`$GT""\
"Cannot move files to read-only anyfs filesystem.
{UNDER}Please, see{CLEAR} {RESCUEDIR} {UNDER}directory for rescued files.{CLEAR}"`"
			echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"
		else
			{
				MOUNTOPTIONS="rw"
				[ -b "$DEVICE" ] || MOUNTOPTIONS="rw,loop"
				[[ "$TOFS" == "any" ]] && MOUNTOPTIONS="$MOUNTOPTIONS,inodetable=$INODETABLE"
				mount -t "$TOFS" -o "$MOUNTOPTIONS" "$DEVICE" "$MOUNTPOINT" ||
				{
					echo
					echo -e "`$GT"Error while mounting new fs"`"
					HANDLER
				}
			}

			while read file; do
				RELPATH="${file#$MOUNTPOINT}"
				RELDIRPATH="${RELPATH%/*}"

				FULLRESCUEPATH="$RESCUEDIR/$RELPATH"
				FULLRESCUEDIRPATH="$RESCUEDIR/$RELDIRPATH"

				mv -f "$FULLRESCUEPATH" "$file" ||
				{
					echo -e "`$GT"Error while move file"`"
					HANDLER
				}
				rmdir --ignore-fail-on-non-empty -p "$FULLRESCUEDIRPATH"
			done < "$RESCUELIST"

			rm -f "$RESCUELIST"

			if [[ "$WASMOUNTED" == "N" ]]; then
				umount "$MOUNTPOINT"
			fi

			echo -e "${BOLD}${GREEN}`$GT"Done"`${CLEAR}"
		fi
	else
		echo -e "`$GT"O.K. Rescue list is empty"`"
		echo -e "${BOLD}${BROWN}`$GT"Skip"`${CLEAR}"

		if [[ "$WASMOUNTED" == "Y" ]]; then
			MOUNTOPTIONS="rw"
			[ -b "$DEVICE" ] || MOUNTOPTIONS="rw,loop"
			[[ "$TOFS" == "any" ]] && MOUNTOPTIONS="$MOUNTOPTIONS,inodetable=$INODETABLE"
			mount -t "$TOFS" -o "$MOUNTOPTIONS" "$DEVICE" "$MOUNTPOINT"
		fi
	fi

elif [[ "$WASMOUNTED" == "Y" ]]; then
	MOUNTOPTIONS="rw"
	[ -b "$DEVICE" ] || MOUNTOPTIONS="rw,loop"
	[[ "$TOFS" == "any" ]] && MOUNTOPTIONS="$MOUNTOPTIONS,inodetable=$INODETABLE"
	mount -t "$TOFS" -o "$MOUNTOPTIONS" "$DEVICE" "$MOUNTPOINT"
fi

[[ ("$DELETE_IT" == "AUTO" && "$RMINODETABLE" == "Y") || 
	"$DELETE_IT" == "NEVER" ]] || RMINODETABLE="N"
[[ "$DELETE_IT" == "SUCCESS" ]] && RMINODETABLE="Y"

[[ "$RMINODETABLE" == "Y" ]] && rm -f "$INODETABLE" ||
PRINT "`$GT"Inode table was saved. See {INODETABLE}."`"

FSATTAB="`cat /etc/fstab | awk -- '( $1 == "'"$DEVICE"'" ) {print $3; exit;}'`"
[ "$FSATTAB" ] && [[ "$FSATTAB" != "$TOFS" ]] &&
	PRINT "`$GT"Don't forget change filesystem of the device in {BOLD}/etc/fstab{CLEAR}"`"

[ "$LOOPDEVICE" ] && [[ "$WASMOUNTED" == "N" ]] && losetup -d "$LOOPDEVICE"

[ -d "$TMPDIR" ] && rmdir "$TMPDIR"

echo -e "${BOLD}${GREEN}`$GT"Successful"`${CLEAR}"

exit 0
