From 9aad34a95c6cc7d68e12cd7d3fc2436f3106e9c6 Mon Sep 17 00:00:00 2001 From: nbd Date: Mon, 8 Jun 2009 01:27:01 +0000 Subject: add ucitrigger: a uci plugin, command line tool and lua interface for automatically applying uci config changes git-svn-id: svn://svn.openwrt.org/openwrt/trunk@16375 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- package/uci/patches/110-plugin_support.patch | 423 +++++++++++++++++++++++++++ package/uci/patches/120-uci_trigger.patch | 182 ++++++++++++ 2 files changed, 605 insertions(+) create mode 100644 package/uci/patches/110-plugin_support.patch create mode 100644 package/uci/patches/120-uci_trigger.patch (limited to 'package/uci/patches') diff --git a/package/uci/patches/110-plugin_support.patch b/package/uci/patches/110-plugin_support.patch new file mode 100644 index 000000000..bdf85aa27 --- /dev/null +++ b/package/uci/patches/110-plugin_support.patch @@ -0,0 +1,423 @@ +--- a/Makefile ++++ b/Makefile +@@ -7,7 +7,7 @@ DEBUG_TYPECAST=0 + + include Makefile.inc + +-LIBS=-lc ++LIBS=-lc -ldl + SHLIB_FILE=libuci.$(SHLIB_EXT).$(VERSION) + + define add_feature +@@ -23,6 +23,7 @@ ucimap.o: ucimap.c uci.h uci_config.h uc + + uci_config.h: FORCE + @rm -f "$@.tmp" ++ @echo "#define UCI_PREFIX \"$(prefix)\"" > "$@.tmp" + $(call add_feature,PLUGIN_SUPPORT) + $(call add_feature,DEBUG) + $(call add_feature,DEBUG_TYPECAST) +@@ -33,10 +34,10 @@ uci_config.h: FORCE + fi + + uci: cli.o libuci.$(SHLIB_EXT) +- $(CC) -o $@ $< -L. -luci ++ $(CC) -o $@ $< -L. -luci $(LIBS) + + uci-static: cli.o libuci.a +- $(CC) $(CFLAGS) -o $@ $^ ++ $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + + libuci-static.o: libuci.c $(LIBUCI_DEPS) + $(CC) $(CFLAGS) -c -o $@ $< +--- a/cli.c ++++ b/cli.c +@@ -27,6 +27,7 @@ static enum { + CLI_FLAG_NOCOMMIT = (1 << 2), + CLI_FLAG_BATCH = (1 << 3), + CLI_FLAG_SHOW_EXT = (1 << 4), ++ CLI_FLAG_NOPLUGINS= (1 << 5), + } flags; + + static FILE *input; +@@ -136,6 +137,7 @@ static void uci_usage(void) + "\t-c set the search path for config files (default: /etc/config)\n" + "\t-d set the delimiter for list values in uci show\n" + "\t-f use as input instead of stdin\n" ++ "\t-L do not load any plugins\n" + "\t-m when importing, merge data into an existing package\n" + "\t-n name unnamed sections on export (default)\n" + "\t-N don't name unnamed sections\n" +@@ -603,7 +605,7 @@ int main(int argc, char **argv) + return 1; + } + +- while((c = getopt(argc, argv, "c:d:f:mnNp:P:sSqX")) != -1) { ++ while((c = getopt(argc, argv, "c:d:f:LmnNp:P:sSqX")) != -1) { + switch(c) { + case 'c': + uci_set_confdir(ctx, optarg); +@@ -618,6 +620,9 @@ int main(int argc, char **argv) + return 1; + } + break; ++ case 'L': ++ flags |= CLI_FLAG_NOPLUGINS; ++ break; + case 'm': + flags |= CLI_FLAG_MERGE; + break; +@@ -662,6 +667,10 @@ int main(int argc, char **argv) + uci_usage(); + return 0; + } ++ ++ if (!(flags & CLI_FLAG_NOPLUGINS)) ++ uci_load_plugins(ctx, NULL); ++ + ret = uci_cmd(argc - 1, argv + 1); + if (input != stdin) + fclose(input); +--- a/history.c ++++ b/history.c +@@ -406,6 +406,17 @@ int uci_save(struct uci_context *ctx, st + if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename) + UCI_THROW(ctx, UCI_ERR_MEM); + ++ uci_foreach_element(&ctx->hooks, tmp) { ++ struct uci_hook *hook = uci_to_hook(tmp); ++ ++ if (!hook->ops->set) ++ continue; ++ ++ uci_foreach_element(&p->history, e) { ++ hook->ops->set(hook->ops, p, uci_to_history(e)); ++ } ++ } ++ + ctx->err = 0; + UCI_TRAP_SAVE(ctx, done); + f = uci_open_stream(ctx, filename, SEEK_END, true, true); +--- a/libuci.c ++++ b/libuci.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + #include "uci.h" + + static const char *uci_confdir = UCI_CONFDIR; +@@ -39,6 +41,7 @@ static const char *uci_errstr[] = { + }; + + static void uci_cleanup(struct uci_context *ctx); ++static void uci_unload_plugin(struct uci_context *ctx, struct uci_plugin *p); + + #include "uci_internal.h" + #include "util.c" +@@ -56,6 +59,8 @@ struct uci_context *uci_alloc_context(vo + uci_list_init(&ctx->root); + uci_list_init(&ctx->history_path); + uci_list_init(&ctx->backends); ++ uci_list_init(&ctx->hooks); ++ uci_list_init(&ctx->plugins); + ctx->flags = UCI_FLAG_STRICT | UCI_FLAG_SAVED_HISTORY; + + ctx->confdir = (char *) uci_confdir; +@@ -86,6 +91,9 @@ void uci_free_context(struct uci_context + uci_free_element(e); + } + UCI_TRAP_RESTORE(ctx); ++ uci_foreach_element_safe(&ctx->root, tmp, e) { ++ uci_unload_plugin(ctx, uci_to_plugin(e)); ++ } + free(ctx); + + ignore: +@@ -209,9 +217,16 @@ int uci_commit(struct uci_context *ctx, + int uci_load(struct uci_context *ctx, const char *name, struct uci_package **package) + { + struct uci_package *p; ++ struct uci_element *e; ++ + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, ctx->backend && ctx->backend->load); + p = ctx->backend->load(ctx, name); ++ uci_foreach_element(&ctx->hooks, e) { ++ struct uci_hook *h = uci_to_hook(e); ++ if (h->ops->load) ++ h->ops->load(h->ops, p); ++ } + if (package) + *package = p; + +@@ -280,3 +295,94 @@ int uci_set_backend(struct uci_context * + ctx->backend = uci_to_backend(e); + return 0; + } ++ ++int uci_add_hook(struct uci_context *ctx, const struct uci_hook_ops *ops) ++{ ++ struct uci_element *e; ++ struct uci_hook *h; ++ ++ UCI_HANDLE_ERR(ctx); ++ ++ /* check for duplicate elements */ ++ uci_foreach_element(&ctx->hooks, e) { ++ h = uci_to_hook(e); ++ if (h->ops == ops) ++ return UCI_ERR_INVAL; ++ } ++ ++ h = uci_alloc_element(ctx, hook, "", 0); ++ h->ops = ops; ++ uci_list_init(&h->e.list); ++ uci_list_add(&ctx->hooks, &h->e.list); ++ ++ return 0; ++} ++ ++int uci_remove_hook(struct uci_context *ctx, const struct uci_hook_ops *ops) ++{ ++ struct uci_element *e; ++ ++ uci_foreach_element(&ctx->hooks, e) { ++ struct uci_hook *h = uci_to_hook(e); ++ if (h->ops == ops) { ++ uci_list_del(&e->list); ++ return 0; ++ } ++ } ++ return UCI_ERR_NOTFOUND; ++} ++ ++int uci_load_plugin(struct uci_context *ctx, const char *filename) ++{ ++ struct uci_plugin *p; ++ const struct uci_plugin_ops *ops; ++ void *dlh; ++ ++ UCI_HANDLE_ERR(ctx); ++ dlh = dlopen(filename, RTLD_GLOBAL|RTLD_NOW); ++ if (!dlh) ++ UCI_THROW(ctx, UCI_ERR_NOTFOUND); ++ ++ ops = dlsym(dlh, "uci_plugin"); ++ if (!ops || !ops->attach || (ops->attach(ctx) != 0)) { ++ if (!ops) ++ fprintf(stderr, "No ops\n"); ++ else if (!ops->attach) ++ fprintf(stderr, "No attach\n"); ++ else ++ fprintf(stderr, "Other weirdness\n"); ++ dlclose(dlh); ++ UCI_THROW(ctx, UCI_ERR_INVAL); ++ } ++ ++ p = uci_alloc_element(ctx, plugin, filename, 0); ++ p->dlh = dlh; ++ p->ops = ops; ++ uci_list_add(&ctx->plugins, &p->e.list); ++ ++ return 0; ++} ++ ++static void uci_unload_plugin(struct uci_context *ctx, struct uci_plugin *p) ++{ ++ if (p->ops->detach) ++ p->ops->detach(ctx); ++ dlclose(p->dlh); ++ uci_free_element(&p->e); ++} ++ ++int uci_load_plugins(struct uci_context *ctx, const char *pattern) ++{ ++ glob_t gl; ++ int i; ++ ++ if (!pattern) ++ pattern = UCI_PREFIX "/lib/uci_*.so"; ++ ++ memset(&gl, 0, sizeof(gl)); ++ glob(pattern, 0, NULL, &gl); ++ for (i = 0; i < gl.gl_pathc; i++) ++ uci_load_plugin(ctx, gl.gl_pathv[i]); ++ ++ return 0; ++} +--- a/uci.h ++++ b/uci.h +@@ -56,6 +56,8 @@ struct uci_list + }; + + struct uci_ptr; ++struct uci_plugin; ++struct uci_hook_ops; + struct uci_element; + struct uci_package; + struct uci_section; +@@ -275,6 +277,43 @@ extern int uci_set_backend(struct uci_co + */ + extern bool uci_validate_text(const char *str); + ++ ++/** ++ * uci_add_hook: add a uci hook ++ * @ctx: uci context ++ * @ops: uci hook ops ++ * ++ * NB: allocated and freed by the caller ++ */ ++extern int uci_add_hook(struct uci_context *ctx, const struct uci_hook_ops *ops); ++ ++/** ++ * uci_remove_hook: remove a uci hook ++ * @ctx: uci context ++ * @ops: uci hook ops ++ */ ++extern int uci_remove_hook(struct uci_context *ctx, const struct uci_hook_ops *ops); ++ ++/** ++ * uci_load_plugin: load an uci plugin ++ * @ctx: uci context ++ * @filename: path to the uci plugin ++ * ++ * NB: plugin will be unloaded automatically when the context is freed ++ */ ++int uci_load_plugin(struct uci_context *ctx, const char *filename); ++ ++/** ++ * uci_load_plugins: load all uci plugins from a directory ++ * @ctx: uci context ++ * @pattern: pattern of uci plugin files (optional) ++ * ++ * if pattern is NULL, then uci_load_plugins will call uci_load_plugin ++ * for uci_*.so in /lib/ ++ */ ++int uci_load_plugins(struct uci_context *ctx, const char *pattern); ++ ++ + /* UCI data structures */ + enum uci_type { + UCI_TYPE_UNSPEC = 0, +@@ -285,6 +324,8 @@ enum uci_type { + UCI_TYPE_PATH = 5, + UCI_TYPE_BACKEND = 6, + UCI_TYPE_ITEM = 7, ++ UCI_TYPE_HOOK = 8, ++ UCI_TYPE_PLUGIN = 9, + }; + + enum uci_option_type { +@@ -346,6 +387,9 @@ struct uci_context + bool internal, nested; + char *buf; + int bufsz; ++ ++ struct uci_list hooks; ++ struct uci_list plugins; + }; + + struct uci_package +@@ -420,6 +464,31 @@ struct uci_ptr + const char *value; + }; + ++struct uci_hook_ops ++{ ++ void (*load)(const struct uci_hook_ops *ops, struct uci_package *p); ++ void (*set)(const struct uci_hook_ops *ops, struct uci_package *p, struct uci_history *e); ++}; ++ ++struct uci_hook ++{ ++ struct uci_element e; ++ const struct uci_hook_ops *ops; ++}; ++ ++struct uci_plugin_ops ++{ ++ int (*attach)(struct uci_context *ctx); ++ void (*detach)(struct uci_context *ctx); ++}; ++ ++struct uci_plugin ++{ ++ struct uci_element e; ++ const struct uci_plugin_ops *ops; ++ void *dlh; ++}; ++ + + /* linked list handling */ + #ifndef offsetof +@@ -490,6 +559,8 @@ struct uci_ptr + #define uci_type_package UCI_TYPE_PACKAGE + #define uci_type_section UCI_TYPE_SECTION + #define uci_type_option UCI_TYPE_OPTION ++#define uci_type_hook UCI_TYPE_HOOK ++#define uci_type_plugin UCI_TYPE_PLUGIN + + /* element typecasting */ + #ifdef UCI_DEBUG_TYPECAST +@@ -499,6 +570,8 @@ static const char *uci_typestr[] = { + [uci_type_package] = "package", + [uci_type_section] = "section", + [uci_type_option] = "option", ++ [uci_type_hook] = "hook", ++ [uci_type_plugin] = "plugin", + }; + + static void uci_typecast_error(int from, int to) +@@ -520,6 +593,8 @@ BUILD_CAST(history) + BUILD_CAST(package) + BUILD_CAST(section) + BUILD_CAST(option) ++BUILD_CAST(hook) ++BUILD_CAST(plugin) + + #else + #define uci_to_backend(ptr) container_of(ptr, struct uci_backend, e) +@@ -527,6 +602,8 @@ BUILD_CAST(option) + #define uci_to_package(ptr) container_of(ptr, struct uci_package, e) + #define uci_to_section(ptr) container_of(ptr, struct uci_section, e) + #define uci_to_option(ptr) container_of(ptr, struct uci_option, e) ++#define uci_to_hook(ptr) container_of(ptr, struct uci_hook, e) ++#define uci_to_plugin(ptr) container_of(ptr, struct uci_plugin, e) + #endif + + /** +--- a/lua/uci.c ++++ b/lua/uci.c +@@ -765,6 +765,20 @@ uci_lua_add_history(lua_State *L) + } + + static int ++uci_lua_load_plugins(lua_State *L) ++{ ++ struct uci_context *ctx; ++ int ret, offset = 0; ++ const char *str = NULL; ++ ++ ctx = find_context(L, &offset); ++ if (lua_isstring(L, -1)) ++ str = lua_tostring(L, -1); ++ ret = uci_load_plugins(ctx, str); ++ return uci_push_status(L, ctx, false); ++} ++ ++static int + uci_lua_set_savedir(lua_State *L) + { + struct uci_context *ctx; +@@ -831,6 +845,7 @@ static const luaL_Reg uci[] = { + { "changes", uci_lua_changes }, + { "foreach", uci_lua_foreach }, + { "add_history", uci_lua_add_history }, ++ { "load_plugins", uci_lua_load_plugins }, + { "get_confdir", uci_lua_get_confdir }, + { "set_confdir", uci_lua_set_confdir }, + { "get_savedir", uci_lua_get_savedir }, diff --git a/package/uci/patches/120-uci_trigger.patch b/package/uci/patches/120-uci_trigger.patch new file mode 100644 index 000000000..a1454a37b --- /dev/null +++ b/package/uci/patches/120-uci_trigger.patch @@ -0,0 +1,182 @@ +--- /dev/null ++++ b/trigger/Makefile +@@ -0,0 +1,44 @@ ++include ../Makefile.inc ++LUA_VERSION=5.1 ++PREFIX_SEARCH=/usr /usr/local /opt/local ++LUA_PLUGINDIR=$(firstword \ ++ $(foreach ldir,$(subst ;, ,$(shell lua -e 'print(package.cpath)')), \ ++ $(if $(findstring lib/lua/,$(ldir)),$(patsubst %/?.so,%,$(ldir))) \ ++ ) \ ++) ++ ++# find lua prefix ++LUA_PREFIX=$(firstword \ ++ $(foreach prefix,$(PREFIX_SEARCH),\ ++ $(if $(wildcard $(prefix)/include/lua.h),$(prefix)) \ ++ ) \ ++) ++ ++libdir=$(prefix)/libs ++luadir=$(if $(LUA_PLUGINDIR),$(LUA_PLUGINDIR),$(libdir)/lua/$(LUA_VERSION)) ++luainc=$(shell pkg-config --silence-errors --cflags lua$(LUA_VERSION)) ++ ++CPPFLAGS=-I.. $(if $(luainc),$(luainc), -I$(LUA_PREFIX)/include) ++LIBS=-L.. -luci $(shell pkg-config --silence-errors --libs lua$(LUA_VERSION)) ++ ++PLUGIN_LD=$(CC) ++ifeq ($(OS),Darwin) ++ PLUGIN_LDFLAGS=-bundle ++else ++ PLUGIN_LDFLAGS=-shared -Wl,-soname,$(SHLIB_FILE) ++endif ++ ++all: uci_trigger.so ++ ++uci_trigger.so: uci_trigger.o ++ $(PLUGIN_LD) $(PLUGIN_LDFLAGS) -o $@ $^ $(LIBS) ++ ++%.o: %.c ++ $(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -c -o $@ $< ++ ++install: ++ mkdir -p $(DESTDIR)$(luadir) ++ $(INSTALL) -m0644 uci_trigger.so $(DESTDIR)$(luadir)/ ++ ++clean: ++ rm -f *.so *.o uci_trigger.so +--- /dev/null ++++ b/trigger/uci_trigger.c +@@ -0,0 +1,132 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "uci.h" ++ ++// #define DEBUG ++ ++static int refcount = 0; ++static lua_State *gL = NULL; ++static bool prepared = false; ++ ++struct trigger_set_op { ++ struct uci_package *p; ++ struct uci_history *h; ++}; ++ ++static int trigger_set(lua_State *L) ++{ ++ struct trigger_set_op *so = ++ (struct trigger_set_op *)lua_touserdata(L, 1); ++ struct uci_package *p = so->p; ++ struct uci_history *h = so->h; ++ struct uci_context *ctx = p->ctx; ++ ++ /* ignore non-standard savedirs/configdirs ++ * in order to not trigger events on uci state changes */ ++ if (strcmp(ctx->savedir, UCI_SAVEDIR) || ++ strcmp(ctx->confdir, UCI_CONFDIR)) ++ return 0; ++ ++ if (!prepared) { ++ lua_getglobal(L, "require"); ++ lua_pushstring(L, "uci.trigger"); ++ lua_call(L, 1, 0); ++ ++ lua_getglobal(L, "uci"); ++ lua_getfield(L, -1, "trigger"); ++ lua_getfield(L, -1, "load_modules"); ++ lua_call(L, 0, 0); ++ prepared = true; ++ } else { ++ lua_getglobal(L, "uci"); ++ lua_getfield(L, -1, "trigger"); ++ } ++ ++ lua_getfield(L, -1, "set"); ++ lua_createtable(L, 4, 0); ++ ++ lua_pushstring(L, p->e.name); ++ lua_rawseti(L, -2, 1); ++ if (h->section) { ++ lua_pushstring(L, h->section); ++ lua_rawseti(L, -2, 2); ++ } ++ if (h->e.name) { ++ lua_pushstring(L, h->e.name); ++ lua_rawseti(L, -2, 3); ++ } ++ if (h->value) { ++ lua_pushstring(L, h->value); ++ lua_rawseti(L, -2, 4); ++ } ++ lua_call(L, 1, 0); ++ lua_pop(L, 2); ++ return 0; ++} ++ ++#ifdef DEBUG ++ ++static int report (lua_State *L, int status) { ++ if (status && !lua_isnil(L, -1)) { ++ const char *msg = lua_tostring(L, -1); ++ if (msg == NULL) msg = "(error object is not a string)"; ++ fprintf(stderr, "ERROR: %s\n", msg); ++ lua_pop(L, 1); ++ } ++ return status; ++} ++ ++#else ++ ++static inline int report(lua_State *L, int status) { ++ return status; ++} ++ ++#endif ++ ++static void trigger_set_hook(const struct uci_hook_ops *ops, struct uci_package *p, struct uci_history *h) ++{ ++ struct trigger_set_op so; ++ ++ so.p = p; ++ so.h = h; ++ report(gL, lua_cpcall(gL, &trigger_set, &so)); ++} ++ ++static struct uci_hook_ops hook = { ++ .set = trigger_set_hook, ++}; ++ ++static int trigger_attach(struct uci_context *ctx) ++{ ++ if (!gL) { ++ gL = luaL_newstate(); ++ if (!gL) ++ return -1; ++ luaL_openlibs(gL); ++ ++ refcount++; ++ } ++ uci_add_hook(ctx, &hook); ++ return 0; ++} ++ ++static void trigger_detach(struct uci_context *ctx) ++{ ++ if (gL && (--refcount <= 0)) { ++ lua_close(gL); ++ gL = NULL; ++ refcount = 0; ++ prepared = false; ++ } ++} ++ ++struct uci_plugin_ops uci_plugin = { ++ .attach = trigger_attach, ++ .detach = trigger_detach, ++}; -- cgit v1.2.3