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