luci-base: switch to ubus uci operations

Switch luci.model.uci to use ubus uci calls instead of driving libuci-lua
directly.

This prepares support for more advanced features such as per-session change
isolation and configuration rollback on errors.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2018-03-14 01:23:50 +01:00
parent f5671b420a
commit 4024d4f224

View file

@ -2,13 +2,12 @@
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local os = require "os" local os = require "os"
local uci = require "uci"
local util = require "luci.util" local util = require "luci.util"
local table = require "table" local table = require "table"
local setmetatable, rawget, rawset = setmetatable, rawget, rawset local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local require, getmetatable = require, getmetatable local require, getmetatable, assert = require, getmetatable, assert
local error, pairs, ipairs = error, pairs, ipairs local error, pairs, ipairs = error, pairs, ipairs
local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
@ -20,110 +19,227 @@ local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
-- reloaded. -- reloaded.
module "luci.model.uci" module "luci.model.uci"
cursor = uci.cursor local ERRSTR = {
"Invalid command",
"Invalid argument",
"Method not found",
"Entry not found",
"No data",
"Permission denied",
"Timeout",
"Not supported",
"Unknown error",
"Connection failed"
}
APIVERSION = uci.APIVERSION
function cursor()
return _M
end
function cursor_state() function cursor_state()
return cursor(nil, "/var/state") return _M
end
function substate(self)
return self
end end
inst = cursor() function get_confdir(self)
inst_state = cursor_state() return "/etc/config"
local Cursor = getmetatable(inst)
function Cursor.apply(self, configlist, command)
configlist = self:_affected(configlist)
if command then
return { "/sbin/luci-reload", unpack(configlist) }
else
return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
% table.concat(configlist, " "))
end
end end
function get_savedir(self)
return "/tmp/.uci"
end
-- returns a boolean whether to delete the current section (optional) function set_confdir(self, directory)
function Cursor.delete_all(self, config, stype, comparator)
local del = {}
if type(comparator) == "table" then
local tbl = comparator
comparator = function(section)
for k, v in pairs(tbl) do
if section[k] ~= v then
return false return false
end end
function set_savedir(self, directory)
return false
end end
function load(self, config)
return true return true
end end
function save(self, config)
return true
end end
local function helper (section) function unload(self, config)
return true
if not comparator or comparator(section) then
del[#del+1] = section[".name"]
end
end end
self:foreach(config, stype, helper)
for i, j in ipairs(del) do function changes(self, config)
self:delete(config, j) local rv = util.ubus("uci", "changes", { config = config })
end local res = {}
end
function Cursor.section(self, config, type, name, values) if type(rv) == "table" and type(rv.changes) == "table" then
local stat = true local package, changes
if name then for package, changes in pairs(rv.changes) do
stat = self:set(config, name, type) res[package] = {}
local _, change
for _, change in ipairs(changes) do
local operation, section, option, value = unpack(change)
if option and value and operation ~= "add" then
res[package][section] = res[package][section] or { }
if operation == "list-add" then
local v = res[package][section][option]
if type(v) == "table" then
v[#v+1] = value or ""
elseif v ~= nil then
res[package][section][option] = { v, value }
else else
name = self:add(config, type) res[package][section][option] = { value }
stat = name and true end
else
res[package][section][option] = value or ""
end
else
res[package][section] = res[package][section] or {}
res[package][section][".type"] = option or ""
end
end
end
end end
if stat and values then return res
stat = self:tset(config, name, values)
end end
return stat and name
function revert(self, config)
local _, err = util.ubus("uci", "revert", { config = config })
return (err == nil), ERRSTR[err]
end end
function Cursor.tset(self, config, section, values) function commit(self, config)
local stat = true local _, err = util.ubus("uci", "commit", { config = config })
for k, v in pairs(values) do return (err == nil), ERRSTR[err]
if k:sub(1, 1) ~= "." then
stat = stat and self:set(config, section, k, v)
end
end
return stat
end end
function Cursor.get_bool(self, ...) --[[
function apply(self, configs, command)
local _, config
assert(not command, "Apply command not supported anymore")
if type(configs) == "table" then
for _, config in ipairs(configs) do
util.ubus("service", "event", {
type = "config.change",
data = { package = config }
})
end
end
end
]]
function foreach(self, config, stype, callback)
if type(callback) == "function" then
local rv, err = util.ubus("uci", "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
function get(self, config, section, option)
if section == nil then
return nil
elseif type(option) == "string" and option:byte(1) ~= 46 then
local rv, err = util.ubus("uci", "get", {
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_all(self, config, section)
local rv, err = util.ubus("uci", "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(...) local val = self:get(...)
return (val == "1" or val == "true" or val == "yes" or val == "on") return (val == "1" or val == "true" or val == "yes" or val == "on")
end end
function Cursor.get_list(self, config, section, option) function get_first(self, config, stype, option, default)
if config and section and option then local rv = default
local val = self:get(config, section, option)
return ( type(val) == "table" and val or { val } )
end
return {}
end
function Cursor.get_first(self, conf, stype, opt, def) self:foreach(conf, stype, function(s)
local rv = def local val = not option and s[".name"] or s[option]
self:foreach(conf, stype, if type(default) == "number" then
function(s)
local val = not opt and s['.name'] or s[opt]
if type(def) == "number" then
val = tonumber(val) val = tonumber(val)
elseif type(def) == "boolean" then elseif type(default) == "boolean" then
val = (val == "1" or val == "true" or val = (val == "1" or val == "true" or
val == "yes" or val == "on") val == "yes" or val == "on")
end end
@ -137,26 +253,168 @@ function Cursor.get_first(self, conf, stype, opt, def)
return rv return rv
end end
function Cursor.set_list(self, config, section, option, value) function get_list(self, config, section, option)
if config and section and option then if config and section and option then
if not value or #value == 0 then local val = self:get(config, section, option)
return self:delete(config, section, option) return (type(val) == "table" and val or { val })
end end
return self:set( return { }
config, section, option,
( type(value) == "table" and value or { value } )
)
end end
function section(self, config, stype, name, values)
local rv, err = util.ubus("uci", "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, value)
if value == nil then
local sname, err = self:section(config, option, section)
return (not not sname), err
else
local _, err = util.ubus("uci", "set", {
config = config,
section = section,
values = { [option] = value }
})
return (err == nil), ERRSTR[err]
end
end
function set_list(self, config, section, option, value)
if section == nil or option == nil then
return false 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 = util.ubus("uci", "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 = util.ubus("uci", "order", {
config = config,
sections = sections
})
return (err == nil), ERRSTR[err]
end
function delete(self, config, section, option)
local _, err = util.ubus("uci", "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 = util.ubus("uci", "delete", {
config = config,
type = stype,
match = comparator
})
elseif type(comparator) == "function" then
local rv = util.ubus("uci", "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 = util.ubus("uci", "delete", {
config = config,
section = sname
})
end
end
end
elseif comparator == nil then
_, err = util.ubus("uci", "delete", {
config = config,
type = stype
})
else
return false, "Invalid argument"
end
return (err == nil), ERRSTR[err]
end
function apply(self, configlist, command)
configlist = self:_affected(configlist)
if command then
return { "/sbin/luci-reload", unpack(configlist) }
else
return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
% table.concat(configlist, " "))
end
end end
-- Return a list of initscripts affected by configuration changes. -- Return a list of initscripts affected by configuration changes.
function Cursor._affected(self, configlist) function _affected(self, configlist)
configlist = type(configlist) == "table" and configlist or { configlist } configlist = type(configlist) == "table" and configlist or { configlist }
local c = cursor()
c:load("ucitrack")
-- Resolve dependencies -- Resolve dependencies
local reloadlist = { } local reloadlist = { }
@ -164,7 +422,7 @@ function Cursor._affected(self, configlist)
local reload = { name } local reload = { name }
local deps = { } local deps = { }
c:foreach("ucitrack", name, self:foreach("ucitrack", name,
function(section) function(section)
if section.affects then if section.affects then
for i, aff in ipairs(section.affects) do for i, aff in ipairs(section.affects) do
@ -173,7 +431,9 @@ function Cursor._affected(self, configlist)
end end
end) end)
local i, dep
for i, dep in ipairs(deps) do for i, dep in ipairs(deps) do
local j, add
for j, add in ipairs(_resolve_deps(dep)) do for j, add in ipairs(_resolve_deps(dep)) do
reload[#reload+1] = add reload[#reload+1] = add
end end
@ -183,7 +443,9 @@ function Cursor._affected(self, configlist)
end end
-- Collect initscripts -- Collect initscripts
local j, config
for j, config in ipairs(configlist) do for j, config in ipairs(configlist) do
local i, e
for i, e in ipairs(_resolve_deps(config)) do for i, e in ipairs(_resolve_deps(config)) do
if not util.contains(reloadlist, e) then if not util.contains(reloadlist, e) then
reloadlist[#reloadlist+1] = e reloadlist[#reloadlist+1] = e
@ -193,44 +455,3 @@ function Cursor._affected(self, configlist)
return reloadlist return reloadlist
end end
-- curser, means it the parent unloads or loads configs, the sub state will
-- do so as well.
function Cursor.substate(self)
Cursor._substates = Cursor._substates or { }
Cursor._substates[self] = Cursor._substates[self] or cursor_state()
return Cursor._substates[self]
end
local _load = Cursor.load
function Cursor.load(self, ...)
if Cursor._substates and Cursor._substates[self] then
_load(Cursor._substates[self], ...)
end
return _load(self, ...)
end
local _unload = Cursor.unload
function Cursor.unload(self, ...)
if Cursor._substates and Cursor._substates[self] then
_unload(Cursor._substates[self], ...)
end
return _unload(self, ...)
end