diff mbox

[U-Boot,RFC,1/5] env: Add support for callbacks to environment vars

Message ID 1348264998-27323-2-git-send-email-joe.hershberger@ni.com
State RFC
Headers show

Commit Message

Joe Hershberger Sept. 21, 2012, 10:03 p.m. UTC
Add support for callbacks to the "hashtable" functions.

One check-patch warning to inter-op with existing hashtable code:
WARNING: do not add new typedefs
+typedef struct entry ENTRY;

Signed-off-by: Joe Hershberger <joe.hershberger@ni.com>
---
 arch/arm/cpu/u-boot.lds |   7 +++
 common/Makefile         |   2 +
 common/env_attr.c       | 159 ++++++++++++++++++++++++++++++++++++++++++++++++
 common/env_callback.c   | 114 ++++++++++++++++++++++++++++++++++
 include/env_attr.h      |  35 +++++++++++
 include/env_callback.h  |  62 +++++++++++++++++++
 include/environment.h   |   2 +
 include/search.h        |   5 ++
 lib/hashtable.c         |  65 +++++++++++++++++++-
 9 files changed, 450 insertions(+), 1 deletion(-)
 create mode 100644 common/env_attr.c
 create mode 100644 common/env_callback.c
 create mode 100644 include/env_attr.h
 create mode 100644 include/env_callback.h

Comments

Wolfgang Denk Sept. 26, 2012, 7:33 p.m. UTC | #1
Dear Joe Hershberger,

In message <1348264998-27323-2-git-send-email-joe.hershberger@ni.com> you wrote:
> Add support for callbacks to the "hashtable" functions.
...
> +/*
> + * Iterate through the whole list calling the callback for each found element.
> + */
> +int env_attr_walk(const char *attr_list,
> +	int (*callback)(const char *name, const char *value))
> +{
> +	const char *entry, *entry_end;
> +	char *name, *value;
> +
> +	if (!attr_list)
> +		/* list not found */
> +		return 1;
> +
> +	entry = attr_list;
> +	do {
> +		char *entry_cpy = NULL;
> +
> +		entry_end = strchr(entry, ENV_ATTR_LIST_DELIM);
> +		if (entry_end == NULL) {
> +			int entry_len = strlen(entry);
> +
> +			if (entry_len) {
> +				entry_cpy = malloc(entry_len + 1);
--------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +				if (entry_cpy)
> +					strcpy(entry_cpy, entry);
> +				else
> +					return -ENOMEM;
> +			}
> +		} else {
> +			int entry_len = entry_end - entry;
> +
> +			if (entry_len) {
> +				entry_cpy = malloc(entry_len + 1);
--------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +				if (entry_cpy) {
> +					strncpy(entry_cpy, entry, entry_len);
> +					entry_cpy[entry_len] = '\0';
> +				} else
> +					return -ENOMEM;
> +			}
> +		}

I find it a bit strange that a function which walks an existing list
of data structures would malloc() anything?

> +		if (entry_cpy != NULL) {
> +			value = strchr(entry_cpy, ENV_ATTR_SEP);
> +			if (value != NULL) {
> +				*value++ = '\0';
> +				value = strim(value);
> +			}
> +			name = strim(entry_cpy);
> +
> +			if (strlen(name) != 0) {
> +				int retval = 0;
> +
> +				retval = callback(name, value);
> +				if (retval) {
> +					free(entry_cpy);
> +					return retval;
> +				}
> +			}
> +		}
> +
> +		free(entry_cpy);
> +		entry = entry_end + 1;
> +	} while (entry_end != NULL);
> +
> +	return 0;
> +}

Actually, this function is unreadable to me, especially because there
is zero documentation about what exactly it is supposed to do,  which
data formats are being used, etc.

> +/*
> + * 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)

Atttributes in a list?

What exactly has this to do with implementing callbacks?

[Yes, I think I do know what you have in mind, but this is based on a
lot of speculation, and I may be wrong.  And people who didn't follow
the previous discussion and the old patch seris probably have no clue
at all.]

> +	entry = strstr(attr_list, name);
> +	while (entry != NULL) {
> +		if ((entry == attr_list ||
> +		    *(entry - 1) == ENV_ATTR_LIST_DELIM ||
> +		    *(entry - 1) == ' ') &&
> +		    (*(entry + strlen(name)) == ENV_ATTR_SEP ||
> +		     *(entry + strlen(name)) == ENV_ATTR_LIST_DELIM ||
> +		     *(entry + strlen(name)) == '\0' ||
> +		     *(entry + strlen(name)) == ' '))

Factor out strlen() ?

...
> diff --git a/include/env_attr.h b/include/env_attr.h
> new file mode 100644
> index 0000000..62a3667
> --- /dev/null
> +++ b/include/env_attr.h
...
> +#define ENV_ATTR_LIST_DELIM	','
> +#define ENV_ATTR_SEP		':'
> +
> +extern int env_attr_walk(const char *attr_list,
> +	int (*callback)(const char *name, const char *value));
> +extern int env_attr_lookup(const char *attr_list, const char *name,
> +	char *attributes);

This does need documentation...

Best regards,

Wolfgang Denk
Joe Hershberger Sept. 27, 2012, 10:47 p.m. UTC | #2
When a variable with a registered callback is inserted, deleted, or
overwritten the callback is called and gives the system an opportunity
to do something in response to the change.  It also has the opportunuty
to reject the change by returning non-zero.

Before I go much further, I want to get a little feedback if this is a
good implementation.  It certainly cleans up cmd_nvedit.c significantly!

Changes in v2:
- Added much-needed documentation
- Factored out prevch and nextch in env_attr_lookup()

Joe Hershberger (5):
  env: Add support for callbacks to environment vars
  env: Add a loadaddr env handler
  env: Add a bootfile env handler
  env: Add a baudrate env handler
  env: Add a console env handler

 README                  |  28 ++++++++
 arch/arm/cpu/u-boot.lds |   7 ++
 common/Makefile         |   4 +-
 common/cmd_load.c       |  24 ++++++-
 common/cmd_nvedit.c     |  87 ----------------------
 common/console.c        |  46 ++++++++++++
 common/env_attr.c       | 187 ++++++++++++++++++++++++++++++++++++++++++++++++
 common/env_callback.c   | 130 +++++++++++++++++++++++++++++++++
 common/serial.c         |  58 +++++++++++++++
 include/env_attr.h      |  55 ++++++++++++++
 include/env_callback.h  |  74 +++++++++++++++++++
 include/environment.h   |   2 +
 include/search.h        |   5 ++
 lib/hashtable.c         |  65 ++++++++++++++++-
 net/net.c               |  16 +++++
 15 files changed, 697 insertions(+), 91 deletions(-)
 create mode 100644 common/env_attr.c
 create mode 100644 common/env_callback.c
 create mode 100644 include/env_attr.h
 create mode 100644 include/env_callback.h
diff mbox

Patch

diff --git a/arch/arm/cpu/u-boot.lds b/arch/arm/cpu/u-boot.lds
index e49ca0c..5af8c36 100644
--- a/arch/arm/cpu/u-boot.lds
+++ b/arch/arm/cpu/u-boot.lds
@@ -55,6 +55,13 @@  SECTIONS
 
 	. = ALIGN(4);
 
+	. = .;
+	__u_boot_env_clbk_start = .;
+	.u_boot_env_clbk : { *(.u_boot_env_clbk) }
+	__u_boot_env_clbk_end = .;
+
+	. = ALIGN(4);
+
 	__image_copy_end = .;
 
 	.rel.dyn : {
diff --git a/common/Makefile b/common/Makefile
index 3d62775..f61c9a1 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -43,7 +43,9 @@  COBJS-y += cmd_nvedit.o
 COBJS-y += cmd_version.o
 
 # environment
+COBJS-y += env_attr.o
 COBJS-y += env_common.o
+COBJS-y += env_callback.o
 COBJS-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o
 COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o
 XCOBJS-$(CONFIG_ENV_IS_EMBEDDED) += env_embedded.o
diff --git a/common/env_attr.c b/common/env_attr.c
new file mode 100644
index 0000000..b5a8bec
--- /dev/null
+++ b/common/env_attr.c
@@ -0,0 +1,159 @@ 
+/*
+ * (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 <common.h>
+#include <environment.h>
+#include <errno.h>
+#include <malloc.h>
+
+/*
+ * Iterate through the whole list calling the callback for each found element.
+ */
+int env_attr_walk(const char *attr_list,
+	int (*callback)(const char *name, const char *value))
+{
+	const char *entry, *entry_end;
+	char *name, *value;
+
+	if (!attr_list)
+		/* list not found */
+		return 1;
+
+	entry = attr_list;
+	do {
+		char *entry_cpy = NULL;
+
+		entry_end = strchr(entry, ENV_ATTR_LIST_DELIM);
+		if (entry_end == NULL) {
+			int entry_len = strlen(entry);
+
+			if (entry_len) {
+				entry_cpy = malloc(entry_len + 1);
+				if (entry_cpy)
+					strcpy(entry_cpy, entry);
+				else
+					return -ENOMEM;
+			}
+		} else {
+			int entry_len = entry_end - entry;
+
+			if (entry_len) {
+				entry_cpy = malloc(entry_len + 1);
+				if (entry_cpy) {
+					strncpy(entry_cpy, entry, entry_len);
+					entry_cpy[entry_len] = '\0';
+				} else
+					return -ENOMEM;
+			}
+		}
+
+		if (entry_cpy != NULL) {
+			value = strchr(entry_cpy, ENV_ATTR_SEP);
+			if (value != NULL) {
+				*value++ = '\0';
+				value = strim(value);
+			}
+			name = strim(entry_cpy);
+
+			if (strlen(name) != 0) {
+				int retval = 0;
+
+				retval = callback(name, value);
+				if (retval) {
+					free(entry_cpy);
+					return retval;
+				}
+			}
+		}
+
+		free(entry_cpy);
+		entry = entry_end + 1;
+	} while (entry_end != NULL);
+
+	return 0;
+}
+
+/*
+ * 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 = strstr(attr_list, name);
+	while (entry != NULL) {
+		if ((entry == attr_list ||
+		    *(entry - 1) == ENV_ATTR_LIST_DELIM ||
+		    *(entry - 1) == ' ') &&
+		    (*(entry + strlen(name)) == ENV_ATTR_SEP ||
+		     *(entry + strlen(name)) == ENV_ATTR_LIST_DELIM ||
+		     *(entry + strlen(name)) == '\0' ||
+		     *(entry + strlen(name)) == ' '))
+			break;
+		entry++;
+		entry = strstr(entry, name);
+	}
+	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;
+			const char delims[2] = {ENV_ATTR_LIST_DELIM, ' '};
+
+			/* 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 0000000..9b9c915
--- /dev/null
+++ b/common/env_callback.c
@@ -0,0 +1,114 @@ 
+/*
+ * (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 <common.h>
+#include <environment.h>
+
+/*
+ * Look up a callback function pointer by name
+ */
+struct env_clbk_tbl *find_env_callback(const char *name)
+{
+	struct env_clbk_tbl *clbkp;
+
+	if (name == NULL)
+		return NULL;
+
+	for (clbkp = &__u_boot_env_clbk_start;
+	     clbkp != &__u_boot_env_clbk_end;
+	     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_bind(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;
+
+	if (callback_list != NULL)
+		ret = env_attr_lookup(callback_list, var_name, callback_name);
+
+	if (ret)
+		ret = env_attr_lookup(ENV_CALLBACK_LIST_STATIC, var_name,
+			callback_name);
+
+	if (!ret && strlen(callback_name)) {
+		clbkp = find_env_callback(callback_name);
+
+		if (clbkp != NULL)
+			var_entry->callback = clbkp->callback;
+	}
+}
+
+static int clear_callbacks(ENTRY *entry)
+{
+	entry->callback = NULL;
+
+	return 0;
+}
+
+static int set_callbacks(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);
+
+	if (ep != NULL) {
+		if (value == NULL || strlen(value) == 0)
+			ep->callback = NULL;
+		else {
+			clbkp = find_env_callback(value);
+
+			if (clbkp != NULL)
+				ep->callback = clbkp->callback;
+		}
+	}
+
+	return 0;
+}
+
+static int on_callbacks(const char *name, const char *value, enum env_op op)
+{
+	hwalk_r(&env_htab, clear_callbacks);
+
+	env_attr_walk(ENV_CALLBACK_LIST_STATIC, set_callbacks);
+	env_attr_walk(value, set_callbacks);
+
+	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 0000000..62a3667
--- /dev/null
+++ b/include/env_attr.h
@@ -0,0 +1,35 @@ 
+/*
+ * (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		':'
+
+extern int env_attr_walk(const char *attr_list,
+	int (*callback)(const char *name, const char *value));
+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 0000000..4a92c24
--- /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__
+
+#define ENV_CALLBACK_VAR ".callbacks"
+
+#ifndef CONFIG_ENV_CALLBACK_LIST_STATIC
+#define CONFIG_ENV_CALLBACK_LIST_STATIC
+#endif
+
+#define ENV_CALLBACK_LIST_STATIC ENV_CALLBACK_VAR ":callbacks," \
+	CONFIG_ENV_CALLBACK_LIST_STATIC
+
+enum env_op {
+	env_op_none,
+	env_op_create,
+	env_op_delete,
+	env_op_overwrite,
+};
+
+struct env_clbk_tbl {
+	const char *name;		/* Callback name */
+	int (*callback)(const char *name, const char *value, enum env_op op);
+};
+
+extern struct env_clbk_tbl __u_boot_env_clbk_start;
+extern struct env_clbk_tbl __u_boot_env_clbk_end;
+
+struct env_clbk_tbl *find_env_callback(const char *);
+typedef struct entry ENTRY;
+void env_callback_bind(ENTRY *var_entry);
+
+#define ENV_CLBK_SECTION __attribute__((unused, section(".u_boot_env_clbk"), \
+		aligned(4)))
+
+#define U_BOOT_ENV_CALLBACK(name, callback) \
+	struct env_clbk_tbl __u_boot_env_clbk_##name ENV_CLBK_SECTION = \
+	{#name, callback}
+
+#endif /* __ENV_CALLBACK_H__ */
diff --git a/include/environment.h b/include/environment.h
index ae3f7b6..040352f 100644
--- a/include/environment.h
+++ b/include/environment.h
@@ -164,6 +164,8 @@  extern void env_reloc(void);
 
 #ifndef DO_DEPS_ONLY
 
+#include <env_attr.h>
+#include <env_callback.h>
 #include <search.h>
 
 extern struct hsearch_data env_htab;
diff --git a/include/search.h b/include/search.h
index ef53edb..9d0d498 100644
--- a/include/search.h
+++ b/include/search.h
@@ -29,6 +29,7 @@ 
 #define	_SEARCH_H 1
 
 #include <stddef.h>
+#include <env_callback.h>
 
 #define __set_errno(val) do { errno = val; } while (0)
 
@@ -41,6 +42,7 @@  typedef enum {
 typedef struct entry {
 	const char *key;
 	char *data;
+	int (*callback)(const char *name, const char *value, enum env_op op);
 } ENTRY;
 
 /* Opaque type for internal use.  */
@@ -98,6 +100,9 @@  extern int himport_r(struct hsearch_data *__htab,
 		     const char *__env, size_t __size, const char __sep,
 		     int __flag);
 
+/* 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() */
 #define	H_NOCLEAR	1	/* do not clear hash table before importing */
 
diff --git a/lib/hashtable.c b/lib/hashtable.c
index abd61c8..22ff617 100644
--- a/lib/hashtable.c
+++ b/lib/hashtable.c
@@ -54,7 +54,8 @@ 
 #define	CONFIG_ENV_MAX_ENTRIES 512
 #endif
 
-#include "search.h"
+#include <env_callback.h>
+#include <search.h>
 
 /*
  * [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
@@ -290,6 +291,15 @@  int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
 		    && strcmp(item.key, htab->table[idx].entry.key) == 0) {
 			/* Overwrite existing value? */
 			if ((action == ENTER) && (item.data != NULL)) {
+				/* 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)) {
+					__set_errno(EINVAL);
+					*retval = NULL;
+					return 0;
+				}
+
 				free(htab->table[idx].entry.data);
 				htab->table[idx].entry.data =
 					strdup(item.data);
@@ -332,6 +342,16 @@  int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
 			    && strcmp(item.key, htab->table[idx].entry.key) == 0) {
 				/* Overwrite existing value? */
 				if ((action == ENTER) && (item.data != NULL)) {
+					/* 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)) {
+						__set_errno(EINVAL);
+						*retval = NULL;
+						return 0;
+					}
+
 					free(htab->table[idx].entry.data);
 					htab->table[idx].entry.data =
 						strdup(item.data);
@@ -380,6 +400,18 @@  int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
 
 		++htab->filled;
 
+		/* This is a new entry, so look up a possible callback */
+		env_callback_bind(&htab->table[idx].entry);
+
+		/* 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)) {
+			__set_errno(EINVAL);
+			*retval = NULL;
+			return 0;
+		}
+
 		/* return new entry */
 		*retval = &htab->table[idx].entry;
 		return 1;
@@ -415,6 +447,13 @@  int hdelete_r(const char *key, struct hsearch_data *htab)
 		return 0;	/* not found */
 	}
 
+	/* If there is a callback, call it */
+	if (htab->table[idx].entry.callback &&
+	    htab->table[idx].entry.callback(key, NULL, env_op_delete)) {
+		__set_errno(EINVAL);
+		return 0;
+	}
+
 	/* free used ENTRY */
 	debug("hdelete: DELETING key \"%s\"\n", key);
 
@@ -765,3 +804,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;
+}