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 require, getmetatable = require, getmetatable
34 local error, pairs, ipairs = error, pairs, ipairs
35 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
37 --- LuCI UCI model library.
38 -- The typical workflow for UCI is: Get a cursor instance from the
39 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
40 -- save the changes to the staging area via Cursor.save and finally
41 -- Cursor.commit the data to the actual config files.
42 -- LuCI then needs to Cursor.apply the changes so deamons etc. are
45 module "luci.model.uci"
47 --- Create a new UCI-Cursor.
53 APIVERSION = uci.APIVERSION
55 --- Create a new Cursor initialized to the state directory.
57 function cursor_state()
58 return cursor(nil, "/var/state")
63 inst_state = cursor_state()
65 local Cursor = getmetatable(inst)
67 --- Applies UCI configuration changes
68 -- @param configlist List of UCI configurations
69 -- @param command Don't apply only return the command
70 function Cursor.apply(self, configlist, command)
71 configlist = self:_affected(configlist)
73 return { "/sbin/luci-reload", unpack(configlist) }
75 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
76 % table.concat(configlist, " "))
81 --- Delete all sections of a given type that match certain criteria.
82 -- @param config UCI config
83 -- @param type UCI section type
84 -- @param comparator Function that will be called for each section and
85 -- returns a boolean whether to delete the current section (optional)
86 function Cursor.delete_all(self, config, stype, comparator)
89 if type(comparator) == "table" then
90 local tbl = comparator
91 comparator = function(section)
92 for k, v in pairs(tbl) do
93 if section[k] ~= v then
101 local function helper (section)
103 if not comparator or comparator(section) then
104 del[#del+1] = section[".name"]
108 self:foreach(config, stype, helper)
110 for i, j in ipairs(del) do
111 self:delete(config, j)
115 --- Create a new section and initialize it with data.
116 -- @param config UCI config
117 -- @param type UCI section type
118 -- @param name UCI section name (optional)
119 -- @param values Table of key - value pairs to initialize the section with
120 -- @return Name of created section
121 function Cursor.section(self, config, type, name, values)
124 stat = self:set(config, name, type)
126 name = self:add(config, type)
130 if stat and values then
131 stat = self:tset(config, name, values)
137 --- Updated the data of a section using data from a table.
138 -- @param config UCI config
139 -- @param section UCI section name (optional)
140 -- @param values Table of key - value pairs to update the section with
141 function Cursor.tset(self, config, section, values)
143 for k, v in pairs(values) do
144 if k:sub(1, 1) ~= "." then
145 stat = stat and self:set(config, section, k, v)
151 --- Get a boolean option and return it's value as true or false.
152 -- @param config UCI config
153 -- @param section UCI section name
154 -- @param option UCI option
156 function Cursor.get_bool(self, ...)
157 local val = self:get(...)
158 return ( val == "1" or val == "true" or val == "yes" or val == "on" )
161 --- Get an option or list and return values as table.
162 -- @param config UCI config
163 -- @param section UCI section name
164 -- @param option UCI option
166 function Cursor.get_list(self, config, section, option)
167 if config and section and option then
168 local val = self:get(config, section, option)
169 return ( type(val) == "table" and val or { val } )
174 --- Get the given option from the first section with the given type.
175 -- @param config UCI config
176 -- @param type UCI section type
177 -- @param option UCI option (optional)
178 -- @param default Default value (optional)
180 function Cursor.get_first(self, conf, stype, opt, def)
183 self:foreach(conf, stype,
185 local val = not opt and s['.name'] or s[opt]
187 if type(def) == "number" then
189 elseif type(def) == "boolean" then
190 val = (val == "1" or val == "true" or
191 val == "yes" or val == "on")
203 --- Set given values as list.
204 -- @param config UCI config
205 -- @param section UCI section name
206 -- @param option UCI option
207 -- @param value UCI value
208 -- @return Boolean whether operation succeeded
209 function Cursor.set_list(self, config, section, option, value)
210 if config and section and option then
212 config, section, option,
213 ( type(value) == "table" and value or { value } )
219 -- Return a list of initscripts affected by configuration changes.
220 function Cursor._affected(self, configlist)
221 configlist = type(configlist) == "table" and configlist or {configlist}
226 -- Resolve dependencies
227 local reloadlist = {}
229 local function _resolve_deps(name)
230 local reload = {name}
233 c:foreach("ucitrack", name,
235 if section.affects then
236 for i, aff in ipairs(section.affects) do
242 for i, dep in ipairs(deps) do
243 for j, add in ipairs(_resolve_deps(dep)) do
244 reload[#reload+1] = add
251 -- Collect initscripts
252 for j, config in ipairs(configlist) do
253 for i, e in ipairs(_resolve_deps(config)) do
254 if not util.contains(reloadlist, e) then
255 reloadlist[#reloadlist+1] = e
263 --- Create a sub-state of this cursor. The sub-state is tied to the parent
264 -- curser, means it the parent unloads or loads configs, the sub state will
266 -- @return UCI state cursor tied to the parent cursor
267 function Cursor.substate(self)
268 Cursor._substates = Cursor._substates or { }
269 Cursor._substates[self] = Cursor._substates[self] or cursor_state()
270 return Cursor._substates[self]
273 local _load = Cursor.load
274 function Cursor.load(self, ...)
275 if Cursor._substates and Cursor._substates[self] then
276 _load(Cursor._substates[self], ...)
278 return _load(self, ...)
281 local _unload = Cursor.unload
282 function Cursor.unload(self, ...)
283 if Cursor._substates and Cursor._substates[self] then
284 _unload(Cursor._substates[self], ...)
286 return _unload(self, ...)
290 --- Add an anonymous section.
293 -- @param config UCI config
294 -- @param type UCI section type
295 -- @return Name of created section
297 --- Get a table of saved but uncommitted changes.
299 -- @name Cursor.changes
300 -- @param config UCI config
301 -- @return Table of changes
304 --- Commit saved changes.
306 -- @name Cursor.commit
307 -- @param config UCI config
308 -- @return Boolean whether operation succeeded
309 -- @see Cursor.revert
312 --- Deletes a section or an option.
314 -- @name Cursor.delete
315 -- @param config UCI config
316 -- @param section UCI section name
317 -- @param option UCI option (optional)
318 -- @return Boolean whether operation succeeded
320 --- Call a function for every section of a certain type.
322 -- @name Cursor.foreach
323 -- @param config UCI config
324 -- @param type UCI section type
325 -- @param callback Function to be called
326 -- @return Boolean whether operation succeeded
328 --- Get a section type or an option
331 -- @param config UCI config
332 -- @param section UCI section name
333 -- @param option UCI option (optional)
336 --- Get all sections of a config or all values of a section.
338 -- @name Cursor.get_all
339 -- @param config UCI config
340 -- @param section UCI section name (optional)
341 -- @return Table of UCI sections or table of UCI values
343 --- Manually load a config.
346 -- @param config UCI config
347 -- @return Boolean whether operation succeeded
349 -- @see Cursor.unload
351 --- Revert saved but uncommitted changes.
353 -- @name Cursor.revert
354 -- @param config UCI config
355 -- @return Boolean whether operation succeeded
356 -- @see Cursor.commit
359 --- Saves changes made to a config to make them committable.
362 -- @param config UCI config
363 -- @return Boolean whether operation succeeded
365 -- @see Cursor.unload
367 --- Set a value or create a named section.
370 -- @param config UCI config
371 -- @param section UCI section name
372 -- @param option UCI option or UCI section type
373 -- @param value UCI value or nil if you want to create a section
374 -- @return Boolean whether operation succeeded
376 --- Get the configuration directory.
378 -- @name Cursor.get_confdir
379 -- @return Configuration directory
381 --- Get the directory for uncomitted changes.
383 -- @name Cursor.get_savedir
384 -- @return Save directory
386 --- Set the configuration directory.
388 -- @name Cursor.set_confdir
389 -- @param directory UCI configuration directory
390 -- @return Boolean whether operation succeeded
392 --- Set the directory for uncommited changes.
394 -- @name Cursor.set_savedir
395 -- @param directory UCI changes directory
396 -- @return Boolean whether operation succeeded
398 --- Discard changes made to a config.
400 -- @name Cursor.unload
401 -- @param config UCI config
402 -- @return Boolean whether operation succeeded