mirror of
https://github.com/openwrt/packages.git
synced 2026-10-12 05:57:04 +04:00
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:
1 parent
a9fc86bec5
commit
cb60600aec
8 files changed
+574
-362
No files matched your search
+6
-6
@@ -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))
|
||||
|
||||
+230
-88
@@ -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;
|
||||
}
|
||||
|
||||
return 0
|
||||
if (up)
|
||||
return register(vol_name, getdev(lv), substr(lv.lv_name, 0, 2) == "ro");
|
||||
|
||||
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,6 +381,98 @@ function lvm_create(vol_name, vol_size, vol_mode) {
|
||||
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) {
|
||||
if (!vol_name || !vg_name)
|
||||
return 22;
|
||||
@@ -344,16 +481,15 @@ function lvm_remove(vol_name) {
|
||||
if (!res[0])
|
||||
return 2;
|
||||
|
||||
if (res[0].lv_active)
|
||||
return 16;
|
||||
|
||||
let ret = lvm("lvremove", "-y", res[0].lv_full_name);
|
||||
// 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;
|
||||
|
||||
uvol_uci_remove(vol_name);
|
||||
uvol_uci_commit(vol_name);
|
||||
return 0;
|
||||
return lvm_reap();
|
||||
}
|
||||
|
||||
function lvm_dd(in_fd, out_fd, vol_size) {
|
||||
@@ -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;
|
||||
|
||||
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 {
|
||||
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 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;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -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
@@ -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,25 +175,111 @@ function ubi_create(vol_name, vol_size, vol_mode) {
|
||||
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) {
|
||||
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));
|
||||
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;
|
||||
|
||||
uvol_uci_remove(vol_name);
|
||||
uvol_uci_commit(vol_name);
|
||||
|
||||
return 0;
|
||||
return ubi_reap();
|
||||
}
|
||||
|
||||
function ubi_up(vol_name) {
|
||||
@@ -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;
|
||||
|
||||
system(sprintf("ubiblock --create /dev/%s", vol_dev));
|
||||
}
|
||||
function ubi_detect() {
|
||||
return ubi_register_active();
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
@@ -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
@@ -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,29 +78,55 @@ 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 || cmd == "-h" || cmd == "help") {
|
||||
printf("%s", help_output);
|
||||
return cmd?0:22;
|
||||
}
|
||||
|
||||
if (!(cmd in keys(backend))) {
|
||||
printf("command %s not found\n", cmd);
|
||||
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;
|
||||
if (ARGV[0] == "-j") {
|
||||
json_output = true;
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user