On 2017-09-11 13:35, [ext] Andreas J. Reichel wrote:
> From: Reichel Andreas <[email protected]>
> 
> 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 <[email protected]>
> ---
>  Makefile.am            |   1 +
>  configure.ac           |  17 ++++
>  docs/TODO.md           |  16 +--
>  docs/TOOLS.md          |  10 ++
>  env/env_api.c          |  29 +++++-
>  env/env_api_fat.c      |  74 ++++++++++----
>  env/uservars.c         | 266 
> +++++++++++++++++++++++++++++++++++++++++++++++++
>  include/ebgenv.h       |  36 ++++++-
>  include/env_api.h      |   9 +-
>  include/envdata.h      |   1 +
>  include/uservars.h     |  37 +++++++
>  tools/bg_setenv.c      |  82 ++++++++++++++-
>  tools/tests/test_api.c | 124 ++++++++++++++++++++++-
>  13 files changed, 665 insertions(+), 37 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/@[email protected] \
>       env/env_api.c \
> +     env/uservars.c \
>       tools/ebgpart.c
>  
>  libebgenv_a_CPPFLAGS = \
> diff --git a/configure.ac b/configure.ac
> index 219c419..514bec7 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -155,6 +155,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
> @@ -177,5 +193,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..ed07c7f 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,
> +                size_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,
> +                size_t datalen)
> +{
> +     return bgenv_set((BGENV *)e->bgenv, key, usertype, value, datalen);
> +}
> +
> +size_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..ca9beb8 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,52 +440,84 @@ 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, size_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;
> +                     size_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;
> @@ -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..8584d12
> --- /dev/null
> +++ b/env/uservars.c
> @@ -0,0 +1,266 @@
> +/*
> + * EFI Boot Guard
> + *
> + * Copyright (c) Siemens AG, 2017
> + *
> + * Authors:
> + *  Andreas Reichel <[email protected]>
> + *
> + * 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,
> +                    size_t *record_size, size_t *data_size)
> +{
> +     /* Each user variable is encoded as follows:
> +      * |------------|------------|-------------|----------------|
> +      * | char KEY[] | size_t len | char type[] | uint8_t data[] |

Is this the storage format? In that case, it leaves some open questions
regarding the widths of the type:

- size_t depends on the platform. Better use some uintX_t.
- What is char[]? A Null-terminated string of variable length?

> +      * |------------|------------|-------------|----------------|
> +      * |   KEY      |  - - - - - - - PAYLOAD - - - - - - - -    |

What does that line mean?

Jan

> +      *
> +      * stored in 'len' is the payload size
> +      */
> +     char *var_key;
> +     size_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 = (size_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(size_t);
> +     if (type) {
> +             *type = var_type;
> +     }
> +
> +     /* Calculate the data size */
> +     if (data_size) {
> +             *data_size = *payload_size - sizeof(size_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,
> +                         size_t record_size)
> +{
> +     size_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(size_t));
> +     p += sizeof(size_t);
> +
> +     /* store datatype */
> +     memcpy(p, type, strlen(type) + 1);
> +     p += strlen(type) + 1;
> +
> +     /* store data */
> +     data_size = payload_size - strlen(type) - 1 - sizeof(size_t);
> +     memcpy(p, data, data_size);
> +}
> +
> +int bgenv_get_uservar(uint8_t *udata, char *key, char *type, void *data,
> +                   size_t maxlen)
> +{
> +     uint8_t *uservar, *value;
> +     char *lkey, *ltype;
> +     size_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,
> +                   size_t datalen)
> +{
> +     size_t total_size;
> +     uint8_t *p;
> +
> +     total_size = datalen + strlen(type) + 1 + sizeof(size_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)
> +{
> +     size_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, size_t datalen)
> +{
> +     size_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, size_t new_rsize,
> +                            uint8_t *p)
> +{
> +     size_t spaceleft;
> +     size_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)
> +{
> +     size_t spaceleft;
> +     size_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);
> +}
> +
> +size_t bgenv_user_free(uint8_t *udata)
> +{
> +     size_t rsize;
> +     size_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..f89b23d 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,
> +                size_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,
> +                size_t maxlen);
> +
> +/** @brief Get available space for user variables
> + *  @param e A pointer to an ebgenv_t context.
> + *  @return Free space in bytes
> + */
> +size_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..e8ca99f 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,7 +81,7 @@ 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,
> +extern int bgenv_get(BGENV *env, char *key, char *type, void *data,
>                    size_t maxlen);
>  extern int bgenv_set(BGENV *env, char *key, char *type, void *data,
>                    size_t datalen);
> 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..a0abb74
> --- /dev/null
> +++ b/include/uservars.h
> @@ -0,0 +1,37 @@
> +/*
> + * EFI Boot Guard
> + *
> + * Copyright (c) Siemens AG, 2017
> + *
> + * Authors:
> + *  Andreas Reichel <[email protected]>
> + *
> + * 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,
> +                    size_t *record_size, size_t *data_size);
> +void bgenv_serialize_uservar(uint8_t *p, char *key, char *type, void *data,
> +                         size_t record_size);
> +
> +int bgenv_get_uservar(uint8_t *udata, char *key, char *type, void *data,
> +                   size_t maxlen);
> +int bgenv_set_uservar(uint8_t *udata, char *key, char *type, void *data,
> +                   size_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, size_t datalen);
> +uint8_t *bgenv_uservar_realloc(uint8_t *udata, size_t new_rsize,
> +                            uint8_t *p);
> +void bgenv_del_uservar(uint8_t *udata, uint8_t *var);
> +size_t bgenv_user_free(uint8_t *udata);
> +
> +#endif // __USER_VARS_H__
> diff --git a/tools/bg_setenv.c b/tools/bg_setenv.c
> index 3911ca3..0c37452 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;
> +     size_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;
> +     size_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 )");
> +             }
> +             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..09c8d3e 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];
> +     size_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(size_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(size_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(size_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(size_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;
> 

-- 
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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/efibootguard-dev/4d288701-d3d6-074e-11c4-b0816e6f4067%40siemens.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to