. /lib/partman/lib/base.sh

# Avoid warnings from lvm2 tools about open file descriptors
# Setting this here should avoid them for partman as a whole
export LVM_SUPPRESS_FD_WARNINGS=1

###############################################################################
#
# Miscellaneous utility functions
#
###############################################################################

# Convert common terms for disk sizes into something LVM understands.
#  e.g. "200 gb" -> "47683"
lvm_extents_from_human() {
	local vg size extent_size
	vg="$1"
	size="$2"

	extent_size=$(vgs --noheadings --units K -o vg_extent_size "$vg")
	echo $(($(human2longint "$size") / $(human2longint "$extent_size")))
}

# Convert LVM disk sizes into something human readable.
#  e.g. "812.15M" -> "812MB"
lvm_size_to_human() {
	echo "${1}B" | sed -e 's/\...//'
}

# Convenience wrapper for lvs/pvs/vgs
lvm_get_info() {
	local type info device output
	type=$1
	info=$2
	device=$3

	output=$($type --noheadings --nosuffix --separator ":" --units M \
		-o "$info" $device 2> /dev/null)
	if [ $? -ne 0 ]; then
		return 1
	fi
	# NOTE: The last sed, s/:$// is necessary due to a bug in lvs which adds a
	#       trailing separator even if there is only one field
	output=$(echo "$output" | sed -e 's/^[:[:space:]]\+//g;s/[:[:space:]]\+$//g')
	# Be careful here, we don't want to output only a newline
	if [ -n "$output" ]; then
		echo "$output"
	fi
	return 0
}

# Converts a list of space (or newline) separated values to comma separated values
ssv_to_csv() {
	local csv value

	csv=""
	for value in $1; do
		if [ -z "$csv" ]; then
			csv="$value"
		else
			csv="$csv, $value"
		fi
	done
	echo "$csv"
}

# Converts a list of comma separated values to space separated values
csv_to_ssv() {
	echo "$1" | sed -e 's/ *, */ /g'
}

# Produces a human readable description of the current LVM config
lvm_get_config() {
	local output pv pvs vg vgs lv lvs line

	# Unallocated PVs
	db_metaget partman-lvm/text/configuration_freepvs description
	output="$RET
"
	pvs=$(pv_list_free)
	if [ -z "$pvs" ]; then
		db_metaget partman-lvm/text/configuration_none_pvs description
		output="$output  * $RET
"
	else
		for pv in $(pv_list_free); do
			pv_get_info "$pv"
			line=$(printf "%-56s (%sMB)" "  * $pv" "$SIZE")
			output="${output}${line}
"
		done
	fi

	# Volume groups
	db_metaget partman-lvm/text/configuration_vgs description
	output="$output
$RET
"
	vgs=$(vg_list)
	if [ -z "$vgs" ]; then
		db_metaget partman-lvm/text/configuration_none_vgs description
		RET="$output  * $RET
"
		return 0
	fi

	for vg in $vgs; do
		# VG name
		vg_get_info "$vg"
		line=$(printf "%-56s (%sMB)" "  * $vg" "$SIZE")
		output="${output}${line}
"

		# PVs used by VG
		# This will return > 0 results, otherwise we'd have no VG
		pvs=$(vg_list_pvs "$vg")
		db_metaget partman-lvm/text/configuration_pv description
		for pv in $pvs; do
			pv_get_info "$pv"
			line=$(printf "%-35s %-20s (%sMB)" "    - $RET" "$pv" "$SIZE")
			output="${output}${line}
"
		done

		# LVs provided by VG
		lvs=$(vg_list_lvs "$vg")
		if [ -z "$lvs" ]; then
			continue
		fi
		db_metaget partman-lvm/text/configuration_lv description
		for lv in $lvs; do
			lv_get_info "$vg" "$lv"
			line=$(printf "%-35s %-20s (%sMB)" "    - $RET" "$lv" "$SIZE")
			output="${output}${line}
"
		done
	done
	RET="$output"
	return 0
}

# Common checks for VG and LV names
# Rules:
# 1) At least one character
# 2) Only alphanumeric characters (isalnum()) and "._-+"
# 3) May not be "." or ".."
# 4) must not start with a hyphen
# 5) maximum name length 128 characters
# See lvm2 source and bug #254630 for details
lvm_name_ok() {
	local name
	name="$1"

	# Rule 1
	if [ -z "$name" ]; then
		return 1
	fi

	# Rule 2
	if [ "$(echo -n "$name" | sed 's/[^-+_\.[:alnum:]]//g')" != "$name" ]; then
		return 1
	fi

	# Rule 3
	if [ "$name" = "." -o "$name" = ".." ]; then
		return 1
	fi

	# Rule 4
	if [ "$(echo -n "$name" | sed 's/^-//')" != "$name" ]; then
		return 1
	fi

	# Rule 5
	if [ $(echo -n "$name" | wc -c) -gt 128 ]; then
		return 1
	fi

	return 0
}

# Would a PV be allowed on this partition?
pv_allowed () {
	local dev=$1
	local id=$2

	cd $dev

	local lvm=no
	if grep -q "/dev/md" $dev/device; then
		# LVM on software RAID
		lvm=yes
	elif grep -q "/dev/mapper/" $dev/device; then
		# LVM on device-mapper crypto
		if type dmsetup >/dev/null 2>&1; then
			device=$(cat $dev/device)
			if [ "$(dmsetup status $device | cut -d' ' -f3)" = crypt ]; then
				lvm=yes
			fi
		fi
	fi

	# sparc can not have LVM starting at 0 or it will destroy the partition table
	if [ "$(udpkg --print-architecture)" = sparc ] && \
	   [ "${id%%-*}" = 0 ] && [ $lvm = no ]; then
		return 1
	fi

	if [ $lvm = no ]; then
		local fs
		open_dialog PARTITION_INFO $id
		read_line x1 x2 size x4 fs x6 x7
		close_dialog
		if [ "$fs" = free ]; then
			# parted can't deal with VALID_FLAGS on free space
			# as yet, so unfortunately we have to special-case
			# label types.
			local label
			open_dialog GET_LABEL_TYPE
			read_line label
			close_dialog
			case $label in
			    amiga|bsd|dasd|gpt|mac|msdos|sun)
				# ... by creating a partition
				lvm=yes
				;;
			esac
		else
			local flag
			open_dialog VALID_FLAGS $id
			while { read_line flag; [ "$flag" ]; }; do
				if [ "$flag" = lvm ]; then
					lvm=yes
				fi
			done
			close_dialog
		fi
	fi

	# Don't list devices that are too small (less than 4MB),
	# they're most likely alignment artifacts
	if [ $size -lt 4194304 ]; then
		lvm=no
	fi

	[ $lvm = yes ]
}

pv_list_allowed () {
	partman_list_allowed pv_allowed
}

pv_list_allowed_free () {
	local line

	IFS="$NL"
	for line in $(pv_list_allowed); do
		restore_ifs
		local dev="${line%%$TAB*}"
		local rest="${line#*$TAB}"
		local id="${rest%%$TAB*}"
		if [ -e "$dev/locked" ] || [ -e "$dev/$id/locked" ]; then
			continue
		fi
		local pv="${line##*$TAB}"
		if [ ! -e "$pv" ]; then
			echo "$line"
		else
			local vg=$(lvm_get_info pvs vg_name "$pv" || true)
			if [ -z "$vg" ]; then
				echo "$line"
			fi
		fi
		IFS="$NL"
	done
	restore_ifs
}

###############################################################################
#
# Physical Volume utility functions
#
###############################################################################

# Check if a device contains PVs
# If called for a disk, this will also check all partitions;
# if called for anything other, it can return false positives!
pv_on_device() {
	local device
	device="$1"

	if $(pvs --noheadings --nosuffix -o pv_name | grep -q "$device"); then
		return 0
	fi
	return 1
}

# Get info on a PV
pv_get_info() {
	local info

	info=$(lvm_get_info pvs pv_size,pv_pe_count,pv_free,pv_pe_alloc_count,vg_name "$1")
	if [ $? -ne 0 ]; then
		return 1
	fi

	SIZE=$(echo "$info"   | cut -d':' -f1 | cut -d'.' -f1)
	SIZEPE=$(echo "$info" | cut -d':' -f2)
	FREE=$(echo "$info"   | cut -d':' -f3 | cut -d'.' -f1)
	FREEPE=$(echo "$info" | cut -d':' -f4) # Used, not free, PEs
	FREEPE=$(( $SIZEPE - $FREEPE ))        # Calculate free PEs
	VG=$(echo "$info"     | cut -d':' -f5)
	return 0
}

# Get VG for a PV
pv_get_vg() {
	lvm_get_info pvs vg_name "$1"
}

# Get all PVs
pv_list() {
	# Scan the partman devices and find partitions that have lvm as method.
	# Do not rely on partition flags since it doesn't work for some partitions
	# (e.g. dm-crypt, RAID)
	local dev method

	for dev in $DEVICES/*; do
		[ -d "$dev" ] || continue
		cd $dev
		open_dialog PARTITIONS
		while { read_line num id size type fs path name; [ "$id" ]; }; do
			[ -f $id/method ] || continue
			method=$(cat $id/method)
			if [ "$method" = lvm ]; then
				echo $(mapdevfs $path)
			fi
		done
		close_dialog
	done
}

# Get all unused PVs
pv_list_free() {
	local pv vg

	for pv in $(pv_list); do
		vg=$(lvm_get_info pvs vg_name "$pv")
		if [ -z "$vg" ]; then
			echo "$pv"
		fi
	done
}

# Prepare a partition for use as a PV. If this returns true, then it did
# some work and a commit is necessary. Prints the new path.
pv_prepare() {
	local dev="$1"
	local id="$2"
	local size parttype fs path

	cd "$dev"
	open_dialog PARTITION_INFO "$id"
	read_line x1 id size freetype fs path x7
	close_dialog

	if [ "$fs" = free ]; then
		local newtype

		case $freetype in
		    primary)
			newtype=primary
			;;
		    logical)
			newtype=logical
			;;
		    pri/log)
			local parttype
			open_dialog PARTITIONS
			while { read_line x1 x2 x3 parttype x5 x6 x7; [ "$parttype" ]; }; do
				if [ "$parttype" = primary ]; then
					has_primary=yes
				fi
			done
			close_dialog
			if [ "$has_primary" = yes ]; then
				newtype=logical
			else
				newtype=primary
			fi
			;;
		esac

		open_dialog NEW_PARTITION $newtype ext2 $id full $size
		read_line x1 id x3 x4 x5 path x7
		close_dialog
	fi

	mkdir -p "$id"
	local method="$(cat "$id/method" 2>/dev/null || true)"
	if [ "$method" = swap ]; then
		disable_swap "$dev" "$id"
	fi
	if [ "$method" != lvm ]; then
		echo lvm >"$id/method"
		rm -f "$id/use_filesystem"
		rm -f "$id/format"
		update_partition "$dev" "$id"
		echo "$path"
		return 0
	fi

	echo "$path"
	return 1
}

# Initialize a PV
pv_create() {
	local pv
	pv="$1"

	if pvs "$pv" > /dev/null 2>&1; then
		return 0
	fi

	log-output -t partman-lvm pvcreate -ff -y "$pv"
	return $?
}

# Remove the LVM signatures from a PV
pv_delete() {
	local pv
	pv="$1"

	if ! pvs "$pv" > /dev/null 2>&1; then
		return 0
	fi

	log-output -t partman-lvm pvremove -ff -y "$pv"
	return $?
}

###############################################################################
#
# Logical Volume utility functions
#
###############################################################################

# Get LV info
lv_get_info() {
	local info vg lv line tmplv
	vg=$1
	lv=$2
	info=$(lvm_get_info lvs lv_name,lv_size "$vg")

	SIZE=""
	FS="unknown"
	MOUNT="unknown"
	for line in $(lvm_get_info lvs lv_name,lv_size "$vg"); do
		tmplv=$(echo "$line" | cut -d':' -f1)
		if [ $tmplv != $lv ]; then
			continue
		fi

		SIZE=$(echo "$line" | cut -d':' -f2 | cut -d'.' -f1)
		MOUNT=$(grep "^/dev/mapper/$vg-$lv" /proc/mounts | cut -d' ' -f2 | sed -e 's/\/target//')
		# FIXME: Get FS - but we should not use parted for that!
		#FS=$(parted "$tmplv" print | grep '^1' | \
		#	sed -e 's/ \+/ /g' | cut -d " " -f 4)
		break
	done
}

# List all LVs and their VGs
lv_list() {
	lvm_get_info lvs lv_name,vg_name ""
}

# Create a LV
lv_create() {
	local vg lv extents
	vg="$1"
	lv="$2"
	extents="$3"

	# Do not ask if signatures should be wiped, to avoid hanging the installer (BTS #757818).
	log-output -t partman-lvm lvcreate --wipesignatures n -l "$extents" -n "$lv" $vg
	return $?
}

# Delete a LV
lv_delete() {
	local vg lv device
	vg="$1"
	lv="$2"
	device="/dev/$vg/$lv"

	swapoff $device > /dev/null 2>&1
	umount $device > /dev/null 2>&1

	# swapoff or umount may have fiddled with metadata on the block
	# device, so we may need to wait for udev to finish rescanning it.
	update-dev --settle

	log-output -t partman-lvm lvremove -f "$device"
	return $?
}

# Checks that a logical volume name is ok
# Rules:
# 1) The common rules (see lvm_name_ok)
# 2) must not start with "snapshot"
# See lvm2 source and bug #254630 for details
lv_name_ok() {
	local lvname
	lvname="$1"

	# Rule 1
	lvm_name_ok "$lvname" || return 1

	# Rule 2
	if [ "${lvname#snapshot}" != "$lvname" ]; then
		return 1
	fi

	return 0
}

###############################################################################
#
# Volume Group utility functions
#
###############################################################################

# Get VG info
vg_get_info() {
	local info

	info=$(lvm_get_info vgs vg_size,vg_extent_count,vg_free,vg_free_count,lv_count,pv_count "$1")
	if [ $? -ne 0 ]; then
		return 1
	fi

	SIZE=$(echo "$info"   | cut -d':' -f1 | cut -d'.' -f1)
	SIZEPE=$(echo "$info" | cut -d':' -f2)
	FREE=$(echo "$info"   | cut -d':' -f3 | cut -d'.' -f1)
	FREEPE=$(echo "$info" | cut -d':' -f4)
	LVS=$(echo "$info"    | cut -d':' -f5)
	PVS=$(echo "$info"    | cut -d':' -f6)
	return 0
}

# List all VGs
vg_list() {
	lvm_get_info vgs vg_name ""
}

# List all VGs with free space
vg_list_free() {
	local vg

	for vg in $(vg_list); do
		vg_get_info "$vg"
		if [ $FREEPE -gt 0 ]; then
			echo "$vg"
		fi
	done
}

# Get all PVs from a VG
vg_list_pvs() {
	local line vg pv

	# vgs doesn't work with pv_name
	for line in $(lvm_get_info pvs vg_name,pv_name ""); do
		vg=$(echo "$line" | cut -d':' -f1)
		pv=$(echo "$line" | cut -d':' -f2)
		if [ "$vg" = "$1" ]; then
			echo "$pv"
		fi
	done
}

# Get all LVs from a VG
vg_list_lvs() {
	lvm_get_info lvs lv_name "$1"
}

# Lock device(s) holding a PV
vg_lock_pvs() {
	local name pv
	name="$1"
	shift

	db_subst partman-lvm/text/in_use VG "$name"
	db_metaget partman-lvm/text/in_use description
	for pv in $*; do
		partman_lock_unit "$pv" "$RET"
	done
}

# Create a volume group
vg_create() {
	local vg pv
	vg="$1"
	shift

	for pv in $*; do
		pv_create "$pv" || return 1
	done
	log-output -t partman-lvm vgcreate "$vg" $* || return 1
	update-dev --settle
	return 0
}

# Delete a volume group
vg_delete() {
	local vg
	vg="$1"

	log-output -t partman-lvm vgchange -a n "$vg" && \
	update-dev --settle && \
	log-output -t partman-lvm vgremove "$vg" && \
	update-dev --settle && \
	return 0

	# reactivate if deleting failed
	log-output -t partman-lvm vgchange -a y "$vg"
	update-dev --settle
	return 1
}

# Extend a volume group (add a PV)
vg_extend() {
	local vg pv
	vg="$1"
	pv="$2"

	pv_create "$pv" || return 1
	log-output -t partman-lvm vgextend "$vg" "$pv" || return 1
	return 0
}

# Reduce a volume group (remove a PV)
vg_reduce() {
	local vg pv
	vg="$1"
	pv="$2"

	log-output -t partman-lvm vgreduce "$vg" "$pv"
	return $?
}

# Checks that a logical volume name is ok
# Rules:
# 1) The common rules (see lvm_name_ok)
# See lvm2 source and bug #254630 for details
vg_name_ok() {
	local vgname
	vgname="$1"

	# Rule 1
	lvm_name_ok "$vgname" || return 1

	return 0
}
