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 deamons 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 = call("changes", { config = config })
101 if type(rv) == "table" and type(rv.changes) == "table" then
102 local package, changes
103 for package, changes in pairs(rv.changes) do
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 { }
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 ""
117 res[package][section][option] = { v, value }
119 res[package][section][option] = { value }
122 res[package][section][option] = value or ""
125 res[package][section] = res[package][section] or {}
126 res[package][section][".type"] = option or ""
136 function revert(self, config)
137 local _, err = call("revert", { config = config })
138 return (err == nil), ERRSTR[err]
141 function commit(self, config)
142 local _, err = call("commit", { config = config })
143 return (err == nil), ERRSTR[err]
146 function apply(self, rollback)
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
154 _, err = call("apply", {
155 timeout = (timeout > 30) and timeout or 30,
160 local now = os.time()
161 local token = sys.uniqueid(16)
163 util.ubus("session", "set", {
164 ubus_rpc_session = "00000000000000000000000000000000",
168 session = session_id,
169 timeout = now + timeout
177 _, err = call("changes", {})
180 if type(_) == "table" and type(_.changes) == "table" then
182 for k, v in pairs(_.changes) do
183 _, err = call("commit", { config = k })
192 _, err = call("apply", { rollback = false })
196 return (err == nil), ERRSTR[err]
199 function confirm(self, token)
200 local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
203 if token ~= rollback_token then
204 return false, "Permission denied"
207 local _, err = util.ubus("uci", "confirm", {
208 ubus_rpc_session = rollback_sid
212 util.ubus("session", "set", {
213 ubus_rpc_session = "00000000000000000000000000000000",
214 values = { rollback = {} }
218 return (err == nil), ERRSTR[err]
221 return false, "No data"
224 function rollback(self)
225 local is_pending, time_remaining, rollback_sid = self:rollback_pending()
228 local _, err = util.ubus("uci", "rollback", {
229 ubus_rpc_session = rollback_sid
233 util.ubus("session", "set", {
234 ubus_rpc_session = "00000000000000000000000000000000",
235 values = { rollback = {} }
239 return (err == nil), ERRSTR[err]
242 return false, "No data"
245 function rollback_pending(self)
246 local rv, err = util.ubus("session", "get", {
247 ubus_rpc_session = "00000000000000000000000000000000",
248 keys = { "rollback" }
251 local now = os.time()
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
262 rv.values.rollback.timeout - now,
263 rv.values.rollback.session,
264 rv.values.rollback.token
267 return false, ERRSTR[err]
271 function foreach(self, config, stype, callback)
272 if type(callback) == "function" then
273 local rv, err = call("get", {
278 if type(rv) == "table" and type(rv.values) == "table" then
284 for _, section in pairs(rv.values) do
285 section[".index"] = section[".index"] or index
286 sections[index] = section
290 table.sort(sections, function(a, b)
291 return a[".index"] < b[".index"]
294 for _, section in ipairs(sections) do
295 local continue = callback(section)
297 if continue == false then
303 return false, ERRSTR[err] or "No data"
306 return false, "Invalid argument"
310 local function _get(self, operation, config, section, option)
311 if section == nil then
313 elseif type(option) == "string" and option:byte(1) ~= 46 then
314 local rv, err = call(operation, {
320 if type(rv) == "table" then
321 return rv.value or nil
323 return false, ERRSTR[err]
327 elseif option == nil then
328 local values = self:get_all(config, section)
330 return values[".type"], values[".name"]
335 return false, "Invalid argument"
339 function get(self, ...)
340 return _get(self, "get", ...)
343 function get_state(self, ...)
344 return _get(self, "state", ...)
347 function get_all(self, config, section)
348 local rv, err = call("get", {
353 if type(rv) == "table" and type(rv.values) == "table" then
356 return false, ERRSTR[err]
362 function get_bool(self, ...)
363 local val = self:get(...)
364 return (val == "1" or val == "true" or val == "yes" or val == "on")
367 function get_first(self, config, stype, option, default)
370 self:foreach(config, stype, function(s)
371 local val = not option and s[".name"] or s[option]
373 if type(default) == "number" then
375 elseif type(default) == "boolean" then
376 val = (val == "1" or val == "true" or
377 val == "yes" or val == "on")
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 })
398 function section(self, config, stype, name, values)
399 local rv, err = call("add", {
406 if type(rv) == "table" then
409 return false, ERRSTR[err]
416 function add(self, config, stype)
417 return self:section(config, stype)
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
425 local _, err = call("set", {
428 values = { [option] = select(1, ...) }
430 return (err == nil), ERRSTR[err]
434 function set_list(self, config, section, option, value)
435 if section == nil or option == nil then
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)
442 return self:set(config, section, option, { value })
446 function tset(self, config, section, values)
447 local _, err = call("set", {
452 return (err == nil), ERRSTR[err]
455 function reorder(self, config, section, index)
458 if type(section) == "string" and type(index) == "number" then
463 self:foreach(config, nil, function(s)
468 if s[".name"] ~= section then
470 sections[pos] = s[".name"]
472 sections[index + 1] = section
475 elseif type(section) == "table" then
478 return false, "Invalid argument"
481 local _, err = call("order", {
486 return (err == nil), ERRSTR[err]
490 function delete(self, config, section, option)
491 local _, err = call("delete", {
496 return (err == nil), ERRSTR[err]
499 function delete_all(self, config, stype, comparator)
501 if type(comparator) == "table" then
502 _, err = call("delete", {
507 elseif type(comparator) == "function" then
508 local rv = call("get", {
513 if type(rv) == "table" and type(rv.values) == "table" then
515 for sname, section in pairs(rv.values) do
516 if comparator(section) then
517 _, err = call("delete", {
524 elseif comparator == nil then
525 _, err = call("delete", {
530 return false, "Invalid argument"
533 return (err == nil), ERRSTR[err]