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 function get(self, 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", "get", {
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_all(self, config, section)
215 local rv, err = util.ubus("uci", "get", {
220 if type(rv) == "table" and type(rv.values) == "table" then
223 return false, ERRSTR[err]
229 function get_bool(self, ...)
230 local val = self:get(...)
231 return (val == "1" or val == "true" or val == "yes" or val == "on")
234 function get_first(self, config, stype, option, default)
237 self:foreach(conf, stype, function(s)
238 local val = not option and s[".name"] or s[option]
240 if type(default) == "number" then
242 elseif type(default) == "boolean" then
243 val = (val == "1" or val == "true" or
244 val == "yes" or val == "on")
256 function get_list(self, config, section, option)
257 if config and section and option then
258 local val = self:get(config, section, option)
259 return (type(val) == "table" and val or { val })
265 function section(self, config, stype, name, values)
266 local rv, err = util.ubus("uci", "add", {
273 if type(rv) == "table" then
276 return false, ERRSTR[err]
283 function add(self, config, stype)
284 return self:section(config, stype)
287 function set(self, config, section, option, value)
289 local sname, err = self:section(config, option, section)
290 return (not not sname), err
292 local _, err = util.ubus("uci", "set", {
295 values = { [option] = value }
297 return (err == nil), ERRSTR[err]
301 function set_list(self, config, section, option, value)
302 if section == nil or option == nil then
304 elseif value == nil or (type(value) == "table" and #value == 0) then
305 return self:delete(config, section, option)
306 elseif type(value) == "table" then
307 return self:set(config, section, option, value)
309 return self:set(config, section, option, { value })
313 function tset(self, config, section, values)
314 local _, err = util.ubus("uci", "set", {
319 return (err == nil), ERRSTR[err]
322 function reorder(self, config, section, index)
325 if type(section) == "string" and type(index) == "number" then
330 self:foreach(config, nil, function(s)
335 if s[".name"] ~= section then
337 sections[pos] = s[".name"]
339 sections[index + 1] = section
342 elseif type(section) == "table" then
345 return false, "Invalid argument"
348 local _, err = util.ubus("uci", "order", {
353 return (err == nil), ERRSTR[err]
357 function delete(self, config, section, option)
358 local _, err = util.ubus("uci", "delete", {
363 return (err == nil), ERRSTR[err]
366 function delete_all(self, config, stype, comparator)
368 if type(comparator) == "table" then
369 _, err = util.ubus("uci", "delete", {
374 elseif type(comparator) == "function" then
375 local rv = util.ubus("uci", "get", {
380 if type(rv) == "table" and type(rv.values) == "table" then
382 for sname, section in pairs(rv.values) do
383 if comparator(section) then
384 _, err = util.ubus("uci", "delete", {
391 elseif comparator == nil then
392 _, err = util.ubus("uci", "delete", {
397 return false, "Invalid argument"
400 return (err == nil), ERRSTR[err]
404 function apply(self, configlist, command)
405 configlist = self:_affected(configlist)
407 return { "/sbin/luci-reload", unpack(configlist) }
409 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
410 % table.concat(configlist, " "))
414 -- Return a list of initscripts affected by configuration changes.
415 function _affected(self, configlist)
416 configlist = type(configlist) == "table" and configlist or { configlist }
418 -- Resolve dependencies
419 local reloadlist = { }
421 local function _resolve_deps(name)
422 local reload = { name }
425 self:foreach("ucitrack", name,
427 if section.affects then
428 for i, aff in ipairs(section.affects) do
435 for i, dep in ipairs(deps) do
437 for j, add in ipairs(_resolve_deps(dep)) do
438 reload[#reload+1] = add
445 -- Collect initscripts
447 for j, config in ipairs(configlist) do
449 for i, e in ipairs(_resolve_deps(config)) do
450 if not util.contains(reloadlist, e) then
451 reloadlist[#reloadlist+1] = e