diff mbox series

[7/7] Introduce SWUpdate persistent variables

Message ID 20231009153152.416365-8-stefano.babic@swupdate.org
State Accepted
Delegated to: Stefano Babic
Headers show
Series Introduce SWUpdate environment | expand

Commit Message

Stefano Babic Oct. 9, 2023, 3:31 p.m. UTC
The reason for this is that there are many cases where an update does
not require a reboot and does not require to involve the bootloader.
Using bootloader's environment (mainly used up now) as several
side-effects, for example for bootloader that foreseen states but not to
set an arbitrary variable to a value (like EBG for example), and there
is no need to change something for the bootloader.

However, in case the bootloader is not involved, but a reboot is still
required, SWUpdate needs to store persistently how the update ran. The
libubootenv library is the interface to U-Boot, but it is able to store
a U-Boot like environment (variable=value) on different places. Release
0.3.5 is able to handle multiple environment by extending the
configuration file, and this can be used to have an environment for
bootloader and an environment for SWUpdate.

Not that libubootenv does not require U-Boot and can be used on systems
with other bootloaders.

The patch here store the variables set in sw-descriprion via the "vars"
section in a libubootenv namespace configured at start up.

Signed-off-by: Stefano Babic <stefano.babic@swupdate.org>
---
 core/Makefile                 |   1 +
 core/installer.c              |  38 +++++++++++--
 core/swupdate_vars.c          | 103 ++++++++++++++++++++++++++++++++++
 doc/source/sw-description.rst |  28 +++++++++
 include/swupdate_vars.h       |  19 +++++++
 5 files changed, 185 insertions(+), 4 deletions(-)
 create mode 100644 core/swupdate_vars.c
 create mode 100644 include/swupdate_vars.h
diff mbox series

Patch

diff --git a/core/Makefile b/core/Makefile
index 621e6585..8cdf42c0 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -27,5 +27,6 @@  obj-y += swupdate.o \
 	 parsing_library.o \
 	 artifacts_versions.o \
 	 swupdate_dict.o \
+	 swupdate_vars.o \
 	 semver.o \
 	 strlcpy.o
diff --git a/core/installer.c b/core/installer.c
index 0088d1a3..20b5b51e 100644
--- a/core/installer.c
+++ b/core/installer.c
@@ -32,6 +32,7 @@ 
 #include "bootloader.h"
 #include "progress.h"
 #include "pctl.h"
+#include "swupdate_vars.h"
 
 /*
  * function returns:
@@ -162,8 +163,10 @@  static int prepare_var_script(struct dict *dict, const char *script)
 	char buf[MAX_BOOT_SCRIPT_LINE_LENGTH];
 
 	fd = openfileoutput(script);
-	if (fd < 0)
+	if (fd < 0) {
+		ERROR("Temporary file %s cannot be opened for writing", script);
 		return -1;
+	}
 
 	LIST_FOREACH(bootvar, dict, next) {
 		char *key = dict_entry_get_key(bootvar);
@@ -194,6 +197,21 @@  static int update_bootloader_env(struct swupdate_cfg *cfg, const char *script)
 	if ((ret = bootloader_apply_list(script)) < 0) {
 		ERROR("Bootloader-specific error %d updating its environment", ret);
 	}
+
+	return ret;
+}
+
+static int update_swupdate_vars(struct swupdate_cfg *cfg, const char *script)
+{
+	int ret = 0;
+
+	ret = prepare_var_script(&cfg->vars, script);
+	if (ret)
+		return ret;
+
+	if ((ret = swupdate_vars_apply_list(script, cfg->namespace_for_vars)) < 0) {
+		ERROR("Bootloader-specific error %d updating its environment", ret);
+	}
 	return ret;
 }
 
@@ -371,10 +389,18 @@  int install_images(struct swupdate_cfg *sw)
 		return ret;
 	}
 
+	char* script = alloca(strlen(TMPDIR)+strlen(BOOT_SCRIPT_SUFFIX)+1);
+	sprintf(script, "%s%s", TMPDIR, BOOT_SCRIPT_SUFFIX);
+
+	if (!LIST_EMPTY(&sw->vars)) {
+		ret = update_swupdate_vars(sw, script);
+		if (ret) {
+			return ret;
+		}
+	}
+
 	if (!LIST_EMPTY(&sw->bootloader)) {
-		char* bootscript = alloca(strlen(TMPDIR)+strlen(BOOT_SCRIPT_SUFFIX)+1);
-		sprintf(bootscript, "%s%s", TMPDIR, BOOT_SCRIPT_SUFFIX);
-		ret = update_bootloader_env(sw, bootscript);
+		ret = update_bootloader_env(sw, script);
 		if (ret) {
 			return ret;
 		}
@@ -446,7 +472,11 @@  void cleanup_files(struct swupdate_cfg *software) {
 		}
 	}
 
+	/*
+	 * drop environment databases
+	 */
 	dict_drop_db(&software->bootloader);
+	dict_drop_db(&software->vars);
 
 	if (asprintf(&fn, "%s%s", TMPDIR, BOOT_SCRIPT_SUFFIX) != ENOMEM_ASPRINTF) {
 		remove_sw_file(fn);
diff --git a/core/swupdate_vars.c b/core/swupdate_vars.c
new file mode 100644
index 00000000..5d2f95ab
--- /dev/null
+++ b/core/swupdate_vars.c
@@ -0,0 +1,103 @@ 
+/*
+ * (C) Copyright 2023
+ * Stefano Babic, <stefano.babic@swupdate.org>
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <dirent.h>
+#include "generated/autoconf.h"
+#include "util.h"
+#include "dlfcn.h"
+#include "bootloader.h"
+
+#include "swupdate_vars.h"
+
+static inline void libuboot_cleanup(struct uboot_ctx *ctx)
+{
+	libuboot_close(ctx);
+	libuboot_exit(ctx);
+}
+
+int swupdate_vars_initialize(struct uboot_ctx **ctx, const char *namespace)
+{
+	int ret;
+
+	if (!namespace)
+		return -EINVAL;
+
+	ret = libuboot_read_config_ext(ctx, get_fwenv_config());
+	if (ret) {
+		ERROR("Cannot initialize environment from %s", get_fwenv_config());
+		return -EINVAL;
+	}
+
+	*ctx = libuboot_get_namespace(*ctx, namespace);
+
+	if (libuboot_open(*ctx) < 0) {
+		WARN("Cannot read environment, maybe still empty ?");
+	}
+
+	return 0;
+}
+
+char *swupdate_vars_get(const char *name, const char *namespace)
+{
+	int ret;
+	struct uboot_ctx *ctx = NULL;
+	char *value = NULL;
+
+	ret = swupdate_vars_initialize(&ctx, namespace);
+	if (!ret) {
+		value = libuboot_get_env(ctx, name);
+	}
+	libuboot_cleanup(ctx);
+
+	return value;
+}
+
+int swupdate_vars_set(const char *name, const char *value, const char *namespace)
+{
+	int ret;
+	struct uboot_ctx *ctx = NULL;
+
+	ret = swupdate_vars_initialize(&ctx, namespace);
+	if (!ret) {
+		libuboot_set_env(ctx, name, value);
+		ret = libuboot_env_store(ctx);
+	}
+
+	libuboot_cleanup(ctx);
+
+	return ret;
+}
+
+int swupdate_vars_unset(const char *name, const char *namespace)
+{
+	return swupdate_vars_set(name, NULL, namespace);
+}
+
+int swupdate_vars_apply_list(const char *filename, const char *namespace)
+{
+	int ret;
+	struct uboot_ctx *ctx = NULL;
+
+	ret = swupdate_vars_initialize(&ctx, namespace);
+	if (!ret) {
+		libuboot_load_file(ctx, filename);
+		ret = libuboot_env_store(ctx);
+	}
+
+	libuboot_cleanup(ctx);
+
+	return ret;
+}
diff --git a/doc/source/sw-description.rst b/doc/source/sw-description.rst
index 03f6faff..3b910295 100644
--- a/doc/source/sw-description.rst
+++ b/doc/source/sw-description.rst
@@ -948,6 +948,34 @@  For backward compatibility with previously built `.swu` images, the
 "uboot" group name is still supported as an alias. However, its usage
 is deprecated.
 
+SWUpdate persistent variables
+-----------------------------
+
+Not all updates require to inform the bootloader about the update, and in many cases a
+reboot is not required. There are also cases where changing bootloader's environment
+is unwanted due to restriction for security.
+SWUpdate needs then some information after new software is running to understand if
+everything is fine or some actions like a fallback are needed. SWUpdate can store
+such as information in variables (like shell variables), that can be stored persistently.
+The library `libubootenv` provide a way for saving such kind as database in a power-cut safe mode.
+It uses the algorythm originally implemented in the U-Boot bootloader. It is then guaranteed
+that the system will always have a valid instance of the environment. The library supports multiple
+environment databases at the same time, identifies with `namespaces`.
+SWUpdate should be configured to set the namespace used for own variables. This is done by setting
+the attribute *namespace-vars* in the runtime configuration file (swupdate.cfg). See also
+example/configuration/swupdate.cfg for details.
+
+The format is the same used with bootloader for single variable:
+
+	vars: (
+		{
+			name = <Variable name>;
+			value = <Variable value>;
+		}
+	)
+
+SWUpdate will set these variables all at once like the bootloader variables. These environment
+is stored just before writing the bootloader environment, that is always the last step in an update.
 
 Board specific settings
 -----------------------
diff --git a/include/swupdate_vars.h b/include/swupdate_vars.h
new file mode 100644
index 00000000..0334f427
--- /dev/null
+++ b/include/swupdate_vars.h
@@ -0,0 +1,19 @@ 
+/*
+ * (C) Copyright 2023
+ * Stefano Babic, <stefano.babic@swupdate.org>
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+#ifndef _SWVARS_H
+#define _SWVARS_H
+
+#include <libuboot.h>
+
+int swupdate_vars_initialize(struct uboot_ctx **ctx, const char *namespace);
+int swupdate_vars_apply_list(const char *filename, const char *namespace);
+char *swupdate_vars_get(const char *name, const char *namespace);
+int swupdate_vars_set(const char *name, const char *value, const char *namespace);
+int swupdate_vars_unset(const char *name, const char *namespace);
+
+#endif