ucitrigger: add options to force enable/disable specific triggers
[openwrt/openwrt.git] / package / uci / trigger / lib / trigger.lua
1 module("uci.trigger", package.seeall)
2 require("posix")
3 require("uci")
4
5 local path = "/lib/config/trigger"
6 local triggers = nil
7 local tmp_cursor = nil
8
9 function 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
28 end
29
30 function check_table(table, name)
31 if table[name] == nil then
32 table[name] = {}
33 end
34 return table[name]
35 end
36
37 function 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
44 end
45
46 function get_name_list(name)
47 return get_table_val(name or ".all")
48 end
49
50 function 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
56 end
57
58 function 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
64 end
65
66 function 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
79 end
80
81 function 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
89 end
90
91 function 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
100 end
101
102
103 function 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
113 end
114
115 function 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)
127 end
128
129 function 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
155 end
156
157 function reset_state()
158 assert(io.open("/var/run/uci_trigger", "w")):close()
159 if tctx then
160 tctx:unload("uci_trigger")
161 end
162 end
163
164 function 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 )
193 end
194
195 function 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
203 end
204
205 function 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
240 end
241
242 -- trigger api functions
243
244 function 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
254 end
255
256 function 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
265 end
266
267 function 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")
313 end
314
315 function 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
324 end
325
326 function 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
344 end
345
346 function 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")
365 end
366
367 function 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")
384 end
385
386 function 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
406 end
407
408 -- helper functions
409
410 function system_command(arg)
411 local cmd = arg
412 return function(arg)
413 return os.execute(cmd:format(arg)) == 0
414 end
415 end
416
417 function service_restart(arg)
418 return system_command("/etc/init.d/" .. arg .. " restart")
419 end