3208f3b372f27c697590a91ef823df2e1826be58
[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 function get(self, 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", "get", {
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_all(self, config, section)
215         local rv, err = util.ubus("uci", "get", {
216                 config  = config,
217                 section = section
218         })
219
220         if type(rv) == "table" and type(rv.values) == "table" then
221                 return rv.values
222         elseif err then
223                 return false, ERRSTR[err]
224         else
225                 return nil
226         end
227 end
228
229 function get_bool(self, ...)
230         local val = self:get(...)
231         return (val == "1" or val == "true" or val == "yes" or val == "on")
232 end
233
234 function get_first(self, config, stype, option, default)
235         local rv = default
236
237         self:foreach(conf, stype, function(s)
238                 local val = not option and s[".name"] or s[option]
239
240                 if type(default) == "number" then
241                         val = tonumber(val)
242                 elseif type(default) == "boolean" then
243                         val = (val == "1" or val == "true" or
244                                val == "yes" or val == "on")
245                 end
246
247                 if val ~= nil then
248                         rv = val
249                         return false
250                 end
251         end)
252
253         return rv
254 end
255
256 function get_list(self, config, section, option)
257         if config and section and option then
258                 local val = self:get(config, section, option)
259                 return (type(val) == "table" and val or { val })
260         end
261         return { }
262 end
263
264
265 function section(self, config, stype, name, values)
266         local rv, err = util.ubus("uci", "add", {
267                 config = config,
268                 type   = stype,
269                 name   = name,
270                 values = values
271         })
272
273         if type(rv) == "table" then
274                 return rv.section
275         elseif err then
276                 return false, ERRSTR[err]
277         else
278                 return nil
279         end
280 end
281
282
283 function add(self, config, stype)
284         return self:section(config, stype)
285 end
286
287 function set(self, config, section, option, value)
288         if value == nil then
289                 local sname, err = self:section(config, option, section)
290                 return (not not sname), err
291         else
292                 local _, err = util.ubus("uci", "set", {
293                         config  = config,
294                         section = section,
295                         values  = { [option] = value }
296                 })
297                 return (err == nil), ERRSTR[err]
298         end
299 end
300
301 function set_list(self, config, section, option, value)
302         if section == nil or option == nil then
303                 return false
304         elseif value == nil or (type(value) == "table" and #value == 0) then
305                 return self:delete(config, section, option)
306         elseif type(value) == "table" then
307                 return self:set(config, section, option, value)
308         else
309                 return self:set(config, section, option, { value })
310         end
311 end
312
313 function tset(self, config, section, values)
314         local _, err = util.ubus("uci", "set", {
315                 config  = config,
316                 section = section,
317                 values  = values
318         })
319         return (err == nil), ERRSTR[err]
320 end
321
322 function reorder(self, config, section, index)
323         local sections
324
325         if type(section) == "string" and type(index) == "number" then
326                 local pos = 0
327
328                 sections = { }
329
330                 self:foreach(config, nil, function(s)
331                         if pos == index then
332                                 pos = pos + 1
333                         end
334
335                         if s[".name"] ~= section then
336                                 pos = pos + 1
337                                 sections[pos] = s[".name"]
338                         else
339                                 sections[index + 1] = section
340                         end
341                 end)
342         elseif type(section) == "table" then
343                 sections = section
344         else
345                 return false, "Invalid argument"
346         end
347
348         local _, err = util.ubus("uci", "order", {
349                 config   = config,
350                 sections = sections
351         })
352
353         return (err == nil), ERRSTR[err]
354 end
355
356
357 function delete(self, config, section, option)
358         local _, err = util.ubus("uci", "delete", {
359                 config  = config,
360                 section = section,
361                 option  = option
362         })
363         return (err == nil), ERRSTR[err]
364 end
365
366 function delete_all(self, config, stype, comparator)
367         local _, err
368         if type(comparator) == "table" then
369                 _, err = util.ubus("uci", "delete", {
370                         config = config,
371                         type   = stype,
372                         match  = comparator
373                 })
374         elseif type(comparator) == "function" then
375                 local rv = util.ubus("uci", "get", {
376                         config = config,
377                         type   = stype
378                 })
379
380                 if type(rv) == "table" and type(rv.values) == "table" then
381                         local sname, section
382                         for sname, section in pairs(rv.values) do
383                                 if comparator(section) then
384                                         _, err = util.ubus("uci", "delete", {
385                                                 config  = config,
386                                                 section = sname
387                                         })
388                                 end
389                         end
390                 end
391         elseif comparator == nil then
392                 _, err = util.ubus("uci", "delete", {
393                         config  = config,
394                         type    = stype
395                 })
396         else
397                 return false, "Invalid argument"
398         end
399
400         return (err == nil), ERRSTR[err]
401 end
402
403
404 function apply(self, configlist, command)
405         configlist = self:_affected(configlist)
406         if command then
407                 return { "/sbin/luci-reload", unpack(configlist) }
408         else
409                 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
410                         % table.concat(configlist, " "))
411         end
412 end
413
414 -- Return a list of initscripts affected by configuration changes.
415 function _affected(self, configlist)
416         configlist = type(configlist) == "table" and configlist or { configlist }
417
418         -- Resolve dependencies
419         local reloadlist = { }
420
421         local function _resolve_deps(name)
422                 local reload = { name }
423                 local deps = { }
424
425                 self:foreach("ucitrack", name,
426                         function(section)
427                                 if section.affects then
428                                         for i, aff in ipairs(section.affects) do
429                                                 deps[#deps+1] = aff
430                                         end
431                                 end
432                         end)
433
434                 local i, dep
435                 for i, dep in ipairs(deps) do
436                         local j, add
437                         for j, add in ipairs(_resolve_deps(dep)) do
438                                 reload[#reload+1] = add
439                         end
440                 end
441
442                 return reload
443         end
444
445         -- Collect initscripts
446         local j, config
447         for j, config in ipairs(configlist) do
448                 local i, e
449                 for i, e in ipairs(_resolve_deps(config)) do
450                         if not util.contains(reloadlist, e) then
451                                 reloadlist[#reloadlist+1] = e
452                         end
453                 end
454         end
455
456         return reloadlist
457 end