Root/package/uci/trigger/lib/trigger.lua

1module("uci.trigger", package.seeall)
2require("posix")
3require("uci")
4
5local path = "/lib/config/trigger"
6local triggers = nil
7local tmp_cursor = nil
8
9function load_modules()
10    if triggers ~= nil then
11        return
12    end
13    triggers = {
14        list = {},
15        uci = {},
16        active = {}
17    }
18    local modules = posix.glob(path .. "/*.lua")
19    if modules == nil then
20        return
21    end
22    local oldpath = package.path
23    package.path = path .. "/?.lua"
24    for i, v in ipairs(modules) do
25        pcall(require(string.gsub(v, path .. "/(%w+)%.lua$", "%1")))
26    end
27    package.path = oldpath
28end
29
30function check_table(table, name)
31    if table[name] == nil then
32        table[name] = {}
33    end
34    return table[name]
35end
36
37function get_table_val(val, vtype)
38    if type(val) == (vtype or "string") then
39        return { val }
40    elseif type(val) == "table" then
41        return val
42    end
43    return nil
44end
45
46function get_name_list(name)
47    return get_table_val(name or ".all")
48end
49
50function add_trigger_option(list, t)
51    local name = get_name_list(t.option)
52    for i, n in ipairs(name) do
53        option = check_table(list, n)
54        table.insert(option, t)
55    end
56end
57
58function add_trigger_section(list, t)
59    local name = get_name_list(t.section)
60    for i, n in ipairs(name) do
61        section = check_table(list, n)
62        add_trigger_option(section, t)
63    end
64end
65
66function check_insert_triggers(dest, list, tuple)
67    if list == nil then
68        return
69    end
70    for i, t in ipairs(list) do
71        local add = true
72        if type(t.check) == "function" then
73            add = t.check(tuple)
74        end
75        if add then
76            dest[t.id] = t
77        end
78    end
79end
80
81function find_section_triggers(tlist, pos, tuple)
82    if pos == nil then
83        return
84    end
85    check_insert_triggers(tlist, pos[".all"], tuple)
86    if tuple.option then
87        check_insert_triggers(tlist, pos[tuple.option], tuple)
88    end
89end
90
91function check_recursion(name, seen)
92    if seen == nil then
93        seen = {}
94    end
95    if seen[name] then
96        return nil
97    end
98    seen[name] = true
99    return seen
100end
101
102
103function find_recursive_depends(list, name, seen)
104    seen = check_recursion(name, seen)
105    if not seen then
106        return
107    end
108    local bt = get_table_val(triggers.list[name].belongs_to) or {}
109    for i, n in ipairs(bt) do
110        table.insert(list, n)
111        find_recursive_depends(list, n, seen)
112    end
113end
114
115function check_trigger_depth(list, name)
116    if name == nil then
117        return
118    end
119
120    local n = list[name]
121    if n == nil then
122        return
123    end
124
125    list[name] = nil
126    return check_trigger_depth(list, n)
127end
128
129function find_triggers(tuple)
130    local pos = triggers.uci[tuple.package]
131    if pos == nil then
132        return {}
133    end
134
135    local tlist = {}
136    find_section_triggers(tlist, pos[".all"], tuple)
137    find_section_triggers(tlist, pos[tuple.section[".type"]], tuple)
138
139    for n, t in pairs(tlist) do
140        local dep = {}
141        find_recursive_depends(dep, t.id)
142        for i, depname in ipairs(dep) do
143            check_trigger_depth(tlist, depname)
144        end
145    end
146
147    local nlist = {}
148    for n, t in pairs(tlist) do
149        if t then
150            table.insert(nlist, t)
151        end
152    end
153
154    return nlist
155end
156
157function reset_state()
158    assert(io.open("/var/run/uci_trigger", "w")):close()
159    if tctx then
160        tctx:unload("uci_trigger")
161    end
162end
163
164function load_state()
165    -- make sure the config file exists before we attempt to load it
166    -- uci doesn't like loading nonexistent config files
167    local f = assert(io.open("/var/run/uci_trigger", "a")):close()
168
169    load_modules()
170    triggers.active = {}
171    if tctx then
172        tctx:unload("uci_trigger")
173    else
174        tctx = uci.cursor()
175    end
176    assert(tctx:load("/var/run/uci_trigger"))
177    tctx:foreach("uci_trigger", "trigger",
178        function(section)
179            trigger = triggers.list[section[".name"]]
180            if trigger == nil then
181                return
182            end
183
184            active = {}
185            triggers.active[trigger.id] = active
186
187            local s = get_table_val(section["sections"]) or {}
188            for i, v in ipairs(s) do
189                active[v] = true
190            end
191        end
192    )
193end
194
195function get_names(list)
196    local slist = {}
197    for name, val in pairs(list) do
198        if val then
199            table.insert(slist, name)
200        end
201    end
202    return slist
203end
204
205function check_cancel(name, seen)
206    local t = triggers.list[name]
207    local dep = get_table_val(t.belongs_to)
208    seen = check_recursion(name, seen)
209
210    if not t or not dep or not seen then
211        return false
212    end
213
214    for i, v in ipairs(dep) do
215        -- only cancel triggers for all sections
216        -- if both the current and the parent trigger
217        -- are per-section
218        local section_only = false
219        if t.section_only then
220            local tdep = triggers.list[v]
221            if tdep then
222                section_only = tdep.section_only
223            end
224        end
225
226        if check_cancel(v, seen) then
227            return true
228        end
229        if triggers.active[v] then
230            if section_only then
231                for n, active in pairs(triggers.active[v]) do
232                    triggers.active[name][n] = false
233                end
234            else
235                return true
236            end
237        end
238    end
239    return false
240end
241
242-- trigger api functions
243
244function add(ts)
245    for i,t in ipairs(ts) do
246        triggers.list[t.id] = t
247        match = {}
248        if t.package then
249            local package = check_table(triggers.uci, t.package)
250            add_trigger_section(package, t)
251            triggers.list[t.id] = t
252        end
253    end
254end
255
256function save_trigger(name)
257    if triggers.active[name] then
258        local slist = get_names(triggers.active[name])
259        if #slist > 0 then
260            tctx:set("uci_trigger", name, "sections", slist)
261        end
262    else
263        tctx:delete("uci_trigger", name)
264    end
265end
266
267function set(data, cursor)
268    assert(data ~= nil)
269    if cursor == nil then
270        cursor = tmp_cursor or uci.cursor()
271        tmp_cursor = uci.cursor
272    end
273
274    local tuple = {
275        package = data[1],
276        section = data[2],
277        option = data[3],
278        value = data[4]
279    }
280    assert(cursor:load(tuple.package))
281
282    load_state()
283    local section = cursor:get_all(tuple.package, tuple.section)
284    if (section == nil) then
285        if option ~= nil then
286            return
287        end
288        section = {
289            [".type"] = value
290        }
291        if tuple.section == nil then
292            tuple.section = ""
293            section[".anonymous"] = true
294        end
295        section[".name"] = tuple.section
296    end
297    tuple.section = section
298
299    local ts = find_triggers(tuple)
300    for i, t in ipairs(ts) do
301        local active = triggers.active[t.id]
302        if not active then
303            active = {}
304            triggers.active[t.id] = active
305            tctx:set("uci_trigger", t.id, "trigger")
306        end
307        if section[".name"] then
308            active[section[".name"]] = true
309        end
310        save_trigger(t.id)
311    end
312    tctx:save("uci_trigger")
313end
314
315function get_description(trigger, sections)
316    if not trigger.title then
317        return trigger.id
318    end
319    local desc = trigger.title
320    if trigger.section_only and sections and #sections > 0 then
321        desc = desc .. " (" .. table.concat(sections, ", ") .. ")"
322    end
323    return desc
324end
325
326function get_active()
327    local slist = {}
328
329    if triggers == nil then
330        load_state()
331    end
332    for name, val in pairs(triggers.active) do
333        if val and not check_cancel(name) then
334            local sections = {}
335            for name, active in pairs(triggers.active[name]) do
336                if active then
337                    table.insert(sections, name)
338                end
339            end
340            table.insert(slist, { triggers.list[name], sections })
341        end
342    end
343    return slist
344end
345
346function set_active(trigger, sections)
347    if triggers == nil then
348        load_state()
349    end
350    if not triggers.list[trigger] then
351        return
352    end
353    if triggers.active[trigger] == nil then
354        tctx:set("uci_trigger", trigger, "trigger")
355        triggers.active[trigger] = {}
356    end
357    local active = triggers.active[trigger]
358    if triggers.list[trigger].section_only or sections ~= nil then
359        for i, t in ipairs(sections) do
360            triggers.active[trigger][t] = true
361        end
362    end
363    save_trigger(trigger)
364    tctx:save("uci_trigger")
365end
366
367function clear_active(trigger, sections)
368    if triggers == nil then
369        load_state()
370    end
371    if triggers.list[trigger] == nil or triggers.active[trigger] == nil then
372        return
373    end
374    local active = triggers.active[trigger]
375    if not triggers.list[trigger].section_only or sections == nil then
376        triggers.active[trigger] = nil
377    else
378        for i, t in ipairs(sections) do
379            triggers.active[trigger][t] = false
380        end
381    end
382    save_trigger(trigger)
383    tctx:save("uci_trigger")
384end
385
386function run(ts)
387    if ts == nil then
388        ts = get_active()
389    end
390    for i, t in ipairs(ts) do
391        local trigger = t[1]
392        local sections = t[2]
393        local actions = get_table_val(trigger.action, "function") or {}
394        for ai, a in ipairs(actions) do
395            if not trigger.section_only then
396                sections = { "" }
397            end
398            for si, s in ipairs(sections) do
399                if a(s) then
400                    tctx:delete("uci_trigger", trigger.id)
401                    tctx:save("uci_trigger")
402                end
403            end
404        end
405    end
406end
407
408-- helper functions
409
410function system_command(arg)
411    local cmd = arg
412    return function(arg)
413        return os.execute(cmd:format(arg)) == 0
414    end
415end
416
417function service_restart(arg)
418    return system_command("/etc/init.d/" .. arg .. " restart")
419end
420

Archive Download this file



interactive