utils/uvol/files/lvm.sh
#!/bin/sh

cmd="$1"
shift

if [ "$cmd" = "name" ]; then
	echo "LVM"
	return 0
fi

command -v lvm >/dev/null || return 1

. /lib/functions.sh
. /lib/upgrade/common.sh
. /usr/share/libubox/jshn.sh

export_bootdevice
[ "$BOOTDEV_MAJOR" ] || return 1
export_partdevice rootdev 0
[ "$rootdev" ] || return 1

case "$rootdev" in
	mtd*|\
	ram*|\
	ubi*)
		return 1
esac

lvm_cmd() {
	local cmd="$1"
	shift
	LVM_SUPPRESS_FD_WARNINGS=1 lvm "$cmd" "$@"
	return $?
}

pvs() {
	lvm_cmd pvs --reportformat json --units b "$@"
}

vgs() {
	lvm_cmd vgs --reportformat json --units b "$@"
}

lvs() {
	lvm_cmd lvs --reportformat json --units b "$@"
}

freebytes() {
	echo $((vg_free_count * vg_extent_size * 1024))
}

totalbytes() {
	echo $((vg_extent_count * vg_extent_size * 1024))
}

existvol() {
	[ "$1" ] || return 1
	test -e "/dev/$vg_name/ro_$1" || test -e "/dev/$vg_name/rw_$1"
	return $?
}

vg_name=
exportpv() {
	local reports rep pv pvs
	vg_name=
	json_init
	json_load "$(pvs -o vg_name -S "pv_name=~^/dev/$rootdev.*\$")"
	json_select report
	json_get_keys reports
	for rep in $reports; do
		json_select "$rep"
		json_select pv
		json_get_keys pvs
		for pv in $pvs; do
			json_select "$pv"
			json_get_vars vg_name
			json_select ..
			break
		done
		json_select ..
		break
	done
}

vg_extent_size=
vg_extent_count=
vg_free_count=
exportvg() {
	local reports rep vg vgs
	vg_extent_size=
	vg_extent_count=
	vg_free_count=
	json_init
	json_load "$(vgs -o vg_extent_size,vg_extent_count,vg_free_count -S "vg_name=$vg_name")"
	json_select report
	json_get_keys reports
	for rep in $reports; do
		json_select "$rep"
		json_select vg
		json_get_keys vgs
		for vg in $vgs; do
			json_select "$vg"
			json_get_vars vg_extent_size vg_extent_count vg_free_count
			vg_extent_size=${vg_extent_size%B}
			json_select ..
			break
		done
		json_select ..
		break
	done
}

lv_active=
lv_name=
lv_full_name=
lv_path=
lv_dm_path=
lv_size=
exportlv() {
	local reports rep lv lvs
	lv_active=
	lv_name=
	lv_full_name=
	lv_path=
	lv_dm_path=
	lv_size=
	json_init

	json_load "$(lvs -o lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path -S "lv_name=~^[rw][owp]_$1\$ && vg_name=$vg_name")"
	json_select report
	json_get_keys reports
	for rep in $reports; do
		json_select "$rep"
		json_select lv
		json_get_keys lvs
		for lv in $lvs; do
			json_select "$lv"
			json_get_vars lv_active lv_name lv_full_name lv_size lv_path lv_dm_path
			lv_size=${lv_size%B}
			json_select ..
			break
		done
		json_select ..
		break
	done
}

getdev() {
	existvol "$1" || return 1
	exportlv "$1"
	echo "$lv_dm_path"
}

getsize() {
	exportlv "$1"
	[ "$lv_size" ] && echo "$lv_size"
}

activatevol() {
	exportlv "$1"
	[ "$lv_path" ] || return 2
	case "$lv_path" in
		/dev/*/wo_*|\
		/dev/*/wp_*)
			return 22
			;;
		*)
			[ "$lv_active" = "active" ] && return 0
			lvm_cmd lvchange -k n "$lv_full_name" || return $?
			lvm_cmd lvchange -a y "$lv_full_name" || return $?
			ubus send block.volume "{\"name\": \"$1\", \"action\": \"up\", \"mode\": \"${lv_name:0:2}\", \"device\": \"$lv_dm_path\"}"
			return 0
			;;
	esac
}

disactivatevol() {
	exportlv "$1"
	[ "$lv_path" ] || return 2
	case "$lv_path" in
		/dev/*/wo_*|\
		/dev/*/wp_*)
			return 22
			;;
		*)
			[ "$lv_active" = "active" ] || return 0
			lvm_cmd lvchange -a n "$lv_full_name" || return $?
			lvm_cmd lvchange -k y "$lv_full_name" || return $?
			ubus send block.volume "{\"name\": \"$1\", \"action\": \"down\", \"mode\": \"${lv_name:0:2}\", \"device\": \"$lv_dm_path\"}"
			return 0
			;;
	esac
}

getstatus() {
	exportlv "$1"
	[ "$lv_full_name" ] || return 2
	existvol "$1" || return 1
	return 0
}

createvol() {
	local mode lvmode ret
	local volsize=$(($2))
	[ "$volsize" ] || return 22
	exportlv "$1"
	[ "$lv_size" ] && return 17
	size_ext=$((volsize / vg_extent_size))
	[ $((size_ext * vg_extent_size)) -lt $volsize ] && size_ext=$((size_ext + 1))

	case "$3" in
		ro|wo)
			lvmode=r
			mode=wo
			;;
		rw)
			lvmode=rw
			mode=wp
			;;
		*)
			return 22
			;;
	esac

	lvm_cmd lvcreate -p "$lvmode" -a n -y -W n -Z n -n "${mode}_$1" -l "$size_ext" "$vg_name"
	ret=$?
	if [ ! $ret -eq 0 ] || [ "$lvmode" = "r" ]; then
		return $ret
	fi
	exportlv "$1"
	[ "$lv_full_name" ] || return 22
	lvm_cmd lvchange -a y "$lv_full_name" || return 1
	if [ "$lv_size" -gt $(( 100 * 1024 * 1024 )) ]; then
		mkfs.f2fs -f -l "$1" "$lv_path"
		ret=$?
		[ $ret != 0 ] && [ $ret != 134 ] && return 1
	else
		mke2fs -F -L "$1" "$lv_path" || return 1
	fi
	lvm_cmd lvrename "$vg_name" "wp_$1" "rw_$1"
	exportlv "$1"
	ubus send block.volume "{\"name\": \"$1\", \"action\": \"up\", \"mode\": \"${lv_name:0:2}\", \"device\": \"$lv_dm_path\"}"
	return 0
}

removevol() {
	exportlv "$1"
	[ "$lv_full_name" ] || return 2
	lvm_cmd lvremove -y "$lv_full_name"
	ubus send block.volume "{\"name\": \"$1\", \"action\": \"down\", \"mode\": \"${lv_name:0:2}\", \"device\": \"$lv_dm_path\"}"
}

updatevol() {
	exportlv "$1"
	[ "$lv_full_name" ] || return 2
	[ "$lv_size" -ge "$2" ] || return 27
	case "$lv_path" in
		/dev/*/wo_*)
			lvm_cmd lvchange -a y -p rw "$lv_full_name"
			dd of="$lv_path"
			lvm_cmd lvchange -p r "$lv_full_name"
			lvm_cmd lvrename "$lv_full_name" "${lv_full_name%%/*}/ro_$1"
			ubus send block.volume "{\"name\": \"$1\", \"action\": \"up\", \"mode\": \"ro\", \"device\": \"$(getdev "$@")\"}"
			return 0
			;;
		default)
			return 22
			;;
	esac
}

listvols() {
	local reports rep lv lvs lv_name lv_size lv_mode volname
	volname=${1:-.*}
	json_init
	json_load "$(lvs -o lv_name,lv_size -S "lv_name=~^[rw][owp]_$volname\$ && vg_name=$vg_name")"
	json_select report
	json_get_keys reports
	for rep in $reports; do
		json_select "$rep"
		json_select lv
		json_get_keys lvs
		for lv in $lvs; do
			json_select "$lv"
			json_get_vars lv_name lv_size
			lv_mode="${lv_name:0:2}"
			lv_name="${lv_name:3}"
			lv_size=${lv_size%B}
			echo "$lv_name $lv_mode $lv_size"
			json_select ..
		done
		json_select ..
		break
	done
}

boot() {
	local reports rep lv lvs lv_name lv_dm_path lv_mode volname
	json_init
	json_load "$(lvs -o lv_name,lv_dm_path -S "lv_name=~^[rw][ow]_.*\$ && vg_name=$vg_name && lv_active=active")"
	json_select report
	json_get_keys reports
	for rep in $reports; do
		json_select "$rep"
		json_select lv
		json_get_keys lvs
		for lv in $lvs; do
			json_select "$lv"
			json_get_vars lv_name lv_dm_path
			lv_mode="${lv_name:0:2}"
			lv_name="${lv_name:3}"
			ubus send block.volume "{\"name\": \"$lv_name\", \"action\": \"up\", \"mode\": \"$lv_mode\", \"device\": \"$lv_dm_path\"}"
			json_select ..
		done
		json_select ..
		break
	done
}

exportpv
exportvg

case "$cmd" in
	align)
		echo "$vg_extent_size"
		;;
	free)
		freebytes
		;;
	total)
		totalbytes
		;;
	boot)
		boot
		;;
	list)
		listvols "$@"
		;;
	create)
		createvol "$@"
		;;
	remove)
		removevol "$@"
		;;
	device)
		getdev "$@"
		;;
	size)
		getsize "$@"
		;;
	up)
		activatevol "$@"
		;;
	down)
		disactivatevol "$@"
		;;
	status)
		getstatus "$@"
		;;
	write)
		updatevol "$@"
		;;
	*)
		echo "unknown command"
		return 1
		;;
esac
