From e7cbce1f9e99ddf686bdef99574c0f62356b5a07 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 8 Jun 2009 01:27:01 +0000 Subject: [PATCH] add ucitrigger: a uci plugin, command line tool and lua interface for automatically applying uci config changes SVN-Revision: 16375 --- package/uci/Makefile | 20 +- package/uci/patches/110-plugin_support.patch | 423 +++++++++++++++++++ package/uci/patches/120-uci_trigger.patch | 182 ++++++++ package/uci/trigger/apply_config | 44 ++ package/uci/trigger/lib/trigger.lua | 371 ++++++++++++++++ package/uci/trigger/modules/base.lua | 63 +++ 6 files changed, 1102 insertions(+), 1 deletion(-) create mode 100644 package/uci/patches/110-plugin_support.patch create mode 100644 package/uci/patches/120-uci_trigger.patch create mode 100644 package/uci/trigger/apply_config create mode 100644 package/uci/trigger/lib/trigger.lua create mode 100644 package/uci/trigger/modules/base.lua diff --git a/package/uci/Makefile b/package/uci/Makefile index b88e72cd0f..f2f320b163 100644 --- a/package/uci/Makefile +++ b/package/uci/Makefile @@ -36,10 +36,17 @@ define Package/uci TITLE:=Utility for the Unified Configuration Interface (UCI) endef +define Package/ucitrigger + SECTION:=base + CATEGORY:=Base system + DEPENDS:=+libuci-lua + TITLE:=Automatic triggers for applying system config changes +endef + define Package/libuci-lua SECTION=libs CATEGORY=Libraries - DEPENDS:=+libuci +lua + DEPENDS:=+libuci +liblua TITLE:=Lua plugin for UCI endef @@ -63,6 +70,8 @@ endif define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) $(UCI_MAKEOPTS) $(MAKE) -C $(PKG_BUILD_DIR)/lua $(UCI_MAKEOPTS) + $(MAKE) -C $(PKG_BUILD_DIR)/trigger $(UCI_MAKEOPTS) \ + LIBS="$(TARGET_LDFLAGS) -L$(PKG_BUILD_DIR) -luci -llua -lcrypt -lm" endef define Package/libuci/install @@ -75,6 +84,14 @@ define Package/libuci-lua/install $(CP) $(PKG_BUILD_DIR)/lua/uci.so $(1)/usr/lib/lua/ endef +define Package/ucitrigger/install + $(INSTALL_DIR) $(1)/usr/lib/lua/uci $(1)/lib/config/trigger $(1)/usr/sbin + $(INSTALL_DATA) ./trigger/lib/trigger.lua $(1)/usr/lib/lua/uci/ + $(INSTALL_DATA) ./trigger/modules/*.lua $(1)/lib/config/trigger/ + $(INSTALL_DATA) $(PKG_BUILD_DIR)/trigger/uci_trigger.so $(1)/usr/lib/ + $(INSTALL_BIN) ./trigger/apply_config $(1)/usr/sbin/ +endef + define Package/uci/install $(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_DIR) $(1)/sbin @@ -94,3 +111,4 @@ endef $(eval $(call BuildPackage,uci)) $(eval $(call BuildPackage,libuci)) $(eval $(call BuildPackage,libuci-lua)) +$(eval $(call BuildPackage,ucitrigger)) diff --git a/package/uci/patches/110-plugin_support.patch b/package/uci/patches/110-plugin_support.patch new file mode 100644 index 0000000000..bdf85aa276 --- /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 0000000000..a1454a37b2 --- /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, ++}; diff --git a/package/uci/trigger/apply_config b/package/uci/trigger/apply_config new file mode 100644 index 0000000000..0d9c6cf15e --- /dev/null +++ b/package/uci/trigger/apply_config @@ -0,0 +1,44 @@ +#!/usr/bin/lua +require("uci") +require("uci.trigger") + +function usage() + print("Usage: " .. arg[0] .. " [options]") + print("Options:") + print(" -a: apply the config changes") + print(" -t: show matching UCI triggers") + print(" -s: show information about tasks to be executed") + print(" -r: reset all triggers") + print("") +end + +if arg[1] == "-s" then + local triggers = uci.trigger.get_active() + if #triggers > 0 then + print("Tasks:") + for i, a in ipairs(triggers) do + local trigger = a[1] + local sections = a[2] + print(" - " .. uci.trigger.get_description(trigger, sections)) + end + else + print "Nothing to do" + end +elseif arg[1] == "-t" then + local triggers = uci.trigger.get_active() + for i, a in ipairs(triggers) do + local trigger = a[1] + local sections = a[2] + if trigger.section_only then + print(trigger.id .. " " .. table.concat(" ", sections)) + else + print(trigger.id) + end + end +elseif arg[1] == "-a" then + uci.trigger.run() +elseif arg[1] == "-r" then + uci.trigger.reset_state() +else + usage() +end diff --git a/package/uci/trigger/lib/trigger.lua b/package/uci/trigger/lib/trigger.lua new file mode 100644 index 0000000000..6710211d00 --- /dev/null +++ b/package/uci/trigger/lib/trigger.lua @@ -0,0 +1,371 @@ +module("uci.trigger", package.seeall) +require("posix") +require("uci") + +local path = "/lib/config/trigger" +local triggers = nil +local tmp_cursor = nil + +function load_modules() + if triggers ~= nil then + return + end + triggers = { + list = {}, + uci = {}, + active = {} + } + local modules = posix.glob(path .. "/*.lua") + if modules == nil then + return + end + local oldpath = package.path + package.path = path .. "/?.lua" + for i, v in ipairs(modules) do + pcall(require(string.gsub(v, path .. "/(%w+)%.lua$", "%1"))) + end + package.path = oldpath +end + +function check_table(table, name) + if table[name] == nil then + table[name] = {} + end + return table[name] +end + +function get_table_val(val, vtype) + if type(val) == (vtype or "string") then + return { val } + elseif type(val) == "table" then + return val + end + return nil +end + +function get_name_list(name) + return get_table_val(name or ".all") +end + +function add_trigger_option(list, t) + local name = get_name_list(t.option) + for i, n in ipairs(name) do + option = check_table(list, n) + table.insert(option, t) + end +end + +function add_trigger_section(list, t) + local name = get_name_list(t.section) + for i, n in ipairs(name) do + section = check_table(list, n) + add_trigger_option(section, t) + end +end + +function check_insert_triggers(dest, list, tuple) + if list == nil then + return + end + for i, t in ipairs(list) do + local add = true + if type(t.check) == "function" then + add = t.check(tuple) + end + if add then + dest[t.id] = t + end + end +end + +function find_section_triggers(tlist, pos, tuple) + if pos == nil then + return + end + check_insert_triggers(tlist, pos[".all"], tuple) + if tuple.option then + check_insert_triggers(tlist, pos[tuple.option], tuple) + end +end + +function check_recursion(name, seen) + if seen == nil then + seen = {} + end + if seen[name] then + return nil + end + seen[name] = true + return seen +end + + +function find_recursive_depends(list, name, seen) + seen = check_recursion(name, seen) + if not seen then + return + end + local bt = get_table_val(triggers.list[name].belongs_to) or {} + for i, n in ipairs(bt) do + table.insert(list, n) + find_recursive_depends(list, n, seen) + end +end + +function check_trigger_depth(list, name) + if name == nil then + return + end + + local n = list[name] + if n == nil then + return + end + + list[name] = nil + return check_trigger_depth(list, n) +end + +function find_triggers(tuple) + local pos = triggers.uci[tuple.package] + if pos == nil then + return {} + end + + local tlist = {} + find_section_triggers(tlist, pos[".all"], tuple) + find_section_triggers(tlist, pos[tuple.section[".type"]], tuple) + + for n, t in pairs(tlist) do + local dep = {} + find_recursive_depends(dep, t.id) + for i, depname in ipairs(dep) do + check_trigger_depth(tlist, depname) + end + end + + local nlist = {} + for n, t in pairs(tlist) do + if t then + table.insert(nlist, t) + end + end + + return nlist +end + +function reset_state() + assert(io.open("/var/run/uci_trigger", "w")):close() + if tctx then + tctx:unload("uci_trigger") + end +end + +function load_state() + -- make sure the config file exists before we attempt to load it + -- uci doesn't like loading nonexistent config files + local f = assert(io.open("/var/run/uci_trigger", "a")):close() + + load_modules() + triggers.active = {} + if tctx then + tctx:unload("uci_trigger") + else + tctx = uci.cursor() + end + assert(tctx:load("/var/run/uci_trigger")) + tctx:foreach("uci_trigger", "trigger", + function(section) + trigger = triggers.list[section[".name"]] + if trigger == nil then + return + end + + active = {} + triggers.active[trigger.id] = active + + local s = get_table_val(section["sections"]) or {} + for i, v in ipairs(s) do + active[v] = true + end + end + ) +end + +function get_names(list) + local slist = {} + for name, val in pairs(list) do + if val then + table.insert(slist, name) + end + end + return slist +end + +function check_cancel(name, seen) + local t = triggers.list[name] + local dep = get_table_val(t.belongs_to) + seen = check_recursion(name, seen) + + if not t or not dep or not seen then + return false + end + + for i, v in ipairs(dep) do + -- only cancel triggers for all sections + -- if both the current and the parent trigger + -- are per-section + local section_only = false + if t.section_only then + local tdep = triggers.list[v] + if tdep then + section_only = tdep.section_only + end + end + + if check_cancel(v, seen) then + return true + end + if triggers.active[v] then + if section_only then + for n, active in pairs(triggers.active[v]) do + triggers.active[name][n] = false + end + else + return true + end + end + end + return false +end + +-- trigger api functions + +function add(ts) + for i,t in ipairs(ts) do + triggers.list[t.id] = t + match = {} + if t.package then + local package = check_table(triggers.uci, t.package) + add_trigger_section(package, t) + triggers.list[t.id] = t + end + end +end + +function set(data, cursor) + assert(data ~= nil) + if cursor == nil then + cursor = tmp_cursor or uci.cursor() + tmp_cursor = uci.cursor + end + + local tuple = { + package = data[1], + section = data[2], + option = data[3], + value = data[4] + } + assert(cursor:load(tuple.package)) + + load_state() + local section = cursor:get_all(tuple.package, tuple.section) + if (section == nil) then + if option ~= nil then + return + end + section = { + [".type"] = value + } + if tuple.section == nil then + tuple.section = "" + section[".anonymous"] = true + end + section[".name"] = tuple.section + end + tuple.section = section + + local ts = find_triggers(tuple) + for i, t in ipairs(ts) do + local active = triggers.active[t.id] + if not active then + active = {} + triggers.active[t.id] = active + tctx:set("uci_trigger", t.id, "trigger") + end + if section[".name"] then + active[section[".name"]] = true + end + local slist = get_names(triggers.active[t.id]) + if #slist > 0 then + tctx:set("uci_trigger", t.id, "sections", slist) + end + end + tctx:save("uci_trigger") +end + +function get_description(trigger, sections) + if not trigger.title then + return trigger.id + end + local desc = trigger.title + if trigger.section_only and sections and #sections > 0 then + desc = desc .. " (" .. table.concat(sections, ", ") .. ")" + end + return desc +end + +function get_active() + local slist = {} + + if triggers == nil then + load_state() + end + for name, val in pairs(triggers.active) do + if val and not check_cancel(name) then + local sections = {} + for name, active in pairs(triggers.active[name]) do + if active then + table.insert(sections, name) + end + end + table.insert(slist, { triggers.list[name], sections }) + end + end + return slist +end + +function run(ts) + if ts == nil then + ts = get_active() + end + for i, t in ipairs(ts) do + local trigger = t[1] + local sections = t[2] + local actions = get_table_val(trigger.action, "function") or {} + for ai, a in ipairs(actions) do + if not trigger.section_only then + sections = { "" } + end + for si, s in ipairs(sections) do + if a(s) then + tctx:delete("uci_trigger", trigger.id) + tctx:save("uci_trigger") + end + end + end + end +end + +-- helper functions + +function system_command(arg) + local cmd = arg + return function(arg) + return os.execute(cmd:format(arg)) == 0 + end +end + +function service_restart(arg) + return system_command("/etc/init.d/" .. arg .. " restart") +end diff --git a/package/uci/trigger/modules/base.lua b/package/uci/trigger/modules/base.lua new file mode 100644 index 0000000000..3ab6bba652 --- /dev/null +++ b/package/uci/trigger/modules/base.lua @@ -0,0 +1,63 @@ +module("trigger.base", package.seeall) +require("uci.trigger") + +uci.trigger.add { + { + id = "dnsmasq_restart", + title = "Restart dnsmasq", + package = "dhcp", + action = uci.trigger.service_restart("dnsmasq"), + }, + { + id = "dropbear_restart", + title = "Restart dropbear", + package = "dropbear", + action = uci.trigger.service_restart("dropbear"), + }, + { + id = "fstab_restart", + title = "Remount filesystems", + package = "fstab", + action = uci.trigger.service_restart("fstab"), + }, + { + id = "firewall_restart", + title = "Reload firewall rules", + package = "firewall", + action = uci.trigger.service_restart("firewall"), + }, + { + id = "httpd_restart", + title = "Restart the http server", + package = "httpd", + action = uci.trigger.service_restart("httpd") + }, + { + id = "led_restart", + title = "Reload LED settings", + package = "system", + section = "led", + action = uci.trigger.service_restart("led") + }, + { + id = "network_restart", + title = "Restart networking and wireless", + package = "network", + action = uci.trigger.service_restart("network") + }, + { + id = "qos_restart", + title = "Reload Quality of Service rules", + package = "qos", + action = uci.trigger.service_restart("qos"), + }, + { + id = "wireless_restart", + title = "Restart all wireless interfaces", + package = "wireless", + section = { "wifi-device", "wifi-iface" }, + action = uci.trigger.system_command("wifi"), + belongs_to = "network_restart" + }, +} +