diff options
Diffstat (limited to 'package/uci/trigger')
-rw-r--r-- | package/uci/trigger/apply_config | 44 | ||||
-rw-r--r-- | package/uci/trigger/lib/trigger.lua | 371 | ||||
-rw-r--r-- | package/uci/trigger/modules/base.lua | 63 |
3 files changed, 478 insertions, 0 deletions
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" + }, +} + |