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