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