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