[openwrt/openwrt] provision: add script for managing device provisioning data

LEDE Commits lede-commits at lists.infradead.org
Mon Mar 17 05:19:44 PDT 2025


nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/5d401238185f56d7791d0850e4f83d6b1cf2ec7d

commit 5d401238185f56d7791d0850e4f83d6b1cf2ec7d
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Fri Mar 14 14:05:06 2025 +0100

    provision: add script for managing device provisioning data
    
    This is useful for keeping specific data on a device across factory reset.
    It uses a separate partition (only UBI supported at the moment) to store
    the data. The primary use case is storing sensitive data like cryptographic
    keys for maintaining a device as part of a network.
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 package/utils/provision/Makefile                   |  33 ++++
 package/utils/provision/files/usr/sbin/provision   | 107 ++++++++++++
 .../provision/files/usr/share/ucode/provision.uc   | 189 +++++++++++++++++++++
 3 files changed, 329 insertions(+)

diff --git a/package/utils/provision/Makefile b/package/utils/provision/Makefile
new file mode 100644
index 0000000000..49c57379b6
--- /dev/null
+++ b/package/utils/provision/Makefile
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2025 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=provision
+PKG_RELEASE:=$(AUTORELEASE)
+
+PKG_LICENSE:=GPL-2.0
+PKG_MAINTAINER:=Felix Fietkau <nbd at nbd.name>
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/provision
+  SECTION:=utils
+  CATEGORY:=Utilities
+  TITLE:=Utility for managing device provisioning data
+  DEPENDS:=+ucode +ucode-mod-fs +ucode-mod-struct
+endef
+
+define Build/Compile
+	:
+endef
+
+define Package/provision/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,provision))
diff --git a/package/utils/provision/files/usr/sbin/provision b/package/utils/provision/files/usr/sbin/provision
new file mode 100755
index 0000000000..49542e5b8d
--- /dev/null
+++ b/package/utils/provision/files/usr/sbin/provision
@@ -0,0 +1,107 @@
+#!/usr/bin/env ucode
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Felix Fietkau <nbd at nbd.name>
+ */
+'use strict';
+import { basename } from "fs";
+import * as provision from "provision";
+
+const usage_message = `Usage: ${basename(sourcepath())} <command> [<args>]
+
+  Commands:
+   - get [<path>]:			Get string value at <path> (or all if no path given)
+   - set <path> <value>			Set string value at <path> to <value>
+   - get_json [<path>]:			Get JSON data at <path> (or all if no path given)
+   - set_json <path> <value>		Set JSON value at <path> to <value>
+   - delete <path>			Delete value at <path>
+   - reset				Clear provision data
+   - create				Create provisioning partition
+   - destroy				Destroy provisioning partition
+
+`;
+
+
+function usage()
+{
+	warn(usage_message);
+	exit(1);
+}
+
+if (!length(ARGV))
+	usage();
+
+const create_error_msg = `Provisioning partition could not be created.
+This is only supported on devices with UBI for now.
+If there was not enough space, please reflash using the sysugrade -P parameter
+`;
+
+let ctx;
+if (ARGV[0] == "create") {
+	ctx = provision.create();
+	if (!ctx) {
+		warn(create_error_msg);
+		exit(1);
+	}
+	ctx.reset();
+	ctx.commit();
+	exit(0);
+} else {
+	ctx = provision.open();
+	if (!ctx) {
+		warn(`Provisioning partition not found. Try ${basename(sourcepath())} enable\n`);
+		exit(1);
+	}
+}
+ctx.init();
+
+let cmd = shift(ARGV);
+switch (cmd) {
+case "get":
+	let val = ctx.get(ARGV[0]);
+	val ??= "";
+	print(val + "\n");
+	break;
+case "get_json":
+	printf("%.J\n", ctx.get(ARGV[0]));
+	break;
+case "set_json":
+	if (length(ARGV) != 2)
+		usage();
+	ARGV[1] = json(ARGV[1]);
+	if (ARGV[1] == null) {
+		warn('Invalid JSON argument\n');
+		exit(1);
+	}
+	// fallthrough
+case "set":
+	if (length(ARGV) != 2)
+		usage();
+
+	if (!ctx.set(ARGV[0], ARGV[1])) {
+		warn('Set failed\n');
+		exit(1);
+	}
+
+	ctx.commit();
+	break;
+case "delete":
+	if (length(ARGV) != 1)
+		usage();
+
+	if (!ctx.set(ARGV[0])) {
+		warn('Delete failed\n');
+		exit(1);
+	}
+	ctx.commit();
+	break;
+case "reset":
+	ctx.reset();
+	ctx.commit();
+	break;
+case "destroy":
+	ctx.destroy();
+	break;
+default:
+	usage();
+}
diff --git a/package/utils/provision/files/usr/share/ucode/provision.uc b/package/utils/provision/files/usr/share/ucode/provision.uc
new file mode 100644
index 0000000000..3f43331d00
--- /dev/null
+++ b/package/utils/provision/files/usr/share/ucode/provision.uc
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Felix Fietkau <nbd at nbd.name>
+ */
+'use strict';
+import * as struct from "struct";
+import * as fs from "fs";
+
+const MAGIC = 0xf09f8697;
+const HDR_LEN = 9;
+
+let hdr = struct.new(">LLc");
+
+const ubi_proto = {
+	read: function() {
+		let file = fs.open(this.dev);
+		if (!file)
+			return;
+
+		let hdr_data = file.read(HDR_LEN);
+		if (!hdr_data)
+			return;
+
+		hdr_data = hdr.unpack(hdr_data);
+		if (!hdr_data)
+			return;
+
+		if (hdr_data[0] != MAGIC)
+			return;
+
+		if (hdr_data[1] > 131072 || hdr_data[2] != 0)
+			return;
+
+		let data = file.read(hdr_data[1]);
+		if (length(data) != hdr_data[1])
+			return;
+
+		return data;
+	},
+	commit: function(data) {
+		let len = HDR_LEN + length(data);
+
+		let file = fs.popen(`ubiupdatevol ${this.dev} -s ${len} -`, "w");
+		file.write(hdr.pack(MAGIC, length(data), 0));
+		file.write(data);
+
+		return file.close() == 0;
+	},
+	destroy: function() {
+		let dev = replace(this.dev, /_\d+$/, "");
+		return system(`ubirmvol ${dev} -N provisioning`) == 0;
+	}
+};
+
+function open_ubi()
+{
+	let found = fs.glob("/sys/class/ubi/*/name");
+	found = filter(found, (v) => trim(fs.readfile(v)) == "provisioning");
+	if (!length(found))
+		return;
+
+	let dev_name = fs.basename(fs.dirname(found[0]));
+
+	return proto({
+		dev: "/dev/" + dev_name,
+	}, ubi_proto);
+}
+
+function create_ubi()
+{
+	let ctx = open_ubi();
+	if (ctx)
+		return ctx;
+
+	let found = fs.glob("/sys/class/ubi/*/name");
+	found = filter(found, (v) => substr(fs.readfile(v), 0, 6) == "rootfs");
+	if (!length(found))
+		return;
+
+	let dev = fs.basename(fs.dirname(found[0]));
+	dev = "/dev/" + replace(dev, /_\d+$/, "");
+	if (system(`ubimkvol ${dev} -N provisioning -s 131072`) != 0)
+		return;
+
+	return open_ubi();
+}
+
+function data_path_get(data, path, create)
+{
+	if (!data)
+		return;
+
+	if (!length(path))
+		return data;
+
+	if (type(path) == "string")
+		path = split(path, ".");
+
+	let last = data;
+	let last_name;
+	for (let name in path) {
+		switch (type(data)) {
+		case "object":
+			last = data;
+			last_name = name;
+			data = data[name];
+			break;
+		case "array":
+			last = data;
+			last_name = name;
+			data = data[+name];
+			break;
+		default:
+			return;
+		}
+
+		if (data == null && create)
+			data = last[last_name] = {};
+	}
+
+	return data;
+}
+
+const provision_proto = {
+	init: function() {
+		this.data = this.backend.read();
+		try {
+			this.data = json(this.data);
+		} catch(e) {
+			this.data = null;
+		}
+		if (!this.data)
+			this.reset();
+		return true;
+	},
+	get: function(path) {
+		return data_path_get(this.data, path);
+	},
+	set: function(path, value) {
+		if (!length(path))
+			return;
+
+		if (type(path) == "string")
+			path = split(path, ".");
+		let name = pop(path);
+		let data = data_path_get(this.data, path, true);
+		if (type(data) != "object")
+			return;
+
+		if (value == null)
+			delete data[name];
+		else
+			data[name] = value;
+		return true;
+	},
+	reset: function() {
+		this.data = {};
+		return true;
+	},
+	commit: function() {
+		if (!this.data)
+			return;
+
+		return this.backend.commit("" + this.data);
+	},
+	destroy: function() {
+		return this.backend.destroy();
+	}
+};
+
+function __open(backend)
+{
+	if (!backend)
+		return;
+
+	return proto({
+		backend,
+	}, provision_proto);
+}
+
+export function create()
+{
+	return __open(create_ubi());
+};
+
+export function open()
+{
+	return __open(open_ubi());
+};




More information about the lede-commits mailing list