You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

509 lines
10 KiB
Lua

1 year ago
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local os = require "os"
local util = require "luci.util"
local table = require "table"
local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local require, getmetatable, assert = require, getmetatable, assert
local error, pairs, ipairs, select = error, pairs, ipairs, select
local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
-- The typical workflow for UCI is: Get a cursor instance from the
-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
-- save the changes to the staging area via Cursor.save and finally
-- Cursor.commit the data to the actual config files.
-- LuCI then needs to Cursor.apply the changes so daemons etc. are
-- reloaded.
module "luci.model.uci"
local ERRSTR = {
"Invalid command",
"Invalid argument",
"Method not found",
"Entry not found",
"No data",
"Permission denied",
"Timeout",
"Not supported",
"Unknown error",
"Connection failed"
}
local session_id = nil
local function call(cmd, args)
if type(args) == "table" and session_id then
args.ubus_rpc_session = session_id
end
return util.ubus("uci", cmd, args)
end
function cursor()
return _M
end
function cursor_state()
return _M
end
function substate(self)
return self
end
function get_confdir(self)
return "/etc/config"
end
function get_savedir(self)
return "/tmp/.uci"
end
function get_session_id(self)
return session_id
end
function set_confdir(self, directory)
return false
end
function set_savedir(self, directory)
return false
end
function set_session_id(self, id)
session_id = id
return true
end
function load(self, config)
return true
end
function save(self, config)
return true
end
function unload(self, config)
return true
end
function changes(self, config)
local rv, err = call("changes", { config = config })
if type(rv) == "table" and type(rv.changes) == "table" then
return rv.changes
elseif err then
return nil, ERRSTR[err]
else
return { }
end
end
function revert(self, config)
local _, err = call("revert", { config = config })
return (err == nil), ERRSTR[err]
end
function commit(self, config)
local _, err = call("commit", { config = config })
return (err == nil), ERRSTR[err]
end
function apply(self, rollback)
local _, err
if rollback then
local sys = require "luci.sys"
local conf = require "luci.config"
local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0
_, err = call("apply", {
timeout = (timeout > 90) and timeout or 90,
rollback = true
})
if not err then
local now = os.time()
local token = sys.uniqueid(16)
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = {
rollback = {
token = token,
session = session_id,
timeout = now + timeout
}
}
})
return token
end
else
_, err = call("changes", {})
if not err then
if type(_) == "table" and type(_.changes) == "table" then
local k, v
for k, v in pairs(_.changes) do
_, err = call("commit", { config = k })
if err then
break
end
end
end
end
if not err then
_, err = call("apply", { rollback = false })
end
end
return (err == nil), ERRSTR[err]
end
function confirm(self, token)
local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
if is_pending then
if token ~= rollback_token then
return false, "Permission denied"
end
local _, err = util.ubus("uci", "confirm", {
ubus_rpc_session = rollback_sid
})
if not err then
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = { rollback = {} }
})
end
return (err == nil), ERRSTR[err]
end
return false, "No data"
end
function rollback(self)
local is_pending, time_remaining, rollback_sid = self:rollback_pending()
if is_pending then
local _, err = util.ubus("uci", "rollback", {
ubus_rpc_session = rollback_sid
})
if not err then
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = { rollback = {} }
})
end
return (err == nil), ERRSTR[err]
end
return false, "No data"
end
function rollback_pending(self)
local rv, err = util.ubus("session", "get", {
ubus_rpc_session = "00000000000000000000000000000000",
keys = { "rollback" }
})
local now = os.time()
if type(rv) == "table" and
type(rv.values) == "table" and
type(rv.values.rollback) == "table" and
type(rv.values.rollback.token) == "string" and
type(rv.values.rollback.session) == "string" and
type(rv.values.rollback.timeout) == "number" and
rv.values.rollback.timeout > now
then
return true,
rv.values.rollback.timeout - now,
rv.values.rollback.session,
rv.values.rollback.token
end
return false, ERRSTR[err]
end
function foreach(self, config, stype, callback)
if type(callback) == "function" then
local rv, err = call("get", {
config = config,
type = stype
})
if type(rv) == "table" and type(rv.values) == "table" then
local sections = { }
local res = false
local index = 1
local _, section
for _, section in pairs(rv.values) do
section[".index"] = section[".index"] or index
sections[index] = section
index = index + 1
end
table.sort(sections, function(a, b)
return a[".index"] < b[".index"]
end)
for _, section in ipairs(sections) do
local continue = callback(section)
res = true
if continue == false then
break
end
end
return res
else
return false, ERRSTR[err] or "No data"
end
else
return false, "Invalid argument"
end
end
local function _get(self, operation, config, section, option)
if section == nil then
return nil
elseif type(option) == "string" and option:byte(1) ~= 46 then
local rv, err = call(operation, {
config = config,
section = section,
option = option
})
if type(rv) == "table" then
return rv.value or nil
elseif err then
return false, ERRSTR[err]
else
return nil
end
elseif option == nil then
local values = self:get_all(config, section)
if values then
return values[".type"], values[".name"]
else
return nil
end
else
return false, "Invalid argument"
end
end
function get(self, ...)
return _get(self, "get", ...)
end
function get_state(self, ...)
return _get(self, "state", ...)
end
function get_all(self, config, section)
local rv, err = call("get", {
config = config,
section = section
})
if type(rv) == "table" and type(rv.values) == "table" then
return rv.values
elseif err then
return false, ERRSTR[err]
else
return nil
end
end
function get_bool(self, ...)
local val = self:get(...)
return (val == "1" or val == "true" or val == "yes" or val == "on")
end
function get_first(self, config, stype, option, default)
local rv = default
self:foreach(config, stype, function(s)
local val = not option and s[".name"] or s[option]
if type(default) == "number" then
val = tonumber(val)
elseif type(default) == "boolean" then
val = (val == "1" or val == "true" or
val == "yes" or val == "on")
end
if val ~= nil then
rv = val
return false
end
end)
return rv
end
function get_list(self, config, section, option)
if config and section and option then
local val = self:get(config, section, option)
return (type(val) == "table" and val or { val })
end
return { }
end
function section(self, config, stype, name, values)
local rv, err = call("add", {
config = config,
type = stype,
name = name,
values = values
})
if type(rv) == "table" then
return rv.section
elseif err then
return false, ERRSTR[err]
else
return nil
end
end
function add(self, config, stype)
return self:section(config, stype)
end
function set(self, config, section, option, ...)
if select('#', ...) == 0 then
local sname, err = self:section(config, option, section)
return (not not sname), err
else
local _, err = call("set", {
config = config,
section = section,
values = { [option] = select(1, ...) }
})
return (err == nil), ERRSTR[err]
end
end
function set_list(self, config, section, option, value)
if section == nil or option == nil then
return false
elseif value == nil or (type(value) == "table" and #value == 0) then
return self:delete(config, section, option)
elseif type(value) == "table" then
return self:set(config, section, option, value)
else
return self:set(config, section, option, { value })
end
end
function tset(self, config, section, values)
local _, err = call("set", {
config = config,
section = section,
values = values
})
return (err == nil), ERRSTR[err]
end
function reorder(self, config, section, index)
local sections
if type(section) == "string" and type(index) == "number" then
local pos = 0
sections = { }
self:foreach(config, nil, function(s)
if pos == index then
pos = pos + 1
end
if s[".name"] ~= section then
pos = pos + 1
sections[pos] = s[".name"]
else
sections[index + 1] = section
end
end)
elseif type(section) == "table" then
sections = section
else
return false, "Invalid argument"
end
local _, err = call("order", {
config = config,
sections = sections
})
return (err == nil), ERRSTR[err]
end
function delete(self, config, section, option)
local _, err = call("delete", {
config = config,
section = section,
option = option
})
return (err == nil), ERRSTR[err]
end
function delete_all(self, config, stype, comparator)
local _, err
if type(comparator) == "table" then
_, err = call("delete", {
config = config,
type = stype,
match = comparator
})
elseif type(comparator) == "function" then
local rv = call("get", {
config = config,
type = stype
})
if type(rv) == "table" and type(rv.values) == "table" then
local sname, section
for sname, section in pairs(rv.values) do
if comparator(section) then
_, err = call("delete", {
config = config,
section = sname
})
end
end
end
elseif comparator == nil then
_, err = call("delete", {
config = config,
type = stype
})
else
return false, "Invalid argument"
end
return (err == nil), ERRSTR[err]
end