#!/bin/sh
. /usr/share/debconf/confmodule
set -e

ARCH=$(udpkg --print-architecture)

log () {
	logger -t iso-scan "$@"
}

mount_device () {
	local dev_to_mount=$1
	local RET

	db_subst iso-scan/progress_mount DRIVE "$dev_to_mount"
	db_progress INFO iso-scan/progress_mount
	mount -t auto -o ro "$dev_to_mount" /hd-media 2>/dev/null
}

is_debian_iso () {
	test -e /cdrom/.disk/info
}

analyze_cd () {
	local dir relfile suite version codename descr

	# Make sure that the iso is usable for the architecture. If so,
	# set the suite and codename to the suite/codename that is on the CD.
	# This assumes that there will be no more than one distribution on
	# the CD, and that one of the testing, stable, or unstable links will
	# point to it. Since the CDs currently have many links, parse the
	# Release file to get the actual suite name to use.
	# Prefer the suite in default-release.
	for dir in $(cat /etc/default-release) $(ls -1 /cdrom/dists/); do
		# FIXME: Would be better if CDs that don't support the ARCH
		# and mini.iso's would be logged as well.
		# FIXME: we should probably also only allow CDs for which
		# the codename matches.
		relfile=/cdrom/dists/"$dir"/Release
		if [ -e "$relfile" ] && \
		   egrep -q "Architectures:.* $ARCH( |$)" "$relfile"; then
			suite=$(sed -n 's/^Suite: *//p' "$relfile")
			codename=$(sed -n 's/^Codename: *//p' "$relfile")
			log "Detected ISO with '$suite' ($codename) distribution"
			version=$(sed -n 's/^Version: *//p' $relfile)

			db_set cdrom/suite "$suite"
			db_set cdrom/codename "$codename"

			log "Detected ISO with distribution '$suite' ($codename)"

			# FIXME: Should we display codename instead of suite?
			# Would be more in line w/ current thinking about releases.
			descr="$(printf "[%s] %s (%s)\n" \
				 "$(echo $iso_device | sed -e 's?^/dev/??')" \
				 "${iso_to_try#.}" \
				 "$suite${version:+ - $version}")"
			# comma is used to separate possible ISO
			echo "$descr" | sed -e 's/,/ /g'

			return 0
		fi
	done

	return 1
}

# Try to mount a file as an iso, and see if it's a Debian cd.
add_usable_iso () {
	local iso_to_try=$1
	local iso_device=$2
	local isodesc
	if ! mount -t iso9660 -o loop,ro,exec $iso_to_try /cdrom 2>/dev/null; then
		log "Failed mounting $iso_to_try (from $iso_device) as an ISO image"
		return
	fi
	ISO_MOUNT_COUNT=$(($ISO_MOUNT_COUNT + 1))

	if is_debian_iso; then
		if isodesc=$(analyze_cd); then
			log "Debian ISO $iso_to_try usable"
			ISOS_FOUND="${ISOS_FOUND:+$ISOS_FOUND, }$isodesc"
		else
			log "Debian ISO not usable, skipping"
		fi
	else
		log "$iso_to_try not a Debian ISO"
	fi
	umount -d /cdrom
}

# scan a list of devices for available Debian ISO
scan_device_for_isos() {
	local look_subdirs=$1
	local devs=$2
	local RET dev_count dev devlist dir iso_list iso opt progress progress_count

	if [ "$look_subdirs" = 1 ]; then
		devlist=$MOUNTABLE_DEVS
		progress=dir
		progress_count=$TOPLEVEL_DIRS_COUNT

	else
		devlist=$devs

		dev_count=0
		for dev in $devs; do
			dev_count=$(($dev_count + 1))
		done
		progress=device
		progress_count=$dev_count

		if [ $dev_count = 1 ] && mount_device $devs; then
			progress=dir
			progress_count=1 # count dirs + mount device directly
			cd /hd-media
			for dir in . ./*; do
				[ -d "$dir" ] && progress_count=$(($progress_count + 1))
			done
			cd /
			umount /hd-media
		fi
	fi
	db_progress START 0 $progress_count iso-scan/progress_title

	ISOS_FOUND=
	ISO_COUNT=0
	ISO_MOUNT_COUNT=0
	for dev in $devlist; do
		if ! mount_device "$dev"; then
			if [ "$look_subdirs" = 0 ]; then
				log "Waiting for $dev to possibly get ready..."
				sleep 3
				if ! mount_device $dev; then
					db_progress STEP 1
					continue
				fi
			else
				continue
			fi
		fi

		db_subst iso-scan/progress_scan DRIVE "$dev"
		if [ "$look_subdirs" = 1 ]; then
			log "Mounted $dev for second pass"
		else
			log "Mounted $dev for first pass"
			MOUNTABLE_DEVS="$MOUNTABLE_DEVS $dev"
			MOUNTABLE_DEVS_COUNT=$(($MOUNTABLE_DEVS_COUNT + 1))
		fi

		cd /hd-media
		for dir in . ./*; do
			[ -d "$dir" ] || continue

			db_subst iso-scan/progress_scan DIRECTORY "${dir#.}/"
			db_progress INFO iso-scan/progress_scan

			opt=
			if [ "$dir" = . ] || [ "$look_subdirs" = 0 ]; then
				opt="-maxdepth 1"
			elif [ "$look_subdirs" = 1 ]; then
				opt="-type f"
			fi
			isolist=$(find "$dir" $opt -name "*.iso" -o -name "*.ISO" 2>/dev/null)
			TOPLEVEL_DIRS_COUNT=$(($TOPLEVEL_DIRS_COUNT + 1))

			for iso in $isolist; do
				# FIXME: is this enough to detect broken symlinks?
				[ -e $iso ] || continue

				log "Found ISO $iso on $dev"
				ISO_COUNT=$(($ISO_COUNT + 1))
				add_usable_iso $iso $dev
			done
			if [ $progress = dir ]; then
				db_progress STEP 1
			fi
		done
		cd /
		# -d option let loop used to mount ISO image to be also
		# released, else we can't umount /hd-media!
		# -l also helps
		umount -d -l /hd-media

		if [ $look_subdirs = 0 ]; then
			# It's possible that the ISO was written right
			# to the front of a device, and not to a filesystem.
			# (Hey, we may even be spinning a real CD here,
			# though that would be pretty weird...)
			add_usable_iso $dev $dev

			db_progress STEP 1
		fi
	done

	db_progress STOP
}

# Mount selected ISO as Installer image
use_this_iso () {
	local iso_to_try=${1#/}
	local iso_device=$2
	local ram=$(grep ^MemAvailable: /proc/meminfo | { read label size unit; echo ${size:-0}; })
	local iso_size=0
	local RET

	mount -t auto -o ro $iso_device /hd-media 2>/dev/null

	# Get iso size in kB to compare with $ram in kB too
	iso_size=$(ls -sk /hd-media/$iso_to_try | { read size filename; echo ${size:-0}; })

	if [ $(( $iso_size + 100000 )) -lt $ram ]; then
		# We have enough RAM to be able to copy the ISO to RAM,
		# let's offer it to the user
		db_input low iso-scan/copy_iso_to_ram || true
		db_go
		db_get iso-scan/copy_iso_to_ram
	else
		log "Skipping debconf question iso-scan/copy_iso_to_ram:" \
		    "not enough memory available ($ram kB) to copy" \
		    "/hd-media/$iso_to_try ($iso_size kB) into RAM and still" \
		    "have 100 MB free."
		RET="false"
	fi

	if [ "$RET" = false ]; then
		# Direct mount
		log "Mounting /hd-media/$iso_to_try on /cdrom"
		mount -t iso9660 -o loop,ro,exec /hd-media/$iso_to_try /cdrom 2>/dev/null
	else
		# We copy the ISO to RAM before mounting it
		log "Copying /hd-media/$iso_to_try to /installer.iso"
		cp /hd-media/$iso_to_try /installer.iso
		log "Mounting /installer.iso on /cdrom"
		mount -t iso9660 -o loop,ro,exec /installer.iso /cdrom 2>/dev/null
		# So that we can free the original device
		umount /hd-media
	fi

	analyze_cd

	db_subst iso-scan/success FILENAME $iso_to_try
	db_set iso-scan/filename $iso_to_try
	db_subst iso-scan/success DEVICE $iso_device
	# FIXME !!!
	db_subst iso-scan/success SUITE FIXME
	db_input medium iso-scan/success || true
	db_go || true

	anna-install apt-mirror-setup || true
	if [ ! -e /cdrom/.disk/base_installable ]; then
		log "Base system not installable from CD image, requesting choose-mirror"
		anna-install choose-mirror || true
	else
		anna-install apt-cdrom-setup || true

		# Install <codename>-support udeb (if available)
		db_get cdrom/codename
		anna-install $RET-support || true
	fi
	exit 0
}

# iso-scan is divided in following stages:
# 1  : get list of devices (no debconf question)
# 2/3: select a device (or all detected ones)
# 4/5: parse selected devices looking for Debian ISOs and let choose one

# This conf script is capable of backing up
db_capb backup

# Main loop starts here
# Use a state machine to allow jumping back to previous questions.
# Main states are multiples of 10 to allow "preparation" states to be
# skipped when backing up.
STATE=1
LASTSTATE=0
BACKUPSTATE=	# If used, must be set just before db_input
CRITICAL=

SELECTED_ISO=
while :; do
	case $STATE in
	     0) # Back up to menu
		exit 10
		;;

	     1)	# Get list of devices

		# Try to unmount anything that was previously mounted
		umount -d /cdrom 2>/dev/null || true
		umount -d /hd-media 2>/dev/null || true

		# Hopefully this will find the drive
		hw-detect iso-scan/detect_progress_title || true

		# Load up every filesystem known to man. The drive could have anything.
		FS="ext2 ext3 ext4 fat vfat xfs jfs iso9660 hfsplus hfs ntfs btrfs"
		for fs in $FS; do
			modprobe $fs >/dev/null 2>&1 || true
		done
		modprobe loop >/dev/null || true

		# Detect LVM logical volumes if possible
		modprobe dm_mod >/dev/null || true
		if type vgchange >/dev/null 2>&1; then
			vgchange -a y >/dev/null || true
		fi

		mkdir /cdrom 2>/dev/null || true
		mkdir /hd-media 2>/dev/null || true

		DEVS="$(list-devices partition; list-devices disk; list-devices maybe-usb-floppy)"
		log "devices found: '$DEVS'"
		STATE=10
		continue
		;;

	    10)	# Ask user to select a device (all by default)
		devlist=
		for dev in $DEVS; do
			devlist=${devlist:+$devlist, }$dev
		done

		db_subst shared/ask_device DEVICES_LIST $devlist
		db_input ${CRITICAL:-medium} shared/ask_device || true
		;;

	    11)
		db_get shared/ask_device
		case "$RET" in
		    manual)
			: ;;
		    all)
			selected_devices=$DEVS
			STATE=19
			continue
			;;
		    *) # specific device
			selected_devices=$RET
			STATE=19
			continue
			;;
		esac

		log "let user specify device"
		db_input critical shared/enter_device || true
		;;

	    12)	# Parse selected device(s)
		db_get shared/enter_device
		selected_devices=$RET
		STATE=19
		continue
		;;

	    19)	# User has selected device(s); scan for ISOs (first pass)
		log "selected_device(s)='$selected_devices'"

		# Variables that are preserved for the 2nd pass
		TOPLEVEL_DIRS_COUNT=0
		MOUNTABLE_DEVS=
		MOUNTABLE_DEVS_COUNT=0

		scan_device_for_isos 0 "$selected_devices"
		log "ISOS_FOUND='$ISOS_FOUND'"

		if [ -z "$ISOS_FOUND" ]; then
			# no ISO found
			log "MOUNTABLE_DEVS_COUNT='$MOUNTABLE_DEVS_COUNT'"
			if [ $MOUNTABLE_DEVS_COUNT -gt 0 ]; then
				STATE=100
			else
				STATE=110
			fi
			continue
		fi
		;;

	    20)	# One or multiple ISO(s) found: ask which one we'll use
		# FIXME: not l10n safe
		db_subst iso-scan/ask_which_iso ISOS_LIST "$ISOS_FOUND"
		db_input ${CRITICAL:-medium} iso-scan/ask_which_iso || true
		;;

	    21)
		db_get iso-scan/ask_which_iso
		if [ "$RET" != "Full search" ]; then
			# We have an ISO
			SELECTED_ISO=$RET
			break
		fi
		STATE=29
		continue
		;;

	    29)	# Scan for ISOs (second pass)
		ISOS_FOUND=
		scan_device_for_isos 1 "$selected_devices"
		log "ISOS_FOUND (2nd pass)='$ISOS_FOUND'"

		if [ -z "$ISOS_FOUND" ]; then
			STATE=120
			continue
		fi
		;;

	    30)	# One or multiple ISO(s) found: ask which one we'll use
		db_clear
		db_subst iso-scan/ask_which_iso ISOS_LIST "$ISOS_FOUND"
		db_subst iso-scan/ask_which_iso SECOND_PASS "."
		if echo "$ISOS_FOUND" | grep -q ', '; then
			priority=high
		else
			priority=medium
		fi
		BACKUPSTATE=10
		db_input ${CRITICAL:-$priority} iso-scan/ask_which_iso || true
		;;

	    31)	# We have an ISO
		db_get iso-scan/ask_which_iso
		if [ "$RET" != "Full search" ]; then
			# We have an ISO
			SELECTED_ISO=$RET
			break
		fi
		STATE=29
		continue
		;;

	   ####	The rest are error/exception handling states
	   100)	# No ISO found; offer second pass (except for automated installs)
		db_get debconf/priority
		if [ "$RET" = critical ] && [ -z "$CRITICAL" ]; then
			STATE=110
			continue
		fi
		BACKUPSTATE=10
		db_input ${CRITICAL:-high} iso-scan/ask_second_pass || true
		;;
	   101)
		db_get iso-scan/ask_second_pass
		if [ "$RET" = false ]; then
			exit 1
		fi
		STATE=29
		continue
		;;

	   110)	# Device(s) not mountable
		BACKUPSTATE=10
		db_input critical iso-scan/no-isos || true
		;;
	   111)
		exit 1
		;;

	   120)	# Display suitable error and fail
		log "Failing with ISO_COUNT = $ISO_COUNT, MOUNTABLE_DEVS_COUNT = $MOUNTABLE_DEVS_COUNT, ISO_MOUNT_COUNT = $ISO_MOUNT_COUNT"

		BACKUPSTATE=10
		if [ "$ISO_COUNT" = 0 ]; then
			db_input critical iso-scan/no-isos || true
		elif [ "$ISO_MOUNT_COUNT" != "$ISO_COUNT" ]; then
			db_input critical iso-scan/bad-isos || true
		else
			db_input critical iso-scan/other-isos || true
		fi
		;;
	    121)
		exit 1
		;;

	    *)
		log "Error: undefined STATE '$STATE'"
		exit 1
		;;
	esac

	LASTSTATE=$STATE
	if db_go; then
		STATE=$(($STATE + 1))
	else
		if [ -z "$CRITICAL" ]; then
			CRITICAL=critical
			db_fset shared/ask_device seen false
			db_fset iso-scan/ask_which_iso seen false
		fi
		if [ "$BACKUPSTATE" ]; then
			STATE=$BACKUPSTATE
			BACKUPSTATE=
		else
			STATELEVEL=$(($STATE / 10 * 10)) # round down to multiple of 10
			if [ $STATE -eq $STATELEVEL ]; then
				STATE=$(($STATE - 10))
			else
				STATE=$(($STATE - 1))
			fi
		fi
	fi
done

device=/dev/$(echo "$SELECTED_ISO" | sed -e 's/\[\(.*\)\] .*/\1/')
iso=$(echo "$SELECTED_ISO" | sed -e 's/\[.*\] \(.*\) (.*/\1/')
log "Selected ISO: $iso on $device"

use_this_iso $iso $device

exit 0
