]> git.99rst.org Git - openwrt-packages.git/commitdiff
uvol: bump version to 1.1
authorDaniel Golle <redacted>
Sat, 25 Jul 2026 17:46:50 +0000 (18:46 +0100)
committerDaniel Golle <redacted>
Sat, 25 Jul 2026 22:53:57 +0000 (23:53 +0100)
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@9b11fa4088c6a.

Signed-off-by: Daniel Golle <redacted>
utils/uvol/Makefile
utils/uvol/files/lvm.uc
utils/uvol/files/mount.uc [new file with mode: 0644]
utils/uvol/files/ubi.uc
utils/uvol/files/uci.uc [deleted file]
utils/uvol/files/uvol
utils/uvol/files/uvol.defaults [deleted file]
utils/uvol/files/uvol.init

index 5c0b3d307a34faede40527f0b12fc9f765ed3f8e..3d022031998600cf4a1eda0ce59b0a872afa847b 100644 (file)
@@ -1,7 +1,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=uvol
-PKG_VERSION:=1.0
+PKG_VERSION:=1.1
 PKG_RELEASE:=1
 
 PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
@@ -14,7 +14,7 @@ define Package/autopart
   CATEGORY:=Utilities
   SUBMENU:=Disc
   TITLE:=Automatically initialize LVM partition
-  DEPENDS:=+lvm2 +partx-utils +sfdisk
+  DEPENDS:=+lvm2 +partx-utils +sfdisk +e2fsprogs +mkf2fs +f2fsck
   PKGARCH=all
 endef
 
@@ -27,7 +27,7 @@ define Package/uvol
   CATEGORY:=Utilities
   SUBMENU:=Disc
   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
 endef
 
@@ -67,14 +67,14 @@ define Package/autopart/install
 endef
 
 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_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/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
+       $(SED) 's/@PKG_VERSION@/$(PKG_VERSION)/g' $(1)/usr/sbin/uvol
 endef
 
 $(eval $(call BuildPackage,autopart))
index c93a50e8e60ad046c9fec91c3b1f19c60a9e7bdb..133daa2d17cf461e775a1f4dde73c02ee528879e 100644 (file)
@@ -41,8 +41,8 @@ function lvm(cmd, ...args) {
 }
 
 function pvs() {
-       let fstab = cursor.get_all('fstab');
-       for (let k, section in fstab) {
+       let fstab = cursor ? cursor.get_all('fstab') : null;
+       for (let k, section in (fstab ?? {})) {
                if (section['.type'] != 'uvol' || !section.vg_name)
                        continue;
 
@@ -95,6 +95,45 @@ function lvs(vg_name, vol_name, extra_exp) {
        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) {
        if (!lv)
                return null;
@@ -123,10 +162,8 @@ function lvm_init(ctx) {
                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;
+       register = ctx.register;
+       unregister = ctx.unregister;
        return true;
 }
 
@@ -198,7 +235,11 @@ function lvm_status(vol_name) {
                return 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 0;
@@ -235,27 +276,28 @@ function lvm_updown(vol_name, up) {
                   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));
+               if (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 (up && lvchange_r.retval != 0)
-               return lvchange_r.retval;
+       if (lv.lv_active != up) {
+               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;
+       }
 
-       lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
-       if (lvchange_r.retval != 0)
-               return lvchange_r.retval;
+       if (up)
+               return register(vol_name, getdev(lv), substr(lv.lv_name, 0, 2) == "ro");
 
-       return 0
+       return 0;
 }
 
 function lvm_up(vol_name) {
@@ -271,16 +313,20 @@ function lvm_create(vol_name, vol_size, vol_mode) {
                return 22;
 
        vol_size = +vol_size;
-       if (vol_size <= 0)
+       if (vol_size != vol_size || vol_size <= 0)
                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);
        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";
@@ -315,7 +361,7 @@ function lvm_create(vol_name, vol_size, vol_mode) {
                        return mkfs_ret;
                }
        } 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) {
                        lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
                        if (lvchange_r.retval != 0)
@@ -323,7 +369,6 @@ function lvm_create(vol_name, vol_size, vol_mode) {
                        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)
@@ -336,26 +381,117 @@ function lvm_create(vol_name, vol_size, vol_mode) {
        return 0;
 }
 
-function lvm_remove(vol_name) {
+// 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 (res[0].lv_active)
-               return 16;
+       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;
+       }
 
-       let ret = lvm("lvremove", "-y", res[0].lv_full_name);
+       // 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;
 
-       uvol_uci_remove(vol_name);
-       uvol_uci_commit(vol_name);
+       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) {
+       if (!vol_name || !vg_name)
+               return 22;
+
+       let res = lvs(vg_name, vol_name);
+       if (!res[0])
+               return 2;
+
+       // mark for deletion: rename to the dd_ state (works whether the volume is
+       // 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
+       // the holder releases it.
+       let ret = lvm("lvrename", vg_name, res[0].lv_name, sprintf("dd_%s", vol_name));
+       if (ret.retval != 0)
+               return ret.retval;
+
+       return lvm_reap();
+}
+
 function lvm_dd(in_fd, out_fd, vol_size) {
        let rem = vol_size;
        let buf;
@@ -369,7 +505,7 @@ function lvm_dd(in_fd, out_fd, vol_size) {
        return rem;
 }
 
-function lvm_write(vol_name, vol_size) {
+function lvm_write(vol_name, vol_size, verify) {
        if (!vol_name || !vg_name)
                return 22;
 
@@ -382,70 +518,74 @@ function lvm_write(vol_name, 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;
+       if (!wildcard(lv.lv_path, "/dev/*/wo_*"))
+               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 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);
-               }
+       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 (ret > 0) {
-                       printf("reading finished %d bytes before given size!\n", ret);
-               }
+       if (rem > 0) {
+               printf("reading finished %d bytes before given size!\n", rem);
+       }
 
-               uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "ro");
+       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", "-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("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;
+       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);
+               if (!lv.lv_active)
+                       continue;
+
+               let mode = substr(lv.lv_name, 0, 2);
+               if (mode != "ro" && mode != "rw")
+                       continue;
+
+               register(substr(lv.lv_name, 3), getdev(lv), mode == "ro");
        }
        return 0;
 }
 
 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;
 }
 
@@ -464,5 +604,7 @@ backend.device = lvm_device;
 backend.up = lvm_up;
 backend.down = lvm_down;
 backend.create = lvm_create;
+backend.resize = lvm_resize;
 backend.remove = lvm_remove;
+backend.reap = lvm_reap;
 backend.write = lvm_write;
diff --git a/utils/uvol/files/mount.uc b/utils/uvol/files/mount.uc
new file mode 100644 (file)
index 0000000..d65e792
--- /dev/null
@@ -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;
+       }
+};
index 8b46112c75383c332f2578e64ef2055432c19483..69edd8c29b42a64b5f2d56e91514ab32f791ae8e 100644 (file)
@@ -50,10 +50,6 @@ function mkubifs(vol_dev) {
        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;
@@ -76,10 +72,8 @@ function ubi_init(ctx) {
 
        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;
+       register = ctx.register;
+       unregister = ctx.unregister;
 
        return true;
 }
@@ -137,10 +131,6 @@ function ubi_device(vol_name) {
 }
 
 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";
@@ -152,6 +142,13 @@ function ubi_create(vol_name, vol_size, vol_mode) {
        let vol_size = +vol_size;
        if (vol_size <= 0)
                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));
        if (ret != 0)
                return ret;
@@ -171,8 +168,6 @@ function ubi_create(vol_name, vol_size, vol_mode) {
        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;
@@ -180,27 +175,113 @@ function ubi_create(vol_name, vol_size, vol_mode) {
        return 0;
 }
 
-function ubi_remove(vol_name) {
+// 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;
 
-       let vol_mode = vol_get_mode(vol_dev);
-       if (vol_mode == "rw" || vol_mode == "ro")
-               return 16;
+       if (vol_get_mode(vol_dev) != "rw")
+               return 1;
 
-       let volnum = split(vol_dev, "_")[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;
 
-       let ret = system(sprintf("ubirmvol /dev/%s -n %d", ubidev, volnum));
-       if (ret != 0)
-               return ret;
+       if (req_lebs == cur_lebs)
+               return 0;
+       if (req_lebs < cur_lebs)
+               return 22;
 
-       uvol_uci_remove(vol_name);
-       uvol_uci_commit(vol_name);
+       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) {
+       let vol_dev = ubi_get_dev(vol_name);
+       if (!vol_dev)
+               return 2;
+
+       let vol_ubiname = read_file(sprintf("/sys/class/ubi/%s/name", vol_dev));
+       let ret = system(sprintf("ubirename /dev/%s \"%s\" \"uvol-dd-%s\"", ubidev, vol_ubiname, vol_name));
+       if (ret != 0)
+               return ret;
+
+       return ubi_reap();
+}
+
 function ubi_up(vol_name) {
        let vol_dev = ubi_get_dev(vol_name);
        if (!vol_dev)
@@ -214,19 +295,22 @@ function ubi_up(vol_name) {
        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));
+               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") {
                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 register(vol_name, vol_dev, false);
        }
        return 0;
 }
@@ -244,14 +328,15 @@ function ubi_down(vol_name) {
        else if (vol_mode == "wp")
                return 16;
        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("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") {
+               unregister(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));
-               block_hotplug("remove", vol_dev);
                return ret;
        }
        return 0;
@@ -279,64 +364,38 @@ function ubi_list(search_name) {
        return volumes;
 }
 
-function ubi_detect() {
-       let tmpdev = [];
+function ubi_register_active() {
        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-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]-*"))
+               if (!wildcard(vol_ubiname, "uvol-r[ow]-*"))
                        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");
+               if (vol_mode == "ro") {
+                       system(sprintf("ubiblock --create /dev/%s", vol_dev));
+                       register(vol_name, sprintf("ubiblock%s", substr(vol_dev, 3)), true);
+               } 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;
 }
 
-function ubi_boot() {
-       for (vol_dir in fs.glob(sprintf("/sys/class/ubi/%s_*", 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;
+function ubi_detect() {
+       return ubi_register_active();
+}
 
-               system(sprintf("ubiblock --create /dev/%s", vol_dev));
-       }
+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) {
+function ubi_write(vol_name, write_size, verify) {
        let vol_dev = ubi_get_dev(vol_name);
        if (!vol_dev)
                return 2;
@@ -349,13 +408,13 @@ function ubi_write(vol_name, write_size) {
        if (vol_mode != "wo")
                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)
                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));
+       if (verify && !verify(sprintf("/dev/%s", vol_dev)))
+               return 74;
+
        system(sprintf("ubirename /dev/%s \"uvol-wo-%s\" \"uvol-rd-%s\"", ubidev, vol_name, vol_name));
 
        return 0;
@@ -376,5 +435,7 @@ backend.device = ubi_device;
 backend.up = ubi_up;
 backend.down = ubi_down;
 backend.create = ubi_create;
+backend.resize = ubi_resize;
 backend.remove = ubi_remove;
+backend.reap = ubi_reap;
 backend.write = ubi_write;
diff --git a/utils/uvol/files/uci.uc b/utils/uvol/files/uci.uc
deleted file mode 100644 (file)
index be3bae1..0000000
+++ /dev/null
@@ -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;
-       }
-};
index 2e2ea2928bec244bb42dce8332af52e06f70d932..1b06bcdf7ab4e933a34a6a3d994a218f83f0a992 100644 (file)
@@ -3,10 +3,13 @@
 // uvol - storage volume manager for OpenWrt
 //  (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 ...
 
 commands:
+  version                      show version and exit
   boot                         get active volumes ready (called on boot)
   free                         show number of bytes available
   total                                show total number of bytes
@@ -25,7 +28,20 @@ commands:
                  1 - volume is not ready for use
                  2 - volume doesn't exist
   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 uci = require("uci");
@@ -34,11 +50,9 @@ let cursor = uci ? uci.cursor() : null;
 let ctx = {};
 ctx.cursor = cursor;
 ctx.fs = fs;
-include("/usr/lib/uvol/uci.uc");
-ctx.uci_add = uvol_uci.uvol_uci_add;
-ctx.uci_remove = uvol_uci.uvol_uci_remove;
-ctx.uci_commit = uvol_uci.uvol_uci_commit;
-ctx.uci_init = uvol_uci.uvol_uci_init;
+include("/usr/lib/uvol/mount.uc");
+ctx.register = uvol_mount.register;
+ctx.unregister = uvol_mount.unregister;
 
 let backend = null;
 let tried_backends = [];
@@ -64,27 +78,53 @@ if (!backend) {
        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 in keys(backend))) {
+       printf("command %s not found\n", cmd);
+       return 22;
 }
 
-if (!cmd || cmd == "-h" || cmd == "help") {
-       printf("%s", help_output);
-       return cmd?0: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");
 }
 
-if (!(cmd in keys(backend))) {
-       printf("command %s not found\n", cmd);
-       return 22;
+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;
@@ -110,6 +150,69 @@ let legacy_output = function(var) {
        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") {
        let ret = backend[cmd](...ARGV);
        if (type(ret) == "int") {
diff --git a/utils/uvol/files/uvol.defaults b/utils/uvol/files/uvol.defaults
deleted file mode 100644 (file)
index 927f8c1..0000000
+++ /dev/null
@@ -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
index d75187368a7155fb7e723ff8ecd5ba4d3699eadf..123b97cae931dc7069d972330128989b8b008570 100644 (file)
@@ -6,16 +6,16 @@ NAME=uvol
 PROG=/usr/sbin/uvol
 
 start_service() {
-       [ "${__BOOT_UVOL}" = "1" ] && return 0
-       procd_open_instance "$NAME"
-       procd_set_param command "$PROG" boot
-       procd_close_instance
-}
+       local ready
 
-boot() {
-       __BOOT_UVOL=1 start
+       ready=$(ubus call block status 2>/dev/null | jsonfilter -e '@.ready')
+       [ "$ready" = "true" ] && "$PROG" boot
 }
 
 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
 }
git clone https://git.99rst.org/PROJECT