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 = error, pairs, ipairs
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 value 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]
147 function apply(self, configs, command)
150 assert(not command, "Apply command not supported anymore")
152 if type(configs) == "table" then
153 for _, config in ipairs(configs) do
154 call("service", "event", {
155 type = "config.change",
156 data = { package = config }
164 function foreach(self, config, stype, callback)
165 if type(callback) == "function" then
166 local rv, err = call("get", {
171 if type(rv) == "table" and type(rv.values) == "table" then
177 for _, section in pairs(rv.values) do
178 section[".index"] = section[".index"] or index
179 sections[index] = section
183 table.sort(sections, function(a, b)
184 return a[".index"] < b[".index"]
187 for _, section in ipairs(sections) do
188 local continue = callback(section)
190 if continue == false then
196 return false, ERRSTR[err] or "No data"
199 return false, "Invalid argument"
203 local function _get(self, operation, config, section, option)
204 if section == nil then
206 elseif type(option) == "string" and option:byte(1) ~= 46 then
207 local rv, err = call(operation, {
213 if type(rv) == "table" then
214 return rv.value or nil
216 return false, ERRSTR[err]
220 elseif option == nil then
221 local values = self:get_all(config, section)
223 return values[".type"], values[".name"]
228 return false, "Invalid argument"
232 function get(self, ...)
233 return _get(self, "get", ...)
236 function get_state(self, ...)
237 return _get(self, "state", ...)
240 function get_all(self, config, section)
241 local rv, err = call("get", {
246 if type(rv) == "table" and type(rv.values) == "table" then
249 return false, ERRSTR[err]
255 function get_bool(self, ...)
256 local val = self:get(...)
257 return (val == "1" or val == "true" or val == "yes" or val == "on")
260 function get_first(self, config, stype, option, default)
263 self:foreach(config, stype, function(s)
264 local val = not option and s[".name"] or s[option]
266 if type(default) == "number" then
268 elseif type(default) == "boolean" then
269 val = (val == "1" or val == "true" or
270 val == "yes" or val == "on")
282 function get_list(self, config, section, option)
283 if config and section and option then
284 local val = self:get(config, section, option)
285 return (type(val) == "table" and val or { val })
291 function section(self, config, stype, name, values)
292 local rv, err = call("add", {
299 if type(rv) == "table" then
302 return false, ERRSTR[err]
309 function add(self, config, stype)
310 return self:section(config, stype)
313 function set(self, config, section, option, value)
315 local sname, err = self:section(config, option, section)
316 return (not not sname), err
318 local _, err = call("set", {
321 values = { [option] = value }
323 return (err == nil), ERRSTR[err]
327 function set_list(self, config, section, option, value)
328 if section == nil or option == nil then
330 elseif value == nil or (type(value) == "table" and #value == 0) then
331 return self:delete(config, section, option)
332 elseif type(value) == "table" then
333 return self:set(config, section, option, value)
335 return self:set(config, section, option, { value })
339 function tset(self, config, section, values)
340 local _, err = call("set", {
345 return (err == nil), ERRSTR[err]
348 function reorder(self, config, section, index)
351 if type(section) == "string" and type(index) == "number" then
356 self:foreach(config, nil, function(s)
361 if s[".name"] ~= section then
363 sections[pos] = s[".name"]
365 sections[index + 1] = section
368 elseif type(section) == "table" then
371 return false, "Invalid argument"
374 local _, err = call("order", {
379 return (err == nil), ERRSTR[err]
383 function delete(self, config, section, option)
384 local _, err = call("delete", {
389 return (err == nil), ERRSTR[err]
392 function delete_all(self, config, stype, comparator)
394 if type(comparator) == "table" then
395 _, err = call("delete", {
400 elseif type(comparator) == "function" then
401 local rv = call("get", {
406 if type(rv) == "table" and type(rv.values) == "table" then
408 for sname, section in pairs(rv.values) do
409 if comparator(section) then
410 _, err = call("delete", {
417 elseif comparator == nil then
418 _, err = call("delete", {
423 return false, "Invalid argument"
426 return (err == nil), ERRSTR[err]
430 function apply(self, configlist, command)
431 configlist = self:_affected(configlist)
433 return { "/sbin/luci-reload", unpack(configlist) }
435 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
436 % util.shellquote(table.concat(configlist, " ")))
440 -- Return a list of initscripts affected by configuration changes.
441 function _affected(self, configlist)
442 configlist = type(configlist) == "table" and configlist or { configlist }
444 -- Resolve dependencies
445 local reloadlist = { }
447 local function _resolve_deps(name)
448 local reload = { name }
451 self:foreach("ucitrack", name,
453 if section.affects then
454 for i, aff in ipairs(section.affects) do
461 for i, dep in ipairs(deps) do
463 for j, add in ipairs(_resolve_deps(dep)) do
464 reload[#reload+1] = add
471 -- Collect initscripts
473 for j, config in ipairs(configlist) do
475 for i, e in ipairs(_resolve_deps(config)) do
476 if not util.contains(reloadlist, e) then
477 reloadlist[#reloadlist+1] = e