From: Andreas Reichel <andreas.reichel....@siemens.com>

Add possibility for user to store arbitrary variables into environment.
Using the API, the user can insert all kind of binary data into the
environment by defining user specific types, stored as `char *`. Please
note that the user is solely responsible for these defined types and the
API itself does not care and only handles raw binary data. The type
field is merely for convenience purpose.
Using `bg_setenv`, the user can specify multiple `-x KEY=VAL` arguments,
which will set corresponding user variables. By omitting the value `VAL`,
the variable can be deleted.
`bg_printenv` outputs a list of user variables and prints their content,
if the type is set to `"String"`. Thus, the type field is not only
practicable for the user, but also for the tools.

Signed-off-by: Andreas Reichel <andreas.reichel....@siemens.com>
---
 Makefile.am            |   1 +
 configure.ac           |  17 ++++
 docs/TODO.md           |  16 +--
 docs/TOOLS.md          |  10 ++
 env/env_api.c          |  29 +++++-
 env/env_api_fat.c      |  76 ++++++++++----
 env/uservars.c         | 267 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/ebgenv.h       |  36 ++++++-
 include/env_api.h      |  13 ++-
 include/envdata.h      |   1 +
 include/uservars.h     |  37 +++++++
 tools/bg_setenv.c      |  82 ++++++++++++++-
 tools/tests/test_api.c | 124 ++++++++++++++++++++++-
 13 files changed, 669 insertions(+), 40 deletions(-)
 create mode 100644 env/uservars.c
 create mode 100644 include/uservars.h

diff --git a/Makefile.am b/Makefile.am
index bbef557..af64646 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -44,6 +44,7 @@ lib_LIBRARIES = libebgenv.a
 libebgenv_a_SOURCES = \
        env/@env_api_file@.c \
        env/env_api.c \
+       env/uservars.c \
        tools/ebgpart.c
 
 libebgenv_a_CPPFLAGS = \
diff --git a/configure.ac b/configure.ac
index aa9e314..fbd14fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -156,6 +156,22 @@ AC_ARG_WITH([num-config-parts],
            [ ENV_NUM_CONFIG_PARTS=2 ])
 
 AC_DEFINE_UNQUOTED([ENV_NUM_CONFIG_PARTS], [${ENV_NUM_CONFIG_PARTS}], [Number 
of config partitions])
+
+AC_ARG_WITH([mem-uservars],
+           AS_HELP_STRING([--with-mem-uservars=INT],
+                          [specify amount of memory reserved for user 
variables in bytes]),
+           [
+               ENV_MEM_USERVARS=${withval:-0}
+               AS_IF([test "${ENV_MEM_USERVARS}" -lt "96"],
+                     [
+                       AC_MSG_ERROR([Minimum space allowed for uservars is 96 
bytes])
+                     ])
+           ],
+           [
+               ENV_MEM_USERVARS=131072
+           ])
+
+AC_DEFINE_UNQUOTED([ENV_MEM_USERVARS], [${ENV_MEM_USERVARS}], [Reserved memory 
for user variables])
 # 
------------------------------------------------------------------------------
 AC_CONFIG_FILES([
        Makefile
@@ -178,5 +194,6 @@ AC_MSG_RESULT([
 
        environment backend:     ${ENV_API_FILE}.c
        number of config parts:  ${ENV_NUM_CONFIG_PARTS}
+       reserved for uservars:   ${ENV_MEM_USERVARS} bytes
 ])
 
diff --git a/docs/TODO.md b/docs/TODO.md
index 29b7fe0..6f104ab 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -6,12 +6,16 @@
          the current working environment to the (latest-1) environment, so
          that if the current environment breaks, there is a backup with the
          latest values.
-
-* Application specific variables
-       * applications may need to store their own variables into the
-         bootloader environment. Currently this is not possible. A generic
-         method must be defined and implemented to account for generic
-         key-value pairs.
+       * Currently, `bg_setenv` generates a virtual environment copy while
+         parsing arguments and later uses an algorithm to find out, how to
+         merge this with the actual environment being modified. This led to
+         the introduction of a special marker byte which tells the algorithm
+         to not touch the original content. Deletion of user variables led to
+         another special case, where *negative* variables had to be defined by
+         a special `DELETED` type to tell the algorithm that the specific user
+         variable has to be deleted. This is rather complicated and a better
+         aproach has already been discussed using a journal with actions
+         instead of a prebuilt state.
 
 * API refactoring
        * Function / Datatype / Variable names remind of Parted and should be
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index 9883589..7dd1f83 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -83,3 +83,13 @@ issue:
 ```
 bg_setenv --partition=1 --ustate=TESTING
 ```
+
+### Setting user variables ###
+
+`bg_setenv` has support for default user variables, meaning of type "String". 
To set a user variable, specify the `-x` flag:
+
+```
+bg_setenv -x key=value
+```
+
+This will set the variable named `key` to `value` in the current environment.
diff --git a/env/env_api.c b/env/env_api.c
index 26d823d..b3377fc 100644
--- a/env/env_api.c
+++ b/env/env_api.c
@@ -12,6 +12,7 @@
 
 #include "env_api.h"
 #include "ebgenv.h"
+#include "uservars.h"
 
 /* UEFI uses 16-bit wide unicode strings.
  * However, wchar_t support functions are fixed to 32-bit wide
@@ -90,10 +91,34 @@ int ebg_env_get(ebgenv_t *e, char *key, char *buffer)
                         ENV_STRING_LENGTH);
 }
 
+int ebg_env_get_ex(ebgenv_t *e, char *key, char *usertype, uint8_t *buffer,
+                  uint32_t maxlen)
+{
+       return bgenv_get((BGENV *)e->bgenv, key, usertype, buffer, maxlen);
+}
+
 int ebg_env_set(ebgenv_t *e, char *key, char *value)
 {
-       return bgenv_set((BGENV *)e->bgenv, key, "String", value,
-                        strlen(value)); }
+       return bgenv_set((BGENV *)e->bgenv, key, USERVAR_TYPE_DEFAULT, value,
+                        strlen(value) + 1);
+}
+
+int ebg_env_set_ex(ebgenv_t *e, char *key, char *usertype, uint8_t *value,
+                  uint32_t datalen)
+{
+       return bgenv_set((BGENV *)e->bgenv, key, usertype, value, datalen);
+}
+
+uint32_t ebg_env_user_free(ebgenv_t *e)
+{
+       if (!e->bgenv) {
+               return 0;
+       }
+       if (!((BGENV *)e->bgenv)->data) {
+               return 0;
+       }
+       return bgenv_user_free(((BGENV *)e->bgenv)->data->userdata);
+}
 
 uint16_t ebg_env_getglobalstate(ebgenv_t *e)
 {
diff --git a/env/env_api_fat.c b/env/env_api_fat.c
index 10fe37b..ed517f7 100644
--- a/env/env_api_fat.c
+++ b/env/env_api_fat.c
@@ -12,11 +12,12 @@
 
 #include "env_api.h"
 #include "ebgpart.h"
+#include "uservars.h"
 #include "test-interface.h"
 
 const char *tmp_mnt_dir = "/tmp/mnt-XXXXXX";
 
-static bool verbosity = false;
+bool bgenv_verbosity = false;
 
 static EBGENVKEY bgenv_str2enum(char *key)
 {
@@ -41,7 +42,7 @@ static EBGENVKEY bgenv_str2enum(char *key)
 
 void bgenv_be_verbose(bool v)
 {
-       verbosity = v;
+       bgenv_verbosity = v;
        ebgpart_beverbose(v);
 }
 
@@ -439,58 +440,90 @@ bool bgenv_close(BGENV *env)
        return false;
 }
 
-int bgenv_get(BGENV *env, char *key, char **type, void *data, size_t maxlen)
+int bgenv_get(BGENV *env, char *key, char *type, void *data, uint32_t maxlen)
 {
        EBGENVKEY e;
+       char buffer[ENV_STRING_LENGTH];
 
-       if (!key || !data || maxlen == 0) {
+       if (!key || maxlen == 0) {
                return EINVAL;
        }
        e = bgenv_str2enum(key);
-       if (e == EBGENV_UNKNOWN) {
-               return EINVAL;
-       }
        if (!env) {
                return EPERM;
        }
+       if (e == EBGENV_UNKNOWN) {
+               if (!data) {
+                       uint8_t *u;
+                       uint32_t size;
+                       u = bgenv_find_uservar(env->data->userdata, key);
+                       bgenv_map_uservar(u, NULL, NULL, NULL, NULL, &size);
+                       return size;
+               }
+               return bgenv_get_uservar(env->data->userdata, key, type, data,
+                                        maxlen);
+       }
        switch (e) {
        case EBGENV_KERNELFILE:
-               str16to8(data, env->data->kernelfile);
+               str16to8(buffer, env->data->kernelfile);
+               if (!data) {
+                       return strlen(buffer);
+               }
+               strncpy(data, buffer, strlen(buffer)+1);
                if (type) {
-                       sprintf(*type, "char*");
+                       sprintf(type, "char*");
                }
                break;
        case EBGENV_KERNELPARAMS:
-               str16to8(data, env->data->kernelparams);
+               str16to8(buffer, env->data->kernelparams);
+               if (!data) {
+                       return strlen(buffer);
+               }
+               strncpy(data, buffer, strlen(buffer)+1);
                if (type) {
-                       sprintf(*type, "char*");
+                       sprintf(type, "char*");
                }
                break;
        case EBGENV_WATCHDOG_TIMEOUT_SEC:
-               sprintf(data, "%lu", env->data->watchdog_timeout_sec);
+               sprintf(buffer, "%lu", env->data->watchdog_timeout_sec);
+               if (!data) {
+                       return strlen(buffer);
+               }
+               strncpy(data, buffer, strlen(buffer)+1);
                if (type) {
-                       sprintf(*type, "uint16_t");
+                       sprintf(type, "uint16_t");
                }
                break;
        case EBGENV_REVISION:
-               sprintf(data, "%lu", env->data->revision);
+               sprintf(buffer, "%lu", env->data->revision);
+               if (!data) {
+                       return strlen(buffer);
+               }
+               strncpy(data, buffer, strlen(buffer)+1);
                if (type) {
-                       sprintf(*type, "uint32_t");
+                       sprintf(type, "uint32_t");
                }
                break;
        case EBGENV_USTATE:
-               sprintf(data, "%u", env->data->ustate);
+               sprintf(buffer, "%u", env->data->ustate);
+               if (!data) {
+                       return strlen(buffer);
+               }
+               strncpy(data, buffer, strlen(buffer)+1);
                if (type) {
-                       sprintf(*type, "uint16_t");
+                       sprintf(type, "uint16_t");
                }
                break;
        default:
+               if (!data) {
+                       return 0;
+               }
                return EINVAL;
        }
        return 0;
 }
 
-int bgenv_set(BGENV *env, char *key, char *type, void *data, size_t datalen)
+int bgenv_set(BGENV *env, char *key, char *type, void *data, uint32_t datalen)
 {
        EBGENVKEY e;
        int val;
@@ -502,12 +535,13 @@ int bgenv_set(BGENV *env, char *key, char *type, void 
*data, size_t datalen)
        }
 
        e = bgenv_str2enum(key);
-       if (e == EBGENV_UNKNOWN) {
-               return EINVAL;
-       }
        if (!env) {
                return EPERM;
        }
+       if (e == EBGENV_UNKNOWN) {
+               return bgenv_set_uservar(env->data->userdata, key, type, data,
+                                        datalen);
+       }
        switch (e) {
        case EBGENV_REVISION:
                val = strtol(value, &p, 10);
diff --git a/env/uservars.c b/env/uservars.c
new file mode 100644
index 0000000..ffdd604
--- /dev/null
+++ b/env/uservars.c
@@ -0,0 +1,267 @@
+/*
+ * EFI Boot Guard
+ *
+ * Copyright (c) Siemens AG, 2017
+ *
+ * Authors:
+ *  Andreas Reichel <andreas.reichel....@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include <string.h>
+#include "env_api.h"
+#include "uservars.h"
+
+void bgenv_map_uservar(uint8_t *udata, char **key, char **type, uint8_t **val,
+                      uint32_t *record_size, uint32_t *data_size)
+{
+       /* Each user variable is encoded as follows:
+        * |------------|--------------|-------------|----------------|
+        * | char KEY[] | uint32_t len | char type[] | uint8_t data[] |
+        * |------------|--------------|-------------|----------------|
+        * |   KEY      | < - - - - - - - - PAYLOAD - - - - - - - - > |
+        *
+        * here char[] is a null-terminated string
+        * 'len' is the payload size (visualized by the horizontal dashes)
+        */
+       char *var_key;
+       uint32_t *payload_size;
+       char *var_type;
+       uint8_t *data;
+
+       /* Get the key */
+       var_key = (char *)udata;
+       if (key) {
+               *key = var_key;
+       }
+
+       /* Get position of payload size */
+       payload_size = (uint32_t *)(var_key + strlen(var_key) + 1);
+
+       /* Calculate the record size (size of the whole thing) */
+       if (record_size) {
+               *record_size = *payload_size + strlen(var_key) + 1;
+       }
+
+       /* Get position of the type field */
+       var_type = (char *)payload_size + sizeof(uint32_t);
+       if (type) {
+               *type = var_type;
+       }
+
+       /* Calculate the data size */
+       if (data_size) {
+               *data_size = *payload_size - sizeof(uint32_t) -
+                            strlen(var_type) - 1;
+       }
+       /* Get the pointer to the data field */
+       data = (uint8_t *)(var_type + strlen(var_type) + 1);
+       if (val) {
+               *val = data;
+       }
+}
+
+void bgenv_serialize_uservar(uint8_t *p, char *key, char *type, void *data,
+                           uint32_t record_size)
+{
+       uint32_t payload_size, data_size;
+
+       /* store key */
+       strncpy(p, key, strlen(key) + 1);
+       p += strlen(key) + 1;
+
+       /* store payload_size after key */
+       payload_size = record_size - strlen(key) - 1;
+       memcpy(p, &payload_size, sizeof(uint32_t));
+       p += sizeof(uint32_t);
+
+       /* store datatype */
+       memcpy(p, type, strlen(type) + 1);
+       p += strlen(type) + 1;
+
+       /* store data */
+       data_size = payload_size - strlen(type) - 1 - sizeof(uint32_t);
+       memcpy(p, data, data_size);
+}
+
+int bgenv_get_uservar(uint8_t *udata, char *key, char *type, void *data,
+                     uint32_t maxlen)
+{
+       uint8_t *uservar, *value;
+       char *lkey, *ltype;
+       uint32_t dsize;
+
+       uservar = bgenv_find_uservar(udata, key);
+
+       if (!uservar) {
+               return EINVAL;
+       }
+
+       bgenv_map_uservar(uservar, &lkey, &ltype, &value, NULL, &dsize);
+
+       if (dsize > maxlen) {
+               dsize = maxlen;
+       }
+
+       memcpy(data, value, dsize);
+
+       if (type) {
+               memcpy(type, ltype, strlen(ltype) + 1);
+       }
+
+       return 0;
+}
+
+int bgenv_set_uservar(uint8_t *udata, char *key, char *type, void *data,
+                     uint32_t datalen)
+{
+       uint32_t total_size;
+       uint8_t *p;
+
+       total_size = datalen + strlen(type) + 1 + sizeof(uint32_t) +
+                    strlen(key) + 1;
+
+       p = bgenv_find_uservar(udata, key);
+       if (p) {
+               if (strncmp(type, USERVAR_TYPE_DELETED,
+                           strlen(USERVAR_TYPE_DELETED) + 1) == 0) {
+                       bgenv_del_uservar(udata, p);
+                       return 0;
+               }
+
+               p = bgenv_uservar_realloc(udata, total_size, p);
+       } else {
+               p = bgenv_uservar_alloc(udata, total_size);
+       }
+       if (!p) {
+               return errno;
+       }
+
+       bgenv_serialize_uservar(p, key, type, data, total_size);
+
+       return 0;
+}
+
+uint8_t *bgenv_find_uservar(uint8_t *udata, char *key)
+{
+       char *varkey;
+
+       if (!udata) {
+               return NULL;
+       }
+       while (*udata) {
+               bgenv_map_uservar(udata, &varkey, NULL, NULL, NULL, NULL);
+
+               if (strncmp(varkey, key, strlen(key) + 1) == 0) {
+                       return udata;
+               }
+               udata = bgenv_next_uservar(udata);
+       }
+       return NULL;
+}
+
+uint8_t *bgenv_next_uservar(uint8_t *udata)
+{
+       uint32_t record_size;
+
+       bgenv_map_uservar(udata, NULL, NULL, NULL, &record_size, NULL);
+
+       return udata + record_size;
+}
+
+uint8_t *bgenv_uservar_alloc(uint8_t *udata, uint32_t datalen)
+{
+       uint32_t spaceleft;
+
+       if (!udata) {
+               errno = EINVAL;
+               return NULL;
+       }
+       spaceleft = bgenv_user_free(udata);
+       VERBOSE(stdout, "uservar_alloc: free: %u requested: %u \n", spaceleft,
+               datalen);
+
+       /* To find the end of user variables, a 2nd 0 must be there after the
+        * last variable content, thus, we need one extra byte if appending a
+        * new variable. */
+       if (spaceleft < datalen + 1) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       return udata + (ENV_MEM_USERVARS - spaceleft);
+}
+
+uint8_t *bgenv_uservar_realloc(uint8_t *udata, uint32_t new_rsize,
+                              uint8_t *p)
+{
+       uint32_t spaceleft;
+       uint32_t rsize;
+
+       bgenv_map_uservar(p, NULL, NULL, NULL, &rsize, NULL);
+
+       /* Is the new record size equal to the old, so that we can
+        * keep the variable in place? */
+       if (new_rsize == rsize) {
+               return p;
+       }
+
+       /* Delete variable and return pointer to end of whole user vars */
+       bgenv_del_uservar(udata, p);
+
+       spaceleft = bgenv_user_free(udata);
+
+       if (spaceleft < new_rsize - 1) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       return udata + ENV_MEM_USERVARS - spaceleft;
+}
+
+void bgenv_del_uservar(uint8_t *udata, uint8_t *var)
+{
+       uint32_t spaceleft;
+       uint32_t rsize;
+
+       /* Get the record size of the variable */
+       bgenv_map_uservar(var, NULL, NULL, NULL, &rsize, NULL);
+
+       /* Move variable out of place and close gap. */
+       spaceleft = bgenv_user_free(udata);
+
+       memmove(var,
+               var + rsize,
+               ENV_MEM_USERVARS - spaceleft - (var - udata) - rsize);
+
+       spaceleft = spaceleft + rsize;
+
+       memset(udata + ENV_MEM_USERVARS - spaceleft, 0, spaceleft);
+}
+
+uint32_t bgenv_user_free(uint8_t *udata)
+{
+       uint32_t rsize;
+       uint32_t spaceleft;
+
+       spaceleft = ENV_MEM_USERVARS;
+
+       if (!udata) {
+               return 0;
+       }
+       if (!*udata) {
+               return spaceleft;
+       }
+
+       while (*udata) {
+               bgenv_map_uservar(udata, NULL, NULL, NULL, &rsize, NULL);
+               spaceleft -= rsize;
+               if (spaceleft <= 0)
+                       break;
+               udata = bgenv_next_uservar(udata);
+       }
+
+       return spaceleft;
+}
diff --git a/include/ebgenv.h b/include/ebgenv.h
index fb3e809..4343cef 100644
--- a/include/ebgenv.h
+++ b/include/ebgenv.h
@@ -46,8 +46,11 @@ int ebg_env_open_current(ebgenv_t *e);
 /** @brief Retrieve variable content
  *  @param e A pointer to an ebgenv_t context.
  *  @param key an enum constant to specify the variable
- *  @param buffer pointer to buffer containing requested value
- *  @return 0 on success, errno on failure
+ *  @param buffer pointer to buffer containing requested value.
+ *         If buffer is NULL, return needed buffer size.
+ *  @return If buffer != NULL: 0 on success, errno on failure
+ *          If buffer == NULL: needed buffer size, 0 if variable
+ *                             is not found.
  */
 int ebg_env_get(ebgenv_t *e, char *key, char* buffer);
 
@@ -59,6 +62,35 @@ int ebg_env_get(ebgenv_t *e, char *key, char* buffer);
  */
 int ebg_env_set(ebgenv_t *e, char *key, char *value);
 
+/** @brief Store new content into variable
+ *  @param e A pointer to an ebgenv_t context.
+ *  @param key name of the environment variable to set
+ *  @param datatype user specific string to identify the datatype of the value
+ *  @param value arbitrary data to be stored into the variable
+ *  @param datalen length of the data to be stored into the variable
+ *  @return 0 on success, errno on failure
+ */
+int ebg_env_set_ex(ebgenv_t *e, char *key, char *datatype, uint8_t *value,
+                  uint32_t datalen);
+
+/** @brief Get content of user variable
+ *  @param e A pointer to an ebgenv_t context.
+ *  @param key name of the environment variable to retrieve
+ *  @param datatype buffer for user specific string to identify the
+ *         datatype of the value
+ *  @param buffer destination for data to be stored into the variable
+ *  @param maxlen size of provided buffer
+ *  @return 0 on success, errno on failure
+ */
+int ebg_env_get_ex(ebgenv_t *e, char *key, char *datatype, uint8_t *buffer,
+                  uint32_t maxlen);
+
+/** @brief Get available space for user variables
+ *  @param e A pointer to an ebgenv_t context.
+ *  @return Free space in bytes
+ */
+uint32_t ebg_env_user_free(ebgenv_t *e);
+
 /** @brief Get global ustate value, accounting for all environments
  *  @param e A pointer to an ebgenv_t context.
  *  @return ustate value
diff --git a/include/env_api.h b/include/env_api.h
index 8fe5454..5f535c5 100644
--- a/include/env_api.h
+++ b/include/env_api.h
@@ -38,10 +38,15 @@
        }
 #endif
 
+extern bool bgenv_verbosity;
+
 #define VERBOSE(o, ...)                                                       \
-       if (verbosity)                                                        \
+       if (bgenv_verbosity)                                                    
\
        fprintf(o, __VA_ARGS__)
 
+#define USERVAR_TYPE_DEFAULT "String"
+#define USERVAR_TYPE_DELETED "\0xDE\0xAD"
+
 typedef enum {
        EBGENV_KERNELFILE,
        EBGENV_KERNELPARAMS,
@@ -76,9 +81,9 @@ extern BG_ENVDATA *bgenv_read(BGENV *env);
 extern bool bgenv_close(BGENV *env);
 
 extern BGENV *bgenv_create_new(void);
-extern int bgenv_get(BGENV *env, char *key, char **type, void *data,
-                    size_t maxlen);
+extern int bgenv_get(BGENV *env, char *key, char *type, void *data,
+                    uint32_t maxlen);
 extern int bgenv_set(BGENV *env, char *key, char *type, void *data,
-                    size_t datalen);
+                    uint32_t datalen);
 
 #endif // __ENV_API_H__
diff --git a/include/envdata.h b/include/envdata.h
index 3f1fb38..011053a 100644
--- a/include/envdata.h
+++ b/include/envdata.h
@@ -38,6 +38,7 @@ struct _BG_ENVDATA {
        uint8_t ustate;
        uint16_t watchdog_timeout_sec;
        uint32_t revision;
+       uint8_t userdata[ENV_MEM_USERVARS];
        uint32_t crc32;
 };
 #pragma pack(pop)
diff --git a/include/uservars.h b/include/uservars.h
new file mode 100644
index 0000000..d33bcce
--- /dev/null
+++ b/include/uservars.h
@@ -0,0 +1,37 @@
+/*
+ * EFI Boot Guard
+ *
+ * Copyright (c) Siemens AG, 2017
+ *
+ * Authors:
+ *  Andreas Reichel <andreas.reichel....@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef __USER_VARS_H__
+#define __USER_VARS_H__
+
+#include <stdint.h>
+
+void bgenv_map_uservar(uint8_t *udata, char **key, char **type, uint8_t **val,
+                      uint32_t *record_size, uint32_t *data_size);
+void bgenv_serialize_uservar(uint8_t *p, char *key, char *type, void *data,
+                           uint32_t record_size);
+
+int bgenv_get_uservar(uint8_t *udata, char *key, char *type, void *data,
+                     uint32_t maxlen);
+int bgenv_set_uservar(uint8_t *udata, char *key, char *type, void *data,
+                     uint32_t datalen);
+
+uint8_t *bgenv_find_uservar(uint8_t *udata, char *key);
+uint8_t *bgenv_next_uservar(uint8_t *udata);
+
+uint8_t *bgenv_uservar_alloc(uint8_t *udata, uint32_t datalen);
+uint8_t *bgenv_uservar_realloc(uint8_t *udata, uint32_t new_rsize,
+                              uint8_t *p);
+void bgenv_del_uservar(uint8_t *udata, uint8_t *var);
+uint32_t bgenv_user_free(uint8_t *udata);
+
+#endif // __USER_VARS_H__
diff --git a/tools/bg_setenv.c b/tools/bg_setenv.c
index 84b580a..6ad1db5 100644
--- a/tools/bg_setenv.c
+++ b/tools/bg_setenv.c
@@ -11,6 +11,7 @@
  */
 
 #include "env_api.h"
+#include "uservars.h"
 
 static char doc[] =
     "bg_setenv/bg_printenv - Environment tool for the EFI Boot Guard";
@@ -31,6 +32,9 @@ static struct argp_option options_setenv[] = {
     {"confirm", 'c', 0, 0, "Confirm working environment"},
     {"update", 'u', 0, 0, "Automatically update oldest revision"},
     {"verbose", 'v', 0, 0, "Be verbose"},
+    {"uservar", 'x', "KEY=VAL", 0, "Set user-defined string variable. For "
+                                  "setting multiple variables, use this "
+                                  "option multiple times."},
     {0}};
 
 static struct argp_option options_printenv[] = {
@@ -85,6 +89,27 @@ static char *ustate2str(uint8_t ustate)
        }
 }
 
+static int set_uservars(uint8_t *uservars, char *arg)
+{
+       char *key, *value, *end_key;
+
+       key = strtok_r(arg, "=", &end_key);
+       if (key == NULL) {
+               return 0;
+       }
+
+       value = strtok_r(NULL, "=", &end_key);
+       if (value == NULL) {
+               bgenv_set_uservar(uservars, key, USERVAR_TYPE_DELETED, NULL, 0);
+               return 0;
+       }
+
+       bgenv_set_uservar(uservars, key, USERVAR_TYPE_DEFAULT, value,
+                         strlen(value) + 1);
+
+       return 0;
+}
+
 static error_t parse_opt(int key, char *arg, struct argp_state *state)
 {
        struct arguments *arguments = state->input;
@@ -200,6 +225,10 @@ static error_t parse_opt(int key, char *arg, struct 
argp_state *state)
                /* Set verbosity in the library */
                bgenv_be_verbose(true);
                break;
+       case 'x':
+               /* Set user-defined variable(s) */
+               set_uservars(arguments->tmpdata.userdata, arg);
+               break;
        case ARGP_KEY_ARG:
                /* too many arguments - program terminates with call to
                 * argp_usage with non-zero return code */
@@ -211,6 +240,33 @@ static error_t parse_opt(int key, char *arg, struct 
argp_state *state)
        return 0;
 }
 
+static void merge_uservars(uint8_t *dest, uint8_t *src)
+{
+       char *key, *val, *type;
+       uint32_t rsize, dsize;
+       uint8_t *var;
+
+       if (!src || !dest) {
+               return;
+       }
+
+       while (*src) {
+               bgenv_map_uservar(src, &key, &type, (uint8_t **)&val, &rsize,
+                                 &dsize);
+               if (strncmp(type, USERVAR_TYPE_DELETED,
+                           strlen(USERVAR_TYPE_DELETED) - 1) == 0) {
+                       var = bgenv_find_uservar(dest, key);
+                       if (var) {
+                               bgenv_del_uservar(dest, var);
+                       }
+               } else {
+                       bgenv_set_uservar(dest, key, type, val,
+                                         strlen(val) + 1);
+               }
+               src = bgenv_next_uservar(src);
+       }
+}
+
 static void update_environment(BG_ENVDATA *dest, BG_ENVDATA *src)
 {
        if (!dest || !src) {
@@ -237,10 +293,30 @@ static void update_environment(BG_ENVDATA *dest, 
BG_ENVDATA *src)
                       (void *)&src->watchdog_timeout_sec,
                       sizeof(src->watchdog_timeout_sec));
        }
+       merge_uservars(dest->userdata, src->userdata);
+
        dest->crc32 =
            crc32(0, (Bytef *)dest, sizeof(BG_ENVDATA) - sizeof(dest->crc32));
 }
 
+static void dump_uservars(uint8_t *udata)
+{
+       char *key, *value, *type;
+       uint32_t rsize, dsize;
+
+       while (*udata) {
+               bgenv_map_uservar(udata, &key, &type, (uint8_t **)&value,
+                                 &rsize, &dsize);
+               printf("%s ", key);
+               if (strcmp(type, USERVAR_TYPE_DEFAULT) == 0) {
+                       printf("= %s\n", value);
+               } else {
+                       printf("( User defined type )\n");
+               }
+               udata = bgenv_next_uservar(udata);
+       }
+}
+
 static void dump_env(BG_ENVDATA *env)
 {
        char buffer[ENV_STRING_LENGTH];
@@ -251,6 +327,9 @@ static void dump_env(BG_ENVDATA *env)
        printf("watchdog timeout: %u seconds\n", env->watchdog_timeout_sec);
        printf("ustate: %u (%s)\n", (uint8_t)env->ustate,
               ustate2str(env->ustate));
+       printf("\n");
+       printf("user variables:\n");
+       dump_uservars(env->userdata);
        printf("\n\n");
 }
 
@@ -274,7 +353,8 @@ int main(int argc, char **argv)
        arguments.which_part = 0;
        memset((void *)&arguments.tmpdata, IGNORE_MARKER_BYTE,
               sizeof(BG_ENVDATA));
-
+       memset((void *)&arguments.tmpdata.userdata, 0,
+              sizeof(arguments.tmpdata.userdata));
        error_t e;
        if (e = argp_parse(argp, argc, argv, 0, 0, &arguments)) {
                return e;
diff --git a/tools/tests/test_api.c b/tools/tests/test_api.c
index 605a23b..37247ff 100644
--- a/tools/tests/test_api.c
+++ b/tools/tests/test_api.c
@@ -103,9 +103,6 @@ static void test_api_accesscurrent(void **state)
        ret = ebg_env_close(&e);
        assert_int_equal(ret, 0);
 
-       ret = ebg_env_set(&e, "NonsenseKey", "AnyValue");
-       assert_int_equal(ret, EINVAL);
-
        ret = ebg_env_set(&e, "kernelfile", "vmlinuz");
        assert_int_equal(ret, EPERM);
 
@@ -185,12 +182,131 @@ static void test_api_update(void **state)
        (void)state;
 }
 
+static void test_api_uservars(void **state)
+{
+       int ret;
+       char *test_key = "NonsenseKey";
+       char *test_key2 = "TestKey2";
+
+       char *test_val = "AnyValue";
+       char *test_val2 = "BnyVbluf";
+       char *test_val3 = "TESTTESTTESTTEST";
+       char *test_val4 = "abc";
+       char buffer[ENV_MEM_USERVARS];
+       uint32_t space_left;
+
+       will_return(bgenv_open_latest, &env);
+       ret = ebg_env_open_current(&e);
+       assert_int_equal(ret, 0);
+
+       assert_int_equal(ebg_env_user_free(&e), ENV_MEM_USERVARS);
+
+       ret = ebg_env_set(&e, test_key, test_val);
+       assert_int_equal(ret, 0);
+
+       space_left = ENV_MEM_USERVARS - strlen(test_key) - 1
+                       - strlen(test_val) - 1 - sizeof(uint32_t)
+                       - strlen(USERVAR_TYPE_DEFAULT) - 1;
+
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       ret = ebg_env_get(&e, test_key, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val);
+
+       // replace value with same length value
+       ret = ebg_env_set(&e, test_key, test_val2);
+       assert_int_equal(ret, 0);
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       ret = ebg_env_get(&e, test_key, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val2);
+
+       // replace value with larger value
+       ret = ebg_env_set(&e, test_key, test_val3);
+       assert_int_equal(ret, 0);
+
+       space_left = ENV_MEM_USERVARS - strlen(test_key) - 1
+                       - strlen(test_val3) - 1 - sizeof(uint32_t)
+                       - strlen(USERVAR_TYPE_DEFAULT) - 1;
+
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       // replace value with smaller value
+       ret = ebg_env_set(&e, test_key, test_val4);
+       assert_int_equal(ret, 0);
+
+       space_left = ENV_MEM_USERVARS - strlen(test_key) - 1
+                       - strlen(test_val4) - 1 - sizeof(uint32_t)
+                       - strlen(USERVAR_TYPE_DEFAULT) - 1;
+
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       // add 2nd variable
+       ret = ebg_env_set(&e, test_key2, test_val2);
+       assert_int_equal(ret, 0);
+
+       space_left = space_left - strlen(test_key2) - 1
+                       - strlen(test_val2) - 1 - sizeof(uint32_t)
+                       - strlen(USERVAR_TYPE_DEFAULT) - 1;
+
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       // retrieve both variables
+       ret = ebg_env_get(&e, test_key2, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val2);
+       ret = ebg_env_get(&e, test_key, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val4);
+
+       // overwrite first variable
+       ret = ebg_env_set(&e, test_key, test_val3);
+       assert_int_equal(ret, 0);
+
+       space_left = space_left + strlen(test_val4)
+                               - strlen(test_val3);
+       assert_int_equal(ebg_env_user_free(&e), space_left);
+
+       // retrieve both variables
+       ret = ebg_env_get(&e, test_key2, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val2);
+       ret = ebg_env_get(&e, test_key, buffer);
+       assert_int_equal(ret, 0);
+       assert_string_equal(buffer, test_val3);
+
+       void *dummymem = malloc(space_left);
+
+       // test out of memory
+       ret = ebg_env_set_ex(&e, "A", "B", dummymem, space_left);
+       free(dummymem);
+
+       assert_int_equal(ret, ENOMEM);
+
+       // test user data type
+       ret = ebg_env_set_ex(&e, "A", "B", "C", 2);
+       assert_int_equal(ret, 0);
+
+       char type[2];
+       char data[2];
+
+       ret = ebg_env_get_ex(&e, "A", type, data, sizeof(data));
+       assert_int_equal(ret, 0);
+       assert_string_equal("B", type);
+       assert_string_equal("C", data);
+
+       (void)state;
+}
+
 int main(void)
 {
        const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_api_openclose),
            cmocka_unit_test(test_api_accesscurrent),
-           cmocka_unit_test(test_api_update)};
+           cmocka_unit_test(test_api_update),
+           cmocka_unit_test(test_api_uservars)};
 
        env.data = &data;
        data.revision = test_env_revision;
-- 
2.14.1

-- 
You received this message because you are subscribed to the Google Groups "EFI 
Boot Guard" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to efibootguard-dev+unsubscr...@googlegroups.com.
To post to this group, send email to efibootguard-dev@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/efibootguard-dev/20170912105511.11060-12-andreas.reichel.ext%40siemens.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to