* luci/libs: uvl: add support for list values in schemes and configurations
[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 = "/etc/scheme"
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                                 if not self.datatypes[item.datatype]( val ) then
309                                         return false, 'Value "' .. ( val or '<nil>' ) ..
310                                                 '" of given option "' .. option:cid() ..
311                                                 '" does not validate as datatype "' ..
312                                                 item.datatype .. '"'
313                                 end
314                         else
315                                 return false, 'Unknown datatype "' ..
316                                         item.datatype .. '" encountered'
317                         end
318                 end
319
320                 if not nodeps then
321                         return luci.uvl.dependencies.check( self, option )
322                 end
323
324                 local ok, err = luci.uvl.validation.check( self, option )
325                 if not ok and STRICT_EXTERNAL_VALIDATORS then
326                         return false, self.log.validator_error( option, err )
327                 end
328         end
329
330         return true, nil
331 end
332
333 --- Find all parts of given scheme and construct validation tree.
334 -- This is normally done on demand, so you don't have to call this function
335 -- by yourself.
336 -- @param scheme        Name of the scheme to parse
337 function UVL.read_scheme( self, scheme )
338         local schemes = { }
339         local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
340
341         if files then
342                 for i, file in ipairs( files ) do
343                         _assert( luci.fs.access(file), "Can't access file '%s'", file )
344
345                         self.uci.set_confdir( luci.fs.dirname(file) )
346                         self.uci.load( luci.fs.basename(file) )
347
348                         table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
349                 end
350
351                 return self:_read_scheme_parts( scheme, schemes )
352         else
353                 error(
354                         'Can not find scheme "' .. scheme ..
355                         '" in "' .. self.schemedir .. '"'
356                 )
357         end
358 end
359
360 -- Process all given parts and construct validation tree
361 function UVL._read_scheme_parts( self, scheme, schemes )
362
363         -- helper function to construct identifiers for given elements
364         local function _id( c, t )
365                 if c == TYPE_SECTION then
366                         return string.format(
367                                 'section "%s.%s"',
368                                         scheme, t.name or '?' )
369                 elseif c == TYPE_VARIABLE then
370                         return string.format(
371                                 'variable "%s.%s.%s"',
372                                         scheme, t.section or '?.?', t.name or '?' )
373                 elseif c == TYPE_ENUM then
374                         return string.format(
375                                 'enum "%s.%s.%s"',
376                                         scheme, t.variable or '?.?.?', t.value or '?' )
377                 end
378         end
379
380         -- helper function to check for required fields
381         local function _req( c, t, r )
382                 for i, v in ipairs(r) do
383                         _assert( t[v], 'Missing required field "%s" in %s', v, _id(c, t) )
384                 end
385         end
386
387         -- helper function to validate references
388         local function _ref( c, t )
389                 local k
390                 if c == TYPE_SECTION then
391                         k = "package"
392                 elseif c == TYPE_VARIABLE then
393                         k = "section"
394                 elseif c == TYPE_ENUM then
395                         k = "variable"
396                 end
397
398                 local r = luci.util.split( t[k], "." )
399                 r[1] = ( #r[1] > 0 and r[1] or scheme )
400
401                 _assert( #r == c, 'Malformed %s reference in %s', k, _id(c, t) )
402
403                 return r
404         end
405
406         -- helper function to read bools
407         local function _bool( v )
408                 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
409         end
410
411         -- Step 1: get all sections
412         for i, conf in ipairs( schemes ) do
413                 for k, v in pairs( conf ) do
414                         if v['.type'] == 'section' then
415
416                                 _req( TYPE_SECTION, v, { "name", "package" } )
417
418                                 local r = _ref( TYPE_SECTION, v )
419
420                                 self.packages[r[1]] =
421                                         self.packages[r[1]] or {
422                                                 ["sections"]  = { };
423                                                 ["variables"] = { };
424                                         }
425
426                                 local p = self.packages[r[1]]
427                                           p.sections[v.name]  = p.sections[v.name]  or { }
428                                           p.variables[v.name] = p.variables[v.name] or { }
429
430                                 local s = p.sections[v.name]
431
432                                 for k, v2 in pairs(v) do
433                                         if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
434                                                 if k == "depends" then
435                                                         s["depends"] = _assert(
436                                                                 self:_read_dependency( v2, s["depends"] ),
437                                                                 'Section "%s" in scheme "%s" has malformed ' ..
438                                                                 'dependency specification in "%s"',
439                                                                 v.name or '<nil>', scheme or '<nil>', k
440                                                         )
441                                                 elseif k == "dynamic" or k == "unique" or k == "required" then
442                                                         s[k] = _bool(v2)
443                                                 else
444                                                         s[k] = v2
445                                                 end
446                                         end
447                                 end
448
449                                 s.dynamic  = s.dynamic  or false
450                                 s.unique   = s.unique   or false
451                                 s.required = s.required or false
452                         end
453                 end
454         end
455
456         -- Step 2: get all variables
457         for i, conf in ipairs( schemes ) do
458                 for k, v in pairs( conf ) do
459                         if v['.type'] == "variable" then
460
461                                 _req( TYPE_VARIABLE, v, { "name", "section" } )
462
463                                 local r = _ref( TYPE_VARIABLE, v )
464
465                                 local p = _assert( self.packages[r[1]],
466                                         'Variable "%s" in scheme "%s" references unknown package "%s"',
467                                         v.name, scheme, r[1] )
468
469                                 local s = _assert( p.variables[r[2]],
470                                         'Variable "%s" in scheme "%s" references unknown section "%s"',
471                                         v.name, scheme, r[2] )
472
473                                 s[v.name] = s[v.name] or { }
474
475                                 local t = s[v.name]
476
477                                 for k, v2 in pairs(v) do
478                                         if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
479                                                 if k == "depends" then
480                                                         t["depends"] = _assert(
481                                                                 self:_read_dependency( v2, t["depends"] ),
482                                                                 'Invalid reference "%s" in "%s.%s.%s"',
483                                                                 v2, v.name, scheme, k
484                                                         )
485                                                 elseif k == "validator" then
486                                                         t["validators"] = _assert(
487                                                                 self:_read_validator( v2, t["validators"] ),
488                                                                 'Variable "%s" in scheme "%s" has malformed ' ..
489                                                                 'validator specification in "%s"',
490                                                                 v.name, scheme, k
491                                                         )
492                                                 elseif k == "required" then
493                                                         t[k] = _bool(v2)
494                                                 else
495                                                         t[k] = v2
496                                                 end
497                                         end
498                                 end
499
500                                 t.type     = t.type     or "variable"
501                                 t.datatype = t.datatype or "string"
502                                 t.required = t.required or false
503                         end
504                 end
505         end
506
507         -- Step 3: get all enums
508         for i, conf in ipairs( schemes ) do
509                 for k, v in pairs( conf ) do
510                         if v['.type'] == "enum" then
511
512                                 _req( TYPE_ENUM, v, { "value", "variable" } )
513
514                                 local r = _ref( TYPE_ENUM, v )
515                                 local p = _assert( self.packages[r[1]],
516                                         'Enum "%s" in scheme "%s" references unknown package "%s"',
517                                         v.value, scheme, r[1] )
518
519                                 local s = _assert( p.variables[r[2]],
520                                         'Enum "%s" in scheme "%s" references unknown section "%s"',
521                                         v.value, scheme, r[2] )
522
523                                 local t = _assert( s[r[3]],
524                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
525                                         'unknown variable "%s"',
526                                         v.value, scheme, r[2], r[3] )
527
528                                 _assert( t.type == "enum",
529                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
530                                         'variable "%s" with non enum type "%s"',
531                                         v.value, scheme, r[2], r[3], t.type )
532
533                                 if not t.values then
534                                         t.values = { [v.value] = v.title or v.value }
535                                 else
536                                         t.values[v.value] = v.title or v.value
537                                 end
538
539                                 if v.default then
540                                         _assert( not t.default,
541                                                 'Enum "%s" in scheme "%s", section "%s" redeclares ' ..
542                                                 'the default value of variable "%s"',
543                                                 v.value, scheme, r[2], v.variable )
544
545                                         t.default = v.value
546                                 end
547                         end
548                 end
549         end
550
551         return self
552 end
553
554 -- Read a dependency specification
555 function UVL._read_dependency( self, values, deps )
556         local expr = "%$?[a-zA-Z0-9_]+"
557         if values then
558                 values = ( type(values) == "table" and values or { values } )
559                 for _, value in ipairs(values) do
560                         local parts     = luci.util.split( value, "%s*,%s*", nil, true )
561                         local condition = { }
562                         for i, val in ipairs(parts) do
563                                 local k, v = unpack(luci.util.split(val, "%s*=%s*", nil, true))
564
565                                 if k and (
566                                         k:match("^"..expr.."%."..expr.."%."..expr.."$") or
567                                         k:match("^"..expr.."%."..expr.."$") or
568                                         k:match("^"..expr.."$")
569                                 ) then
570                                         condition[k] = v or true
571                                 else
572                                         return nil
573                                 end
574                         end
575
576                         if not deps then
577                                 deps = { condition }
578                         else
579                                 table.insert( deps, condition )
580                         end
581                 end
582         end
583
584         return deps
585 end
586
587 -- Read a validator specification
588 function UVL._read_validator( self, values, validators )
589         if values then
590                 values = ( type(values) == "table" and values or { values } )
591                 for _, value in ipairs(values) do
592                         local validator
593
594                         if value:match("^exec:") then
595                                 validator = value:gsub("^exec:","")
596                         elseif value:match("^lua:") then
597                                 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
598                         end
599
600                         if validator then
601                                 if not validators then
602                                         validators = { validator }
603                                 else
604                                         table.insert( validators, validator )
605                                 end
606                         else
607                                 return nil
608                         end
609                 end
610
611                 return validators
612         end
613 end
614
615 -- Resolve given path
616 function UVL._resolve_function( self, value )
617         local path = luci.util.split(value, ".")
618
619         for i=1, #path-1 do
620                 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
621                 if stat and mod then
622                         for j=i+1, #path-1 do
623                                 if not type(mod) == "table" then
624                                         break
625                                 end
626                                 mod = mod[path[j]]
627                                 if not mod then
628                                         break
629                                 end
630                         end
631                         mod = type(mod) == "table" and mod[path[#path]] or nil
632                         if type(mod) == "function" then
633                                 return mod
634                         end
635                 end
636         end
637 end
638
639
640 --- Object representation of a scheme/config section.
641 -- @class       module
642 -- @cstyle      instance
643 -- @name        luci.uvl.section
644
645 --- Section instance constructor.
646 -- @class                       function
647 -- @name                        section
648 -- @param scheme        Scheme instance
649 -- @param co            Configuration data
650 -- @param st            Section type
651 -- @param c                     Configuration name
652 -- @param s                     Section name
653 -- @return                      Section instance
654 section = luci.util.class()
655
656 function section.__init__(self, scheme, co, st, c, s)
657         self.csection = co[s]
658         self.ssection = scheme.packages[c].sections[st]
659         self.cref     = { c, s }
660         self.sref     = { c, st }
661         self.scheme   = scheme
662         self.config   = co
663         self.type     = luci.uvl.TYPE_SECTION
664 end
665
666 --- Get the config path of this section.
667 -- @return      String containing the identifier
668 function section.cid(self)
669         return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
670 end
671
672 --- Get the scheme path of this section.
673 -- @return      String containing the identifier
674 function section.sid(self)
675         return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
676 end
677
678 --- Get all configuration values within this section.
679 -- @return      Table containing the values
680 function section.values(self)
681         return self.csection
682 end
683
684 --- Get the associated section information in scheme.
685 -- @return      Table containing the scheme properties
686 function section.section(self)
687         return self.ssection
688 end
689
690 --- Get all option objects associated with this section.
691 -- @return      Table containing all associated luci.uvl.option instances
692 function section.variables(self)
693         local v = { }
694         if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
695                 for o, _ in pairs(
696                         self.scheme.packages[self.sref[1]].variables[self.sref[2]]
697                 ) do
698                         table.insert( v, luci.uvl.option(
699                                 self.scheme, self.config, self.sref[2],
700                                 self.cref[1], self.cref[2], o
701                         ) )
702                 end
703         end
704         return v
705 end
706
707
708 --- Object representation of a scheme/config option.
709 -- @class       module
710 -- @cstyle      instance
711 -- @name        luci.uvl.option
712
713 --- Section instance constructor.
714 -- @class                       function
715 -- @name                        option
716 -- @param scheme        Scheme instance
717 -- @param co            Configuration data
718 -- @param st            Section type
719 -- @param c                     Configuration name
720 -- @param s                     Section name
721 -- @param o                     Option name
722 -- @return                      Option instance
723 option = luci.util.class()
724
725 function option.__init__(self, scheme, co, st, c, s, o)
726         self.coption = co[s] and co[s][o] or nil
727         self.soption = scheme.packages[c].variables[st][o]
728         self.cref    = { c, s, o }
729         self.sref    = { c, st, o }
730         self.scheme  = scheme
731         self.config  = co
732         self.type    = luci.uvl.TYPE_OPTION
733 end
734
735 --- Get the config path of this option.
736 -- @return      String containing the identifier
737 function option.cid(self)
738         return ( self.cref[1] or '?' ) .. '.' ..
739                    ( self.cref[2] or '?' ) .. '.' ..
740                    ( self.cref[3] or '?' )
741 end
742
743 --- Get the scheme path of this option.
744 -- @return      String containing the identifier
745 function option.sid(self)
746         return ( self.sref[1] or '?' ) .. '.' ..
747                    ( self.sref[2] or '?' ) .. '.' ..
748                    ( self.sref[3] or '?' )
749 end
750
751 --- Get the value of this option.
752 -- @return      The associated configuration value
753 function option.value(self)
754         return self.coption
755 end
756
757 --- Get the associated option information in scheme.
758 -- @return      Table containing the scheme properties
759 function option.option(self)
760         return self.soption
761 end
762
763 --- Get the associated section information in scheme.
764 -- @return      Table containing the scheme properties
765 function option.section(self)
766         return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
767 end