add support for an override config directory
authorFelix Fietkau <nbd@nbd.name>
Fri, 17 Jan 2025 10:09:29 +0000 (11:09 +0100)
committerFelix Fietkau <nbd@nbd.name>
Fri, 17 Jan 2025 10:25:08 +0000 (11:25 +0100)
This can be used to override specific config files at runtime without
touching them on primary storage. It is used by default for both reading
and updating.
The primary use case for this is adding a config management system which
generates and manages uci files at run time without overwriting existing
config files on flash.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
cli.c
file.c
libuci.c
lua/uci.c
uci.h

diff --git a/cli.c b/cli.c
index f169e429478b12a56333aa0494cffe64ea616318..36c21e4496c7312ca8887975c93c6ae2e440c143 100644 (file)
--- a/cli.c
+++ b/cli.c
@@ -159,7 +159,8 @@ static void uci_usage(void)
                "\treorder    <config>.<section>=<position>\n"
                "\n"
                "Options:\n"
-               "\t-c <path>  set the search path for config files (default: /etc/config)\n"
+               "\t-c <path>  set the search path for config files (default: "UCI_CONFDIR")\n"
+               "\t-C <path>  set the search path for config override files (default: "UCI_CONF2DIR")\n"
                "\t-d <str>   set the delimiter for list values in uci show\n"
                "\t-f <file>  use <file> as input instead of stdin\n"
                "\t-m         when importing, merge data into an existing package\n"
@@ -690,11 +691,14 @@ int main(int argc, char **argv)
                return 1;
        }
 
-       while((c = getopt(argc, argv, "c:d:f:LmnNp:P:qsSt:X")) != -1) {
+       while((c = getopt(argc, argv, "c:C:d:f:LmnNp:P:qsSt:X")) != -1) {
                switch(c) {
                        case 'c':
                                uci_set_confdir(ctx, optarg);
                                break;
+                       case 'C':
+                               uci_set_conf2dir(ctx, optarg);
+                               break;
                        case 'd':
                                delimiter = optarg;
                                break;
diff --git a/file.c b/file.c
index 9db8dbd3eeca0a4176cd4a3fcac28fe080913fc3..6840cfcf34cf58a89b265edb8d965287d38308f0 100644 (file)
--- a/file.c
+++ b/file.c
@@ -719,13 +719,16 @@ error:
 }
 
 
-static char *uci_config_path(struct uci_context *ctx, const char *name)
+static char *uci_config_path(struct uci_context *ctx, const char *name, bool conf2)
 {
+       const char *confdir = conf2 ? ctx->conf2dir : ctx->confdir;
        char *filename;
 
        UCI_ASSERT(ctx, uci_validate_package(name));
-       filename = uci_malloc(ctx, strlen(name) + strlen(ctx->confdir) + 2);
-       sprintf(filename, "%s/%s", ctx->confdir, name);
+       if (!confdir)
+               return NULL;
+       filename = uci_malloc(ctx, strlen(name) + strlen(confdir) + 2);
+       sprintf(filename, "%s/%s", confdir, name);
 
        return filename;
 }
@@ -739,18 +742,18 @@ static void uci_file_commit(struct uci_context *ctx, struct uci_package **packag
        char *filename = NULL;
        struct stat statbuf;
        volatile bool do_rename = false;
+       const char *confdir;
        int fd, sz;
 
-       if (!p->path) {
-               if (overwrite)
-                       p->path = uci_config_path(ctx, p->e.name);
-               else
-                       UCI_THROW(ctx, UCI_ERR_INVAL);
-       }
+       if (!p->path && overwrite)
+               p->path = uci_config_path(ctx, p->e.name, p->uses_conf2);
+       if (!p->path)
+               UCI_THROW(ctx, UCI_ERR_INVAL);
 
-       sz = snprintf(NULL, 0, "%s/.%s.uci-XXXXXX", ctx->confdir, p->e.name);
+       confdir = p->uses_conf2 ? ctx->conf2dir : ctx->confdir;
+       sz = snprintf(NULL, 0, "%s/.%s.uci-XXXXXX", confdir, p->e.name);
        filename = alloca(sz + 1);
-       snprintf(filename, sz + 1, "%s/.%s.uci-XXXXXX", ctx->confdir, p->e.name);
+       snprintf(filename, sz + 1, "%s/.%s.uci-XXXXXX", confdir, p->e.name);
 
        /* open the config file for writing now, so that it is locked */
        f1 = uci_open_stream(ctx, p->path, NULL, SEEK_SET, true, true);
@@ -910,6 +913,8 @@ static struct uci_package *uci_file_load(struct uci_context *ctx,
        char *filename;
        bool confdir;
        FILE *volatile file = NULL;
+       struct stat st;
+       bool conf2;
 
        switch (name[0]) {
        case '.':
@@ -922,10 +927,17 @@ static struct uci_package *uci_file_load(struct uci_context *ctx,
                filename = uci_strdup(ctx, name);
                name = strrchr(name, '/') + 1;
                confdir = false;
+               conf2 = false;
                break;
        default:
                /* config in /etc/config */
-               filename = uci_config_path(ctx, name);
+               conf2 = true;
+               filename = uci_config_path(ctx, name, conf2);
+               if (!filename || stat(filename, &st) != 0) {
+                       conf2 = false;
+                       free(filename);
+                       filename = uci_config_path(ctx, name, conf2);
+               }
                confdir = true;
                break;
        }
@@ -937,6 +949,7 @@ static struct uci_package *uci_file_load(struct uci_context *ctx,
        UCI_TRAP_RESTORE(ctx);
 
        if (package) {
+               package->uses_conf2 = conf2;
                package->path = filename;
                package->has_delta = confdir;
                uci_load_delta(ctx, package, false);
index ae4c96476507c42e55f6817dd67d8a5c15bbcf1a..cffb916d6715eb87987cdacecd8541bf0fb5d05d 100644 (file)
--- a/libuci.c
+++ b/libuci.c
@@ -41,6 +41,7 @@ static const char *uci_errstr[] = {
 #include "list.c"
 
 __private const char *uci_confdir = UCI_CONFDIR;
+__private const char *uci_conf2dir = UCI_CONF2DIR;
 __private const char *uci_savedir = UCI_SAVEDIR;
 
 /* exported functions */
@@ -58,6 +59,7 @@ struct uci_context *uci_alloc_context(void)
        ctx->flags = UCI_FLAG_STRICT | UCI_FLAG_SAVED_DELTA;
 
        ctx->confdir = (char *) uci_confdir;
+       ctx->conf2dir = (char *) uci_conf2dir;
        ctx->savedir = (char *) uci_savedir;
        uci_add_delta_path(ctx, uci_savedir);
 
@@ -92,6 +94,22 @@ ignore:
        return;
 }
 
+int uci_set_conf2dir(struct uci_context *ctx, const char *dir)
+{
+       char *cdir;
+
+       UCI_HANDLE_ERR(ctx);
+       if (dir && !dir[0])
+               dir = NULL;
+
+       cdir = dir ? uci_strdup(ctx, dir) : NULL;
+       if (ctx->conf2dir != uci_conf2dir)
+               free(ctx->conf2dir);
+       ctx->conf2dir = cdir;
+
+       return 0;
+}
+
 int uci_set_confdir(struct uci_context *ctx, const char *dir)
 {
        char *cdir;
index 78b5e1f43827ead80350b246bbf8cde153512007..5957f309e57f72f1301b9ea316f7909055b4e9bf 100644 (file)
--- a/lua/uci.c
+++ b/lua/uci.c
@@ -906,6 +906,26 @@ uci_lua_set_confdir(lua_State *L)
        return uci_push_status(L, ctx, false);
 }
 
+static int
+uci_lua_get_conf2dir(lua_State *L)
+{
+       struct uci_context *ctx = find_context(L, NULL);
+       lua_pushstring(L, ctx->conf2dir ? ctx->conf2dir : "");
+       return 1;
+}
+
+static int
+uci_lua_set_conf2dir(lua_State *L)
+{
+       struct uci_context *ctx;
+       int offset = 0;
+
+       ctx = find_context(L, &offset);
+       luaL_checkstring(L, 1 + offset);
+       uci_set_conf2dir(ctx, lua_tostring(L, -1));
+       return uci_push_status(L, ctx, false);
+}
+
 static int
 uci_lua_get_savedir(lua_State *L)
 {
@@ -1029,6 +1049,8 @@ static const luaL_Reg uci[] = {
        { "add_delta", uci_lua_add_delta },
        { "get_confdir", uci_lua_get_confdir },
        { "set_confdir", uci_lua_set_confdir },
+       { "get_conf2dir", uci_lua_get_conf2dir },
+       { "set_conf2dir", uci_lua_set_conf2dir },
        { "get_savedir", uci_lua_get_savedir },
        { "set_savedir", uci_lua_set_savedir },
        { "list_configs", uci_lua_list_configs },
diff --git a/uci.h b/uci.h
index d0374f20a00bd4dfb5a900eb39b30beba1e50c55..b3ffdf9331b36bafae94ecb2a2bac9686900a8de 100644 (file)
--- a/uci.h
+++ b/uci.h
@@ -38,6 +38,7 @@ extern "C" {
 
 #define UCI_CONFDIR "/etc/config"
 #define UCI_SAVEDIR "/tmp/.uci"
+#define UCI_CONF2DIR "/var/run/uci"
 #define UCI_DIRMODE 0700
 #define UCI_FILEMODE 0600
 
@@ -265,6 +266,13 @@ extern int uci_set_savedir(struct uci_context *ctx, const char *dir);
  */
 extern int uci_set_confdir(struct uci_context *ctx, const char *dir);
 
+/**
+ * uci_set_conf2dir: change the override config storage directory
+ * @ctx: uci context
+ * @dir: directory name (can be NULL to disable config override)
+ */
+extern int uci_set_conf2dir(struct uci_context *ctx, const char *dir);
+
 /**
  * uci_add_delta_path: add a directory to the search path for change delta files
  * @ctx: uci context
@@ -411,6 +419,7 @@ struct uci_context
 
        char *confdir;
        char *savedir;
+       char *conf2dir;
 
        /* search path for delta files */
        struct uci_list delta_path;
@@ -429,7 +438,7 @@ struct uci_package
        struct uci_element e;
        struct uci_list sections;
        struct uci_context *ctx;
-       bool has_delta;
+       bool has_delta, uses_conf2;
        char *path;
 
        /* private: */