treewide: Fix typos in comments
[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 util = require "luci.util"
6 local table = require "table"
7
8
9 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
10 local require, getmetatable, assert = require, getmetatable, assert
11 local error, pairs, ipairs, select = error, pairs, ipairs, select
12 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
13
14 -- The typical workflow for UCI is: Get a cursor instance from the
15 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
16 -- save the changes to the staging area via Cursor.save and finally
17 -- Cursor.commit the data to the actual config files.
18 -- LuCI then needs to Cursor.apply the changes so daemons etc. are
19 -- reloaded.
20 module "luci.model.uci"
21
22 local ERRSTR = {
23 "Invalid command",
24 "Invalid argument",
25 "Method not found",
26 "Entry not found",
27 "No data",
28 "Permission denied",
29 "Timeout",
30 "Not supported",
31 "Unknown error",
32 "Connection failed"
33 }
34
35 local session_id = nil
36
37 local function call(cmd, args)
38 if type(args) == "table" and session_id then
39 args.ubus_rpc_session = session_id
40 end
41 return util.ubus("uci", cmd, args)
42 end
43
44
45 function cursor()
46 return _M
47 end
48
49 function cursor_state()
50 return _M
51 end
52
53 function substate(self)
54 return self
55 end
56
57
58 function get_confdir(self)
59 return "/etc/config"
60 end
61
62 function get_savedir(self)
63 return "/tmp/.uci"
64 end
65
66 function get_session_id(self)
67 return session_id
68 end
69
70 function set_confdir(self, directory)
71 return false
72 end
73
74 function set_savedir(self, directory)
75 return false
76 end
77
78 function set_session_id(self, id)
79 session_id = id
80 return true
81 end
82
83
84 function load(self, config)
85 return true
86 end
87
88 function save(self, config)
89 return true
90 end
91
92 function unload(self, config)
93 return true
94 end
95
96
97 function changes(self, config)
98 local rv, err = call("changes", { config = config })
99
100 if type(rv) == "table" and type(rv.changes) == "table" then
101 return rv.changes
102 elseif err then
103 return nil, ERRSTR[err]
104 else
105 return { }
106 end
107 end
108
109
110 function revert(self, config)
111 local _, err = call("revert", { config = config })
112 return (err == nil), ERRSTR[err]
113 end
114
115 function commit(self, config)
116 local _, err = call("commit", { config = config })
117 return (err == nil), ERRSTR[err]
118 end
119
120 function apply(self, rollback)
121 local _, err
122
123 if rollback then
124 local sys = require "luci.sys"
125 local conf = require "luci.config"
126 local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 30) or 0
127
128 _, err = call("apply", {
129 timeout = (timeout > 30) and timeout or 30,
130 rollback = true
131 })
132
133 if not err then
134 local now = os.time()
135 local token = sys.uniqueid(16)
136
137 util.ubus("session", "set", {
138 ubus_rpc_session = "00000000000000000000000000000000",
139 values = {
140 rollback = {
141 token = token,
142 session = session_id,
143 timeout = now + timeout
144 }
145 }
146 })
147
148 return token
149 end
150 else
151 _, err = call("changes", {})
152
153 if not err then
154 if type(_) == "table" and type(_.changes) == "table" then
155 local k, v
156 for k, v in pairs(_.changes) do
157 _, err = call("commit", { config = k })
158 if err then
159 break
160 end
161 end
162 end
163 end
164
165 if not err then
166 _, err = call("apply", { rollback = false })
167 end
168 end
169
170 return (err == nil), ERRSTR[err]
171 end
172
173 function confirm(self, token)
174 local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
175
176 if is_pending then
177 if token ~= rollback_token then
178 return false, "Permission denied"
179 end
180
181 local _, err = util.ubus("uci", "confirm", {
182 ubus_rpc_session = rollback_sid
183 })
184
185 if not err then
186 util.ubus("session", "set", {
187 ubus_rpc_session = "00000000000000000000000000000000",
188 values = { rollback = {} }
189 })
190 end
191
192 return (err == nil), ERRSTR[err]
193 end
194
195 return false, "No data"
196 end
197
198 function rollback(self)
199 local is_pending, time_remaining, rollback_sid = self:rollback_pending()
200
201 if is_pending then
202 local _, err = util.ubus("uci", "rollback", {
203 ubus_rpc_session = rollback_sid
204 })
205
206 if not err then
207 util.ubus("session", "set", {
208 ubus_rpc_session = "00000000000000000000000000000000",
209 values = { rollback = {} }
210 })
211 end
212
213 return (err == nil), ERRSTR[err]
214 end
215
216 return false, "No data"
217 end
218
219 function rollback_pending(self)
220 local rv, err = util.ubus("session", "get", {
221 ubus_rpc_session = "00000000000000000000000000000000",
222 keys = { "rollback" }
223 })
224
225 local now = os.time()
226
227 if type(rv) == "table" and
228 type(rv.values) == "table" and
229 type(rv.values.rollback) == "table" and
230 type(rv.values.rollback.token) == "string" and
231 type(rv.values.rollback.session) == "string" and
232 type(rv.values.rollback.timeout) == "number" and
233 rv.values.rollback.timeout > now
234 then
235 return true,
236 rv.values.rollback.timeout - now,
237 rv.values.rollback.session,
238 rv.values.rollback.token
239 end
240
241 return false, ERRSTR[err]
242 end
243
244
245 function foreach(self, config, stype, callback)
246 if type(callback) == "function" then
247 local rv, err = call("get", {
248 config = config,
249 type = stype
250 })
251
252 if type(rv) == "table" and type(rv.values) == "table" then
253 local sections = { }
254 local res = false
255 local index = 1
256
257 local _, section
258 for _, section in pairs(rv.values) do
259 section[".index"] = section[".index"] or index
260 sections[index] = section
261 index = index + 1
262 end
263
264 table.sort(sections, function(a, b)
265 return a[".index"] < b[".index"]
266 end)
267
268 for _, section in ipairs(sections) do
269 local continue = callback(section)
270 res = true
271 if continue == false then
272 break
273 end
274 end
275 return res
276 else
277 return false, ERRSTR[err] or "No data"
278 end
279 else
280 return false, "Invalid argument"
281 end
282 end
283
284 local function _get(self, operation, config, section, option)
285 if section == nil then
286 return nil
287 elseif type(option) == "string" and option:byte(1) ~= 46 then
288 local rv, err = call(operation, {
289 config = config,
290 section = section,
291 option = option
292 })
293
294 if type(rv) == "table" then
295 return rv.value or nil
296 elseif err then
297 return false, ERRSTR[err]
298 else
299 return nil
300 end
301 elseif option == nil then
302 local values = self:get_all(config, section)
303 if values then
304 return values[".type"], values[".name"]
305 else
306 return nil
307 end
308 else
309 return false, "Invalid argument"
310 end
311 end
312
313 function get(self, ...)
314 return _get(self, "get", ...)
315 end
316
317 function get_state(self, ...)
318 return _get(self, "state", ...)
319 end
320
321 function get_all(self, config, section)
322 local rv, err = call("get", {
323 config = config,
324 section = section
325 })
326
327 if type(rv) == "table" and type(rv.values) == "table" then
328 return rv.values
329 elseif err then
330 return false, ERRSTR[err]
331 else
332 return nil
333 end
334 end
335
336 function get_bool(self, ...)
337 local val = self:get(...)
338 return (val == "1" or val == "true" or val == "yes" or val == "on")
339 end
340
341 function get_first(self, config, stype, option, default)
342 local rv = default
343
344 self:foreach(config, stype, function(s)
345 local val = not option and s[".name"] or s[option]
346
347 if type(default) == "number" then
348 val = tonumber(val)
349 elseif type(default) == "boolean" then
350 val = (val == "1" or val == "true" or
351 val == "yes" or val == "on")
352 end
353
354 if val ~= nil then
355 rv = val
356 return false
357 end
358 end)
359
360 return rv
361 end
362
363 function get_list(self, config, section, option)
364 if config and section and option then
365 local val = self:get(config, section, option)
366 return (type(val) == "table" and val or { val })
367 end
368 return { }
369 end
370
371
372 function section(self, config, stype, name, values)
373 local rv, err = call("add", {
374 config = config,
375 type = stype,
376 name = name,
377 values = values
378 })
379
380 if type(rv) == "table" then
381 return rv.section
382 elseif err then
383 return false, ERRSTR[err]
384 else
385 return nil
386 end
387 end
388
389
390 function add(self, config, stype)
391 return self:section(config, stype)
392 end
393
394 function set(self, config, section, option, ...)
395 if select('#', ...) == 0 then
396 local sname, err = self:section(config, option, section)
397 return (not not sname), err
398 else
399 local _, err = call("set", {
400 config = config,
401 section = section,
402 values = { [option] = select(1, ...) }
403 })
404 return (err == nil), ERRSTR[err]
405 end
406 end
407
408 function set_list(self, config, section, option, value)
409 if section == nil or option == nil then
410 return false
411 elseif value == nil or (type(value) == "table" and #value == 0) then
412 return self:delete(config, section, option)
413 elseif type(value) == "table" then
414 return self:set(config, section, option, value)
415 else
416 return self:set(config, section, option, { value })
417 end
418 end
419
420 function tset(self, config, section, values)
421 local _, err = call("set", {
422 config = config,
423 section = section,
424 values = values
425 })
426 return (err == nil), ERRSTR[err]
427 end
428
429 function reorder(self, config, section, index)
430 local sections
431
432 if type(section) == "string" and type(index) == "number" then
433 local pos = 0
434
435 sections = { }
436
437 self:foreach(config, nil, function(s)
438 if pos == index then
439 pos = pos + 1
440 end
441
442 if s[".name"] ~= section then
443 pos = pos + 1
444 sections[pos] = s[".name"]
445 else
446 sections[index + 1] = section
447 end
448 end)
449 elseif type(section) == "table" then
450 sections = section
451 else
452 return false, "Invalid argument"
453 end
454
455 local _, err = call("order", {
456 config = config,
457 sections = sections
458 })
459
460 return (err == nil), ERRSTR[err]
461 end
462
463
464 function delete(self, config, section, option)
465 local _, err = call("delete", {
466 config = config,
467 section = section,
468 option = option
469 })
470 return (err == nil), ERRSTR[err]
471 end
472
473 function delete_all(self, config, stype, comparator)
474 local _, err
475 if type(comparator) == "table" then
476 _, err = call("delete", {
477 config = config,
478 type = stype,
479 match = comparator
480 })
481 elseif type(comparator) == "function" then
482 local rv = call("get", {
483 config = config,
484 type = stype
485 })
486
487 if type(rv) == "table" and type(rv.values) == "table" then
488 local sname, section
489 for sname, section in pairs(rv.values) do
490 if comparator(section) then
491 _, err = call("delete", {
492 config = config,
493 section = sname
494 })
495 end
496 end
497 end
498 elseif comparator == nil then
499 _, err = call("delete", {
500 config = config,
501 type = stype
502 })
503 else
504 return false, "Invalid argument"
505 end
506
507 return (err == nil), ERRSTR[err]
508 end