11 Copyright 2008 Steven Barth <steven@midlink.org>
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
17 http://www.apache.org/licenses/LICENSE-2.0
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
26 local os = require "os"
27 local uci = require "uci"
28 local util = require "luci.util"
29 local table = require "table"
32 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
33 local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring
34 local require, getmetatable, type = require, getmetatable, type
36 --- LuCI UCI model library.
37 -- The typical workflow for UCI is: Get a cursor instance from the
38 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
39 -- save the changes to the staging area via Cursor.save and finally
40 -- Cursor.commit the data to the actual config files.
41 -- LuCI then needs to Cursor.apply the changes so deamons etc. are
44 module "luci.model.uci"
46 --- Create a new UCI-Cursor.
52 APIVERSION = uci.APIVERSION
54 --- Create a new Cursor initialized to the state directory.
56 function cursor_state()
57 return cursor(nil, "/var/state")
62 inst_state = cursor_state()
64 local Cursor = getmetatable(inst)
66 --- Applies UCI configuration changes
67 -- @param configlist List of UCI configurations
68 -- @param command Don't apply only return the command
69 function Cursor.apply(self, configlist, command)
70 configlist = self:_affected(configlist)
71 local reloadcmd = "/sbin/luci-reload " .. table.concat(configlist, " ")
73 return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1")
77 --- Delete all sections of a given type that match certain criteria.
78 -- @param config UCI config
79 -- @param type UCI section type
80 -- @param comparator Function that will be called for each section and
81 -- returns a boolean whether to delete the current section (optional)
82 function Cursor.delete_all(self, config, stype, comparator)
85 if type(comparator) == "table" then
86 local tbl = comparator
87 comparator = function(section)
88 for k, v in pairs(tbl) do
89 if section[k] ~= v then
97 local function helper (section)
99 if not comparator or comparator(section) then
100 del[#del+1] = section[".name"]
104 self:foreach(config, stype, helper)
106 for i, j in ipairs(del) do
107 self:delete(config, j)
111 --- Create a new section and initialize it with data.
112 -- @param config UCI config
113 -- @param type UCI section type
114 -- @param name UCI section name (optional)
115 -- @param values Table of key - value pairs to initialize the section with
116 -- @return Name of created section
117 function Cursor.section(self, config, type, name, values)
120 stat = self:set(config, name, type)
122 name = self:add(config, type)
126 if stat and values then
127 stat = self:tset(config, name, values)
133 --- Updated the data of a section using data from a table.
134 -- @param config UCI config
135 -- @param section UCI section name (optional)
136 -- @param values Table of key - value pairs to update the section with
137 function Cursor.tset(self, config, section, values)
139 for k, v in pairs(values) do
140 if k:sub(1, 1) ~= "." then
141 stat = stat and self:set(config, section, k, v)
147 --- Get a boolean option and return it's value as true or false.
148 -- @param config UCI config
149 -- @param section UCI section name
150 -- @param option UCI option
152 function Cursor.get_bool(self, ...)
153 local val = self:get(...)
154 return ( val == "1" or val == "true" or val == "yes" or val == "on" )
157 --- Get an option or list and return values as table.
158 -- @param config UCI config
159 -- @param section UCI section name
160 -- @param option UCI option
162 function Cursor.get_list(self, config, section, option)
163 if config and section and option then
164 local val = self:get(config, section, option)
165 return ( type(val) == "table" and val or { val } )
170 --- Set given values as list.
171 -- @param config UCI config
172 -- @param section UCI section name
173 -- @param option UCI option
174 -- @param value UCI value
175 -- @return Boolean whether operation succeeded
176 function Cursor.set_list(self, config, section, option, value)
177 if config and section and option then
179 config, section, option,
180 ( type(value) == "table" and value or { value } )
186 -- Return a list of initscripts affected by configuration changes.
187 function Cursor._affected(self, configlist)
188 configlist = type(configlist) == "table" and configlist or {configlist}
193 -- Resolve dependencies
194 local reloadlist = {}
196 local function _resolve_deps(name)
197 local reload = {name}
200 c:foreach("ucitrack", name,
202 if section.affects then
203 for i, aff in ipairs(section.affects) do
209 for i, dep in ipairs(deps) do
210 for j, add in ipairs(_resolve_deps(dep)) do
211 reload[#reload+1] = add
218 -- Collect initscripts
219 for j, config in ipairs(configlist) do
220 for i, e in ipairs(_resolve_deps(config)) do
221 if not util.contains(reloadlist, e) then
222 reloadlist[#reloadlist+1] = e
230 --- Create a sub-state of this cursor. The sub-state is tied to the parent
231 -- curser, means it the parent unloads or loads configs, the sub state will
233 -- @return UCI state cursor tied to the parent cursor
234 function Cursor.substate(self)
235 Cursor._substates = Cursor._substates or { }
236 Cursor._substates[self] = Cursor._substates[self] or cursor_state()
237 return Cursor._substates[self]
240 local _load = Cursor.load
241 function Cursor.load(self, ...)
242 if Cursor._substates and Cursor._substates[self] then
243 _load(Cursor._substates[self], ...)
245 return _load(self, ...)
248 local _unload = Cursor.unload
249 function Cursor.unload(self, ...)
250 if Cursor._substates and Cursor._substates[self] then
251 _unload(Cursor._substates[self], ...)
253 return _unload(self, ...)
257 --- Add an anonymous section.
260 -- @param config UCI config
261 -- @param type UCI section type
262 -- @return Name of created section
264 --- Get a table of saved but uncommitted changes.
266 -- @name Cursor.changes
267 -- @param config UCI config
268 -- @return Table of changes
271 --- Commit saved changes.
273 -- @name Cursor.commit
274 -- @param config UCI config
275 -- @return Boolean whether operation succeeded
276 -- @see Cursor.revert
279 --- Deletes a section or an option.
281 -- @name Cursor.delete
282 -- @param config UCI config
283 -- @param section UCI section name
284 -- @param option UCI option (optional)
285 -- @return Boolean whether operation succeeded
287 --- Call a function for every section of a certain type.
289 -- @name Cursor.foreach
290 -- @param config UCI config
291 -- @param type UCI section type
292 -- @param callback Function to be called
293 -- @return Boolean whether operation succeeded
295 --- Get a section type or an option
298 -- @param config UCI config
299 -- @param section UCI section name
300 -- @param option UCI option (optional)
303 --- Get all sections of a config or all values of a section.
305 -- @name Cursor.get_all
306 -- @param config UCI config
307 -- @param section UCI section name (optional)
308 -- @return Table of UCI sections or table of UCI values
310 --- Manually load a config.
313 -- @param config UCI config
314 -- @return Boolean whether operation succeeded
316 -- @see Cursor.unload
318 --- Revert saved but uncommitted changes.
320 -- @name Cursor.revert
321 -- @param config UCI config
322 -- @return Boolean whether operation succeeded
323 -- @see Cursor.commit
326 --- Saves changes made to a config to make them committable.
329 -- @param config UCI config
330 -- @return Boolean whether operation succeeded
332 -- @see Cursor.unload
334 --- Set a value or create a named section.
337 -- @param config UCI config
338 -- @param section UCI section name
339 -- @param option UCI option or UCI section type
340 -- @param value UCI value or nil if you want to create a section
341 -- @return Boolean whether operation succeeded
343 --- Get the configuration directory.
345 -- @name Cursor.get_confdir
346 -- @return Configuration directory
348 --- Get the directory for uncomitted changes.
350 -- @name Cursor.get_savedir
351 -- @return Save directory
353 --- Set the configuration directory.
355 -- @name Cursor.set_confdir
356 -- @param directory UCI configuration directory
357 -- @return Boolean whether operation succeeded
359 --- Set the directory for uncommited changes.
361 -- @name Cursor.set_savedir
362 -- @param directory UCI changes directory
363 -- @return Boolean whether operation succeeded
365 --- Discard changes made to a config.
367 -- @name Cursor.unload
368 -- @param config UCI config
369 -- @return Boolean whether operation succeeded