summaryrefslogtreecommitdiff
path: root/package/uci/trigger
diff options
context:
space:
mode:
Diffstat (limited to 'package/uci/trigger')
-rw-r--r--package/uci/trigger/apply_config44
-rw-r--r--package/uci/trigger/lib/trigger.lua371
-rw-r--r--package/uci/trigger/modules/base.lua63
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"
+ },
+}
+