trigger.lua 7.1 KB

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