#!/bin/sh

. /lib/partman/lib/md-base.sh
. /lib/partman/lib/commit.sh

do_initial_setup () {
	# Commit the changes
	confirm_changes partman-md || exit 0
	commit_changes partman-md/commit_failed || exit $?

	# Try to detect MD devices, and start them
	mkdir -p /dev/md
	log-output -t partman-md --pass-stdout \
		mdadm --examine --scan --config=partitions >/tmp/mdadm.conf
	log-output -t partman-md \
		mdadm --assemble --scan --run \
			--config=/tmp/mdadm.conf --auto=yes
}

make_choices () {
	local line
	descriptions=""
	pathmap=""
	IFS="$NL"
	for line in $(md_list_allowed_free); do
		restore_ifs
		local dev="${line%%$TAB*}"
		line="${line#*$TAB}"
		local id="${line%%$TAB*}"
		line="${line#*$TAB}"
		local size="${line%%$TAB*}"
		local path="${line#*$TAB}"
		cd $dev
		if [ -s "$id/visual_filesystem" ]; then
			local visual="$(cat "$id/visual_filesystem")"
			output=$(printf "%-30s (%sMB; %s)" "$path" "$(convert_to_megabytes $size)" "$visual")
		else
			output=$(printf "%-30s (%sMB)" "$path" "$(convert_to_megabytes $size)")
		fi
		descriptions="${descriptions:+$descriptions, }$output"
		pathmap="${pathmap:+$pathmap$NL}$path$TAB$dev//$id"
		IFS="$NL"
	done
	restore_ifs
}

count_choices () {
	echo "$1" | sed -e "s/ *, */\\$NL/g" | wc -l
}

prune_choices () {
	local new_descriptions=
	while [ "$descriptions" ]; do
		local description="${descriptions%%, *}"
		if [ "${descriptions#*, }" = "$descriptions" ]; then
			descriptions=
		else
			descriptions="${descriptions#*, }"
		fi
		local chosen="$1"
		local found=0
		while [ "$chosen" ]; do
			local choice="${chosen%%, *}"
			if [ "${chosen#*, }" = "$chosen" ]; then
				chosen=
			else
				chosen="${chosen#*, }"
			fi
			if [ "$description" = "$choice" ]; then
				found=1
				break
			fi
		done
		if [ "$found" -eq 0 ]; then
			new_descriptions="${new_descriptions:+$new_descriptions, }$description"
		fi
	done
	descriptions="$new_descriptions"
}

prepare_devices () {
	local devices="$1"
	local new_devices=
	local need_commit=
	while [ "$devices" ]; do
		local dev="${devices%%, *}"
		if [ "${devices#*, }" = "$devices" ]; then
			devices=
		else
			devices="${devices#*, }"
		fi
		dev="$(echo "$dev" | sed -e 's/ *([^)]*)//g')"
		local line
		IFS="$NL"
		for line in $pathmap; do
			restore_ifs
			if [ "${line%%$TAB*}" = "$dev" ]; then
				local devid="${line#*$TAB}"
				local path
				if path="$(md_prepare "${devid%//*}" "${devid#*//}")"; then
					need_commit=true
				fi
				new_devices="${new_devices:+$new_devices }$path"
				break
			fi
			IFS="$NL"
		done
		restore_ifs
	done
	echo "$new_devices"

	[ "$need_commit" ]
}

md_create_raid0 () {
	db_subst partman-md/raid0devs PARTITIONS "$descriptions"
	db_reset partman-md/raid0devs
	db_input critical partman-md/raid0devs
	db_go || return

	db_get partman-md/raid0devs
	local selected="$(count_choices "$RET")"
	prune_choices "$RET"

	local md_num="$(md_next_device_number)"

	logger -t partman-md "Number of devices in the RAID0 array md$md_num: $selected"

	local raid_devices="$RET"
	if raid_devices="$(prepare_devices "$raid_devices")"; then
		do_initial_setup
	fi

	log-output -t partman-md \
		mdadm --create "$(md_devnode_create "$md_num")" --auto=yes \
		      --force -R -l raid0 \
		      -n $selected $raid_devices || return $?
	md_lock_devices "md$md_num" $raid_devices
}

md_create_array () {
	local min_size i

	case $1 in
	    RAID1)	min_size=2 ;;
	    RAID5)	min_size=3 ;;
	    RAID6)	min_size=4 ;;
	    RAID10)	min_size=2 ;;
	    *)		return ;;
	esac

	local level="${1#RAID}"

	for i in devcount devs sparecount sparedevs; do
		db_subst partman-md/raid$i LEVEL "$level"
	done
	db_subst partman-md/raiddevcount MINIMUM "$min_size"

	db_set partman-md/raiddevcount "$min_size"
	md_db_get_number partman-md/raiddevcount "$min_size" 99 || return
	local dev_count="$RET"

	db_set partman-md/raidsparecount 0
	md_db_get_number partman-md/raidsparecount 0 99 || return
	local spare_count="$RET"

	if [ "$level" -ne 1 ] && [ "$dev_count" -lt "$min_size" ]; then
		# minimum number for the selected RAID level
		dev_count="$min_size"
	fi
	local required="$(($dev_count + $spare_count))"
	if [ "$level" -ne 1 ] && [ "$required" -gt "$num_parts" ]; then
		db_subst partman-md/notenoughparts NUM_PART "$num_parts"
		db_subst partman-md/notenoughparts REQUIRED "$required"
		db_input critical partman-md/notenoughparts
		db_go
		return
	fi

	db_set partman-md/raiddevs ''
	local selected=0

	# RAID 5/6/10: loop until correct number of active devices selected
	# RAID 1: loop until at least one device selected
	until ([ "$level" -ne 1 ] && [ "$selected" -eq "$dev_count" ]) || \
	      ([ "$level" -eq 1 ] && [ "$selected" -gt 0 ] && [ "$selected" -le "$dev_count" ]); do
		db_subst partman-md/raiddevs COUNT "$dev_count"
		db_subst partman-md/raiddevs PARTITIONS "$descriptions"
		db_input critical partman-md/raiddevs
		db_go || return

		db_get partman-md/raiddevs
		selected="$(count_choices "$RET")"
	done

	local missing_devices=
	if [ "$level" -eq 1 ]; then
		# Add "missing" for as many devices as weren't selected
		while [ "$selected" -lt "$dev_count" ]; do
			missing_devices="$missing_devices missing"
			selected="$(($selected + 1))"
		done
	fi

	# Remove partitions selected in raiddevs from the descriptions list
	db_get partman-md/raiddevs
	prune_choices "$RET"

	db_set partman-md/raidsparedevs ''
	selected=0
	if [ "$spare_count" -gt 0 ] && [ -n "$descriptions" ]; then
		local first=1
		# Loop until the correct number of devices has been selected.
		# That means any number less than or equal to the spare count.
		while [ "$selected" -gt "$spare_count" ] || [ "$first" -eq 1 ]; do
			first=0
			db_subst partman-md/raidsparedevs COUNT "$spare_count"
			db_subst partman-md/raidsparedevs PARTITIONS "$descriptions"
			db_input critical partman-md/raidsparedevs
			db_go || return

			db_get partman-md/raidsparedevs
			selected="$(count_choices "$RET")"
		done
	fi

	local layout=
	if [ "$level" -eq 10 ]; then
		db_set partman-md/raid10layout n2
		while :; do
			db_input low partman-md/raid10layout
			db_go || return
			db_get partman-md/raid10layout
			if echo "$RET" | egrep -q '^[nfo][0-9]{1,2}$' && \
			   [ "$(echo "$RET" | sed 's/.//')" -le "$dev_count" ]; then
				break
			fi
		done
		layout="--layout=$RET"
	fi

	# The number of spares the user has selected
	local named_spares="$selected"

	db_get partman-md/raiddevs
	local raid_devices="$RET"

	db_get partman-md/raidsparedevs
	local spare_devices="$RET"

	local need_commit=
	if raid_devices="$(prepare_devices "$raid_devices")"; then
		need_commit=true
	fi
	if spare_devices="$(prepare_devices "$spare_devices")"; then
		need_commit=true
	fi
	if [ "$need_commit" ]; then
		do_initial_setup
	fi

	local missing_spares=
	local count="$named_spares"
	while [ "$count" -lt "$spare_count" ]; do
		missing_spares="$missing_spares missing"
		count="$(($count + 1))"
	done

	local md_num="$(md_next_device_number)"

	logger -t partman-md "Selected spare count: $named_spares"
	logger -t partman-md "RAID devices count: $dev_count"
	logger -t partman-md "Spare devices count: $spare_count"
	log-output -t partman-md \
		mdadm --create "$(md_devnode_create "$md_num")" --auto=yes \
			--force -R -l raid$level $layout \
			-n $dev_count -x $spare_count $raid_devices \
			$missing_devices $spare_devices $missing_spares || \
		return $?
	md_lock_devices "md$md_num" $raid_devices $spare_devices
}

do_create () {
	db_input critical partman-md/createmain
	db_go || return
	db_get partman-md/createmain
	local raid_sel="$RET"
	make_choices
	if [ -z "$descriptions" ]; then
		db_input critical partman-md/noparts
		db_go
		return
	fi
	num_parts="$(count_choices "$descriptions")"
	case $raid_sel in
	    RAID10|RAID6|RAID5|RAID1)
		md_create_array "$raid_sel" ;;
	    RAID0)
		md_create_raid0 ;;
	esac
}

md_delete_verify () {
	local device="$(echo "$1" | sed 's/^\(md.*\)_.*/\1/')"
	local devices="$(grep "^$device[ :]" /proc/mdstat | \
			 sed 's/^.*active \(.*\)/\1/; s/raid[0-9]* //')"
	local mddev="$(md_devnode "$device")" || return 1
	local mdtype="$(md_get_level "$mddev")"

	db_set partman-md/deleteverify false
	db_subst partman-md/deleteverify DEVICE "/dev/$device"
	db_subst partman-md/deleteverify TYPE "$mdtype"
	db_subst partman-md/deleteverify DEVICES "$devices"
	db_input critical partman-md/deleteverify
	db_go || return 0
	db_get partman-md/deleteverify
	[ "$RET" = true ] || return 0

	# Stop the MD device and zero the superblock of all the component
	# devices
	devices="$(mdadm -Q --detail "$mddev" | \
		   egrep '^[[:space:]]*[0-9].*(active|spare)' | \
		   sed 's/.* //')"
	logger -t partman-md "Removing $mddev ($devices)"
	log-output -t partman-md mdadm --stop "$mddev" || return 1
	local dev
	for dev in $devices; do
		log-output -t partman-md \
			mdadm --zero-superblock --force "$dev" || return 1
		partman_unlock_unit "$dev"
	done
	return 0
}

do_delete () {
	md_get_devices
	if [ -z "$MD_DEVICES" ]; then
		db_input high partman-md/delete_no_md
		db_go
		return
	fi

	db_subst partman-md/deletemenu DEVICES "$MD_DEVICES"
	db_input critical partman-md/deletemenu
	db_go || return
	db_get partman-md/deletemenu

	if ! md_delete_verify "$RET"; then
		db_input critical partman-md/deletefailed
		db_go
	fi
}

do_initial_setup

while :; do
	db_input critical partman-md/mainmenu
	db_go || exit 10
	db_get partman-md/mainmenu
	case $RET in
	    create)	do_create ;;
	    delete)	do_delete ;;
	    finish)	break ;;
	    *)
		logger -t partman-md "Unknown selection '$RET'"
		break ;;
	esac
done

stop_parted_server

restart_partman

exit 0
