#!/bin/sh

PREREQ=""

prereqs()
{
	echo "$PREREQ"
}

case $1 in
prereqs)
	prereqs
	exit 0
	;;
esac

. /usr/share/initramfs-tools/hook-functions

get_root_device() {
	local device mount type options dump pass

	if [ ! -r /etc/fstab ]; then
		return 1
	fi

	grep '^[^#]' /etc/fstab | \
	while read device mount type options dump pass; do
		if [ "$mount" = "/" ]; then
			echo "$device"
			return
		fi
	done
}

get_resume_devices() {
	local device opt count dupe candidates devices
	candidates=""

	# First, get a list of potential resume devices

	# uswsusp
	if [ -e /etc/uswsusp.conf ]; then
		device=$(sed -rn 's/^resume device[[:space:]]+[:=][[:space:]]+// p' /etc/uswsusp.conf)
		if [ -n "$device" ]; then
			candidates="$candidates $device"
		fi
	fi

	# uswsusp - again...
	if [ -e /etc/suspend.conf ]; then
		device=$(sed -rn 's/^resume device[[:space:]]+[:=][[:space:]]+// p' /etc/suspend.conf)
		if [ -n "$device" ]; then
			candidates="$candidates $device"
		fi
	fi

	# regular swsusp
	for opt in $(cat /proc/cmdline); do
		case $opt in
		resume=*)
			device="${opt#resume=}"
			candidates="$candidates $device"
			;;
		esac
	done

	# initramfs-tools
	if [ -e /etc/initramfs-tools/conf.d/resume ]; then
		device=$(sed -rn 's/^RESUME[[:space:]]+=[[:space:]]+// p' /etc/initramfs-tools/conf.d/resume)
		if [ -n "$device" ]; then
			candidates="$candidates $device"
		fi
	fi

	# Now check the sanity of all candidates
	devices=""
	count=0
	for device in $candidates; do
		# Weed out clever defaults
		if [ "$device" = "<path_to_resume_device_file>" ]; then
			continue
		fi

		# Weed out duplicates
		dupe=0
		for opt in $devices; do
			if [ "$device" = "$opt" ]; then
				dupe=1
			fi
		done
		if [ $dupe -eq 1 ]; then
			continue
		fi

		# This device seems ok
		devices="$devices $device"
		count=$(( $count + 1 ))
	done

	if [ $count -gt 1 ]; then
		echo "cryptsetup: WARNING: found more than one resume device candidate:" >&2
		for device in $devices; do
			echo "                     $device" >&2
		done
	fi

	if [ $count -gt 0 ]; then
		echo $devices
	fi

	return 0
}

node_is_in_crypttab() {
	local node
	node="$1"

	grep -q ^$node /etc/crypttab
	return $?
}

get_lvm_deps() {
	local node deps maj min depnode
	node="$1"

	if [ -z $node ]; then
		echo "cryptsetup: WARNING: get_lvm_deps - invalid arguments" >&2
		return 1
	fi

	if ! deps=$(dmsetup deps "$node" 2> /dev/null | sed 's/[^:]*: *//;s/[ (]//g;s/)/ /g'); then
		echo "cryptsetup: WARNING: failed to find deps for $node" >&2
		return 1
	fi

	# We should now have a list of major,minor pairs, e.g. "3,2 3,3"
	for dep in $deps; do
		maj=${dep%,*}
		min=${dep#*,}
		depnode=$(dmsetup ls | sed -n "s/\\([^ ]*\\) *($maj, $min)/\\1/p")
		if [ -z "$depnode" ]; then
			continue
		fi
		if [ "$(dmsetup table $depnode 2> /dev/null | cut -d' ' -f3)" != "crypt" ]; then
			continue
		fi
		echo "$depnode"
	done

	return 0
}

get_device_opts() {
	local target source link extraopts rootopts opt
	target="$1"
	extraopts="$2"
	KEYSCRIPT=""
	OPTIONS=""

	if [ -z "$target" ]; then
		echo "cryptsetup: WARNING: get_device_opts - invalid arguments" >&2
		return 1
	fi

	opt=$( grep ^$target /etc/crypttab | head -1 | sed 's/[[:space:]]\+/ /g' )
	source=$( echo $opt | cut -d " " -f2 )
	key=$( echo $opt | cut -d " " -f3 )
	rootopts=$( echo $opt | cut -d " " -f4- )

	if [ -z "$opt" ] || [ -z "$source" ] || [ -z "$key" ] || [ -z "$rootopts" ]; then
		echo "cryptsetup: WARNING: invalid line in /etc/crypttab - $opt" >&2
		return 1
	fi

	# Sanity checks for $source
	if [ -h "$source" ]; then
		link=$(readlink -nqe "$source")
		if [ -z "$link" ]; then
			echo "cryptsetup: WARNING: $source is a dangling symlink" >&2
			return 1
		fi

		if [ "$link" != "${link#/dev/mapper/}" ]; then
			echo "cryptsetup: NOTE: using $link instead of $source for $target" >&2
			source="$link"
		fi
	fi

	# Sanity checks for $key
	if [ "$key" = "/dev/random" ] || [ "$key" = "/dev/urandom" ]; then
		echo "cryptsetup: WARNING: target $target has a random key, skipped" >&2
		return 1
	fi

	if [ -n "$extraopts" ]; then
		rootopts="$extraopts,$rootopts"
	fi

	# We have all the basic options, let's go trough them
	OPTIONS="target=$target,source=$source,key=$key"
	local IFS=", "
	for opt in $rootopts; do
		case $opt in
			cipher=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			hash=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			size=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			lvm=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			keyscript=*)
				opt=${opt#keyscript=}
				if [ ! -x "$opt" ]; then
					echo "cryptsetup: WARNING: target $target has an invalid keyscript, skipped" >&2
					return 1
				fi
				KEYSCRIPT="$opt"
				opt=$(basename "$opt")
				OPTIONS="$OPTIONS,keyscript=/keyscripts/$opt"
				;;
			*)
				# Presumably a non-supported option
				;;
		esac
	done

	# If keyscript is set, the "key" is just an argument to the script
	if [ "$key" != "none" ] && [ -z "$KEYSCRIPT" ]; then
		echo "cryptsetup: WARNING: target $target uses a key file, skipped" >&2
		return 1
	fi
}

get_device_modules() {
	local node value cipher blockcipher ivhash
	node="$1"

	# Check the ciphers used by the active root mapping
	value=$(dmsetup table "$node" | cut -d " " -f4)
	cipher=$(echo "$value" | cut -d ":" -f1 | cut -d "-" -f1)
	blockcipher=$(echo "$value" | cut -d ":" -f1 | cut -d "-" -f2)
	ivhash=$(echo "$value" | cut -d ":" -s -f2)

	if [ -n "$cipher" ]; then
		echo "$cipher"
	else
		return 1
	fi

	if [ -n "$blockcipher" ] && [ "$blockcipher" != "plain" ]; then
		echo "$blockcipher"
	fi

	if [ -n "$ivhash" ] && [ "$ivhash" != "plain" ]; then
		echo "$ivhash"
	fi
	return 0
}

prepare_keymap() {
	local env charmap

	# Allow the correct keymap to be loaded if possible
	if [ ! -x /bin/loadkeys ] || [ ! -r /etc/console/boottime.kmap.gz ]; then
		return 1
	fi

	copy_exec /bin/loadkeys /bin/
	cp /etc/console/boottime.kmap.gz $DESTDIR/etc/

	# Check for UTF8 console
	if [ ! -x /usr/bin/kbd_mode ]; then
		return 0
	fi

	if [ -r /etc/environment ]; then
		env="/etc/environment"
	elif [ -r /etc/default/locale ]; then
		env="/etc/default/locale"
	else
		return 0
	fi

	for var in LANG LC_ALL LC_CTYPE; do
		value=$(egrep "^[^#]*${var}=" $env | tail -n1 | cut -d= -f2)
		eval $var=$value
	done

	charmap=$(LANG=$LANG LC_ALL=$LC_ALL LC_CTYPE=$LC_CTYPE locale charmap)
	if [ "$charmap" = "UTF-8" ]; then
		copy_exec /usr/bin/kbd_mode /bin/
	fi
	return 0
}

add_device() {
	local node nodes opts lastopts i count
	nodes="$1"
	opts=""     # Applied to all nodes
	lastopts="" # Applied to last node

	if [ -z "$nodes" ]; then
		return 0
	fi

	# Check that it is a node under /dev/mapper/
	nodes="${nodes#/dev/mapper/}"
	if [ "$nodes" = "$1" ]; then
		return 0
	fi

	# Can we find this node in crypttab
	if ! node_is_in_crypttab "$nodes"; then
		# dm node but not in crypttab, is it a lvm device backed by dm-crypt nodes?
		lvmnodes=$(get_lvm_deps "$nodes") || return 1

		# not backed by any dm-crypt nodes; stop here
		if [ -z "$lvmnodes" ]; then
		    return 0
		fi

		# It is a lvm device!
		lastopts="lvm=$nodes"
		nodes="$lvmnodes"
	fi

	# Prepare to setup each node
	count=$(echo "$nodes" | wc -w)
	i=1
	for node in $nodes; do
		# Prepare the additional options
		if [ $i -eq $count ]; then
			if [ -z "$opts" ]; then
				opts="$lastopts"
			else
				opts="$opts,$lastopts"
			fi
		fi

		# Get crypttab root options
		if ! get_device_opts $node $opts; then
			continue
		fi
		echo "$OPTIONS" >> "$DESTDIR/conf/conf.d/cryptroot"

		# If we have a keyscript, make sure it is included
		if [ -n "$KEYSCRIPT" ]; then
			if [ ! -d "$DESTDIR/keyscripts" ]; then
				mkdir "$DESTDIR/keyscripts"
			fi
			copy_exec "$KEYSCRIPT" /keyscripts
		fi

		# Calculate needed modules
		modules=$(get_device_modules $node | sort | uniq)
		if [ -z "$modules" ]; then
			echo "cryptsetup: WARNING: failed to determine cipher modules to load for $node" >&2
			continue
		fi
		echo dm_mod
		echo dm_crypt
		echo "$modules"

		i=$(( $i + 1 ))
	done

	return 0
}


#
# Begin real processing
#

# Unless MODULES = "dep", we always add a basic subset of modules/tools
if [ "$MODULES" != "dep" ]; then
	for mod in dm_mod dm_crypt aes sha256 cbc; do
		manual_add_modules $mod
	done
	copy_exec /sbin/cryptsetup /sbin
	copy_exec /sbin/dmsetup /sbin
	prepare_keymap
fi

# We can't do anything without a config file
if [ ! -r /etc/crypttab ]; then
	exit 0
fi

# Find out which devices to add to the config file
rootdev=$(get_root_device)
if [ -z "$rootdev" ]; then
	echo "cryptsetup: WARNING: could not determine root device from /etc/fstab" >&2
fi
resumedevs=$(get_resume_devices)
if [ -z "$rootdev" ] && [ -z "$resumedevs" ]; then
	exit 0
fi

# Load the config opts and modules for each device
for dev in $rootdev $resumedevs; do
	if ! modules=$(add_device "$dev"); then
		echo "cryptsetup: FAILURE: could not determine configuration for $dev" >&2
		continue
	fi

	for mod in $modules; do
		manual_add_modules $mod
	done
done

# Prepare the initramfs
copy_exec /sbin/cryptsetup /sbin
copy_exec /sbin/dmsetup /sbin
prepare_keymap

# Done
exit 0
