uvol: replace with re-write in ucode

Replace previous Shell draft-quality implementation of uvol with a
rewrite in ucode[1].
While the new code is slightly larger, it performs much better (as
we no longer fork() for parsing strings like in Shell with grep, sed
and friends).

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

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

[1]: https://github.com/jow-/ucode
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2022-03-31 17:51:22 +01:00
parent 73b304bf0c
commit 6350c7bc63
No known key found for this signature in database
GPG Key ID: 5A8F39C31C3217CA
9 changed files with 1222 additions and 934 deletions

View File

@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=uvol
PKG_VERSION:=0.6
PKG_VERSION:=0.7
PKG_RELEASE:=$(AUTORELEASE)
PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
@ -19,8 +19,7 @@ define Package/autopart
endef
define Package/autopart/description
Automatically allocate the GPT partition for LVM and initialize it
on first boot.
Automatically allocate and initialize a partition for LVM on first boot.
endef
define Package/uvol
@ -28,18 +27,22 @@ define Package/uvol
CATEGORY:=Utilities
SUBMENU:=Disc
TITLE:=OpenWrt UBI/LVM volume abstraction
DEPENDS:=+blockd
DEPENDS:=+blockd +ucode +ucode-mod-fs +ucode-mod-uci
PKGARCH=all
endef
define Package/uvol/description
'uvol' is tool to automate storage volume handling on embedded
devices in a generic way.
Depending on what is available, 'uvol' will use either UBI or LVM2
as storage backends and transparently offer identical operations on
top of them.
Also install the 'autopart' package to easily make use of 'uvol' on
block-storage based devices.
Examples:
uvol create example_volume_1 256MiB rw
uvol create example_volume_1 268435456 rw
uvol up example_volume_1
uvol device example_volume_1
@ -64,11 +67,12 @@ define Package/autopart/install
endef
define Package/uvol/install
$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/libexec/uvol $(1)/usr/sbin $(1)/lib/functions $(1)/etc/uci-defaults
$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/uvol/backends $(1)/usr/sbin $(1)/etc/uci-defaults
$(INSTALL_BIN) ./files/uvol.init $(1)/etc/init.d/uvol
$(INSTALL_BIN) ./files/common.sh $(1)/lib/functions/uvol.sh
$(INSTALL_BIN) ./files/ubi.sh $(1)/usr/libexec/uvol/20-ubi.sh
$(INSTALL_BIN) ./files/lvm.sh $(1)/usr/libexec/uvol/50-lvm.sh
$(INSTALL_DATA) ./files/blockdev_common.uc $(1)/usr/lib/uvol/
$(INSTALL_DATA) ./files/uci.uc $(1)/usr/lib/uvol/
$(INSTALL_DATA) ./files/lvm.uc $(1)/usr/lib/uvol/backends/
$(INSTALL_DATA) ./files/ubi.uc $(1)/usr/lib/uvol/backends/
$(INSTALL_BIN) ./files/uvol $(1)/usr/sbin
$(INSTALL_BIN) ./files/uvol.defaults $(1)/etc/uci-defaults/90-uvol-init
endef

View File

@ -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;
%}

View File

@ -1,83 +0,0 @@
#!/bin/sh
UCI_SPOOLDIR="/var/spool/uvol"
_uvol_init_spooldir() {
[ ! -d "$(dirname "$UCI_SPOOLDIR")" ] && mkdir -p "$(dirname "$UCI_SPOOLDIR")"
mkdir -m 0700 -p "$UCI_SPOOLDIR"
}
uvol_uci_add() {
local volname="$1"
local devname="$2"
local mode="$3"
local autofs=0
local target="/tmp/run/uvol/$volname"
local uuid uciname
[ "$mode" = "ro" ] && autofs=1
uciname="${volname//[-.]/_}"
uciname="${uciname//[!([:alnum:]_)]}"
uuid="$(/sbin/block info | grep "^$2" | xargs -n 1 echo | grep "^UUID=.*")"
[ "$uuid" ] || return 22
uuid="${uuid:5}"
case "$uciname" in
"_meta")
target="/tmp/run/uvol/.meta"
;;
"_"*)
return 1
;;
esac
_uvol_init_spooldir
if [ -e "${UCI_SPOOLDIR}/remove-$1" ]; then
rm "${UCI_SPOOLDIR}/remove-$1"
fi
cat >"${UCI_SPOOLDIR}/add-$1" <<EOF
set fstab.$uciname=mount
set fstab.$uciname.uuid=$uuid
set fstab.$uciname.target=$target
set fstab.$uciname.options=$mode
set fstab.$uciname.autofs=$autofs
set fstab.$uciname.enabled=1
EOF
}
uvol_uci_remove() {
local volname="$1"
local uciname
uciname="${volname//[-.]/_}"
uciname="${uciname//[!([:alnum:]_)]}"
if [ -e "${UCI_SPOOLDIR}/add-$1" ]; then
rm "${UCI_SPOOLDIR}/add-$1"
return
fi
_uvol_init_spooldir
cat >"${UCI_SPOOLDIR}/remove-$1" <<EOF
delete fstab.$uciname
EOF
}
uvol_uci_commit() {
local volname="$1"
local ucibatch
for ucibatch in "${UCI_SPOOLDIR}/"*"-$volname"${volname+*} ; do
[ -e "$ucibatch" ] || break
uci batch < "$ucibatch"
[ $? -eq 0 ] && rm "$ucibatch"
done
uci commit fstab
return $?
}
uvol_uci_init() {
uci -q get fstab.@uvol[0] && return
uci add fstab uvol
uci set fstab.@uvol[-1].initialized=1
}

View File

@ -1,460 +0,0 @@
#!/bin/sh
cmd="$1"
shift
if [ "$cmd" = "name" ]; then
echo "LVM"
return 0
fi
command -v lvm >/dev/null || return 1
. /lib/functions.sh
. /lib/functions/uvol.sh
. /lib/upgrade/common.sh
. /usr/share/libubox/jshn.sh
export_bootdevice
[ "$BOOTDEV_MAJOR" ] || return 1
export_partdevice rootdev 0
[ "$rootdev" ] || return 1
case "$rootdev" in
mtd*|\
ram*|\
ubi*)
return 1
esac
lvm_cmd() {
local cmd="$1"
shift
LVM_SUPPRESS_FD_WARNINGS=1 lvm "$cmd" "$@"
return $?
}
pvs() {
lvm_cmd pvs --reportformat json --units b "$@"
}
vgs() {
lvm_cmd vgs --reportformat json --units b "$@"
}
lvs() {
lvm_cmd lvs --reportformat json --units b "$@"
}
freebytes() {
echo $((vg_free_count * vg_extent_size))
}
totalbytes() {
echo $((vg_extent_count * vg_extent_size))
}
existvol() {
[ "$1" ] || return 1
test -e "/dev/$vg_name/ro_$1" || test -e "/dev/$vg_name/rw_$1"
return $?
}
vg_name=
exportpv() {
vg_name=
config_load fstab
local uvolsect="$(config_foreach echo uvol)"
[ -n "$uvolsect" ] && config_get vg_name "$uvolsect" vg_name
[ -n "$vg_name" ] && return
local reports rep pv pvs
json_init
json_load "$(pvs -o vg_name -S "pv_name=~^/dev/$rootdev.*\$")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select pv
json_get_keys pvs
for pv in $pvs; do
json_select "$pv"
json_get_vars vg_name
json_select ..
break
done
json_select ..
break
done
}
vg_extent_size=
vg_extent_count=
vg_free_count=
exportvg() {
local reports rep vg vgs
vg_extent_size=
vg_extent_count=
vg_free_count=
json_init
json_load "$(vgs -o vg_extent_size,vg_extent_count,vg_free_count -S "vg_name=$vg_name")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select vg
json_get_keys vgs
for vg in $vgs; do
json_select "$vg"
json_get_vars vg_extent_size vg_extent_count vg_free_count
vg_extent_size=${vg_extent_size%B}
json_select ..
break
done
json_select ..
break
done
}
lv_active=
lv_name=
lv_full_name=
lv_path=
lv_dm_path=
lv_size=
exportlv() {
local reports rep lv lvs
lv_active=
lv_name=
lv_full_name=
lv_path=
lv_dm_path=
lv_size=
json_init
json_load "$(lvs -o lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path -S "lv_name=~^[rw][owp]_$1\$ && vg_name=$vg_name")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select lv
json_get_keys lvs
for lv in $lvs; do
json_select "$lv"
json_get_vars lv_active lv_name lv_full_name lv_size lv_path lv_dm_path
lv_size=${lv_size%B}
json_select ..
break
done
json_select ..
break
done
}
getdev() {
local dms dm_name
for dms in /sys/devices/virtual/block/dm-* ; do
[ "$dms" = "/sys/devices/virtual/block/dm-*" ] && break
read -r dm_name < "$dms/dm/name"
[ "$(basename "$lv_dm_path")" = "$dm_name" ] && basename "$dms"
done
}
getuserdev() {
local dms dm_name
existvol "$1" || return 1
exportlv "$1"
getdev "$@"
}
getsize() {
exportlv "$1"
[ "$lv_size" ] && echo "$lv_size"
}
activatevol() {
exportlv "$1"
[ "$lv_path" ] || return 2
case "$lv_path" in
/dev/*/wo_*|\
/dev/*/wp_*)
return 22
;;
*)
uvol_uci_commit "$1"
[ "$lv_active" = "active" ] && return 0
lvm_cmd lvchange -k n "$lv_full_name" || return $?
lvm_cmd lvchange -a y "$lv_full_name" || return $?
return 0
;;
esac
}
disactivatevol() {
exportlv "$1"
local devname
[ "$lv_path" ] || return 2
case "$lv_path" in
/dev/*/wo_*|\
/dev/*/wp_*)
return 22
;;
*)
[ "$lv_active" = "active" ] || return 0
devname="$(getdev "$1")"
[ "$devname" ] && umount "/dev/$devname"
lvm_cmd lvchange -a n "$lv_full_name"
lvm_cmd lvchange -k y "$lv_full_name" || return $?
return 0
;;
esac
}
getstatus() {
exportlv "$1"
[ "$lv_full_name" ] || return 2
existvol "$1" || return 1
return 0
}
createvol() {
local mode lvmode ret
local volsize=$(($2))
[ "$volsize" ] || return 22
exportlv "$1"
[ "$lv_size" ] && return 17
size_ext=$((volsize / vg_extent_size))
[ $((size_ext * vg_extent_size)) -lt $volsize ] && size_ext=$((size_ext + 1))
case "$3" in
ro|wo)
lvmode=r
mode=wo
;;
rw)
lvmode=rw
mode=wp
;;
*)
return 22
;;
esac
lvm_cmd lvcreate -p "$lvmode" -a n -y -W n -Z n -n "${mode}_$1" -l "$size_ext" "$vg_name" || return $?
ret=$?
if [ ! $ret -eq 0 ] || [ "$lvmode" = "r" ]; then
return $ret
fi
exportlv "$1"
[ "$lv_full_name" ] || return 22
lvm_cmd lvchange -a y "$lv_full_name" || return $?
if [ "$lv_size" -gt $(( 100 * 1024 * 1024 )) ]; then
mkfs.f2fs -f -l "$1" "$lv_path"
ret=$?
[ $ret != 0 ] && [ $ret != 134 ] && {
lvm_cmd lvchange -a n "$lv_full_name" || return $?
return $ret
}
else
mke2fs -F -L "$1" "$lv_path" || {
ret=$?
lvm_cmd lvchange -a n "$lv_full_name" || return $?
return $ret
}
fi
uvol_uci_add "$1" "/dev/$(getdev "$1")" "rw"
lvm_cmd lvchange -a n "$lv_full_name" || return $?
lvm_cmd lvrename "$vg_name" "wp_$1" "rw_$1" || return $?
return 0
}
removevol() {
exportlv "$1"
[ "$lv_full_name" ] || return 2
[ "$lv_active" = "active" ] && return 16
lvm_cmd lvremove -y "$lv_full_name" || return $?
uvol_uci_remove "$1"
uvol_uci_commit "$1"
}
updatevol() {
exportlv "$1"
[ "$lv_full_name" ] || return 2
[ "$lv_size" -ge "$2" ] || return 27
case "$lv_path" in
/dev/*/wo_*)
lvm_cmd lvchange -p rw "$lv_full_name" || return $?
lvm_cmd lvchange -a y "$lv_full_name" || return $?
dd of="$lv_path"
uvol_uci_add "$1" "/dev/$(getdev "$1")" "ro"
lvm_cmd lvchange -a n "$lv_full_name" || return $?
lvm_cmd lvchange -p r "$lv_full_name" || return $?
lvm_cmd lvrename "$lv_full_name" "${lv_full_name%%/*}/ro_$1" || return $?
return 0
;;
default)
return 22
;;
esac
}
listvols() {
local reports rep lv lvs lv_name lv_size lv_mode volname json_output json_notfirst
if [ "$1" = "-j" ]; then
json_output=1
echo "["
shift
fi
volname=${1:-.*}
json_init
json_load "$(lvs -o lv_name,lv_size -S "lv_name=~^[rw][owp]_$volname\$ && vg_name=$vg_name")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select lv
json_get_keys lvs
for lv in $lvs; do
json_select "$lv"
json_get_vars lv_name lv_size
lv_mode="${lv_name:0:2}"
lv_name="${lv_name:3}"
lv_size=${lv_size%B}
if [ "${lv_name:0:1}" != "." ]; then
if [ "$json_output" = "1" ]; then
[ "$json_notfirst" = "1" ] && echo ","
echo -e "\t{"
echo -e "\t\t\"name\": \"$lv_name\","
echo -e "\t\t\"mode\": \"$lv_mode\","
echo -e "\t\t\"size\": $lv_size"
echo -n -e "\t}"
json_notfirst=1
else
echo "$lv_name $lv_mode $lv_size"
fi
fi
json_select ..
done
json_select ..
break
done
if [ "$json_output" = "1" ]; then
[ "$json_notfirst" = "1" ] && echo
echo "]"
fi
}
detect() {
local reports rep lv lvs lv_name lv_full_name lv_mode volname devname
local temp_up=""
json_init
json_load "$(lvs -o lv_full_name -S "lv_name=~^[rw][owp]_.*\$ && vg_name=$vg_name && lv_skip_activation!=0")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select lv
json_get_keys lvs
for lv in $lvs; do
json_select "$lv"
json_get_vars lv_full_name
echo "lvchange -a y $lv_full_name"
lvm_cmd lvchange -k n "$lv_full_name"
lvm_cmd lvchange -a y "$lv_full_name"
temp_up="$temp_up $lv_full_name"
json_select ..
done
json_select ..
break
done
sleep 1
uvol_uci_init
json_init
json_load "$(lvs -o lv_name,lv_dm_path -S "lv_name=~^[rw][owp]_.*\$ && vg_name=$vg_name")"
json_select report
json_get_keys reports
for rep in $reports; do
json_select "$rep"
json_select lv
json_get_keys lvs
for lv in $lvs; do
json_select "$lv"
json_get_vars lv_name lv_dm_path
lv_mode="${lv_name:0:2}"
lv_name="${lv_name:3}"
echo uvol_uci_add "$lv_name" "/dev/$(getdev "$lv_name")" "$lv_mode"
uvol_uci_add "$lv_name" "/dev/$(getdev "$lv_name")" "$lv_mode"
json_select ..
done
json_select ..
break
done
uvol_uci_commit
for lv_full_name in $temp_up; do
echo "lvchange -a n $lv_full_name"
lvm_cmd lvchange -a n "$lv_full_name"
lvm_cmd lvchange -k y "$lv_full_name"
done
}
boot() {
true ; # nothing to do, lvm does it all for us
}
exportpv
exportvg
case "$cmd" in
align)
echo "$vg_extent_size"
;;
free)
freebytes
;;
total)
totalbytes
;;
detect)
detect
;;
boot)
boot
;;
list)
listvols "$@"
;;
create)
createvol "$@"
;;
remove)
removevol "$@"
;;
device)
getuserdev "$@"
;;
size)
getsize "$@"
;;
up)
activatevol "$@"
;;
down)
disactivatevol "$@"
;;
status)
getstatus "$@"
;;
write)
updatevol "$@"
;;
*)
echo "unknown command"
return 1
;;
esac

467
utils/uvol/files/lvm.uc Normal file
View File

@ -0,0 +1,467 @@
{%
// SPDX-License-Identifier: GPL-2.0-or-later
// LVM2 backend for uvol
// (c) 2022 Daniel Golle <daniel@makrotopia.org>
//
// This plugin uses LVM2 as a storage backend for uvol.
//
// By default, volumes are allocated on the physical device used for booting,
// the LVM2 PV and VG are initialized auto-magically by the 'autopart' script.
// By setting the UCI option 'vg_name' in the 'uvol' section in /etc/config/fstab
// you may set an arbitrary LVM2 volume group to back uvol instad.
let lvm_exec = "/sbin/lvm";
function lvm(cmd, ...args) {
let lvm_json_cmds = [ "lvs", "pvs", "vgs" ];
try {
let json_param = "";
if (cmd in lvm_json_cmds)
json_param = "--reportformat json --units b ";
let stdout = fs.popen(sprintf("LVM_SUPPRESS_FD_WARNINGS=1 %s %s %s%s", lvm_exec, cmd, json_param, join(" ", args)));
let tmp;
if (stdout) {
tmp = stdout.read("all");
let ret = {};
ret.retval = stdout.close();
if (json_param) {
let data = json(tmp);
if (data.report)
ret.report = data.report[0];
} else {
ret.stdout = trim(tmp);
}
return ret;
} else {
printf("lvm cli command failed: %s\n", fs.error());
}
} catch(e) {
printf("Failed to parse lvm cli output: %s\n%s\n", e, e.stacktrace[0].context);
}
return null;
}
function pvs() {
let fstab = cursor.get_all('fstab');
for (let k, section in fstab) {
if (section['.type'] != 'uvol' || !section.vg_name)
continue;
return section.vg_name;
}
include("/usr/lib/uvol/blockdev_common.uc");
let rootdev = blockdev_common.get_partition(blockdev_common.get_bootdev(), 0);
let tmp = lvm("pvs", "-o", "vg_name", "-S", sprintf("\"pv_name=~^/dev/%s.*\$\"", rootdev));
if (tmp.report.pv)
return tmp.report.pv[0].vg_name;
else
return null;
}
function vgs(vg_name) {
let tmp = lvm("vgs", "-o", "vg_extent_size,vg_extent_count,vg_free_count", "-S", sprintf("\"vg_name=%s\"", vg_name));
let ret = null;
if (tmp && tmp.report.vg) {
ret = tmp.report.vg;
for (let r in ret) {
r.vg_extent_size = +(rtrim(r.vg_extent_size, "B"));
r.vg_extent_count = +r.vg_extent_count;
r.vg_free_count = +r.vg_free_count;
}
}
if (ret)
return ret[0];
else
return null;
}
function lvs(vg_name, vol_name, extra_exp) {
let ret = [];
if (!vol_name)
vol_name = ".*";
let lvexpr = sprintf("\"lvname=~^[rw][owp]_%s\$ && vg_name=%s%s%s\"",
vol_name, vg_name, extra_exp?" && ":"", extra_exp?extra_exp:"");
let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path", "-S", lvexpr);
if (tmp && tmp.report.lv) {
ret = tmp.report.lv;
for (let r in ret) {
r.lv_size = +(rtrim(r.lv_size, "B"));
r.lv_active = (r.lv_active == "active");
}
}
return ret;
}
function getdev(lv) {
if (!lv)
return null;
for (let dms in fs.glob("/sys/devices/virtual/block/dm-*")) {
let f = fs.open(sprintf("%s/dm/name", dms), "r");
if (!f)
continue;
let dm_name = trim(f.read("all"));
f.close();
if ( split(lv.lv_dm_path, '/')[-1] == dm_name )
return split(dms, '/')[-1]
}
return null;
}
function lvm_init(ctx) {
cursor = ctx.cursor;
fs = ctx.fs;
if (!fs.access(lvm_exec, "x"))
return false;
vg_name = pvs();
if (!vg_name)
return false;
vg = vgs(vg_name);
uvol_uci_add = ctx.uci_add;
uvol_uci_commit = ctx.uci_commit;
uvol_uci_remove = ctx.uci_remove;
uvol_uci_init = ctx.uci_init;
return true;
}
function lvm_free() {
if (!vg || !vg.vg_free_count || !vg.vg_extent_size)
return 2;
return sprintf("%d", vg.vg_free_count * vg.vg_extent_size);
}
function lvm_total() {
if (!vg || !vg.vg_extent_count || !vg.vg_extent_size)
return 2;
return sprintf("%d", vg.vg_extent_count * vg.vg_extent_size);
}
function lvm_align() {
if (!vg || !vg.vg_extent_size)
return 2;
return sprintf("%d", vg.vg_extent_size);
}
function lvm_list(vol_name) {
let vols = [];
if (!vg_name)
return vols;
let res = lvs(vg_name, vol_name);
for (let lv in res) {
let vol = {};
if (substr(lv.lv_name, 3, 1) == ".")
continue;
vol.name = substr(lv.lv_name, 3);
vol.mode = substr(lv.lv_name, 0, 2);
if (!lv.lv_active) {
if (vol.mode == "ro")
vol.mode = "rd";
if (vol.mode == "rw")
vol.mode = "wd";
}
vol.size = lv.lv_size;
push(vols, vol);
}
return vols;
}
function lvm_size(vol_name) {
if (!vol_name || !vg_name)
return 2;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
return sprintf("%d", res[0].lv_size);
}
function lvm_status(vol_name) {
if (!vol_name || !vg_name)
return 22;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
let mode = substr(res[0].lv_name, 0, 2);
if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
return 1;
return 0;
}
function lvm_device(vol_name) {
if (!vol_name || !vg_name)
return 22;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
let mode = substr(res[0].lv_name, 0, 2);
if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
return 22;
return getdev(res[0]);
}
function lvm_updown(vol_name, up) {
if (!vol_name || !vg_name)
return 22;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
let lv = res[0];
if (!lv.lv_path)
return 2;
if (up && (wildcard(lv.lv_path, "/dev/*/wo_*") ||
wildcard(lv.lv_path, "/dev/*/wp_*")))
return 22;
if (up)
uvol_uci_commit(vol_name);
if (lv.lv_active == up)
return 0;
if (!up) {
let devname = getdev(lv);
if (devname)
system(sprintf("umount /dev/%s", devname));
}
let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name);
if (up && lvchange_r.retval != 0)
return lvchange_r.retval;
lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
if (lvchange_r.retval != 0)
return lvchange_r.retval;
return 0
}
function lvm_up(vol_name) {
return lvm_updown(vol_name, true);
}
function lvm_down(vol_name) {
return lvm_updown(vol_name, false);
}
function lvm_create(vol_name, vol_size, vol_mode) {
if (!vol_name || !vg_name)
return 22;
vol_size = +vol_size;
if (vol_size <= 0)
return 22;
let res = lvs(vg_name, vol_name);
if (res[0])
return 17;
let size_ext = vol_size / vg.vg_extent_size;
if (vol_size % vg.vg_extent_size)
++size_ext;
let lvmode, mode;
if (vol_mode == "ro" || vol_mode == "wo") {
lvmode = "r";
mode = "wo";
} else if (vol_mode == "rw") {
lvmode = "rw";
mode = "wp";
} else {
return 22;
}
let ret = lvm("lvcreate", "-p", lvmode, "-a", "n", "-y", "-W", "n", "-Z", "n", "-n", sprintf("%s_%s", mode, vol_name), "-l", size_ext, vg_name);
if (ret.retval != 0 || lvmode == "r")
return ret.retval;
let lv = lvs(vg_name, vol_name);
if (!lv[0] || !lv[0].lv_full_name)
return 22;
lv = lv[0];
let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
let use_f2fs = (lv.lv_size > (100 * 1024 * 1024));
if (use_f2fs) {
let mkfs_ret = system(sprintf("/usr/sbin/mkfs.f2fs -f -l \"%s\" \"%s\"", vol_name, lv.lv_path));
if (mkfs_ret != 0 && mkfs_ret != 134) {
lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (lvchange_r.retval != 0)
return lvchange_r.retval;
return mkfs_ret;
}
} else {
let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -L \"%s\" \"%s\"", vol_name, lv.lv_path));
if (mkfs_ret != 0) {
lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (lvchange_r.retval != 0)
return lvchange_r.retval;
return mkfs_ret;
}
}
uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "rw");
ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
ret = lvm("lvrename", vg_name, sprintf("wp_%s", vol_name), sprintf("rw_%s", vol_name));
if (ret.retval != 0)
return ret.retval;
return 0;
}
function lvm_remove(vol_name) {
if (!vol_name || !vg_name)
return 22;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
if (res[0].lv_active)
return 16;
let ret = lvm("lvremove", "-y", res[0].lv_full_name);
if (ret.retval != 0)
return ret.retval;
uvol_uci_remove(vol_name);
uvol_uci_commit(vol_name);
return 0;
}
function lvm_dd(in_fd, out_fd, vol_size) {
let rem = vol_size;
let buf;
while ((buf = in_fd.read(vg.vg_extent_size)) && (rem > 0)) {
rem -= length(buf);
if (rem < 0) {
buf = substr(buf, 0, rem);
}
out_fd.write(buf);
}
return rem;
}
function lvm_write(vol_name, vol_size) {
if (!vol_name || !vg_name)
return 22;
let lv = lvs(vg_name, vol_name);
if (!lv[0] || !lv[0].lv_full_name)
return 2;
lv = lv[0];
vol_size = +vol_size;
if (vol_size > lv.lv_size)
return 27;
if (wildcard(lv.lv_path, "/dev/*/wo_*")) {
let ret = lvm("lvchange", "-p", "rw", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
let volfile = fs.open(lv.lv_path, "w");
let ret = lvm_dd(fs.stdin, volfile, vol_size);
volfile.close();
if (ret < 0) {
printf("more %d bytes data than given size!\n", -ret);
}
if (ret > 0) {
printf("reading finished %d bytes before given size!\n", ret);
}
uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "ro");
let ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
let ret = lvm("lvchange", "-p", "r", lv.lv_full_name);
if (ret.retval != 0)
return ret.retval;
let ret = lvm("lvrename", vg_name, sprintf("wo_%s", vol_name), sprintf("ro_%s", vol_name));
if (ret.retval != 0)
return ret.retval;
} else {
return 22;
}
return 0;
}
function lvm_detect() {
let temp_up = [];
let inactive_lv = lvs(vg_name, null, "lv_skip_activation!=0");
for (let lv in inactive_lv) {
lvm("lvchange", "-k", "n", lv.lv_full_name);
lvm("lvchange", "-a", "y", lv.lv_full_name);
push(temp_up, lv.lv_full_name);
}
sleep(1000);
uvol_uci_init();
for (let lv in lvs(vg_name)) {
let vol_name = substr(lv.lv_name, 3);
let vol_mode = substr(lv.lv_name, 0, 2);
uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), vol_mode);
}
uvol_uci_commit();
for (let lv_full_name in temp_up) {
lvm("lvchange", "-a", "n", lv_full_name);
lvm("lvchange", "-k", "y", lv_full_name);
}
return 0;
}
function lvm_boot() {
return 0;
}
backend.backend = "LVM";
backend.priority = 50;
backend.init = lvm_init;
backend.boot = lvm_boot;
backend.detect = lvm_detect;
backend.free = lvm_free;
backend.align = lvm_align;
backend.total = lvm_total;
backend.list = lvm_list;
backend.size = lvm_size;
backend.status = lvm_status;
backend.device = lvm_device;
backend.up = lvm_up;
backend.down = lvm_down;
backend.create = lvm_create;
backend.remove = lvm_remove;
backend.write = lvm_write;
%}

View File

@ -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

378
utils/uvol/files/ubi.uc Normal file
View File

@ -0,0 +1,378 @@
{%
// SPDX-License-Identifier: GPL-2.0-or-later
// UBI backend for uvol
// (c) 2022 Daniel Golle <daniel@makrotopia.org>
//
// This plugin uses UBI on NAND flash as a storage backend for uvol.
function read_file(file) {
let fp = fs.open(file);
if (!fp)
return null;
let var = rtrim(fp.read("all"));
fp.close();
return var;
}
function mkdtemp() {
math = require("math");
let r1 = math.rand();
let r2 = math.rand();
let randbytes = chr((r1 >> 24) & 0xff, (r1 >> 16) & 0xff, (r1 >> 8) & 0xff, r1 & 0xff,
(r2 >> 24) & 0xff, (r2 >> 16) & 0xff, (r2 >> 8) & 0xff, r2 & 0xff);
let randstr = replace(b64enc(randbytes), /[\/-_.=]/g, "");
let dirname = sprintf("/tmp/uvol-%s", randstr);
fs.mkdir(dirname, 0700);
return dirname;
}
function ubi_get_dev(vol_name) {
let wcstring = sprintf("uvol-[rw][owpd]-%s", vol_name);
for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (wildcard(vol_ubiname, wcstring))
return fs.basename(vol_dir);
}
return null;
}
function vol_get_mode(vol_dev, mode) {
let vol_name = read_file(sprintf("/sys/devices/virtual/ubi/%s/%s/name", ubidev, vol_dev));
return substr(vol_name, 5, 2);
}
function mkubifs(vol_dev) {
let temp_mp = mkdtemp();
system(sprintf("mount -t ubifs /dev/%s %s", vol_dev, temp_mp));
system(sprintf("umount %s", temp_mp));
fs.rmdir(temp_mp);
return 0;
}
function block_hotplug(action, devname) {
return system(sprintf("ACTION=%s DEVNAME=%s /sbin/block hotplug", action, devname));
}
function ubi_init(ctx) {
cursor = ctx.cursor;
fs = ctx.fs;
let ubiver = read_file("/sys/class/ubi/version");
if (ubiver != 1)
return false;
let ubidevpath = null;
for (ubidevpath in fs.glob("/sys/devices/virtual/ubi/*"))
break;
if (!ubidevpath)
return false;
ubidev = fs.basename(ubidevpath);
ebsize = read_file(sprintf("%s/eraseblock_size", ubidevpath));
uvol_uci_add = ctx.uci_add;
uvol_uci_commit = ctx.uci_commit;
uvol_uci_remove = ctx.uci_remove;
uvol_uci_init = ctx.uci_init;
return true;
}
function ubi_free() {
let availeb = read_file(sprintf("/sys/devices/virtual/ubi/%s/avail_eraseblocks", ubidev));
return sprintf("%d", availeb * ebsize);
}
function ubi_align() {
return sprintf("%d", ebsize);
}
function ubi_total() {
let totaleb = read_file(sprintf("/sys/devices/virtual/ubi/%s/total_eraseblocks", ubidev));
return sprintf("%d", totaleb * ebsize);
}
function ubi_status(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode == "wo") return 22;
if (vol_mode == "wp") return 16;
if (vol_mode == "wd") return 1;
if (vol_mode == "ro" &&
!fs.access(sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "r")) return 1;
return 0;
}
function ubi_size(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_size = read_file(sprintf("/sys/devices/virtual/ubi/%s/%s/data_bytes", ubidev, vol_dev));
return sprintf("%d", vol_size);
}
function ubi_device(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode == "ro")
return sprintf("/dev/ubiblock%s", substr(vol_dev, 3));
else if (vol_mode == "rw")
return sprintf("/dev/%s", vol_dev);
return null;
}
function ubi_create(vol_name, vol_size, vol_mode) {
let vol_dev = ubi_get_dev(vol_name);
if (vol_dev)
return 17;
let mode;
if (vol_mode == "ro" || vol_mode == "wo")
mode = "wo";
else if (vol_mode == "rw")
mode = "wp";
else
return 22;
let vol_size = +vol_size;
if (vol_size <= 0)
return 22;
let ret = system(sprintf("ubimkvol /dev/%s -N \"uvol-%s-%s\" -s %d", ubidev, mode, vol_name, vol_size));
if (ret != 0)
return ret;
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let ret = system(sprintf("ubiupdatevol -t /dev/%s", vol_dev));
if (ret != 0)
return ret;
if (mode != "wp")
return 0;
let ret = mkubifs(vol_dev);
if (ret != 0)
return ret;
uvol_uci_add(vol_name, sprintf("/dev/%s", vol_dev), "rw");
let ret = system(sprintf("ubirename /dev/%s \"uvol-wp-%s\" \"uvol-wd-%s\"", ubidev, vol_name, vol_name));
if (ret != 0)
return ret;
return 0;
}
function ubi_remove(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode == "rw" || vol_mode == "ro")
return 16;
let volnum = split(vol_dev, "_")[1];
let ret = system(sprintf("ubirmvol /dev/%s -n %d", ubidev, volnum));
if (ret != 0)
return ret;
uvol_uci_remove(vol_name);
uvol_uci_commit(vol_name);
return 0;
}
function ubi_up(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode == "rw" || vol_mode == "ro")
return 0;
else if (vol_mode == "wo")
return 22;
else if (vol_mode == "wp")
return 16;
uvol_uci_commit(vol_name);
if (vol_mode == "rd") {
let ret = system(sprintf("ubirename /dev/%s \"uvol-rd-%s\" \"uvol-ro-%s\"", ubidev, vol_name, vol_name));
if (ret != 0)
return ret;
return system(sprintf("ubiblock --create /dev/%s", vol_dev));
} else if (vol_mode == "wd") {
let ret = system(sprintf("ubirename /dev/%s \"uvol-wd-%s\" \"uvol-rw-%s\"", ubidev, vol_name, vol_name));
if (ret != 0)
return ret;
return block_hotplug("add", vol_dev);
}
return 0;
}
function ubi_down(vol_name) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode == "rd" || vol_mode == "wd")
return 0;
else if (vol_mode == "wo")
return 22;
else if (vol_mode == "wp")
return 16;
else if (vol_mode == "ro") {
system(sprintf("umount /dev/ubiblock%s 2>&1 >/dev/null", substr(vol_dev, 3)));
system(sprintf("ubiblock --remove /dev/%s", vol_dev));
let ret = system(sprintf("ubirename /dev/%s \"uvol-ro-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
return ret;
} else if (vol_mode == "rw") {
system(sprintf("umount /dev/%s 2>&1 >/dev/null", vol_dev));
let ret = system(sprintf("ubirename /dev/%s \"uvol-rw-%s\" \"uvol-wd-%s\"", ubidev, vol_name, vol_name));
block_hotplug("remove", vol_dev);
return ret;
}
return 0;
}
function ubi_list(search_name) {
let volumes = [];
for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
let vol = {};
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-[rw][wod]-*"))
continue;
let vol_mode = substr(vol_ubiname, 5, 2);
let vol_name = substr(vol_ubiname, 8);
let vol_size = read_file(sprintf("%s/data_bytes", vol_dir));
if (substr(vol_name, 0, 1) == ".")
continue;
vol.name = vol_name;
vol.mode = vol_mode;
vol.size = vol_size;
push(volumes, vol);
}
return volumes;
}
function ubi_detect() {
let tmpdev = [];
for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-r[od]-*"))
continue;
let vol_name = substr(vol_ubiname, 8);
let vol_mode = substr(vol_ubiname, 5, 2);
let vol_dev = fs.basename(vol_dir);
ret = system(sprintf("ubiblock --create /dev/%s", vol_dev));
if (ret)
continue;
if (vol_mode == "rd")
push(tmpdev, vol_dev);
}
uvol_uci_init();
for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-[rw][wod]-*"))
continue;
let vol_dev = fs.basename(vol_dir);
let vol_name = substr(vol_ubiname, 8);
let vol_mode = substr(vol_ubiname, 5, 2);
if (vol_mode == "ro" || vol_mode == "rd")
uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro");
else if (vol_mode == "rw" || vol_mode == "wd")
uvol_uci_add(vol_name, sprintf("/dev/%s", vol_dev), "rw");
}
uvol_uci_commit();
for (vol_dev in tmpdev)
system(sprintf("ubiblock --remove /dev/%s", vol_dev));
return 0;
}
function ubi_boot() {
for (vol_dir in fs.glob(sprintf("/sys/devices/virtual/ubi/%s/%s_*", ubidev, ubidev))) {
let vol_dev = fs.basename(vol_dir);
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-ro-*"))
continue;
system(sprintf("ubiblock --create /dev/%s", vol_dev));
}
}
function ubi_write(vol_name, write_size) {
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
write_size = +write_size;
if (write_size <= 0)
return 22;
let vol_mode = vol_get_mode(vol_dev);
if (vol_mode != "wo")
return 22;
let ret = system(sprintf("ubiupdatevol -s %s /dev/%s -", write_size, vol_dev));
if (ret)
return ret;
system(sprintf("ubiblock --create /dev/%s", vol_dev));
uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro");
system(sprintf("ubiblock --remove /dev/%s", vol_dev));
system(sprintf("ubirename /dev/%s \"uvol-wo-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
return 0;
}
backend.backend = "UBI";
backend.priority = 20;
backend.init = ubi_init;
backend.boot = ubi_boot;
backend.detect = ubi_detect;
backend.free = ubi_free;
backend.align = ubi_align;
backend.total = ubi_total;
backend.list = ubi_list;
backend.size = ubi_size;
backend.status = ubi_status;
backend.device = ubi_device;
backend.up = ubi_up;
backend.down = ubi_down;
backend.create = ubi_create;
backend.remove = ubi_remove;
backend.write = ubi_write;
%}

139
utils/uvol/files/uci.uc Normal file
View File

@ -0,0 +1,139 @@
{%
// SPDX-License-Identifier: GPL-2.0-or-later
// UCI tools for uvol
// (c) 2022 Daniel Golle <daniel@makrotopia.org>
let uci_spooldir = "/var/spool/uvol";
let init_spooldir = function(void) {
parentdir = fs.stat(fs.dirname(uci_spooldir));
if (!parentdir || parentdir.type != "directory")
fs.mkdir(fs.dirname(uci_spooldir), 0755);
fs.mkdir(uci_spooldir, 0700);
};
uvol_uci = {
uvol_uci_add: function(vol_name, dev_name, mode) {
try {
let autofs = false;
let uuid;
let target;
if (mode == "ro")
autofs = true;
let uciname = replace(vol_name, /[-.]/g, "_");
uciname = replace(uciname, /!([:alnum:]_)/g, "");
let bdinfo_p = fs.popen("/sbin/block info");
let bdinfo_l;
while (bdinfo_l = bdinfo_p.read("line")) {
if (substr(bdinfo_l, 0, length(dev_name) + 1) != dev_name + ":")
continue;
let bdinfo_e = split(bdinfo_l, " ");
shift(bdinfo_e);
for (let bdinfo_a in bdinfo_e) {
let bdinfo_v = split(bdinfo_a, "=");
if (bdinfo_v[0] && bdinfo_v[0] == "UUID") {
uuid = trim(bdinfo_v[1], "\"");
break;
}
}
break;
}
if (!uuid)
return 22;
if (uciname == "_meta")
target = "/tmp/run/uvol/.meta";
else if (substr(uciname, 0, 1) == "_")
return 1;
else
target = sprintf("/tmp/run/uvol/%s", vol_name);
init_spooldir();
let remspool = sprintf("%s/remove-%s", uci_spooldir, uciname);
if (fs.stat(remspool))
fs.unlink(remspool);
let addobj = {};
addobj.name=uciname;
addobj.uuid=uuid;
addobj.target=target;
addobj.options=mode;
addobj.autofs=autofs;
addobj.enabled=true;
let spoolfile = fs.open(sprintf("%s/add-%s", uci_spooldir, uciname), "w");
spoolfile.write(addobj);
spoolfile.close();
} catch(e) {
printf("adding UCI section to spool failed");
return -1;
}
return 0;
},
uvol_uci_remove: function(vol_name) {
let uciname = replace(vol_name, /[-.]/g, "_");
uciname = replace(uciname, /!([:alnum:]_)/g, "");
let addspool = sprintf("%s/add-%s", uci_spooldir, uciname);
if (fs.stat(addspool)) {
fs.unlink(addspool);
return 0;
}
init_spooldir();
let spoolfile = fs.open(sprintf("%s/remove-%s", uci_spooldir, uciname), "w");
spoolfile.write(uciname);
spoolfile.close();
return 0;
},
uvol_uci_commit: function(vol_name) {
try {
let uciname = null;
if (vol_name) {
uciname = replace(vol_name, /[-.]/g, "_");
uciname = replace(uciname, /!([:alnum:]_)/g, "");
}
for (let file in fs.glob(sprintf("%s/*-%s", uci_spooldir, uciname?uciname:"*"))) {
let action = split(fs.basename(file), "-")[0];
let spoolfd = fs.open(file, "r");
let spoolstr = spoolfd.read("all");
spoolfd.close();
fs.unlink(file);
if (action == "remove") {
cursor.delete("fstab", spoolstr);
} else if (action == "add") {
let spoolobj = json(spoolstr);
cursor.set("fstab", spoolobj.name, "mount");
for (key in keys(spoolobj)) {
if (key == "name")
continue;
cursor.set("fstab", spoolobj.name, key, spoolobj[key]);
}
}
}
cursor.commit();
} catch(e) {
printf("committing UCI spool failed");
return -1;
}
return 0;
},
uvol_uci_init: function () {
cursor.load("fstab");
let f = cursor.get("fstab", "@uvol[0]", "initialized");
if (f == 1)
return 0;
cursor.add("fstab", "uvol");
cursor.set("fstab", "@uvol[-1]", "initialized", true);
cursor.commit();
cursor.unload("fstab");
return 0;
}
};
%}

View File

@ -1,12 +1,11 @@
#!/bin/sh
#!/usr/bin/ucode
{%
// SPDX-License-Identifier: GPL-2.0-or-later
// uvol - storage volume manager for OpenWrt
// (c) 2022 Daniel Golle <daniel@makrotopia.org>
# uvol prototype
# future development roadmap (aka. to-do):
# * re-implement in C (use libubox, execve lvm/ubi*)
# * hash to validate volume while writing
# * add atomic batch processing for use by container/package manager
if [ -z "$1" ]; then cat <<EOF
function help() {
%}
uvol storage volume manager
syntax: uvol command ...
@ -30,23 +29,102 @@ commands:
1 - volume is not ready for use
2 - volume doesn'y exist
write volname size write to volume from stdin, size in bytes
EOF
return 22
fi
{%
}
uvol_backend=
backends_tried=
let fs = require("fs");
let uci = require("uci");
let cursor = uci ? uci.cursor() : null;
for backend in /usr/libexec/uvol/*.sh; do
total=$($backend total)
backends_tried="$backends_tried $($backend name)"
[ "$total" ] && uvol_backend=$backend
done
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;
if [ -z "$uvol_backend" ]; then
echo "No backend available. (tried:$backends_tried)"
echo "To setup devices with block storage install 'autopart'."
return 2
fi
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;
flock -x /tmp/run/uvol.lock "$uvol_backend" "$@"
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;
%}