Merge pull request #1705 from Mushoz/add-igmp-snooping
[project/luci.git] / modules / luci-base / luasrc / model / uci.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local os    = require "os"
5 local util  = require "luci.util"
6 local table = require "table"
7
8
9 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
10 local require, getmetatable, assert = require, getmetatable, assert
11 local error, pairs, ipairs = error, pairs, ipairs
12 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
13
14 -- The typical workflow for UCI is:  Get a cursor instance from the
15 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
16 -- save the changes to the staging area via Cursor.save and finally
17 -- Cursor.commit the data to the actual config files.
18 -- LuCI then needs to Cursor.apply the changes so deamons etc. are
19 -- reloaded.
20 module "luci.model.uci"
21
22 local ERRSTR = {
23         "Invalid command",
24         "Invalid argument",
25         "Method not found",
26         "Entry not found",
27         "No data",
28         "Permission denied",
29         "Timeout",
30         "Not supported",
31         "Unknown error",
32         "Connection failed"
33 }
34
35 local session_id = nil
36
37 local function call(cmd, args)
38         if type(args) == "table" and session_id then
39                 args.ubus_rpc_session = session_id
40         end
41         return util.ubus("uci", cmd, args)
42 end
43
44
45 function cursor()
46         return _M
47 end
48
49 function cursor_state()
50         return _M
51 end
52
53 function substate(self)
54         return self
55 end
56
57
58 function get_confdir(self)
59         return "/etc/config"
60 end
61
62 function get_savedir(self)
63         return "/tmp/.uci"
64 end
65
66 function get_session_id(self)
67         return session_id
68 end
69
70 function set_confdir(self, directory)
71         return false
72 end
73
74 function set_savedir(self, directory)
75         return false
76 end
77
78 function set_session_id(self, id)
79         session_id = id
80         return true
81 end
82
83
84 function load(self, config)
85         return true
86 end
87
88 function save(self, config)
89         return true
90 end
91
92 function unload(self, config)
93         return true
94 end
95
96
97 function changes(self, config)
98         local rv = call("changes", { config = config })
99         local res = {}
100
101         if type(rv) == "table" and type(rv.changes) == "table" then
102                 local package, changes
103                 for package, changes in pairs(rv.changes) do
104                         res[package] = {}
105
106                         local _, change
107                         for _, change in ipairs(changes) do
108                                 local operation, section, option, value = unpack(change)
109                                 if option and value and operation ~= "add" then
110                                         res[package][section] = res[package][section] or { }
111
112                                         if operation == "list-add" then
113                                                 local v = res[package][section][option]
114                                                 if type(v) == "table" then
115                                                         v[#v+1] = value or ""
116                                                 elseif v ~= nil then
117                                                         res[package][section][option] = { v, value }
118                                                 else
119                                                         res[package][section][option] = { value }
120                                                 end
121                                         else
122                                                 res[package][section][option] = value or ""
123                                         end
124                                 else
125                                         res[package][section] = res[package][section] or {}
126                                         res[package][section][".type"] = option or ""
127                                 end
128                         end
129                 end
130         end
131
132         return res
133 end
134
135
136 function revert(self, config)
137         local _, err = call("revert", { config = config })
138         return (err == nil), ERRSTR[err]
139 end
140
141 function commit(self, config)
142         local _, err = call("commit", { config = config })
143         return (err == nil), ERRSTR[err]
144 end
145
146 --[[
147 function apply(self, configs, command)
148         local _, config
149
150         assert(not command, "Apply command not supported anymore")
151
152         if type(configs) == "table" then
153                 for _, config in ipairs(configs) do
154                         call("service", "event", {
155                                 type = "config.change",
156                                 data = { package = config }
157                         })
158                 end
159         end
160 end
161 ]]
162
163
164 function foreach(self, config, stype, callback)
165         if type(callback) == "function" then
166                 local rv, err = call("get", {
167                         config = config,
168                         type   = stype
169                 })
170
171                 if type(rv) == "table" and type(rv.values) == "table" then
172                         local sections = { }
173                         local res = false
174                         local index = 1
175
176                         local _, section
177                         for _, section in pairs(rv.values) do
178                                 section[".index"] = section[".index"] or index
179                                 sections[index] = section
180                                 index = index + 1
181                         end
182
183                         table.sort(sections, function(a, b)
184                                 return a[".index"] < b[".index"]
185                         end)
186
187                         for _, section in ipairs(sections) do
188                                 local continue = callback(section)
189                                 res = true
190                                 if continue == false then
191                                         break
192                                 end
193                         end
194                         return res
195                 else
196                         return false, ERRSTR[err] or "No data"
197                 end
198         else
199                 return false, "Invalid argument"
200         end
201 end
202
203 local function _get(self, operation, config, section, option)
204         if section == nil then
205                 return nil
206         elseif type(option) == "string" and option:byte(1) ~= 46 then
207                 local rv, err = call(operation, {
208                         config  = config,
209                         section = section,
210                         option  = option
211                 })
212
213                 if type(rv) == "table" then
214                         return rv.value or nil
215                 elseif err then
216                         return false, ERRSTR[err]
217                 else
218                         return nil
219                 end
220         elseif option == nil then
221                 local values = self:get_all(config, section)
222                 if values then
223                         return values[".type"], values[".name"]
224                 else
225                         return nil
226                 end
227         else
228                 return false, "Invalid argument"
229         end
230 end
231
232 function get(self, ...)
233         return _get(self, "get", ...)
234 end
235
236 function get_state(self, ...)
237         return _get(self, "state", ...)
238 end
239
240 function get_all(self, config, section)
241         local rv, err = call("get", {
242                 config  = config,
243                 section = section
244         })
245
246         if type(rv) == "table" and type(rv.values) == "table" then
247                 return rv.values
248         elseif err then
249                 return false, ERRSTR[err]
250         else
251                 return nil
252         end
253 end
254
255 function get_bool(self, ...)
256         local val = self:get(...)
257         return (val == "1" or val == "true" or val == "yes" or val == "on")
258 end
259
260 function get_first(self, config, stype, option, default)
261         local rv = default
262
263         self:foreach(config, stype, function(s)
264                 local val = not option and s[".name"] or s[option]
265
266                 if type(default) == "number" then
267                         val = tonumber(val)
268                 elseif type(default) == "boolean" then
269                         val = (val == "1" or val == "true" or
270                                val == "yes" or val == "on")
271                 end
272
273                 if val ~= nil then
274                         rv = val
275                         return false
276                 end
277         end)
278
279         return rv
280 end
281
282 function get_list(self, config, section, option)
283         if config and section and option then
284                 local val = self:get(config, section, option)
285                 return (type(val) == "table" and val or { val })
286         end
287         return { }
288 end
289
290
291 function section(self, config, stype, name, values)
292         local rv, err = call("add", {
293                 config = config,
294                 type   = stype,
295                 name   = name,
296                 values = values
297         })
298
299         if type(rv) == "table" then
300                 return rv.section
301         elseif err then
302                 return false, ERRSTR[err]
303         else
304                 return nil
305         end
306 end
307
308
309 function add(self, config, stype)
310         return self:section(config, stype)
311 end
312
313 function set(self, config, section, option, value)
314         if value == nil then
315                 local sname, err = self:section(config, option, section)
316                 return (not not sname), err
317         else
318                 local _, err = call("set", {
319                         config  = config,
320                         section = section,
321                         values  = { [option] = value }
322                 })
323                 return (err == nil), ERRSTR[err]
324         end
325 end
326
327 function set_list(self, config, section, option, value)
328         if section == nil or option == nil then
329                 return false
330         elseif value == nil or (type(value) == "table" and #value == 0) then
331                 return self:delete(config, section, option)
332         elseif type(value) == "table" then
333                 return self:set(config, section, option, value)
334         else
335                 return self:set(config, section, option, { value })
336         end
337 end
338
339 function tset(self, config, section, values)
340         local _, err = call("set", {
341                 config  = config,
342                 section = section,
343                 values  = values
344         })
345         return (err == nil), ERRSTR[err]
346 end
347
348 function reorder(self, config, section, index)
349         local sections
350
351         if type(section) == "string" and type(index) == "number" then
352                 local pos = 0
353
354                 sections = { }
355
356                 self:foreach(config, nil, function(s)
357                         if pos == index then
358                                 pos = pos + 1
359                         end
360
361                         if s[".name"] ~= section then
362                                 pos = pos + 1
363                                 sections[pos] = s[".name"]
364                         else
365                                 sections[index + 1] = section
366                         end
367                 end)
368         elseif type(section) == "table" then
369                 sections = section
370         else
371                 return false, "Invalid argument"
372         end
373
374         local _, err = call("order", {
375                 config   = config,
376                 sections = sections
377         })
378
379         return (err == nil), ERRSTR[err]
380 end
381
382
383 function delete(self, config, section, option)
384         local _, err = call("delete", {
385                 config  = config,
386                 section = section,
387                 option  = option
388         })
389         return (err == nil), ERRSTR[err]
390 end
391
392 function delete_all(self, config, stype, comparator)
393         local _, err
394         if type(comparator) == "table" then
395                 _, err = call("delete", {
396                         config = config,
397                         type   = stype,
398                         match  = comparator
399                 })
400         elseif type(comparator) == "function" then
401                 local rv = call("get", {
402                         config = config,
403                         type   = stype
404                 })
405
406                 if type(rv) == "table" and type(rv.values) == "table" then
407                         local sname, section
408                         for sname, section in pairs(rv.values) do
409                                 if comparator(section) then
410                                         _, err = call("delete", {
411                                                 config  = config,
412                                                 section = sname
413                                         })
414                                 end
415                         end
416                 end
417         elseif comparator == nil then
418                 _, err = call("delete", {
419                         config  = config,
420                         type    = stype
421                 })
422         else
423                 return false, "Invalid argument"
424         end
425
426         return (err == nil), ERRSTR[err]
427 end
428
429
430 function apply(self, configlist, command)
431         configlist = self:_affected(configlist)
432         if command then
433                 return { "/sbin/luci-reload", unpack(configlist) }
434         else
435                 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
436                         % util.shellquote(table.concat(configlist, " ")))
437         end
438 end
439
440 -- Return a list of initscripts affected by configuration changes.
441 function _affected(self, configlist)
442         configlist = type(configlist) == "table" and configlist or { configlist }
443
444         -- Resolve dependencies
445         local reloadlist = { }
446
447         local function _resolve_deps(name)
448                 local reload = { name }
449                 local deps = { }
450
451                 self:foreach("ucitrack", name,
452                         function(section)
453                                 if section.affects then
454                                         for i, aff in ipairs(section.affects) do
455                                                 deps[#deps+1] = aff
456                                         end
457                                 end
458                         end)
459
460                 local i, dep
461                 for i, dep in ipairs(deps) do
462                         local j, add
463                         for j, add in ipairs(_resolve_deps(dep)) do
464                                 reload[#reload+1] = add
465                         end
466                 end
467
468                 return reload
469         end
470
471         -- Collect initscripts
472         local j, config
473         for j, config in ipairs(configlist) do
474                 local i, e
475                 for i, e in ipairs(_resolve_deps(config)) do
476                         if not util.contains(reloadlist, e) then
477                                 reloadlist[#reloadlist+1] = e
478                         end
479                 end
480         end
481
482         return reloadlist
483 end