* overwriting of write-once variables.
  */
 
-int env_check_apply(const char *name, const char *oldval,
-                       const char *newval, int flag)
+int env_change_ok(const ENTRY *item, const char *newval, enum env_op op,
+       int flag)
 {
        int   console = -1;
+       const char *name;
+#if !defined(CONFIG_ENV_OVERWRITE) && defined(CONFIG_OVERWRITE_ETHADDR_ONCE) \
+&& defined(CONFIG_ETHADDR)
+       const char *oldval = NULL;
+
+       if (op != env_op_create)
+               oldval = item->data;
+#endif
+
+       name = item->key;
 
        /* Default value for NULL to protect string-manipulating functions */
        newval = newval ? : "";
 #endif /* CONFIG_CONSOLE_MUX */
        }
 
+#ifndef CONFIG_ENV_OVERWRITE
        /*
         * Some variables like "ethaddr" and "serial#" can be set only once and
         * cannot be deleted, unless CONFIG_ENV_OVERWRITE is defined.
         */
-#ifndef CONFIG_ENV_OVERWRITE
-       if (oldval != NULL &&                   /* variable exists */
+       if (op != env_op_create &&              /* variable exists */
                (flag & H_FORCE) == 0) {        /* and we are not forced */
                if (strcmp(name, "serial#") == 0 ||
                    (strcmp(name, "ethaddr") == 0
         * (which will erase all variables prior to calling this),
         * we want the baudrate to actually change - for real.
         */
-       if (oldval != NULL ||                   /* variable exists */
+       if (op != env_op_create ||              /* variable exists */
                (flag & H_NOCLEAR) == 0) {      /* or env is clear */
                /*
                 * Switch to new baudrate if new baudrate is supported
        }
 
        env_id++;
-       /*
-        * search if variable with this name already exists
-        */
-       e.key = name;
-       e.data = NULL;
-       hsearch_r(e, FIND, &ep, &env_htab, 0);
-
-       /*
-        * Perform requested checks.
-        */
-       if (env_check_apply(name, ep ? ep->data : NULL, value, 0)) {
-               debug("check function did not approve, refusing\n");
-               return 1;
-       }
 
        /* Delete only ? */
        if (argc < 3 || argv[2] == NULL) {
 
 #include <env_default.h>
 
 struct hsearch_data env_htab = {
-       .apply = env_check_apply,
+       .change_ok = env_change_ok,
 };
 
 static uchar __env_get_char_spec(int index)
 {
 #if defined(CONFIG_NEEDS_MANUAL_RELOC)
        env_reloc();
+       env_htab.change_ok += gd->reloc_off;
 #endif
        if (gd->env_valid == 0) {
 #if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
 
 int env_import(const char *buf, int check);
 
 /*
- * Check if variable "name" can be changed from oldval to newval,
- * and if so, apply the changes (e.g. baudrate).
+ * Check if variable "item" can be changed to newval
  * When (flag & H_FORCE) is set, it does not print out any error
  * message and forces overwriting of write-once variables.
  */
-int env_check_apply(const char *name, const char *oldval,
-                       const char *newval, int flag);
+int env_change_ok(const ENTRY *item, const char *newval, enum env_op op,
+       int flag);
 
 #endif /* DO_DEPS_ONLY */
 
 
 
 #define __set_errno(val) do { errno = val; } while (0)
 
+enum env_op {
+       env_op_create,
+       env_op_delete,
+       env_op_overwrite,
+};
+
 /* Action which shall be performed in the call the hsearch.  */
 typedef enum {
        FIND,
        unsigned int filled;
 /*
  * Callback function which will check whether the given change for variable
- * "name" from "oldval" to "newval" may be applied or not, and possibly apply
- * such change.
+ * "item" to "newval" may be applied or not, and possibly apply such change.
  * When (flag & H_FORCE) is set, it shall not print out any error message and
  * shall force overwriting of write-once variables.
 .* Must return 0 for approval, 1 for denial.
  */
-       int (*apply)(const char *name, const char *oldval,
-                       const char *newval, int flag);
+       int (*change_ok)(const ENTRY *__item, const char *newval, enum env_op,
+               int flag);
 };
 
 /* Create a new hashing table which will at most contain NEL elements.  */
 
  * Instead the interface of all functions is extended to take an argument
  * which describes the current status.
  */
+
 typedef struct _ENTRY {
        int used;
        ENTRY entry;
 } _ENTRY;
 
 
+static void _hdelete(const char *key, struct hsearch_data *htab, ENTRY *ep,
+       int idx);
+
 /*
  * hcreate()
  */
            && strcmp(item.key, htab->table[idx].entry.key) == 0) {
                /* Overwrite existing value? */
                if ((action == ENTER) && (item.data != NULL)) {
+                       /* check for permission */
+                       if (htab->change_ok != NULL && htab->change_ok(
+                           &htab->table[idx].entry, item.data,
+                           env_op_overwrite, flag)) {
+                               debug("change_ok() rejected setting variable "
+                                       "%s, skipping it!\n", item.key);
+                               __set_errno(EPERM);
+                               *retval = NULL;
+                               return 0;
+                       }
+
                        free(htab->table[idx].entry.data);
                        htab->table[idx].entry.data = strdup(item.data);
                        if (!htab->table[idx].entry.data) {
 
                ++htab->filled;
 
+               /* check for permission */
+               if (htab->change_ok != NULL && htab->change_ok(
+                   &htab->table[idx].entry, item.data, env_op_create, flag)) {
+                       debug("change_ok() rejected setting variable "
+                               "%s, skipping it!\n", item.key);
+                       _hdelete(item.key, htab, &htab->table[idx].entry, idx);
+                       __set_errno(EPERM);
+                       *retval = NULL;
+                       return 0;
+               }
+
                /* return new entry */
                *retval = &htab->table[idx].entry;
                return 1;
  * do that.
  */
 
+static void _hdelete(const char *key, struct hsearch_data *htab, ENTRY *ep,
+       int idx)
+{
+       /* free used ENTRY */
+       debug("hdelete: DELETING key \"%s\"\n", key);
+       free((void *)ep->key);
+       free(ep->data);
+       htab->table[idx].used = -1;
+
+       --htab->filled;
+}
+
 int hdelete_r(const char *key, struct hsearch_data *htab, int flag)
 {
        ENTRY e, *ep;
        }
 
        /* Check for permission */
-       if (htab->apply != NULL &&
-           htab->apply(ep->key, ep->data, NULL, flag)) {
+       if (htab->change_ok != NULL &&
+           htab->change_ok(ep, NULL, env_op_delete, flag)) {
+               debug("change_ok() rejected deleting variable "
+                       "%s, skipping it!\n", key);
                __set_errno(EPERM);
                return 0;
        }
 
-       /* free used ENTRY */
-       debug("hdelete: DELETING key \"%s\"\n", key);
-       free((void *)ep->key);
-       free(ep->data);
-       htab->table[idx].used = -1;
-
-       --htab->filled;
+       _hdelete(key, htab, ep, idx);
 
        return 1;
 }
                e.key = name;
                e.data = value;
 
-               /* if there is an apply function, check what it has to say */
-               if (htab->apply != NULL) {
-                       debug("searching before calling cb function"
-                               " for  %s\n", name);
-                       /*
-                        * Search for variable in existing env, so to pass
-                        * its previous value to the apply callback
-                        */
-                       hsearch_r(e, FIND, &rv, htab, 0);
-                       debug("previous value was %s\n", rv ? rv->data : "");
-                       if (htab->apply(name, rv ? rv->data : NULL,
-                               value, flag)) {
-                               debug("callback function refused to set"
-                                       " variable %s, skipping it!\n", name);
-                               continue;
-                       }
-               }
-
                hsearch_r(e, ENTER, &rv, htab, flag);
                if (rv == NULL) {
                        printf("himport_r: can't insert \"%s=%s\" into hash table\n",