From: Joe Hershberger Date: Wed, 12 Dec 2012 04:16:24 +0000 (-0600) Subject: env: Add support for callbacks to environment vars X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=170ab11075d3be56e89d6444abf1148329130f4b;p=u-boot.git env: Add support for callbacks to environment vars Add support for per-variable callbacks to the "hashtable" functions. Signed-off-by: Joe Hershberger !!!fix comment in callback --- diff --git a/README b/README index a80441c9b5..e00a7c2a8a 100644 --- a/README +++ b/README @@ -4213,6 +4213,36 @@ Please note that changes to some configuration parameters may take only effect after the next boot (yes, that's just like Windoze :-). +Callback functions for environment variables: +--------------------------------------------- + +For some environment variables, the behavior of u-boot needs to change +when their values are changed. This functionailty allows functions to +be associated with arbitrary variables. On creation, overwrite, or +deletion, the callback will provide the opportunity for some side +effect to happen or for the change to be rejected. + +The callbacks are named and associated with a function using the +U_BOOT_ENV_CALLBACK macro in your board or driver code. + +These callbacks are associated with variables in one of two ways. The +static list can be added to by defining CONFIG_ENV_CALLBACK_LIST_STATIC +in the board configuration to a string that defines a list of +associations. The list must be in the following format: + + entry = variable_name[:callback_name] + list = entry[,list] + +If the callback name is not specified, then the callback is deleted. +Spaces are also allowed anywhere in the list. + +Callbacks can also be associated by defining the ".callbacks" variable +with the same list format above. Any association in ".callbacks" will +override any association in the static list. You can define +CONFIG_ENV_CALLBACK_LIST_DEFAULT to a list (string) to define the +".callbacks" envirnoment variable in the default or embedded environment. + + Command Line Parsing: ===================== diff --git a/common/Makefile b/common/Makefile index ce3035937e..04812e9b2a 100644 --- a/common/Makefile +++ b/common/Makefile @@ -44,6 +44,8 @@ COBJS-y += cmd_nvedit.o COBJS-y += cmd_version.o # environment +COBJS-y += env_attr.o +COBJS-y += env_callback.o COBJS-y += env_common.o COBJS-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o @@ -207,6 +209,8 @@ COBJS-y += env_common.o COBJS-$(CONFIG_ENV_IS_IN_FLASH) += env_flash.o COBJS-$(CONFIG_SPL_YMODEM_SUPPORT) += xyzModem.o COBJS-$(CONFIG_SPL_NET_SUPPORT) += cmd_nvedit.o +COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_attr.o +COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_callback.o COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_common.o COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_nowhere.o COBJS-$(CONFIG_SPL_NET_SUPPORT) += miiphyutil.o diff --git a/common/env_attr.c b/common/env_attr.c new file mode 100644 index 0000000000..7d330a58bb --- /dev/null +++ b/common/env_attr.c @@ -0,0 +1,222 @@ +/* + * (C) Copyright 2012 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +/* + * Iterate through the whole list calling the callback for each found element. + * "attr_list" takes the form: + * attributes = [^,:\s]* + * entry = name[:attributes] + * list = entry[,list] + */ +int env_attr_walk(const char *attr_list, + int (*callback)(const char *name, const char *attributes)) +{ + const char *entry, *entry_end; + char *name, *attributes; + + if (!attr_list) + /* list not found */ + return 1; + + entry = attr_list; + do { + char *entry_cpy = NULL; + + entry_end = strchr(entry, ENV_ATTR_LIST_DELIM); + /* check if this is the last entry in the list */ + if (entry_end == NULL) { + int entry_len = strlen(entry); + + if (entry_len) { + /* + * allocate memory to copy the entry into since + * we will need to inject '\0' chars and squash + * white-space before calling the callback + */ + entry_cpy = malloc(entry_len + 1); + if (entry_cpy) + /* copy the rest of the list */ + strcpy(entry_cpy, entry); + else + return -ENOMEM; + } + } else { + int entry_len = entry_end - entry; + + if (entry_len) { + /* + * allocate memory to copy the entry into since + * we will need to inject '\0' chars and squash + * white-space before calling the callback + */ + entry_cpy = malloc(entry_len + 1); + if (entry_cpy) { + /* copy just this entry and null term */ + strncpy(entry_cpy, entry, entry_len); + entry_cpy[entry_len] = '\0'; + } else + return -ENOMEM; + } + } + + /* check if there is anything to process (e.g. not ",,,") */ + if (entry_cpy != NULL) { + attributes = strchr(entry_cpy, ENV_ATTR_SEP); + /* check if there is a ':' */ + if (attributes != NULL) { + /* replace the ':' with '\0' to term name */ + *attributes++ = '\0'; + /* remove white-space from attributes */ + attributes = strim(attributes); + } + /* remove white-space from name */ + name = strim(entry_cpy); + + /* only call the callback if there is a name */ + if (strlen(name) != 0) { + int retval = 0; + + retval = callback(name, attributes); + if (retval) { + free(entry_cpy); + return retval; + } + } + } + + free(entry_cpy); + entry = entry_end + 1; + } while (entry_end != NULL); + + return 0; +} + +/* + * Search for the last matching string in another string with the option to + * start looking at a certain point (i.e. ignore anything beyond that point). + */ +static char *reverse_strstr(const char *searched, const char *search_for, + const char *searched_start) +{ + char *result = NULL; + + if (*search_for == '\0') + return (char *)searched; + + for (;;) { + char *match = strstr(searched, search_for); + + /* + * Stop looking if no new match is found or looking past the + * searched_start pointer + */ + if (match == NULL || (searched_start != NULL && + match + strlen(search_for) > searched_start)) + break; + + result = match; + searched = match + 1; + } + + return result; +} + +/* + * Retrieve the attributes string associated with a single name in the list + * There is no protection on attributes being too small for the value + */ +int env_attr_lookup(const char *attr_list, const char *name, char *attributes) +{ + const char *entry = NULL; + + if (!attributes) + /* bad parameter */ + return -1; + if (!attr_list) + /* list not found */ + return 1; + + entry = reverse_strstr(attr_list, name, NULL); + while (entry != NULL) { + const char *prevch = entry - 1; + const char *nextch = entry + strlen(name); + + /* Skip spaces */ + while (*prevch == ' ') + prevch--; + while (*nextch == ' ') + nextch++; + + /* check for an exact match */ + if ((entry == attr_list || + *prevch == ENV_ATTR_LIST_DELIM) && + (*nextch == ENV_ATTR_SEP || + *nextch == ENV_ATTR_LIST_DELIM || + *nextch == '\0')) + break; + + entry = reverse_strstr(attr_list, name, entry); + } + if (entry != NULL) { + int len; + + /* skip the name */ + entry += strlen(name); + /* skip spaces */ + while (*entry == ' ') + entry++; + if (*entry != ENV_ATTR_SEP) + len = 0; + else { + const char *delim; + static const char delims[] = { + ENV_ATTR_LIST_DELIM, ' ', '\0'}; + + /* skip the attr sep */ + entry += 1; + /* skip spaces */ + while (*entry == ' ') + entry++; + + delim = strpbrk(entry, delims); + if (delim == NULL) + len = strlen(entry); + else + len = delim - entry; + memcpy(attributes, entry, len); + } + attributes[len] = '\0'; + + /* success */ + return 0; + } + + /* not found in list */ + return 2; +} diff --git a/common/env_callback.c b/common/env_callback.c new file mode 100644 index 0000000000..78ca3674f0 --- /dev/null +++ b/common/env_callback.c @@ -0,0 +1,144 @@ +/* + * (C) Copyright 2012 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include + +#if defined(CONFIG_NEEDS_MANUAL_RELOC) +DECLARE_GLOBAL_DATA_PTR; +#endif + +/* + * Look up a callback function pointer by name + */ +struct env_clbk_tbl *find_env_callback(const char *name) +{ + struct env_clbk_tbl *clbkp; + int i; + int num_callbacks = ll_entry_count(struct env_clbk_tbl, env_clbk); + + if (name == NULL) + return NULL; + + /* look up the callback in the linker-list */ + for (i = 0, clbkp = ll_entry_start(struct env_clbk_tbl, env_clbk); + i < num_callbacks; + i++, clbkp++) { + if (strcmp(name, clbkp->name) == 0) + return clbkp; + } + + return NULL; +} + +/* + * Look for a possible callback for a newly added variable + * This is called specifically when the variable did not exist in the hash + * previously, so the blanket update did not find this variable. + */ +void env_callback_init(ENTRY *var_entry) +{ + const char *var_name = var_entry->key; + const char *callback_list = getenv(ENV_CALLBACK_VAR); + char callback_name[256] = ""; + struct env_clbk_tbl *clbkp; + int ret = 1; + + /* look in the ".callbacks" var for a reference to this variable */ + if (callback_list != NULL) + ret = env_attr_lookup(callback_list, var_name, callback_name); + + /* only if not found there, look in the static list */ + if (ret) + ret = env_attr_lookup(ENV_CALLBACK_LIST_STATIC, var_name, + callback_name); + + /* if an association was found, set the callback pointer */ + if (!ret && strlen(callback_name)) { + clbkp = find_env_callback(callback_name); + if (clbkp != NULL) +#if defined(CONFIG_NEEDS_MANUAL_RELOC) + var_entry->callback = clbkp->callback + gd->reloc_off; +#else + var_entry->callback = clbkp->callback; +#endif + } +} + +/* + * Called on each existing env var prior to the blanket update since removing + * a callback association should remove its callback. + */ +static int clear_callback(ENTRY *entry) +{ + entry->callback = NULL; + + return 0; +} + +/* + * Call for each element in the list that associates variables to callbacks + */ +static int set_callback(const char *name, const char *value) +{ + ENTRY e, *ep; + struct env_clbk_tbl *clbkp; + + e.key = name; + e.data = NULL; + hsearch_r(e, FIND, &ep, &env_htab, 0); + + /* does the env variable actually exist? */ + if (ep != NULL) { + /* the assocaition delares no callback, so remove the pointer */ + if (value == NULL || strlen(value) == 0) + ep->callback = NULL; + else { + /* assign the requested callback */ + clbkp = find_env_callback(value); + if (clbkp != NULL) +#if defined(CONFIG_NEEDS_MANUAL_RELOC) + ep->callback = clbkp->callback + gd->reloc_off; +#else + ep->callback = clbkp->callback; +#endif + } + } + + return 0; +} + +static int on_callbacks(const char *name, const char *value, enum env_op op, + int flags) +{ + /* remove all callbacks */ + hwalk_r(&env_htab, clear_callback); + + /* configure any static callback bindings */ + env_attr_walk(ENV_CALLBACK_LIST_STATIC, set_callback); + /* configure any dynamic callback bindings */ + env_attr_walk(value, set_callback); + + return 0; +} +U_BOOT_ENV_CALLBACK(callbacks, on_callbacks); diff --git a/include/env_attr.h b/include/env_attr.h new file mode 100644 index 0000000000..6ef114f5da --- /dev/null +++ b/include/env_attr.h @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2012 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __ENV_ATTR_H__ +#define __ENV_ATTR_H__ + +#define ENV_ATTR_LIST_DELIM ',' +#define ENV_ATTR_SEP ':' + +/* + * env_attr_walk takes as input an "attr_list" that takes the form: + * attributes = [^,:\s]* + * entry = name[:attributes] + * list = entry[,list] + * It will call the "callback" function with the "name" and attribute as "value" + * The callback may return a non-0 to abort the list walk. + * This return value will be passed through to the caller. + * 0 is returned on success. + */ +extern int env_attr_walk(const char *attr_list, + int (*callback)(const char *name, const char *value)); + +/* + * env_attr_lookup takes as input an "attr_list" with the same form as above. + * It also takes as input a "name" to look for. + * If the name is found in the list, it's value is copied into "attributes". + * There is no protection on attributes being too small for the value. + * It returns -1 if attributes is NULL, 1 if "name" is not found, 2 if + * "attr_list" is NULL. + * Returns 0 on success. + */ +extern int env_attr_lookup(const char *attr_list, const char *name, + char *attributes); + +#endif /* __ENV_ATTR_H__ */ diff --git a/include/env_callback.h b/include/env_callback.h new file mode 100644 index 0000000000..5a460f3532 --- /dev/null +++ b/include/env_callback.h @@ -0,0 +1,62 @@ +/* + * (C) Copyright 2012 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __ENV_CALLBACK_H__ +#define __ENV_CALLBACK_H__ + +#include +#include + +#define ENV_CALLBACK_VAR ".callbacks" + +/* Board configs can define additional static callback bindings */ +#ifndef CONFIG_ENV_CALLBACK_LIST_STATIC +#define CONFIG_ENV_CALLBACK_LIST_STATIC +#endif + +/* + * This list of callback bindings is static, but may be overridden by defining + * a new association in the ".callbacks" environment variable. + */ +#define ENV_CALLBACK_LIST_STATIC ENV_CALLBACK_VAR ":callbacks," \ + CONFIG_ENV_CALLBACK_LIST_STATIC + +struct env_clbk_tbl { + const char *name; /* Callback name */ + int (*callback)(const char *name, const char *value, enum env_op op, + int flags); +}; + +struct env_clbk_tbl *find_env_callback(const char *); +void env_callback_init(ENTRY *var_entry); + +/* + * Define a callback that can be associated with variables. + * when associated through the ".callbacks" environment variable, the callback + * will be executed any time the variable is inserted, overwritten, or deleted. + */ +#define U_BOOT_ENV_CALLBACK(name, callback) \ + ll_entry_declare(struct env_clbk_tbl, name, env_clbk, env_clbk) = \ + {#name, callback} + +#endif /* __ENV_CALLBACK_H__ */ diff --git a/include/env_default.h b/include/env_default.h index a1db73a2c5..d05eba1618 100644 --- a/include/env_default.h +++ b/include/env_default.h @@ -24,6 +24,8 @@ * MA 02111-1307 USA */ +#include + #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED env_t environment __PPCENV__ = { ENV_CRC, /* CRC Sum */ @@ -36,6 +38,9 @@ static char default_environment[] = { #else const uchar default_environment[] = { #endif +#ifdef CONFIG_ENV_CALLBACK_LIST_DEFAULT + ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0" +#endif #ifdef CONFIG_BOOTARGS "bootargs=" CONFIG_BOOTARGS "\0" #endif diff --git a/include/environment.h b/include/environment.h index 4b19f32be4..6c30215522 100644 --- a/include/environment.h +++ b/include/environment.h @@ -164,6 +164,8 @@ extern void env_reloc(void); #ifndef DO_DEPS_ONLY +#include +#include #include extern struct hsearch_data env_htab; diff --git a/include/search.h b/include/search.h index 1e48deb1cf..d68e24a030 100644 --- a/include/search.h +++ b/include/search.h @@ -47,6 +47,8 @@ typedef enum { typedef struct entry { const char *key; char *data; + int (*callback)(const char *name, const char *value, enum env_op op, + int flags); } ENTRY; /* Opaque type for internal use. */ @@ -120,6 +122,9 @@ extern int himport_r(struct hsearch_data *__htab, const char *__env, size_t __size, const char __sep, int __flag, int nvars, char * const vars[]); +/* Walk the whole table calling the callback on each element */ +extern int hwalk_r(struct hsearch_data *__htab, int (*callback)(ENTRY *)); + /* Flags for himport_r(), hexport_r(), hdelete_r(), and hsearch_r() */ #define H_NOCLEAR (1 << 0) /* do not clear hash table before importing */ #define H_FORCE (1 << 1) /* overwrite read-only/write-once variables */ diff --git a/lib/hashtable.c b/lib/hashtable.c index 7c6b96cac8..e9226665f3 100644 --- a/lib/hashtable.c +++ b/lib/hashtable.c @@ -54,7 +54,8 @@ #define CONFIG_ENV_MAX_ENTRIES 512 #endif -#include "search.h" +#include +#include /* * [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986 @@ -274,6 +275,17 @@ static inline int _compare_and_overwrite_entry(ENTRY item, ACTION action, return 0; } + /* If there is a callback, call it */ + if (htab->table[idx].entry.callback && + htab->table[idx].entry.callback(item.key, + item.data, env_op_overwrite, flag)) { + debug("callback() rejected setting variable " + "%s, skipping it!\n", item.key); + __set_errno(EINVAL); + *retval = NULL; + return 0; + } + free(htab->table[idx].entry.data); htab->table[idx].entry.data = strdup(item.data); if (!htab->table[idx].entry.data) { @@ -398,6 +410,9 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval, ++htab->filled; + /* This is a new entry, so look up a possible callback */ + env_callback_init(&htab->table[idx].entry); + /* check for permission */ if (htab->change_ok != NULL && htab->change_ok( &htab->table[idx].entry, item.data, env_op_create, flag)) { @@ -409,6 +424,18 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval, return 0; } + /* If there is a callback, call it */ + if (htab->table[idx].entry.callback && + htab->table[idx].entry.callback(item.key, item.data, + env_op_create, flag)) { + debug("callback() rejected setting variable " + "%s, skipping it!\n", item.key); + _hdelete(item.key, htab, &htab->table[idx].entry, idx); + __set_errno(EINVAL); + *retval = NULL; + return 0; + } + /* return new entry */ *retval = &htab->table[idx].entry; return 1; @@ -437,6 +464,7 @@ static void _hdelete(const char *key, struct hsearch_data *htab, ENTRY *ep, debug("hdelete: DELETING key \"%s\"\n", key); free((void *)ep->key); free(ep->data); + ep->callback = NULL; htab->table[idx].used = -1; --htab->filled; @@ -466,6 +494,15 @@ int hdelete_r(const char *key, struct hsearch_data *htab, int flag) return 0; } + /* If there is a callback, call it */ + if (htab->table[idx].entry.callback && + htab->table[idx].entry.callback(key, NULL, env_op_delete, flag)) { + debug("callback() rejected deleting variable " + "%s, skipping it!\n", key); + __set_errno(EINVAL); + return 0; + } + _hdelete(key, htab, ep, idx); return 1; @@ -838,11 +875,9 @@ int himport_r(struct hsearch_data *htab, e.data = value; hsearch_r(e, ENTER, &rv, htab, flag); - if (rv == NULL) { + if (rv == NULL) printf("himport_r: can't insert \"%s=%s\" into hash table\n", name, value); - return 0; - } debug("INSERT: table %p, filled %d/%d rv %p ==> name=\"%s\" value=\"%s\"\n", htab, htab->filled, htab->size, @@ -873,3 +908,27 @@ int himport_r(struct hsearch_data *htab, debug("INSERT: done\n"); return 1; /* everything OK */ } + +/* + * hwalk_r() + */ + +/* + * Walk all of the entries in the hash, calling the callback for each one. + * this allows some generic operation to be performed on each element. + */ +int hwalk_r(struct hsearch_data *htab, int (*callback)(ENTRY *)) +{ + int i; + int retval; + + for (i = 1; i <= htab->size; ++i) { + if (htab->table[i].used > 0) { + retval = callback(&htab->table[i].entry); + if (retval) + return retval; + } + } + + return 0; +}