src/core/libs/lib-ui-interactive.sh
#!/bin/bash
# A library which allows you to do backend stuff by using user interfaces

# Global Variables
# Be sure to override these if you have the configuration file elsewhere
syslinuxmenu="/boot/syslinux/syslinux.cfg"

# check if a worker has completed successfully. if not -> tell user he must do it + return 1
# if ok -> don't warn anything and return 0
check_depend ()
{
	[ -z "$1" -o -z "$2" ] && die_error "Use the check_depend function like this: check_depend <type> <name> with type=phase/worker"
	[ "$1" != phase -a "$1" != worker ] && die_error "check_depend's first argument must be a valid type (phase/worker)"

	ended_ok $1 $2 && return 0
	subject="$1 $2"
	title=$1_$2_title
	[ -n "${!title}" ] && subject="'${!title}'"
	show_warning "Cannot Continue.  Going back to $2" "You must do $subject first before going here!." && return 1
}

# populate config and data files with what we know about the target system
# note that you could run this function multiple times (i.e. you change some stuff and then come back),
# all logic in here is written to do the right thing in that case
preconfigure_target () {
	local failed=()
	target_configure_initial_locale || failed+=('initial locale')
	target_configure_initial_keymap_font || failed+=('keymap/font setting')
	target_configure_fstab || failed+=('fstab configuration')
	target_configure_network || failed+=('network config export')
	configure_mirrorlist target || failed+=('mirrorlist configuration')
	target_configure_time || failed+=('time configuration')
	target_configure_initcpio || failed+=('initcpio configuration')
	# TODO: we should probably update /etc/crypttab if the user has non-/ encrypted disks.

	[ ${#failed[@]} -gt 0 ] && warn_failed 'Preconfigure' "${failed[@]}" && return 1
	return 0
}

# do some target configuration steps automatically, after user decided he configured his system.
# as usual, this function is okay with being called multiple times
postconfigure_target () {
	local failed=()
	target_run_mkinitcpio $(ask_mkinitcpio_preset $var_TARGET_DIR) || failed+=('mkinitcpio creation')
	target_locale-gen || failed+=('locale generation')
	target_localtime || failed+=('localtime copying')
	[ ${#failed[@]} -gt 0 ] && warn_failed 'Postconfigure' "${failed[@]}" && return 1
	return 0
}

interactive_configure_system()
{
	seteditor || return 1

	if ! preconfigure_target
	then
		ask_yesno "Do you want to continue?" no || return 1
	fi

	local default=no
	local helptext
	while true; do
		helptext="\nNote that if you want to change any file not listed here (unlikely) you can go to another tty and update ${var_TARGET_DIR}/etc/<filename> yourself"
		ls -1 /sys/block | grep -q ^dm- && helptext="$helptext\n/dev/mapper/ users: Pay attention to HOOKS in mkinitcpio.conf"
		list=(
			"/etc/rc.conf"                  "System Config"
			"/etc/fstab"                    "Filesystem Mountpoints"
			"/etc/mkinitcpio.conf"          "Initramfs Config"
		)
		grep -q dm_crypt $TMP_BLOCKDEVICES && list+=("/etc/crypttab" "Decryption for non-root encrypted disks") # this simple grep will give some false positives. but oh well.
		[ -f ${var_TARGET_DIR}/etc/profile.d/proxy.sh ] && list+=("/etc/profile.d/proxy.sh" "Proxy setings")
		list+=(
			"/etc/modprobe.d/modprobe.conf" "Kernel Modules"
			"/etc/resolv.conf"              "DNS Servers"
			"/etc/hosts"                    "Network Hosts"
			"/etc/locale.gen"               "Glibc Locales"
			"/etc/pacman.conf"              "Pacman.conf"
			"$var_MIRRORLIST"               "Pacman Mirror List"
			"Root-Password"                 "Set the root password"
			"Done"                          "Return to Main Menu"
		)
		ask_option $default "Configuration" "$helptext" required "${list[@]}" || return 1
		FILE=$ANSWER_OPTION
		default=$FILE
		if [ "$FILE" = "Done" ]; then
			break
		elif [ "$FILE" = "Root-Password" ]; then
			while true; do
				chroot ${var_TARGET_DIR} passwd root && break
			done
		elif [ "$FILE" = "/etc/rc.conf" ]; then
			HOSTNAME_PRE=$(source ${var_TARGET_DIR}${FILE} && echo $HOSTNAME)
			$EDITOR ${var_TARGET_DIR}${FILE}
			HOSTNAME_POST=$(source ${var_TARGET_DIR}${FILE} && echo $HOSTNAME)
			if [ "$HOSTNAME_PRE" != "$HOSTNAME_POST" ]; then
				# remove all occurences of old hostname in /etc/hosts:
				sed -i "s# $HOSTNAME_PRE##" ${var_TARGET_DIR}/etc/hosts
				# add new hostname to /etc/hosts except in the - admittedly unlikely, but anyway - event the user already added it himself
				sed -i "/$HOSTNAME/"'!s'"/127\.0\.0\.1.*$HOSTNAME.*/& $HOSTNAME/" hosts
				sed -i "/$HOSTNAME/"'!s'"/\:\:1.*$HOSTNAME.*/& $HOSTNAME/" hosts
			fi
		else
			$EDITOR ${var_TARGET_DIR}${FILE}
		fi
	done

	# temporary backup files are not useful anymore past this point.
	find "${var_TARGET_DIR}/etc/" -name '*~' -delete &>/dev/null
	if ! postconfigure_target
	then
		ask_yesno "Do you want to continue?" no || return 1
	fi

	return 0
}


interactive_timezone () {
	ask_timezone || return 1
	TIMEZONE=$ANSWER_TIMEZONE
	return 0
}

# this should be executed, whether the user changed $TIMEZONE or not
copy_timezone_file () {
	if [ -z "$TIMEZONE" ]
	then
		debug UI-INTERACTIVE "\$TIMEZONE is empty, not creating/updating /etc/localtime"
		return 0
	fi
	debug UI-INTERACTIVE "Setting Timezone to $TIMEZONE"
	local file="/usr/share/zoneinfo/$TIMEZONE"
	[ -e "$file" ] || die_error "No such timezone file: $file, did you choose a non-existing timezone?"
	# This changes probably also the systemtime (UTC->$TIMEZONE)!
	# localtime users will have a false time after that!
	rm -f /etc/localtime || return 1
	cp "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime || return 1
	return 0
}


interactive_time () {
	# utc or localtime?
	ask_option UTC "Clock configuration" "Is your hardware clock in UTC or local time? UTC is recommended" required "UTC" - "localtime" - || return 1
	HARDWARECLOCK=$ANSWER_OPTION
	# To avoid a false time for localtime users after above
	# we must re-read the hwclock value again, but now into the
	# correct timezone.
	if [ "$HARDWARECLOCK" == "localtime" ]
	then
		dohwclock $HARDWARECLOCK hctosys || return $?
	fi

	timeset=
	local default=no
	while true; do
		current=$(date)
		#TODO: only propose if network ok
		EXTRA=()
		type ntpdate &>/dev/null && EXTRA=('ntp' 'Set time and date using ntp')

		ask_option $default "Date/time configuration" "According to your settings and your hardwareclock, the date should now be $current.  If this is incorrect, you can correct this now" required \
		"return" "Looks good. back to main menu" "${EXTRA[@]}" "manual" "Set time and date manually" || return 1
		if [ "$ANSWER_OPTION" = ntp ]; then
			inform "Syncing clock with internet pool ..."
			if ntpdate pool.ntp.org >/dev/null; then
				notify "Synced clock with internet pool successfully."
				dohwclock $HARDWARECLOCK systohc && default=3 || return $?
				timeset=1
			else
				show_warning 'Ntp failure' "An error has occured, time was not changed!"
				timeset=0
			fi
		fi
		if [ "$ANSWER_OPTION" = manual ]; then
			ask_datetime || continue
			if date -s "$ANSWER_DATETIME"; then
				dohwclock $HARDWARECLOCK systohc && default=3 || return $?
				timeset=1
			else
				show_warning "Date/time setting failed" "Something went wrong when doing date -s $ANSWER_DATETIME" 
				timeset=0
			fi
		fi
		[ "$ANSWER_OPTION" = return ] && timeset=1 && break
	done
	[ "$timeset" = '0' ] && return 1
	return 0
}


interactive_prepare_disks ()
{
	DONE=0
	local ret=1 # 1 means unsuccessful. 0 for ok
	DISK_CONFIG_TYPE=
	[ "$BLOCK_ROLLBACK_USELESS" = "0" ] && show_warning "Rollback may be needed" "It seems you already went here.  You should probably rollback previous changes before reformatting, otherwise stuff will probably fail"
	local default=no
	while [ "$DONE" = "0" ]
	do
		rollbackstr=" (you don't need to do this)"
		[ "$BLOCK_ROLLBACK_USELESS" = "0" ] && rollbackstr=" (this will revert your last changes)"

		ask_option $default "Prepare Hard Drive" '' required \
			"1" "Auto-Prepare (erases an ENTIRE hard drive and sets up partitions, filesystems and mountpoints)" \
			"2" "Manually Partition Hard Drives" \
			"3" "Manually Configure block devices, filesystems and mountpoints" \
			"4" "Rollback last filesystem changes$rollbackstr" \
			"5" "Return to Main Menu" || return 1

		case $ANSWER_OPTION in
			"1")
				[ "$BLOCK_ROLLBACK_USELESS" = "0" ] && ask_yesno "You should probably rollback your last changes first, otherwise this will probably fail.  Go back to menu to do rollback?" && default=4 && continue
				interactive_autoprepare && default=5 && ret=0 && DISK_CONFIG_TYPE=auto;;
			"2")
				[ "$BLOCK_ROLLBACK_USELESS" = "0" ] && ask_yesno "You should probably rollback your last changes first, otherwise this will probably fail.  Go back to menu to do rollback?" && default=4 && continue
				interactive_partition && ret=1 && default=3 && DISK_CONFIG_TYPE=manual;;
			"3")
				[ "$BLOCK_ROLLBACK_USELESS" = "0" ] && ask_yesno "You should probably rollback your last changes first, otherwise this will probably fail.  Go back to menu to do rollback?" && default=4 && continue
				interactive_filesystems && ret=0 && default=5 && DISK_CONFIG_TYPE=manual;;
			"4")
				interactive_rollback_filesystems;;
			"5")
				DONE=1 ;;
		esac
	done
	return $ret
}

maybe_interactive_rollback_filesystems ()
{
	[ "$BLOCK_ROLLBACK_USELESS" = "1" ] && return 0
	if ask_yesno "Do you want to rollback your filesystem changes?"
	then
		interactive_rollback_filesystems || return $?
	fi
	return 0
}

# it's up to the caller to decide if it's needed to call this function, so we warn user when he wants to do a useless rollback
interactive_rollback_filesystems ()
{
	if [ "$BLOCK_ROLLBACK_USELESS" = "1" ]
	then
		ask_yesno "It seems like there is nothing rollback right now.  This operation is useless, but it shouldn't harm.  Do you want to continue?" || return
	fi
	if rollback_filesystems
	then
		inform "Rollback succeeded"
	else
		show_warning "Rollback failed" "Rollback failed"
		return 1
	fi
}

interactive_autoprepare()
{
	listblockfriendly
	if [ ${#BLOCKFRIENDLY[@]} -gt 2 ]
	then
		ask_option no 'Harddrive selection' "Select the hard drive to use" required "${BLOCKFRIENDLY[@]}" || return 1
		DISC=$ANSWER_OPTION
	elif [ ${#BLOCKFRIENDLY[@]} -eq 0 ]; then
		ask_string "Could not find disk. Please enter path of devicefile manually" "" || return 1
		DISC=${ANSWER_STRING// /} # TODO : some checks if $DISC is really a blockdevice is probably a good idea
	else
		DISC=${BLOCKFRIENDLY[0]}
	fi

	local FSOPTS=()
	local fs
	for fs in ext2 ext3 ext4 reiserfs xfs jfs vfat nilfs2 btrfs
	do
		check_is_in $fs "${possible_fs[@]}" && FSOPTS+=($fs "${filesystem_names[$fs]}")
	done
	get_blockdevice_size $DISC MiB
	local size_left=$BLOCKDEVICE_SIZE

	ask_number "Enter the size (MiB) of your /boot partition.  Recommended size: 100MiB\n\nDisk space left: $size_left MiB" 16 $size_left 100 || return 1
	BOOT_PART_SIZE=$ANSWER_NUMBER
	size_left=$(($size_left-$BOOT_PART_SIZE))

	ask_number "Enter the size (MiB) of your swap partition.  Recommended size: 256MiB\n\nDisk space left: $size_left MiB" 1 $size_left 256 || return 1
	SWAP_PART_SIZE=$ANSWER_NUMBER
	size_left=$(($size_left-$SWAP_PART_SIZE))

	local suggest_root=7500
	# if the disk is too small to hold a 7.5GB root and 5GB home (these are arbitrary numbers), just give root 3/4 of the size, if that's too small leave it up to the user
	[ $(($suggest_root+5000)) -gt $size_left ] && suggest_root=$(($size_left*3/4))
	ask_number "Enter the size (MiB) of your / partition.  Recommended size:7500.  The /home partition will use the remaining space.\n\nDisk space left:  $size_left MiB" 1 $size_left $suggest_root || return 1
	ROOT_PART_SIZE=$ANSWER_NUMBER
	HOME_PART_SIZE=$(($size_left-$ROOT_PART_SIZE))

	ask_option no 'Filesystem selection' "Select a filesystem for / and /home:" required "${FSOPTS[@]}" || return 1
	FSTYPE=$ANSWER_OPTION

	echo "$DISC $BOOT_PART_SIZE:ext2:+ $SWAP_PART_SIZE:swap $ROOT_PART_SIZE:$FSTYPE *:$FSTYPE" > $TMP_PARTITIONS

	echo "${DISC}1 raw no_label ext2;yes;/boot;target;no_opts;no_label;no_params"         >  $TMP_BLOCKDEVICES
	echo "${DISC}2 raw no_label swap;yes;no_mountpoint;target;no_opts;no_label;no_params" >> $TMP_BLOCKDEVICES
	echo "${DISC}3 raw no_label $FSTYPE;yes;/;target;no_opts;no_label;no_params"          >> $TMP_BLOCKDEVICES
	echo "${DISC}4 raw no_label $FSTYPE;yes;/home;target;no_opts;no_label;no_params"      >> $TMP_BLOCKDEVICES

msg="/boot $BOOT_PART_SIZE MiB
swap  $SWAP_PART_SIZE MiB
/     $ROOT_PART_SIZE MiB ($FSTYPE)
/home $HOME_PART_SIZE MiB ($FSTYPE)

$DISC will be COMPLETELY ERASED!  Are you absolutely sure?"
	ask_yesno "$msg" || return 1

	PART_ACCESS=uuid

	process_disks       || die_error "Something went wrong while partitioning"
	if ! process_filesystems
	then
		show_warning "Filesystem processing" "Something went wrong while processing the filesystems.  Attempting rollback."
		if rollback_filesystems
		then
			show_warning "Filesystem rollback" "Rollback succeeded.  Please try to figure out what went wrong and try me again.  If you found a bug in the installer, please report it."
			return 1
		else
			die_error "Filesystem processing and rollback failed.  Please try the installer again.  If you found a bug in the installer, please report it."
		fi
	else
		notify "Auto-prepare was successful"
		return 0
	fi
}


interactive_partition() {
	target_umountall

	question_text="Select the disk you want to partition"
	if [ -f "$TMP_PARTITIONS" ]
	then
		if ask_yesno "I've detected you already have partition definitions in place:\n`cat $TMP_PARTITIONS`\nDo you want apply these now?  Pick 'no' when in doubt to start from scratch" no
		then
			process_disks || die_error "Something went wrong while partitioning"
			question_text="If you want to do further changes, you can (re)partition disks here"
		fi
	fi

	# Select disk to partition
	listblockfriendly
	DISCS=("${BLOCKFRIENDLY[@]}" OTHER OTHER DONE DONE)
	DISC=
	while true; do
		# Prompt the user with a list of known disks
		ask_option no 'Disc selection' "$question_text (select DONE when finished)" required "${DISCS[@]}" || return 1
		DISC=$ANSWER_OPTION
		if [ "$DISC" = "OTHER" ]; then
			ask_string "Enter the full path to the device you wish to partition" "/dev/sda" || return 1
			DISC=$ANSWER_STRING
		fi
		# Leave our loop if the user is done partitioning
		[ "$DISC" = "DONE" ] && break
		# Partition disc
		notify "Now you'll be put into the cfdisk program where you can partition your hard drive. You should make a swap partition and as many data partitions as you will need.\
		NOTE: cfdisk may tell you to reboot after creating partitions.  If you need to reboot, just re-enter this install program, skip this step and go on to the mountpoints selection step."
		cfdisk $DISC
	done
	return 0
}


# create new, delete, or edit a filesystem
# At first I had the idea of a menu where all properties of a filesystem and you could pick one to update only that one (eg mountpoint, type etc)\
# but I think it's better to go through them all and by default always show the previous choice.
interactive_filesystem ()
{
	# note: fs_mount: we dont need to ask this to the user. this is always 'target' for 99.99% of the users
	local part=$1       # must be given and (scheduled to become) a valid device -> don't do [ -b "$1" ] because the device might not exist *yet*
	local part_type=$2  # a part should always have a type
	local part_label=$3 # can be empty
	local fs_string=$4  # can be empty
	local fs_type=
	local fs_create
	local fs_mountpoint=
	local fs_mount
	local fs_opts=
	local fs_label=
	local fs_params=
	local default
	NEW_FILESYSTEM=
	if [ -n "$fs_string" ]
	then
		parse_filesystem_string "$fs_string" '' ''
		local old_fs_type=$fs_type
		local old_fs_create=$fs_create
		local old_fs_mountpoint=$fs_mountpoint
		local old_fs_mount=$fs_mount
		local old_fs_opts="$fs_opts"
		local old_fs_label=$fs_label
		local old_fs_params="$fs_params"

		ask_option edit "Change $fs_type filesystem settings on $part ?" \
						"Change $fs_type filesystem settings (create:$fs_create, label:$fs_label, mountpoint:$fs_mountpoint) on $part (type:$part_type, label:$part_label) ?" required \
						edit EDIT delete DELETE

		# Don't alter, and return if user cancels
		[ $? -gt 0 ] && NEW_FILESYSTEM=$fs_string && return 0
		# Erase and return if that's what the user wants
		[ "$ANSWER_OPTION" = delete ] && NEW_FILESYSTEM=no_fs
	fi

	if [ "$NEW_FILESYSTEM" != no_fs ]
	then
		# Determine which filesystems/blockdevices are possible for this blockdevice
		local FSOPTS=()
		local fs
		for fs in ${fs_on[$part_type]}
		do
			check_is_in $fs "${possible_fs[@]}" && FSOPTS+=($fs "${filesystem_names[$fs]}")
		done

		fs_create=no
		ask_yesno "Do you want to have this filesystem (re)created ?  If not, make sure there already is a filesystem!" && fs_create=yes

		# determine FS choice
		if [ ${#FSOPTS[*]} -lt 4 ] # less then 4 words in $FSOPTS. eg only one option
		then
			notify "Automatically picked the ${FSOPTS[1]} filesystem.  It's the only option for $part_type blockdevices"
			fs_type=${FSOPTS[0]}
		else
			default=${fs_type:-no}
			extratext="Select a filesystem for $part:"
			[ "$fs_create" == no ] && extratext="Select which filesystem $part is.  Make sure you get this right" #otherwise he'll be screwed when we try to mount it :)
			ask_option $default "Select filesystem" "$extratext" required "${FSOPTS[@]}" || return 1
			fs_type=$ANSWER_OPTION
		fi

		# ask mountpoint, if relevant
		if check_is_in $fs_type "${fs_mountable[@]}"
		then
			default=${fs_mountpoint:-no}
			fs_mountpoint=$(ask_mountpoint $default $part)
		fi

		# ask label, if relevant
		if [ "$fs_create" == yes ] && check_is_in "$fs_type" "${fs_label_mandatory[@]}"
		then
			default="$fs_label" # can be empty
			ask_string "Enter the label/name for this $fs_type on $part (Mandatory for this type of FS!)" "$default" || return 1 #TODO: check that you can't give LV's labels that have been given already or the installer will break.
			fs_label=$ANSWER_STRING
		elif [ "$fs_create" == yes ] && check_is_in "$fs_type" "${fs_label_optional[@]}"
		then
			default="$fs_label" # can be empty
			ask_string "Enter the label for this $fs_type on $part (optional) [keep it short and don't use spaces]" "$default" 0
			fs_label=${ANSWER_STRING// } # strip spaces to prevent problems in our bash code and to keep things simple.
		fi

		# ask special params, if relevant
		# lvm-vg: PV's to use
		# lvm-lv: LV size
		if [ "$fs_create" == yes ] && [ "$fs_type" = lvm-vg ]
		then
			# add $part to $fs_params if it's not in there because the user wants this enabled by default. TODO: we should find something out so you can't disable $part. (would be weird to have a vg listed on $part and not have $part it fs_params)
			local pv=${part/+/}
			if ! egrep -q "$pv(\$| )" <<< "$fs_params"; then
				[ -n "$fs_params" ] && fs_params="$fs_params "
				fs_params="$fs_params$pv"
			fi

			local list=()
			local pv
			for pv in $fs_params
			do
				list+=("$pv" ^ ON)
			done
			for pv in `grep '+ lvm-pv' $TMP_BLOCKDEVICES | awk '{print $1}' | sed 's/\+$//'` # find PV's to be added: their blockdevice ends on + and has lvm-pv as type #TODO: i'm not sure we check which pv's are taken already
			do
				grep -q "$pv ^ ON" <<< "${list[@]}" || list+=("$pv" - OFF)
			done
			if [ ${#list[*]} -lt 6 ] # less then 6 words in the list. eg only one option
			then
				notify "Automatically picked PV ${list[0]} to use for this VG.  It's the only available lvm PV"
				fs_params=${list[0]}
			else
				ask_checklist "Which lvm PV's must this volume group span?" 0 "${list[@]}" || return 1
				fs_params="${ANSWER_CHECKLIST[@]}"
			fi
		fi
		if [ "$fs_create" == yes ] && [ "$fs_type" = lvm-lv ]
		then
			[ -z "$fs_params" ] && default='5000'
			[ -n "$fs_params" ] && default="$fs_params"
			ask_number "Enter the size for this $fs_type on $part in MiB" 1 0 "$default" || return 1 #TODO: can we get the upperlimit from somewhere?
			# Lvm tools use binary units but have their own suffixes ( K,M,G,T,P,E, but they mean KiB, MiB etc)
			fs_params="${ANSWER_NUMBER}M"
		fi
		if [ "$fs_create" == yes ] && [ "$fs_type" = dm_crypt ]
		then
			[ -z "$fs_params" ] && default='-c aes-xts-plain -y -s 512'
			[ -n "$fs_params" ] && default="$fs_params"
			ask_string "Enter the options for this $fs_type on $part" "$default" || return 1
			fs_params="$ANSWER_STRING"
		fi

		# ask opts
		if [ "$fs_create" == yes ]
		then
			default="$fs_opts" # can be empty
			program="${filesystem_programs[$fs_type]}"
			ask_string "Enter any additional opts for $program" "$default" 0
			fs_opts="$ANSWER_STRING"
		fi

		[ -z "$fs_type"       ] && fs_type=no_type
		[ -z "$fs_mountpoint" ] && fs_mountpoint=no_mountpoint
		[ -z "$fs_opts"       ] && fs_opts=no_opts
		[ -z "$fs_label"      ] && fs_label=no_label
		[ -z "$fs_params"     ] && fs_params=no_params
		NEW_FILESYSTEM="$fs_type;$fs_create;$fs_mountpoint;target;${fs_opts// /__};$fs_label;${fs_params// /__}"

		# add new theoretical blockdevice, if relevant
		# these are just the resulting DM devices,
		# exception for lvm-pv: we create $part+ to represent the PV (+ is to differentiate from $part itself)
		new_device=
		[ "$fs_type" = lvm-vg   ] && new_device="/dev/mapper/$fs_label $fs_type $fs_label"
		[ "$fs_type" = lvm-pv   ] && new_device="$part+ $fs_type no_label"
		[ "$fs_type" = lvm-lv   ] && new_device="/dev/$part_label/$fs_label $fs_type $fs_label"
		[ "$fs_type" = dm_crypt ] && new_device="/dev/mapper/$fs_label $fs_type no_label"
		[ -n "$new_device" ] && ! grep -q "^$new_device " $TMP_BLOCKDEVICES && echo "$new_device no_fs" >> $TMP_BLOCKDEVICES
	fi

	[ -z "$old_fs_label" ] && old_fs_label=no_label

	# Cascading remove theoretical blockdevice(s), if relevant ( eg if we just changed from vg->ext3, dm_crypt -> fat, or if we changed the label of a FS, causing a name change in a dm_mapper device)
	if [[ $old_fs_type = lvm-* || $old_fs_type = dm_crypt ]] && [ "$NEW_FILESYSTEM" = no_fs -o "$old_fs_type" != "$fs_type" -o "$old_fs_label" != "$fs_label" ]
	then
		[ "$old_fs_type" = lvm-vg   ] && remove_blockdevice "/dev/mapper/$old_fs_label"      "$old_fs_type" "$old_fs_label"
		[ "$old_fs_type" = lvm-pv   ] && remove_blockdevice "$part+"                         "$old_fs_type" "$old_fs_label"
		[ "$old_fs_type" = lvm-lv   ] && remove_blockdevice "/dev/$part_label/$old_fs_label" "$old_fs_type" "$old_fs_label"
		[ "$old_fs_type" = dm_crypt ] && remove_blockdevice "/dev/mapper/$old_fs_label"      "$old_fs_type" "$old_fs_label"
	fi

	return 0
}


remove_blockdevice ()
{
	local part=$1       # must be given but doesn't need to exist
	local part_type=$2  # a part should always have a type
	local part_label=$3 # must be given

	local target="$part $part_type $part_label"
	declare target_escaped=${target//\//\\/} # note: apparently no need to escape the '+' sign for sed.
	declare target_escawk=${target_escaped/+/\\+} # ...but that doesn't count for awk
	fs_string=`awk "/^$target_escawk / { print \$4}" $TMP_BLOCKDEVICES` #TODO: fs_string is the entire line, incl part?
	debug 'UI-INTERACTIVE' "Cleaning up partition $part (type $part_type, label $part_label).  It has the following FS's on it: $fs_string"
	sed -i "/$target_escaped/d" $TMP_BLOCKDEVICES || show_warning "blockdevice removal" "Could not remove partition $part (type $part_type, label $part_label).  This is a bug. please report it"
	local fs
	for fs in `sed 's/|/ /g' <<< $fs_string`
	do
		fs_type=`       cut -d ';' -f 1 <<< $fs`
		fs_label=`      cut -d ';' -f 6 <<< $fs`
		[ "$fs_type" = lvm-vg   ] && remove_blockdevice "/dev/mapper/$fs_label"      "$fs_type" "$fs_label"
		[ "$fs_type" = lvm-pv   ] && remove_blockdevice "$part+"                     "$fs_type" "$fs_label"
		[ "$fs_type" = lvm-lv   ] && remove_blockdevice "/dev/$part_label/$fs_label" "$fs_type" "$fs_label"
		[ "$fs_type" = dm_crypt ] && remove_blockdevice "/dev/mapper/$fs_label"      "$fs_type" "$fs_label"
	done
}


interactive_filesystems() {
	if [ ! -f $TMP_BLOCKDEVICES ] || ! ask_yesno "Previous blockdevice definitions found:\n`cat $TMP_BLOCKDEVICES`\n\
		Use these as a starting point?  Make sure your disk(s) are partitioned correctly so your definitions can be applied. Pick 'no' when in doubt to start from scratch" no
	then
		find_usable_blockdevices 'raw no_label no_fs\n' > $TMP_BLOCKDEVICES
	fi

	[ -z "$PART_ACCESS" ] && PART_ACCESS=dev
	ask_option $PART_ACCESS 'Partition Access Method' 'How do you want your partitions to be accessed in /etc/fstab?' '' \
		"dev"	"directly by /dev/* (most intuitive but devicefile names can change on kernel updates)" \
		"label"	"by Disk-Label (Will use the filesystem labels where you give them, and fall back on 'dev' otherwise)" \
		"uuid"	"by Universally Unique Identifier (You don't need to do anything, but doesn't look pretty)" || return 1
	PART_ACCESS=$ANSWER_OPTION
	local ALLOK=0
	while [ "$ALLOK" = 0 ]
	do
		# Let the user make filesystems and mountpoints. USERHAPPY becomes 1 when the user hits DONE.
		local USERHAPPY=0

		while [ "$USERHAPPY" = 0 ]
		do
			# generate a menu based on the information in the datafile
			local menu_list=()
			local part type label fs
			while read part type label fs
			do
				parse_filesystem_string "$fs" '' '-'
				local fs_create_display=N
				[ "$fs_create" = yes ] && fs_create_display=Y
				local fs_display="$fs_type $fs_create_display $fs_mountpoint $fs_opts $fs_label $fs_params"

				local part_label_display=-
				local part_size_display=-
				[ "$label" != no_label ] && part_label_display="$label"
				# add size in MiB for existing blockdevices (eg not for mapper devices that are not yet created yet)
				if [ -b "${part/+/}" ] && get_blockdevice_size ${part/+/} MiB # test -b <-- exit's 0, test -b '' exits >0.
				then
					part_size_display="${BLOCKDEVICE_SIZE}MiB"
				fi
				local part_display="$part $type $part_label_display $part_size_display"
				menu_list+=("$part_display" "$fs_display")
			done < $TMP_BLOCKDEVICES

			ask_option no "Manage filesystems" "Here you can manage your filesystems and block devices. \
				The display format is as follows:\n\
Partition              Filesystem(s)\n\
device type label size type create? mountpoint options label params" required "${menu_list[@]}" DONE _
			[ $? -gt 0                 ] && return 1
			[ "$ANSWER_OPTION" == DONE ] && USERHAPPY=1 && break

			part=$(echo $ANSWER_OPTION | cut -d ' ' -f1)
			getpartinfo $part ''
			local fs=$PARTINFO_fs
			local part_label=$PARTINFO_part_label
			local part_type=$PARTINFO_part_type

			if [ $part_type = lvm-vg ] # one lvm VG can host multiple LV's so that's a bit a special blockdevice...
			then
				local list=()
				if [ -n "$fs" ]
				then
					local lv
					for lv in `sed 's/|/ /g' <<< $fs`
					do
						label=$(cut -d ';' -f 6 <<< $lv)
						size=$( cut -d ';' -f 7 <<< $lv)
						list+=("$label" "$size")
					done
				fi
				list+=(empty NEW)
				ask_option empty "Manage LV's on this VG" "Edit/create new LV's on this VG:" required "${list[@]}" && {
					local EDIT_VG=$ANSWER_OPTION
					if [ "$ANSWER_OPTION" = empty  ]
					then
						# a new LV must be created on this VG
						if interactive_filesystem $part $part_type $part_label '' 
						then
							if [ "$NEW_FILESYSTEM" != no_fs ]
							then
								[ -n "$fs" ] && fs="$fs|$NEW_FILESYSTEM"
								[ -z "$fs" ] && fs=$NEW_FILESYSTEM
							fi
						fi
					else
						# an existing LV will be edited and it's settings updated
						local lv
						for lv in `sed 's/|/ /g' <<< $fs`
						do
							label=$(cut -d ';' -f 6 <<< $lv)
							[ "$label" = "$EDIT_VG" ] && found_lv="$lv"
						done
						interactive_filesystem $part $part_type $part_label "$found_lv"
						local newfs add
						for lv in `sed 's/|/ /g' <<< $fs`
						do
							label=$(cut -d ';' -f 6 <<< $lv)
							if [ "$label" != "$EDIT_VG" ]
							then
								add=$lv
							elif [ $NEW_FILESYSTEM != no_fs ]
							then
								add=$NEW_FILESYSTEM
							else
								add=
							fi
							[ -n "$add" -a -n "$newfs" ] && newfs="$newfs|$add"
							[ -n "$add" -a -z "$newfs" ] && newfs=$add
						done
						fs=$newfs
					fi
				}
			else
				interactive_filesystem $part $part_type "$part_label" "$fs"
				[ $? -eq 0 ] && fs=$NEW_FILESYSTEM
			fi

			# update the menu # NOTE that part_type remains raw for basic filesystems!
			[ -z "$part_label" ] && part_label=no_label
			[ -z "$fs"         ] && fs=no_fs
			sed -i "s#^$part $part_type $part_label.*#$part $part_type $part_label $fs#" $TMP_BLOCKDEVICES # '#' is a forbidden character !
		done

		# Check all conditions that need to be fixed and ask the user if he wants to go back and correct them
		local errors=
		local warnings=

		grep -q ';/boot;' $TMP_BLOCKDEVICES || warnings="$warnings\n-No separate /boot filesystem"
		grep -q ';/;'     $TMP_BLOCKDEVICES || errors="$errors\n-No filesystem with mountpoint /"
		grep -q ' swap;'  $TMP_BLOCKDEVICES || grep -q '|swap;' $TMP_BLOCKDEVICES || warnings="$warnings\n-No swap partition defined"

		if [ -n "$errors$warnings" ]
		then
			local str="The following issues have been detected:\n"
			[ -n "$errors" ] && str="$str\n - Errors: $errors"
			[ -n "$warnings" ] && str="$str\n - Warnings: $warnings"
			[ -n "$errors" ] && str="$str\nIt is highly recommended you go back to fix at least the errors."
			str="$str\nIf you hit cancel, we will abort here and go back to the menu"
			if ask_option back "Issues detected. what do you want to do?" "$str" required back "go back to fix the issues" ignore "continue, ignoring the issues"
			then
				[ "$ANSWER_OPTION" == ignore ] && ALLOK=1
			else
				return 1
			fi
		else
			ALLOK=1
		fi

	done


	process_filesystems && notify "Partitions were successfully created." && return 0
	ask_yesno "Seems like some stuff went wrong while processing the filesystems.  do you want to rollback? (this cleans up the new mountpoints, filesystems, etc. not doing this can break the next run of the installer unless you clean it up yourself" yes && rollback_filesystems
	return 1
}


# select_packages()
# prompts the user to select packages to install
#
# params: none
# returns: 1 on error
interactive_select_packages() {
	local needed_pkgs=()

	# set up our install location if necessary and sync up so we can get package lists
	target_prepare_pacman || { show_warning 'Pacman preparation failure' "Pacman preparation failed! Check $LOG for errors."; return 1; }

	repos=`list_pacman_repos target`
	notify "Package selection is split into three stages. First, you will select a bootloader. Then, you will select package groups that contain packages that you may be interested in. Lastly, you will be presented with a full list of packages for each group, allowing you to fine-tune.\n\n
Note that packages (and groups) selection are limited to the repos you enabled earlier ($repos)\n
Once you have your Arch system up and running, you have access to more repositories and packages.\n\n
If any previous configuration you've done until now (like fancy filesystems) require extra packages, we've already preselected them for your convenience"

	# show group listing for group selection, base is ON by default, all others are OFF
	local grouplist=(base ^ ON)
	for i in $(list_package_groups | grep -v '^base$'); do
		grouplist+=(${i} - OFF)
	done

	ask_option no "Choose bootloader" "Which bootloader would you like to use?" optional \
	"syslinux" "Syslinux bootloader (${syslinux_supported_fs[*]})"

	bootloader=$ANSWER_OPTION

	# Make sure selected bootloader is a supported_bootloader and mark bootloader for installation
	check_is_in $bootloader "${supported_bootloaders[@]}" && needed_pkgs+=("$bootloader")

	ask_checklist "Select Package groups\nDo not deselect base unless you know what you're doing!" 0 "${grouplist[@]}" || return 1
	grouplist=("${ANSWER_CHECKLIST[@]}")

	# get sorted array of available packages, with their groups.
	local pkgall=($(list_packages repo "$repos" | cut -d ' ' -f2 | sort | uniq)) # list of all packages
	pkginfo "${pkgall[@]}"

	# build the list of options, sorted primarily by group, then by packagename (this is already). marking where appropriate
	local pkglist=()
	needed_pkgs+=("${needed_pkgs_fs[@]}")
	while read pkgname pkgver pkggroup pkgdesc; do
		mark=OFF
		if check_is_in "$pkggroup" "${grouplist[@]}" || check_is_in $pkgname "${needed_pkgs[@]}"; then
			mark=ON
		fi
		pkglist+=("$pkgname" "$pkggroup" $mark "$pkgname $pkgver: $pkgdesc")
	done < <(echo "$PACKAGE_INFO" | sort -f -k 3b,3 -k 1,1)

	[ ${#pkglist[@]} -eq 0 ] && show_warning "No packages found" "Sorry. I could not find any packages. maybe your network is not setup correctly, you lost connection, no mirror setup, bad group, ..." && return 1

	ask_checklist "Select Packages To Install." 1 "${pkglist[@]}" || return 1
	var_TARGET_PACKAGES="${ANSWER_CHECKLIST[@]}"
	return 0
}


# Hand-hold through setting up networking
#
# args: none
# returns: 1 on failure
interactive_runtime_network() {
	notify "If you wish to load your ethernet modules manually, please do so now in an another terminal."
	local ifaces=()

	for i in /sys/class/net/!(lo); do
		ifaces+=(${i##*/} $(<"$i/address"))
	done

	[ -z "$ifaces" ] && show_warning "No network interfaces?" "Cannot find any ethernet interfaces. This usually means udev was\nunable to load the module and you must do it yourself. Switch to\nanother VT, load the appropriate module, and run this step again." && return 1

	INTERACE_PREV=$INTERFACE
	unset INTERFACE DHCP IPADDR SUBNET BROADCAST GW DNS PROXY_HTTP PROXY_FTP
	ask_option no "Interface selection" "Select a network interface" required "${ifaces[@]}" || return 1
	INTERFACE=$ANSWER_OPTION
	[ "$INTERFACE" = "$INTERFACE_PREV" ] && INTERFACE_PREV=

	if ask_yesno "Do you want to use DHCP?"
	then
		inform "Please wait.  Polling for DHCP server on $INTERFACE..."
		dhcpcd -k $INTERFACE >$LOG 2>&1
		if ! dhcpcd $INTERFACE >$LOG 2>&1
		then
			show_warning "Dhcpcd problem" "Failed to run dhcpcd.  See $LOG for details."
			return 1
		fi
		if ! ip -o addr show dev $INTERFACE | grep -q inet
		then
			show_warning "Dhcpcd problem" "DHCP request failed. dhcpcd returned 0 but no ip configured for $INTERFACE"
			return 1
		fi
		DHCP=1
	else
		DHCP=0
		local USERHAPPY=0
		while [ "$USERHAPPY" = 0 ]; do
			ask_string "Enter your IP address" "192.168.0.2" || return 1
			IPADDR=$ANSWER_STRING
			ask_string "Enter your netmask" "255.255.255.0" || return 1
			SUBNET=$ANSWER_STRING
			ask_string "Enter your broadcast" "$(sed 's/\.[^.]*$/\.255/' <<< $IPADDR)" || return 1
			BROADCAST=$ANSWER_STRING
			ask_string "Enter your gateway (optional)" "$(sed 's/\.[^.]*$/\.1/' <<< $IPADDR)" 0 || return 1
			GW=$ANSWER_STRING
			[ -n "$GW" ] && default_dns="$GW"
			[ -z "$GW" ] && default_dns="$(sed 's/\.[^.]*$/\.1/' <<< $IPADDR)"
			ask_string "Enter your DNS server IP" "$default_dns" || return 1
			DNS=$ANSWER_STRING
			ask_string "Enter your HTTP proxy server, for example:\nhttp://name:port\nhttp://ip:port\nhttp://username:password@ip:port\n\n Leave the field empty if no proxy is needed to install." "" 0 || return 1
			PROXY_HTTP=$ANSWER_STRING
			ask_string "Enter your FTP proxy server, for example:\nhttp://name:port\nhttp://ip:port\nhttp://username:password@ip:port\n\n Leave the field empty if no proxy is needed to install." "" 0 || return 1
			PROXY_FTP=$ANSWER_STRING
			if ask_yesno "Are these settings correct?\n\nIP address:         $IPADDR\nNetmask:            $SUBNET\nBroadcast:          $BROADCAST\nGateway (optional): $GW\nDNS server:         $DNS\nHTTP proxy server:  $PROXY_HTTP\nFTP proxy server:   $PROXY_FTP"
			then
				USERHAPPY=1
			fi
		done

		echo "running: ip link set dev $INTERFACE up" >$LOG
		if ! ip link set dev $INTERFACE up
		then
			show_warning "ip link problem" "Failed to bring the interface $INTERFACE up"
			return 1
		fi
		echo "running: ip addr add $IPADDR/${SUBNET:-24} broadcast ${BROADCAST:-+} dev $INTERFACE" >$LOG
		if ! ip addr add $IPADDR/${SUBNET:-24} broadcast ${BROADCAST:-+} dev $INTERFACE >$LOG 2>&1
		then
			show_warning "ip addr problem" "Failed to setup interface $INTERFACE"
			return 1
		fi
		if [ -n "$GW" ]; then
			ip route add default via $GW >$LOG 2>&1 || { show_warning 'ip route problem' "Failed to setup your gateway. Check $LOG for errors."; return 1; }
		fi
		if [ -z "$PROXY_HTTP" ]; then
			unset http_proxy
		else
			export http_proxy=$PROXY_HTTP
		fi
		if [ -z "$PROXY_FTP" ]; then
			unset ftp_proxy
		else
			export ftp_proxy=$PROXY_FTP
		fi
		echo "nameserver $DNS" >/etc/resolv.conf
	fi

	echo "INTERFACE_PREV=$INTERFACE_PREV
          INTERFACE=$INTERFACE
          DHCP=$DHCP
          IPADDR=$IPADDR
          SUBNET=$SUBNET
          BROADCAST=$BROADCAST
          GW=$GW
          DNS=$DNS
          PROXY_HTTP=$PROXY_HTTP
          PROXY_FTP=$PROXY_FTP" > $RUNTIME_DIR/aif-network-settings || return 1
	notify "The network is configured."
	return 0
}

# bootloader is global variable that gets set in interactive_select_packages
interactive_install_bootloader () {
	if [[ $bootloader = syslinux ]]; then
		interactive_syslinux || return 1
	else
		show_warning 'No Bootloader' 'You did not select a bootloader. No bootloader will be installed.'
	fi
	return 0
}

interactive_syslinux() {
	debug FS "starting interactive_syslinux"

	# Find and Store the device that has the root filesystem
	get_device_with_mount '/' || return 1
	PART_ROOT="$ANSWER_DEVICE"

	# Gets boot device
	get_device_with_mount '/boot'
	bootdev="$ANSWER_DEVICE"

	# Check to see if /boot or / (root) has a valid FS type and set bootpart
	# bootpart == device with /boot dir
	if [[ $bootdev ]]; then
		filesystem="$(awk '/ \/boot /{print $4}' $TMP_FILESYSTEMS)"
		debug FS "$bootdev - FS type: $filesystem"

		local bootpart="$bootdev"
	else
		filesystem="$(awk '/ \/ /{print $4}' $TMP_FILESYSTEMS)"
		debug FS "$PART_ROOT - FS type: $filesystem"

		local bootpart="$PART_ROOT"
	fi

	if ! check_is_in "$filesystem" "${syslinux_supported_fs[@]}"; then
		show_warning "Invalid FS" "Error: Syslinux does not support $filesystem.\n\nThe following filesystems are supported:\n  ${syslinux_supported_fs[@]}"
		return 1
	fi

	# remove default entries by truncating file at our little tag (#-*)
	sed -i -e '/#-\*/q' "$syslinuxmenu"

	# Generate menu and prompt user to edit it
	interactive_bootloader_menu "syslinux" "$syslinuxmenu" || return $?

	if device_is_raid "$bootpart"; then
		debug FS "Software RAID detected"
		local onraid=true
	fi

	debug FS "Installing Syslinux (chroot $var_TARGET_DIR syslinux-install_update -i)"
	inform "Installing Syslinux..."
	if ! "chroot $var_TARGET_DIR syslinux-install_update" -i >$LOG 2>&1; then
		debug FS "FAILED: syslinux-install_update -i failed"
		show_warning "FAILED" "syslinux-install_update -i failed"
		return 1
	fi

	if ask_yesno "Set boot flag(s) and install the Syslinux MBR?" yes; then
		inform "Setting Boot Flag(s)...\nThis could take a while. Please be patient.\n\n" syslinuxprog
		if "chroot $var_TARGET_DIR syslinux-install_update" -a >$LOG 2>&1; then
			debug FS "Successfully set boot flag(s)"
		else
			debug FS "Failde to set boot flag(s). syslinux-install_update -a failed with Error Code - $?"
			show_warning "FAILED" "Failed to set boot flag(s). MBR not installed" && return 1
		fi

		inform "Installing Syslinux MBR..." syslinuxprog
		if "chroot $var_TARGET_DIR syslinux-install_update" -m >$LOG 2>&1; then
			debug FS "Successfully installed MBR(s)"
		else
			debug FS "Failed to install MBR. syslinux-install_update -m failed with Error Code - $?"
			show_warning "FAILED" "Failed to install the MBR!" && return 1
		fi
	fi
	notify "Syslinux Installation Successful"
}

generate_syslinux_menu () {
	get_kernel_parameters || return

	cat >>$syslinuxmenu <<EOF

# (0) Arch Linux
LABEL arch
    MENU LABEL Arch Linux
    LINUX ../vmlinuz-linux
    APPEND $kernel_parameters
    INITRD ../initramfs-linux.img

# (1) Arch Linux Fallback
LABEL archfallback
    MENU LABEL Arch Linux Fallback
    LINUX ../vmlinuz-linux
    APPEND $kernel_parameters
    INITRD ../initramfs-linux-fallback.img

# (2) Windows
#LABEL windows
#COM32 chain.c32
#APPEND hd0 0

LABEL hdt
    MENU LABEL HDT (Hardware Detection Tool)
    COM32 hdt.c32

LABEL reboot
    MENU LABEL Reboot
    COM32 reboot.c32

LABEL off
    MENU LABEL Power Off
    COMBOOT poweroff.com
EOF
}

# $1 - Bootloader Name
# $2 - Bootloader Configuration Files
interactive_bootloader_menu() {
	if [[ $1 = syslinux ]]; then
		generate_syslinux_menu || return
	fi

	ls -1 /sys/block | grep -q ^dm- && local helptext="  /dev/mapper/ users: Pay attention to the kernel line!"
	notify "Before installing $1, you must review the configuration file.  You will now be put into the editor.  After you save your changes and exit the editor, you can install $1.$helptext"

	seteditor || return 1

	$EDITOR $2
}

get_kernel_parameters() {
	get_device_with_mount '/' || return 1
	local rootpart="$ANSWER_DEVICE"

	case "$PART_ACCESS" in
		label)
			local label
			label="$(getlabel $rootpart)" && \
			rootpart="/dev/disk/by-label/$label" ;;
		uuid)
			local uuid
			uuid="$(getuuid $rootpart)" && \
			rootpart="/dev/disk/by-uuid/$uuid" ;;
	esac

	kernel_parameters="root=$rootpart ro"

	local raw_device crypt_device lv_device 

	if get_anchestors_mount ';/;'; then
		if echo "$ANSWER_DEVICES" | sed -n '1p' | grep -q 'dm_crypt$' && echo "$ANSWER_DEVICES" | sed -n '2p' | grep -q 'raw$'
		then
			debug 'FS' 'get_kernel_parameters - Found / on dm_crypt on raw'
			raw_device="$(echo "$ANSWER_DEVICES" | sed -n '2p' | cut -d ' ' -f1)"
			crypt_device="$(echo "$ANSWER_DEVICES" | sed -n '1p' | cut -d ' ' -f1)"
			kernel_parameters="root=$crypt_device cryptdevice=$raw_device:`basename $crypt_device` ro"
		elif echo "$ANSWER_DEVICES" | sed -n '1p' | grep -q 'lvm-lv$' && echo "$ANSWER_DEVICES" | sed -n '4p' | grep -q 'dm_crypt$' && echo "$ANSWER_DEVICES" | sed -n '5p' | grep -q 'raw$'
		then
			debug 'FS' 'get_kernel_parameters - Found / on lvm on dm_crypt on raw'
			lv_device="$(echo "$ANSWER_DEVICES" | sed -n '1p' | cut -d ' ' -f1)"
			crypt_device="$(echo "$ANSWER_DEVICES" | sed -n '4p' | cut -d ' ' -f1)"
			raw_device="$(echo "$ANSWER_DEVICES" | sed -n '5p' | cut -d ' ' -f1)"
			kernel_parameters="root=$lv_device cryptdevice=$raw_device:`basename $crypt_device` ro"
		elif echo "$ANSWER_DEVICES" | sed -n '1p' | grep -q 'dm_crypt$' && echo "$ANSWER_DEVICES" | sed -n '2p' | grep -q 'lvm-lv$' && echo "$ANSWER_DEVICES" | sed -n '5p' | grep -q 'raw$'
		then
			debug 'FS' 'get_kernel_parameters - Found / on dm_crypt on lvm on raw'
			crypt_device="$(echo "$ANSWER_DEVICES" | sed -n '1p' | cut -d ' ' -f1)"
			lv_device="$(echo "$ANSWER_DEVICES" | sed -n '2p' | cut -d ' ' -f1)"
			kernel_parameters="root=$crypt_device cryptdevice=$lv_device:`basename $crypt_device` ro"
		elif echo "$ANSWER_DEVICES" | sed -n '1p' | grep -q 'raw$'
		then
			debug 'FS' 'get_kernel_parameters - Found / on raw'
		elif echo "$ANSWER_DEVICES" | sed -n '1p' | grep -q 'lvm-lv$' && echo "$ANSWER_DEVICES" | sed -n '4p' | grep -q 'raw$'
		then
			debug 'FS' 'get_kernel_parameters - Found / on lvm on raw'
		else
			debug 'FS' 'get_kernel_parameters - Could not figure this one out'
			show_warning "Disk setup detection" "Are you using some really fancy dm_crypt/lvm/softraid setup?\nI could not figure it out, so the kernel line will be the default: you'll probably need to edit it.\nPlease file a bug for this and supply all files from /tmp/aif/"
		fi
	else
		show_warning "Disk setup detection" "Could not find out where your / is.  Did you setup filesystems/blockdevices? manual/autoprepare?  If yes, please file a bug and tell us about this"
		return 1
	fi
}


# displays installation source selection menu
# and sets up relevant configuration
#
# params: none
# returns: nothing
interactive_select_source() {
	shopt -s nullglob
	repos_onboard=(/repo/*)
	shopt -u nullglob
	list=()
	for repo in "${repos_onboard[@]##*/}"; do
		if [ $repo == 'core' ]; then
			list+=($repo-local "$repo mounted in install medium" ON)
		else
			list+=($repo-local "$repo mounted in install medium" OFF)
		fi
	done
	for repo in $(list_possible_repos); do
		if [ $repo == 'core' -a ! -d /repo/core ]; then
			list+=($repo-remote "$repo on remote mirror" ON)
		else
			list+=($repo-remote "$repo on remote mirror" OFF)
		fi
	done
	# TODO: enable repositories in pacman.conf on target system based on this
	warn="Be sure to select [core], that's the only must."
	[ -d /repo/core ] && warn="Be sure to select at least one [core] location,
	having [core] available is the only must.  DON'T combine a local repository
	with remote mirrors unless you know what you're doing (this will cause BROKEN packages)!"
	ask_checklist "Please select which repositories you want to activate.  $warn.
	Note that this does not affect pacman.conf on the target system, you can
	finetune repositories later in the \"Configure system\" step.
	If you want to manually mount other repositories first, you can mount them under /repo and rerun this step." 0 "${list[@]}" || return 1
	repos="${ANSWER_CHECKLIST[@]}"
	if echo ${repos[@]} | grep -q remote; then
		interactive_select_mirror
	fi
	if echo ${repos[@]} | grep -q local && echo ${repos[@]} | grep -q remote; then
		ask_option local-first "Repository ordering" "How do you want to order
		them?  Like mentioned before, you are aware this will usually BREAK your system right?" '' \
		"local_first" "Only consult remote when package is not found locally. Consumes less bandwith" \
		"remote_first" "Consult local repository only when package is not found on remote.  Can yield more up-to-date system"
		repo_ordering=$ANSWER_OPTION
		if [ $repo_ordering = 'remote_first' ]; then
			# we use the property that the reverse of remote ('etomer') sorts
			# before the reverse of local
			repos=($(echo ${repos[@]} | tr ' ' '\n' | rev | sort | rev | tr '\n' ' '))
		fi
	fi
	TARGET_REPOSITORIES=()
	for repo in ${repos[@]}; do
		if echo $repo | grep -q local; then
			repo=$(echo $repo | sed 's/-local//')
			TARGET_REPOSITORIES+=($repo 'file:///repo/$repo/$arch')
		else
			repo=$(echo $repo | sed 's/-remote//')
			TARGET_REPOSITORIES+=($repo $var_MIRRORLIST)
		fi
	done
	return 0
}


# Prompt user for preferred mirror and set $MIRROR
# args: none
# returns: nothing
interactive_select_mirror() { 
	notify "Keep in mind ftp.archlinux.org is throttled.\nPlease select another mirror to get full download speed."
	# FIXME: this regex doesn't honor commenting
	MIRRORS=$(egrep -o '((ftp)|(http))://[^/]*' "${var_MIRRORLIST}" | sed 's|$| _|g')
	ask_option no "Mirror selection" "Select an FTP/HTTP mirror" required $MIRRORS "Custom" "_" || return 1
	local _server=$ANSWER_OPTION
	if [ "${_server}" = "Custom" ]; then
		ask_string "Enter the full URL to core repo." "ftp://ftp.archlinux.org/core/os/$var_ARCH" || return 1
		MIRROR=${ANSWER_STRING/\/core\///\$repo/} #replace '/core/' by '/$repo/'
		# MIRROR now something like ftp://ftp.archlinux.org/$repo/os/i686
	else
		# _server is something like ftp://archlinux.puzzle.ch
		# Form the full URL for our mirror by grepping for the server name in
		# our mirrorlist and pulling the full URL out.
		# Ensure that if it was listed twice we only return one line for the mirror.
		MIRROR=$(egrep -o "${_server}.*" "${var_MIRRORLIST}" | head -n1)
		# MIRROR now something like ftp://archlinux.puzzle.ch/$repo/os/$arch
	fi
	echo "Using mirror: $MIRROR" >$LOG
}
