1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
4 local os = require "os"
5 local util = require "luci.util"
6 local table = require "table"
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
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
20 module "luci.model.uci"
35 local session_id = nil
37 local function call(cmd, args)
38 if type(args) == "table" and session_id then
39 args.ubus_rpc_session = session_id
41 return util.ubus("uci", cmd, args)
49 function cursor_state()
53 function substate(self)
58 function get_confdir(self)
62 function get_savedir(self)
66 function get_session_id(self)
70 function set_confdir(self, directory)
74 function set_savedir(self, directory)
78 function set_session_id(self, id)
84 function load(self, config)
88 function save(self, config)
92 function unload(self, config)
97 function changes(self, config)
98 local rv, err = call("changes", { config = config })
100 if type(rv) == "table" and type(rv.changes) == "table" then
103 return nil, ERRSTR[err]
110 function revert(self, config)
111 local _, err = call("revert", { config = config })
112 return (err == nil), ERRSTR[err]
115 function commit(self, config)
116 local _, err = call("commit", { config = config })
117 return (err == nil), ERRSTR[err]
120 function apply(self, rollback)
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
128 _, err = call("apply", {
129 timeout = (timeout > 30) and timeout or 30,
134 local now = os.time()
135 local token = sys.uniqueid(16)
137 util.ubus("session", "set", {
138 ubus_rpc_session = "00000000000000000000000000000000",
142 session = session_id,
143 timeout = now + timeout
151 _, err = call("changes", {})
154 if type(_) == "table" and type(_.changes) == "table" then
156 for k, v in pairs(_.changes) do
157 _, err = call("commit", { config = k })
166 _, err = call("apply", { rollback = false })
170 return (err == nil), ERRSTR[err]
173 function confirm(self, token)
174 local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
177 if token ~= rollback_token then
178 return false, "Permission denied"
181 local _, err = util.ubus("uci", "confirm", {
182 ubus_rpc_session = rollback_sid
186 util.ubus("session", "set", {
187 ubus_rpc_session = "00000000000000000000000000000000",
188 values = { rollback = {} }
192 return (err == nil), ERRSTR[err]
195 return false, "No data"
198 function rollback(self)
199 local is_pending, time_remaining, rollback_sid = self:rollback_pending()
202 local _, err = util.ubus("uci", "rollback", {
203 ubus_rpc_session = rollback_sid
207 util.ubus("session", "set", {
208 ubus_rpc_session = "00000000000000000000000000000000",
209 values = { rollback = {} }
213 return (err == nil), ERRSTR[err]
216 return false, "No data"
219 function rollback_pending(self)
220 local rv, err = util.ubus("session", "get", {
221 ubus_rpc_session = "00000000000000000000000000000000",
222 keys = { "rollback" }
225 local now = os.time()
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
236 rv.values.rollback.timeout - now,
237 rv.values.rollback.session,
238 rv.values.rollback.token
241 return false, ERRSTR[err]
245 function foreach(self, config, stype, callback)
246 if type(callback) == "function" then
247 local rv, err = call("get", {
252 if type(rv) == "table" and type(rv.values) == "table" then
258 for _, section in pairs(rv.values) do
259 section[".index"] = section[".index"] or index
260 sections[index] = section
264 table.sort(sections, function(a, b)
265 return a[".index"] < b[".index"]
268 for _, section in ipairs(sections) do
269 local continue = callback(section)
271 if continue == false then
277 return false, ERRSTR[err] or "No data"
280 return false, "Invalid argument"
284 local function _get(self, operation, config, section, option)
285 if section == nil then
287 elseif type(option) == "string" and option:byte(1) ~= 46 then
288 local rv, err = call(operation, {
294 if type(rv) == "table" then
295 return rv.value or nil
297 return false, ERRSTR[err]
301 elseif option == nil then
302 local values = self:get_all(config, section)
304 return values[".type"], values[".name"]
309 return false, "Invalid argument"
313 function get(self, ...)
314 return _get(self, "get", ...)
317 function get_state(self, ...)
318 return _get(self, "state", ...)
321 function get_all(self, config, section)
322 local rv, err = call("get", {
327 if type(rv) == "table" and type(rv.values) == "table" then
330 return false, ERRSTR[err]
336 function get_bool(self, ...)
337 local val = self:get(...)
338 return (val == "1" or val == "true" or val == "yes" or val == "on")
341 function get_first(self, config, stype, option, default)
344 self:foreach(config, stype, function(s)
345 local val = not option and s[".name"] or s[option]
347 if type(default) == "number" then
349 elseif type(default) == "boolean" then
350 val = (val == "1" or val == "true" or
351 val == "yes" or val == "on")
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 })
372 function section(self, config, stype, name, values)
373 local rv, err = call("add", {
380 if type(rv) == "table" then
383 return false, ERRSTR[err]
390 function add(self, config, stype)
391 return self:section(config, stype)
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
399 local _, err = call("set", {
402 values = { [option] = select(1, ...) }
404 return (err == nil), ERRSTR[err]
408 function set_list(self, config, section, option, value)
409 if section == nil or option == nil then
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)
416 return self:set(config, section, option, { value })
420 function tset(self, config, section, values)
421 local _, err = call("set", {
426 return (err == nil), ERRSTR[err]
429 function reorder(self, config, section, index)
432 if type(section) == "string" and type(index) == "number" then
437 self:foreach(config, nil, function(s)
442 if s[".name"] ~= section then
444 sections[pos] = s[".name"]
446 sections[index + 1] = section
449 elseif type(section) == "table" then
452 return false, "Invalid argument"
455 local _, err = call("order", {
460 return (err == nil), ERRSTR[err]
464 function delete(self, config, section, option)
465 local _, err = call("delete", {
470 return (err == nil), ERRSTR[err]
473 function delete_all(self, config, stype, comparator)
475 if type(comparator) == "table" then
476 _, err = call("delete", {
481 elseif type(comparator) == "function" then
482 local rv = call("get", {
487 if type(rv) == "table" and type(rv.values) == "table" then
489 for sname, section in pairs(rv.values) do
490 if comparator(section) then
491 _, err = call("delete", {
498 elseif comparator == nil then
499 _, err = call("delete", {
504 return false, "Invalid argument"
507 return (err == nil), ERRSTR[err]