bbd9b4cfbf25b9bfe54513d5f1ba19aca326a3d3
[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 % util.shellquote(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