@@ -27,5 +27,6 @@ obj-y += swupdate.o \
parsing_library.o \
artifacts_versions.o \
swupdate_dict.o \
+ swupdate_vars.o \
semver.o \
strlcpy.o
@@ -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);
new file mode 100644
@@ -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;
+}
@@ -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
-----------------------
new file mode 100644
@@ -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
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