diff mbox series

[libubootenv] Add new configuration file in YAML

Message ID 20230326124108.152837-1-sbabic@denx.de
State Accepted
Headers show
Series [libubootenv] Add new configuration file in YAML | expand

Commit Message

Stefano Babic March 26, 2023, 12:41 p.m. UTC
Legacy configuration file was taken by U-Boot project. Its format is
very simple, but unflexible and it cannot be extended. Changes in the
format could lead to uncompatibility with U-Boot tools, tht are still
part of U-Boot.

This introduce a new format, letting the old format as fallback if YAML
cannot be parsed. It uses the libyaml library.

Multiple sets can be configured - an environment is not bound with the
bootloader, and can be used for other purposes, but still having the
features provided by the handling for the bootloader (redundancy,
power-cut safe).

See documentation for the format of the yaml file. Each not recogniyed
keyword generates an error.

Signed-off-by: Stefano Babic <sbabic@denx.de>
---
 docs/fw_env_config.md |  40 ++++-
 src/CMakeLists.txt    |   2 +-
 src/fw_printenv.c     |  37 +++-
 src/libuboot.h        |  17 ++
 src/uboot_env.c       | 399 +++++++++++++++++++++++++++++++++++++++++-
 src/uboot_private.h   |   9 +
 6 files changed, 489 insertions(+), 15 deletions(-)
diff mbox series

Patch

diff --git a/docs/fw_env_config.md b/docs/fw_env_config.md
index d9a212c..757f06d 100644
--- a/docs/fw_env_config.md
+++ b/docs/fw_env_config.md
@@ -3,10 +3,12 @@  SPDX-FileCopyrightText: 2019-2021 Stefano Babic <sbabic@denx.de>
 
 SPDX-License-Identifier:     LGPL-2.1-or-later
 -->
-fw_env.config Configuration File
-================================
+fw_env.config Configuration File- Legacy format
+================================================
+
+This is the configuration file for fw_{printenv,setenv} utility. It was defined in U-Boot project
+and it is defined here as legacy format.
 
-This is the configuration file for fw_{printenv,setenv} utility.
 Up to two entries are valid, in this case the redundant
 environment sector is assumed present.
 Notice, that the "Number of Sectors" is not required on NOR and SPI dataflash.
@@ -94,3 +96,35 @@  UBI Volume by Name Example
 |------------------|---------------|------------------|-------------------|-------------------|------------------------|
 | /dev/ubi0:env    |     0x0       |      0x1f000     |      0x1f000      |                   |                        |
 | /dev/ubi0:redund |     0x0       |      0x1f000     |      0x1f000      |                   |                        |
+
+Configuration File in YAML
+==========================
+
+A YAML format is defined to allow multiple sets of variable. This lets have same features (redundancy, power-cut safe) for
+environment that are not bound to the U-Boot bootloader.
+
+uboot:
+  size : 0x4000
+  lockfile : /var/lock/fw_printenv.lock 
+  devices:
+    - path : /dev/mtd0
+      offset : 0xA0000
+      sectorsize : 0x10000
+      unlock : yes
+    - path : /dev/mtd0
+      offset : 0xB0000
+      sectorsize : 0x10000
+      disable-lock : yes
+
+appvar:
+  size : 0x4000
+  lockfile : /var/lock/appvar.lock 
+  devices:
+    - path : /dev/mtd1
+      offset : 0
+      sectorsize : 0x10000
+      unlock : yes
+    - path : /dev/mtd1
+      offset : 0x10000
+      sectorsize : 0x10000
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index de4162b..ababe0f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,7 +21,7 @@  SET_TARGET_PROPERTIES(ubootenv PROPERTIES VERSION ${VERSION} SOVERSION ${SOVERSI
 ADD_LIBRARY(ubootenv_static STATIC ${libubootenv_SOURCES} ${include_HEADERS})
 SET_TARGET_PROPERTIES(ubootenv_static PROPERTIES OUTPUT_NAME ubootenv)
 add_executable(fw_printenv fw_printenv.c)
-target_link_libraries(ubootenv z)
+target_link_libraries(ubootenv z yaml)
 target_link_libraries(fw_printenv ubootenv)
 add_custom_target(fw_setenv ALL ${CMAKE_COMMAND} -E create_symlink fw_printenv fw_setenv)
 
diff --git a/src/fw_printenv.c b/src/fw_printenv.c
index b2d3599..1d9a35e 100644
--- a/src/fw_printenv.c
+++ b/src/fw_printenv.c
@@ -37,6 +37,7 @@  static struct option long_options[] = {
 	{"config", required_argument, NULL, 'c'},
 	{"defenv", required_argument, NULL, 'f'},
 	{"script", required_argument, NULL, 's'},
+	{"namespace", required_argument, NULL, 'm'},
 	{NULL, 0, NULL, 0}
 };
 
@@ -49,6 +50,7 @@  static void usage(char *program, bool setprogram)
 		" -h, --help                       : print this help\n"
 		" -c, --config <filename>          : configuration file (by default: " DEFAULT_CFG_FILE ")\n"
 		" -f, --defenv <filename>          : default environment if no one found (by default: " DEFAULT_ENV_FILE ")\n"
+		" -m, --namespace <name>           : chose one of sets in the YAML file, default first in YAML\n"
 		" -V, --version                    : print version and exit\n"
 	);
 	if (!setprogram)
@@ -74,11 +76,12 @@  static void usage(char *program, bool setprogram)
 }
 
 int main (int argc, char **argv) {
-	struct uboot_ctx *ctx;
-	char *options = "Vc:f:s:nh";
+	struct uboot_ctx *ctx = NULL;
+	char *options = "Vc:f:s:nhm:";
 	char *cfgfname = NULL;
 	char *defenvfile = NULL;
 	char *scriptfile = NULL;
+	char *namespace = NULL;
 	int c, i;
 	int ret = 0;
 	void *tmp;
@@ -120,6 +123,9 @@  int main (int argc, char **argv) {
 		case 'f':
 			defenvfile = strdup(optarg);
 			break;
+		case 'm':
+			namespace = strdup(optarg);
+			break;
 		case 's':
 			scriptfile = strdup(optarg);
 			break;
@@ -129,17 +135,30 @@  int main (int argc, char **argv) {
 	argc -= optind;
 	argv += optind;
 
-	if (libuboot_initialize(&ctx, NULL) < 0) {
-		fprintf(stderr, "Cannot initialize environment\n");
-		exit(1);
-	}
 
 	if (!cfgfname)
 		cfgfname = DEFAULT_CFG_FILE;
 
-	if ((ret = libuboot_read_config(ctx, cfgfname)) < 0) {
-		fprintf(stderr, "Configuration file wrong or corrupted\n");
-		exit (ret);
+	/*
+	 * Try first new format, fallback to legacy
+	 */
+	ret = libuboot_read_multiple_config(&ctx, cfgfname);
+	if (ret) {
+		if (libuboot_initialize(&ctx, NULL) < 0) {
+			fprintf(stderr, "Cannot initialize environment\n");
+			exit(1);
+			}
+		if ((ret = libuboot_read_config(ctx, cfgfname)) < 0) {
+			fprintf(stderr, "Configuration file wrong or corrupted\n");
+			exit (ret);
+		}
+	} else {
+		if (namespace)
+			ctx = libuboot_get_namespace(ctx, namespace);
+		if (!ctx) {
+			fprintf(stderr, "Namespace %s not found\n", namespace);
+			exit (1);
+		}
 	}
 
 	if (!defenvfile)
diff --git a/src/libuboot.h b/src/libuboot.h
index e59d0c4..f7a5fb9 100644
--- a/src/libuboot.h
+++ b/src/libuboot.h
@@ -43,6 +43,23 @@  struct uboot_env_device {
  */
 int libuboot_read_config(struct uboot_ctx *ctx, const char *config);
 
+/** @brief Read multiple environment configuration from a file
+ *
+ * @param[in] ctx libuboot context
+ * @param[in] config path to the configuration file in yaml format
+ * @return 0 in case of success, else negative value
+ */
+int libuboot_read_multiple_config(struct uboot_ctx **ctxlist, const char *config);
+
+/** @brief Get ctx from list
+ *
+ * @param[in] ctxlist libuboot context array
+ * @param[in] name name identifier for the single ctx
+ * @return 0 in case of success, else negative value
+ */
+struct uboot_ctx *libuboot_get_namespace(struct uboot_ctx *ctxlist, const char *name);
+
+
 /** @brief Read U-Boot environment configuration from structure
  *
  * @param[in] ctx libuboot context
diff --git a/src/uboot_env.c b/src/uboot_env.c
index a8e2e50..1662ba8 100644
--- a/src/uboot_env.c
+++ b/src/uboot_env.c
@@ -9,7 +9,7 @@ 
  * @file uboot_env.c
  *
  * @brief This is the implementation of libubootenv library
- *
+	*
  */
 
 #define _GNU_SOURCE
@@ -33,6 +33,7 @@ 
 #include <sys/wait.h>
 #include <sys/ioctl.h>
 #include <zlib.h>
+#include <yaml.h>
 #include <mtd/mtd-user.h>
 #include <mtd/ubi-user.h>
 
@@ -56,11 +57,54 @@ 
 #define MTDUNLOCK(dev, psector) \
 	if (!dev->disable_mtd_lock) ioctl (dev->fd, MEMUNLOCK, psector)
 
+/* yaml_* functions return 1 on success and 0 on failure. */
+enum yaml_status {
+    SUCCESS = 0,
+    FAILURE = 1
+};
+
+enum yaml_state {
+	STATE_START,    /* start state */
+	STATE_STREAM,   /* start/end stream */
+	STATE_DOCUMENT, /* start/end document */
+	STATE_SECTION,  /* top level */
+	
+	STATE_NAMESPACE,	/* Init Configuration Namespace */
+	STATE_NAMESPACE_FIELDS,	/* namespace key list */
+	STATE_NKEY,		/* Check key names */
+	STATE_NSIZE,		/* Size key-value pair */
+	STATE_NLOCKFILE,	/* Lockfile key-value pair */
+	STATE_DEVVALUES,	/* Devices key names */
+
+	STATE_NPATH,
+	STATE_NOFFSET,
+	STATE_NSECTORSIZE,
+	STATE_NUNLOCK,
+	STATE_STOP      /* end state */
+};
+	
+typedef enum yaml_parse_error_e {
+	YAML_UNEXPECTED_STATE,
+	YAML_UNEXPECTED_KEY,
+	YAML_BAD_DEVICE,
+	YAML_BAD_DEVNAME,
+} yaml_parse_error_type_t;
+
+struct parser_state {
+	enum yaml_state state;		/* The current parse state */
+	struct uboot_ctx *ctxsets;	/* Array of vars set ctx */
+	struct uboot_ctx *ctx;		/* Current ctx in parsing */
+	unsigned int nelem;		/* Number of elemets in ctxsets */ 
+	unsigned int cdev;		/* current device in parsing */ 
+	yaml_parse_error_type_t error;	/* error causing parser to stop */
+	yaml_event_type_t event_type;	/* event type causing error */
+};
+
 /*
  * The lockfile is the same as defined in U-Boot for
  * the fw_printenv utilities
  */
-static const char *lockname = "/var/lock/fw_printenv.lock";
+	static const char *lockname = "/var/lock/fw_printenv.lock";
 static int libuboot_lock(struct uboot_ctx *ctx)
 {
 	int lockfd = -1;
@@ -1146,6 +1190,315 @@  static int libuboot_load(struct uboot_ctx *ctx)
 	return ctx->valid ? 0 : -ENODATA;
 }
 
+int consume_event(struct parser_state *s, yaml_event_t *event)
+{
+	char *value;
+	struct uboot_flash_env *dev;
+	int cdev;
+
+	switch (s->state) {
+	case STATE_START:
+		switch (event->type) {
+		case YAML_STREAM_START_EVENT:
+			s->state = STATE_STREAM;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_STREAM:
+		switch (event->type) {
+		case YAML_DOCUMENT_START_EVENT:
+			s->state = STATE_DOCUMENT;
+			break;
+		case YAML_STREAM_END_EVENT:
+			s->state = STATE_STOP;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_DOCUMENT:
+		switch (event->type) {
+		case YAML_MAPPING_START_EVENT:
+			s->state = STATE_SECTION;
+			break;
+		case YAML_DOCUMENT_END_EVENT:
+			s->state = STATE_STREAM;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_SECTION:
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			struct uboot_ctx *newctx;
+			value = (char *)event->data.scalar.value;
+			newctx = calloc (s->nelem + 1, sizeof(*newctx));
+			for (int i = 0; i < s->nelem; i++) {
+				newctx[i] = s->ctxsets[i];
+			}
+			if (s->ctxsets) free(s->ctxsets);
+			s->ctxsets = newctx;
+			s->ctx = &newctx[s->nelem];
+			s->ctx->name = strdup(value);
+			s->nelem++;
+			s->state = STATE_NAMESPACE;
+			break;
+		case YAML_MAPPING_END_EVENT:
+			s->state = STATE_DOCUMENT;
+			break;
+		case YAML_DOCUMENT_END_EVENT:
+			s->state = STATE_STREAM;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NAMESPACE:
+		switch (event->type) {
+		case YAML_MAPPING_START_EVENT:
+			s->state = STATE_NAMESPACE_FIELDS;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NAMESPACE_FIELDS:
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			value = (char *)event->data.scalar.value;
+			if (!strcmp(value, "size")) {
+				s->state = STATE_NSIZE;
+			} else if (!strcmp(value, "lockfile")) {
+				s->state = STATE_NLOCKFILE;
+			} else if (!strcmp(value, "devices")) {
+				s->state = STATE_DEVVALUES;
+				s->cdev = 0;
+			} else {
+				s->error = YAML_UNEXPECTED_KEY;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			break;
+		case YAML_MAPPING_END_EVENT:
+			s->state = STATE_SECTION;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NSIZE:		
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			value = (char *)event->data.scalar.value;
+			errno = 0;
+			s->ctx->size = strtoull(value, NULL, 0);
+			s->state = STATE_NAMESPACE_FIELDS;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NLOCKFILE:		
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			value = (char *)event->data.scalar.value;
+			s->ctx->lockfile = strdup(value);
+			s->state = STATE_NAMESPACE_FIELDS;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_DEVVALUES:		
+		switch (event->type) {
+		case YAML_MAPPING_START_EVENT:
+		case YAML_SEQUENCE_START_EVENT:
+			break;
+		case YAML_MAPPING_END_EVENT:
+			dev = &s->ctx->envdevs[s->cdev];
+			if (check_env_device(dev) < 0) {
+				s->error = YAML_BAD_DEVICE;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			s->cdev++;
+			break;
+		case YAML_SEQUENCE_END_EVENT:
+			s->state = STATE_NAMESPACE_FIELDS;
+			break;
+		case YAML_SCALAR_EVENT:
+			value = (char *)event->data.scalar.value;
+			if (s->cdev)
+				s->ctx->redundant = true;
+			if (!strcmp(value, "path")) {
+				s->state = STATE_NPATH;
+			} else if (!strcmp(value, "offset")) {
+				s->state = STATE_NOFFSET;
+			} else if (!strcmp(value, "sectorsize")) {
+				s->state = STATE_NSECTORSIZE;
+				} else if (!strcmp(value, "disablelock")) {
+				s->state = STATE_NUNLOCK;
+			} else {
+				s->error = YAML_UNEXPECTED_KEY;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NPATH:		
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			dev = &s->ctx->envdevs[s->cdev];
+			value = (char *)event->data.scalar.value;
+			if (normalize_device_path(value, dev) < 0) {
+				s->error = YAML_BAD_DEVNAME;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			dev->envsize = s->ctx->size;
+			s->state = STATE_DEVVALUES;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NOFFSET:		
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			dev = &s->ctx->envdevs[s->cdev];
+			value = (char *)event->data.scalar.value;
+			dev->offset = strtoull(value, NULL, 0);
+			s->state = STATE_DEVVALUES;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NSECTORSIZE:		
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			dev = &s->ctx->envdevs[s->cdev];
+			value = (char *)event->data.scalar.value;
+			dev->sectorsize = strtoull(value, NULL, 0);
+			s->state = STATE_DEVVALUES;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+	case STATE_NUNLOCK:
+		switch (event->type) {
+		case YAML_SCALAR_EVENT:
+			dev = &s->ctx->envdevs[s->cdev];
+			value = (char *)event->data.scalar.value;
+			if (!strcmp(value, "yes"))
+				dev->disable_mtd_lock = 1;
+			s->state = STATE_DEVVALUES;
+			break;
+		default:
+			s->error = YAML_UNEXPECTED_STATE;
+			s->event_type = event->type;
+			return FAILURE;
+		}
+		break;
+
+    case STATE_STOP:
+        break;
+    }
+    return SUCCESS;
+}
+
+int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp)
+{
+	yaml_parser_t parser;
+	yaml_event_t  event;
+	enum yaml_status status;
+	struct parser_state state;
+	struct uboot_ctx *ctx;
+
+	if (!yaml_parser_initialize(&parser))
+		return -ENOMEM;
+
+	 /* Set input file */
+	yaml_parser_set_input_file(&parser, fp);
+	memset(&state, 0, sizeof(state));
+	state.state = STATE_START;
+	do {
+		if (!yaml_parser_parse(&parser, &event)) {
+			status = FAILURE;
+			goto cleanup;
+		}
+		status = consume_event(&state, &event);
+		yaml_event_delete(&event);
+		if (status == FAILURE) {
+			goto cleanup;
+		}
+	} while (state.state != STATE_STOP);
+
+	state.ctxsets[0].nelem = state.nelem;
+
+	for (int i = 0; i < state.nelem; i++) {
+		ctx = &state.ctxsets[i];
+		ctx->ctxlist = &state.ctxsets[0];
+		if (ctx->redundant && !check_compatible_devices(ctx)) {
+			status = FAILURE;
+			break;
+		}
+	}
+
+
+cleanup:
+	yaml_parser_delete(&parser);
+	if (status == FAILURE) {
+		if (state.ctxsets) free (state.ctxsets);
+		state.ctxsets = NULL;
+	}
+	*ctxlist = state.ctxsets;
+	return status; 
+}
+
 #define LINE_LENGTH 1024
 int libuboot_load_file(struct uboot_ctx *ctx, const char *filename)
 {
@@ -1198,6 +1551,24 @@  int libuboot_load_file(struct uboot_ctx *ctx, const char *filename)
 	return 0;
 }
 
+int libuboot_read_multiple_config(struct uboot_ctx **ctxlist, const char *config) 
+{
+	FILE *fp;
+	int ret;
+
+	if (!config)
+		return -EINVAL;
+
+	fp = fopen(config, "r");
+	if (!fp)
+		return -EBADF;
+
+	ret = parse_yaml_config(ctxlist, fp);
+	fclose(fp);
+
+	return ret;
+}
+
 int libuboot_read_config(struct uboot_ctx *ctx, const char *config)
 {
 	FILE *fp;
@@ -1466,6 +1837,28 @@  int libuboot_configure(struct uboot_ctx *ctx,
 	return 0;
 }
 
+struct uboot_ctx *libuboot_get_namespace(struct uboot_ctx *ctxlist, const char *name)
+{
+	struct uboot_ctx *ctx;
+	int i;
+
+	if (!ctxlist)
+		return NULL;
+
+	/*
+	 * Be sure to get the whole list, pointer is stored into each
+	 * CTX pointer in the list
+	 */
+	if (ctxlist->ctxlist)
+		ctxlist = ctxlist->ctxlist;
+	for (i = 0, ctx = ctxlist; i < ctxlist->nelem; i++, ctx++) {
+		if (!strcmp(ctx->name, name))
+			return ctx;
+	}
+
+	return NULL;
+}
+
 int libuboot_initialize(struct uboot_ctx **out,
 			struct uboot_env_device *envdevs) {
 	struct uboot_ctx *ctx;
@@ -1515,5 +1908,7 @@  void libuboot_close(struct uboot_ctx *ctx) {
 }
 
 void libuboot_exit(struct uboot_ctx *ctx) {
+	if (ctx->ctxlist)
+		ctx = ctx->ctxlist;
 	free(ctx);
 }
diff --git a/src/uboot_private.h b/src/uboot_private.h
index 25cc468..40e5446 100644
--- a/src/uboot_private.h
+++ b/src/uboot_private.h
@@ -10,6 +10,7 @@ 
 #include <stdbool.h>
 #include <stdint.h>
 #include <sys/queue.h>
+#include <sys/types.h>
 #include <mtd/mtd-user.h>
 #include "libuboot.h"
 
@@ -125,4 +126,12 @@  struct uboot_ctx {
 	int lock;
 	/** pointer to the internal db */
 	struct vars varlist;
+	/** name of the set */
+	char *name;
+	/** lockfile */
+	char *lockfile;
+	/** Number of namespaces */
+	int nelem;
+	/** private pointer to list */
+	struct uboot_ctx *ctxlist;
 };