diff mbox series

[v3,1/5] bootloader: runtime-dynamic bootloader selection

Message ID 20220322112903.32726-1-christian.storm@siemens.com
State Accepted
Headers show
Series [v3,1/5] bootloader: runtime-dynamic bootloader selection | expand

Commit Message

Storm, Christian March 22, 2022, 11:28 a.m. UTC
Make the bootloader interface implementation selectable at run-time.
The mechanism is three-staged with the latter overriding the former:
(1) compile-time selected default bootloader interface implementation
(2) run-time configuration file selected bootloader
    interface implementation
(3) run-time command line selected bootloader interface
    implementation (by the new -B switch)

At compile-time, a set of enabled bootloader interface implementations
is selected. Via dynamically linking against the enabled bootloaders'
shared libraries (currently EFI Boot Guard and U-Boot), the very same
SWUpdate compile configuration can be used for different use cases
requiring different bootloaders, simply by run-time configuration
rather than having to recompile/repackage SWUpdate for each. This
helps distributions to pick up and package SWUpdate more easily.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
---
 Kconfig                             |   8 --
 Makefile                            |   4 +-
 Makefile.deps                       |   8 --
 Makefile.flags                      |  17 ++++-
 bootloader/Config.in                | 110 ++++++++++++++++++----------
 bootloader/Makefile                 |   8 +-
 core/Makefile                       |   1 +
 core/bootloader.c                   |  71 ++++++++++++++++++
 core/swupdate.c                     |  34 ++++++++-
 doc/source/swupdate.rst             |   3 +
 examples/configuration/swupdate.cfg |   5 ++
 include/bootloader.h                |  64 +++++++++++++---
 include/util.h                      |   1 +
 13 files changed, 259 insertions(+), 75 deletions(-)
 create mode 100644 core/bootloader.c

Comments

Stefano Babic April 11, 2022, 7:52 p.m. UTC | #1
Hi Christian,

applied (whole series) to -master, thanks !

Best regards,
Stefano

On 22.03.22 12:28, Christian Storm wrote:
> Make the bootloader interface implementation selectable at run-time.
> The mechanism is three-staged with the latter overriding the former:
> (1) compile-time selected default bootloader interface implementation
> (2) run-time configuration file selected bootloader
>      interface implementation
> (3) run-time command line selected bootloader interface
>      implementation (by the new -B switch)
> 
> At compile-time, a set of enabled bootloader interface implementations
> is selected. Via dynamically linking against the enabled bootloaders'
> shared libraries (currently EFI Boot Guard and U-Boot), the very same
> SWUpdate compile configuration can be used for different use cases
> requiring different bootloaders, simply by run-time configuration
> rather than having to recompile/repackage SWUpdate for each. This
> helps distributions to pick up and package SWUpdate more easily.
> 
> Signed-off-by: Christian Storm <christian.storm@siemens.com>
> ---
>   Kconfig                             |   8 --
>   Makefile                            |   4 +-
>   Makefile.deps                       |   8 --
>   Makefile.flags                      |  17 ++++-
>   bootloader/Config.in                | 110 ++++++++++++++++++----------
>   bootloader/Makefile                 |   8 +-
>   core/Makefile                       |   1 +
>   core/bootloader.c                   |  71 ++++++++++++++++++
>   core/swupdate.c                     |  34 ++++++++-
>   doc/source/swupdate.rst             |   3 +
>   examples/configuration/swupdate.cfg |   5 ++
>   include/bootloader.h                |  64 +++++++++++++---
>   include/util.h                      |   1 +
>   13 files changed, 259 insertions(+), 75 deletions(-)
>   create mode 100644 core/bootloader.c
> 
> diff --git a/Kconfig b/Kconfig
> index da6af8d..85fa5fd 100644
> --- a/Kconfig
> +++ b/Kconfig
> @@ -53,14 +53,6 @@ config HAVE_LIBUBI
>   	bool
>   	option env="HAVE_LIBUBI"
>   
> -config HAVE_LIBUBOOTENV
> -	bool
> -	option env="HAVE_LIBUBOOTENV"
> -
> -config HAVE_LIBEBGENV
> -	bool
> -	option env="HAVE_LIBEBGENV"
> -
>   config HAVE_LIBEXT2FS
>   	bool
>   	option env="HAVE_LIBEXT2FS"
> diff --git a/Makefile b/Makefile
> index 932330c..8d30532 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -362,8 +362,8 @@ include $(srctree)/Makefile.flags
>   # This allow a user to issue only 'make' to build a kernel including modules
>   # Defaults to vmlinux, but the arch makefile usually adds further targets
>   
> -objs-y		:= core handlers
> -libs-y		:= corelib mongoose parser suricatta bootloader fs
> +objs-y		:= core handlers bootloader
> +libs-y		:= corelib mongoose parser suricatta fs
>   bindings-y	:= bindings
>   tools-y		:= tools
>   
> diff --git a/Makefile.deps b/Makefile.deps
> index 58ed373..08df4e2 100644
> --- a/Makefile.deps
> +++ b/Makefile.deps
> @@ -54,14 +54,6 @@ ifeq ($(HAVE_ZSTD),)
>   export HAVE_ZSTD = y
>   endif
>   
> -ifeq ($(HAVE_LIBUBOOTENV),)
> -export HAVE_LIBUBOOTENV = y
> -endif
> -
> -ifeq ($(HAVE_LIBEBGENV),)
> -export HAVE_LIBEBGENV = y
> -endif
> -
>   ifeq ($(HAVE_LIBEXT2FS),)
>   export HAVE_LIBEXT2FS = y
>   endif
> diff --git a/Makefile.flags b/Makefile.flags
> index 019ef77..9d9163d 100644
> --- a/Makefile.flags
> +++ b/Makefile.flags
> @@ -210,7 +210,7 @@ LDLIBS += gpiod
>   endif
>   
>   ifeq ($(CONFIG_UBOOT),y)
> -LDLIBS += z ubootenv
> +LDLIBS += dl
>   endif
>   
>   ifeq ($(CONFIG_SYSTEMD),y)
> @@ -218,7 +218,20 @@ LDLIBS += systemd
>   endif
>   
>   ifeq ($(CONFIG_BOOTLOADER_EBG),y)
> -LDLIBS += ebgenv z
> +LDLIBS += dl
> +endif
> +
> +ifneq ($(CONFIG_BOOTLOADER_DEFAULT_NONE),)
> +KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="none"
> +endif
> +ifneq ($(CONFIG_BOOTLOADER_DEFAULT_GRUB),)
> +KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="grub"
> +endif
> +ifneq ($(CONFIG_BOOTLOADER_DEFAULT_UBOOT),)
> +KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="uboot"
> +endif
> +ifneq ($(CONFIG_BOOTLOADER_DEFAULT_EBG),)
> +KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="ebg"
>   endif
>   
>   # SWU forwarder
> diff --git a/bootloader/Config.in b/bootloader/Config.in
> index ad9fec1..a0ebeef 100644
> --- a/bootloader/Config.in
> +++ b/bootloader/Config.in
> @@ -2,56 +2,100 @@
>   #
>   # SPDX-License-Identifier: GPL-2.0-only
>   
> -choice
> -	prompt "Bootloader (U-Boot, ..)"
> -	default UBOOT
> +menu "Bootloader Interfaces"
> +
> +config BOOTLOADER_NONE
> +	bool "Environment in RAM"
>   	help
> -	  Choose the bootloader
> +	  This simulates the interface to a bootloader.
> +	  Bootloader environment is just maintained in RAM
> +	  and lost when SWUpdate exits.
>   
>   config BOOTLOADER_EBG
>   	bool "EFI Boot Guard"
> -	depends on HAVE_LIBEBGENV
> -	depends on HAVE_ZLIB
>   	help
>   	  Support for EFI Boot Guard
>   	  https://github.com/siemens/efibootguard
>   
> -comment "EFI Boot Guard needs libebgenv and libz"
> -	depends on !HAVE_ZLIB || !HAVE_LIBEBGENV
> -
>   config UBOOT
>   	bool "U-Boot"
> -	depends on HAVE_LIBUBOOTENV
> -	depends on HAVE_ZLIB
>   	help
>   	  Support for U-Boot
>   	  https://www.denx.de/wiki/U-Boot
>   
> -comment "U-Boot support needs libubootenv and libz"
> -	depends on !HAVE_LIBUBOOTENV || !HAVE_ZLIB
> +config UBOOT_FWENV
> +	string "U-Boot Environment Configuration file"
> +	depends on UBOOT
> +	default "/etc/fw_env.config"
> +	help
> +	  This is the file described in U-Boot documentation
> +	  in the tools directory. It tells where the U-Boot
> +	  environment is saved.
>   
> -config BOOTLOADER_NONE
> -	bool "Environment in RAM"
> +config UBOOT_DEFAULTENV
> +	string "U-Boot Initial Environment file"
> +	depends on UBOOT
> +	default "/etc/u-boot-initial-env"
>   	help
> -	  This simulates the interface to a bootloader.
> -	  Bootloader environment is just maitained in RAM
> -	  and lost when SWUpdate exits.
> +	  This is the file with the initial environment delivered
> +	  with the bootloader. It is used by SWUpdate if no environment
> +	  is found on the storage.
>   
>   config BOOTLOADER_GRUB
>   	bool "GRUB"
>   	help
>   	  Support for GRUB
>   	  https://www.gnu.org/software/grub/
> -endchoice
>   
> -config UBOOT_FWENV
> -	string "U-Boot Environment Configuration file"
> +config GRUBENV_PATH
> +	string "GRUB Environment block file path"
> +	depends on BOOTLOADER_GRUB
> +	default "/boot/efi/EFI/BOOT/grub/grubenv"
> +	help
> +	  Provide path to GRUB environment block file
> +
> +endmenu
> +
> +choice
> +	prompt "Default Bootloader Interface"
> +	help
> +	  Default bootloader interface to use if not explicitly
> +	  overridden via configuration or command-line option
> +	  at run-time.
> +
> +config BOOTLOADER_DEFAULT_EBG
> +	bool "EBG"
> +	depends on BOOTLOADER_EBG
> +	help
> +	  Use EFI Boot Guard as default bootloader interface.
> +	
> +	  Note: Make sure the environment modification shared
> +	  library https://github.com/siemens/efibootguard (including
> +	  dependencies) is installed on the target system.
> +
> +config BOOTLOADER_DEFAULT_UBOOT
> +	bool "U-Boot"
>   	depends on UBOOT
> -	default "/etc/fw_env.config"
>   	help
> -	  This is the file described in U-Boot documentation
> -	  in the tools directory. It tells where the U-Boot
> -	  environment is saved.
> +	  Use U-Boot as default bootloader interface.
> +	
> +	  Note: Make sure the environment modification shared
> +	  library https://github.com/sbabic/libubootenv (including
> +	  dependencies) is installed on the target system.
> +
> +config BOOTLOADER_DEFAULT_GRUB
> +	bool "GRUB"
> +	depends on BOOTLOADER_GRUB
> +	help
> +	  Use GRUB as default bootloader interface.
> +
> +config BOOTLOADER_DEFAULT_NONE
> +	bool "Environment in RAM"
> +	depends on BOOTLOADER_NONE
> +	help
> +	  Use Environment in RAM as default bootloader interface.
> +
> +endchoice
>   
>   choice
>   	prompt "Update Status Storage"
> @@ -67,6 +111,7 @@ config UPDATE_STATE_CHOICE_NONE
>   
>   config UPDATE_STATE_CHOICE_BOOTLOADER
>   	bool "Bootloader"
> +	depends on BOOTLOADER_EBG || UBOOT || BOOTLOADER_NONE || BOOTLOADER_GRUB
>   	help
>   	  Store update status in Bootloader's environment.
>   	  Specify Bootloader environment variable name to store update status in.
> @@ -80,18 +125,3 @@ config UPDATE_STATE_BOOTLOADER
>   	help
>   	  Store update information in Bootloader's environment.
>   
> -config UBOOT_DEFAULTENV
> -	string "U-Boot Initial Environment file"
> -	depends on UBOOT
> -	default "/etc/u-boot-initial-env"
> -	help
> -	  This is the file with the initial environment delivered
> -	  with the bootloader. It is used by SWUpdate if no environment
> -	  is found on the storage.
> -
> -config GRUBENV_PATH
> -	string "GRUB Environment block file path"
> -	depends on BOOTLOADER_GRUB
> -	default "/boot/efi/EFI/BOOT/grub/grubenv"
> -	help
> -	  Provide path to GRUB environment block file
> diff --git a/bootloader/Makefile b/bootloader/Makefile
> index ff56387..2c34291 100644
> --- a/bootloader/Makefile
> +++ b/bootloader/Makefile
> @@ -2,7 +2,7 @@
>   #
>   # SPDX-License-Identifier:     GPL-2.0-only
>   #
> -lib-$(CONFIG_UBOOT)		+= uboot.o
> -lib-$(CONFIG_BOOTLOADER_NONE)	+= none.o
> -lib-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
> -lib-$(CONFIG_BOOTLOADER_EBG)		+= ebg.o
> +obj-$(CONFIG_UBOOT)		+= uboot.o
> +obj-$(CONFIG_BOOTLOADER_NONE)	+= none.o
> +obj-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
> +obj-$(CONFIG_BOOTLOADER_EBG)	+= ebg.o
> diff --git a/core/Makefile b/core/Makefile
> index fa30e6e..90f1177 100644
> --- a/core/Makefile
> +++ b/core/Makefile
> @@ -12,6 +12,7 @@ obj-y += swupdate.o \
>   	 cpio_utils.o \
>   	 notifier.o \
>   	 handler.o \
> +	 bootloader.o \
>   	 install_from_file.o \
>   	 util.o \
>   	 parser.o \
> diff --git a/core/bootloader.c b/core/bootloader.c
> new file mode 100644
> index 0000000..f34cb41
> --- /dev/null
> +++ b/core/bootloader.c
> @@ -0,0 +1,71 @@
> +/*
> + * Author: Christian Storm
> + * Copyright (C) 2022, Siemens AG
> + *
> + * SPDX-License-Identifier:     GPL-2.0-only
> + */
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <util.h>
> +#include <bootloader.h>
> +
> +int   (*bootloader_env_set)(const char *, const char *);
> +int   (*bootloader_env_unset)(const char *);
> +char* (*bootloader_env_get)(const char *);
> +int   (*bootloader_apply_list)(const char *);
> +
> +typedef struct {
> +	const char *name;
> +	bootloader *funcs;
> +} entry;
> +
> +static entry *current = NULL;
> +static entry *available = NULL;
> +static unsigned int num_available = 0;
> +
> +int register_bootloader(const char *name, bootloader *bl)
> +{
> +	entry *tmp = reallocarray(available, num_available + 1, sizeof(entry));
> +	if (!tmp) {
> +		return -ENOMEM;
> +	}
> +	tmp[num_available].name = (char*)name;
> +	tmp[num_available].funcs = bl;
> +	num_available++;
> +	available = tmp;
> +	return 0;
> +}
> +
> +int set_bootloader(const char *name)
> +{
> +	if (!name) {
> +		return -ENOENT;
> +	}
> +	for (unsigned int i = 0; i < num_available; i++) {
> +		if (available[i].funcs &&
> +		    (strcmp(available[i].name, name) == 0)) {
> +			bootloader_env_set = available[i].funcs->env_set;
> +			bootloader_env_get = available[i].funcs->env_get;
> +			bootloader_env_unset = available[i].funcs->env_unset;
> +			bootloader_apply_list = available[i].funcs->apply_list;
> +			current = &available[i];
> +			return 0;
> +		}
> +	}
> +	return -ENOENT;
> +}
> +
> +const char* get_bootloader(void)
> +{
> +	return current ? current->name : NULL;
> +}
> +
> +void print_registered_bootloaders(void)
> +{
> +	INFO("Registered bootloaders:");
> +	for (unsigned int i = 0; i < num_available; i++) {
> +		INFO("\t%s\t%s", available[i].name,
> +		     available[i].funcs == NULL ? "shared lib not found."
> +						: "loaded.");
> +	}
> +}
> diff --git a/core/swupdate.c b/core/swupdate.c
> index b59e0eb..d2035f9 100644
> --- a/core/swupdate.c
> +++ b/core/swupdate.c
> @@ -114,6 +114,7 @@ static struct option long_options[] = {
>   #ifdef CONFIG_WEBSERVER
>   	{"webserver", required_argument, NULL, 'w'},
>   #endif
> +	{"bootloader", required_argument, NULL, 'B'},
>   	{NULL, 0, NULL, 0}
>   };
>   
> @@ -129,6 +130,7 @@ static void usage(char *programname)
>   #ifdef CONFIG_UBIATTACH
>   		" -b, --blacklist <list of mtd>  : MTDs that must not be scanned for UBI\n"
>   #endif
> +		" -B, --bootloader               : bootloader interface (default: " PREPROCVALUE(BOOTLOADER_DEFAULT) ")\n"
>   		" -p, --postupdate               : execute post-update command\n"
>   		" -P, --preupdate                : execute pre-update command\n"
>   		" -e, --select <software>,<mode> : Select software images set and source\n"
> @@ -283,6 +285,15 @@ static int read_globals_settings(void *elem, void *data)
>   	char tmp[SWUPDATE_GENERAL_STRING_SIZE] = "";
>   	struct swupdate_cfg *sw = (struct swupdate_cfg *)data;
>   
> +	GET_FIELD_STRING(LIBCFG_PARSER, elem,
> +				"bootloader", tmp);
> +	if (tmp[0] != '\0') {
> +		if (set_bootloader(tmp) != 0) {
> +			ERROR("Bootloader interface '%s' could not be initialized.", tmp);
> +			exit(EXIT_FAILURE);
> +		}
> +		tmp[0] = '\0';
> +	}
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
>   				"public-key-file", sw->publickeyfname);
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
> @@ -435,7 +446,7 @@ int main(int argc, char **argv)
>   #endif
>   	memset(main_options, 0, sizeof(main_options));
>   	memset(image_url, 0, sizeof(image_url));
> -	strcpy(main_options, "vhni:e:gq:l:Lcf:p:P:o:N:R:Mm");
> +	strcpy(main_options, "vhni:e:gq:l:Lcf:p:P:o:N:R:MmB:");
>   #ifdef CONFIG_MTD
>   	strcat(main_options, "b:");
>   #endif
> @@ -581,6 +592,13 @@ int main(int argc, char **argv)
>   		case 'o':
>   			strlcpy(swcfg.output, optarg, sizeof(swcfg.output));
>   			break;
> +		case 'B':
> +			if (set_bootloader(optarg) != 0) {
> +				ERROR("Bootloader interface '%s' could not be initialized.", optarg);
> +				print_registered_bootloaders();
> +				exit(EXIT_FAILURE);
> +			}
> +			break;
>   		case 'l':
>   			loglevel = strtoul(optarg, NULL, 10);
>   			break;
> @@ -754,6 +772,20 @@ int main(int argc, char **argv)
>   	printf("Licensed under GPLv2. See source distribution for detailed "
>   		"copyright notices.\n\n");
>   
> +	print_registered_bootloaders();
> +	if (!get_bootloader()) {
> +		if (set_bootloader(PREPROCVALUE(BOOTLOADER_DEFAULT)) != 0) {
> +			ERROR("Default bootloader interface '" PREPROCVALUE(
> +			    BOOTLOADER_DEFAULT) "' couldn't be loaded.");
> +			INFO("Check that the bootloader interface shared library is present.");
> +			INFO("Or chose another bootloader interface by supplying -B <loader>.");
> +			exit(EXIT_FAILURE);
> +		}
> +		INFO("Using default bootloader interface: " PREPROCVALUE(BOOTLOADER_DEFAULT));
> +	} else {
> +		INFO("Using bootloader interface: %s", get_bootloader());
> +	}
> +
>   	/*
>   	 * Install a child handler to check if a subprocess
>   	 * dies
> diff --git a/doc/source/swupdate.rst b/doc/source/swupdate.rst
> index 7bd6c92..802b16a 100644
> --- a/doc/source/swupdate.rst
> +++ b/doc/source/swupdate.rst
> @@ -467,6 +467,9 @@ Command line parameters
>   |             |          | Example: U-Boot and environment in MTD0-1: |
>   |             |          | ``swupdate -b "0 1"``.                     |
>   +-------------+----------+--------------------------------------------+
> +| -B <loader> | string   | Override the default bootloader interface  |
> +|             |          | to use ``loader`` instead.                 |
> ++-------------+----------+--------------------------------------------+
>   | -e <sel>    | string   | ``sel`` is in the format <software>,<mode>.|
>   |             |          | It allows one to find a subset of rules in |
>   |             |          | the sw-description file. With it,          |
> diff --git a/examples/configuration/swupdate.cfg b/examples/configuration/swupdate.cfg
> index 2f19ffa..76d0a02 100644
> --- a/examples/configuration/swupdate.cfg
> +++ b/examples/configuration/swupdate.cfg
> @@ -43,6 +43,11 @@
>   #			  set expected common name of signer certificate
>   # select:		: string
>   #			  select software images set and source (<software>,<mode>)
> +# bootloader:		: string
> +#			  bootloader interface to use, overruling compile-time default.
> +#			  Possible values are ebg, grub, uboot, and none for
> +#			  EFI Boot Guard, U-Boot, GRUB, and the Environment in RAM bootloader,
> +#			  respectively, given the respective bootloader support is compiled-in.
>   globals :
>   {
>   
> diff --git a/include/bootloader.h b/include/bootloader.h
> index 7bf5fa3..8315759 100644
> --- a/include/bootloader.h
> +++ b/include/bootloader.h
> @@ -5,8 +5,56 @@
>    * SPDX-License-Identifier:     GPL-2.0-only
>    */
>   
> -#ifndef _BOOTLOADER_INTERFACE_H
> -#define _BOOTLOADER_INTERFACE_H
> +#pragma once
> +
> +#define load_symbol(handle, container, fname) \
> +	*(void**)(container) = dlsym(handle, fname); \
> +	if (dlerror() != NULL) { \
> +		(void)dlclose(handle); \
> +		return NULL; \
> +	}
> +
> +typedef struct {
> +	int (*env_set)(const char *, const char *);
> +	int (*env_unset)(const char *);
> +	char* (*env_get)(const char *);
> +	int (*apply_list)(const char *);
> +} bootloader;
> +
> +/*
> + * register_bootloader - register bootloader.
> + *
> + * @name : bootloader's name to register.
> + * @bootloader : struct bootloader with bootloader details.
> + *
> + * Return:
> + *   0 on success, -ENOMEM on error.
> + */
> +int register_bootloader(const char *name, bootloader *bl);
> +
> +/*
> + * set_bootloader - set bootloader to use.
> + *
> + * @name : bootloader's name to set.
> + *
> + * Return:
> + *   0 on success, -ENOENT on error.
> + */
> +int set_bootloader(const char *name);
> +
> +/*
> + * get_bootloader - get set bootloader's name
> + *
> + * Return:
> + *   name on success, NULL on error.
> + */
> +const char* get_bootloader(void);
> +
> +/*
> + * print_registered_bootloaders - print registered bootloaders
> + */
> +void print_registered_bootloaders(void);
> +
>   
>   /*
>    * bootloader_env_set - modify a variable
> @@ -17,8 +65,7 @@
>    * Return:
>    *   0 on success
>    */
> -
> -int bootloader_env_set(const char *name, const char *value);
> +extern int (*bootloader_env_set)(const char *, const char *);
>   
>   /*
>    * bootloader_env_unset - drop a variable
> @@ -28,8 +75,7 @@ int bootloader_env_set(const char *name, const char *value);
>    * Return:
>    *   0 on success
>    */
> -
> -int bootloader_env_unset(const char *name);
> +extern int (*bootloader_env_unset)(const char *);
>   
>   /*
>    * bootloader_env_get - get value of a variable
> @@ -40,8 +86,7 @@ int bootloader_env_unset(const char *name);
>    *   string if variable is found
>    *   NULL if no variable with name is found
>    */
> -
> -char *bootloader_env_get(const char *name);
> +extern char* (*bootloader_env_get)(const char *);
>   
>   /*
>    * bootloader_apply_list - set multiple variables
> @@ -51,6 +96,5 @@ char *bootloader_env_get(const char *name);
>    * Return:
>    *   0 on success
>    */
> -int bootloader_apply_list(const char *script);
> +extern int (*bootloader_apply_list)(const char *);
>   
> -#endif
> diff --git a/include/util.h b/include/util.h
> index 270978d..302ea04 100644
> --- a/include/util.h
> +++ b/include/util.h
> @@ -125,6 +125,7 @@ void notifier_set_color(int level, char *col);
>   #endif
>   
>   #define STRINGIFY(...) #__VA_ARGS__
> +#define PREPROCVALUE(s) STRINGIFY(s)
>   #define SETSTRING(p, v) do { \
>   	if (p) \
>   		free(p); \
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index da6af8d..85fa5fd 100644
--- a/Kconfig
+++ b/Kconfig
@@ -53,14 +53,6 @@  config HAVE_LIBUBI
 	bool
 	option env="HAVE_LIBUBI"
 
-config HAVE_LIBUBOOTENV
-	bool
-	option env="HAVE_LIBUBOOTENV"
-
-config HAVE_LIBEBGENV
-	bool
-	option env="HAVE_LIBEBGENV"
-
 config HAVE_LIBEXT2FS
 	bool
 	option env="HAVE_LIBEXT2FS"
diff --git a/Makefile b/Makefile
index 932330c..8d30532 100644
--- a/Makefile
+++ b/Makefile
@@ -362,8 +362,8 @@  include $(srctree)/Makefile.flags
 # This allow a user to issue only 'make' to build a kernel including modules
 # Defaults to vmlinux, but the arch makefile usually adds further targets
 
-objs-y		:= core handlers
-libs-y		:= corelib mongoose parser suricatta bootloader fs
+objs-y		:= core handlers bootloader
+libs-y		:= corelib mongoose parser suricatta fs
 bindings-y	:= bindings
 tools-y		:= tools
 
diff --git a/Makefile.deps b/Makefile.deps
index 58ed373..08df4e2 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -54,14 +54,6 @@  ifeq ($(HAVE_ZSTD),)
 export HAVE_ZSTD = y
 endif
 
-ifeq ($(HAVE_LIBUBOOTENV),)
-export HAVE_LIBUBOOTENV = y
-endif
-
-ifeq ($(HAVE_LIBEBGENV),)
-export HAVE_LIBEBGENV = y
-endif
-
 ifeq ($(HAVE_LIBEXT2FS),)
 export HAVE_LIBEXT2FS = y
 endif
diff --git a/Makefile.flags b/Makefile.flags
index 019ef77..9d9163d 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -210,7 +210,7 @@  LDLIBS += gpiod
 endif
 
 ifeq ($(CONFIG_UBOOT),y)
-LDLIBS += z ubootenv
+LDLIBS += dl
 endif
 
 ifeq ($(CONFIG_SYSTEMD),y)
@@ -218,7 +218,20 @@  LDLIBS += systemd
 endif
 
 ifeq ($(CONFIG_BOOTLOADER_EBG),y)
-LDLIBS += ebgenv z
+LDLIBS += dl
+endif
+
+ifneq ($(CONFIG_BOOTLOADER_DEFAULT_NONE),)
+KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="none"
+endif
+ifneq ($(CONFIG_BOOTLOADER_DEFAULT_GRUB),)
+KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="grub"
+endif
+ifneq ($(CONFIG_BOOTLOADER_DEFAULT_UBOOT),)
+KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="uboot"
+endif
+ifneq ($(CONFIG_BOOTLOADER_DEFAULT_EBG),)
+KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="ebg"
 endif
 
 # SWU forwarder
diff --git a/bootloader/Config.in b/bootloader/Config.in
index ad9fec1..a0ebeef 100644
--- a/bootloader/Config.in
+++ b/bootloader/Config.in
@@ -2,56 +2,100 @@ 
 #
 # SPDX-License-Identifier: GPL-2.0-only
 
-choice
-	prompt "Bootloader (U-Boot, ..)"
-	default UBOOT
+menu "Bootloader Interfaces"
+
+config BOOTLOADER_NONE
+	bool "Environment in RAM"
 	help
-	  Choose the bootloader
+	  This simulates the interface to a bootloader.
+	  Bootloader environment is just maintained in RAM
+	  and lost when SWUpdate exits.
 
 config BOOTLOADER_EBG
 	bool "EFI Boot Guard"
-	depends on HAVE_LIBEBGENV
-	depends on HAVE_ZLIB
 	help
 	  Support for EFI Boot Guard
 	  https://github.com/siemens/efibootguard
 
-comment "EFI Boot Guard needs libebgenv and libz"
-	depends on !HAVE_ZLIB || !HAVE_LIBEBGENV
-
 config UBOOT
 	bool "U-Boot"
-	depends on HAVE_LIBUBOOTENV
-	depends on HAVE_ZLIB
 	help
 	  Support for U-Boot
 	  https://www.denx.de/wiki/U-Boot
 
-comment "U-Boot support needs libubootenv and libz"
-	depends on !HAVE_LIBUBOOTENV || !HAVE_ZLIB
+config UBOOT_FWENV
+	string "U-Boot Environment Configuration file"
+	depends on UBOOT
+	default "/etc/fw_env.config"
+	help
+	  This is the file described in U-Boot documentation
+	  in the tools directory. It tells where the U-Boot
+	  environment is saved.
 
-config BOOTLOADER_NONE
-	bool "Environment in RAM"
+config UBOOT_DEFAULTENV
+	string "U-Boot Initial Environment file"
+	depends on UBOOT
+	default "/etc/u-boot-initial-env"
 	help
-	  This simulates the interface to a bootloader.
-	  Bootloader environment is just maitained in RAM
-	  and lost when SWUpdate exits.
+	  This is the file with the initial environment delivered
+	  with the bootloader. It is used by SWUpdate if no environment
+	  is found on the storage.
 
 config BOOTLOADER_GRUB
 	bool "GRUB"
 	help
 	  Support for GRUB
 	  https://www.gnu.org/software/grub/
-endchoice
 
-config UBOOT_FWENV
-	string "U-Boot Environment Configuration file"
+config GRUBENV_PATH
+	string "GRUB Environment block file path"
+	depends on BOOTLOADER_GRUB
+	default "/boot/efi/EFI/BOOT/grub/grubenv"
+	help
+	  Provide path to GRUB environment block file
+
+endmenu
+
+choice
+	prompt "Default Bootloader Interface"
+	help
+	  Default bootloader interface to use if not explicitly
+	  overridden via configuration or command-line option
+	  at run-time.
+
+config BOOTLOADER_DEFAULT_EBG
+	bool "EBG"
+	depends on BOOTLOADER_EBG
+	help
+	  Use EFI Boot Guard as default bootloader interface.
+	  
+	  Note: Make sure the environment modification shared
+	  library https://github.com/siemens/efibootguard (including
+	  dependencies) is installed on the target system.
+
+config BOOTLOADER_DEFAULT_UBOOT
+	bool "U-Boot"
 	depends on UBOOT
-	default "/etc/fw_env.config"
 	help
-	  This is the file described in U-Boot documentation
-	  in the tools directory. It tells where the U-Boot
-	  environment is saved.
+	  Use U-Boot as default bootloader interface.
+	  
+	  Note: Make sure the environment modification shared
+	  library https://github.com/sbabic/libubootenv (including
+	  dependencies) is installed on the target system.
+
+config BOOTLOADER_DEFAULT_GRUB
+	bool "GRUB"
+	depends on BOOTLOADER_GRUB
+	help
+	  Use GRUB as default bootloader interface.
+
+config BOOTLOADER_DEFAULT_NONE
+	bool "Environment in RAM"
+	depends on BOOTLOADER_NONE
+	help
+	  Use Environment in RAM as default bootloader interface.
+
+endchoice
 
 choice
 	prompt "Update Status Storage"
@@ -67,6 +111,7 @@  config UPDATE_STATE_CHOICE_NONE
 
 config UPDATE_STATE_CHOICE_BOOTLOADER
 	bool "Bootloader"
+	depends on BOOTLOADER_EBG || UBOOT || BOOTLOADER_NONE || BOOTLOADER_GRUB
 	help
 	  Store update status in Bootloader's environment.
 	  Specify Bootloader environment variable name to store update status in.
@@ -80,18 +125,3 @@  config UPDATE_STATE_BOOTLOADER
 	help
 	  Store update information in Bootloader's environment.
 
-config UBOOT_DEFAULTENV
-	string "U-Boot Initial Environment file"
-	depends on UBOOT
-	default "/etc/u-boot-initial-env"
-	help
-	  This is the file with the initial environment delivered
-	  with the bootloader. It is used by SWUpdate if no environment
-	  is found on the storage.
-
-config GRUBENV_PATH
-	string "GRUB Environment block file path"
-	depends on BOOTLOADER_GRUB
-	default "/boot/efi/EFI/BOOT/grub/grubenv"
-	help
-	  Provide path to GRUB environment block file
diff --git a/bootloader/Makefile b/bootloader/Makefile
index ff56387..2c34291 100644
--- a/bootloader/Makefile
+++ b/bootloader/Makefile
@@ -2,7 +2,7 @@ 
 #
 # SPDX-License-Identifier:     GPL-2.0-only
 #
-lib-$(CONFIG_UBOOT)		+= uboot.o
-lib-$(CONFIG_BOOTLOADER_NONE)	+= none.o
-lib-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
-lib-$(CONFIG_BOOTLOADER_EBG)		+= ebg.o
+obj-$(CONFIG_UBOOT)		+= uboot.o
+obj-$(CONFIG_BOOTLOADER_NONE)	+= none.o
+obj-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
+obj-$(CONFIG_BOOTLOADER_EBG)	+= ebg.o
diff --git a/core/Makefile b/core/Makefile
index fa30e6e..90f1177 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -12,6 +12,7 @@  obj-y += swupdate.o \
 	 cpio_utils.o \
 	 notifier.o \
 	 handler.o \
+	 bootloader.o \
 	 install_from_file.o \
 	 util.o \
 	 parser.o \
diff --git a/core/bootloader.c b/core/bootloader.c
new file mode 100644
index 0000000..f34cb41
--- /dev/null
+++ b/core/bootloader.c
@@ -0,0 +1,71 @@ 
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2022, Siemens AG
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+#include <stdlib.h>
+#include <errno.h>
+#include <util.h>
+#include <bootloader.h>
+
+int   (*bootloader_env_set)(const char *, const char *);
+int   (*bootloader_env_unset)(const char *);
+char* (*bootloader_env_get)(const char *);
+int   (*bootloader_apply_list)(const char *);
+
+typedef struct {
+	const char *name;
+	bootloader *funcs;
+} entry;
+
+static entry *current = NULL;
+static entry *available = NULL;
+static unsigned int num_available = 0;
+
+int register_bootloader(const char *name, bootloader *bl)
+{
+	entry *tmp = reallocarray(available, num_available + 1, sizeof(entry));
+	if (!tmp) {
+		return -ENOMEM;
+	}
+	tmp[num_available].name = (char*)name;
+	tmp[num_available].funcs = bl;
+	num_available++;
+	available = tmp;
+	return 0;
+}
+
+int set_bootloader(const char *name)
+{
+	if (!name) {
+		return -ENOENT;
+	}
+	for (unsigned int i = 0; i < num_available; i++) {
+		if (available[i].funcs &&
+		    (strcmp(available[i].name, name) == 0)) {
+			bootloader_env_set = available[i].funcs->env_set;
+			bootloader_env_get = available[i].funcs->env_get;
+			bootloader_env_unset = available[i].funcs->env_unset;
+			bootloader_apply_list = available[i].funcs->apply_list;
+			current = &available[i];
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+const char* get_bootloader(void)
+{
+	return current ? current->name : NULL;
+}
+
+void print_registered_bootloaders(void)
+{
+	INFO("Registered bootloaders:");
+	for (unsigned int i = 0; i < num_available; i++) {
+		INFO("\t%s\t%s", available[i].name,
+		     available[i].funcs == NULL ? "shared lib not found."
+						: "loaded.");
+	}
+}
diff --git a/core/swupdate.c b/core/swupdate.c
index b59e0eb..d2035f9 100644
--- a/core/swupdate.c
+++ b/core/swupdate.c
@@ -114,6 +114,7 @@  static struct option long_options[] = {
 #ifdef CONFIG_WEBSERVER
 	{"webserver", required_argument, NULL, 'w'},
 #endif
+	{"bootloader", required_argument, NULL, 'B'},
 	{NULL, 0, NULL, 0}
 };
 
@@ -129,6 +130,7 @@  static void usage(char *programname)
 #ifdef CONFIG_UBIATTACH
 		" -b, --blacklist <list of mtd>  : MTDs that must not be scanned for UBI\n"
 #endif
+		" -B, --bootloader               : bootloader interface (default: " PREPROCVALUE(BOOTLOADER_DEFAULT) ")\n"
 		" -p, --postupdate               : execute post-update command\n"
 		" -P, --preupdate                : execute pre-update command\n"
 		" -e, --select <software>,<mode> : Select software images set and source\n"
@@ -283,6 +285,15 @@  static int read_globals_settings(void *elem, void *data)
 	char tmp[SWUPDATE_GENERAL_STRING_SIZE] = "";
 	struct swupdate_cfg *sw = (struct swupdate_cfg *)data;
 
+	GET_FIELD_STRING(LIBCFG_PARSER, elem,
+				"bootloader", tmp);
+	if (tmp[0] != '\0') {
+		if (set_bootloader(tmp) != 0) {
+			ERROR("Bootloader interface '%s' could not be initialized.", tmp);
+			exit(EXIT_FAILURE);
+		}
+		tmp[0] = '\0';
+	}
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
 				"public-key-file", sw->publickeyfname);
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
@@ -435,7 +446,7 @@  int main(int argc, char **argv)
 #endif
 	memset(main_options, 0, sizeof(main_options));
 	memset(image_url, 0, sizeof(image_url));
-	strcpy(main_options, "vhni:e:gq:l:Lcf:p:P:o:N:R:Mm");
+	strcpy(main_options, "vhni:e:gq:l:Lcf:p:P:o:N:R:MmB:");
 #ifdef CONFIG_MTD
 	strcat(main_options, "b:");
 #endif
@@ -581,6 +592,13 @@  int main(int argc, char **argv)
 		case 'o':
 			strlcpy(swcfg.output, optarg, sizeof(swcfg.output));
 			break;
+		case 'B':
+			if (set_bootloader(optarg) != 0) {
+				ERROR("Bootloader interface '%s' could not be initialized.", optarg);
+				print_registered_bootloaders();
+				exit(EXIT_FAILURE);
+			}
+			break;
 		case 'l':
 			loglevel = strtoul(optarg, NULL, 10);
 			break;
@@ -754,6 +772,20 @@  int main(int argc, char **argv)
 	printf("Licensed under GPLv2. See source distribution for detailed "
 		"copyright notices.\n\n");
 
+	print_registered_bootloaders();
+	if (!get_bootloader()) {
+		if (set_bootloader(PREPROCVALUE(BOOTLOADER_DEFAULT)) != 0) {
+			ERROR("Default bootloader interface '" PREPROCVALUE(
+			    BOOTLOADER_DEFAULT) "' couldn't be loaded.");
+			INFO("Check that the bootloader interface shared library is present.");
+			INFO("Or chose another bootloader interface by supplying -B <loader>.");
+			exit(EXIT_FAILURE);
+		}
+		INFO("Using default bootloader interface: " PREPROCVALUE(BOOTLOADER_DEFAULT));
+	} else {
+		INFO("Using bootloader interface: %s", get_bootloader());
+	}
+
 	/*
 	 * Install a child handler to check if a subprocess
 	 * dies
diff --git a/doc/source/swupdate.rst b/doc/source/swupdate.rst
index 7bd6c92..802b16a 100644
--- a/doc/source/swupdate.rst
+++ b/doc/source/swupdate.rst
@@ -467,6 +467,9 @@  Command line parameters
 |             |          | Example: U-Boot and environment in MTD0-1: |
 |             |          | ``swupdate -b "0 1"``.                     |
 +-------------+----------+--------------------------------------------+
+| -B <loader> | string   | Override the default bootloader interface  |
+|             |          | to use ``loader`` instead.                 |
++-------------+----------+--------------------------------------------+
 | -e <sel>    | string   | ``sel`` is in the format <software>,<mode>.|
 |             |          | It allows one to find a subset of rules in |
 |             |          | the sw-description file. With it,          |
diff --git a/examples/configuration/swupdate.cfg b/examples/configuration/swupdate.cfg
index 2f19ffa..76d0a02 100644
--- a/examples/configuration/swupdate.cfg
+++ b/examples/configuration/swupdate.cfg
@@ -43,6 +43,11 @@ 
 #			  set expected common name of signer certificate
 # select:		: string
 #			  select software images set and source (<software>,<mode>)
+# bootloader:		: string
+#			  bootloader interface to use, overruling compile-time default.
+#			  Possible values are ebg, grub, uboot, and none for
+#			  EFI Boot Guard, U-Boot, GRUB, and the Environment in RAM bootloader,
+#			  respectively, given the respective bootloader support is compiled-in.
 globals :
 {
 
diff --git a/include/bootloader.h b/include/bootloader.h
index 7bf5fa3..8315759 100644
--- a/include/bootloader.h
+++ b/include/bootloader.h
@@ -5,8 +5,56 @@ 
  * SPDX-License-Identifier:     GPL-2.0-only
  */
 
-#ifndef _BOOTLOADER_INTERFACE_H
-#define _BOOTLOADER_INTERFACE_H
+#pragma once
+
+#define load_symbol(handle, container, fname) \
+	*(void**)(container) = dlsym(handle, fname); \
+	if (dlerror() != NULL) { \
+		(void)dlclose(handle); \
+		return NULL; \
+	}
+
+typedef struct {
+	int (*env_set)(const char *, const char *);
+	int (*env_unset)(const char *);
+	char* (*env_get)(const char *);
+	int (*apply_list)(const char *);
+} bootloader;
+
+/*
+ * register_bootloader - register bootloader.
+ *
+ * @name : bootloader's name to register.
+ * @bootloader : struct bootloader with bootloader details.
+ *
+ * Return:
+ *   0 on success, -ENOMEM on error.
+ */
+int register_bootloader(const char *name, bootloader *bl);
+
+/*
+ * set_bootloader - set bootloader to use.
+ *
+ * @name : bootloader's name to set.
+ *
+ * Return:
+ *   0 on success, -ENOENT on error.
+ */
+int set_bootloader(const char *name);
+
+/*
+ * get_bootloader - get set bootloader's name
+ *
+ * Return:
+ *   name on success, NULL on error.
+ */
+const char* get_bootloader(void);
+
+/*
+ * print_registered_bootloaders - print registered bootloaders
+ */
+void print_registered_bootloaders(void);
+
 
 /*
  * bootloader_env_set - modify a variable
@@ -17,8 +65,7 @@ 
  * Return:
  *   0 on success
  */
-
-int bootloader_env_set(const char *name, const char *value);
+extern int (*bootloader_env_set)(const char *, const char *);
 
 /*
  * bootloader_env_unset - drop a variable
@@ -28,8 +75,7 @@  int bootloader_env_set(const char *name, const char *value);
  * Return:
  *   0 on success
  */
-
-int bootloader_env_unset(const char *name);
+extern int (*bootloader_env_unset)(const char *);
 
 /*
  * bootloader_env_get - get value of a variable
@@ -40,8 +86,7 @@  int bootloader_env_unset(const char *name);
  *   string if variable is found
  *   NULL if no variable with name is found
  */
-
-char *bootloader_env_get(const char *name);
+extern char* (*bootloader_env_get)(const char *);
 
 /*
  * bootloader_apply_list - set multiple variables
@@ -51,6 +96,5 @@  char *bootloader_env_get(const char *name);
  * Return:
  *   0 on success
  */
-int bootloader_apply_list(const char *script);
+extern int (*bootloader_apply_list)(const char *);
 
-#endif
diff --git a/include/util.h b/include/util.h
index 270978d..302ea04 100644
--- a/include/util.h
+++ b/include/util.h
@@ -125,6 +125,7 @@  void notifier_set_color(int level, char *col);
 #endif
 
 #define STRINGIFY(...) #__VA_ARGS__
+#define PREPROCVALUE(s) STRINGIFY(s)
 #define SETSTRING(p, v) do { \
 	if (p) \
 		free(p); \