uvol: replace with re-write in ucode
authorDaniel Golle <daniel@makrotopia.org>
Thu, 31 Mar 2022 16:51:22 +0000 (17:51 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Thu, 31 Mar 2022 17:45:22 +0000 (18:45 +0100)
Replace previous Shell draft-quality implementation of uvol with a
rewrite in ucode[1].
While the new code is slightly larger, it performs much better (as
we no longer fork() for parsing strings like in Shell with grep, sed
and friends).

Before:
  time uvol list -j
  [ ... ]
  real 0m 0.82s
  user 0m 0.13s
  sys 0m 0.10s

After:
  time uvol list -j
  [ ... ]
  real 0m 0.47s
  user 0m 0.05s
  sys 0m 0.05s

[1]: https://github.com/jow-/ucode
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
utils/uvol/Makefile
utils/uvol/files/blockdev_common.uc [new file with mode: 0644]
utils/uvol/files/common.sh [deleted file]
utils/uvol/files/lvm.sh [deleted file]
utils/uvol/files/lvm.uc [new file with mode: 0644]
utils/uvol/files/ubi.sh [deleted file]
utils/uvol/files/ubi.uc [new file with mode: 0644]
utils/uvol/files/uci.uc [new file with mode: 0644]
utils/uvol/files/uvol

index 6583e6e75663f3904e6cf4fa75c1ac99707bb253..21d46fcff1832617de3125057599322fb7dda440 100644 (file)
@@ -1,7 +1,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=uvol
-PKG_VERSION:=0.6
+PKG_VERSION:=0.7
 PKG_RELEASE:=$(AUTORELEASE)
 
 PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
@@ -19,8 +19,7 @@ define Package/autopart
 endef
 
 define Package/autopart/description
- Automatically allocate the GPT partition for LVM and initialize it
- on first boot.
+ Automatically allocate and initialize a partition for LVM on first boot.
 endef
 
 define Package/uvol
@@ -28,18 +27,22 @@ define Package/uvol
   CATEGORY:=Utilities
   SUBMENU:=Disc
   TITLE:=OpenWrt UBI/LVM volume abstraction
-  DEPENDS:=+blockd
+  DEPENDS:=+blockd +ucode +ucode-mod-fs +ucode-mod-uci
   PKGARCH=all
 endef
 
 define Package/uvol/description
   'uvol' is tool to automate storage volume handling on embedded
   devices in a generic way.
+  Depending on what is available, 'uvol' will use either UBI or LVM2
+  as storage backends and transparently offer identical operations on
+  top of them.
+
   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 create example_volume_1 268435456 rw
   uvol up example_volume_1
   uvol device example_volume_1
 
@@ -64,11 +67,12 @@ define Package/autopart/install
 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_DIR) $(1)/etc/init.d $(1)/usr/uvol/backends $(1)/usr/sbin $(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_DATA) ./files/blockdev_common.uc $(1)/usr/lib/uvol/
+       $(INSTALL_DATA) ./files/uci.uc $(1)/usr/lib/uvol/
+       $(INSTALL_DATA) ./files/lvm.uc $(1)/usr/lib/uvol/backends/
+       $(INSTALL_DATA) ./files/ubi.uc $(1)/usr/lib/uvol/backends/
        $(INSTALL_BIN) ./files/uvol $(1)/usr/sbin
        $(INSTALL_BIN) ./files/uvol.defaults $(1)/etc/uci-defaults/90-uvol-init
 endef
diff --git a/utils/uvol/files/blockdev_common.uc b/utils/uvol/files/blockdev_common.uc
new file mode 100644 (file)
index 0000000..f45e573
--- /dev/null
@@ -0,0 +1,123 @@
+{%
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Helper functions used to identify the boot device
+       // adapted from /lib/functions.sh
+       let cmdline_get_var = function(var) {
+               let cmdline = fs.open("/proc/cmdline", "r");
+               let allargs = cmdline.read("all");
+               cmdline.close();
+               let ret = null;
+               for (let arg in split(allargs, /[ \t\n]/)) {
+                       let el = split(arg, "=");
+                       if (shift(el) == var)
+                               return join("=", el);
+               }
+               return ret;
+       };
+
+       // adapted from /lib/upgrade/common.sh
+       let get_blockdevs = function() {
+               let devs = [];
+               for (let dev in fs.glob('/dev/*'))
+                       if (fs.stat(dev).type == "block")
+                               push(devs, split(dev, '/')[-1]);
+
+               return devs;
+       };
+
+       // adapted from /lib/upgrade/common.sh
+       let get_uevent_major_minor = function(file) {
+               let uevf = fs.open(file, "r");
+               if (!uevf)
+                       return null;
+
+               let r = {};
+               let evl;
+               while ((evl = uevf.read("line"))) {
+               let ev = split(evl, '=');
+                       if (ev[0] == "MAJOR")
+                               r.major = +ev[1];
+                       if (ev[0] == "MINOR")
+                               r.minor = +ev[1];
+               }
+               uevf.close();
+               return r;
+       };
+
+       // adapted from /lib/upgrade/common.sh
+       let get_bootdev = function(void) {
+               let rootpart = cmdline_get_var("root");
+               let uevent = null;
+
+               if (wildcard(rootpart, "PARTUUID=[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]-[a-f0-9][a-f0-9]")) {
+                       let uuidarg = split(substr(rootpart, 9), '-')[0];
+                       for (let bd in get_blockdevs()) {
+                               let bdf = fs.open(sprintf("/dev/%s", bd), "r");
+                               bdf.seek(440);
+                               let bduuid = bdf.read(4);
+                               bdf.close();
+                               if (uuidarg == sprintf("%x%x%x%x", ord(bduuid, 3), ord(bduuid, 2), ord(bduuid, 1), ord(bduuid, 0))) {
+                                       uevent = sprintf("/sys/class/block/%s/uevent", bd);
+                                       break;
+                               }
+                       }
+               } else if (wildcard(rootpart, "PARTUUID=????????-????-????-????-??????????0?/PARTNROFF=*") ||
+                          wildcard(rootpart, "PARTUUID=????????-????-????-????-??????????02")) {
+                       let uuidarg = substr(split(substr(rootpart, 9), '/')[0], 0, -2) + "00";
+                       for (let bd in get_blockdevs()) {
+                               let bdf = fs.open(sprintf("/dev/%s", bd), "r");
+                               bdf.seek(568);
+                               let bduuid = bdf.read(16);
+                               bdf.close();
+                               if (!bduuid)
+                                       continue;
+
+                               let uuid = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+                                       ord(bduuid, 3), ord(bduuid, 2), ord(bduuid, 1), ord(bduuid, 0),
+                                       ord(bduuid, 5), ord(bduuid, 4),
+                                       ord(bduuid, 7), ord(bduuid, 6),
+                                       ord(bduuid, 8), ord(bduuid, 9),
+                                       ord(bduuid, 10), ord(bduuid, 11), ord(bduuid, 12), ord(bduuid, 13), ord(bduuid, 14), ord(bduuid, 15));
+                               if (uuidarg == uuid) {
+                                       uevent = sprintf("/sys/class/block/%s/uevent", bd);
+                                       break;
+                               }
+                       }
+               } else if (wildcard(rootpart, "0x[a-f0-9][a-f0-9][a-f0-9]") ||
+                          wildcard(rootpart, "0x[a-f0-9][a-f0-9][a-f0-9][a-f0-9]") ||
+                          wildcard(rootpart, "[a-f0-9][a-f0-9][a-f0-9]") ||
+                          wildcard(rootpart, "[a-f0-9][a-f0-9][a-f0-9][a-f0-9]")) {
+                       let devid = rootpart;
+                       if (substr(devid, 0, 2) == "0x")
+                               devid = substr(devid, 2);
+
+                       devid = hex(devid);
+                       for (let bd in get_blockdevs()) {
+                               let r = get_uevent_major_minor(sprintf("/sys/class/block/%s/uevent", bd));
+                               if (r && (r.major == devid / 256) && (r.minor == devid % 256)) {
+                                       uevent = sprintf("/sys/class/block/%s/../uevent", bd);
+                                       break;
+                               }
+                       }
+               } else if (wildcard(rootpart, "/dev/*")) {
+                       uevent = sprintf("/sys/class/block/%s/../uevent", split(rootpart, '/')[-1]);
+               }
+                       return get_uevent_major_minor(uevent);
+       };
+
+       // adapted from /lib/upgrade/common.sh
+       let get_partition = function(dev, num) {
+               for (let bd in get_blockdevs()) {
+                       let r = get_uevent_major_minor(sprintf("/sys/class/block/%s/uevent", bd));
+                       if (r.major == dev.major && r.minor == dev.minor + num) {
+                               return bd;
+                               break;
+                       }
+               }
+               return null;
+       };
+
+       blockdev_common = {};
+       blockdev_common.get_partition = get_partition;
+       blockdev_common.get_bootdev = get_bootdev;
+%}
diff --git a/utils/uvol/files/common.sh b/utils/uvol/files/common.sh
deleted file mode 100644 (file)
index eee486c..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/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="/tmp/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="/tmp/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/utils/uvol/files/lvm.sh b/utils/uvol/files/lvm.sh
deleted file mode 100644 (file)
index 6dd5139..0000000
+++ /dev/null
@@ -1,460 +0,0 @@
-#!/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}
-                       if [ "${lv_name:0:1}" != "." ]; then
-                               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
-                       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/utils/uvol/files/lvm.uc b/utils/uvol/files/lvm.uc
new file mode 100644 (file)
index 0000000..5be27e5
--- /dev/null
@@ -0,0 +1,467 @@
+{%
+// SPDX-License-Identifier: GPL-2.0-or-later
+// LVM2 backend for uvol
+//  (c) 2022 Daniel Golle <daniel@makrotopia.org>
+//
+// This plugin uses LVM2 as a storage backend for uvol.
+//
+// By default, volumes are allocated on the physical device used for booting,
+// the LVM2 PV and VG are initialized auto-magically by the 'autopart' script.
+// By setting the UCI option 'vg_name' in the 'uvol' section in /etc/config/fstab
+// you may set an arbitrary LVM2 volume group to back uvol instad.
+
+       let lvm_exec = "/sbin/lvm";
+
+       function lvm(cmd, ...args) {
+               let lvm_json_cmds = [ "lvs", "pvs", "vgs" ];
+               try {
+                       let json_param = "";
+                       if (cmd in lvm_json_cmds)
+                               json_param = "--reportformat json --units b ";
+                       let stdout = fs.popen(sprintf("LVM_SUPPRESS_FD_WARNINGS=1 %s %s %s%s", lvm_exec, cmd, json_param, join(" ", args)));
+                       let tmp;
+                       if (stdout) {
+                               tmp = stdout.read("all");
+                               let ret = {};
+                               ret.retval = stdout.close();
+                               if (json_param) {
+                                       let data = json(tmp);
+                                       if (data.report)
+                                               ret.report = data.report[0];
+                               } else {
+                                       ret.stdout = trim(tmp);
+                               }
+                               return ret;
+                       } else {
+                               printf("lvm cli command failed: %s\n", fs.error());
+                       }
+               } catch(e) {
+                       printf("Failed to parse lvm cli output: %s\n%s\n", e, e.stacktrace[0].context);
+               }
+               return null;
+       }
+
+       function pvs() {
+               let fstab = cursor.get_all('fstab');
+               for (let k, section in fstab) {
+                       if (section['.type'] != 'uvol' || !section.vg_name)
+                               continue;
+
+                       return section.vg_name;
+               }
+               include("/usr/lib/uvol/blockdev_common.uc");
+               let rootdev = blockdev_common.get_partition(blockdev_common.get_bootdev(), 0);
+               let tmp = lvm("pvs", "-o", "vg_name", "-S", sprintf("\"pv_name=~^/dev/%s.*\$\"", rootdev));
+               if (tmp.report.pv)
+                       return tmp.report.pv[0].vg_name;
+               else
+                       return null;
+       }
+
+       function vgs(vg_name) {
+               let tmp = lvm("vgs", "-o", "vg_extent_size,vg_extent_count,vg_free_count", "-S", sprintf("\"vg_name=%s\"", vg_name));
+               let ret = null;
+               if (tmp && tmp.report.vg) {
+                       ret = tmp.report.vg;
+                       for (let r in ret) {
+                               r.vg_extent_size = +(rtrim(r.vg_extent_size, "B"));
+                               r.vg_extent_count = +r.vg_extent_count;
+                               r.vg_free_count = +r.vg_free_count;
+                       }
+               }
+               if (ret)
+                       return ret[0];
+               else
+                       return null;
+       }
+
+       function lvs(vg_name, vol_name, extra_exp) {
+               let ret = [];
+               if (!vol_name)
+                       vol_name = ".*";
+
+               let lvexpr = sprintf("\"lvname=~^[rw][owp]_%s\$ && vg_name=%s%s%s\"",
+                                    vol_name, vg_name, extra_exp?" && ":"", extra_exp?extra_exp:"");
+               let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path", "-S", lvexpr);
+               if (tmp && tmp.report.lv) {
+                       ret = tmp.report.lv;
+                       for (let r in ret) {
+                               r.lv_size = +(rtrim(r.lv_size, "B"));
+                               r.lv_active = (r.lv_active == "active");
+                       }
+               }
+               return ret;
+       }
+
+       function getdev(lv) {
+               if (!lv)
+                       return null;
+
+               for (let dms in fs.glob("/sys/devices/virtual/block/dm-*")) {
+                       let f = fs.open(sprintf("%s/dm/name", dms), "r");
+                       if (!f)
+                               continue;
+
+                       let dm_name = trim(f.read("all"));
+                       f.close();
+                       if ( split(lv.lv_dm_path, '/')[-1] == dm_name )
+                               return split(dms, '/')[-1]
+               }
+               return null;
+       }
+
+       function lvm_init(ctx) {
+               cursor = ctx.cursor;
+               fs = ctx.fs;
+               if (!fs.access(lvm_exec, "x"))
+                       return false;
+
+               vg_name = pvs();
+               if (!vg_name)
+                       return false;
+
+               vg = vgs(vg_name);
+               uvol_uci_add = ctx.uci_add;
+               uvol_uci_commit = ctx.uci_commit;
+               uvol_uci_remove = ctx.uci_remove;
+               uvol_uci_init = ctx.uci_init;
+               return true;
+       }
+
+       function lvm_free() {
+               if (!vg || !vg.vg_free_count || !vg.vg_extent_size)
+                       return 2;
+
+               return sprintf("%d", vg.vg_free_count * vg.vg_extent_size);
+       }
+
+       function lvm_total() {
+               if (!vg || !vg.vg_extent_count || !vg.vg_extent_size)
+                       return 2;
+
+               return sprintf("%d", vg.vg_extent_count * vg.vg_extent_size);
+       }
+
+       function lvm_align() {
+               if (!vg || !vg.vg_extent_size)
+                       return 2;
+
+               return sprintf("%d", vg.vg_extent_size);
+       }
+
+       function lvm_list(vol_name) {
+               let vols = [];
+
+               if (!vg_name)
+                       return vols;
+
+               let res = lvs(vg_name, vol_name);
+               for (let lv in res) {
+                       let vol = {};
+                       if (substr(lv.lv_name, 3, 1) == ".")
+                               continue;
+
+                       vol.name = substr(lv.lv_name, 3);
+                       vol.mode = substr(lv.lv_name, 0, 2);
+                       if (!lv.lv_active) {
+                               if (vol.mode == "ro")
+                                       vol.mode = "rd";
+                               if (vol.mode == "rw")
+                                       vol.mode = "wd";
+                       }
+                       vol.size = lv.lv_size;
+                       push(vols, vol);
+               }
+
+               return vols;
+       }
+
+       function lvm_size(vol_name) {
+               if (!vol_name || !vg_name)
+                       return 2;
+
+               let res = lvs(vg_name, vol_name);
+               if (!res[0])
+                       return 2;
+
+               return sprintf("%d", res[0].lv_size);
+       }
+
+       function lvm_status(vol_name) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               let res = lvs(vg_name, vol_name);
+               if (!res[0])
+                       return 2;
+
+               let mode = substr(res[0].lv_name, 0, 2);
+               if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
+                       return 1;
+
+               return 0;
+       }
+
+       function lvm_device(vol_name) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               let res = lvs(vg_name, vol_name);
+               if (!res[0])
+                       return 2;
+
+               let mode = substr(res[0].lv_name, 0, 2);
+               if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
+                       return 22;
+
+               return getdev(res[0]);
+       }
+
+       function lvm_updown(vol_name, up) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               let res = lvs(vg_name, vol_name);
+               if (!res[0])
+                       return 2;
+
+               let lv = res[0];
+               if (!lv.lv_path)
+                       return 2;
+
+               if (up && (wildcard(lv.lv_path, "/dev/*/wo_*") ||
+                          wildcard(lv.lv_path, "/dev/*/wp_*")))
+                       return 22;
+
+               if (up)
+                       uvol_uci_commit(vol_name);
+
+               if (lv.lv_active == up)
+                       return 0;
+
+               if (!up) {
+                       let devname = getdev(lv);
+                       if (devname)
+                               system(sprintf("umount /dev/%s", devname));
+               }
+
+               let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name);
+               if (up && lvchange_r.retval != 0)
+                       return lvchange_r.retval;
+
+               lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
+               if (lvchange_r.retval != 0)
+                       return lvchange_r.retval;
+
+               return 0
+       }
+
+       function lvm_up(vol_name) {
+               return lvm_updown(vol_name, true);
+       }
+
+       function lvm_down(vol_name) {
+               return lvm_updown(vol_name, false);
+       }
+
+       function lvm_create(vol_name, vol_size, vol_mode) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               vol_size = +vol_size;
+               if (vol_size <= 0)
+                       return 22;
+
+               let res = lvs(vg_name, vol_name);
+               if (res[0])
+                       return 17;
+
+               let size_ext = vol_size / vg.vg_extent_size;
+               if (vol_size % vg.vg_extent_size)
+                       ++size_ext;
+               let lvmode, mode;
+               if (vol_mode == "ro" || vol_mode == "wo") {
+                       lvmode = "r";
+                       mode = "wo";
+               } else if (vol_mode == "rw") {
+                       lvmode = "rw";
+                       mode = "wp";
+               } else {
+                       return 22;
+               }
+
+               let ret = lvm("lvcreate", "-p", lvmode, "-a", "n", "-y", "-W", "n", "-Z", "n", "-n", sprintf("%s_%s", mode, vol_name), "-l", size_ext, vg_name);
+               if (ret.retval != 0 || lvmode == "r")
+                       return ret.retval;
+
+               let lv = lvs(vg_name, vol_name);
+               if (!lv[0] || !lv[0].lv_full_name)
+                       return 22;
+
+               lv = lv[0];
+               let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
+               if (ret.retval != 0)
+                       return ret.retval;
+
+               let use_f2fs = (lv.lv_size > (100 * 1024 * 1024));
+               if (use_f2fs) {
+                       let mkfs_ret = system(sprintf("/usr/sbin/mkfs.f2fs -f -l \"%s\" \"%s\"", vol_name, lv.lv_path));
+                       if (mkfs_ret != 0 && mkfs_ret != 134) {
+                               lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
+                               if (lvchange_r.retval != 0)
+                                       return lvchange_r.retval;
+                               return mkfs_ret;
+                       }
+               } else {
+                       let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -L \"%s\" \"%s\"", vol_name, lv.lv_path));
+                       if (mkfs_ret != 0) {
+                               lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
+                               if (lvchange_r.retval != 0)
+                                       return lvchange_r.retval;
+                               return mkfs_ret;
+                       }
+               }
+               uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "rw");
+
+               ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
+               if (ret.retval != 0)
+                       return ret.retval;
+
+               ret = lvm("lvrename", vg_name, sprintf("wp_%s", vol_name), sprintf("rw_%s", vol_name));
+               if (ret.retval != 0)
+                       return ret.retval;
+
+               return 0;
+       }
+
+       function lvm_remove(vol_name) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               let res = lvs(vg_name, vol_name);
+               if (!res[0])
+                       return 2;
+
+               if (res[0].lv_active)
+                       return 16;
+
+               let ret = lvm("lvremove", "-y", res[0].lv_full_name);
+               if (ret.retval != 0)
+                       return ret.retval;
+
+               uvol_uci_remove(vol_name);
+               uvol_uci_commit(vol_name);
+               return 0;
+       }
+
+       function lvm_dd(in_fd, out_fd, vol_size) {
+               let rem = vol_size;
+               let buf;
+               while ((buf = in_fd.read(vg.vg_extent_size)) && (rem > 0)) {
+                       rem -= length(buf);
+                       if (rem < 0) {
+                               buf = substr(buf, 0, rem);
+                       }
+                       out_fd.write(buf);
+               }
+               return rem;
+       }
+
+       function lvm_write(vol_name, vol_size) {
+               if (!vol_name || !vg_name)
+                       return 22;
+
+               let lv = lvs(vg_name, vol_name);
+               if (!lv[0] || !lv[0].lv_full_name)
+                       return 2;
+
+               lv = lv[0];
+               vol_size = +vol_size;
+               if (vol_size > lv.lv_size)
+                       return 27;
+
+               if (wildcard(lv.lv_path, "/dev/*/wo_*")) {
+                       let ret = lvm("lvchange", "-p", "rw", lv.lv_full_name);
+                       if (ret.retval != 0)
+                               return ret.retval;
+
+                       let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
+                       if (ret.retval != 0)
+                               return ret.retval;
+
+                       let volfile = fs.open(lv.lv_path, "w");
+                       let ret = lvm_dd(fs.stdin, volfile, vol_size);
+                       volfile.close();
+                       if (ret < 0) {
+                               printf("more %d bytes data than given size!\n", -ret);
+                       }
+
+                       if (ret > 0) {
+                               printf("reading finished %d bytes before given size!\n", ret);
+                       }
+
+                       uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "ro");
+
+                       let ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
+                       if (ret.retval != 0)
+                               return ret.retval;
+
+                       let ret = lvm("lvchange", "-p", "r", lv.lv_full_name);
+                       if (ret.retval != 0)
+                               return ret.retval;
+
+                       let ret = lvm("lvrename", vg_name, sprintf("wo_%s", vol_name), sprintf("ro_%s", vol_name));
+                       if (ret.retval != 0)
+                               return ret.retval;
+
+               } else {
+                       return 22;
+               }
+               return 0;
+       }
+
+       function lvm_detect() {
+               let temp_up = [];
+               let inactive_lv = lvs(vg_name, null, "lv_skip_activation!=0");
+               for (let lv in inactive_lv) {
+                       lvm("lvchange", "-k", "n", lv.lv_full_name);
+                       lvm("lvchange", "-a", "y", lv.lv_full_name);
+                       push(temp_up, lv.lv_full_name);
+               }
+               sleep(1000);
+               uvol_uci_init();
+               for (let lv in lvs(vg_name)) {
+                       let vol_name = substr(lv.lv_name, 3);
+                       let vol_mode = substr(lv.lv_name, 0, 2);
+                       uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), vol_mode);
+               }
+               uvol_uci_commit();
+               for (let lv_full_name in temp_up) {
+                       lvm("lvchange", "-a", "n", lv_full_name);
+                       lvm("lvchange", "-k", "y", lv_full_name);
+               }
+               return 0;
+       }
+
+       function lvm_boot() {
+               return 0;
+       }
+
+       backend.backend = "LVM";
+       backend.priority = 50;
+       backend.init = lvm_init;
+       backend.boot = lvm_boot;
+       backend.detect = lvm_detect;
+       backend.free = lvm_free;
+       backend.align = lvm_align;
+       backend.total = lvm_total;
+       backend.list = lvm_list;
+       backend.size = lvm_size;
+       backend.status = lvm_status;
+       backend.device = lvm_device;
+       backend.up = lvm_up;
+       backend.down = lvm_down;
+       backend.create = lvm_create;
+       backend.remove = lvm_remove;
+       backend.write = lvm_write;
+%}
diff --git a/utils/uvol/files/ubi.sh b/utils/uvol/files/ubi.sh
deleted file mode 100644 (file)
index 7637fba..0000000
+++ /dev/null
@@ -1,358 +0,0 @@
-#!/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/utils/uvol/files/ubi.uc b/utils/uvol/files/ubi.uc
new file mode 100644 (file)
index 0000000..6f1cfbc
--- /dev/null
@@ -0,0 +1,378 @@
+{%
+// SPDX-License-Identifier: GPL-2.0-or-later
+// UBI backend for uvol
+//  (c) 2022 Daniel Golle <daniel@makrotopia.org>
+//
+// This plugin uses UBI on NAND flash as a storage backend for uvol.
+
+       function read_file(file) {
+               let fp = fs.open(file);
+               if (!fp)
+                       return null;
+
+               let var = rtrim(fp.read("all"));
+               fp.close();
+               return var;
+       }
+
+       function mkdtemp() {
+               math = require("math");
+               let r1 = math.rand();
+               let r2 = math.rand();
+               let randbytes = chr((r1 >> 24) & 0xff, (r1 >> 16) & 0xff, (r1 >> 8) & 0xff, r1 & 0xff,
+                                   (r2 >> 24) & 0xff, (r2 >> 16) & 0xff, (r2 >> 8) & 0xff, r2 & 0xff);
+
+               let randstr = replace(b64enc(randbytes), /[\/-_.=]/g, "");
+               let dirname = sprintf("/tmp/uvol-%s", randstr);
+               fs.mkdir(dirname, 0700);
+               return dirname;
+       }
+
+       function ubi_get_dev(vol_name) {
+               let wcstring = sprintf("uvol-[rw][owpd]-%s", vol_name);
+               for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
+                       let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
+                       if (wildcard(vol_ubiname, wcstring))
+                               return fs.basename(vol_dir);
+               }
+               return null;
+       }
+
+       function vol_get_mode(vol_dev, mode) {
+               let vol_name = read_file(sprintf("/sys/devices/virtual/ubi/%s/%s/name", ubidev, vol_dev));
+               return substr(vol_name, 5, 2);
+       }
+
+       function mkubifs(vol_dev) {
+               let temp_mp = mkdtemp();
+               system(sprintf("mount -t ubifs /dev/%s %s", vol_dev, temp_mp));
+               system(sprintf("umount %s", temp_mp));
+               fs.rmdir(temp_mp);
+               return 0;
+       }
+
+       function block_hotplug(action, devname) {
+               return system(sprintf("ACTION=%s DEVNAME=%s /sbin/block hotplug", action, devname));
+       }
+
+       function ubi_init(ctx) {
+               cursor = ctx.cursor;
+               fs = ctx.fs;
+
+               let ubiver = read_file("/sys/class/ubi/version");
+               if (ubiver != 1)
+                       return false;
+
+               let ubidevpath = null;
+               for (ubidevpath in fs.glob("/sys/devices/virtual/ubi/*"))
+                       break;
+
+               if (!ubidevpath)
+                       return false;
+
+               ubidev = fs.basename(ubidevpath);
+               ebsize = read_file(sprintf("%s/eraseblock_size", ubidevpath));
+
+               uvol_uci_add = ctx.uci_add;
+               uvol_uci_commit = ctx.uci_commit;
+               uvol_uci_remove = ctx.uci_remove;
+               uvol_uci_init = ctx.uci_init;
+
+               return true;
+       }
+
+       function ubi_free() {
+               let availeb = read_file(sprintf("/sys/devices/virtual/ubi/%s/avail_eraseblocks", ubidev));
+               return sprintf("%d", availeb * ebsize);
+       }
+
+       function ubi_align() {
+               return sprintf("%d", ebsize);
+       }
+
+       function ubi_total() {
+               let totaleb = read_file(sprintf("/sys/devices/virtual/ubi/%s/total_eraseblocks", ubidev));
+               return sprintf("%d", totaleb * ebsize);
+       }
+
+       function ubi_status(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode == "wo") return 22;
+               if (vol_mode == "wp") return 16;
+               if (vol_mode == "wd") return 1;
+               if (vol_mode == "ro" &&
+                   !fs.access(sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "r")) return 1;
+
+               return 0;
+       }
+
+       function ubi_size(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_size = read_file(sprintf("/sys/devices/virtual/ubi/%s/%s/data_bytes", ubidev, vol_dev));
+               return sprintf("%d", vol_size);
+       }
+
+       function ubi_device(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode == "ro")
+                       return sprintf("/dev/ubiblock%s", substr(vol_dev, 3));
+               else if (vol_mode == "rw")
+                       return sprintf("/dev/%s", vol_dev);
+
+               return null;
+       }
+
+       function ubi_create(vol_name, vol_size, vol_mode) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (vol_dev)
+                       return 17;
+
+               let mode;
+               if (vol_mode == "ro" || vol_mode == "wo")
+                       mode = "wo";
+               else if (vol_mode == "rw")
+                       mode = "wp";
+               else
+                       return 22;
+
+               let vol_size = +vol_size;
+               if (vol_size <= 0)
+                       return 22;
+               let ret = system(sprintf("ubimkvol /dev/%s -N \"uvol-%s-%s\" -s %d", ubidev, mode, vol_name, vol_size));
+               if (ret != 0)
+                       return ret;
+
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let ret = system(sprintf("ubiupdatevol -t /dev/%s", vol_dev));
+               if (ret != 0)
+                       return ret;
+
+               if (mode != "wp")
+                       return 0;
+
+               let ret = mkubifs(vol_dev);
+               if (ret != 0)
+                       return ret;
+
+               uvol_uci_add(vol_name, sprintf("/dev/%s", vol_dev), "rw");
+
+               let ret = system(sprintf("ubirename /dev/%s \"uvol-wp-%s\" \"uvol-wd-%s\"", ubidev, vol_name, vol_name));
+               if (ret != 0)
+                       return ret;
+
+               return 0;
+       }
+
+       function ubi_remove(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode == "rw" || vol_mode == "ro")
+                       return 16;
+
+               let volnum = split(vol_dev, "_")[1];
+
+               let ret = system(sprintf("ubirmvol /dev/%s -n %d", ubidev, volnum));
+               if (ret != 0)
+                       return ret;
+
+               uvol_uci_remove(vol_name);
+               uvol_uci_commit(vol_name);
+
+               return 0;
+       }
+
+       function ubi_up(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode == "rw" || vol_mode == "ro")
+                       return 0;
+               else if (vol_mode == "wo")
+                       return 22;
+               else if (vol_mode == "wp")
+                       return 16;
+
+               uvol_uci_commit(vol_name);
+               if (vol_mode == "rd") {
+                       let ret = system(sprintf("ubirename /dev/%s \"uvol-rd-%s\" \"uvol-ro-%s\"", ubidev, vol_name, vol_name));
+                       if (ret != 0)
+                               return ret;
+
+                       return system(sprintf("ubiblock --create /dev/%s", vol_dev));
+               } else if (vol_mode == "wd") {
+                       let ret = system(sprintf("ubirename /dev/%s \"uvol-wd-%s\" \"uvol-rw-%s\"", ubidev, vol_name, vol_name));
+                       if (ret != 0)
+                               return ret;
+
+                       return block_hotplug("add", vol_dev);
+               }
+               return 0;
+       }
+
+       function ubi_down(vol_name) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode == "rd" || vol_mode == "wd")
+                       return 0;
+               else if (vol_mode == "wo")
+                       return 22;
+               else if (vol_mode == "wp")
+                       return 16;
+               else if (vol_mode == "ro") {
+                       system(sprintf("umount /dev/ubiblock%s 2>&1 >/dev/null", substr(vol_dev, 3)));
+                       system(sprintf("ubiblock --remove /dev/%s", vol_dev));
+                       let ret = system(sprintf("ubirename /dev/%s \"uvol-ro-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
+                       return ret;
+               } else if (vol_mode == "rw") {
+                       system(sprintf("umount /dev/%s 2>&1 >/dev/null", vol_dev));
+                       let ret = system(sprintf("ubirename /dev/%s \"uvol-rw-%s\" \"uvol-wd-%s\"", ubidev, vol_name, vol_name));
+                       block_hotplug("remove", vol_dev);
+                       return ret;
+               }
+               return 0;
+       }
+
+       function ubi_list(search_name) {
+               let volumes = [];
+               for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
+                       let vol = {};
+                       let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
+                       if (!wildcard(vol_ubiname, "uvol-[rw][wod]-*"))
+                               continue;
+
+                       let vol_mode = substr(vol_ubiname, 5, 2);
+                       let vol_name = substr(vol_ubiname, 8);
+                       let vol_size = read_file(sprintf("%s/data_bytes", vol_dir));
+                       if (substr(vol_name, 0, 1) == ".")
+                               continue;
+
+                       vol.name = vol_name;
+                       vol.mode = vol_mode;
+                       vol.size = vol_size;
+                       push(volumes, vol);
+               }
+               return volumes;
+       }
+
+       function ubi_detect() {
+               let tmpdev = [];
+               for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
+                       let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
+
+                       if (!wildcard(vol_ubiname, "uvol-r[od]-*"))
+                               continue;
+
+                       let vol_name = substr(vol_ubiname, 8);
+                       let vol_mode = substr(vol_ubiname, 5, 2);
+                       let vol_dev = fs.basename(vol_dir);
+
+                       ret = system(sprintf("ubiblock --create /dev/%s", vol_dev));
+                       if (ret)
+                               continue;
+
+                       if (vol_mode == "rd")
+                               push(tmpdev, vol_dev);
+               }
+
+               uvol_uci_init();
+
+               for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
+                       let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
+                       if (!wildcard(vol_ubiname, "uvol-[rw][wod]-*"))
+                               continue;
+
+                       let vol_dev = fs.basename(vol_dir);
+                       let vol_name = substr(vol_ubiname, 8);
+                       let vol_mode = substr(vol_ubiname, 5, 2);
+
+                       if (vol_mode == "ro" || vol_mode == "rd")
+                               uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro");
+                       else if (vol_mode == "rw" || vol_mode == "wd")
+                               uvol_uci_add(vol_name, sprintf("/dev/%s", vol_dev), "rw");
+               }
+
+               uvol_uci_commit();
+
+               for (vol_dev in tmpdev)
+                       system(sprintf("ubiblock --remove /dev/%s", vol_dev));
+
+               return 0;
+       }
+
+       function ubi_boot() {
+               for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
+                       let vol_dev = fs.basename(vol_dir);
+                       let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
+
+                       if (!wildcard(vol_ubiname, "uvol-ro-*"))
+                               continue;
+
+                       system(sprintf("ubiblock --create /dev/%s", vol_dev));
+               }
+       }
+
+       function ubi_write(vol_name, write_size) {
+               let vol_dev = ubi_get_dev(vol_name);
+               if (!vol_dev)
+                       return 2;
+
+               write_size = +write_size;
+               if (write_size <= 0)
+                       return 22;
+
+               let vol_mode = vol_get_mode(vol_dev);
+               if (vol_mode != "wo")
+                       return 22;
+
+               let ret = system(sprintf("ubiupdatevol -s %s /dev/%s -", write_size, vol_dev));
+               if (ret)
+                       return ret;
+
+               system(sprintf("ubiblock --create /dev/%s", vol_dev));
+               uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro");
+               system(sprintf("ubiblock --remove /dev/%s", vol_dev));
+               system(sprintf("ubirename /dev/%s \"uvol-wo-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
+
+               return 0;
+       }
+
+       backend.backend = "UBI";
+       backend.priority = 20;
+       backend.init = ubi_init;
+       backend.boot = ubi_boot;
+       backend.detect = ubi_detect;
+       backend.free = ubi_free;
+       backend.align = ubi_align;
+       backend.total = ubi_total;
+       backend.list = ubi_list;
+       backend.size = ubi_size;
+       backend.status = ubi_status;
+       backend.device = ubi_device;
+       backend.up = ubi_up;
+       backend.down = ubi_down;
+       backend.create = ubi_create;
+       backend.remove = ubi_remove;
+       backend.write = ubi_write;
+%}
diff --git a/utils/uvol/files/uci.uc b/utils/uvol/files/uci.uc
new file mode 100644 (file)
index 0000000..24589ca
--- /dev/null
@@ -0,0 +1,139 @@
+{%
+// SPDX-License-Identifier: GPL-2.0-or-later
+// UCI tools for uvol
+//  (c) 2022 Daniel Golle <daniel@makrotopia.org>
+
+let uci_spooldir = "/var/spool/uvol";
+let init_spooldir = function(void) {
+       parentdir = fs.stat(fs.dirname(uci_spooldir));
+       if (!parentdir || parentdir.type != "directory")
+               fs.mkdir(fs.dirname(uci_spooldir), 0755);
+       fs.mkdir(uci_spooldir, 0700);
+};
+
+uvol_uci = {
+       uvol_uci_add: function(vol_name, dev_name, mode) {
+               try {
+                       let autofs = false;
+                       let uuid;
+                       let target;
+                       if (mode == "ro")
+                               autofs = true;
+
+                       let uciname = replace(vol_name, /[-.]/g, "_");
+                       uciname = replace(uciname, /!([:alnum:]_)/g, "");
+                       let bdinfo_p = fs.popen("/sbin/block info");
+                       let bdinfo_l;
+                       while (bdinfo_l = bdinfo_p.read("line")) {
+                               if (substr(bdinfo_l, 0, length(dev_name) + 1) != dev_name + ":")
+                                       continue;
+                               let bdinfo_e = split(bdinfo_l, " ");
+                               shift(bdinfo_e);
+                               for (let bdinfo_a in bdinfo_e) {
+                                       let bdinfo_v = split(bdinfo_a, "=");
+                                       if (bdinfo_v[0] && bdinfo_v[0] == "UUID") {
+                                               uuid = trim(bdinfo_v[1], "\"");
+                                               break;
+                                       }
+                               }
+                               break;
+                       }
+
+                       if (!uuid)
+                               return 22;
+
+                       if (uciname == "_meta")
+                               target = "/tmp/run/uvol/.meta";
+                       else if (substr(uciname, 0, 1) == "_")
+                               return 1;
+                       else
+                               target = sprintf("/tmp/run/uvol/%s", vol_name);
+
+                       init_spooldir();
+                       let remspool = sprintf("%s/remove-%s", uci_spooldir, uciname);
+                       if (fs.stat(remspool))
+                               fs.unlink(remspool);
+
+                       let addobj = {};
+                       addobj.name=uciname;
+                       addobj.uuid=uuid;
+                       addobj.target=target;
+                       addobj.options=mode;
+                       addobj.autofs=autofs;
+                       addobj.enabled=true;
+
+                       let spoolfile = fs.open(sprintf("%s/add-%s", uci_spooldir, uciname), "w");
+                       spoolfile.write(addobj);
+                       spoolfile.close();
+               } catch(e) {
+                       printf("adding UCI section to spool failed");
+                       return -1;
+               }
+               return 0;
+       },
+
+       uvol_uci_remove: function(vol_name) {
+               let uciname = replace(vol_name, /[-.]/g, "_");
+               uciname = replace(uciname, /!([:alnum:]_)/g, "");
+
+               let addspool = sprintf("%s/add-%s", uci_spooldir, uciname);
+               if (fs.stat(addspool)) {
+                       fs.unlink(addspool);
+                       return 0;
+               }
+               init_spooldir();
+               let spoolfile = fs.open(sprintf("%s/remove-%s", uci_spooldir, uciname), "w");
+               spoolfile.write(uciname);
+               spoolfile.close();
+               return 0;
+       },
+
+       uvol_uci_commit: function(vol_name) {
+               try {
+                       let uciname = null;
+                       if (vol_name) {
+                               uciname = replace(vol_name, /[-.]/g, "_");
+                               uciname = replace(uciname, /!([:alnum:]_)/g, "");
+                       }
+
+                       for (let file in fs.glob(sprintf("%s/*-%s", uci_spooldir, uciname?uciname:"*"))) {
+                               let action = split(fs.basename(file), "-")[0];
+                               let spoolfd = fs.open(file, "r");
+                               let spoolstr = spoolfd.read("all");
+                               spoolfd.close();
+                               fs.unlink(file);
+                               if (action == "remove") {
+                                       cursor.delete("fstab", spoolstr);
+                               } else if (action == "add") {
+                                       let spoolobj = json(spoolstr);
+                                       cursor.set("fstab", spoolobj.name, "mount");
+                                       for (key in keys(spoolobj)) {
+                                               if (key == "name")
+                                                       continue;
+
+                                               cursor.set("fstab", spoolobj.name, key, spoolobj[key]);
+                                       }
+                               }
+                       }
+                       cursor.commit();
+               } catch(e) {
+                       printf("committing UCI spool failed");
+                       return -1;
+               }
+               return 0;
+       },
+
+       uvol_uci_init: function () {
+               cursor.load("fstab");
+               let f = cursor.get("fstab", "@uvol[0]", "initialized");
+               if (f == 1)
+                       return 0;
+
+               cursor.add("fstab", "uvol");
+               cursor.set("fstab", "@uvol[-1]", "initialized", true);
+               cursor.commit();
+               cursor.unload("fstab");
+               return 0;
+       }
+};
+%}
index 4ecd2e165a6be2e39bc78e7b01de42b8ea8f7dfb..f89b96687e6b5aff71bfb75417a03bb43e45a31f 100644 (file)
@@ -1,12 +1,11 @@
-#!/bin/sh
+#!/usr/bin/ucode
+{%
+// SPDX-License-Identifier: GPL-2.0-or-later
+// uvol - storage volume manager for OpenWrt
+//  (c) 2022 Daniel Golle <daniel@makrotopia.org>
 
-# 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
+       function help() {
+       %}
 uvol storage volume manager
 
 syntax: uvol command ...
@@ -30,23 +29,102 @@ commands:
                  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" "$@"
+{%
+       }
+
+       let fs = require("fs");
+       let uci = require("uci");
+       let cursor = uci ? uci.cursor() : null;
+
+       let ctx = {};
+       ctx.cursor = cursor;
+       ctx.fs = fs;
+       include("/usr/lib/uvol/uci.uc");
+       ctx.uci_add = uvol_uci.uvol_uci_add;
+       ctx.uci_remove = uvol_uci.uvol_uci_remove;
+       ctx.uci_commit = uvol_uci.uvol_uci_commit;
+       ctx.uci_init = uvol_uci.uvol_uci_init;
+
+       let backend = null;
+       let tried_backends = [];
+       for (plugin in fs.glob("/usr/lib/uvol/backends/*.uc")) {
+               let current_backend = {};
+               include(plugin, { backend: current_backend });
+               push(tried_backends, current_backend.backend);
+               if (type(backend) == "object" &&
+                   type(backend.priority) == "int" &&
+                   type(current_backend.priority) == "int" &&
+                   backend.priority > current_backend.priority)
+                       continue;
+
+               if (type(current_backend.init) == "function" &&
+                   current_backend.init(ctx)) {
+                       backend = current_backend;
+                       break;
+               }
+       }
+
+       if (!backend) {
+               printf("No backend available. (tried: %s)\n", join(" ", tried_backends));
+               printf("To setup devices with block storage install 'autopart'.\n");
+               exit(2);
+       }
+
+       shift(ARGV);
+       shift(ARGV);
+       let cmd = shift(ARGV);
+
+       if (!cmd || cmd == "-h" || cmd == "help") {
+               help();
+               return cmd?0:22;
+       }
+
+       if (!(cmd in keys(backend))) {
+               printf("command %s not found\n", cmd);
+               return 22;
+       }
+
+       let json_output = false;
+       if (ARGV[0] == "-j") {
+               json_output = true;
+               shift(ARGV);
+       }
+
+       let legacy_output = function(var) {
+               let out = "";
+               if (type(var) == "array") {
+                       for (let line in var) {
+                               out += join(" ", values(line));
+                               out += "\n";
+                       }
+               } else if (type(var) == "object") {
+                       out += join(" ", values(line));
+                       out += "\n";
+               }
+               return out;
+       };
+
+       if (type(backend[cmd]) == "string") {
+               printf("%s\n", backend[cmd]);
+       } else if (type(backend[cmd]) == "function") {
+               let ret = backend[cmd](...ARGV);
+               if (type(ret) == "int")
+                       exit(ret);
+
+               if (type(ret) == "string") {
+                       printf("%s\n", ret);
+               } else {
+                       if (json_output)
+                               printf("%.J\n", ret);
+                       else
+                               printf("%s", legacy_output(ret));
+               }
+       } else {
+               if (json_output)
+                       printf("%.J\n", backend[cmd]);
+               else
+                       printf("%s\n", legacy_output(backend[cmd]));
+       }
+
+       return 0;
+%}