#!/bin/bash

ks_devname()
{
	local devname=''
	case "$1" in
		LABEL=*)
			devname="$(blkid -o device -L "${1#LABEL=}")"
			;;
		UUID=*)
			devname="$(blkid -o device -U "${1#UUID=}")"
			;;
		/dev/*)
			[ -L "$1" ] &&
				devname="$(readlink -f "$1")" ||
				devname="$1"
			;;
		*)
			if [ -e "/sys/class/block/$1" ]; then
				devname=$(sed -n -e 's,DEVNAME=,/dev/,p' "/sys/class/block/$1/uevent")
			elif [ -e "/dev/md/$1" ]; then
				devname="$(readlink -f "/dev/md/$1")"
			fi
			;;
	esac

	[ -n "$devname" ] && [ -b "$devname" ] ||
		fatal "unable to get devname for argument: $1"

	printf '%s\n' "${devname#/dev/}"
}

ks_devname_list()
{
	set -- ${1//,/ }

	while [ "$#" -gt 0 ]; do
		ks_devname "$1"
		shift
	done
}

ks_cat_exec()
{
	cat >"$1"
	chmod +x "$1"
}

ignoredisk()
{
	local PROG TEMP dev
	local drives='' onlyuse=''
	local ret=0

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l 'drives:,only-use:' -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--drives)
				shift
				drives="$(ks_devname_list "$1")"
				;;
			--only-use)
				shift
				onlyuse="$(ks_devname_list "$1")"
				;;
			--) shift; break
				;;
		esac
		shift
	done

	if [ -n "$drives" ] && [ -n "$onlyuse" ]; then
		message "options --drives and --only-use are mutually exclusive."
		return 1
	fi

	drives=",${drives//$'\n'/,},"
	onlyuse=",${onlyuse//$'\n'/,},"

	set --

	for dev in $BLOCK_DEVICES; do
		if [ "$onlyuse" != ',,' ] && [ -n "${onlyuse##*,$dev,*}" ]; then
			verbose "'$dev' disk ignored"
			continue
		elif [ "$drives" != ',,' ] && [ -z "${drives##*,$dev,*}" ]; then
			verbose "'$dev' disk ignored"
			continue
		fi
		set -- "$@" "$dev"
	done

	BLOCK_DEVICES="$*"
}

clearpart()
{
	local PROG TEMP dev skipped
	local all='' disks='' list='' initlabel=''

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l 'all,drives:,list:,none,initlabel,disklabel:' -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	DISKLABEL=dos

	while :; do
		case "$1" in
			--all)
				all=1
				disks="$BLOCK_DEVICES"
				;;
			--drives)
				shift
				disks="$(ks_devname_list "$1")"
				;;
			--none)
				disks=''
				;;
			--list)
				shift
				list="$(ks_devname_list "$1")"
				;;
			--disklabel)
				shift
				DISKLABEL="${1##*/}"
				;;
			--initlabel)
				initlabel=1
				;;
			--) shift; break
				;;
		esac
		shift
	done

	case "$DISKLABEL" in
		''|gpt|dos)
			;;
		mbr)
			DISKLABEL=dos
			;;
		*)
			message "unsupported disklabel: $DISKLABEL"
			return 1
	esac

	if [ -n "$initlabel" ] && [ -z "$all" ]; then
		message "unable to initializes the disk label"
		return 1
	fi

	list=",${list//$'\n'/,},"

	for dev in $disks; do
		skipped=0
		for num in $(sfdisk -d "/dev/$dev" | sed -n -e 's,^/dev/[^[:space:]]\+\([0-9]\+\) : .*,\1,p'); do
			if [ "$list" != ',,' ] && [ -n "${list##,$dev$num,}" ]; then
				skipped=$(( $skipped + 1 ))
				continue
			fi

			[ -b "/dev/$dev$num" ] ||
				continue

			findmnt -no TARGET "/dev/$dev$num" |
			while read -r mntpoint; do
				verbose "umounting $mntpoint"
				umount -fR "$mntpoint"
			done

			verbose "dropping /dev/$dev$num"

			wipefs -qfa "/dev/$dev$num" ||:
			sfdisk -q --delete "/dev/$dev" "$num" ||
				return 1
			dropped=1
		done

		[ -n "$initlabel" ] ||
			continue

		if [ "$skipped" -gt 0 ]; then
			message "unable to initializes the disk label because there are partitions left($skipped) on the disk"
			continue
		fi

		verbose "creating new $DISKLABEL partition table on /dev/$dev"

		echo "label: $DISKLABEL" |
			sfdisk -q -w always "/dev/$dev" ||
			return 1
	done
}

ks_get_dev_size()
{
	local name value dev
	dev="$1"; shift

	while read -r name value; do
		case "$name" in
			bytes)   totalsize_bytes=$value ;;
			sectors) totalsize_sectors=$value ;;
		esac
	done <<< "$(
		sfdisk "$@" "/dev/$dev" |
			head -1 |
			sed -n -e 's|^.*, \([0-9]\+\) bytes, \([0-9]\+\) sectors|bytes \1\nsectors \2|p'
		)"
}

ks_get_dev_sector_size()
{
	local value
	value="$(sfdisk "/dev/$1" --list |
		sed -n -e 's|^Sector size.*: \([0-9]\+\) bytes .*|\1|p' |
		head -1)"
	sector_size="${value:-512}"
}

# ----------------+-----------------------------+------------------------------+
# Amount of RAM   | Recommended swap space      | Recommended swap space       |
# in the system   |                             | if allowing for hibernation  |
# ----------------+-----------------------------+------------------------------+
# less than 2 GB  | 2 times the amount of RAM   | 3 times the amount of RAM    |
# 2 GB - 8 GB     | Equal to the amount of RAM  | 2 times the amount of RAM    |
# 8 GB - 64 GB    | 0.5 times the amount of RAM | 1.5 times the amount of RAM  |
# more than 64 GB | workload dependent          | 1.5 times the amount of RAM  |
# ----------------+-----------------------------+------------------------------+
ks_guess_swap_size()
{
	local memtotal

	memtotal="$(sed -ne 's|^MemTotal:[[:space:]]*\([0-9]\+\) kB$|\1|p' /proc/meminfo)"
	memtotal="$(numfmt --from=iec ${memtotal}K)"

	if [ "$memtotal" -lt "$(numfmt --from=iec 2G)" ]; then
		echo $(( $memtotal * 3 ))
		return
	fi
	if [ "$memtotal" -lt "$(numfmt --from=iec 8G)" ]; then
		echo $(( $memtotal * 2 ))
		return
	fi

	echo $(( $memtotal + ( $memtotal / 2 ) ))
}

ks_mountpoint_name()
{
	local fname

	fname="$(echo "$1" | sha256sum)"
	fname="${fname% *}"

	echo "$fname"
}

ks_is_mountpoint_exists()
{
	local fname
	fname="$(ks_mountpoint_name "$1")"
	[ -f "$ks_datadir/fstab.d/$fname" ]
}

ks_fstab()
{
	local fname dev mpoint fstype fsopts

	dev="${1#/dev/}"; shift
	mpoint="$1"; shift
	fsopts="$1"; shift

	fstype="$(blkid -o value -s TYPE "/dev/$dev")"
	fname="$(ks_mountpoint_name "$mpoint")"

	printf '/dev/%s %s %s %s 0 0\n' \
		"$dev" "$ks_rootdir$mpoint" "$fstype" "${fsopts:-defaults}" \
		> "$ks_datadir/fstab.d/$fname"

	find "$ks_datadir/fstab.d" -type f -exec cat '{}' '+' \
		> "$ks_datadir/fstab"
}

getopt_useexisting='useexisting,noformat'
ks_set_useexisting()
{
	case "$1" in
		--useexisting|--noformat)
			useexisting=1
			shift_args=1
			;;
	esac
}

getopt_makefs='fstype:,label:'
ks_set_makefs_args()
{
	case "$1" in
		--label|--fstype)
			makefs_args="${makefs_args-} \"$1=$2\""
			shift_args=2
			;;
	esac
}

makefs()
{
	local PROG TEMP
	local dev=''
	local fstype='ext4' label=''

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "$getopt_makefs" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--fstype) shift; fstype="$1"
				;;
			--label) shift; label="$1"
				;;
			--) shift; break
				;;
		esac
		shift
	done

	if [ "$#" = 0 ]; then
		message "device required"
		return 1
	fi

	if [ -z "$fstype" ]; then
		message "filesystem type required"
		return 1
	fi

	dev="${1#/dev/}"

	verbose "clearing previous filesystem on /dev/$dev"

	wipefs -qfa "/dev/$dev" ||
		return 1

	verbose "creating $fstype filesystem${label:+ (LABEL=$label)} on /dev/$dev"

	case "$fstype" in
		swap) mkswap -f ${label:+-L "$label"} "/dev/$dev" >/dev/null ;;
		fat|vfat) mkfs."$fstype" ${label:+-n "$label"} "/dev/$dev" >/dev/null ;;
		*) "mkfs.$fstype" -q ${label:+-L "$label"} "/dev/$dev" ;;
	esac
}

getopt_crypto='cipher:,passphrase:,passfile:,pbkdf:,pbkdf-memory:,pbkdf-time:,pbkdf-iterations:'
ks_set_crypto_args()
{
	case "$1" in
		--cipher|--passphrase|--passfile|--pbkdf|--pbkdf-memory|--pbkdf-time|--pbkdf-iterations)
			crypto_args="${crypto_args-} \"$1=$2\""
			shift_args=2
			;;
	esac
}

crypto()
{
	local PROG TEMP dev
	local luks_passphrase='' luks_cipher='' luks_pbkdf='' luks_pbkdf_memory=''
	local luks_pbkdf_time='' luks_pbkdf_iterations='' luks_passfile=''
	local name=''

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "name:,$getopt_crypto" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--name) shift; name="$1"
				;;
			--cipher) shift; luks_cipher="$1"
				;;
			--passphrase) shift; luks_passphrase="$1"
				;;
			--passfile) shift; luks_passfile="$1"
				;;
			--pbkdf) shift; luks_pbkdf="$1"
				;;
			--pbkdf-memory) shift; luks_pbkdf_memory="$1"
				;;
			--pbkdf-time) shift; luks_pbkdf_time="$1"
				;;
			--pbkdf-iterations) shift; luks_pbkdf_iterations="$1"
				;;
			--) shift; break
				;;
		esac
		shift
	done

	if [ -z "$luks_passphrase" ] && [ -z "$luks_passfile" ]; then
		message "specify the passphrase to use."
		return 1
	fi

	if [ -n "$luks_pbkdf_time" ] && [ -n "$luks_pbkdf_iterations" ]; then
		message "only one of --pbkdf-time and --pbkdf-iterations can be specified."
		return 1
	fi

	[ -z "${ONLYARGS-}" ] ||
		return 0

	if [ "$#" = 0 ] || [ -z "${1-}" ]; then
		message "device not specified"
		return 1
	fi

	[ -d /run/cryptsetup ] ||
		mkdir -p /run/cryptsetup

	dev="${1#/dev/}"

	verbose "creating LUKS encrypted volume on /dev/$dev"

	if [ -n "$luks_passfile" ] && [ ! -f "$luks_passfile" ]; then
		message "passfile does not exist: $luks_passfile"
		return 1
	fi

	local crypt_name="${name:-${dev}_crypt}"
	local passfile="$ks_datadir/crypto/$crypt_name/pass"

	mkdir -p -- "${passfile%/*}"

	if [ -n "$luks_passphrase" ]; then
		printf '%s' "$luks_passphrase" > "$passfile"
	elif [ -n "$luks_passfile" ]; then
		cp -f -- $luks_passfile "$passfile"
	else
		message "unable to create luks without password or passfile"
		return 1
	fi

	cryptsetup \
		--batch-mode \
		--type luks \
		--key-file "$passfile" \
		${luks_cipher:+--cipher="$luks_cipher"} \
		${luks_passphrase:+ --force-password} \
		${luks_pbkdf:+--pbkdf="$luks_pbkdf"} \
		${luks_pbkdf_memory:+--pbkdf-memory="$luks_pbkdf_memory"} \
		${luks_pbkdf_time:+--iter-time="$luks_pbkdf_time"} \
		${luks_pbkdf_iterations:+--pbkdf-force-iterations="$luks_pbkdf_iterations"} \
		luksFormat "/dev/$dev" || {
			message "unable to format luks device: /dev/$dev"
			return 1
		}

	cryptsetup \
		--key-file "$passfile" \
		open --type luks "/dev/$dev" "$crypt_name" || {
			message "unable to open luks device: /dev/$dev"
			return 1
		}

	message "luks device ready: /dev/$dev"
	return 0
}

part()
{
	local PROG TEMP shift_args=0 sfdisk_args exe
	local mntpoint='' dev='' partdev='' parttype='' fstype='' fsoptions='' size='' grow='' useexisting='' resize=''
	local asprimary='' hibernation=''
	local encrypted='' makefs_args='' crypto_args=''
	local disks="$BLOCK_DEVICES"

	local ret=0

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "asprimary,ondisk:,ondrive:,onpart:,usepart:,fsoptions:,size:,grow,resize,encrypted,hibernation,$getopt_makefs,$getopt_crypto,$getopt_useexisting" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--) shift; break
				;;
			--asprimary) asprimary=1
				;;
			--ondisk|--ondrive)
				shift
				disks="$(ks_devname "$1")"
				;;
			--onpart|--usepart)
				shift
				partdev="$(ks_devname "$1")"
				if [ ! -f "/sys/class/block/$partdev/partition" ]; then
					message "argument is not like a disk partition: $1"
					return 1
				fi
				disks=''
				;;
			--fstype) shift; fstype="$1"
				;;
			--fsoptions) shift; fsoptions="$1"
				;;
			--size) shift;
				# B   -> B
				# MiB -> Mi
				# KiB -> Ki
				size="${1/i[Bb]/i}"
				[ -z "${size##*[!0-9]*}" ] || size="${size}Mi"
				;;
			--resize) resize=1
				;;
			--grow) grow=1; size=''
				;;
			--encrypted)
				encrypted=1
				;;
			--hibernation)
				hibernation=1
				;;
			*)
				shift_args=0
				ks_set_useexisting "$@"
				ks_set_makefs_args "$@"
				ks_set_crypto_args "$@"
				if [ "$shift_args" != 0 ]; then
					shift "$shift_args"
					continue
				fi
				;;
		esac
		shift
	done

	if [ -n "$encrypted" ]; then
		quote_shell_args crypto_args "$crypto_args"
		eval "ONLYARGS=1 crypto $crypto_args || return"
	fi

	mntpoint="${1:-none}"
	set --

	case "$mntpoint" in
		raid.*)    parttype="raid"  ;;
		pv.*)      parttype="lvm"   ;;
		swap)
			fstype="swap"
			;;
		biosboot)
			fstype="biosboot"
			;;
		/boot/efi)
			fstype="efi"
			;;
		/home)
			[ "$DISKLABEL" = 'gpt' ] &&
				parttype="home" ||
				parttype="linux"
			;;
		*)
			parttype="linux"
			;;
	esac

	if [ -z "${mntpoint##/*}" ] && ks_is_mountpoint_exists "$mntpoint"; then
		message "partition for \`$mntpoint' mountpoint already exists. Ignore."
		return
	fi

	case "$fstype" in
		'')
			;;
		efi)
			parttype="uefi"
			size="${size:-200Mi}"
			ks_set_makefs_args --fstype "vfat"
			;;
		biosboot)
			parttype="ef02"
			size="${size:-1Mi}"
			;;
		swap)
			parttype="swap"
			if [ -z "$size" ] && [ -n "$hibernation" ]; then
				size="$(ks_guess_swap_size)"

				if [ "$size" -eq 0 ]; then
					message "no need to create a \`$fstype' partition. Ignore."
					return
				fi

				size="${size}B"
			fi
			ks_set_makefs_args --fstype "swap"
			;;
		*)
			ks_set_makefs_args --fstype "$fstype"
			;;
	esac

	for dev in $disks; do
		if [ -n "$size" ] && [ -n "${size%\%}" ]; then
			local totalsize_bytes=0 totalsize_sectors=0 needbytes=0 sectors='' sector_size=0

			ks_get_dev_sector_size "$dev"

			if [ -z "${size##*%}" ]; then
				local totalfree_percent freesize_sectors

				ks_get_dev_size "$dev" --list-free
				freesize_sectors="$totalsize_sectors"

				ks_get_dev_size "$dev" --list
				totalfree_percent=$(( $freesize_sectors * 1000 / $totalsize_sectors ))

				[ ${totalfree_percent:$(( ${#totalfree_percent} - 1 ))} -ge 5 ] &&
					totalfree_percent=$(( ${totalfree_percent:0:$(( ${#totalfree_percent} - 1 ))} + 1 )) ||
					totalfree_percent=$(( ${totalfree_percent:0:$(( ${#totalfree_percent} - 1 ))} ))

				if [ "${size%\%}" -eq "$totalfree_percent" ]; then
					sectors="$freesize_sectors"
				else
					sectors="$(( $totalsize_sectors * ${size%\%} / 100 ))"
				fi
				needbytes=$(( $sectors * $sector_size ))
			elif [ -z "${size##*[!0-9]*}" ]; then
				case "$size" in
					*[Bb]) size="${size%[Bb]}" ;;
					*i)    size=$(LANG=C numfmt --from=iec-i "$size") ;;
					*)     size=$(LANG=C numfmt --from=iec   "$size") ;;
				esac

				sectors="$(( $size / $sector_size ))"
				needbytes="$size"
			else
				# sectors
				sectors="$size"
				needbytes=$(( $sectors * $sector_size ))
			fi

			ks_get_dev_size "$dev" --list-free

			if [ "$needbytes" -gt "$totalsize_bytes" ]; then
				verbose "/dev/$dev has too little free space, ignoring."
				continue
			fi

			[ -z "$sectors" ] ||
				set -- "size=$sectors"
		fi

		[ ! -d "$ks_datadir/part/$dev/stop" ] ||
			find "$ks_datadir/part/$dev/stop" -type f -executable -exec '{}' ';'

		set -- ${1:+"$@" ,} "type=$parttype"

		local prev_parttable curr_parttable sz i
		prev_parttable=()
		curr_parttable=()

		readarray -t prev_parttable <<< "$(sfdisk -d "/dev/$dev" | grep ^/dev/)"

		if [ -z "$asprimary" ] && [ "$DISKLABEL" = dos ] && [ "${#prev_parttable[@]}" = 3 ]; then
			verbose "creating new extended partition on /dev/$dev"

			printf 'type=extended\n' |
				sfdisk -q -W always --append "/dev/$dev" ||
				break

			readarray -t prev_parttable <<< "$(sfdisk -d "/dev/$dev" | grep ^/dev/)"
		fi

		verbose "creating new partition on /dev/$dev"

		ret=0
		printf '%s\n' "$*" |
			sfdisk -q -W always --append "/dev/$dev" ||
			ret=1

		if [ "$ret" = 1 ]; then
			message "unable to create partition"
			return 1
		fi

		readarray -t curr_parttable <<< "$(sfdisk -d "/dev/$dev" | grep ^/dev/)"

		sz=${#curr_parttable[@]}

		for (( i=0; $i < $sz; i++ )); do
			local prev="${prev_parttable[$i]-}"
			local curr="${curr_parttable[$i]}"

			if [ "${prev#* : }" != "${curr#* : }" ]; then
				partdev="${curr%% : *}"
				partdev="${partdev#/dev/}"
				break
			fi
		done
		break
	done

	if [ -z "$partdev" ]; then
		if [ -n "$size" ]; then
			message "unable to create partition with $size size"
			return 1
		fi
		return 0
	fi

	if [ -n "$resize" ]; then
		[ ! -d "$ks_datadir/part/$dev/stop" ] ||
			find "$ks_datadir/part/$dev/stop" -type f -executable -exec '{}' ';'

		# https://karelzak.blogspot.com/2015/05/resize-by-sfdisk.html

		if [ -n "$grow" ]; then
			size='+'
		elif [ -z "$size" ]; then
			message "you need to specify the size by which you want to increase the partition."
			return 1
		else
			size="+$size"
		fi

		local partnum
		read -r partnum < "/sys/class/block/$partdev/partition"

		dev="$(readlink -ev "/sys/class/block/$partdev")"
		dev="${dev%/*}"
		dev="${dev##*/}"

		verbose "increasing partition /dev/$dev$partnum"

		printf ', %s\n' "$size" |
			sfdisk -q -N "$partnum" "/dev/$dev"
	fi

	if [ -n "$encrypted" ]; then
		eval "set -- $crypto_args"

		local crypt_name="${partdev}_crypt"

		crypto --name="$crypt_name" "$@" "/dev/$partdev" ||
			return

		mkdir -p -- \
			"$ks_datadir/part/$dev/start" \
			"$ks_datadir/part/$dev/stop"

		ks_cat_exec "$ks_datadir/part/$dev/start/$partdev" <<-EOF
		#!/bin/sh -efu
		[ ! -b "/dev/mapper/$crypt_name" ] ||
			exit 0
		cryptsetup --key-file "$ks_datadir/crypto/$crypt_name/pass" open --type luks "/dev/$partdev" "$crypt_name"
		EOF

		ks_cat_exec "$ks_datadir/part/$dev/stop/$partdev" <<-EOF
		#!/bin/sh -efu
		[ -b "/dev/mapper/$crypt_name" ] ||
			exit 0
		cryptsetup close "$crypt_name"
		EOF

		partdev="mapper/$crypt_name"
	fi

	[ ! -d "$ks_datadir/part/$dev/start" ] ||
		find "$ks_datadir/part/$dev/start" -type f -executable -exec '{}' ';'

	quote_shell_args makefs_args "$makefs_args"
	eval "set -- $makefs_args"

	case "$mntpoint" in
		/|/*)
			[ -n "$useexisting" ] ||
				makefs "$partdev" "$@"
			ks_fstab "$partdev" "$mntpoint" "$fsoptions"
			;;
		swap)
			makefs "$partdev" "$@" --fstype=swap
			ks_fstab "$partdev" "swap" "$fsoptions"
			;;
		none)
			[ -n "$useexisting" ] ||
				makefs "$partdev" "$@"
			;;
		btrfs.*|pv.*|raid.*)
			[ -d "$ks_datadir/partid" ] ||
				mkdir -- "$ks_datadir/partid"
			printf '%s\n' "$partdev" >> "$ks_datadir/partid/$mntpoint"
			;;
	esac
}

partition() { part "$@"; }

volgroup()
{
	local PROG TEMP shift_args
	local useexisting='' pesize=''

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "pesize:,$getopt_useexisting" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--) shift; break
				;;
			--pesize) shift; pesize="$1"
				;;
			*)
				shift_args=0
				ks_set_useexisting "$@"
				if [ "$shift_args" != 0 ]; then
					shift "$shift_args"
					continue
				fi
				;;
		esac
		shift
	done

	local name="$1"; shift
	local i arg num="$#"

	if [ -n "$useexisting" ] && [ "$num" != 0 ]; then
		message "do not specify partitions when using --useexisting option."
		return 1
	fi

	for (( i=0; $i < $num; i++ )); do
		arg=
		case "$1" in
			'')
				;;
			pv.*)
				for arg in $(set +f; printf '%s\n' "$ks_datadir/partid/"$1); do
					read -r arg < "$arg"
					set -- "$@" "/dev/$arg"
				done
				;;
			*)
				for arg in $(set +f; printf '%s\n' $1); do
					arg="$(ks_devname "$arg")"
					set -- "$@" "/dev/$arg"
				done
				;;
		esac
		shift
	done

	if [ -z "$useexisting" ]; then
		verbose "creating lvm volume group '$name': $*"
		lvm pvcreate -q "$@" ||
			return 1
		lvm vgcreate -q ${pesize:+-s "$pesize"} "$name" "$@" ||
			return 1
		return 0
	fi

	verbose "changing lvm volume group '$name'"
	vgchange -q ${pesize:+-s "$pesize"} "$name" ||
		return 1
}

logvol()
{
	local PROG TEMP shift_args
	local vgname='' name='' hibernation=''
	local useexisting='' fstype='' fsoptions='' size='' resize='' grow='' chunksize=''
	local encrypted='' makefs_args='' crypto_args=''
	local ret=0

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "vgname:,name:,fsoptions:,chunksize:,percent:,size:,resize,grow,encrypted,hibernation,$getopt_crypto,$getopt_makefs,$getopt_useexisting" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--) shift; break
				;;
			--vgname) shift; vgname="$1"
				;;
			--name) shift; name="$1"
				;;
			--fstype) shift; fstype="$1"
				;;
			--fsoptions) shift; fsoptions="$1"
				;;
			--chunksize) shift; chunksize="$1"
				;;
			--percent) shift;
				size="${1%\%}%"
				;;
			--size) shift;
				# B   -> B
				# MiB -> Mi
				# KiB -> Ki
				size="${1/i[Bb]/i}"
				[ -z "${size##*[!0-9]*}" ] || size="${size}Mi"
				;;
			--resize) resize=1
				;;
			--grow) grow=1; size=''
				;;
			--encrypted)
				encrypted=1
				;;
			--hibernation)
				hibernation=1
				;;
			*)
				shift_args=0
				ks_set_useexisting "$@"
				ks_set_makefs_args "$@"
				ks_set_crypto_args "$@"
				if [ "$shift_args" != 0 ]; then
					shift "$shift_args"
					continue
				fi
				;;
		esac
		shift
	done

	if [ -n "$encrypted" ]; then
		quote_shell_args crypto_args "$crypto_args"
		eval "ONLYARGS=1 crypto $crypto_args || return"
	fi

	mntpoint="${1:-none}"

	if [ -z "${mntpoint##/*}" ] && ks_is_mountpoint_exists "$mntpoint"; then
		message "partition for \`$mntpoint' mountpoint already exists. Ignore."
		return
	fi

	case "$fstype" in
		swap)
			if [ -z "$size" ] && [ -n "$hibernation" ]; then
				size="$(ks_guess_swap_size)"

				if [ "$size" -eq 0 ]; then
					message "no need to create a \`$fstype' volume. Ignore."
					return
				fi

				size="${size}B"
			fi
			ks_set_makefs_args --fstype "swap"
			;;
	esac

	set --

	if [ -n "$grow" ]; then
		set -- "$@" --extents '100%FREE'
	elif [ -n "$size" ]; then
		if [ -z "${size##*%}" ]; then
			set -- "$@" --extents "${size%\%}%FREE"
		else
			set -- "$@" --size "$size"
		fi
	fi

	[ -z "$chunksize" ] ||
		set -- "$@" --chunksize "$chunksize"

	[ -n "$name" ] ||
		name="lvol$(lvs --noheadings -o name |wc -l)"

	partdev="$vgname/$name"
	ret=0

	if [ -z "$useexisting" ]; then
		verbose "creating logical volume '$name' in '$vgname' volume group"
		lvm lvcreate "$@" "$vgname" --activate n --name "$name" &&
			lvm vgscan &&
			lvm vgchange -ay --noudevsync &&
			lvm vgmknodes ||
			ret=1
	elif [ -n "$resize" ]; then
		verbose "increasing logical volume /dev/$partdev"
		lvm lvextend "$@" "/dev/$partdev" ||
			ret=1
	fi

	if [ "$ret" = 1 ]; then
		message "unable to handle logical volume: /dev/$partdev"
		return 1
	fi

	if [ -n "$encrypted" ]; then
		eval "set -- $crypto_args"
		crypto --name="${name}_crypt" "$@" "/dev/$partdev" ||
			return
		partdev="mapper/${name}_crypt"
	fi

	if [ -z "$useexisting" ]; then
		quote_shell_args makefs_args "$makefs_args"
		eval "set -- $makefs_args"
		makefs "$partdev" "$@"
	fi

	case "$mntpoint" in
		/|/*)
			ks_fstab "$partdev" "$mntpoint" "$fsoptions"
			;;
		none)
			;;
	esac
}

raid()
{
	local PROG TEMP shift_args dev=''
	local device='' level='' chunksize='' useexisting='' fstype='' fsoptions=''
	local encrypted='' hibernation='' makefs_args='' crypto_args=''

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "device:,level:,chunksize:,encrypted,hibernation,$getopt_crypto,$getopt_makefs,$getopt_useexisting" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--) shift; break
				;;
			--device) shift; device="$1"
				;;
			--level) shift
				level="${1,,}"
				level="${level#raid}"
				;;
			--fstype) shift; fstype="$1"
				;;
			--fsoptions) shift; fsoptions="$1"
				;;
			--chunksize) shift; chunksize="$1"
				;;
			--encrypted)
				encrypted=1
				;;
			--hibernation)
				hibernation=1
				;;
			*)
				shift_args=0
				ks_set_useexisting "$@"
				ks_set_makefs_args "$@"
				ks_set_crypto_args "$@"
				if [ "$shift_args" != 0 ]; then
					shift "$shift_args"
					continue
				fi
				;;
		esac
		shift
	done

	if [ -n "$encrypted" ]; then
		quote_shell_args crypto_args "$crypto_args"
		eval "ONLYARGS=1 crypto $crypto_args || return"
	fi

	local mntpoint i num

	mntpoint="$1"; shift

	if [ -n "$mntpoint" ] && [ -z "${mntpoint##/*}" ] && ks_is_mountpoint_exists "$mntpoint"; then
		message "partition for \`$mntpoint' mountpoint already exists. Ignore."
		return
	fi

	case "$fstype" in
		'')
			;;
		efi)
			size="${size:-200Mi}"
			ks_set_makefs_args --fstype "vfat"
			;;
		biosboot)
			size="${size:-1Mi}"
			;;
		swap)
			if [ -z "$size" ] && [ -n "$hibernation" ]; then
				size="$(ks_guess_swap_size)"

				if [ "$size" -eq 0 ]; then
					message "no need to create a \`$fstype' partition. Ignore."
					return
				fi

				size="${size}B"
			fi
			ks_set_makefs_args --fstype "swap"
			;;
		*)
			ks_set_makefs_args --fstype "$fstype"
			;;
	esac

	num="$#"

	for (( i=0; $i < $num; i++ )); do
		arg=
		case "$1" in
			'')
				;;
			raid.*)
				read -r arg < "$ks_datadir/partid/$1"
				set -- "$@" "/dev/$arg"
				;;
			*)
				for arg in $(set +f; printf '%s\n' $1); do
					arg="$(ks_devname "$arg")"
					set -- "$@" "/dev/$arg"
				done
				;;
		esac
		shift
	done

	if [ -z "$useexisting" ]; then
		case "$device" in
			UUID=*|LABEL=*)
				message "it is not possible to use such a device name to create"
				return 1
				;;
		esac

		for arg; do
			wipefs -qfa "$arg" ||:
		done

		dev="/dev/md/$device"

		if mdadm --query --brief "$dev" 2>/dev/null |grep -qs ' [1-9][0-9]* devices'; then
			mdadm --quiet --stop "$dev"
		fi

		verbose "creating raid$level array '$device': $*"

		mdadm --create --quiet \
			--metadata=default \
			--level="$level" \
			--raid-devices="$#" \
			${chunksize:+--chunk="$chunksize"} \
			"$device" "$@" ||
			return 1

		#mdadm --wait "$device"
	fi

	dev="$(ks_devname "$device")"

	if [ -n "$encrypted" ]; then
		eval "set -- $crypto_args"
		crypto --name="${device}_crypt" "$@" "$dev" ||
			return
		dev="mapper/${device}_crypt"
	fi

	if [ -z "$useexisting" ]; then
		quote_shell_args makefs_args "$makefs_args"
		eval "set -- $makefs_args"
		makefs "$dev" "$@"
	fi

	case "$mntpoint" in
		/|/*)
			ks_fstab "$dev" "$mntpoint" "$fsoptions"
			;;
		none)
			;;
	esac
}

btrfs()
{
	local PROG TEMP shift_args dev
	local subvol='' data='' metadata='' label='' name='' fsoptions='' useexisting=''
	local mntpoint i num

	PROG="kickstart"
	message "command: ${FUNCNAME[0]} $*"

	PROG="${FUNCNAME[0]}"
	TEMP=`getopt -n "$PROG" -l "subvol,name:,label:,data:,metadata:,fsoptions:,$getopt_useexisting" -- "$PROG" "$@"` ||
		return 1
	eval set -- "$TEMP"

	while :; do
		case "$1" in
			--) shift; break
				;;
			--subvol) subvol=1
				;;
			--name) shift; name="$1"
				;;
			--label) shift; label="$1"
				;;
			--fsoptions) shift; fsoptions="$1"
				;;
			--data) shift
				data="${1,,}"
				data="${data#raid}"
				;;
			--metadata) shift
				metadata="${1,,}"
				metadata="${metadata#raid}"
				;;
			*)
				shift_args=0
				ks_set_useexisting "$@"
				if [ "$shift_args" != 0 ]; then
					shift "$shift_args"
					continue
				fi
				;;
		esac
		shift
	done

	mntpoint="$1"; shift

	if [ -n "$mntpoint" ] && [ -z "${mntpoint##/*}" ] && ks_is_mountpoint_exists "$mntpoint"; then
		message "partition for \`$mntpoint' mountpoint already exists. Ignore."
		return
	fi

	num="$#"

	if [ "$num" = 0 ]; then
		message "partition required"
		return 1
	fi

	[ -e /dev/btrfs-control ] ||
		modprobe btrfs

	for (( i=0; $i < $num; i++ )); do
		arg=
		case "$1" in
			'')
				;;
			btrfs.*)
				for arg in $(set +f; printf '%s\n' "$ks_datadir"/partid/$1); do
					read -r arg < "$arg"
					set -- "$@" "/dev/$arg"
				done
				;;
			*)
				for arg in $(set +f; printf '%s\n' $1); do
					arg="$(ks_devname "$arg")"
					set -- "$@" "/dev/$arg"
				done
				;;
		esac
		shift
	done

	dev="$(ks_devname "$1")"

	if [ -z "$useexisting" ]; then
		local ret=0

		if [ -z "$subvol" ]; then
			wipefs -qfa "$@" ||:
			mkfs.btrfs -q -f \
				${data:+--data "raid$data"} \
				${metadata:+--metadata "raid$metadata"} \
				${label:+--label "$label"} \
				"$@" ||
				ret=1
		else
			if [ -z "$name" ]; then
				message "name of subvolume required."
				return 1
			fi

			if [ "$#" != 1 ]; then
				message "too many arguments. Must be the identifier of the subvolume's parent volume."
				return 1
			fi

			mount -n -o rw,X-mount.mkdir \
				"/dev/$dev" \
				"$ks_datadir/btrfs.subvol"
			command btrfs subvolume create \
				"$ks_datadir/btrfs.subvol/$name" ||
				ret=1
			umount -f \
				"$ks_datadir/btrfs.subvol"
		fi

		if [ "$ret" != 0 ]; then
			message "unable to process btrfs volume or subvolume."
			return 1
		fi
	fi

	[ -z "$name" ] ||
		fsoptions="${fsoptions:+$fsoptions,}subvol=$name"

	case "$mntpoint" in
		/|/*)
			ks_fstab "$dev" "$mntpoint" "$fsoptions"
			;;
		none)
			;;
	esac
}
