Merge pull request #2043 from Ansuel/materialfix
[oweals/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, select = error, pairs, ipairs, select
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 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 function apply(self, rollback)
147         local _, err
148
149         if rollback then
150                 local sys = require "luci.sys"
151                 local conf = require "luci.config"
152                 local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 30) or 0
153
154                 _, err = call("apply", {
155                         timeout = (timeout > 30) and timeout or 30,
156                         rollback = true
157                 })
158
159                 if not err then
160                         local now = os.time()
161                         local token = sys.uniqueid(16)
162
163                         util.ubus("session", "set", {
164                                 ubus_rpc_session = "00000000000000000000000000000000",
165                                 values = {
166                                         rollback = {
167                                                 token   = token,
168                                                 session = session_id,
169                                                 timeout = now + timeout
170                                         }
171                                 }
172                         })
173
174                         return token
175                 end
176         else
177                 _, err = call("changes", {})
178
179                 if not err then
180                         if type(_) == "table" and type(_.changes) == "table" then
181                                 local k, v
182                                 for k, v in pairs(_.changes) do
183                                         _, err = call("commit", { config = k })
184                                         if err then
185                                                 break
186                                         end
187                                 end
188                         end
189                 end
190
191                 if not err then
192                         _, err = call("apply", { rollback = false })
193                 end
194         end
195
196         return (err == nil), ERRSTR[err]
197 end
198
199 function confirm(self, token)
200         local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
201
202         if is_pending then
203                 if token ~= rollback_token then
204                         return false, "Permission denied"
205                 end
206
207                 local _, err = util.ubus("uci", "confirm", {
208                         ubus_rpc_session = rollback_sid
209                 })
210
211                 if not err then
212                         util.ubus("session", "set", {
213                                 ubus_rpc_session = "00000000000000000000000000000000",
214                                 values = { rollback = {} }
215                         })
216                 end
217
218                 return (err == nil), ERRSTR[err]
219         end
220
221         return false, "No data"
222 end
223
224 function rollback(self)
225         local is_pending, time_remaining, rollback_sid = self:rollback_pending()
226
227         if is_pending then
228                 local _, err = util.ubus("uci", "rollback", {
229                         ubus_rpc_session = rollback_sid
230                 })
231
232                 if not err then
233                         util.ubus("session", "set", {
234                                 ubus_rpc_session = "00000000000000000000000000000000",
235                                 values = { rollback = {} }
236                         })
237                 end
238
239                 return (err == nil), ERRSTR[err]
240         end
241
242         return false, "No data"
243 end
244
245 function rollback_pending(self)
246         local rv, err = util.ubus("session", "get", {
247                 ubus_rpc_session = "00000000000000000000000000000000",
248                 keys = { "rollback" }
249         })
250
251         local now = os.time()
252
253         if type(rv) == "table" and
254            type(rv.values) == "table" and
255            type(rv.values.rollback) == "table" and
256            type(rv.values.rollback.token) == "string" and
257            type(rv.values.rollback.session) == "string" and
258            type(rv.values.rollback.timeout) == "number" and
259            rv.values.rollback.timeout > now
260         then
261                 return true,
262                         rv.values.rollback.timeout - now,
263                         rv.values.rollback.session,
264                         rv.values.rollback.token
265         end
266
267         return false, ERRSTR[err]
268 end
269
270
271 function foreach(self, config, stype, callback)
272         if type(callback) == "function" then
273                 local rv, err = call("get", {
274                         config = config,
275                         type   = stype
276                 })
277
278                 if type(rv) == "table" and type(rv.values) == "table" then
279                         local sections = { }
280                         local res = false
281                         local index = 1
282
283                         local _, section
284                         for _, section in pairs(rv.values) do
285                                 section[".index"] = section[".index"] or index
286                                 sections[index] = section
287                                 index = index + 1
288                         end
289
290                         table.sort(sections, function(a, b)
291                                 return a[".index"] < b[".index"]
292                         end)
293
294                         for _, section in ipairs(sections) do
295                                 local continue = callback(section)
296                                 res = true
297                                 if continue == false then
298                                         break
299                                 end
300                         end
301                         return res
302                 else
303                         return false, ERRSTR[err] or "No data"
304                 end
305         else
306                 return false, "Invalid argument"
307         end
308 end
309
310 local function _get(self, operation, config, section, option)
311         if section == nil then
312                 return nil
313         elseif type(option) == "string" and option:byte(1) ~= 46 then
314                 local rv, err = call(operation, {
315                         config  = config,
316                         section = section,
317                         option  = option
318                 })
319
320                 if type(rv) == "table" then
321                         return rv.value or nil
322                 elseif err then
323                         return false, ERRSTR[err]
324                 else
325                         return nil
326                 end
327         elseif option == nil then
328                 local values = self:get_all(config, section)
329                 if values then
330                         return values[".type"], values[".name"]
331                 else
332                         return nil
333                 end
334         else
335                 return false, "Invalid argument"
336         end
337 end
338
339 function get(self, ...)
340         return _get(self, "get", ...)
341 end
342
343 function get_state(self, ...)
344         return _get(self, "state", ...)
345 end
346
347 function get_all(self, config, section)
348         local rv, err = call("get", {
349                 config  = config,
350                 section = section
351         })
352
353         if type(rv) == "table" and type(rv.values) == "table" then
354                 return rv.values
355         elseif err then
356                 return false, ERRSTR[err]
357         else
358                 return nil
359         end
360 end
361
362 function get_bool(self, ...)
363         local val = self:get(...)
364         return (val == "1" or val == "true" or val == "yes" or val == "on")
365 end
366
367 function get_first(self, config, stype, option, default)
368         local rv = default
369
370         self:foreach(config, stype, function(s)
371                 local val = not option and s[".name"] or s[option]
372
373                 if type(default) == "number" then
374                         val = tonumber(val)
375                 elseif type(default) == "boolean" then
376                         val = (val == "1" or val == "true" or
377                                val == "yes" or val == "on")
378                 end
379
380                 if val ~= nil then
381                         rv = val
382                         return false
383                 end
384         end)
385
386         return rv
387 end
388
389 function get_list(self, config, section, option)
390         if config and section and option then
391                 local val = self:get(config, section, option)
392                 return (type(val) == "table" and val or { val })
393         end
394         return { }
395 end
396
397
398 function section(self, config, stype, name, values)
399         local rv, err = call("add", {
400                 config = config,
401                 type   = stype,
402                 name   = name,
403                 values = values
404         })
405
406         if type(rv) == "table" then
407                 return rv.section
408         elseif err then
409                 return false, ERRSTR[err]
410         else
411                 return nil
412         end
413 end
414
415
416 function add(self, config, stype)
417         return self:section(config, stype)
418 end
419
420 function set(self, config, section, option, ...)
421         if select('#', ...) == 0 then
422                 local sname, err = self:section(config, option, section)
423                 return (not not sname), err
424         else
425                 local _, err = call("set", {
426                         config  = config,
427                         section = section,
428                         values  = { [option] = select(1, ...) }
429                 })
430                 return (err == nil), ERRSTR[err]
431         end
432 end
433
434 function set_list(self, config, section, option, value)
435         if section == nil or option == nil then
436                 return false
437         elseif value == nil or (type(value) == "table" and #value == 0) then
438                 return self:delete(config, section, option)
439         elseif type(value) == "table" then
440                 return self:set(config, section, option, value)
441         else
442                 return self:set(config, section, option, { value })
443         end
444 end
445
446 function tset(self, config, section, values)
447         local _, err = call("set", {
448                 config  = config,
449                 section = section,
450                 values  = values
451         })
452         return (err == nil), ERRSTR[err]
453 end
454
455 function reorder(self, config, section, index)
456         local sections
457
458         if type(section) == "string" and type(index) == "number" then
459                 local pos = 0
460
461                 sections = { }
462
463                 self:foreach(config, nil, function(s)
464                         if pos == index then
465                                 pos = pos + 1
466                         end
467
468                         if s[".name"] ~= section then
469                                 pos = pos + 1
470                                 sections[pos] = s[".name"]
471                         else
472                                 sections[index + 1] = section
473                         end
474                 end)
475         elseif type(section) == "table" then
476                 sections = section
477         else
478                 return false, "Invalid argument"
479         end
480
481         local _, err = call("order", {
482                 config   = config,
483                 sections = sections
484         })
485
486         return (err == nil), ERRSTR[err]
487 end
488
489
490 function delete(self, config, section, option)
491         local _, err = call("delete", {
492                 config  = config,
493                 section = section,
494                 option  = option
495         })
496         return (err == nil), ERRSTR[err]
497 end
498
499 function delete_all(self, config, stype, comparator)
500         local _, err
501         if type(comparator) == "table" then
502                 _, err = call("delete", {
503                         config = config,
504                         type   = stype,
505                         match  = comparator
506                 })
507         elseif type(comparator) == "function" then
508                 local rv = call("get", {
509                         config = config,
510                         type   = stype
511                 })
512
513                 if type(rv) == "table" and type(rv.values) == "table" then
514                         local sname, section
515                         for sname, section in pairs(rv.values) do
516                                 if comparator(section) then
517                                         _, err = call("delete", {
518                                                 config  = config,
519                                                 section = sname
520                                         })
521                                 end
522                         end
523                 end
524         elseif comparator == nil then
525                 _, err = call("delete", {
526                         config  = config,
527                         type    = stype
528                 })
529         else
530                 return false, "Invalid argument"
531         end
532
533         return (err == nil), ERRSTR[err]
534 end