c927b4ca5ab0a48a956b8c41d00f78acbe61b1cd
[project/luci.git] / libs / uci / luasrc / model / uci.lua
1 --[[
2 LuCI - UCI model
3
4 Description:
5 Generalized UCI model
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
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
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
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.
24
25 ]]--
26 local os    = require "os"
27 local uci   = require "uci"
28 local util  = require "luci.util"
29 local table = require "table"
30
31
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
35
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
42 -- reloaded.
43 -- @cstyle      instance
44 module "luci.model.uci"
45
46 --- Create a new UCI-Cursor.
47 -- @class function
48 -- @name cursor
49 -- @return      UCI-Cursor
50 cursor = uci.cursor
51
52 APIVERSION = uci.APIVERSION
53
54 --- Create a new Cursor initialized to the state directory.
55 -- @return UCI cursor
56 function cursor_state()
57         return cursor(nil, "/var/state")
58 end
59
60
61 local Cursor = getmetatable(cursor())
62
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, " ")
69
70         return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1")
71 end
72
73
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)
80         local del = {}
81
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
87                                         return false
88                                 end
89                         end
90                         return true
91                 end
92         end
93
94         local function helper (section)
95
96                 if not comparator or comparator(section) then
97                         del[#del+1] = section[".name"]
98                 end
99         end
100
101         self:foreach(config, stype, helper)
102
103         for i, j in ipairs(del) do
104                 self:delete(config, j)
105         end
106 end
107
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)
115         local stat = true
116         if name then
117                 stat = self:set(config, name, type)
118         else
119                 name = self:add(config, type)
120                 stat = name and true
121         end
122
123         if stat and values then
124                 stat = self:tset(config, name, values)
125         end
126
127         return stat and name
128 end
129
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)
135         local stat = true
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)
139                 end
140         end
141         return stat
142 end
143
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
148 -- @return                      Boolean
149 function Cursor.get_bool(self, ...)
150         local val = self:get(...)
151         return ( val == "1" or val == "true" or val == "yes" or val == "on" )
152 end
153
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
158 -- @return                      UCI value
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 } )
163         end
164         return nil
165 end
166
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
175                 return self:set(
176                         config, section, option,
177                         ( type(value) == "table" and value or { value } )
178                 )
179         end
180         return false
181 end
182
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}
186
187         local c = cursor()
188         c:load("ucitrack")
189
190         -- Resolve dependencies
191         local reloadlist = {}
192
193         local function _resolve_deps(name)
194                 local reload = {name}
195                 local deps = {}
196
197                 c:foreach("ucitrack", name,
198                         function(section)
199                                 if section.affects then
200                                         for i, aff in ipairs(section.affects) do
201                                                 deps[#deps+1] = aff
202                                         end
203                                 end
204                         end)
205
206                 for i, dep in ipairs(deps) do
207                         for j, add in ipairs(_resolve_deps(dep)) do
208                                 reload[#reload+1] = add
209                         end
210                 end
211
212                 return reload
213         end
214
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
220                         end
221                 end
222         end
223
224         return reloadlist
225 end
226
227
228 --- Add an anonymous section.
229 -- @class function
230 -- @name Cursor.add
231 -- @param config        UCI config
232 -- @param type          UCI section type
233 -- @return                      Name of created section
234
235 --- Get a table of saved but uncommitted changes.
236 -- @class function
237 -- @name Cursor.changes
238 -- @param config        UCI config
239 -- @return                      Table of changes
240 -- @see Cursor.save
241
242 --- Commit saved changes.
243 -- @class function
244 -- @name Cursor.commit
245 -- @param config        UCI config
246 -- @return                      Boolean whether operation succeeded
247 -- @see Cursor.revert
248 -- @see Cursor.save
249
250 --- Deletes a section or an option.
251 -- @class function
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
257
258 --- Call a function for every section of a certain type.
259 -- @class function
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
265
266 --- Get a section type or an option
267 -- @class function
268 -- @name Cursor.get
269 -- @param config        UCI config
270 -- @param section       UCI section name
271 -- @param option        UCI option (optional)
272 -- @return                      UCI value
273
274 --- Get all sections of a config or all values of a section.
275 -- @class function
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
280
281 --- Manually load a config.
282 -- @class function
283 -- @name Cursor.load
284 -- @param config        UCI config
285 -- @return                      Boolean whether operation succeeded
286 -- @see Cursor.save
287 -- @see Cursor.unload
288
289 --- Revert saved but uncommitted changes.
290 -- @class function
291 -- @name Cursor.revert
292 -- @param config        UCI config
293 -- @return                      Boolean whether operation succeeded
294 -- @see Cursor.commit
295 -- @see Cursor.save
296
297 --- Saves changes made to a config to make them committable.
298 -- @class function
299 -- @name Cursor.save
300 -- @param config        UCI config
301 -- @return                      Boolean whether operation succeeded
302 -- @see Cursor.load
303 -- @see Cursor.unload
304
305 --- Set a value or create a named section.
306 -- @class function
307 -- @name Cursor.set
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
313
314 --- Get the configuration directory.
315 -- @class function
316 -- @name Cursor.get_confdir
317 -- @return                      Configuration directory
318
319 --- Get the directory for uncomitted changes.
320 -- @class function
321 -- @name Cursor.get_savedir
322 -- @return                      Save directory
323
324 --- Set the configuration directory.
325 -- @class function
326 -- @name Cursor.set_confdir
327 -- @param directory     UCI configuration directory
328 -- @return                      Boolean whether operation succeeded
329
330 --- Set the directory for uncommited changes.
331 -- @class function
332 -- @name Cursor.set_savedir
333 -- @param directory     UCI changes directory
334 -- @return                      Boolean whether operation succeeded
335
336 --- Discard changes made to a config.
337 -- @class function
338 -- @name Cursor.unload
339 -- @param config        UCI config
340 -- @return                      Boolean whether operation succeeded
341 -- @see Cursor.load
342 -- @see Cursor.save