uvol: bump version to 1.1

manage volume state over ubus instead of the rootfs

Rework the volume lifecycle around blockd's ubus API, so volume state
lives entirely on the self-describing LVM/UBI backing store and never
leaks into the firmware rootfs.

- register active volumes with blockd over ubus (mount.uc) instead of
  spooling mounts into /etc/config/fstab (uci.uc)

- bootstrap .meta from 'uvol boot' and drop the broken 90-uvol-init
  uci-default

- run 'uvol boot' straight from the mount.ready trigger, and query
  'ubus call block status' at service start to cover storage already
  being ready

- defer removal of a volume whose backing device is still held, and
  reap it from the mount.umount trigger once it is free

- enforce content-addressed "<algo>-<hexdigest>" volumes in the
  dispatcher: reuse an existing volume of the same digest, verify
  every write

- verify a content-addressed write in place while the volume is still
  incomplete, reporting EBADMSG on mismatch instead of flipping
  volume state

- purge incomplete wo/wp leftovers on boot and reclaim an exactly
  matching one on create

- serialise mutating commands with a device-wide lock, plus a
  per-volume lock ordered after it so a slow write cannot stall other
  volumes

- add a grow-only 'resize' verb to both backends: lvextend plus the
  matching fs-grow tool for lvm, ubirsvol for ubi

- create ext4 volumes with a journal (was ext2) and request check_fs
  when registering a read-write volume

- declare read-only mounts to blockd rather than letting block guess
  from the filesystem

- reject non-numeric and non-positive sizes in create and resize

- report its own version

- simplify command line parsing (drop compatibility with ancient ucode)

Requires the matching fstools changes from
openwrt/openwrt@9b11fa4088.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle committed 2026-07-25 23:53:57 +01:00
1 parent a9fc86bec5
commit cb60600aec
8 files changed
+574 -362

No files matched your search

+6 -6
View File
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=uvol PKG_NAME:=uvol
PKG_VERSION:=1.0 PKG_VERSION:=1.1
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org> PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
@@ -14,7 +14,7 @@ define Package/autopart
CATEGORY:=Utilities CATEGORY:=Utilities
SUBMENU:=Disc SUBMENU:=Disc
TITLE:=Automatically initialize LVM partition TITLE:=Automatically initialize LVM partition
DEPENDS:=+lvm2 +partx-utils +sfdisk DEPENDS:=+lvm2 +partx-utils +sfdisk +e2fsprogs +mkf2fs +f2fsck
PKGARCH=all PKGARCH=all
endef endef
@@ -27,7 +27,7 @@ define Package/uvol
CATEGORY:=Utilities CATEGORY:=Utilities
SUBMENU:=Disc SUBMENU:=Disc
TITLE:=OpenWrt UBI/LVM volume abstraction TITLE:=OpenWrt UBI/LVM volume abstraction
DEPENDS:=+blockd +ucode +ucode-mod-fs +ucode-mod-math +ucode-mod-uci DEPENDS:=+blockd +ucode +ucode-mod-fs +ucode-mod-math +ucode-mod-uci +ucode-mod-ubus
PKGARCH=all PKGARCH=all
endef endef
@@ -67,14 +67,14 @@ define Package/autopart/install
endef endef
define Package/uvol/install define Package/uvol/install
$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/lib/uvol/backends $(1)/usr/sbin $(1)/etc/uci-defaults $(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/lib/uvol/backends $(1)/usr/sbin
$(INSTALL_BIN) ./files/uvol.init $(1)/etc/init.d/uvol $(INSTALL_BIN) ./files/uvol.init $(1)/etc/init.d/uvol
$(INSTALL_DATA) ./files/blockdev_common.uc $(1)/usr/lib/uvol/ $(INSTALL_DATA) ./files/blockdev_common.uc $(1)/usr/lib/uvol/
$(INSTALL_DATA) ./files/uci.uc $(1)/usr/lib/uvol/ $(INSTALL_DATA) ./files/mount.uc $(1)/usr/lib/uvol/
$(INSTALL_DATA) ./files/lvm.uc $(1)/usr/lib/uvol/backends/ $(INSTALL_DATA) ./files/lvm.uc $(1)/usr/lib/uvol/backends/
$(INSTALL_DATA) ./files/ubi.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 $(1)/usr/sbin
$(INSTALL_BIN) ./files/uvol.defaults $(1)/etc/uci-defaults/90-uvol-init $(SED) 's/@PKG_VERSION@/$(PKG_VERSION)/g' $(1)/usr/sbin/uvol
endef endef
$(eval $(call BuildPackage,autopart)) $(eval $(call BuildPackage,autopart))
+230 -88
View File
@@ -41,8 +41,8 @@ function lvm(cmd, ...args) {
} }
function pvs() { function pvs() {
let fstab = cursor.get_all('fstab'); let fstab = cursor ? cursor.get_all('fstab') : null;
for (let k, section in fstab) { for (let k, section in (fstab ?? {})) {
if (section['.type'] != 'uvol' || !section.vg_name) if (section['.type'] != 'uvol' || !section.vg_name)
continue; continue;
@@ -95,6 +95,45 @@ function lvs(vg_name, vol_name, extra_exp) {
return ret; return ret;
} }
function lvs_deleting(vg_name) {
let ret = [];
let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_dm_path", "-S",
sprintf("\"lvname=~^dd_.* && vg_name=%s\"", vg_name));
if (tmp && tmp.report.lv) {
ret = tmp.report.lv;
for (let r in ret)
r.lv_active = (r.lv_active == "active");
}
return ret;
}
function lvs_incomplete(vg_name, vol_name) {
let ret = [];
let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_size", "-S",
sprintf("\"lvname=~^w[op]_%s\$ && vg_name=%s\"", vol_name ?? ".*", vg_name));
if (tmp && tmp.report.lv) {
ret = tmp.report.lv;
for (let r in ret) {
r.lv_active = (r.lv_active == "active");
r.lv_size = +(rtrim(r.lv_size, "B"));
}
}
return ret;
}
// purge incomplete (wo_/wp_) leftovers; with match_size, only those of exactly
// that allocated size (null purges all)
function lvm_purge_incomplete(vol_name, match_size) {
for (let lv in lvs_incomplete(vg_name, vol_name)) {
if (match_size != null && lv.lv_size != match_size)
continue;
if (lv.lv_active)
lvm("lvchange", "-a", "n", lv.lv_full_name);
lvm("lvremove", "-y", lv.lv_full_name);
}
return 0;
}
function getdev(lv) { function getdev(lv) {
if (!lv) if (!lv)
return null; return null;
@@ -123,10 +162,8 @@ function lvm_init(ctx) {
return false; return false;
vg = vgs(vg_name); vg = vgs(vg_name);
uvol_uci_add = ctx.uci_add; register = ctx.register;
uvol_uci_commit = ctx.uci_commit; unregister = ctx.unregister;
uvol_uci_remove = ctx.uci_remove;
uvol_uci_init = ctx.uci_init;
return true; return true;
} }
@@ -198,7 +235,11 @@ function lvm_status(vol_name) {
return 2; return 2;
let mode = substr(res[0].lv_name, 0, 2); let mode = substr(res[0].lv_name, 0, 2);
if ((mode != "ro" && mode != "rw") || !res[0].lv_active) if (mode == "wo")
return 22;
if (mode == "wp")
return 16;
if (!res[0].lv_active)
return 1; return 1;
return 0; return 0;
@@ -235,27 +276,28 @@ function lvm_updown(vol_name, up) {
wildcard(lv.lv_path, "/dev/*/wp_*"))) wildcard(lv.lv_path, "/dev/*/wp_*")))
return 22; return 22;
if (up)
uvol_uci_commit(vol_name);
if (lv.lv_active == up)
return 0;
if (!up) { if (!up) {
let devname = getdev(lv); let devname = getdev(lv);
if (devname) if (devname) {
system(sprintf("umount /dev/%s", devname)); unregister(devname);
system(sprintf("umount /dev/%s 2>/dev/null", devname));
}
} }
let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name); if (lv.lv_active != up) {
if (up && lvchange_r.retval != 0) let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name);
return lvchange_r.retval; if (up && lvchange_r.retval != 0)
return lvchange_r.retval;
lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name); lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
if (lvchange_r.retval != 0) if (lvchange_r.retval != 0)
return lvchange_r.retval; return lvchange_r.retval;
}
return 0 if (up)
return register(vol_name, getdev(lv), substr(lv.lv_name, 0, 2) == "ro");
return 0;
} }
function lvm_up(vol_name) { function lvm_up(vol_name) {
@@ -271,16 +313,20 @@ function lvm_create(vol_name, vol_size, vol_mode) {
return 22; return 22;
vol_size = +vol_size; vol_size = +vol_size;
if (vol_size <= 0) if (vol_size != vol_size || vol_size <= 0)
return 22; return 22;
let size_ext = vol_size / vg.vg_extent_size;
if (vol_size % vg.vg_extent_size)
++size_ext;
// reclaim only an exact name+size retry; a size mismatch surfaces as EEXIST
lvm_purge_incomplete(vol_name, size_ext * vg.vg_extent_size);
let res = lvs(vg_name, vol_name); let res = lvs(vg_name, vol_name);
if (res[0]) if (res[0])
return 17; return 17;
let size_ext = vol_size / vg.vg_extent_size;
if (vol_size % vg.vg_extent_size)
++size_ext;
let lvmode, mode; let lvmode, mode;
if (vol_mode == "ro" || vol_mode == "wo") { if (vol_mode == "ro" || vol_mode == "wo") {
lvmode = "r"; lvmode = "r";
@@ -315,7 +361,7 @@ function lvm_create(vol_name, vol_size, vol_mode) {
return mkfs_ret; return mkfs_ret;
} }
} else { } else {
let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -L \"%s\" \"%s\"", vol_name, lv.lv_path)); let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -t ext4 -O has_journal -L \"%s\" \"%s\"", vol_name, lv.lv_path));
if (mkfs_ret != 0) { if (mkfs_ret != 0) {
lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name); lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (lvchange_r.retval != 0) if (lvchange_r.retval != 0)
@@ -323,7 +369,6 @@ function lvm_create(vol_name, vol_size, vol_mode) {
return mkfs_ret; return mkfs_ret;
} }
} }
uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "rw");
ret = lvm("lvchange", "-a", "n", lv.lv_full_name); ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
if (ret.retval != 0) if (ret.retval != 0)
@@ -336,6 +381,98 @@ function lvm_create(vol_name, vol_size, vol_mode) {
return 0; return 0;
} }
// identify the on-disk filesystem by superblock magic (no external tool needed)
function fs_type(devpath) {
let f = fs.open(devpath, "r");
if (!f)
return null;
f.seek(1024);
let sb = f.read(58);
f.close();
if (type(sb) != "string" || length(sb) < 58)
return null;
if (ord(sb, 0) == 0x10 && ord(sb, 1) == 0x20 && ord(sb, 2) == 0xf5 && ord(sb, 3) == 0xf2)
return "f2fs";
if (ord(sb, 56) == 0x53 && ord(sb, 57) == 0xef)
return "ext";
return null;
}
// Grow a rw volume in place; the fs (ext4 or f2fs, chosen at create by size) is
// grown with its own tool. Shrink is refused. The fs-grow tool is an optional
// dependency: if absent, report and refuse rather than grow the LV past the fs.
function lvm_resize(vol_name, vol_size) {
if (!vol_name || !vg_name)
return 22;
vol_size = +vol_size;
if (vol_size != vol_size || vol_size <= 0)
return 22;
let res = lvs(vg_name, vol_name);
if (!res[0])
return 2;
if (substr(res[0].lv_name, 0, 2) != "rw")
return 1;
let size_ext = vol_size / vg.vg_extent_size;
if (vol_size % vg.vg_extent_size)
++size_ext;
let new_size = size_ext * vg.vg_extent_size;
if (new_size == +res[0].lv_size)
return 0;
if (new_size < +res[0].lv_size)
return 22;
let dev = getdev(res[0]);
if (!dev)
return 2;
let fstype = fs_type(sprintf("/dev/%s", dev));
let tool = (fstype == "f2fs") ? "resize.f2fs" : (fstype == "ext") ? "resize2fs" : null;
if (!tool) {
warn(sprintf("uvol: cannot identify filesystem on %s; not resizing\n", vol_name));
return 95;
}
// the fs-grow tool first, so the LV is never left larger than its filesystem
if (system(sprintf("command -v %s >/dev/null 2>&1", tool)) != 0) {
warn(sprintf("uvol: %s not found; install %s to grow this %s volume\n",
tool, (fstype == "f2fs") ? "f2fs-tools" : "e2fsprogs", fstype));
return 95;
}
let ret = lvm("lvextend", "-l", size_ext, res[0].lv_full_name);
if (ret.retval != 0)
return ret.retval;
if (fstype == "f2fs")
return system(sprintf("resize.f2fs /dev/%s", dev));
return system(sprintf("resize2fs /dev/%s", dev));
}
// Reap volumes marked for deferred deletion (dd_ prefix). A volume can only be
// reaped once its backing device has no holder; blockd signals that moment with
// a mount.umount notification (autofs idle-expiry), which triggers 'uvol reap'.
// Still-held volumes are left for the next signal (or the boot sweep).
function lvm_reap() {
for (let dd in lvs_deleting(vg_name)) {
let dev = getdev(dd);
if (dd.lv_active) {
let r = lvm("lvchange", "-a", "n", dd.lv_full_name);
if (r.retval != 0)
continue;
}
if (dev)
unregister(dev);
lvm("lvremove", "-y", dd.lv_full_name);
}
return 0;
}
function lvm_remove(vol_name) { function lvm_remove(vol_name) {
if (!vol_name || !vg_name) if (!vol_name || !vg_name)
return 22; return 22;
@@ -344,16 +481,15 @@ function lvm_remove(vol_name) {
if (!res[0]) if (!res[0])
return 2; return 2;
if (res[0].lv_active) // mark for deletion: rename to the dd_ state (works whether the volume is
return 16; // active or not, and hides it from list/status). reap removes it now if the
// device is already free, otherwise blockd's mount.umount triggers reap once
let ret = lvm("lvremove", "-y", res[0].lv_full_name); // the holder releases it.
let ret = lvm("lvrename", vg_name, res[0].lv_name, sprintf("dd_%s", vol_name));
if (ret.retval != 0) if (ret.retval != 0)
return ret.retval; return ret.retval;
uvol_uci_remove(vol_name); return lvm_reap();
uvol_uci_commit(vol_name);
return 0;
} }
function lvm_dd(in_fd, out_fd, vol_size) { function lvm_dd(in_fd, out_fd, vol_size) {
@@ -369,7 +505,7 @@ function lvm_dd(in_fd, out_fd, vol_size) {
return rem; return rem;
} }
function lvm_write(vol_name, vol_size) { function lvm_write(vol_name, vol_size, verify) {
if (!vol_name || !vg_name) if (!vol_name || !vg_name)
return 22; return 22;
@@ -382,70 +518,74 @@ function lvm_write(vol_name, vol_size) {
if (vol_size > lv.lv_size) if (vol_size > lv.lv_size)
return 27; return 27;
if (wildcard(lv.lv_path, "/dev/*/wo_*")) { 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 22;
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 rem = lvm_dd(fs.stdin, volfile, vol_size);
volfile.close();
if (rem < 0) {
printf("more %d bytes data than given size!\n", -rem);
} }
if (rem > 0) {
printf("reading finished %d bytes before given size!\n", rem);
}
if (verify && !verify(lv.lv_path)) {
lvm("lvchange", "-a", "n", lv.lv_full_name);
lvm("lvchange", "-p", "r", lv.lv_full_name);
return 74;
}
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;
return 0; return 0;
} }
function lvm_detect() { 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)) { for (let lv in lvs(vg_name)) {
let vol_name = substr(lv.lv_name, 3); if (!lv.lv_active)
let vol_mode = substr(lv.lv_name, 0, 2); continue;
uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), vol_mode);
} let mode = substr(lv.lv_name, 0, 2);
uvol_uci_commit(); if (mode != "ro" && mode != "rw")
for (let lv_full_name in temp_up) { continue;
lvm("lvchange", "-a", "n", lv_full_name);
lvm("lvchange", "-k", "y", lv_full_name); register(substr(lv.lv_name, 3), getdev(lv), mode == "ro");
} }
return 0; return 0;
} }
function lvm_boot() { function lvm_boot() {
// clear crash/power-loss leftovers before activating
lvm_reap();
lvm_purge_incomplete();
for (let lv in lvs(vg_name, null, "lv_skip_activation=0")) {
let mode = substr(lv.lv_name, 0, 2);
if (mode != "ro" && mode != "rw")
continue;
lvm_up(substr(lv.lv_name, 3));
}
return 0; return 0;
} }
@@ -464,5 +604,7 @@ backend.device = lvm_device;
backend.up = lvm_up; backend.up = lvm_up;
backend.down = lvm_down; backend.down = lvm_down;
backend.create = lvm_create; backend.create = lvm_create;
backend.resize = lvm_resize;
backend.remove = lvm_remove; backend.remove = lvm_remove;
backend.reap = lvm_reap;
backend.write = lvm_write; backend.write = lvm_write;
+64
View File
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// blockd mount registration for uvol
// (c) 2022 Daniel Golle <daniel@makrotopia.org>
//
// Register volumes with blockd over ubus instead of writing /etc/config/fstab,
// so volume state stays on the self-describing backing store and never leaks
// into the firmware rootfs.
let mount_fs = require("fs");
let mount_ubus = require("ubus");
let uvol_target = function(vol_name) {
return sprintf("/tmp/run/uvol/%s", vol_name);
};
uvol_mount = {
register: function(vol_name, dev_name, read_only) {
if (!dev_name)
return 22;
if (substr(vol_name, 0, 1) == "." && vol_name != ".meta")
return 1;
let target = uvol_target(vol_name);
let st = mount_fs.lstat(target);
if (st && st.type == "link")
mount_fs.unlink(target);
else if (st && st.type == "directory")
mount_fs.rmdir(target);
let data = {
device: dev_name,
target: target,
autofs: 1,
};
// declare the volume's read-only-ness to blockd, which carries it
// to block as a mount option; block never guesses from the fs type.
if (read_only)
data.options = "ro";
else
data.check_fs = 1;
mount_ubus.call({
object: "block",
method: "hotplug",
data: data,
});
return mount_ubus.error() ? -1 : 0;
},
unregister: function(dev_name) {
if (!dev_name)
return 22;
mount_ubus.call({
object: "block",
method: "hotplug",
data: {
device: dev_name,
remove: 1,
},
});
return mount_ubus.error() ? -1 : 0;
}
};
+138 -77
View File
@@ -50,10 +50,6 @@ function mkubifs(vol_dev) {
return 0; return 0;
} }
function block_hotplug(action, devname) {
return system(sprintf("ACTION=%s DEVNAME=%s /sbin/block hotplug", action, devname));
}
function ubi_init(ctx) { function ubi_init(ctx) {
cursor = ctx.cursor; cursor = ctx.cursor;
fs = ctx.fs; fs = ctx.fs;
@@ -76,10 +72,8 @@ function ubi_init(ctx) {
ebsize = read_file(sprintf("%s/eraseblock_size", ubidevpath)); ebsize = read_file(sprintf("%s/eraseblock_size", ubidevpath));
uvol_uci_add = ctx.uci_add; register = ctx.register;
uvol_uci_commit = ctx.uci_commit; unregister = ctx.unregister;
uvol_uci_remove = ctx.uci_remove;
uvol_uci_init = ctx.uci_init;
return true; return true;
} }
@@ -137,10 +131,6 @@ function ubi_device(vol_name) {
} }
function ubi_create(vol_name, vol_size, vol_mode) { function ubi_create(vol_name, vol_size, vol_mode) {
let vol_dev = ubi_get_dev(vol_name);
if (vol_dev)
return 17;
let mode; let mode;
if (vol_mode == "ro" || vol_mode == "wo") if (vol_mode == "ro" || vol_mode == "wo")
mode = "wo"; mode = "wo";
@@ -152,6 +142,13 @@ function ubi_create(vol_name, vol_size, vol_mode) {
let vol_size = +vol_size; let vol_size = +vol_size;
if (vol_size <= 0) if (vol_size <= 0)
return 22; return 22;
ubi_purge_incomplete(vol_name, vol_size);
let vol_dev = ubi_get_dev(vol_name);
if (vol_dev)
return 17;
let ret = system(sprintf("ubimkvol /dev/%s -N \"uvol-%s-%s\" -s %d", ubidev, mode, vol_name, vol_size)); let ret = system(sprintf("ubimkvol /dev/%s -N \"uvol-%s-%s\" -s %d", ubidev, mode, vol_name, vol_size));
if (ret != 0) if (ret != 0)
return ret; return ret;
@@ -171,8 +168,6 @@ function ubi_create(vol_name, vol_size, vol_mode) {
if (ret != 0) if (ret != 0)
return ret; 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)); let ret = system(sprintf("ubirename /dev/%s \"uvol-wp-%s\" \"uvol-wd-%s\"", ubidev, vol_name, vol_name));
if (ret != 0) if (ret != 0)
return ret; return ret;
@@ -180,25 +175,111 @@ function ubi_create(vol_name, vol_size, vol_mode) {
return 0; return 0;
} }
// Grow a rw volume; UBIFS uses the new size directly. Shrink is refused.
function ubi_resize(vol_name, vol_size) {
vol_size = +vol_size;
if (vol_size <= 0)
return 22;
let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev)
return 2;
if (vol_get_mode(vol_dev) != "rw")
return 1;
let leb = +read_file(sprintf("/sys/class/ubi/%s/usable_eb_size", vol_dev));
let cur_lebs = +read_file(sprintf("/sys/class/ubi/%s/reserved_ebs", vol_dev));
let req_lebs = vol_size / leb;
if (vol_size % leb)
++req_lebs;
if (req_lebs == cur_lebs)
return 0;
if (req_lebs < cur_lebs)
return 22;
return system(sprintf("ubirsvol /dev/%s -N \"uvol-rw-%s\" -s %d", ubidev, vol_name, vol_size));
}
function ubi_get_deleting() {
let ret = [];
for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-dd-*"))
continue;
push(ret, fs.basename(vol_dir));
}
return ret;
}
function ubi_get_incomplete(vol_name) {
let ret = [];
let pat = vol_name ? sprintf("uvol-w[op]-%s", vol_name) : "uvol-w[op]-*";
for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, pat))
continue;
push(ret, {
dev: fs.basename(vol_dir),
lebs: +read_file(sprintf("%s/reserved_ebs", vol_dir)),
leb: +read_file(sprintf("%s/usable_eb_size", vol_dir)),
});
}
return ret;
}
// purge incomplete (wo/wp) leftovers; with match_size, only those whose
// reservation matches that size (null purges all)
function ubi_purge_incomplete(vol_name, match_size) {
for (let v in ubi_get_incomplete(vol_name)) {
if (match_size != null) {
if (!v.leb || !v.lebs)
continue;
let want = match_size / v.leb;
if (match_size % v.leb)
want++;
if (v.lebs != want)
continue;
}
let volnum = split(v.dev, "_")[1];
system(sprintf("ubirmvol /dev/%s -n %d 2>/dev/null", ubidev, volnum));
}
return 0;
}
function ubi_reap() {
for (let vol_dev in ubi_get_deleting()) {
let volnum = split(vol_dev, "_")[1];
let blkdev = sprintf("ubiblock%s", substr(vol_dev, 3));
let isblock = fs.access(sprintf("/dev/%s", blkdev), "r");
if (isblock && system(sprintf("ubiblock --remove /dev/%s 2>/dev/null", vol_dev)) != 0)
continue;
if (!isblock)
system(sprintf("umount /dev/%s 2>/dev/null", vol_dev));
if (system(sprintf("ubirmvol /dev/%s -n %d 2>/dev/null", ubidev, volnum)) != 0)
continue;
unregister(isblock ? blkdev : vol_dev);
}
return 0;
}
function ubi_remove(vol_name) { function ubi_remove(vol_name) {
let vol_dev = ubi_get_dev(vol_name); let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev) if (!vol_dev)
return 2; return 2;
let vol_mode = vol_get_mode(vol_dev); let vol_ubiname = read_file(sprintf("/sys/class/ubi/%s/name", vol_dev));
if (vol_mode == "rw" || vol_mode == "ro") let ret = system(sprintf("ubirename /dev/%s \"%s\" \"uvol-dd-%s\"", ubidev, vol_ubiname, vol_name));
return 16;
let volnum = split(vol_dev, "_")[1];
let ret = system(sprintf("ubirmvol /dev/%s -n %d", ubidev, volnum));
if (ret != 0) if (ret != 0)
return ret; return ret;
uvol_uci_remove(vol_name); return ubi_reap();
uvol_uci_commit(vol_name);
return 0;
} }
function ubi_up(vol_name) { function ubi_up(vol_name) {
@@ -214,19 +295,22 @@ function ubi_up(vol_name) {
else if (vol_mode == "wp") else if (vol_mode == "wp")
return 16; return 16;
uvol_uci_commit(vol_name);
if (vol_mode == "rd") { if (vol_mode == "rd") {
let ret = system(sprintf("ubirename /dev/%s \"uvol-rd-%s\" \"uvol-ro-%s\"", ubidev, vol_name, vol_name)); let ret = system(sprintf("ubirename /dev/%s \"uvol-rd-%s\" \"uvol-ro-%s\"", ubidev, vol_name, vol_name));
if (ret != 0) if (ret != 0)
return ret; return ret;
return system(sprintf("ubiblock --create /dev/%s", vol_dev)); ret = system(sprintf("ubiblock --create /dev/%s", vol_dev));
if (ret != 0)
return ret;
return register(vol_name, sprintf("ubiblock%s", substr(vol_dev, 3)), true);
} else if (vol_mode == "wd") { } else if (vol_mode == "wd") {
let ret = system(sprintf("ubirename /dev/%s \"uvol-wd-%s\" \"uvol-rw-%s\"", ubidev, vol_name, vol_name)); let ret = system(sprintf("ubirename /dev/%s \"uvol-wd-%s\" \"uvol-rw-%s\"", ubidev, vol_name, vol_name));
if (ret != 0) if (ret != 0)
return ret; return ret;
return block_hotplug("add", vol_dev); return register(vol_name, vol_dev, false);
} }
return 0; return 0;
} }
@@ -244,14 +328,15 @@ function ubi_down(vol_name) {
else if (vol_mode == "wp") else if (vol_mode == "wp")
return 16; return 16;
else if (vol_mode == "ro") { else if (vol_mode == "ro") {
unregister(sprintf("ubiblock%s", substr(vol_dev, 3)));
system(sprintf("umount /dev/ubiblock%s 2>&1 >/dev/null", substr(vol_dev, 3))); system(sprintf("umount /dev/ubiblock%s 2>&1 >/dev/null", substr(vol_dev, 3)));
system(sprintf("ubiblock --remove /dev/%s", vol_dev)); 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)); let ret = system(sprintf("ubirename /dev/%s \"uvol-ro-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
return ret; return ret;
} else if (vol_mode == "rw") { } else if (vol_mode == "rw") {
unregister(vol_dev);
system(sprintf("umount /dev/%s 2>&1 >/dev/null", vol_dev)); 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)); 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 ret;
} }
return 0; return 0;
@@ -279,64 +364,38 @@ function ubi_list(search_name) {
return volumes; return volumes;
} }
function ubi_detect() { function ubi_register_active() {
let tmpdev = [];
for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", ubidev))) { for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir)); let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-r[ow]-*"))
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/class/ubi/%s_*", ubidev))) {
let vol_ubiname = read_file(sprintf("%s/name", vol_dir));
if (!wildcard(vol_ubiname, "uvol-[rw][wod]-*"))
continue; continue;
let vol_dev = fs.basename(vol_dir); let vol_dev = fs.basename(vol_dir);
let vol_name = substr(vol_ubiname, 8); let vol_name = substr(vol_ubiname, 8);
let vol_mode = substr(vol_ubiname, 5, 2); let vol_mode = substr(vol_ubiname, 5, 2);
if (vol_mode == "ro" || vol_mode == "rd") if (vol_mode == "ro") {
uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro"); system(sprintf("ubiblock --create /dev/%s", vol_dev));
else if (vol_mode == "rw" || vol_mode == "wd") register(vol_name, sprintf("ubiblock%s", substr(vol_dev, 3)), true);
uvol_uci_add(vol_name, sprintf("/dev/%s", vol_dev), "rw"); } else {
register(vol_name, vol_dev, false);
}
} }
uvol_uci_commit();
for (vol_dev in tmpdev)
system(sprintf("ubiblock --remove /dev/%s", vol_dev));
return 0; return 0;
} }
function ubi_boot() { function ubi_detect() {
for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", ubidev))) { return ubi_register_active();
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) { function ubi_boot() {
// clear crash/power-loss leftovers before activating
ubi_reap();
ubi_purge_incomplete();
return ubi_register_active();
}
function ubi_write(vol_name, write_size, verify) {
let vol_dev = ubi_get_dev(vol_name); let vol_dev = ubi_get_dev(vol_name);
if (!vol_dev) if (!vol_dev)
return 2; return 2;
@@ -349,13 +408,13 @@ function ubi_write(vol_name, write_size) {
if (vol_mode != "wo") if (vol_mode != "wo")
return 22; return 22;
let ret = system(sprintf("ubiupdatevol -s %s /dev/%s -", write_size, vol_dev)); let ret = system(sprintf("ubiupdatevol -s %d /dev/%s -", write_size, vol_dev));
if (ret) if (ret)
return ret; return ret;
system(sprintf("ubiblock --create /dev/%s", vol_dev)); if (verify && !verify(sprintf("/dev/%s", vol_dev)))
uvol_uci_add(vol_name, sprintf("/dev/ubiblock%s", substr(vol_dev, 3)), "ro"); return 74;
system(sprintf("ubiblock --remove /dev/%s", vol_dev));
system(sprintf("ubirename /dev/%s \"uvol-wo-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name)); system(sprintf("ubirename /dev/%s \"uvol-wo-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
return 0; return 0;
@@ -376,5 +435,7 @@ backend.device = ubi_device;
backend.up = ubi_up; backend.up = ubi_up;
backend.down = ubi_down; backend.down = ubi_down;
backend.create = ubi_create; backend.create = ubi_create;
backend.resize = ubi_resize;
backend.remove = ubi_remove; backend.remove = ubi_remove;
backend.reap = ubi_reap;
backend.write = ubi_write; backend.write = ubi_write;
-137
View File
@@ -1,137 +0,0 @@
// 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;
}
};
+128 -25
View File
@@ -3,10 +3,13 @@
// uvol - storage volume manager for OpenWrt // uvol - storage volume manager for OpenWrt
// (c) 2022 Daniel Golle <daniel@makrotopia.org> // (c) 2022 Daniel Golle <daniel@makrotopia.org>
let help_output = "uvol storage volume manager let uvol_version = "@PKG_VERSION@";
let help_output = `uvol ${uvol_version} - storage volume manager
syntax: uvol command ... syntax: uvol command ...
commands: commands:
version show version and exit
boot get active volumes ready (called on boot) boot get active volumes ready (called on boot)
free show number of bytes available free show number of bytes available
total show total number of bytes total show total number of bytes
@@ -25,7 +28,20 @@ commands:
1 - volume is not ready for use 1 - volume is not ready for use
2 - volume doesn't exist 2 - volume doesn't exist
write volname size write to volume from stdin, size in bytes write volname size write to volume from stdin, size in bytes
"; `;
let cmd = shift(ARGV);
// answer before probing for a backend, so both work on a system without storage
if (cmd == "version" || cmd == "--version" || cmd == "-v" || cmd == "-V") {
printf("uvol %s\n", uvol_version);
return 0;
}
if (!cmd || cmd == "-h" || cmd == "--help" || cmd == "help") {
printf("%s", help_output);
return cmd?0:22;
}
let fs = require("fs"); let fs = require("fs");
let uci = require("uci"); let uci = require("uci");
@@ -34,11 +50,9 @@ let cursor = uci ? uci.cursor() : null;
let ctx = {}; let ctx = {};
ctx.cursor = cursor; ctx.cursor = cursor;
ctx.fs = fs; ctx.fs = fs;
include("/usr/lib/uvol/uci.uc"); include("/usr/lib/uvol/mount.uc");
ctx.uci_add = uvol_uci.uvol_uci_add; ctx.register = uvol_mount.register;
ctx.uci_remove = uvol_uci.uvol_uci_remove; ctx.unregister = uvol_mount.unregister;
ctx.uci_commit = uvol_uci.uvol_uci_commit;
ctx.uci_init = uvol_uci.uvol_uci_init;
let backend = null; let backend = null;
let tried_backends = []; let tried_backends = [];
@@ -64,29 +78,55 @@ if (!backend) {
exit(2); exit(2);
} }
// The below code is needed as older versions of ucode pass the complete cmdline via ARGV
// Once we can rely in more recent ucode the while loop can be replaced by simply
// let cmd = shift(ARGV);
let skip = null;
let cmd = null;
let skip_argv = ["/usr/bin/ucode", "-R", "/usr/sbin/uvol"];
while (skip = shift(ARGV)) {
if (skip != shift(skip_argv)) {
cmd = skip;
break;
}
}
if (!cmd || cmd == "-h" || cmd == "help") {
printf("%s", help_output);
return cmd?0:22;
}
if (!(cmd in keys(backend))) { if (!(cmd in keys(backend))) {
printf("command %s not found\n", cmd); printf("command %s not found\n", cmd);
return 22; return 22;
} }
// serialise state-mutating commands device-wide; released on process exit.
// create and write additionally serialise per volume, as create may reclaim
// an incomplete volume of the same name and size while a writer is still
// streaming to it. The device-wide lock is always taken before the
// per-volume lock; write takes only the per-volume one so a slow stream
// does not stall operations on other volumes.
let uvol_mutating = [ "boot", "detect", "create", "up", "down", "remove", "reap", "resize" ];
let uvol_vol_mutating = [ "create", "write" ];
let uvol_lockfd;
let uvol_vol_lockfd;
if (index(uvol_mutating, cmd) >= 0 || index(uvol_vol_mutating, cmd) >= 0)
fs.mkdir("/tmp/run", 0755);
if (index(uvol_mutating, cmd) >= 0) {
uvol_lockfd = fs.open("/tmp/run/uvol.lock", "a");
if (uvol_lockfd)
uvol_lockfd.lock("x");
}
if (index(uvol_vol_mutating, cmd) >= 0 && ARGV[0]) {
uvol_vol_lockfd = fs.open(sprintf("/tmp/run/uvol.lock.%s", ARGV[0]), "a");
if (uvol_vol_lockfd)
uvol_vol_lockfd.lock("x");
}
let meta_init = function() {
let sz = backend.size(".meta");
if (type(sz) != "string" || +sz <= 0) {
let metasz = +backend.total() / 10240;
if (metasz < 4194304)
metasz = 4194304;
if (metasz > +backend.free())
return;
if (backend.create(".meta", sprintf("%d", metasz), "rw") != 0)
return;
}
backend.up(".meta");
fs.mkdir("/tmp/run/uvol/.meta/apk", 0755);
};
if (cmd == "boot") {
backend.boot();
meta_init();
exit(0);
}
let json_output = false; let json_output = false;
if (ARGV[0] == "-j") { if (ARGV[0] == "-j") {
json_output = true; json_output = true;
@@ -110,6 +150,69 @@ let legacy_output = function(var) {
return out; return out;
}; };
// Content-addressed volumes are named "<algo>-<hexdigest>"; the name carries the
// expected digest of its content. Validation lives here in the generic layer so
// backends stay plain storage. Add an entry to ca_algos to support a new digest.
let ca_algos = {
sha256: { cmd: "sha256sum", len: 64 },
};
let ca_parse = function(name) {
let m = match(name, /^([0-9a-z]+)-([0-9a-f]+)$/);
if (!m)
return null;
let a = ca_algos[m[1]];
if (!a || length(m[2]) != a.len)
return null;
return { cmd: a.cmd, hash: m[2] };
};
let ca_digest = function(cmd, size, path) {
let src = path ? sprintf("head -c %d %s", size, path) : sprintf("head -c %d", size);
let f = fs.popen(sprintf("%s | %s", src, cmd), "r");
if (!f)
return null;
let line = f.read("line");
f.close();
return split(trim(line ?? ""), " ")[0];
};
// complete means the volume exists and has left the write-only state:
// ready (0) or merely down (1); incomplete volumes report 22 or 16
let ca_complete = function(name) {
let st = backend.status(name);
return st == 0 || st == 1;
};
let ca_mismatch = function(name) {
printf("content hash mismatch for %s\n", name);
exit(74);
};
if (cmd == "create" && ca_parse(ARGV[0])) {
let ret = backend.create(...ARGV);
exit(ret == 17 ? 0 : ret);
}
if (cmd == "write") {
let ca = ca_parse(ARGV[0]);
if (ca) {
let size = +ARGV[1];
if (ca_complete(ARGV[0])) {
if (ca_digest(ca.cmd, size, null) != ca.hash)
ca_mismatch(ARGV[0]);
exit(0);
}
// the backend calls verify() on the raw volume before finalising it;
// 74 (EBADMSG) reports a rejected payload, the volume stays incomplete
let verify = (path) => ca_digest(ca.cmd, size, path) == ca.hash;
let ret = backend.write(ARGV[0], size, verify);
if (ret == 74)
ca_mismatch(ARGV[0]);
exit(ret);
}
}
if (type(backend[cmd]) == "function") { if (type(backend[cmd]) == "function") {
let ret = backend[cmd](...ARGV); let ret = backend[cmd](...ARGV);
if (type(ret) == "int") { if (type(ret) == "int") {
-21
View File
@@ -1,21 +0,0 @@
#!/bin/sh
uvol_init() {
local metasz freesz totalsz
uvol detect
metasz="$(uvol size .meta 2>/dev/null)"
if [ "$metasz" ]; then
[ "$((metasz))" -gt 0 ] && return
fi
totalsz="$(uvol total)"
freesz="$(uvol free)"
metasz="$((totalsz / 10240))"
[ "$metasz" -lt 4194304 ] && metasz=4194304
[ "$metasz" -gt "$freesz" ] && return
uvol create .meta "$metasz" rw
uvol up .meta
grep -q "uvol/.meta" /proc/mounts || return
mkdir -p "/var/run/uvol/.meta/apk"
}
uci -q get fstab.@uvol[0].initialized >/dev/null || uvol_init
+8 -8
View File
@@ -6,16 +6,16 @@ NAME=uvol
PROG=/usr/sbin/uvol PROG=/usr/sbin/uvol
start_service() { start_service() {
[ "${__BOOT_UVOL}" = "1" ] && return 0 local ready
procd_open_instance "$NAME"
procd_set_param command "$PROG" boot
procd_close_instance
}
boot() { ready=$(ubus call block status 2>/dev/null | jsonfilter -e '@.ready')
__BOOT_UVOL=1 start [ "$ready" = "true" ] && "$PROG" boot
} }
service_triggers() { service_triggers() {
procd_add_raw_trigger "mount.ready" 200 /etc/init.d/uvol start procd_add_raw_trigger "mount.ready" 1000 "$PROG" boot
# blockd emits mount.umount when an idle volume's device becomes free;
# reap any volume that was marked for deferred deletion while in use
procd_add_raw_trigger "mount.umount" 1000 "$PROG" reap
} }