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