diff --git a/utils/uvol/Makefile b/utils/uvol/Makefile index 6583e6e75..21d46fcff 100644 --- a/utils/uvol/Makefile +++ b/utils/uvol/Makefile @@ -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 @@ -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 index 000000000..f45e573d2 --- /dev/null +++ b/utils/uvol/files/blockdev_common.uc @@ -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 index eee486c53..000000000 --- a/utils/uvol/files/common.sh +++ /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" <"${UCI_SPOOLDIR}/remove-$1" </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 index 000000000..5be27e5c2 --- /dev/null +++ b/utils/uvol/files/lvm.uc @@ -0,0 +1,467 @@ +{% +// SPDX-License-Identifier: GPL-2.0-or-later +// LVM2 backend for uvol +// (c) 2022 Daniel Golle +// +// 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 index 7637fba68..000000000 --- a/utils/uvol/files/ubi.sh +++ /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 index 000000000..6f1cfbcd7 --- /dev/null +++ b/utils/uvol/files/ubi.uc @@ -0,0 +1,378 @@ +{% +// SPDX-License-Identifier: GPL-2.0-or-later +// UBI backend for uvol +// (c) 2022 Daniel Golle +// +// 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 index 000000000..24589ca4c --- /dev/null +++ b/utils/uvol/files/uci.uc @@ -0,0 +1,139 @@ +{% +// SPDX-License-Identifier: GPL-2.0-or-later +// UCI tools for uvol +// (c) 2022 Daniel Golle + +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; + } +}; +%} diff --git a/utils/uvol/files/uvol b/utils/uvol/files/uvol index 4ecd2e165..f89b96687 100644 --- a/utils/uvol/files/uvol +++ b/utils/uvol/files/uvol @@ -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 -# 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 < 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; +%}