372 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			372 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
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
 |