461ba9d5a38efae2c042829dce83d5aaeec13749
[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 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 value 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 conf = require "luci.config"
151                 local timeout = tonumber(conf and 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