Code that runs foreign code, like boot scripts, often wants to revert
whatever that code changed in the global namespace afterwards. So far
this is done by hand for each use case: either by saving and restoring a
fixed list of variables, or by putting variables into a dedicated
namespace which is cleared afterwards.

Add globalvar_scope_push() and globalvar_scope_pop() to do this
generically. A scope records the current values of all globalvars whose
name begins with one of the given prefixes. Popping it restores changed
values, removes variables created in the meantime and creates removed
variables again. Variables are only removed or recreated if they were
created with globalvar_add_simple(), which is what the shell's global
command and most dynamically created variables use. For all other
globalvars, which are backed by a C variable, only the value is
restored, through the regular setter so the C variable follows.

Scopes can be nested as long as they are popped in reverse order.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Sascha Hauer <[email protected]>
---
 common/globalvar.c  | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/globalvar.h |  29 +++++++++++
 2 files changed, 171 insertions(+)

diff --git a/common/globalvar.c b/common/globalvar.c
index 876379b253..4686378ded 100644
--- a/common/globalvar.c
+++ b/common/globalvar.c
@@ -456,6 +456,148 @@ void globalvar_set_match(const char *match, const char 
*val)
        }
 }
 
+struct globalvar_scope_var {
+       struct list_head list;
+       char *name;
+       char *value;
+       bool unqualified;
+       bool seen;
+};
+
+struct globalvar_scope {
+       const char * const *prefixes;
+       struct list_head vars;
+};
+
+static bool globalvar_scope_match(const struct globalvar_scope *scope,
+                                 const char *name)
+{
+       const char * const *prefix;
+
+       for (prefix = scope->prefixes; *prefix; prefix++)
+               if (str_has_prefix(name, *prefix))
+                       return true;
+
+       return false;
+}
+
+static struct globalvar_scope_var *
+globalvar_scope_find(struct globalvar_scope *scope, const char *name)
+{
+       struct globalvar_scope_var *var;
+
+       list_for_each_entry(var, &scope->vars, list)
+               if (!strcmp(var->name, name))
+                       return var;
+
+       return NULL;
+}
+
+/**
+ * __globalvar_scope_push - snapshot a set of globalvars
+ * @prefixes: NULL terminated array of name prefixes to snapshot, which
+ *            must stay valid until globalvar_scope_pop()
+ *
+ * Records the current value of all globalvars whose name starts with one
+ * of @prefixes. A later globalvar_scope_pop() reverts all changes done
+ * to these variables in the meantime, including adding and removing them.
+ * Scopes can be nested, but must be popped in reverse order.
+ *
+ * Return: the scope to be passed to globalvar_scope_pop()
+ */
+struct globalvar_scope *__globalvar_scope_push(const char * const *prefixes)
+{
+       struct globalvar_scope *scope;
+       struct param_d *p;
+
+       scope = xzalloc(sizeof(*scope));
+       scope->prefixes = prefixes;
+       INIT_LIST_HEAD(&scope->vars);
+
+       dev_for_each_param(&global_device, p) {
+               struct globalvar_scope_var *var;
+
+               if (!globalvar_scope_match(scope, p->name))
+                       continue;
+
+               var = xzalloc(sizeof(*var));
+               var->name = xstrdup(p->name);
+               var->value = xstrdup(p->get(p->bobj, p) ?: "");
+               var->unqualified = p->flags & PARAM_GLOBALVAR_UNQUALIFIED;
+               list_add_tail(&var->list, &scope->vars);
+       }
+
+       return scope;
+}
+
+/**
+ * globalvar_scope_pop - revert globalvars to a snapshot
+ * @scope: the scope returned by globalvar_scope_push()
+ *
+ * Restores all globalvars covered by @scope to the values they had when
+ * the scope was pushed: changed values are set back, variables created
+ * in the meantime are removed and removed variables are created again.
+ * Variables which were not created with globalvar_add_simple() are never
+ * removed or recreated, only their values are restored.
+ *
+ * @scope is freed afterwards.
+ */
+void globalvar_scope_pop(struct globalvar_scope *scope)
+{
+       struct globalvar_scope_var *var, *vtmp;
+       struct param_d *p, *tmp;
+       int ret;
+
+       if (!scope)
+               return;
+
+       dev_for_each_param_safe(&global_device, p, tmp) {
+               const char *cur;
+
+               if (!globalvar_scope_match(scope, p->name))
+                       continue;
+
+               var = globalvar_scope_find(scope, p->name);
+               if (!var) {
+                       if (!(p->flags & PARAM_GLOBALVAR_UNQUALIFIED))
+                               continue;
+
+                       pr_debug("scope: removing global.%s\n", p->name);
+                       param_remove(p);
+                       continue;
+               }
+
+               var->seen = true;
+
+               cur = p->get(p->bobj, p) ?: "";
+               if (!strcmp(cur, var->value))
+                       continue;
+
+               pr_debug("scope: restoring global.%s: \"%s\" -> \"%s\"\n",
+                        p->name, cur, var->value);
+
+               ret = dev_set_param(&global_device, p->name, var->value);
+               if (ret)
+                       pr_warn("Cannot restore global.%s=\"%s\": %pe\n",
+                               p->name, var->value, ERR_PTR(ret));
+       }
+
+       list_for_each_entry_safe(var, vtmp, &scope->vars, list) {
+               if (!var->seen && var->unqualified) {
+                       pr_debug("scope: recreating global.%s=\"%s\"\n",
+                                var->name, var->value);
+                       globalvar_add_simple(var->name, var->value);
+               }
+
+               list_del(&var->list);
+               free(var->name);
+               free(var->value);
+               free(var);
+       }
+
+       free(scope);
+}
+
 void globalvar_set(const char *name, const char *val)
 {
        dev_set_param(&global_device, name, val);
diff --git a/include/globalvar.h b/include/globalvar.h
index e369616c63..7fbfdd1284 100644
--- a/include/globalvar.h
+++ b/include/globalvar.h
@@ -10,6 +10,24 @@
 
 extern struct device global_device;
 
+struct globalvar_scope;
+
+/**
+ * globalvar_scope_push - snapshot a set of globalvars
+ * @...: name prefixes of the globalvars to snapshot
+ *
+ * See __globalvar_scope_push(). The scope borrows the prefixes until
+ * globalvar_scope_pop(). Collecting them in a static array makes the
+ * compiler reject prefixes that don't live that long, as only string
+ * literals and addresses of static objects are allowed as initializers.
+ *
+ * Return: the scope to be passed to globalvar_scope_pop()
+ */
+#define globalvar_scope_push(...) ({                                   \
+       static const char * const __prefixes[] = { __VA_ARGS__, NULL }; \
+       __globalvar_scope_push(__prefixes);                             \
+})
+
 #ifdef CONFIG_GLOBALVAR
 
 static inline const char *globalvar_get(const char *name)
@@ -24,6 +42,9 @@ char *globalvar_get_match(const char *match, const char 
*separator);
 void globalvar_set_match(const char *match, const char *val);
 void globalvar_set(const char *name, const char *val);
 
+struct globalvar_scope *__globalvar_scope_push(const char * const *prefixes);
+void globalvar_scope_pop(struct globalvar_scope *scope);
+
 int globalvar_add_simple_string(const char *name, char **value);
 int globalvar_add_simple_int(const char *name, int *value,
                             const char *format);
@@ -126,6 +147,14 @@ static inline void globalvar_set_match(const char *match, 
const char *val) {}
 
 static inline void globalvar_set(const char *name, const char *val) {}
 
+static inline struct globalvar_scope *
+__globalvar_scope_push(const char * const *prefixes)
+{
+       return NULL;
+}
+
+static inline void globalvar_scope_pop(struct globalvar_scope *scope) {}
+
 static inline int nvvar_load(void)
 {
        return 0;

-- 
2.47.3


Reply via email to