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>
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
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
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
--- /dev/null
+{%
+// 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;
+%}
+++ /dev/null
-#!/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
-}
+++ /dev/null
-#!/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
--- /dev/null
+{%
+// 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;
+%}
+++ /dev/null
-#!/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
--- /dev/null
+{%
+// 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;
+%}
--- /dev/null
+{%
+// 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;
+ }
+};
+%}
-#!/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 ...
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;
+%}