* luci/libs: change default scheme directory to /lib/uci/schema/ in uvl
[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