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"
40 function cursor_state()
44 function substate(self)
49 function get_confdir(self)
53 function get_savedir(self)
57 function set_confdir(self, directory)
61 function set_savedir(self, directory)
66 function load(self, config)
70 function save(self, config)
74 function unload(self, config)
79 function changes(self, config)
80 local rv = util.ubus("uci", "changes", { config = config })
83 if type(rv) == "table" and type(rv.changes) == "table" then
84 local package, changes
85 for package, changes in pairs(rv.changes) do
89 for _, change in ipairs(changes) do
90 local operation, section, option, value = unpack(change)
91 if option and value and operation ~= "add" then
92 res[package][section] = res[package][section] or { }
94 if operation == "list-add" then
95 local v = res[package][section][option]
96 if type(v) == "table" then
99 res[package][section][option] = { v, value }
101 res[package][section][option] = { value }
104 res[package][section][option] = value or ""
107 res[package][section] = res[package][section] or {}
108 res[package][section][".type"] = option or ""
118 function revert(self, config)
119 local _, err = util.ubus("uci", "revert", { config = config })
120 return (err == nil), ERRSTR[err]
123 function commit(self, config)
124 local _, err = util.ubus("uci", "commit", { config = config })
125 return (err == nil), ERRSTR[err]
129 function apply(self, configs, command)
132 assert(not command, "Apply command not supported anymore")
134 if type(configs) == "table" then
135 for _, config in ipairs(configs) do
136 util.ubus("service", "event", {
137 type = "config.change",
138 data = { package = config }
146 function foreach(self, config, stype, callback)
147 if type(callback) == "function" then
148 local rv, err = util.ubus("uci", "get", {
153 if type(rv) == "table" and type(rv.values) == "table" then
159 for _, section in pairs(rv.values) do
160 section[".index"] = section[".index"] or index
161 sections[index] = section
165 table.sort(sections, function(a, b)
166 return a[".index"] < b[".index"]
169 for _, section in ipairs(sections) do
170 local continue = callback(section)
172 if continue == false then
178 return false, ERRSTR[err] or "No data"
181 return false, "Invalid argument"
185 local function _get(self, operation, config, section, option)
186 if section == nil then
188 elseif type(option) == "string" and option:byte(1) ~= 46 then
189 local rv, err = util.ubus("uci", operation, {
195 if type(rv) == "table" then
196 return rv.value or nil
198 return false, ERRSTR[err]
202 elseif option == nil then
203 local values = self:get_all(config, section)
205 return values[".type"], values[".name"]
210 return false, "Invalid argument"
214 function get(self, ...)
215 return _get(self, "get", ...)
218 function get_state(self, ...)
219 return _get(self, "state", ...)
222 function get_all(self, config, section)
223 local rv, err = util.ubus("uci", "get", {
228 if type(rv) == "table" and type(rv.values) == "table" then
231 return false, ERRSTR[err]
237 function get_bool(self, ...)
238 local val = self:get(...)
239 return (val == "1" or val == "true" or val == "yes" or val == "on")
242 function get_first(self, config, stype, option, default)
245 self:foreach(config, stype, function(s)
246 local val = not option and s[".name"] or s[option]
248 if type(default) == "number" then
250 elseif type(default) == "boolean" then
251 val = (val == "1" or val == "true" or
252 val == "yes" or val == "on")
264 function get_list(self, config, section, option)
265 if config and section and option then
266 local val = self:get(config, section, option)
267 return (type(val) == "table" and val or { val })
273 function section(self, config, stype, name, values)
274 local rv, err = util.ubus("uci", "add", {
281 if type(rv) == "table" then
284 return false, ERRSTR[err]
291 function add(self, config, stype)
292 return self:section(config, stype)
295 function set(self, config, section, option, value)
297 local sname, err = self:section(config, option, section)
298 return (not not sname), err
300 local _, err = util.ubus("uci", "set", {
303 values = { [option] = value }
305 return (err == nil), ERRSTR[err]
309 function set_list(self, config, section, option, value)
310 if section == nil or option == nil then
312 elseif value == nil or (type(value) == "table" and #value == 0) then
313 return self:delete(config, section, option)
314 elseif type(value) == "table" then
315 return self:set(config, section, option, value)
317 return self:set(config, section, option, { value })
321 function tset(self, config, section, values)
322 local _, err = util.ubus("uci", "set", {
327 return (err == nil), ERRSTR[err]
330 function reorder(self, config, section, index)
333 if type(section) == "string" and type(index) == "number" then
338 self:foreach(config, nil, function(s)
343 if s[".name"] ~= section then
345 sections[pos] = s[".name"]
347 sections[index + 1] = section
350 elseif type(section) == "table" then
353 return false, "Invalid argument"
356 local _, err = util.ubus("uci", "order", {
361 return (err == nil), ERRSTR[err]
365 function delete(self, config, section, option)
366 local _, err = util.ubus("uci", "delete", {
371 return (err == nil), ERRSTR[err]
374 function delete_all(self, config, stype, comparator)
376 if type(comparator) == "table" then
377 _, err = util.ubus("uci", "delete", {
382 elseif type(comparator) == "function" then
383 local rv = util.ubus("uci", "get", {
388 if type(rv) == "table" and type(rv.values) == "table" then
390 for sname, section in pairs(rv.values) do
391 if comparator(section) then
392 _, err = util.ubus("uci", "delete", {
399 elseif comparator == nil then
400 _, err = util.ubus("uci", "delete", {
405 return false, "Invalid argument"
408 return (err == nil), ERRSTR[err]
412 function apply(self, configlist, command)
413 configlist = self:_affected(configlist)
415 return { "/sbin/luci-reload", unpack(configlist) }
417 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
418 % util.shellquote(table.concat(configlist, " ")))
422 -- Return a list of initscripts affected by configuration changes.
423 function _affected(self, configlist)
424 configlist = type(configlist) == "table" and configlist or { configlist }
426 -- Resolve dependencies
427 local reloadlist = { }
429 local function _resolve_deps(name)
430 local reload = { name }
433 self:foreach("ucitrack", name,
435 if section.affects then
436 for i, aff in ipairs(section.affects) do
443 for i, dep in ipairs(deps) do
445 for j, add in ipairs(_resolve_deps(dep)) do
446 reload[#reload+1] = add
453 -- Collect initscripts
455 for j, config in ipairs(configlist) do
457 for i, e in ipairs(_resolve_deps(config)) do
458 if not util.contains(reloadlist, e) then
459 reloadlist[#reloadlist+1] = e