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