577c6cde08eaa6e10887c97b26fed000f3289070
[project/luci.git] / modules / luci-base / luasrc / model / uci.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local os    = require "os"
5 local uci   = require "uci"
6 local util  = require "luci.util"
7 local table = require "table"
8
9
10 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
11 local require, getmetatable = require, getmetatable
12 local error, pairs, ipairs = error, pairs, ipairs
13 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
14
15 -- The typical workflow for UCI is:  Get a cursor instance from the
16 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
17 -- save the changes to the staging area via Cursor.save and finally
18 -- Cursor.commit the data to the actual config files.
19 -- LuCI then needs to Cursor.apply the changes so deamons etc. are
20 -- reloaded.
21 module "luci.model.uci"
22
23 cursor = uci.cursor
24
25 APIVERSION = uci.APIVERSION
26
27 function cursor_state()
28         return cursor(nil, "/var/state")
29 end
30
31
32 inst = cursor()
33 inst_state = cursor_state()
34
35 local Cursor = getmetatable(inst)
36
37 function Cursor.apply(self, configlist, command)
38         configlist = self:_affected(configlist)
39         if command then
40                 return { "/sbin/luci-reload", unpack(configlist) }
41         else
42                 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
43                         % table.concat(configlist, " "))
44         end
45 end
46
47
48 -- returns a boolean whether to delete the current section (optional)
49 function Cursor.delete_all(self, config, stype, comparator)
50         local del = {}
51
52         if type(comparator) == "table" then
53                 local tbl = comparator
54                 comparator = function(section)
55                         for k, v in pairs(tbl) do
56                                 if section[k] ~= v then
57                                         return false
58                                 end
59                         end
60                         return true
61                 end
62         end
63
64         local function helper (section)
65
66                 if not comparator or comparator(section) then
67                         del[#del+1] = section[".name"]
68                 end
69         end
70
71         self:foreach(config, stype, helper)
72
73         for i, j in ipairs(del) do
74                 self:delete(config, j)
75         end
76 end
77
78 function Cursor.section(self, config, type, name, values)
79         local stat = true
80         if name then
81                 stat = self:set(config, name, type)
82         else
83                 name = self:add(config, type)
84                 stat = name and true
85         end
86
87         if stat and values then
88                 stat = self:tset(config, name, values)
89         end
90
91         return stat and name
92 end
93
94 function Cursor.tset(self, config, section, values)
95         local stat = true
96         for k, v in pairs(values) do
97                 if k:sub(1, 1) ~= "." then
98                         stat = stat and self:set(config, section, k, v)
99                 end
100         end
101         return stat
102 end
103
104 function Cursor.get_bool(self, ...)
105         local val = self:get(...)
106         return ( val == "1" or val == "true" or val == "yes" or val == "on" )
107 end
108
109 function Cursor.get_list(self, config, section, option)
110         if config and section and option then
111                 local val = self:get(config, section, option)
112                 return ( type(val) == "table" and val or { val } )
113         end
114         return {}
115 end
116
117 function Cursor.get_first(self, conf, stype, opt, def)
118         local rv = def
119
120         self:foreach(conf, stype,
121                 function(s)
122                         local val = not opt and s['.name'] or s[opt]
123
124                         if type(def) == "number" then
125                                 val = tonumber(val)
126                         elseif type(def) == "boolean" then
127                                 val = (val == "1" or val == "true" or
128                                        val == "yes" or val == "on")
129                         end
130
131                         if val ~= nil then
132                                 rv = val
133                                 return false
134                         end
135                 end)
136
137         return rv
138 end
139
140 function Cursor.set_list(self, config, section, option, value)
141         if config and section and option then
142                 if not value or #value == 0 then
143                         return self:delete(config, section, option)
144                 end
145                 return self:set(
146                         config, section, option,
147                         ( type(value) == "table" and value or { value } )
148                 )
149         end
150         return false
151 end
152
153 -- Return a list of initscripts affected by configuration changes.
154 function Cursor._affected(self, configlist)
155         configlist = type(configlist) == "table" and configlist or {configlist}
156
157         local c = cursor()
158         c:load("ucitrack")
159
160         -- Resolve dependencies
161         local reloadlist = {}
162
163         local function _resolve_deps(name)
164                 local reload = {name}
165                 local deps = {}
166
167                 c:foreach("ucitrack", name,
168                         function(section)
169                                 if section.affects then
170                                         for i, aff in ipairs(section.affects) do
171                                                 deps[#deps+1] = aff
172                                         end
173                                 end
174                         end)
175
176                 for i, dep in ipairs(deps) do
177                         for j, add in ipairs(_resolve_deps(dep)) do
178                                 reload[#reload+1] = add
179                         end
180                 end
181
182                 return reload
183         end
184
185         -- Collect initscripts
186         for j, config in ipairs(configlist) do
187                 for i, e in ipairs(_resolve_deps(config)) do
188                         if not util.contains(reloadlist, e) then
189                                 reloadlist[#reloadlist+1] = e
190                         end
191                 end
192         end
193
194         return reloadlist
195 end
196
197 -- curser, means it the parent unloads or loads configs, the sub state will
198 -- do so as well.
199 function Cursor.substate(self)
200         Cursor._substates = Cursor._substates or { }
201         Cursor._substates[self] = Cursor._substates[self] or cursor_state()
202         return Cursor._substates[self]
203 end
204
205 local _load = Cursor.load
206 function Cursor.load(self, ...)
207         if Cursor._substates and Cursor._substates[self] then
208                 _load(Cursor._substates[self], ...)
209         end
210         return _load(self, ...)
211 end
212
213 local _unload = Cursor.unload
214 function Cursor.unload(self, ...)
215         if Cursor._substates and Cursor._substates[self] then
216                 _unload(Cursor._substates[self], ...)
217         end
218         return _unload(self, ...)
219 end
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236