diff mbox series

bootloader: EFI Boot Guard support

Message ID 20180112115212.11430-1-christian.storm@siemens.com
State Accepted
Headers show
Series bootloader: EFI Boot Guard support | expand

Commit Message

Storm, Christian Jan. 12, 2018, 11:52 a.m. UTC
Add support for EFI Boot Guard to SWUpdate.
See: https://github.com/siemens/efibootguard

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Andreas Reichel <andreas.reichel.ext@siemens.com>
---
 Kconfig              |   4 ++
 Makefile.deps        |   4 ++
 Makefile.flags       |   4 ++
 bootloader/Config.in |  11 +++
 bootloader/Makefile  |   1 +
 bootloader/ebg.c     | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 217 insertions(+)
 create mode 100644 bootloader/ebg.c

Comments

Stefano Babic Jan. 12, 2018, 12:02 p.m. UTC | #1
Hi Christian,

On 12/01/2018 12:52, Christian Storm wrote:
> Add support for EFI Boot Guard to SWUpdate.
> See: https://github.com/siemens/efibootguard
> 

A small note: after this patch goes in, we will need a corresponding
patch for meta-swupdate to add dependency to libebgenv

> Signed-off-by: Christian Storm <christian.storm@siemens.com>
> Signed-off-by: Andreas Reichel <andreas.reichel.ext@siemens.com>
> ---
>  Kconfig              |   4 ++
>  Makefile.deps        |   4 ++
>  Makefile.flags       |   4 ++
>  bootloader/Config.in |  11 +++
>  bootloader/Makefile  |   1 +
>  bootloader/ebg.c     | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 217 insertions(+)
>  create mode 100644 bootloader/ebg.c
> 
> diff --git a/Kconfig b/Kconfig
> index e46ff05..4469096 100644
> --- a/Kconfig
> +++ b/Kconfig
> @@ -37,6 +37,10 @@ config HAVE_LIBUBOOTENV
>  	bool
>  	option env="HAVE_LIBUBOOTENV"
>  
> +config HAVE_LIBEBGENV
> +	bool
> +	option env="HAVE_LIBEBGENV"
> +
>  config HAVE_LIBZEROMQ
>  	bool
>  	option env="HAVE_LIBZEROMQ"
> diff --git a/Makefile.deps b/Makefile.deps
> index d06b42c..1a01ca0 100644
> --- a/Makefile.deps
> +++ b/Makefile.deps
> @@ -34,6 +34,10 @@ ifeq ($(HAVE_LIBUBOOTENV),)
>  export HAVE_LIBUBOOTENV = y
>  endif
>  
> +ifeq ($(HAVE_LIBEBGENV),)
> +export HAVE_LIBEBGENV = y
> +endif
> +
>  ifeq ($(HAVE_LIBSSL),)
>  export HAVE_LIBSSL = y
>  endif
> diff --git a/Makefile.flags b/Makefile.flags
> index 041a08e..08990a6 100644
> --- a/Makefile.flags
> +++ b/Makefile.flags
> @@ -179,6 +179,10 @@ ifeq ($(CONFIG_SYSTEMD),y)
>  LDLIBS += systemd
>  endif
>  
> +ifeq ($(CONFIG_BOOTLOADER_EBG),y)
> +LDLIBS += ebgenv z
> +endif
> +
>  # suricatta
>  ifneq ($(CONFIG_SURICATTA),)
>  ifneq ($(CONFIG_SURICATTA_SSL),)
> diff --git a/bootloader/Config.in b/bootloader/Config.in
> index 3b2d73b..9e33dba 100644
> --- a/bootloader/Config.in
> +++ b/bootloader/Config.in
> @@ -4,6 +4,17 @@ choice
>  	help
>  	  Choose the bootloader
>  
> +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
> diff --git a/bootloader/Makefile b/bootloader/Makefile
> index a652058..1b09048 100644
> --- a/bootloader/Makefile
> +++ b/bootloader/Makefile
> @@ -1,3 +1,4 @@
>  lib-$(CONFIG_UBOOT)		+= uboot.o
>  lib-$(CONFIG_BOOTLOADER_NONE)	+= none.o
>  lib-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
> +lib-$(CONFIG_BOOTLOADER_EBG)		+= ebg.o
> diff --git a/bootloader/ebg.c b/bootloader/ebg.c
> new file mode 100644
> index 0000000..8f40970
> --- /dev/null
> +++ b/bootloader/ebg.c
> @@ -0,0 +1,193 @@
> +/*
> + * Author: Christian Storm
> + * Author: Andreas Reichel
> + * Copyright (C) 2018, Siemens AG
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc.
> + */
> +
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <limits.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <util.h>
> +#include <efibootguard/ebgenv.h>
> +#include <generated/autoconf.h>
> +#include <suricatta/state.h>
> +#include "bootloader.h"
> +
> +#ifdef CONFIG_SURICATTA_STATE_CHOICE_BOOTLOADER
> +#define EXPANDTOKL2(token) token
> +#define EXPANDTOK(token) EXPANDTOKL2(token)
> +#define STATE_KEY EXPANDTOK(CONFIG_SURICATTA_STATE_BOOTLOADER)
> +#else
> +#define STATE_KEY "none"
> +#endif
> +
> +#define RCS_KEY   "recovery_status"
> +#define RCS_VALUE "in_progress"
> +
> +static ebgenv_t ebgenv = {0};
> +
> +int bootloader_env_set(const char *name, const char *value)
> +{
> +	int ret;
> +
> +	errno = 0;
> +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);

Do we introduce another trace mechanism ? This will be not forwarded to
SWUpdate's notifier and I presume it will be lost. Anyway, I have no
problem to let it in, if you think it is useful.

> +
> +	DEBUG("Setting %s=%s in bootloader environment", name, value);
> +
> +	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
> +		ERROR("Cannot open current bootloader environment: %s.", strerror(ret));
> +		return ret;
> +	}
> +
> +	if (strncmp(name, RCS_KEY, strlen(name) + 1) == 0 &&
> +	    strncmp(value, RCS_VALUE, strlen(RCS_VALUE) + 1) == 0) {
> +		/* Open or create a new environment to reflect
> +		 * EFI Boot Guard's representation of SWUpdate's
> +		 * recovery_status=in_progress. */
> +		if ((ret = ebg_env_create_new(&ebgenv)) != 0) {
> +			ERROR("Cannot open/create new bootloader environment: %s.",
> +			     strerror(ret));
> +		}
> +	} else if (strncmp(name, (char *)STATE_KEY, strlen((char *)STATE_KEY) + 1) == 0) {
> +		/* Map suricatta's update_state_t to EFI Boot Guard's API. */
> +		if ((ret = ebg_env_setglobalstate(&ebgenv, *value - '0')) != 0) {
> +			ERROR("Cannot set %s=%s in bootloader environment.", STATE_KEY, value);
> +		}
> +	} else {
> +		/* A new environment is created if EFI Boot Guard's
> +		 * representation of SWUpdate's recovery_status is
> +		 * not in_progress. */
> +		if ((ret = ebg_env_create_new(&ebgenv)) != 0) {
> +			ERROR("Cannot open/create new bootloader environment: %s.",
> +			     strerror(ret));
> +			return ret;
> +		}
> +		if ((ret = ebg_env_set(&ebgenv, (char *)name, (char *)value)) != 0) {
> +			ERROR("Cannot set %s=%s in bootloader environment: %s.",
> +			    name, value, strerror(ret));
> +		}
> +	}
> +	(void)ebg_env_close(&ebgenv);
> +
> +	return ret;
> +}
> +
> +int bootloader_env_unset(const char *name)
> +{
> +	int ret;
> +
> +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
> +
> +	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
> +		ERROR("Cannot open current bootloader environment: %s.", strerror(ret));
> +		return ret;
> +	}
> +
> +	if (strncmp(name, RCS_KEY, strlen(name) + 1) == 0) {
> +		ret = ebg_env_finalize_update(&ebgenv);
> +		if (ret) {
> +			ERROR("Cannot unset %s in bootloader environment: %s.", RCS_KEY, strerror(ret));
> +		}
> +	} else {
> +		ret = ebg_env_set_ex(&ebgenv, (char *)name, USERVAR_TYPE_DELETED, (uint8_t *)"", 1);
> +	}
> +	(void)ebg_env_close(&ebgenv);
> +
> +	return ret;
> +}
> +
> +char *bootloader_env_get(const char *name)
> +{
> +	char *value = NULL;
> +	size_t size;
> +
> +	errno = 0;
> +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
> +
> +	int ret;
> +	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
> +		ERROR("Cannot open current bootloader environment: %s.",
> +		     strerror(ret));
> +		return NULL;
> +	}
> +
> +	if (strncmp(name, (char *)STATE_KEY, strlen((char *)STATE_KEY) + 1) == 0) {
> +		value = (char *)malloc(sizeof(char));
> +		*value = ebg_env_getglobalstate(&ebgenv);
> +	} else {
> +		if ((size = ebg_env_get(&ebgenv, (char *)name, NULL)) != 0) {
> +			value = malloc(size);
> +			if (value) {
> +				if (ebg_env_get(&ebgenv, (char *)name, value) != 0) {
> +					value = NULL;
> +				}
> +			}
> +		}
> +	}
> +
> +	(void)ebg_env_close(&ebgenv);
> +
> +	if (value == NULL) {
> +		ERROR("Cannot get %s from bootloader environment: %s",
> +		    name, strerror(errno));
> +	}
> +
> +	/* Map EFI Boot Guard's int return to update_state_t's char value */
> +	*value = *value + '0';
> +	return value;
> +}
> +
> +int bootloader_apply_list(const char *filename)
> +{
> +	FILE *fp = NULL;
> +	char *line = NULL;
> +	char *key;
> +	char *value;
> +	size_t len = 0;
> +	int ret = 0;
> +
> +	errno = 0;
> +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
> +
> +	if (!(fp = fopen(filename, "rb"))) {
> +		ERROR("Failed to open bootloader environment file %s: %s",
> +			      filename, strerror(errno));
> +		return -1;
> +	}
> +
> +	while ((getline(&line, &len, fp)) != -1) {
> +		key = strtok(line, " \t\n");
> +		value = strtok(NULL, "\t\n");
> +		if (value != NULL && key != NULL) {
> +			if ((ret = bootloader_env_set(key, value)) != 0) {
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (fp) {
> +		fclose(fp);
> +	}
> +	if (line) {
> +		free(line);
> +	}
> +	return ret;
> +}
> 

Reviewed-by: Stefano Babic <sbabic@denx.de>

Best regards,
Stefano
Storm, Christian Jan. 12, 2018, 1:16 p.m. UTC | #2
Hi Stefano,

> > Add support for EFI Boot Guard to SWUpdate.
> > See: https://github.com/siemens/efibootguard
> > 
> 
> A small note: after this patch goes in, we will need a corresponding
> patch for meta-swupdate to add dependency to libebgenv

Yes, and probably some more changes as well. We're currently assembling
the bits and pieces...

> [...]
> > +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
> 
> Do we introduce another trace mechanism ? This will be not forwarded to
> SWUpdate's notifier and I presume it will be lost. Anyway, I have no
> problem to let it in, if you think it is useful.

Well, not quite. First and foremost this spits out some very helpful
(debug) information from EFI Boot Guard to stdout/stderr when developing
-- which I'd like to make available via the loglevel "switch" in
SWUpdate instead of having to recompile libebgenv.a for this. On top of
that, this also proves useful when debugging in the field, sparing you
the need to inject a special "debug" binary.
Granted, it doesn't use SWUpdate's notification system so that its
messages aren't forwarded to SWUpdate. However, this information is
mostly useful when debugging. In normal operation I'm fine with the
information being lost. That said, If you happen to use SWUpdate's
systemd feature and have set an appropriate loglevel, then the
information will make it to journald as it captures stdout/stderr
of the swupdate service :)


Kind regards,

   Christian
Stefano Babic Jan. 12, 2018, 1:21 p.m. UTC | #3
On 12/01/2018 14:16, Christian Storm wrote:
> Hi Stefano,
> 
>>> Add support for EFI Boot Guard to SWUpdate.
>>> See: https://github.com/siemens/efibootguard
>>>
>>
>> A small note: after this patch goes in, we will need a corresponding
>> patch for meta-swupdate to add dependency to libebgenv
> 
> Yes, and probably some more changes as well. We're currently assembling
> the bits and pieces...
> 
>> [...]
>>> +	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
>>
>> Do we introduce another trace mechanism ? This will be not forwarded to
>> SWUpdate's notifier and I presume it will be lost. Anyway, I have no
>> problem to let it in, if you think it is useful.
> 
> Well, not quite. First and foremost this spits out some very helpful
> (debug) information from EFI Boot Guard to stdout/stderr when developing
> -- which I'd like to make available via the loglevel "switch" in
> SWUpdate instead of having to recompile libebgenv.a for this. On top of
> that, this also proves useful when debugging in the field, sparing you
> the need to inject a special "debug" binary.
> Granted, it doesn't use SWUpdate's notification system so that its
> messages aren't forwarded to SWUpdate. However, this information is
> mostly useful when debugging. In normal operation I'm fine with the
> information being lost. That said, If you happen to use SWUpdate's
> systemd feature and have set an appropriate loglevel, then the
> information will make it to journald as it captures stdout/stderr
> of the swupdate service :)
> 

Thanks for explanation, I am fine with it.

Best regards,
Stefano
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index e46ff05..4469096 100644
--- a/Kconfig
+++ b/Kconfig
@@ -37,6 +37,10 @@  config HAVE_LIBUBOOTENV
 	bool
 	option env="HAVE_LIBUBOOTENV"
 
+config HAVE_LIBEBGENV
+	bool
+	option env="HAVE_LIBEBGENV"
+
 config HAVE_LIBZEROMQ
 	bool
 	option env="HAVE_LIBZEROMQ"
diff --git a/Makefile.deps b/Makefile.deps
index d06b42c..1a01ca0 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -34,6 +34,10 @@  ifeq ($(HAVE_LIBUBOOTENV),)
 export HAVE_LIBUBOOTENV = y
 endif
 
+ifeq ($(HAVE_LIBEBGENV),)
+export HAVE_LIBEBGENV = y
+endif
+
 ifeq ($(HAVE_LIBSSL),)
 export HAVE_LIBSSL = y
 endif
diff --git a/Makefile.flags b/Makefile.flags
index 041a08e..08990a6 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -179,6 +179,10 @@  ifeq ($(CONFIG_SYSTEMD),y)
 LDLIBS += systemd
 endif
 
+ifeq ($(CONFIG_BOOTLOADER_EBG),y)
+LDLIBS += ebgenv z
+endif
+
 # suricatta
 ifneq ($(CONFIG_SURICATTA),)
 ifneq ($(CONFIG_SURICATTA_SSL),)
diff --git a/bootloader/Config.in b/bootloader/Config.in
index 3b2d73b..9e33dba 100644
--- a/bootloader/Config.in
+++ b/bootloader/Config.in
@@ -4,6 +4,17 @@  choice
 	help
 	  Choose the bootloader
 
+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
diff --git a/bootloader/Makefile b/bootloader/Makefile
index a652058..1b09048 100644
--- a/bootloader/Makefile
+++ b/bootloader/Makefile
@@ -1,3 +1,4 @@ 
 lib-$(CONFIG_UBOOT)		+= uboot.o
 lib-$(CONFIG_BOOTLOADER_NONE)	+= none.o
 lib-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
+lib-$(CONFIG_BOOTLOADER_EBG)		+= ebg.o
diff --git a/bootloader/ebg.c b/bootloader/ebg.c
new file mode 100644
index 0000000..8f40970
--- /dev/null
+++ b/bootloader/ebg.c
@@ -0,0 +1,193 @@ 
+/*
+ * Author: Christian Storm
+ * Author: Andreas Reichel
+ * Copyright (C) 2018, Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <util.h>
+#include <efibootguard/ebgenv.h>
+#include <generated/autoconf.h>
+#include <suricatta/state.h>
+#include "bootloader.h"
+
+#ifdef CONFIG_SURICATTA_STATE_CHOICE_BOOTLOADER
+#define EXPANDTOKL2(token) token
+#define EXPANDTOK(token) EXPANDTOKL2(token)
+#define STATE_KEY EXPANDTOK(CONFIG_SURICATTA_STATE_BOOTLOADER)
+#else
+#define STATE_KEY "none"
+#endif
+
+#define RCS_KEY   "recovery_status"
+#define RCS_VALUE "in_progress"
+
+static ebgenv_t ebgenv = {0};
+
+int bootloader_env_set(const char *name, const char *value)
+{
+	int ret;
+
+	errno = 0;
+	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
+
+	DEBUG("Setting %s=%s in bootloader environment", name, value);
+
+	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
+		ERROR("Cannot open current bootloader environment: %s.", strerror(ret));
+		return ret;
+	}
+
+	if (strncmp(name, RCS_KEY, strlen(name) + 1) == 0 &&
+	    strncmp(value, RCS_VALUE, strlen(RCS_VALUE) + 1) == 0) {
+		/* Open or create a new environment to reflect
+		 * EFI Boot Guard's representation of SWUpdate's
+		 * recovery_status=in_progress. */
+		if ((ret = ebg_env_create_new(&ebgenv)) != 0) {
+			ERROR("Cannot open/create new bootloader environment: %s.",
+			     strerror(ret));
+		}
+	} else if (strncmp(name, (char *)STATE_KEY, strlen((char *)STATE_KEY) + 1) == 0) {
+		/* Map suricatta's update_state_t to EFI Boot Guard's API. */
+		if ((ret = ebg_env_setglobalstate(&ebgenv, *value - '0')) != 0) {
+			ERROR("Cannot set %s=%s in bootloader environment.", STATE_KEY, value);
+		}
+	} else {
+		/* A new environment is created if EFI Boot Guard's
+		 * representation of SWUpdate's recovery_status is
+		 * not in_progress. */
+		if ((ret = ebg_env_create_new(&ebgenv)) != 0) {
+			ERROR("Cannot open/create new bootloader environment: %s.",
+			     strerror(ret));
+			return ret;
+		}
+		if ((ret = ebg_env_set(&ebgenv, (char *)name, (char *)value)) != 0) {
+			ERROR("Cannot set %s=%s in bootloader environment: %s.",
+			    name, value, strerror(ret));
+		}
+	}
+	(void)ebg_env_close(&ebgenv);
+
+	return ret;
+}
+
+int bootloader_env_unset(const char *name)
+{
+	int ret;
+
+	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
+
+	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
+		ERROR("Cannot open current bootloader environment: %s.", strerror(ret));
+		return ret;
+	}
+
+	if (strncmp(name, RCS_KEY, strlen(name) + 1) == 0) {
+		ret = ebg_env_finalize_update(&ebgenv);
+		if (ret) {
+			ERROR("Cannot unset %s in bootloader environment: %s.", RCS_KEY, strerror(ret));
+		}
+	} else {
+		ret = ebg_env_set_ex(&ebgenv, (char *)name, USERVAR_TYPE_DELETED, (uint8_t *)"", 1);
+	}
+	(void)ebg_env_close(&ebgenv);
+
+	return ret;
+}
+
+char *bootloader_env_get(const char *name)
+{
+	char *value = NULL;
+	size_t size;
+
+	errno = 0;
+	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
+
+	int ret;
+	if ((ret = ebg_env_open_current(&ebgenv)) != 0) {
+		ERROR("Cannot open current bootloader environment: %s.",
+		     strerror(ret));
+		return NULL;
+	}
+
+	if (strncmp(name, (char *)STATE_KEY, strlen((char *)STATE_KEY) + 1) == 0) {
+		value = (char *)malloc(sizeof(char));
+		*value = ebg_env_getglobalstate(&ebgenv);
+	} else {
+		if ((size = ebg_env_get(&ebgenv, (char *)name, NULL)) != 0) {
+			value = malloc(size);
+			if (value) {
+				if (ebg_env_get(&ebgenv, (char *)name, value) != 0) {
+					value = NULL;
+				}
+			}
+		}
+	}
+
+	(void)ebg_env_close(&ebgenv);
+
+	if (value == NULL) {
+		ERROR("Cannot get %s from bootloader environment: %s",
+		    name, strerror(errno));
+	}
+
+	/* Map EFI Boot Guard's int return to update_state_t's char value */
+	*value = *value + '0';
+	return value;
+}
+
+int bootloader_apply_list(const char *filename)
+{
+	FILE *fp = NULL;
+	char *line = NULL;
+	char *key;
+	char *value;
+	size_t len = 0;
+	int ret = 0;
+
+	errno = 0;
+	ebg_beverbose(&ebgenv, loglevel > INFOLEVEL ? true : false);
+
+	if (!(fp = fopen(filename, "rb"))) {
+		ERROR("Failed to open bootloader environment file %s: %s",
+			      filename, strerror(errno));
+		return -1;
+	}
+
+	while ((getline(&line, &len, fp)) != -1) {
+		key = strtok(line, " \t\n");
+		value = strtok(NULL, "\t\n");
+		if (value != NULL && key != NULL) {
+			if ((ret = bootloader_env_set(key, value)) != 0) {
+				break;
+			}
+		}
+	}
+
+	if (fp) {
+		fclose(fp);
+	}
+	if (line) {
+		free(line);
+	}
+	return ret;
+}