85962b402b20cfea23b065fee6729f25b83fcc37
[project/luci.git] / libs / uvl / luasrc / uvl.lua
1 --[[
2
3 UCI Validation Layer - Main Library
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17
18 --- UVL - UCI Validation Layer
19 -- @class module
20 -- @cstyle instance
21
22 module( "luci.uvl", package.seeall )
23
24 require("luci.fs")
25 require("luci.util")
26 require("luci.model.uci")
27 require("luci.uvl.loghelper")
28 require("luci.uvl.datatypes")
29 require("luci.uvl.validation")
30 require("luci.uvl.dependencies")
31
32
33 TYPE_SECTION = 0x01
34 TYPE_VARIABLE = 0x02
35 TYPE_ENUM = 0x03
36
37 --- Boolean; default true;
38 -- treat sections found in config but not in scheme as error
39 STRICT_UNKNOWN_SECTIONS = true
40
41 --- Boolean; default true;
42 -- treat options found in config but not in scheme as error
43 STRICT_UNKNOWN_OPTIONS = true
44
45 --- Boolean; default true;
46 -- treat failed external validators as error
47 STRICT_EXTERNAL_VALIDATORS = true
48
49
50 local default_schemedir = "/etc/scheme"
51
52 local function _assert( condition, fmt, ... )
53 if not condition then
54 return assert( nil, string.format( fmt, ... ) )
55 else
56 return condition
57 end
58 end
59
60
61 --- Object constructor
62 -- @class function
63 -- @name UVL
64 -- @param schemedir Path to the scheme directory (optional)
65 -- @return Instance object
66 UVL = luci.util.class()
67
68 function UVL.__init__( self, schemedir )
69 self.schemedir = schemedir or default_schemedir
70 self.packages = { }
71 self.beenthere = { }
72 self.uci = luci.model.uci
73 self.log = luci.uvl.loghelper
74 self.datatypes = luci.uvl.datatypes
75 end
76
77
78 --- Validate given configuration, section or option.
79 -- @param config Name of the configuration to validate
80 -- @param section Name of the section to validate (optional)
81 -- @param option Name of the option to validate (optional)
82 -- @return Boolean indicating whether the given config validates
83 -- @return String containing the reason for errors (if any)
84 function UVL.validate( self, config, section, option )
85 if config and section and option then
86 return self:validate_option( config, section, option )
87 elseif config and section then
88 return self:validate_section( config, section )
89 elseif config then
90 return self:validate_config( config )
91 end
92 end
93
94 --- Validate given configuration.
95 -- @param config Name of the configuration to validate
96 -- @return Boolean indicating whether the given config validates
97 -- @return String containing the reason for errors (if any)
98 function UVL.validate_config( self, config )
99
100 if not self.packages[config] then
101 local ok, err = pcall( self.read_scheme, self, config )
102 if not ok then
103 return false, self.log.scheme_error( config, err )
104 end
105 end
106
107 self.uci.load_config( config )
108 self.beenthere = { }
109
110 local co = self.uci.get_all( config )
111 local sc = { }
112
113 if not co then
114 return false, 'Unable to load configuration "' .. config .. '"'
115 end
116
117 local function _uci_foreach( type, func )
118 local ok, err
119 for k, v in pairs(co) do
120 if co[k]['.type'] == type then
121 sc[type] = sc[type] + 1
122 ok, err = func( k, v )
123 if not ok then
124 err = self.log.config_error( config, err )
125 break
126 end
127 end
128 end
129 return ok, err
130 end
131
132 for k, v in pairs( self.packages[config].sections ) do
133 sc[k] = 0
134 local ok, err = _uci_foreach( k,
135 function(s)
136 local sect = luci.uvl.section( self, co, k, config, s )
137 return self:_validate_section( sect )
138 end
139 )
140 if not ok then return false, err end
141 end
142
143 if STRICT_UNKNOWN_SECTIONS then
144 for k, v in pairs(co) do
145 if not self.beenthere[config..'.'..k] then
146 return false, self.log.config_error( config,
147 "Section '" .. config .. '.' .. co[k]['.type'] ..
148 "' not found in scheme" )
149 end
150 end
151 end
152
153 for _, k in ipairs(luci.util.keys(sc)) do
154 local s = self.packages[config].sections[k]
155
156 if s.required and sc[k] == 0 then
157 return false, self.log.config_error( config,
158 'Required section "' .. k .. '" not found in config' )
159 elseif s.unique and sc[k] > 1 then
160 return false, self.log.config_error( config,
161 'Unique section "' .. k .. '" occurs multiple times in config' )
162 end
163 end
164
165 return true, nil
166 end
167
168 --- Validate given config section.
169 -- @param config Name of the configuration to validate
170 -- @param section Name of the section to validate
171 -- @return Boolean indicating whether the given config validates
172 -- @return String containing the reason for errors (if any)
173 function UVL.validate_section( self, config, section )
174
175 if not self.packages[config] then
176 local ok, err = pcall( self.read_scheme, self, config )
177 if not ok then
178 return false, self.log.scheme_error( config, err )
179 end
180 end
181
182 self.uci.load_config( config )
183 self.beenthere = { }
184
185 local co = self.uci.get_all( config )
186
187 if not co then
188 return false, 'Unable to load configuration "' .. config .. '"'
189 end
190
191 if co[section] then
192 return self:_validate_section( luci.uvl.section(
193 self, co, co[section]['.type'], config, section
194 ) )
195 else
196 return false, "Section '" .. config .. '.' .. section ..
197 "' not found in config. Nothing to do."
198 end
199 end
200
201 --- Validate given config option.
202 -- @param config Name of the configuration to validate
203 -- @param section Name of the section to validate
204 -- @param option Name of the option to validate
205 -- @return Boolean indicating whether the given config validates
206 -- @return String containing the reason for errors (if any)
207 function UVL.validate_option( self, config, section, option )
208
209 if not self.packages[config] then
210 local ok, err = pcall( self.read_scheme, self, config )
211 if not ok then
212 return false, self.log.scheme_error( config, err )
213 end
214 end
215
216 self.uci.load_config( config )
217 self.beenthere = { }
218
219 local co = self.uci.get_all( config )
220
221 if not co then
222 return false, 'Unable to load configuration "' .. config .. '"'
223 end
224
225 if co[section] and co[section][option] then
226 return self:_validate_option( luci.uvl.option(
227 self, co, co[section]['.type'], config, section, option
228 ) )
229 else
230 return false, "Option '" ..
231 config .. '.' .. section .. '.' .. option ..
232 "' not found in config. Nothing to do."
233 end
234 end
235
236
237 function UVL._validate_section( self, section )
238
239 if section:values() then
240
241 for _, v in ipairs(section:variables()) do
242 local ok, err = self:_validate_option( v )
243
244 if not ok then
245 return ok, self.log.section_error( section, err )
246 end
247 end
248
249 local ok, err = luci.uvl.dependencies.check( self, section )
250
251 if not ok then
252 return false, err
253 end
254 else
255 print( "Error, scheme section '" .. section:sid() .. "' not found in data" )
256 end
257
258 if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
259 for k, v in pairs(section:values()) do
260 if k:sub(1,1) ~= "." and not self.beenthere[
261 section:cid() .. '.' .. k
262 ] then
263 return false, "Option '" .. section:sid() .. '.' .. k ..
264 "' not found in scheme"
265 end
266 end
267 end
268
269 return true, nil
270 end
271
272 function UVL._validate_option( self, option, nodeps )
273
274 if not option:option() and
275 not ( option:section() and option:section().dynamic )
276 then
277 return false, "Option '" .. option:cid() ..
278 "' not found in scheme"
279 end
280
281 if option:option() then
282 if option:option().required and not option:value() then
283 return false, "Mandatory variable '" .. option:cid() ..
284 "' doesn't have a value"
285 end
286
287 if option:option().type == "enum" and option:value() then
288 if not option:option().values or
289 not option:option().values[option:value()]
290 then
291 return false, "Value '" .. ( option:value() or '<nil>' ) ..
292 "' of given option '" .. option:cid() ..
293 "' is not defined in enum { " ..
294 table.concat(luci.util.keys(option:option().values),", ") ..
295 " }"
296 end
297 end
298
299 if option:option().datatype and option:value() then
300 if self.datatypes[option:option().datatype] then
301 if not self.datatypes[option:option().datatype](
302 option:value()
303 ) then
304 return false, "Value '" .. ( option:value() or '<nil>' ) ..
305 "' of given option '" .. option:cid() ..
306 "' doesn't validate as datatype '" ..
307 option:option().datatype .. "'"
308 end
309 else
310 return false, "Unknown datatype '" ..
311 option:option().datatype .. "' encountered"
312 end
313 end
314
315 if not nodeps then
316 return luci.uvl.dependencies.check( self, option )
317 end
318
319 local ok, err = luci.uvl.validation.check( self, option )
320 if not ok and STRICT_EXTERNAL_VALIDATORS then
321 return false, self.log.validator_error( option, err )
322 end
323 end
324
325 return true, nil
326 end
327
328 --- Find all parts of given scheme and construct validation tree.
329 -- This is normally done on demand, so you don't have to call this function
330 -- by yourself.
331 -- @param scheme Name of the scheme to parse
332 function UVL.read_scheme( self, scheme )
333 local schemes = { }
334 local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
335
336 if files then
337 for i, file in ipairs( files ) do
338 _assert( luci.fs.access(file), "Can't access file '%s'", file )
339
340 self.uci.set_confdir( luci.fs.dirname(file) )
341 self.uci.load( luci.fs.basename(file) )
342
343 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
344 end
345
346 return self:_read_scheme_parts( scheme, schemes )
347 else
348 error(
349 'Can\'t find scheme "' .. scheme ..
350 '" in "' .. self.schemedir .. '"'
351 )
352 end
353 end
354
355 -- Process all given parts and construct validation tree
356 function UVL._read_scheme_parts( self, scheme, schemes )
357
358 -- helper function to construct identifiers for given elements
359 local function _id( c, t )
360 if c == TYPE_SECTION then
361 return string.format(
362 "section '%s.%s'",
363 scheme, t.name or '?' )
364 elseif c == TYPE_VARIABLE then
365 return string.format(
366 "variable '%s.%s.%s'",
367 scheme, t.section or '?.?', t.name or '?' )
368 elseif c == TYPE_ENUM then
369 return string.format(
370 "enum '%s.%s.%s'",
371 scheme, t.variable or '?.?.?', t.value or '?' )
372 end
373 end
374
375 -- helper function to check for required fields
376 local function _req( c, t, r )
377 for i, v in ipairs(r) do
378 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
379 end
380 end
381
382 -- helper function to validate references
383 local function _ref( c, t )
384 local k
385 if c == TYPE_SECTION then
386 k = "package"
387 elseif c == TYPE_VARIABLE then
388 k = "section"
389 elseif c == TYPE_ENUM then
390 k = "variable"
391 end
392
393 local r = luci.util.split( t[k], "." )
394 r[1] = ( #r[1] > 0 and r[1] or scheme )
395
396 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
397
398 return r
399 end
400
401 -- helper function to read bools
402 local function _bool( v )
403 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
404 end
405
406 -- Step 1: get all sections
407 for i, conf in ipairs( schemes ) do
408 for k, v in pairs( conf ) do
409 if v['.type'] == 'section' then
410
411 _req( TYPE_SECTION, v, { "name", "package" } )
412
413 local r = _ref( TYPE_SECTION, v )
414
415 self.packages[r[1]] =
416 self.packages[r[1]] or {
417 ["sections"] = { };
418 ["variables"] = { };
419 }
420
421 local p = self.packages[r[1]]
422 p.sections[v.name] = p.sections[v.name] or { }
423 p.variables[v.name] = p.variables[v.name] or { }
424
425 local s = p.sections[v.name]
426
427 for k, v2 in pairs(v) do
428 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
429 if k:match("^depends") then
430 s["depends"] = _assert(
431 self:_read_dependency( v2, s["depends"] ),
432 "Section '%s' in scheme '%s' has malformed " ..
433 "dependency specification in '%s'",
434 v.name or '<nil>', scheme or '<nil>', k
435 )
436 elseif k == "dynamic" or k == "unique" or k == "required" then
437 s[k] = _bool(v2)
438 else
439 s[k] = v2
440 end
441 end
442 end
443
444 s.dynamic = s.dynamic or false
445 s.unique = s.unique or false
446 s.required = s.required or false
447 end
448 end
449 end
450
451 -- Step 2: get all variables
452 for i, conf in ipairs( schemes ) do
453 for k, v in pairs( conf ) do
454 if v['.type'] == "variable" then
455
456 _req( TYPE_VARIABLE, v, { "name", "section" } )
457
458 local r = _ref( TYPE_VARIABLE, v )
459
460 local p = _assert( self.packages[r[1]],
461 "Variable '%s' in scheme '%s' references unknown package '%s'",
462 v.name, scheme, r[1] )
463
464 local s = _assert( p.variables[r[2]],
465 "Variable '%s' in scheme '%s' references unknown section '%s'",
466 v.name, scheme, r[2] )
467
468 s[v.name] = s[v.name] or { }
469
470 local t = s[v.name]
471
472 for k, v2 in pairs(v) do
473 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
474 if k:match("^depends") then
475 t["depends"] = _assert(
476 self:_read_dependency( v2, t["depends"] ),
477 'Invalid reference "%s" in "%s.%s.%s"',
478 v2, v.name, scheme, k
479 )
480 elseif k:match("^validator") then
481 t["validators"] = _assert(
482 self:_read_validator( v2, t["validators"] ),
483 "Variable '%s' in scheme '%s' has malformed " ..
484 "validator specification in '%s'",
485 v.name, scheme, k
486 )
487 elseif k == "required" then
488 t[k] = _bool(v2)
489 else
490 t[k] = v2
491 end
492 end
493 end
494
495 t.type = t.type or "variable"
496 t.datatype = t.datatype or "string"
497 t.required = t.required or false
498 end
499 end
500 end
501
502 -- Step 3: get all enums
503 for i, conf in ipairs( schemes ) do
504 for k, v in pairs( conf ) do
505 if v['.type'] == "enum" then
506
507 _req( TYPE_ENUM, v, { "value", "variable" } )
508
509 local r = _ref( TYPE_ENUM, v )
510
511 local p = _assert( self.packages[r[1]],
512 "Enum '%s' in scheme '%s' references unknown package '%s'",
513 v.value, scheme, r[1] )
514
515 local s = _assert( p.variables[r[2]],
516 "Enum '%s' in scheme '%s' references unknown section '%s'",
517 v.value, scheme, r[2] )
518
519 local t = _assert( s[r[3]],
520 "Enum '%s' in scheme '%s', section '%s' references " ..
521 "unknown variable '%s'",
522 v.value, scheme, r[2], r[3] )
523
524 _assert( t.type == "enum",
525 "Enum '%s' in scheme '%s', section '%s' references " ..
526 "variable '%s' with non enum type '%s'",
527 v.value, scheme, r[2], r[3], t.type )
528
529 if not t.values then
530 t.values = { [v.value] = v.title or v.value }
531 else
532 t.values[v.value] = v.title or v.value
533 end
534
535 if v.default then
536 _assert( not t.default,
537 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
538 "the default value of variable '%s'",
539 v.value, scheme, r[2], v.variable )
540
541 t.default = v.value
542 end
543 end
544 end
545 end
546
547 return self
548 end
549
550 -- Read a dependency specification
551 function UVL._read_dependency( self, value, deps )
552 local parts = luci.util.split( value, "%s*,%s*", nil, true )
553 local condition = { }
554
555 for i, val in ipairs(parts) do
556 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
557
558 if k and (
559 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
560 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
561 k:match("^%$?[a-zA-Z0-9_]+$")
562 ) then
563 condition[k] = v or true
564 else
565 return nil
566 end
567 end
568
569 if not deps then
570 deps = { condition }
571 else
572 table.insert( deps, condition )
573 end
574
575 return deps
576 end
577
578 -- Read a validator specification
579 function UVL._read_validator( self, value, validators )
580 if value then
581 local validator
582
583 if value:match("^exec:") then
584 validator = value:gsub("^exec:","")
585 elseif value:match("^lua:") then
586 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
587 end
588
589 if validator then
590 if not validators then
591 validators = { validator }
592 else
593 table.insert( validators, validator )
594 end
595
596 return validators
597 end
598 end
599 end
600
601 -- Resolve given path
602 function UVL._resolve_function( self, value )
603 local path = luci.util.split(value, ".")
604
605 for i=1, #path-1 do
606 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
607 if stat and mod then
608 for j=i+1, #path-1 do
609 if not type(mod) == "table" then
610 break
611 end
612 mod = mod[path[j]]
613 if not mod then
614 break
615 end
616 end
617 mod = type(mod) == "table" and mod[path[#path]] or nil
618 if type(mod) == "function" then
619 return mod
620 end
621 end
622 end
623 end
624
625
626 --- Object representation of a scheme/config section.
627 -- @class module
628 -- @cstyle instance
629 -- @name luci.uvl.section
630
631 --- Section instance constructor.
632 -- @class function
633 -- @name section
634 -- @param scheme Scheme instance
635 -- @param co Configuration data
636 -- @param st Section type
637 -- @param c Configuration name
638 -- @param s Section name
639 -- @return Section instance
640 section = luci.util.class()
641
642 function section.__init__(self, scheme, co, st, c, s)
643 self.csection = co[s]
644 self.ssection = scheme.packages[c].sections[st]
645 self.cref = { c, s }
646 self.sref = { c, st }
647 self.scheme = scheme
648 self.config = co
649 self.type = luci.uvl.TYPE_SECTION
650 end
651
652 --- Get the config path of this section.
653 -- @return String containing the identifier
654 function section.cid(self)
655 return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
656 end
657
658 --- Get the scheme path of this section.
659 -- @return String containing the identifier
660 function section.sid(self)
661 return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
662 end
663
664 --- Get all configuration values within this section.
665 -- @return Table containing the values
666 function section.values(self)
667 return self.csection
668 end
669
670 --- Get the associated section information in scheme.
671 -- @return Table containing the scheme properties
672 function section.section(self)
673 return self.ssection
674 end
675
676 --- Get all option objects associated with this section.
677 -- @return Table containing all associated luci.uvl.option instances
678 function section.variables(self)
679 local v = { }
680 if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
681 for o, _ in pairs(
682 self.scheme.packages[self.sref[1]].variables[self.sref[2]]
683 ) do
684 table.insert( v, luci.uvl.option(
685 self.scheme, self.config, self.sref[2],
686 self.cref[1], self.cref[2], o
687 ) )
688 end
689 end
690 return v
691 end
692
693
694 --- Object representation of a scheme/config option.
695 -- @class module
696 -- @cstyle instance
697 -- @name luci.uvl.option
698
699 --- Section instance constructor.
700 -- @class function
701 -- @name option
702 -- @param scheme Scheme instance
703 -- @param co Configuration data
704 -- @param st Section type
705 -- @param c Configuration name
706 -- @param s Section name
707 -- @param o Option name
708 -- @return Option instance
709 option = luci.util.class()
710
711 function option.__init__(self, scheme, co, st, c, s, o)
712 self.coption = co[s] and co[s][o] or nil
713 self.soption = scheme.packages[c].variables[st][o]
714 self.cref = { c, s, o }
715 self.sref = { c, st, o }
716 self.scheme = scheme
717 self.config = co
718 self.type = luci.uvl.TYPE_OPTION
719 end
720
721 --- Get the config path of this option.
722 -- @return String containing the identifier
723 function option.cid(self)
724 return ( self.cref[1] or '?' ) .. '.' ..
725 ( self.cref[2] or '?' ) .. '.' ..
726 ( self.cref[3] or '?' )
727 end
728
729 --- Get the scheme path of this option.
730 -- @return String containing the identifier
731 function option.sid(self)
732 return ( self.sref[1] or '?' ) .. '.' ..
733 ( self.sref[2] or '?' ) .. '.' ..
734 ( self.sref[3] or '?' )
735 end
736
737 --- Get the value of this option.
738 -- @return The associated configuration value
739 function option.value(self)
740 return self.coption
741 end
742
743 --- Get the associated option information in scheme.
744 -- @return Table containing the scheme properties
745 function option.option(self)
746 return self.soption
747 end
748
749 --- Get the associated section information in scheme.
750 -- @return Table containing the scheme properties
751 function option.section(self)
752 return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
753 end