From 813b09542fd13428d432e47eecd7191d1b3067fd Mon Sep 17 00:00:00 2001
From: John Crispin <john@phrozen.org>
Date: Fri, 13 Aug 2021 08:46:57 +0200
Subject: [PATCH 01/83] uvol: backport package

Signed-off-by: John Crispin <john@phrozen.org>
---
 package/system/uvol/Makefile                |  77 ++++
 package/system/uvol/files/autopart.defaults | 127 ++++++
 package/system/uvol/files/common.sh         |  83 ++++
 package/system/uvol/files/lvm.sh            | 459 ++++++++++++++++++++
 package/system/uvol/files/ubi.sh            | 358 +++++++++++++++
 package/system/uvol/files/uvol              |  52 +++
 package/system/uvol/files/uvol.defaults     |  17 +
 package/system/uvol/files/uvol.init         |  23 +
 8 files changed, 1196 insertions(+)
 create mode 100644 package/system/uvol/Makefile
 create mode 100644 package/system/uvol/files/autopart.defaults
 create mode 100644 package/system/uvol/files/common.sh
 create mode 100644 package/system/uvol/files/lvm.sh
 create mode 100644 package/system/uvol/files/ubi.sh
 create mode 100644 package/system/uvol/files/uvol
 create mode 100644 package/system/uvol/files/uvol.defaults
 create mode 100644 package/system/uvol/files/uvol.init

diff --git a/package/system/uvol/Makefile b/package/system/uvol/Makefile
new file mode 100644
index 0000000000..6583e6e756
--- /dev/null
+++ b/package/system/uvol/Makefile
@@ -0,0 +1,77 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=uvol
+PKG_VERSION:=0.6
+PKG_RELEASE:=$(AUTORELEASE)
+
+PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
+PKG_LICENSE:=GPL-2.0-or-later
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/autopart
+  SECTION:=utils
+  CATEGORY:=Utilities
+  SUBMENU:=Disc
+  TITLE:=Automatically initialize LVM partition
+  DEPENDS:=+lvm2 +partx-utils +sfdisk
+  PKGARCH=all
+endef
+
+define Package/autopart/description
+ Automatically allocate the GPT partition for LVM and initialize it
+ on first boot.
+endef
+
+define Package/uvol
+  SECTION:=utils
+  CATEGORY:=Utilities
+  SUBMENU:=Disc
+  TITLE:=OpenWrt UBI/LVM volume abstraction
+  DEPENDS:=+blockd
+  PKGARCH=all
+endef
+
+define Package/uvol/description
+  'uvol' is tool to automate storage volume handling on embedded
+  devices in a generic way.
+  Also install the 'autopart' package to easily make use of 'uvol' on
+  block-storage based devices.
+
+  Examples:
+  uvol create example_volume_1 256MiB rw
+  uvol up example_volume_1
+  uvol device example_volume_1
+
+  uvol create example_volume_2 9812733 ro
+  cat example_volume_2.squashfs | uvol write example_volume_2 9812733
+  uvol up example_volume_2
+  uvol device example_volume_2
+endef
+
+define Build/Prepare
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/autopart/install
+	$(INSTALL_DIR) $(1)/etc/uci-defaults
+	$(INSTALL_BIN) ./files/autopart.defaults $(1)/etc/uci-defaults/30-autopart
+endef
+
+define Package/uvol/install
+	$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/libexec/uvol $(1)/usr/sbin $(1)/lib/functions $(1)/etc/uci-defaults
+	$(INSTALL_BIN) ./files/uvol.init $(1)/etc/init.d/uvol
+	$(INSTALL_BIN) ./files/common.sh $(1)/lib/functions/uvol.sh
+	$(INSTALL_BIN) ./files/ubi.sh $(1)/usr/libexec/uvol/20-ubi.sh
+	$(INSTALL_BIN) ./files/lvm.sh $(1)/usr/libexec/uvol/50-lvm.sh
+	$(INSTALL_BIN) ./files/uvol $(1)/usr/sbin
+	$(INSTALL_BIN) ./files/uvol.defaults $(1)/etc/uci-defaults/90-uvol-init
+endef
+
+$(eval $(call BuildPackage,autopart))
+$(eval $(call BuildPackage,uvol))
diff --git a/package/system/uvol/files/autopart.defaults b/package/system/uvol/files/autopart.defaults
new file mode 100644
index 0000000000..870cd44156
--- /dev/null
+++ b/package/system/uvol/files/autopart.defaults
@@ -0,0 +1,127 @@
+#!/bin/sh
+
+. /lib/functions.sh
+. /lib/upgrade/common.sh
+. /usr/share/libubox/jshn.sh
+
+OWRT_VOLUMES=owrt-volumes
+
+load_partitions() {
+	local dev="$1"
+	json_init
+	json_load "$(sfdisk -J "$dev" 2>/dev/null)"
+	json_select "partitiontable" || return 1
+	return 0
+}
+
+get_partition_by_name_gpt() {
+	local label part parts node name
+	json_get_vars label
+	[ "$label" = "gpt" ] || return
+	json_select "partitions" || return
+	json_get_keys parts
+	for part in $parts; do
+		json_select "$part"
+		json_get_vars node name
+		if [ "$1" = "$name" ]; then
+			echo "$node"
+			break
+		fi
+		json_select ..
+	done
+	json_select ..
+}
+
+get_partition_by_type_mbr() {
+	local label part parts node type
+	json_get_vars label
+	[ "$label" = "dos" ] || return
+	json_select "partitions" || return
+	json_get_keys parts
+	for part in $parts; do
+		json_select "$part"
+		json_get_vars node type
+		if [ "$1" = "$type" ]; then
+			echo "$node"
+			break
+		fi
+		json_select ..
+	done
+	json_select ..
+}
+
+part_fixup() {
+	echo "write" | sfdisk --force -q -w never "$1" 1>/dev/null 2>/dev/null
+}
+
+get_free_area() {
+	local found=
+	sfdisk -q -F "$1" 2>/dev/null | while read -r start end sectors size; do
+		case $start in
+		*"Unpartitioned"* | *"Units:"* | *"Sector"* | *"Start"* )
+			continue
+			;;
+		[0-9]*)
+			case "$size" in
+				*"M")
+					[ "${size%%M}" -lt 100 ] && continue
+					;;
+				*"G" | *"T")
+					;;
+				*"k" | *"b")
+					continue
+					;;
+			esac
+			[ "$found" ] || echo "start=$start, size=$((end - start))"
+			found=1
+			;;
+		esac
+	done
+}
+
+create_lvm_part() {
+	local disk="$1"
+	local freepart
+
+	freepart="$(get_free_area "$disk")"
+	if [ "$freepart" ]; then
+		echo "$freepart, type=lvm, name=$OWRT_VOLUMES" | sfdisk --force -w never -a "$disk" || return 1
+		partx -a "$disk" 1>/dev/null 2>/dev/null || true
+		return 0
+	else
+		return 1
+	fi
+}
+
+lvm_init() {
+	lvm pvcreate -f "$1"
+	lvm vgcreate "$2" "$1"
+	lvm vgs
+}
+
+autopart_init() {
+	local diskdev
+	local lvmpart
+	local diskserial diskhash
+
+	export_bootdevice && export_partdevice diskdev 0
+
+	[ "$diskdev" ] || return
+
+	[ -e "/sys/class/block/$diskdev/device/serial" ] && diskserial="$(cat "/sys/class/block/$diskdev/device/serial")"
+	[ -e "/sys/class/block/$diskdev/device/cid" ] && diskserial="$diskserial$(cat "/sys/class/block/$diskdev/device/cid")"
+	[ "$diskserial" ] || diskserial="$(cat /proc/sys/kernel/random/uuid)"
+	diskhash="$(echo "$diskserial" | sha256sum | cut -d' ' -f1)"
+
+	part_fixup "/dev/$diskdev"
+	create_lvm_part "/dev/$diskdev" || return
+	load_partitions "/dev/$diskdev" || return
+	lvmpart="$(get_partition_by_name_gpt "$OWRT_VOLUMES")"
+	[ "$lvmpart" ] || lvmpart="$(get_partition_by_type_mbr "8e")"
+	[ "$lvmpart" ] || return
+
+	lvm_init "$lvmpart" "${OWRT_VOLUMES}-${diskhash:0:16}"
+}
+
+autopart_init
+exit 0
diff --git a/package/system/uvol/files/common.sh b/package/system/uvol/files/common.sh
new file mode 100644
index 0000000000..8306477720
--- /dev/null
+++ b/package/system/uvol/files/common.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+UCI_SPOOLDIR="/var/spool/uvol"
+
+_uvol_init_spooldir() {
+	[ ! -d "$(dirname "$UCI_SPOOLDIR")" ] && mkdir -p "$(dirname "$UCI_SPOOLDIR")"
+	mkdir -m 0700 -p "$UCI_SPOOLDIR"
+}
+
+uvol_uci_add() {
+	local volname="$1"
+	local devname="$2"
+	local mode="$3"
+	local autofs=0
+	local target="/var/run/uvol/$volname"
+	local uuid uciname
+
+	[ "$mode" = "ro" ] && autofs=1
+	uciname="${volname//[-.]/_}"
+	uciname="${uciname//[!([:alnum:]_)]}"
+	uuid="$(/sbin/block info | grep "^$2" | xargs -n 1 echo | grep "^UUID=.*")"
+	[ "$uuid" ] || return 22
+	uuid="${uuid:5}"
+
+	case "$uciname" in
+		"_meta")
+			target="/var/run/uvol/.meta"
+			;;
+		"_"*)
+			return 1
+			;;
+	esac
+
+	_uvol_init_spooldir
+	if [ -e "${UCI_SPOOLDIR}/remove-$1" ]; then
+		rm "${UCI_SPOOLDIR}/remove-$1"
+	fi
+
+	cat >"${UCI_SPOOLDIR}/add-$1" <<EOF
+set fstab.$uciname=mount
+set fstab.$uciname.uuid=$uuid
+set fstab.$uciname.target=$target
+set fstab.$uciname.options=$mode
+set fstab.$uciname.autofs=$autofs
+set fstab.$uciname.enabled=1
+EOF
+}
+
+uvol_uci_remove() {
+	local volname="$1"
+	local uciname
+
+	uciname="${volname//[-.]/_}"
+	uciname="${uciname//[!([:alnum:]_)]}"
+	if [ -e "${UCI_SPOOLDIR}/add-$1" ]; then
+		rm "${UCI_SPOOLDIR}/add-$1"
+		return
+	fi
+	_uvol_init_spooldir
+	cat >"${UCI_SPOOLDIR}/remove-$1" <<EOF
+delete fstab.$uciname
+EOF
+}
+
+uvol_uci_commit() {
+	local volname="$1"
+	local ucibatch
+
+	for ucibatch in "${UCI_SPOOLDIR}/"*"-$volname"${volname+*} ; do
+		[ -e "$ucibatch" ] || break
+		uci batch < "$ucibatch"
+		[ $? -eq 0 ] && rm "$ucibatch"
+	done
+
+	uci commit fstab
+	return $?
+}
+
+uvol_uci_init() {
+	uci -q get fstab.@uvol[0] && return
+	uci add fstab uvol
+	uci set fstab.@uvol[-1].initialized=1
+}
diff --git a/package/system/uvol/files/lvm.sh b/package/system/uvol/files/lvm.sh
new file mode 100644
index 0000000000..c7e93f13f2
--- /dev/null
+++ b/package/system/uvol/files/lvm.sh
@@ -0,0 +1,459 @@
+#!/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/functions/uvol.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))
+}
+
+totalbytes() {
+	echo $((vg_extent_count * vg_extent_size))
+}
+
+existvol() {
+	[ "$1" ] || return 1
+	test -e "/dev/$vg_name/ro_$1" || test -e "/dev/$vg_name/rw_$1"
+	return $?
+}
+
+vg_name=
+exportpv() {
+	vg_name=
+	config_load fstab
+	local uvolsect="$(config_foreach echo uvol)"
+	[ -n "$uvolsect" ] && config_get vg_name "$uvolsect" vg_name
+	[ -n "$vg_name" ] && return
+	local reports rep pv pvs
+	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() {
+	local dms dm_name
+
+	for dms in /sys/devices/virtual/block/dm-* ; do
+		[ "$dms" = "/sys/devices/virtual/block/dm-*" ] && break
+		read -r dm_name < "$dms/dm/name"
+		[ "$(basename "$lv_dm_path")" = "$dm_name" ] && basename "$dms"
+	done
+}
+
+getuserdev() {
+	local dms dm_name
+	existvol "$1" || return 1
+	exportlv "$1"
+	getdev "$@"
+}
+
+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
+			;;
+		*)
+			uvol_uci_commit "$1"
+			[ "$lv_active" = "active" ] && return 0
+			lvm_cmd lvchange -k n "$lv_full_name" || return $?
+			lvm_cmd lvchange -a y "$lv_full_name" || return $?
+			return 0
+			;;
+	esac
+}
+
+disactivatevol() {
+	exportlv "$1"
+	local devname
+	[ "$lv_path" ] || return 2
+	case "$lv_path" in
+		/dev/*/wo_*|\
+		/dev/*/wp_*)
+			return 22
+			;;
+		*)
+			[ "$lv_active" = "active" ] || return 0
+			devname="$(getdev "$1")"
+			[ "$devname" ] && umount "/dev/$devname"
+			lvm_cmd lvchange -a n "$lv_full_name"
+			lvm_cmd lvchange -k y "$lv_full_name" || return $?
+			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" || return $?
+	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 $?
+	if [ "$lv_size" -gt $(( 100 * 1024 * 1024 )) ]; then
+		mkfs.f2fs -f -l "$1" "$lv_path"
+		ret=$?
+		[ $ret != 0 ] && [ $ret != 134 ] && {
+			lvm_cmd lvchange -a n "$lv_full_name" || return $?
+			return $ret
+		}
+	else
+		mke2fs -F -L "$1" "$lv_path" || {
+			ret=$?
+			lvm_cmd lvchange -a n "$lv_full_name" || return $?
+			return $ret
+		}
+	fi
+	uvol_uci_add "$1" "/dev/$(getdev "$1")" "rw"
+	lvm_cmd lvchange -a n "$lv_full_name" || return $?
+	lvm_cmd lvrename "$vg_name" "wp_$1" "rw_$1" || return $?
+	return 0
+}
+
+removevol() {
+	exportlv "$1"
+	[ "$lv_full_name" ] || return 2
+	[ "$lv_active" = "active" ] && return 16
+	lvm_cmd lvremove -y "$lv_full_name" || return $?
+	uvol_uci_remove "$1"
+	uvol_uci_commit "$1"
+}
+
+updatevol() {
+	exportlv "$1"
+	[ "$lv_full_name" ] || return 2
+	[ "$lv_size" -ge "$2" ] || return 27
+	case "$lv_path" in
+		/dev/*/wo_*)
+			lvm_cmd lvchange -p rw "$lv_full_name" || return $?
+			lvm_cmd lvchange -a y "$lv_full_name" || return $?
+			dd of="$lv_path"
+			uvol_uci_add "$1" "/dev/$(getdev "$1")" "ro"
+			lvm_cmd lvchange -a n "$lv_full_name" || return $?
+			lvm_cmd lvchange -p r "$lv_full_name" || return $?
+			lvm_cmd lvrename "$lv_full_name" "${lv_full_name%%/*}/ro_$1" || return $?
+			return 0
+			;;
+		default)
+			return 22
+			;;
+	esac
+}
+
+listvols() {
+	local reports rep lv lvs lv_name lv_size lv_mode volname json_output json_notfirst
+	if [ "$1" = "-j" ]; then
+		json_output=1
+		echo "["
+		shift
+	fi
+	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}
+			[ "${lv_name:0:1}" = "." ] && continue
+			if [ "$json_output" = "1" ]; then
+				[ "$json_notfirst" = "1" ] && echo ","
+				echo -e "\t{"
+				echo -e "\t\t\"name\": \"$lv_name\","
+				echo -e "\t\t\"mode\": \"$lv_mode\","
+				echo -e "\t\t\"size\": $lv_size"
+				echo -n -e "\t}"
+				json_notfirst=1
+			else
+				echo "$lv_name $lv_mode $lv_size"
+			fi
+			json_select ..
+		done
+		json_select ..
+		break
+	done
+
+	if [ "$json_output" = "1" ]; then
+		[ "$json_notfirst" = "1" ] && echo
+		echo "]"
+	fi
+}
+
+detect() {
+	local reports rep lv lvs lv_name lv_full_name lv_mode volname devname
+	local temp_up=""
+
+	json_init
+	json_load "$(lvs -o lv_full_name -S "lv_name=~^[rw][owp]_.*\$ && vg_name=$vg_name && lv_skip_activation!=0")"
+	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_full_name
+			echo "lvchange -a y $lv_full_name"
+			lvm_cmd lvchange -k n "$lv_full_name"
+			lvm_cmd lvchange -a y "$lv_full_name"
+			temp_up="$temp_up $lv_full_name"
+			json_select ..
+		done
+		json_select ..
+		break
+	done
+	sleep 1
+
+	uvol_uci_init
+
+	json_init
+	json_load "$(lvs -o lv_name,lv_dm_path -S "lv_name=~^[rw][owp]_.*\$ && 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_dm_path
+			lv_mode="${lv_name:0:2}"
+			lv_name="${lv_name:3}"
+			echo uvol_uci_add "$lv_name" "/dev/$(getdev "$lv_name")" "$lv_mode"
+			uvol_uci_add "$lv_name" "/dev/$(getdev "$lv_name")" "$lv_mode"
+			json_select ..
+		done
+		json_select ..
+		break
+	done
+
+	uvol_uci_commit
+
+	for lv_full_name in $temp_up; do
+		echo "lvchange -a n $lv_full_name"
+		lvm_cmd lvchange -a n "$lv_full_name"
+		lvm_cmd lvchange -k y "$lv_full_name"
+	done
+}
+
+boot() {
+	true ; # nothing to do, lvm does it all for us
+}
+
+exportpv
+exportvg
+
+case "$cmd" in
+	align)
+		echo "$vg_extent_size"
+		;;
+	free)
+		freebytes
+		;;
+	total)
+		totalbytes
+		;;
+	detect)
+		detect
+		;;
+	boot)
+		boot
+		;;
+	list)
+		listvols "$@"
+		;;
+	create)
+		createvol "$@"
+		;;
+	remove)
+		removevol "$@"
+		;;
+	device)
+		getuserdev "$@"
+		;;
+	size)
+		getsize "$@"
+		;;
+	up)
+		activatevol "$@"
+		;;
+	down)
+		disactivatevol "$@"
+		;;
+	status)
+		getstatus "$@"
+		;;
+	write)
+		updatevol "$@"
+		;;
+	*)
+		echo "unknown command"
+		return 1
+		;;
+esac
diff --git a/package/system/uvol/files/ubi.sh b/package/system/uvol/files/ubi.sh
new file mode 100644
index 0000000000..7637fba682
--- /dev/null
+++ b/package/system/uvol/files/ubi.sh
@@ -0,0 +1,358 @@
+#!/bin/sh
+
+cmd="$1"
+shift
+
+if [ "$cmd" = "name" ]; then
+	echo "UBI"
+	return 0
+fi
+
+test -e /sys/class/ubi/version || return 0
+read -r ubiver < /sys/class/ubi/version
+[ "$ubiver" = "1" ] || return 1
+test -e /sys/devices/virtual/ubi || return 0
+
+ubidev=$(ls -1 /sys/devices/virtual/ubi | head -n 1)
+
+read -r ebsize < "/sys/devices/virtual/ubi/${ubidev}/eraseblock_size"
+
+. /lib/functions/uvol.sh
+
+freebytes() {
+	read -r availeb < "/sys/devices/virtual/ubi/${ubidev}/avail_eraseblocks"
+	echo $((availeb * ebsize))
+}
+
+totalbytes() {
+	read -r totaleb < "/sys/devices/virtual/ubi/${ubidev}/total_eraseblocks"
+	echo $((totaleb * ebsize))
+}
+
+getdev() {
+	local voldir volname
+	for voldir in "/sys/devices/virtual/ubi/${ubidev}/${ubidev}_"*; do
+		read -r volname < "${voldir}/name"
+		case "$volname" in
+			uvol-[rw][owpd]-$1)
+				basename "$voldir"
+				break
+				;;
+			*)
+				continue
+				;;
+		esac
+	done
+}
+
+vol_is_mode() {
+	local voldev="$1"
+	local volname
+	read -r volname < "/sys/devices/virtual/ubi/${ubidev}/${voldev}/name"
+	case "$volname" in
+		uvol-$2-*)
+			return 0
+			;;
+	esac
+	return 1
+}
+
+getstatus() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	vol_is_mode "$voldev" wo && return 22
+	vol_is_mode "$voldev" wp && return 16
+	vol_is_mode "$voldev" wd && return 1
+	vol_is_mode "$voldev" ro && [ ! -e "/dev/ubiblock${voldev:3}" ] && return 1
+	return 0
+}
+
+getsize() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	cat "/sys/devices/virtual/ubi/${ubidev}/${voldev}/data_bytes"
+}
+
+getuserdev() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	if vol_is_mode "$voldev" ro ; then
+		echo "/dev/ubiblock${voldev:3}"
+	elif vol_is_mode "$voldev" rw ; then
+		echo "/dev/$voldev"
+	fi
+}
+
+mkubifs() {
+	local tmp_mp
+	tmp_mp="$(mktemp -d)"
+	mount -t ubifs "$1" "$tmp_mp" || return $?
+	umount "$tmp_mp" || return $?
+	rmdir "$tmp_mp" || return $?
+	return 0
+}
+
+createvol() {
+	local mode ret voldev
+	voldev=$(getdev "$@")
+	[ "$voldev" ] && return 17
+	case "$3" in
+		ro|wo)
+			mode=wo
+			;;
+		rw)
+			mode=wp
+			;;
+		*)
+			return 22
+			;;
+	esac
+	ubimkvol "/dev/$ubidev" -N "uvol-$mode-$1" -s "$2" || return $?
+	ret=$?
+	[ $ret -eq 0 ] || return $ret
+	voldev="$(getdev "$@")"
+	ubiupdatevol -t "/dev/$voldev" || return $?
+	[ "$mode" = "wp" ] || return 0
+	mkubifs "/dev/$voldev" || return $?
+	uvol_uci_add "$1" "/dev/$voldev" "rw"
+	ubirename "/dev/$ubidev" "uvol-wp-$1" "uvol-wd-$1" || return $?
+}
+
+removevol() {
+	local voldev volnum
+	voldev=$(getdev "$@")
+	[ "$voldev" ] || return 2
+	vol_is_mode "$voldev" rw && return 16
+	vol_is_mode "$voldev" ro && return 16
+	volnum="${voldev#${ubidev}_}"
+	ubirmvol "/dev/$ubidev" -n "$volnum" || return $?
+	uvol_uci_remove "$1"
+	uvol_uci_commit "$1"
+}
+
+block_hotplug() {
+	export ACTION="$1"
+	export DEVNAME="$2"
+	/sbin/block hotplug
+}
+
+activatevol() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	vol_is_mode "$voldev" rw && return 0
+	vol_is_mode "$voldev" ro && return 0
+	vol_is_mode "$voldev" wo && return 22
+	vol_is_mode "$voldev" wp && return 16
+	uvol_uci_commit "$1"
+	if vol_is_mode "$voldev" rd; then
+		ubirename "/dev/$ubidev" "uvol-rd-$1" "uvol-ro-$1" || return $?
+		ubiblock --create "/dev/$voldev" || return $?
+		return 0
+	elif vol_is_mode "$voldev" wd; then
+		ubirename "/dev/$ubidev" "uvol-wd-$1" "uvol-rw-$1" || return $?
+		block_hotplug add "$voldev"
+		return 0
+	fi
+}
+
+disactivatevol() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	vol_is_mode "$voldev" rd && return 0
+	vol_is_mode "$voldev" wd && return 0
+	vol_is_mode "$voldev" wo && return 22
+	vol_is_mode "$voldev" wp && return 16
+	if vol_is_mode "$voldev" ro; then
+		grep -q "^/dev/ubiblock${voldev:3}" /proc/self/mounts && umount "/dev/ubiblock${voldev:3}"
+		ubiblock --remove "/dev/$voldev"
+		ubirename "/dev/$ubidev" "uvol-ro-$1" "uvol-rd-$1" || return $?
+		return 0
+	elif vol_is_mode "$voldev" rw; then
+		umount "/dev/$voldev"
+		ubirename "/dev/$ubidev" "uvol-rw-$1" "uvol-wd-$1" || return $?
+		block_hotplug remove "$voldev"
+		return 0
+	fi
+}
+
+updatevol() {
+	local voldev
+	voldev="$(getdev "$@")"
+	[ "$voldev" ] || return 2
+	[ "$2" ] || return 22
+	vol_is_mode "$voldev" wo || return 22
+	ubiupdatevol -s "$2" "/dev/$voldev" -
+	ubiblock --create "/dev/$voldev"
+	uvol_uci_add "$1" "/dev/ubiblock${voldev:3}" "ro"
+	ubiblock --remove "/dev/$voldev"
+	ubirename "/dev/$ubidev" "uvol-wo-$1" "uvol-rd-$1"
+}
+
+listvols() {
+	local volname volmode volsize json_output json_notfirst
+	if [ "$1" = "-j" ]; then
+		json_output=1
+		shift
+		echo "["
+	fi
+	for voldir in "/sys/devices/virtual/ubi/${ubidev}/${ubidev}_"*; do
+		read -r volname < "$voldir/name"
+		case "$volname" in
+			uvol-[rw][wod]*)
+				read -r volsize < "$voldir/data_bytes"
+				;;
+			*)
+				continue
+				;;
+		esac
+		volmode="${volname:5:2}"
+		volname="${volname:8}"
+		[ "${volname:0:1}" = "." ] && continue
+		if [ "$json_output" = "1" ]; then
+			[ "$json_notfirst" = "1" ] && echo ","
+				echo -e "\t{"
+				echo -e "\t\t\"name\": \"$volname\","
+				echo -e "\t\t\"mode\": \"$volmode\","
+				echo -e "\t\t\"size\": $volsize"
+				echo -n -e "\t}"
+				json_notfirst=1
+		else
+			echo "$volname $volmode $volsize"
+		fi
+	done
+
+	if [ "$json_output" = "1" ]; then
+		[ "$json_notfirst" = "1" ] && echo
+		echo "]"
+	fi
+}
+
+bootvols() {
+	local volname volmode volsize voldev fstype
+	for voldir in "/sys/devices/virtual/ubi/${ubidev}/${ubidev}_"*; do
+		read -r volname < "$voldir/name"
+		voldev="$(basename "$voldir")"
+		fstype=
+		case "$volname" in
+			uvol-ro-*)
+				ubiblock --create "/dev/$voldev" || return $?
+				;;
+			*)
+				continue
+				;;
+		esac
+		volmode="${volname:5:2}"
+		volname="${volname:8}"
+	done
+}
+
+detect() {
+	local volname voldev volmode voldev fstype tmpdev=""
+	for voldir in "/sys/devices/virtual/ubi/${ubidev}/${ubidev}_"*; do
+		read -r volname < "$voldir/name"
+		voldev="$(basename "$voldir")"
+		fstype=
+		case "$volname" in
+			uvol-r[od]-*)
+				if ! [ -e "/dev/ubiblock${voldev:3}" ]; then
+					ubiblock --create "/dev/$voldev" || return $?
+				fi
+				case "$volname" in
+				uvol-rd-*)
+					tmpdev="$tmpdev $voldev"
+					;;
+				esac
+				;;
+			*)
+				continue
+				;;
+		esac
+		volmode="${volname:5:2}"
+		volname="${volname:8}"
+	done
+
+	uvol_uci_init
+
+	for voldir in "/sys/devices/virtual/ubi/${ubidev}/${ubidev}_"*; do
+		read -r volname < "$voldir/name"
+		voldev="$(basename "$voldir")"
+		case "$volname" in
+			uvol-[rw][wod]*)
+				true
+				;;
+			*)
+				continue
+				;;
+		esac
+		volmode="${volname:5:2}"
+		volname="${volname:8}"
+		case "$volmode" in
+		"ro" | "rd")
+			uvol_uci_add "$volname" "/dev/ubiblock${voldev:3}" "ro"
+			;;
+		"rw" | "wd")
+			uvol_uci_add "$volname" "/dev/${voldev}" "rw"
+			;;
+		esac
+	done
+
+	uvol_uci_commit
+
+	for voldev in $tmpdev ; do
+		ubiblock --remove "/dev/$voldev" || return $?
+	done
+}
+
+case "$cmd" in
+	align)
+		echo "$ebsize"
+		;;
+	free)
+		freebytes
+		;;
+	total)
+		totalbytes
+		;;
+	detect)
+		detect
+		;;
+	boot)
+		bootvols
+		;;
+	list)
+		listvols "$@"
+		;;
+	create)
+		createvol "$@"
+		;;
+	remove)
+		removevol "$@"
+		;;
+	device)
+		getuserdev "$@"
+		;;
+	size)
+		getsize "$@"
+		;;
+	up)
+		activatevol "$@"
+		;;
+	down)
+		disactivatevol "$@"
+		;;
+	status)
+		getstatus "$@"
+		;;
+	write)
+		updatevol "$@"
+		;;
+	*)
+		echo "unknown command"
+		return 1
+		;;
+esac
diff --git a/package/system/uvol/files/uvol b/package/system/uvol/files/uvol
new file mode 100644
index 0000000000..4ecd2e165a
--- /dev/null
+++ b/package/system/uvol/files/uvol
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# uvol prototype
+# future development roadmap (aka. to-do):
+# * re-implement in C (use libubox, execve lvm/ubi*)
+# * hash to validate volume while writing
+# * add atomic batch processing for use by container/package manager
+
+if [ -z "$1" ]; then cat <<EOF
+uvol storage volume manager
+
+syntax: uvol command ...
+
+commands:
+  boot				get active volumes ready (called on boot)
+  free				show number of bytes available
+  total				show total number of bytes
+  align				show sector size in bytes
+  list [volname]		list volumes
+  create volname size type	create new volume
+    size: in bytes
+    type: 'ro' or 'rw'
+  remove volname		delete volume
+  device volname		show block device for mounting
+  size volname			show size of volume
+  up volname			get volume ready for mounting
+  down volname			take volume down after unmounting
+  status volname		return status of volume
+    return code: 0 - volume is ready for use
+                 1 - volume is not ready for use
+                 2 - volume doesn'y exist
+  write volname size		write to volume from stdin, size in bytes
+EOF
+	return 22
+fi
+
+uvol_backend=
+backends_tried=
+
+for backend in /usr/libexec/uvol/*.sh; do
+	total=$($backend total)
+	backends_tried="$backends_tried $($backend name)"
+	[ "$total" ] && uvol_backend=$backend
+done
+
+if [ -z "$uvol_backend" ]; then
+	echo "No backend available. (tried:$backends_tried)"
+	echo "To setup devices with block storage install 'autopart'."
+	return 2
+fi
+
+flock -x /tmp/run/uvol.lock "$uvol_backend" "$@"
diff --git a/package/system/uvol/files/uvol.defaults b/package/system/uvol/files/uvol.defaults
new file mode 100644
index 0000000000..e71135f7a8
--- /dev/null
+++ b/package/system/uvol/files/uvol.defaults
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+uvol_init() {
+	local metasz freesz totalsz
+	uvol detect
+	metasz="$(uvol size .meta 2>/dev/null)"
+	[ "$((metasz))" -gt 0 ] && return
+	totalsz="$(uvol total)"
+	freesz="$(uvol free)"
+	metasz="$((totalsz / 10240))"
+	[ "$metasz" -lt 4194304 ] && metasz=4194304
+	[ "$metasz" -gt "$freesz" ] && return
+	uvol create .meta "$metasz" rw
+	uvol up .meta
+}
+
+uci -q get fstab.@uvol[0].initialized >/dev/null || uvol_init
diff --git a/package/system/uvol/files/uvol.init b/package/system/uvol/files/uvol.init
new file mode 100644
index 0000000000..1f6e2aac08
--- /dev/null
+++ b/package/system/uvol/files/uvol.init
@@ -0,0 +1,23 @@
+#!/bin/sh /etc/rc.common
+
+START=99
+USE_PROCD=1
+NAME=uvol
+PROG=/usr/sbin/uvol
+
+start_service() {
+	[ "${__BOOT_UVOL}" = "1" ] && return 0
+	procd_open_instance "$NAME"
+	procd_set_param command "$PROG" boot
+	procd_close_instance
+}
+
+boot() {
+	__BOOT_UVOL=1
+	start
+}
+
+service_triggers() {
+	procd_add_raw_trigger "mount.ready" 200 /etc/init.d/uvol start
+}
+
-- 
2.25.1

