| 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 |
| 420 | |