diff mbox series

[libubootenv] Make libyaml optional

Message ID 20241029110434.1004740-1-stefano.babic@swupdate.org
State New
Headers show
Series [libubootenv] Make libyaml optional | expand

Commit Message

Stefano Babic Oct. 29, 2024, 11:04 a.m. UTC
NewYAML format is required for extended features because the format
foreseen by U-Boot is very limited. However, some systems due to
low resources don't want to link to libyaml. Add the option
NO_YML_SUPPORT to disable YAML configuration file and just use
fw_env.config in the U-Boot format.

There are no functional changes in this patch - function depending on
YML are moved in a separate file, and some functions are factorized.

Signed-off-by: Stefano Babic <stefano.babic@swupdate.org>
---
 CMakeLists.txt        |   6 +
 src/CMakeLists.txt    |   9 +-
 src/common.c          | 248 ++++++++++++++++
 src/common.h          |  15 +
 src/extended_config.c | 452 +++++++++++++++++++++++++++++
 src/uboot_env.c       | 655 +-----------------------------------------
 6 files changed, 735 insertions(+), 650 deletions(-)
 create mode 100644 src/common.c
 create mode 100644 src/common.h
 create mode 100644 src/extended_config.c

--
2.34.1
diff mbox series

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3bb93e1..796d7bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,8 @@  set(VERSION	"0.3.6")
 SET(SOVERSION "0")
 add_definitions(-DVERSION="${VERSION}")

+option(NO_YML_SUPPORT "YML Support")
+
 if(DEFAULT_CFG_FILE)
     add_definitions(-DDEFAULT_CFG_FILE="${DEFAULT_CFG_FILE}")
 endif(DEFAULT_CFG_FILE)
@@ -20,6 +22,10 @@  if(DEFAULT_ENV_FILE)
     add_definitions(-DDEFAULT_ENV_FILE="${DEFAULT_ENV_FILE}")
 endif(DEFAULT_ENV_FILE)

+if(NO_YML_SUPPORT)
+  add_definitions(-DNO_YAML_SUPPORT)
+endif(NO_YML_SUPPORT)
+
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")

 #set(CMAKE_C_FLAGS_DEBUG "-g")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 63d4822..c56d0c7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,6 +6,9 @@  cmake_minimum_required (VERSION 2.6)
 SET(libubootenv_SOURCES
   uboot_env.c
   uboot_mtd.c
+  extended_config.c
+  common.c
+  common.h
   uboot_private.h
 )

@@ -22,7 +25,11 @@  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 yaml)
+target_link_libraries(ubootenv z)
+if (NOT NO_YML_SUPPORT)
+target_link_libraries(ubootenv yaml)
+endif(NOT NO_YML_SUPPORT)
+
 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/common.c b/src/common.c
new file mode 100644
index 0000000..2cbde93
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,248 @@ 
+/*
+ * (C) Copyright 2024
+ * Stefano Babic, <stefano.babic@swupdate.org>
+ *
+ * SPDX-License-Identifier:     LGPL-2.1-or-later
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#ifdef __FreeBSD__
+#include <sys/disk.h>
+#define BLKGETSIZE64 DIOCGMEDIASIZE
+#else
+#include <linux/fs.h>
+#endif
+
+#include "uboot_private.h"
+#include "common.h"
+
+static enum device_type get_device_type(char *device)
+{
+	enum device_type type = DEVICE_NONE;
+
+	if (!strncmp(device, DEVICE_MTD_NAME, strlen(DEVICE_MTD_NAME)))
+		if (strchr(device, DEVNAME_SEPARATOR)) {
+			type = DEVICE_UBI;
+		} else {
+			type = DEVICE_MTD;
+		}
+	else if (!strncmp(device, DEVICE_UBI_NAME, strlen(DEVICE_UBI_NAME)))
+		type = DEVICE_UBI;
+	else if (strlen(device) > 0)
+		type = DEVICE_FILE;
+
+	return type;
+}
+
+int normalize_device_path(char *path, struct uboot_flash_env *dev)
+{
+	char *sep = NULL, *normalized = NULL;
+	size_t normalized_len = 0, volume_len = 0, output_len = 0;
+
+	/*
+	 * if volume name is present, split into device path and volume
+	 * since only the device path needs normalized
+	 */
+	sep = strchr(path, DEVNAME_SEPARATOR);
+	if (sep)
+	{
+		volume_len = strlen(sep);
+		*sep = '\0';
+	}
+
+	if ((normalized = realpath(path, NULL)) == NULL)
+	{
+		/* device file didn't exist */
+		return -EINVAL;
+	}
+
+	normalized_len = strlen(normalized);
+	output_len = sizeof(dev->devname) - 1; /* leave room for null */
+	if ((normalized_len + volume_len) > output_len)
+	{
+		/* full name is too long to fit */
+		free(normalized);
+		return -EINVAL;
+	}
+
+	/*
+	 * save normalized path to device file,
+	 * and possibly append separator char & volume name
+	 */
+	memset(dev->devname, 0, sizeof(dev->devname));
+	strncpy(dev->devname, normalized, output_len);
+	free(normalized);
+
+	if (sep)
+	{
+		*sep = DEVNAME_SEPARATOR;
+		strncpy(dev->devname + normalized_len, sep, output_len - normalized_len);
+	}
+
+	return 0;
+}
+
+void set_var_access_type(struct var_entry *entry, const char *pvarflags)
+{
+	if (entry) {
+		for (int i = 0; i < strlen(pvarflags); i++) {
+			switch (pvarflags[i]) {
+			case 's':
+				entry->type = TYPE_ATTR_STRING;
+				break;
+			case 'd':
+				entry->type = TYPE_ATTR_DECIMAL;
+				break;
+			case 'x':
+				entry->type = TYPE_ATTR_HEX;
+				break;
+			case 'b':
+				entry->type = TYPE_ATTR_BOOL;
+				break;
+			case 'i':
+				entry->type = TYPE_ATTR_IP;
+				break;
+			case 'm':
+				entry->type = TYPE_ATTR_MAC;
+				break;
+			case 'a':
+				entry->access = ACCESS_ATTR_ANY;
+				break;
+			case 'r':
+				entry->access = ACCESS_ATTR_READ_ONLY;
+				break;
+			case 'o':
+				entry->access = ACCESS_ATTR_WRITE_ONCE;
+				break;
+			case 'c':
+				entry->access = ACCESS_ATTR_CHANGE_DEFAULT;
+				break;
+			default: /* ignore it */
+				break;
+			}
+		}
+	}
+}
+
+struct var_entry *create_var_entry(const char *name)
+{
+	struct var_entry *entry;
+
+	entry = (struct var_entry *)calloc(1, sizeof(*entry));
+	if (!entry)
+		return NULL;
+	entry->name = strdup(name);
+	if (!entry->name) {
+		free(entry);
+		return NULL;
+	}
+
+	return entry;
+}
+
+bool check_compatible_devices(struct uboot_ctx *ctx)
+{
+	if (!ctx->redundant)
+		return true;
+
+	if (ctx->envdevs[0].mtdinfo.type != ctx->envdevs[1].mtdinfo.type)
+		return false;
+	if (ctx->envdevs[0].flagstype != ctx->envdevs[1].flagstype)
+		return false;
+	if (ctx->envdevs[0].envsize != ctx->envdevs[1].envsize)
+		return false;
+
+	return true;
+}
+
+int check_env_device(struct uboot_flash_env *dev)
+{
+	int fd, ret;
+	struct stat st;
+
+	dev->device_type = get_device_type(dev->devname);
+	if (dev->device_type == DEVICE_NONE)
+		return -EBADF;
+
+	if (dev->device_type == DEVICE_UBI) {
+		ret = libubootenv_ubi_update_name(dev);
+		if (ret)
+			return ret;
+	}
+
+	ret = stat(dev->devname, &st);
+	if (ret < 0)
+		return -EBADF;
+	fd = open(dev->devname, O_RDONLY);
+	if (fd < 0)
+		return -EBADF;
+
+	if (S_ISCHR(st.st_mode)) {
+		if (dev->device_type == DEVICE_MTD) {
+			ret = libubootenv_mtdgetinfo(fd, dev);
+			if (ret < 0 || (dev->mtdinfo.type != MTD_NORFLASH &&
+					dev->mtdinfo.type != MTD_NANDFLASH)) {
+				close(fd);
+				return -EBADF;
+			}
+			if (dev->sectorsize == 0) {
+				dev->sectorsize = dev->mtdinfo.erasesize;
+			}
+		}
+	}
+
+	switch (dev->device_type) {
+	case DEVICE_FILE:
+		dev->flagstype = FLAGS_INCREMENTAL;
+		break;
+	case DEVICE_MTD:
+		switch (dev->mtdinfo.type) {
+		case MTD_NORFLASH:
+			dev->flagstype = FLAGS_BOOLEAN;
+			break;
+		case MTD_NANDFLASH:
+			dev->flagstype = FLAGS_INCREMENTAL;
+		};
+		break;
+	case DEVICE_UBI:
+		dev->flagstype = FLAGS_INCREMENTAL;
+		break;
+	default:
+		close(fd);
+		return -EBADF;
+	};
+
+	/*
+	 * Check for negative offsets, treat it as backwards offset
+	 * from the end of the block device
+	 */
+	if (dev->offset < 0) {
+		uint64_t blkdevsize;
+		int rc;
+
+		rc = ioctl(fd, BLKGETSIZE64, &blkdevsize);
+		if (rc < 0) {
+			close(fd);
+			return -EINVAL;
+		}
+
+		dev->offset += blkdevsize;
+	}
+
+	close(fd);
+
+	return 0;
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..adacd48
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,15 @@ 
+/*
+ * (C) Copyright 2024
+ * Stefano Babic, <stefano.babic@swupdate.org>
+ *
+ * SPDX-License-Identifier:     LGPL-2.1-or-later
+ */
+
+
+#include "uboot_private.h"
+
+struct var_entry *create_var_entry(const char *name);
+void set_var_access_type(struct var_entry *entry, const char *pvarflags);
+int normalize_device_path(char *path, struct uboot_flash_env *dev);
+int check_env_device(struct uboot_flash_env *dev);
+bool check_compatible_devices(struct uboot_ctx *ctx);
diff --git a/src/extended_config.c b/src/extended_config.c
new file mode 100644
index 0000000..ec814f4
--- /dev/null
+++ b/src/extended_config.c
@@ -0,0 +1,452 @@ 
+/*
+ * (C) Copyright 2024
+ * Stefano Babic, <stefano.babic@swupdate.org>
+ *
+ * SPDX-License-Identifier:     LGPL-2.1-or-later
+ */
+
+/**
+ * @file extended_config.c
+ *
+ * @brief Implement the extended config file YAML
+	*
+ */
+#define _GNU_SOURCE
+
+#if !defined(NO_YAML_SUPPORT)
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <yaml.h>
+
+#include "uboot_private.h"
+#include "common.h"
+
+/* 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_WRITELIST,	/* List with vars that are accepted by write
+				 * if list is missing, all vars are accepted
+				 * var is in the format name:flags, see U-Boot
+				 * documentation
+				 */
+
+	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_BAD_VARLIST,
+	YAML_DUPLICATE_VARLIST,
+	YAML_OOM,
+} 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 */
+};
+
+static int consume_event(struct parser_state *s, yaml_event_t *event)
+{
+	char *value;
+	struct uboot_flash_env *dev;
+	struct uboot_ctx *newctx;
+	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:
+			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 if (!strcmp(value, "writelist")) {
+				s->state = STATE_WRITELIST;
+			} 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_WRITELIST:
+		switch (event->type) {
+
+		char *varflag, *name;
+		struct var_entry *entry;
+
+		case YAML_MAPPING_START_EVENT:
+		case YAML_SEQUENCE_START_EVENT:
+			break;
+		case YAML_MAPPING_END_EVENT:
+			break;
+		case YAML_SEQUENCE_END_EVENT:
+			s->state = STATE_NAMESPACE_FIELDS;
+			break;
+		case YAML_SCALAR_EVENT:
+			value = (char *)event->data.scalar.value;
+
+			/*
+			 * Format is name:flags, split it into two values
+			 */
+			varflag = strchr(value, ':');
+			if (!varflag || varflag > value + (strlen(value) - 1)) {
+				s->error = YAML_BAD_VARLIST;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			*varflag++ = '\0';
+
+			/*
+			 * Check there is not yet an entry for this variable
+			 */
+			LIST_FOREACH(entry, &s->ctx->writevarlist, next) {
+				if (strcmp(entry->name, value) == 0) {
+					s->error = YAML_DUPLICATE_VARLIST;
+					s->event_type = event->type;
+					return FAILURE;
+				}
+			}
+
+			/*
+			 * Insert variable with its configuration into the list
+			 * of modifiable vars
+			 */
+			entry = create_var_entry(value);
+			if (!entry) {
+				s->error = YAML_OOM;
+				s->event_type = event->type;
+				return FAILURE;
+			}
+			set_var_access_type(entry, varflag);
+			LIST_INSERT_HEAD(&s->ctx->writevarlist, entry, next);
+
+#if !defined(NDEBUG)
+			fprintf(stdout, "Writelist: %s flags %s\n", value, varflag);
+#endif
+			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;
+}
+#endif
diff --git a/src/uboot_env.c b/src/uboot_env.c
index 0b4f9f4..d8b93da 100644
--- a/src/uboot_env.c
+++ b/src/uboot_env.c
@@ -21,12 +21,6 @@ 
 #include <dirent.h>
 #include <unistd.h>
 #include <limits.h>
-#ifdef __FreeBSD__
-#include <sys/disk.h>
-#define BLKGETSIZE64 DIOCGMEDIASIZE
-#else
-#include <linux/fs.h>
-#endif
 #include <string.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -38,60 +32,15 @@ 
 #include <sys/wait.h>
 #include <sys/ioctl.h>
 #include <zlib.h>
-#include <yaml.h>

 #include "uboot_private.h"
+#include "common.h"

-/* 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_WRITELIST,	/* List with vars that are accepted by write
-				 * if list is missing, all vars are accepted
-				 * var is in the format name:flags, see U-Boot
-				 * documentation
-				 */
-
-	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_BAD_VARLIST,
-	YAML_DUPLICATE_VARLIST,
-	YAML_OOM,
-} 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 */
-};
+#if defined(NO_YAML_SUPPORT)
+#define parse_yaml_config(ctx,fp) -1
+#else
+extern int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp);
+#endif

 #define FREE_ENTRY do { \
 	free(entry->name); \
@@ -152,64 +101,6 @@  static char attr_tostring(type_attribute a)
 	return 's';
 }

-static void set_var_access_type(struct var_entry *entry, const char *pvarflags)
-{
-	if (entry) {
-		for (int i = 0; i < strlen(pvarflags); i++) {
-			switch (pvarflags[i]) {
-			case 's':
-				entry->type = TYPE_ATTR_STRING;
-				break;
-			case 'd':
-				entry->type = TYPE_ATTR_DECIMAL;
-				break;
-			case 'x':
-				entry->type = TYPE_ATTR_HEX;
-				break;
-			case 'b':
-				entry->type = TYPE_ATTR_BOOL;
-				break;
-			case 'i':
-				entry->type = TYPE_ATTR_IP;
-				break;
-			case 'm':
-				entry->type = TYPE_ATTR_MAC;
-				break;
-			case 'a':
-				entry->access = ACCESS_ATTR_ANY;
-				break;
-			case 'r':
-				entry->access = ACCESS_ATTR_READ_ONLY;
-				break;
-			case 'o':
-				entry->access = ACCESS_ATTR_WRITE_ONCE;
-				break;
-			case 'c':
-				entry->access = ACCESS_ATTR_CHANGE_DEFAULT;
-				break;
-			default: /* ignore it */
-				break;
-			}
-		}
-	}
-}
-
-static struct var_entry *create_var_entry(const char *name)
-{
-	struct var_entry *entry;
-
-	entry = (struct var_entry *)calloc(1, sizeof(*entry));
-	if (!entry)
-		return NULL;
-	entry->name = strdup(name);
-	if (!entry->name) {
-		free(entry);
-		return NULL;
-	}
-
-	return entry;
-}
-
 static char access_tostring(access_attribute a)
 {
 	switch(a) {
@@ -389,166 +280,6 @@  static int __libuboot_set_env(struct uboot_ctx *ctx, const char *varname, const
 	return 0;
 }

-static enum device_type get_device_type(char *device)
-{
-	enum device_type type = DEVICE_NONE;
-
-	if (!strncmp(device, DEVICE_MTD_NAME, strlen(DEVICE_MTD_NAME)))
-		if (strchr(device, DEVNAME_SEPARATOR)) {
-			type = DEVICE_UBI;
-		} else {
-			type = DEVICE_MTD;
-		}
-	else if (!strncmp(device, DEVICE_UBI_NAME, strlen(DEVICE_UBI_NAME)))
-		type = DEVICE_UBI;
-	else if (strlen(device) > 0)
-		type = DEVICE_FILE;
-
-	return type;
-}
-
-static int normalize_device_path(char *path, struct uboot_flash_env *dev)
-{
-	char *sep = NULL, *normalized = NULL;
-	size_t normalized_len = 0, volume_len = 0, output_len = 0;
-
-	/*
-	 * if volume name is present, split into device path and volume
-	 * since only the device path needs normalized
-	 */
-	sep = strchr(path, DEVNAME_SEPARATOR);
-	if (sep)
-	{
-		volume_len = strlen(sep);
-		*sep = '\0';
-	}
-
-	if ((normalized = realpath(path, NULL)) == NULL)
-	{
-		/* device file didn't exist */
-		return -EINVAL;
-	}
-
-	normalized_len = strlen(normalized);
-	output_len = sizeof(dev->devname) - 1; /* leave room for null */
-	if ((normalized_len + volume_len) > output_len)
-	{
-		/* full name is too long to fit */
-		free(normalized);
-		return -EINVAL;
-	}
-
-	/*
-	 * save normalized path to device file,
-	 * and possibly append separator char & volume name
-	 */
-	memset(dev->devname, 0, sizeof(dev->devname));
-	strncpy(dev->devname, normalized, output_len);
-	free(normalized);
-
-	if (sep)
-	{
-		*sep = DEVNAME_SEPARATOR;
-		strncpy(dev->devname + normalized_len, sep, output_len - normalized_len);
-	}
-
-	return 0;
-}
-
-static int check_env_device(struct uboot_flash_env *dev)
-{
-	int fd, ret;
-	struct stat st;
-
-	dev->device_type = get_device_type(dev->devname);
-	if (dev->device_type == DEVICE_NONE)
-		return -EBADF;
-
-	if (dev->device_type == DEVICE_UBI) {
-		ret = libubootenv_ubi_update_name(dev);
-		if (ret)
-			return ret;
-	}
-
-	ret = stat(dev->devname, &st);
-	if (ret < 0)
-		return -EBADF;
-	fd = open(dev->devname, O_RDONLY);
-	if (fd < 0)
-		return -EBADF;
-
-	if (S_ISCHR(st.st_mode)) {
-		if (dev->device_type == DEVICE_MTD) {
-			ret = libubootenv_mtdgetinfo(fd, dev);
-			if (ret < 0 || (dev->mtdinfo.type != MTD_NORFLASH &&
-					dev->mtdinfo.type != MTD_NANDFLASH)) {
-				close(fd);
-				return -EBADF;
-			}
-			if (dev->sectorsize == 0) {
-				dev->sectorsize = dev->mtdinfo.erasesize;
-			}
-		}
-	}
-
-	switch (dev->device_type) {
-	case DEVICE_FILE:
-		dev->flagstype = FLAGS_INCREMENTAL;
-		break;
-	case DEVICE_MTD:
-		switch (dev->mtdinfo.type) {
-		case MTD_NORFLASH:
-			dev->flagstype = FLAGS_BOOLEAN;
-			break;
-		case MTD_NANDFLASH:
-			dev->flagstype = FLAGS_INCREMENTAL;
-		};
-		break;
-	case DEVICE_UBI:
-		dev->flagstype = FLAGS_INCREMENTAL;
-		break;
-	default:
-		close(fd);
-		return -EBADF;
-	};
-
-	/*
-	 * Check for negative offsets, treat it as backwards offset
-	 * from the end of the block device
-	 */
-	if (dev->offset < 0) {
-		uint64_t blkdevsize;
-		int rc;
-
-		rc = ioctl(fd, BLKGETSIZE64, &blkdevsize);
-		if (rc < 0) {
-			close(fd);
-			return -EINVAL;
-		}
-
-		dev->offset += blkdevsize;
-	}
-
-	close(fd);
-
-	return 0;
-}
-
-static bool check_compatible_devices(struct uboot_ctx *ctx)
-{
-	if (!ctx->redundant)
-		return true;
-
-	if (ctx->envdevs[0].mtdinfo.type != ctx->envdevs[1].mtdinfo.type)
-		return false;
-	if (ctx->envdevs[0].flagstype != ctx->envdevs[1].flagstype)
-		return false;
-	if (ctx->envdevs[0].envsize != ctx->envdevs[1].envsize)
-		return false;
-
-	return true;
-}
-
 static int fileread(struct uboot_flash_env *dev, void *data)
 {
 	int ret = 0;
@@ -1035,380 +766,6 @@  static int libuboot_load(struct uboot_ctx *ctx)
 	return ctx->valid ? 0 : -ENODATA;
 }

-static int consume_event(struct parser_state *s, yaml_event_t *event)
-{
-	char *value;
-	struct uboot_flash_env *dev;
-	struct uboot_ctx *newctx;
-	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:
-			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 if (!strcmp(value, "writelist")) {
-				s->state = STATE_WRITELIST;
-			} 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_WRITELIST:
-		switch (event->type) {
-
-		char *varflag, *name;
-		struct var_entry *entry;
-
-		case YAML_MAPPING_START_EVENT:
-		case YAML_SEQUENCE_START_EVENT:
-			break;
-		case YAML_MAPPING_END_EVENT:
-			break;
-		case YAML_SEQUENCE_END_EVENT:
-			s->state = STATE_NAMESPACE_FIELDS;
-			break;
-		case YAML_SCALAR_EVENT:
-			value = (char *)event->data.scalar.value;
-
-			/*
-			 * Format is name:flags, split it into two values
-			 */
-			varflag = strchr(value, ':');
-			if (!varflag || varflag > value + (strlen(value) - 1)) {
-				s->error = YAML_BAD_VARLIST;
-				s->event_type = event->type;
-				return FAILURE;
-			}
-			*varflag++ = '\0';
-
-			/*
-			 * Check there is not yet an entry for this variable
-			 */
-			LIST_FOREACH(entry, &s->ctx->writevarlist, next) {
-				if (strcmp(entry->name, value) == 0) {
-					s->error = YAML_DUPLICATE_VARLIST;
-					s->event_type = event->type;
-					return FAILURE;
-				}
-			}
-
-			/*
-			 * Insert variable with its configuration into the list
-			 * of modifiable vars
-			 */
-			entry = create_var_entry(value);
-			if (!entry) {
-				s->error = YAML_OOM;
-				s->event_type = event->type;
-				return FAILURE;
-			}
-			set_var_access_type(entry, varflag);
-			LIST_INSERT_HEAD(&s->ctx->writevarlist, entry, next);
-
-#if !defined(NDEBUG)
-			fprintf(stdout, "Writelist: %s flags %s\n", value, varflag);
-#endif
-			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 2048
 int libuboot_load_file(struct uboot_ctx *ctx, const char *filename)
 {