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