#!/bin/dash
########  Click image files to mount & unmount  ########
# Originally by: Terry Becker	aka: SunBurnt
# Rewritten by jamesbond 2012, 2014, 2016, 2018, 2019 for Fatdog64 (License: GPLv3)
# Support for LUKS encrypted images/savefiles by JakeSFR 2021 (License: GPLv3)
# Supported filetypes: iso, sfs, 2fs/ext2, 3fs/ext3, 4fs/ext4, img, initrd/initrd.gz
# parameter: $1 - filename

### configuration
MKINITRD_NAME=repack-initrd

### helper - create a script to re-pack initrd
# create a simple script to rebuild initrd
# $1-location of original initrd, $2-compression type
create_mkinitrd() {
	local COMPRESSOR 
	touch $MKINITRD_NAME; chmod +x $MKINITRD_NAME
	case $2 in
		gzip) COMPRESSOR="gzip -9" ;;
		bzip2) COMPRESSOR="bzip2" ;;
		xz) COMPRESSOR="xz --check=crc32 --lzma2=dict=512KiB" ;;
		none) COMPRESSOR=cat;;
	esac
	cat >> $MKINITRD_NAME << EOF
#!/bin/dash
[ \$(id -u) -ne 0 ] && exec gtksu "Rebuild initrd" \$(readlink -f "\$0")
SOURCE="\$(dirname "\$0")"; cd "\$SOURCE"
exec Xdialog --no-buttons --title "$MKINITRD_NAME" --infobox "Working, please wait ..." 0 0 1000000 &
XPID=\$!
find . | grep -v $MKINITRD_NAME | cpio -o -H newc | $COMPRESSOR > "$1"
kill \$XPID
su \$USER -c "rox -D \"\$SOURCE\""; rm -rf "\$SOURCE"
exec Xdialog --title "$MKINITRD_NAME" --infobox "Repacking done, $1 is now updated." 0 0 10000
EOF
}

# $1-mountpoint
remove_mountpoint() {
	# workaround a nasty aufs bug
	# only remove mountpoint if there is exactly one aufs root
	# otherwise aufs will *hard-lockup*
	if [ $(ls -d /sys/fs/aufs/* | wc -l) -eq 1 ]; then
		rmdir "$1"
	fi
	return 0 # always return success
}

# $1-path to sfs
find_loop_device() {
	local p pp
	for p in /sys/block/loop*; do
		if [ -e $p/loop ]; then
			read pp < $p/loop/backing_file
			if [ "$pp" = "$1" ]; then
				echo /dev/${p##*/}
			fi
		fi
	done
}

# $1-cryptpoint
close_luks() {
	if [ "$filesystem" = 'crypto_LUKS' ]; then
		cryptsetup close "$1"
	fi
}

############### main ##################
! [ -e "$1" ] && exit 1

### need root
[ $(id -u) -ne 0 ] && exec gtksu "File Mounter" "$0" $(readlink -f "$1")

### get image file and generate mountpoint
filepath=$1
fullpath=$(readlink -f "$filepath")
filename=${filepath##*/}
extension=${filename##*.}
[ "$extension" = "$filename" ] && extension=
case  "$filename" in
	initrd|initrd.?z|initrd.?z?|initrd-*) extension=$filename ;;
esac
extension=$(echo $extension | tr "A-Z" "a-z")
mountpoint=$(echo $fullpath | tr "'\\\\/ .;:?*[]{}()<>&|\"\`\t" +)
cryptpoint="$mountpoint"	# use the same name for /dev/mapper/... device
filesystem=$(guess_fstype "$filepath")
#echo $filepath $filename $extension $mountpoint $filesystem

# only supported extensions
case $extension in
	iso|sfs|2fs|3fs|4fs|ext2|ext3|ext4|img|btrfs|initrd|initrd.?z|initrd.?z?|initrd-*)
		true
		;;
	*)
		Xdialog --title "Error!" --infobox "Unsupported file type: $extension" 0 0 10000
		exit
		;;
esac

# handle LUKS encrypted images
if [ "$filesystem" = 'crypto_LUKS' ]; then
	if ! ls /dev/mapper | grep -qw "^${cryptpoint}$"; then
		result=$(Xdialog --stdout --title "Request" --check "Use keyfile instead of a password" false \
					--password --input "Enter password for $filename:" 0 0)
		[ $? -ne 0 ] && exit

		password="$(echo "$result" | head -n 1)"
		checkbox="$(echo "$result" | tail -n 1)" 

		if [ "$checkbox" = 'checked' ]; then
			keyfile=$(Xdialog --stdout --title "Request" --backtitle "Select a keyfile for $filename:" \
						--fselect "$HOME" 0 0)
			[ $? -ne 0 ] && exit
			cryptsetup -d "$keyfile" open "$filepath" "$cryptpoint"
		else
			echo "$password" | cryptsetup open "$filepath" "$cryptpoint"
		fi

		if [ $? -ne 0 ]; then
			Xdialog --title "Error!" --infobox "Failed to decrypt $filename." 0 0 10000
			exit
		fi
	fi

	# now the corresponding dm-* becomes a new $mountpoint and /dev/$mountpoint becomes a new filepath
	mountpoint=$(grep -H "^${mountpoint}$" /sys/devices/virtual/block/dm-*/dm/name | grep -wo 'dm-[0-9]\+' | head -n 1)
	filepath="/dev/$mountpoint"
fi

# mount only if it is not already mounted
if ! grep -q $mountpoint /proc/mounts; then

	case $extension in
		initrd|initrd.?z|initrd.?z?|initrd-*)
			tmpdir=$(mktemp -dp /tmp initrd-XXXXXX)
			chgrp $USER $tmpdir
			chmod a+rwx $tmpdir
			cd $tmpdir
			if case $(file -b --mime-type "$fullpath") in
				*gzip) TYPE="gzip" && zcat "$fullpath" | cpio -i ;;
				*xz) TYPE="xz" && xzcat "$fullpath" | cpio -i ;;
				*bzip2) TYPE="bzip2" && bzcat "$fullpath" | cpio -i ;;
				*) TYPE="none" && cat "$fullpath" | cpio -i ;;
			esac; then
				if [ -w "$fullpath" ]; then
					create_mkinitrd "$fullpath" $TYPE &&
					disposition="
When done, you can rebuild $filename by running $MKINITRD_NAME.\n
If you decide otherwise, simply remove $tmpdir."
				else
					disposition="
When done, simply remove $tmpdir.\n\n
NOTE: you can't rebuild $filename because it isn't writable."
				fi
				su $USER -c "rox $tmpdir"
				Xdialog --left --title "Success" --infobox "
$filename is an initrd archive, and it has been extracted to $tmpdir.\n
Do what you want to do with it.\n
$disposition" 0 0 10000 
			else
				close_luks "$cryptpoint"
				Xdialog --title "Error!" --infobox "Failed to extract $filename." 0 0 10000 
			fi
			;;

		*)
			# ensure we understand the filesystem. Otherwise bail out.
			case $filesystem in
				unknown|"")
					close_luks "$cryptpoint"
					Xdialog --title "Error!" --infobox "Cannot mount $filename.\nIt contains unknown filesystem." 0 0 10000
					;;
				*)
					# prepare to mount
					mountpoint="/mnt/$mountpoint"
					mkdir -p "$mountpoint" 2> /dev/null

					# check if it is already mounted elsewhere
					# (does not work with LUKS containers)
					elsewhere=$(find_loop_device "$fullpath")

					if [ "$elsewhere" ] && ! [ "$filesystem" = 'crypto_LUKS' ]; then
						# yes, do bind mount
						elsewhere="$(awk -v loopdev="$elsewhere" '$1 == loopdev {print $2}' /proc/mounts)"
						mount -o bind "$elsewhere" "$mountpoint"
					else
						# no, do loop mount
						#mount -o loop "$filepath" "$mountpoint"
						# implicit '-o loop' is not needed anymore and prevents mounting dm-* devices
						mount "$filepath" "$mountpoint"
					fi &&

					su $USER -c "rox $mountpoint" ||
					{
						close_luks "$cryptpoint"
						Xdialog --title "Error!" --infobox "Failed to mount $filename." 0 0 10000
					}
					;;
			esac
			;;
	esac

else

	#if ! grep $mountpoint /proc/mounts && busybox losetup -a | grep $fullpath; then
	#	load_sfs.sh	# in losetup but not in /proc/mounts, probably mounted by SFS loader
	#	exit
	#fi
	
	# already mounted, will try to unmount
	su $USER -c "rox -D /mnt/$mountpoint"
	if umount -d /mnt/$mountpoint && remove_mountpoint /mnt/$mountpoint; then
		close_luks "$cryptpoint"
		Xdialog --title "Success!" --infobox "$filename has been un-mounted." 0 0 10000 
	else
		if Xdialog --title "Error!" --yesno "Failed to un-mount $filename, it is currently used by the following process:
$(fuser -m /mnt/$mountpoint | xargs ps -o cmd -p | sed 's/CMD//')\n\n
Do you want me to stop them and try again?" 0 0; then
			fuser -m -k /mnt/$mountpoint
			$0 $filepath
		fi
	fi
	
fi
