#!/bin/ash
# simple tool to enlarge / fsck savefile
# Copyright (C) James Budiono 2012, 2013, 2020
# License: GNU GPL Version 3 or later
#
# Update Apr 2013: Support encrypted savefile 
# $1-option to pass advanced encryption option
#131113 L18L internationalisation

# std localisation stanza
export TEXTDOMAIN=fatdog
. gettext.sh
# performance tweak - use "C" if there is no localisation
! [ -e $TEXTDOMAINDIR/${LANG%.*}/LC_MESSAGES/$TEXTDOMAIN.mo ] &&
! [ -e $TEXTDOMAINDIR/${LANG%_*}/LC_MESSAGES/$TEXTDOMAIN.mo ] && LANG=C


### configurations
APPTITLE="$(gettext 'Fatdog64 Savefile Tool')"
. $BOOTSTATE_PATH
DEFAULT_ADDSIZE=128	# default add size in MB
DMCRYPT_TEMPNAME=savetool.$(hexdump -e '"%04x"'  -n 2 /dev/urandom)
ONE_MEG=1048576 # use MiB

### runtime variables
savefile=${SAVEFILE_PROTO:-fdsave.ext4} 
savefile=${savefile##*/}	# default savefilename
savesize=					# current size of the savefile (in bytes)
fs_type=					# filesystem type of savefile
freespace=					# available free space where the savefile is
addsize=$DEFAULT_ADDSIZE	# enlarge the file by this amount (default is 128MB)
enc_devtype=				# encryption device type (dmcrypt or crypt or "")
enc_password=				# encryption password
enc_device=					# temp dmcrypt device that holds mounted encrypted savefile
enc_option="$1"				# encrypted savefile, LEAVE BLANK IF NOT SURE.

### wizard step movements
step=1
prev_action=
prev_step=

go_step() { step=$1; }
go_next() { step=$((step + 1)); }
go_prev() { step=$((step - 1)); }
go_exit() { step=exit; }
next_wizard_step() {
	local action=$?
	[ "$1" ] && action=$1
	prev_step=$step
	case $action in
		0) go_next ;;				# Xdialog ok
		3) go_prev ;;				# Xdialog previous
		1|255) go_step cancel ;;	# Xdialog Esc/window kill
	esac
	prev_action=$action
}

# parameter: $1 - use $BACKTITLE
std_wizard_flags() {
	local flags="--title \"$APPTITLE\" --wizard --stdout --separator :"
	[ "$1" ] && flags="$flags --backtitle \"$1\""
	echo $flags
}

### helpers
message() {
	Xdialog --title "$APPTITLE" --infobox "$1" 0 0 10000
}

# get_fs_type of savefile, run guess_fstype first, if fails try disktype
# $1-file, output: fs_type
get_fs_type() {
	fs_type=$(guess_fstype "$1" 2>/dev/null)
	case $fs_type in
		""|unknown) fs_type=$(disktype "$1" | sed -n '/file system/ {s/ *file system.*//; p}' | tr 'A-Z' 'a-z')
	esac
}

# check whether "savefile" mounted on "device" is supported for resizing
# $1-device, $2-savefile output: fs_type if valid, or go_prev if invalid
check_supported_fs() {
	local FILE=$2
	get_fs_type "$1"
	case "$fs_type" in 
		ext*|ntfs|vfat|fat16|fat32|f2fs|btrfs) true ;;
#		*) message "$(eval_gettext '$2 contains $fs_type filesystem, which is not supported by this tool.\nPlease choose another file.')"
		*) message "$(eval_gettext '$FILE contains $fs_type filesystem, which is not supported by this tool.\nPlease choose another file.')"
		   go_prev 
	esac	
}

# open encrypted savefile
# $1-type $2-savefile, $3-password output: $enc_device, true=success
open_dmcrypt() {
	case $1 in
		dmcrypt) echo "$3" | cryptsetup open ${enc_option} "$2" $DMCRYPT_TEMPNAME ;;
		crypt) echo "$3" | cryptsetup open -M plain -c ${enc_option:-aes}-cbc-plain -h plain "$2" $DMCRYPT_TEMPNAME ;;
	esac
	enc_device=	
	[ -e /dev/mapper/$DMCRYPT_TEMPNAME ] && enc_device=/dev/mapper/$DMCRYPT_TEMPNAME
}

# close encrypted savefile
# $1-encrypted device
close_dmcrypt() {
	sleep 1	# delay to finish whatever previous operation was
	cryptsetup close "$1"
}

### wizard actions here
introduction() {
	msg="$(gettext 'This tool enables you to:
- enlarge your savefile.
- check and correct inconsistencies in your savefile.')\n"
	Xdialog --title "$APPTITLE" --fill --yesno "$msg" 0 0
	next_wizard_step
}

choose_savefile() {
	local choice savefile_enc
	msg="$(gettext 'Locate the savefile:')"	
	if choice=$(eval "Xdialog $(std_wizard_flags "$msg") --no-buttons --fselect \"$savefile\" 0 0"); then
		next_wizard_step
		savefile=$choice
		
		# do some sanity checks here
		if [ ! -f "$savefile" ]; then
			message "$(eval_gettext '$savefile does not exist.')\n$(gettext 'Please choose another file.')"
			go_prev
			return
		fi
		
		# check for encrypted savefile & supported filesystem
		if [ "$(guess_fstype "$savefile")" = "crypto_LUKS" ]; then
			savefile_enc='_dmcrypt_'
		else
			savefile_enc="$savefile"
		fi

		case "${savefile_enc##*/}" in
			*_dmcrypt_*|*_crypt_*) 
				case "${savefile_enc##*/}" in
					*_dmcrypt_*) enc_devtype=dmcrypt ;;
					*_crypt_*) enc_devtype=crypt ;;
				esac
				if enc_password=$(Xdialog --stdout --title "$APPTITLE" --password --inputbox "$(gettext 'Please provide password for encrypted savefile')" 0 0); then
					if open_dmcrypt $enc_devtype "$savefile" "$enc_password"; then
						check_supported_fs "$enc_device" "$savefile"
						close_dmcrypt "$enc_device"
					else
						message "$(eval_gettext 'Encrypted $savefile cannot be opened, perhaps wrong password?')"
						go_prev					
					fi
				fi
				;;
				
			*)	enc_devtype=
				check_supported_fs "$savefile" "$savefile" 
				;;
			
		esac
		
	else
		next_wizard_step	
	fi
}

choose_action() {
	local action enlarge fsck
	msg="$(gettext 'What do you want to do today?')"
	enlarge='enlarge "Enlarge savefile" off'
	fsck='fsck "Check and repair savefile" off'
	action=$(eval "Xdialog $(std_wizard_flags) --no-tags --radiolist \"$msg\" 0 0 5 $enlarge $fsck")
	next_wizard_step
	case $action in
		fsck) go_step fsck ;;
	esac
}

get_new_size() {
	# get current savefile size and free space on the partition
	savesize=$(stat -c %s "$savefile")
	freespace=$(( ( $(stat -fc %a "$savefile") * $(stat -fc %S "$savefile") ) / $ONE_MEG ))
	
	# prompt
	while :; do
		msg=$(cat << EOF
$(gettext 'Savefile') $savefile ($enc_devtype $fs_type)\n
$(gettext 'Current size is') $(( savesize / $ONE_MEG )) MiB.\n
$(gettext 'Available free space:') $freespace MiB.

$(gettext 'Please specify how much space you want to add to the savefile in megabytes (MiB).')
EOF
)
		if addsize=$(eval "Xdialog $(std_wizard_flags) --left --inputbox \"$msg\" 0 0 $addsize"); then
			[ "$addsize" -gt 0 ] && next_wizard_step && break
			message "$(gettext 'Please enter a positive integer value!')"		
		else
			next_wizard_step
			break
		fi
	done
}

confirmation() {
	local current=$(( savesize / $ONE_MEG ))
	local new=$(( savesize / $ONE_MEG + addsize))
	local free=$(( freespace - addsize ))
 
	msg="$(eval_gettext '
You are about to enlarge $savefile ($enc_devtype $fs_type)
Current size: $current MiB
Enlarge by:   $addsize MiB
New size:     $new MiB

Available free space: $freespace MiB
Free space after enlarge: $free MiB ')

$(gettext 'Confirm that the above looks correct, then click YES to proceed.')
"

	eval "Xdialog $(std_wizard_flags) --fixed-font --left --yesno \"$msg\" 0 0"
	next_wizard_step
}

# resize then fsck
do_enlarge() {
	local device="$savefile" tmpfifo
	tmpfifo=$(mktemp -p /tmp savetool.XXXXXXXX)
	{
        [ -n "$enc_devtype" ] && MOUNT_TEXT="$(eval_gettext '(mounted on $enc_device)')"
		echo "$(eval_gettext 'Enlarging $savefile by $addsize MiB $MOUNT_TEXT ...')"
		dd if=/dev/zero of="$savefile" bs=1 count=0 seek=$(( savesize + addsize * $ONE_MEG )) 
		[ -n "$enc_devtype" ] && open_dmcrypt $enc_devtype "$savefile" "$enc_password" && device=$enc_device
		case "$fs_type" in 
			ext*) e2fsck -y "$device"
				  resize2fs -f "$device"
				  e2fsck -y "$device" ;;
			ntfs) echo y | ntfsfix "$device"
				  echo y | ntfsresize -f -v "$device"
				  echo y | ntfsfix "$device" ;;
			vfat|fat16|fat32) echo "resizing $fs_type ..."
				  dosfsck -y -w "$device"
				  fs-resize "$device"
				  dosfsck -y -w "$device" ;;
			f2fs) fsck.f2fs -y "$device" 
				  resize.f2fs -t $(( (savesize + addsize * $ONE_MEG)/512 )) "$device" 
				  fsck.f2fs -y "$device" ;;
			btrfs) btrfs check --repair "$device"
				   mkdir -p /tmp/btrfs-resize
				   mount -t btrfs "$device" /tmp/btrfs-resize
				   btrfs filesystem resize +${addsize}M /tmp/btrfs-resize
				   umount /tmp/btrfs-resize
				   rmdir /tmp/btrfs-resize
				   ;;
		esac 
		echo "$(gettext 'New size is now:') $(( $(stat -c %s "$savefile") / $ONE_MEG )) MiB"
		echo "$(gettext 'Operation completed.')"
		[ -n "$enc_devtype" ] && close_dmcrypt "$enc_device"
	} > $tmpfifo 2>&1 &
	Xdialog --title "$APPTITLE" --backtitle "$(gettext 'Click OK to close when you see "Operation completed".')" --no-cancel --tailbox $tmpfifo 20 100
	rm $tmpfifo
	go_exit
}

# fsck only
do_fsck() {
	local device="$savefile" tmpfifo
	tmpfifo=$(mktemp -p /tmp savetool.XXXXXXXX)
	{
		echo "Checking $savefile $([ -n "$enc_devtype" ] && echo \(mounted on $enc_device\) ) ..."
		[ -n "$enc_devtype" ] && open_dmcrypt $enc_devtype "$savefile" "$enc_password" && device=$enc_device		
		case "$fs_type" in
			ext*) e2fsck -y "$device" ;;
			ntfs) echo y | ntfsfix "$device" ;;
			vfat|fat16|fat32) dosfsck -y -w "$device" ;;
			f2fs) fsck.f2fs -y "$device" ;;
			btrfs) btrfs check --repair "$device" ;;
		esac 
		echo "$(gettext 'Operation completed.')"
		[ -n "$enc_devtype" ] && close_dmcrypt "$enc_device"
	} > $tmpfifo 2>&1 &
	Xdialog --title "$APPTITLE" --backtitle "$(gettext 'Click OK to close when you see "Operation completed".')" --no-cancel --tailbox $tmpfifo 20 100
	rm $tmpfifo
	go_exit
}


################ main ##################
case $1 in
	--help|-h) T=${0##*/}
		message "
$(eval_gettext 'Usage: $T [encrypted-savefile-options]')

$(gettext 'Tool to resize and check the integrity of your savefile.
Must be used when running *without* savefile, ie boot with savefile=none first.')

$(gettext 'WARNING: Do not specify encrypted-savefile-options unless you know what you are doing.')"
	exit ;;
esac

if [ "$SAVEFILE_MOUNT" ]; then
	message "$(eval_gettext '$APPTITLE can only be used when you run without using a savefile.
Reboot your system and specify savefile=none as your boot options.')"
else
	[ $(id -u) -ne 0 ] && exec gtksu "$APPTITLE" "$0"
	while true; do 
		case $step in 
			1) introduction ;;
			2) choose_savefile ;;
			3) choose_action ;;
			4) get_new_size ;;
			5) confirmation ;;
			6) do_enlarge ;;
			7|exit) break ;;
			fsck) do_fsck ;;			
			cancel) go_exit ;;
		esac
	done
fi
