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