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")
61 local Cursor = getmetatable(cursor())
63 --- Applies UCI configuration changes
64 -- @param configlist List of UCI configurations
65 -- @param command Don't apply only return the command
66 function Cursor.apply(self, configlist, command)
67 configlist = self:_affected(configlist)
68 local reloadcmd = "/sbin/luci-reload " .. table.concat(configlist, " ")
70 return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1")
74 --- Delete all sections of a given type that match certain criteria.
75 -- @param config UCI config
76 -- @param type UCI section type
77 -- @param comparator Function that will be called for each section and
78 -- returns a boolean whether to delete the current section (optional)
79 function Cursor.delete_all(self, config, stype, comparator)
82 if type(comparator) == "table" then
83 local tbl = comparator
84 comparator = function(section)
85 for k, v in pairs(tbl) do
86 if section[k] ~= v then
94 local function helper (section)
96 if not comparator or comparator(section) then
97 del[#del+1] = section[".name"]
101 self:foreach(config, stype, helper)
103 for i, j in ipairs(del) do
104 self:delete(config, j)
108 --- Create a new section and initialize it with data.
109 -- @param config UCI config
110 -- @param type UCI section type
111 -- @param name UCI section name (optional)
112 -- @param values Table of key - value pairs to initialize the section with
113 -- @return Name of created section
114 function Cursor.section(self, config, type, name, values)
117 stat = self:set(config, name, type)
119 name = self:add(config, type)
123 if stat and values then
124 stat = self:tset(config, name, values)
130 --- Updated the data of a section using data from a table.
131 -- @param config UCI config
132 -- @param section UCI section name (optional)
133 -- @param values Table of key - value pairs to update the section with
134 function Cursor.tset(self, config, section, values)
136 for k, v in pairs(values) do
137 if k:sub(1, 1) ~= "." then
138 stat = stat and self:set(config, section, k, v)
144 --- Get a boolean option and return it's value as true or false.
145 -- @param config UCI config
146 -- @param section UCI section name
147 -- @param option UCI option
149 function Cursor.get_bool(self, ...)
150 local val = self:get(...)
151 return ( val == "1" or val == "true" or val == "yes" or val == "on" )
154 --- Get an option or list and return values as table.
155 -- @param config UCI config
156 -- @param section UCI section name
157 -- @param option UCI option
159 function Cursor.get_list(self, config, section, option)
160 if config and section and option then
161 local val = self:get(config, section, option)
162 return ( type(val) == "table" and val or { val } )
167 --- Set given values as list.
168 -- @param config UCI config
169 -- @param section UCI section name
170 -- @param option UCI option
171 -- @param value UCI value
172 -- @return Boolean whether operation succeeded
173 function Cursor.set_list(self, config, section, option, value)
174 if config and section and option then
176 config, section, option,
177 ( type(value) == "table" and value or { value } )
183 -- Return a list of initscripts affected by configuration changes.
184 function Cursor._affected(self, configlist)
185 configlist = type(configlist) == "table" and configlist or {configlist}
190 -- Resolve dependencies
191 local reloadlist = {}
193 local function _resolve_deps(name)
194 local reload = {name}
197 c:foreach("ucitrack", name,
199 if section.affects then
200 for i, aff in ipairs(section.affects) do
206 for i, dep in ipairs(deps) do
207 for j, add in ipairs(_resolve_deps(dep)) do
208 reload[#reload+1] = add
215 -- Collect initscripts
216 for j, config in ipairs(configlist) do
217 for i, e in ipairs(_resolve_deps(config)) do
218 if not util.contains(reloadlist, e) then
219 reloadlist[#reloadlist+1] = e
228 --- Add an anonymous section.
231 -- @param config UCI config
232 -- @param type UCI section type
233 -- @return Name of created section
235 --- Get a table of saved but uncommitted changes.
237 -- @name Cursor.changes
238 -- @param config UCI config
239 -- @return Table of changes
242 --- Commit saved changes.
244 -- @name Cursor.commit
245 -- @param config UCI config
246 -- @return Boolean whether operation succeeded
247 -- @see Cursor.revert
250 --- Deletes a section or an option.
252 -- @name Cursor.delete
253 -- @param config UCI config
254 -- @param section UCI section name
255 -- @param option UCI option (optional)
256 -- @return Boolean whether operation succeeded
258 --- Call a function for every section of a certain type.
260 -- @name Cursor.foreach
261 -- @param config UCI config
262 -- @param type UCI section type
263 -- @param callback Function to be called
264 -- @return Boolean whether operation succeeded
266 --- Get a section type or an option
269 -- @param config UCI config
270 -- @param section UCI section name
271 -- @param option UCI option (optional)
274 --- Get all sections of a config or all values of a section.
276 -- @name Cursor.get_all
277 -- @param config UCI config
278 -- @param section UCI section name (optional)
279 -- @return Table of UCI sections or table of UCI values
281 --- Manually load a config.
284 -- @param config UCI config
285 -- @return Boolean whether operation succeeded
287 -- @see Cursor.unload
289 --- Revert saved but uncommitted changes.
291 -- @name Cursor.revert
292 -- @param config UCI config
293 -- @return Boolean whether operation succeeded
294 -- @see Cursor.commit
297 --- Saves changes made to a config to make them committable.
300 -- @param config UCI config
301 -- @return Boolean whether operation succeeded
303 -- @see Cursor.unload
305 --- Set a value or create a named section.
308 -- @param config UCI config
309 -- @param section UCI section name
310 -- @param option UCI option or UCI section type
311 -- @param value UCI value or nil if you want to create a section
312 -- @return Boolean whether operation succeeded
314 --- Get the configuration directory.
316 -- @name Cursor.get_confdir
317 -- @return Configuration directory
319 --- Get the directory for uncomitted changes.
321 -- @name Cursor.get_savedir
322 -- @return Save directory
324 --- Set the configuration directory.
326 -- @name Cursor.set_confdir
327 -- @param directory UCI configuration directory
328 -- @return Boolean whether operation succeeded
330 --- Set the directory for uncommited changes.
332 -- @name Cursor.set_savedir
333 -- @param directory UCI changes directory
334 -- @return Boolean whether operation succeeded
336 --- Discard changes made to a config.
338 -- @name Cursor.unload
339 -- @param config UCI config
340 -- @return Boolean whether operation succeeded