* luci/libs: uvl: add support for external validation commands, various cleanups
[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 module( "luci.uvl", package.seeall )
18
19 require("luci.fs")
20 require("luci.util")
21 require("luci.model.uci")
22 require("luci.uvl.loghelper")
23 require("luci.uvl.datatypes")
24 require("luci.uvl.validation")
25 require("luci.uvl.dependencies")
26
27 TYPE_SECTION  = 0x01
28 TYPE_VARIABLE = 0x02
29 TYPE_ENUM     = 0x03
30
31 STRICT_UNKNOWN_SECTIONS    = true
32 STRICT_UNKNOWN_OPTIONS     = true
33 STRICT_EXTERNAL_VALIDATORS = true
34
35
36 local default_schemedir = "/etc/scheme"
37
38 local function _assert( condition, fmt, ... )
39         if not condition then
40                 return assert( nil, string.format( fmt, ... ) )
41         else
42                 return condition
43         end
44 end
45
46 UVL = luci.util.class()
47
48 function UVL.__init__( self, schemedir )
49         self.schemedir  = schemedir or default_schemedir
50         self.packages   = { }
51         self.beenthere  = { }
52         self.uci                = luci.model.uci
53         self.log        = luci.uvl.loghelper
54         self.datatypes  = luci.uvl.datatypes
55 end
56
57
58 --- Validate given configuration.
59 -- @param config        Name of the configuration to validate
60 -- @param scheme        Scheme to validate against (optional)
61 -- @return                      Boolean indicating weather the given config validates
62 -- @return                      String containing the reason for errors (if any)
63 function UVL.validate( self, config )
64         self.uci.load_config( config )
65         self.beenthere = { }
66
67         local co = self.uci.get_all( config )
68         local sc = { }
69
70         local function _uci_foreach( type, func )
71                 local ok, err
72                 for k, v in pairs(co) do
73                         if co[k]['.type'] == type then
74                                 sc[type] = sc[type] + 1
75                                 ok, err = func( k, v )
76                                 if not ok then
77                                         err = self.log.config_error( config, err )
78                                         break
79                                 end
80                         end
81                 end
82                 return ok, err
83         end
84
85         for k, v in pairs( self.packages[config].sections ) do
86                 sc[k] = 0
87                 local ok, err = _uci_foreach( k,
88                         function(s)
89                                 local sect = luci.uvl.section( self, co, k, config, s )
90                                 return self:_validate_section( sect )
91                         end
92                 )
93                 if not ok then return false, err end
94         end
95
96         if STRICT_UNKNOWN_SECTIONS then
97                 for k, v in pairs(co) do
98                         if not self.beenthere[config..'.'..k] then
99                                 return false, self.log.config_error( config,
100                                         "Section '" .. config .. '.' .. co[k]['.type'] ..
101                                         "' not found in scheme" )
102                         end
103                 end
104         end
105
106         for _, k in ipairs(luci.util.keys(sc)) do
107                 local s = self.packages[config].sections[k]
108
109                 if s.required and sc[k] == 0 then
110                         return false, self.log.config_error( config,
111                                 'Required section "' .. k .. '" not found in config' )
112                 elseif s.unique and sc[k] > 1 then
113                         return false, self.log.config_error( config,
114                                 'Unique section "' .. k .. '" occurs multiple times in config' )
115                 end
116         end
117
118         return true, nil
119 end
120
121 function UVL.validate_section( self, config, section )
122         self.uci.load_config( config )
123         self.beenthere = { }
124
125         local co = self.uci.get_all( config )
126         if co[section] then
127                 return self:_validate_section( luci.uvl.section(
128                         self, co, co[section]['.type'], config, section
129                 ) )
130         else
131                 return false, "Section '" .. config .. '.' .. section ..
132                         "' not found in config. Nothing to do."
133         end
134 end
135
136 function UVL.validate_option( self, config, section, option )
137         self.uci.load_config( config )
138         self.beenthere = { }
139
140         local co = self.uci.get_all( config )
141         if co[section] and co[section][option] then
142                 return self:_validate_option( luci.uvl.option(
143                         self, co, co[section]['.type'], config, section, option
144                 ) )
145         else
146                 return false, "Option '" ..
147                         config .. '.' .. section .. '.' .. option ..
148                         "' not found in config. Nothing to do."
149         end
150 end
151
152 --- Validate given section of given configuration.
153 -- @param config        Name of the configuration to validate
154 -- @param section       Key of the section to validate
155 -- @param scheme        Scheme to validate against
156 -- @return                      Boolean indicating weather the given config validates
157 -- @return                      String containing the reason for errors (if any)
158 function UVL._validate_section( self, section )
159
160         if section:values() then
161
162                 for _, v in ipairs(section:variables()) do
163                         local ok, err = self:_validate_option( v )
164
165                         if not ok then
166                                 return ok, self.log.section_error( section, err )
167                         end
168                 end
169
170                 local ok, err = luci.uvl.dependencies.check( self, section )
171
172                 if not ok then
173                         return false, err
174                 end
175         else
176                 print( "Error, scheme section '" .. section:sid() .. "' not found in data" )
177         end
178
179         if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
180                 for k, v in pairs(section:values()) do
181                         if k:sub(1,1) ~= "." and not self.beenthere[
182                                 section:cid() .. '.' .. k
183                         ] then
184                                 return false, "Option '" .. section:sid() .. '.' .. k ..
185                                         "' not found in scheme"
186                         end
187                 end
188         end
189
190         return true, nil
191 end
192
193 --- Validate given option within section of given configuration.
194 -- @param config        Name of the configuration to validate
195 -- @param section       Key of the section to validate
196 -- @param option        Name of the option to validate
197 -- @param scheme        Scheme to validate against
198 -- @return                      Boolean indicating weather the given config validates
199 -- @return                      String containing the reason for errors (if any)
200 function UVL._validate_option( self, option, nodeps )
201
202         if not option:option() and
203            not ( option:section() and option:section().dynamic )
204         then
205                 return false, "Option '" .. option:cid() ..
206                         "' not found in scheme"
207         end
208
209         if option:option() then
210                 if option:option().required and not option:value() then
211                         return false, "Mandatory variable '" .. option:cid() ..
212                                 "' doesn't have a value"
213                 end
214
215                 if option:option().type == "enum" and option:value() then
216                         if not option:option().values or
217                            not option:option().values[option:value()]
218                         then
219                                 return false, "Value '" .. ( option:value() or '<nil>' ) ..
220                                         "' of given option '" .. option:cid() ..
221                                         "' is not defined in enum { " ..
222                                         table.concat(luci.util.keys(option:option().values),", ") ..
223                                         " }"
224                         end
225                 end
226
227                 if option:option().datatype and option:value() then
228                         if self.datatypes[option:option().datatype] then
229                                 if not self.datatypes[option:option().datatype](
230                                         option:value()
231                                 ) then
232                                         return false, "Value '" .. ( option:value() or '<nil>' ) ..
233                                                 "' of given option '" .. option:cid() ..
234                                                 "' doesn't validate as datatype '" ..
235                                                 option:option().datatype .. "'"
236                                 end
237                         else
238                                 return false, "Unknown datatype '" ..
239                                         option:option().datatype .. "' encountered"
240                         end
241                 end
242
243                 if not nodeps then
244                         return luci.uvl.dependencies.check( self, option )
245                 end
246
247                 local ok, err = luci.uvl.validation.check( self, option )
248                 if not ok and STRICT_EXTERNAL_VALIDATORS then
249                         return false, self.log.validator_error( option, err )
250                 end
251         end
252
253         return true, nil
254 end
255
256 --- Find all parts of given scheme and construct validation tree
257 -- @param scheme        Name of the scheme to parse
258 -- @return                      Parsed scheme
259 function UVL.read_scheme( self, scheme )
260         local schemes = { }
261
262         for i, file in ipairs( luci.fs.glob(self.schemedir .. '/*/' .. scheme) ) do
263                 _assert( luci.fs.access(file), "Can't access file '%s'", file )
264
265                 self.uci.set_confdir( luci.fs.dirname(file) )
266                 self.uci.load( luci.fs.basename(file) )
267
268                 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
269         end
270
271         return self:_read_scheme_parts( scheme, schemes )
272 end
273
274 -- Process all given parts and construct validation tree
275 function UVL._read_scheme_parts( self, scheme, schemes )
276
277         -- helper function to construct identifiers for given elements
278         local function _id( c, t )
279                 if c == TYPE_SECTION then
280                         return string.format(
281                                 "section '%s.%s'",
282                                         scheme, t.name or '?' )
283                 elseif c == TYPE_VARIABLE then
284                         return string.format(
285                                 "variable '%s.%s.%s'",
286                                         scheme, t.section or '?.?', t.name or '?' )
287                 elseif c == TYPE_ENUM then
288                         return string.format(
289                                 "enum '%s.%s.%s'",
290                                         scheme, t.variable or '?.?.?', t.value or '?' )
291                 end
292         end
293
294         -- helper function to check for required fields
295         local function _req( c, t, r )
296                 for i, v in ipairs(r) do
297                         _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
298                 end
299         end
300
301         -- helper function to validate references
302         local function _ref( c, t )
303                 local k
304                 if c == TYPE_SECTION then
305                         k = "package"
306                 elseif c == TYPE_VARIABLE then
307                         k = "section"
308                 elseif c == TYPE_ENUM then
309                         k = "variable"
310                 end
311
312                 local r = luci.util.split( t[k], "." )
313                 r[1] = ( #r[1] > 0 and r[1] or scheme )
314
315                 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
316
317                 return r
318         end
319
320         -- helper function to read bools
321         local function _bool( v )
322                 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
323         end
324
325         -- Step 1: get all sections
326         for i, conf in ipairs( schemes ) do
327                 for k, v in pairs( conf ) do
328                         if v['.type'] == 'section' then
329
330                                 _req( TYPE_SECTION, v, { "name", "package" } )
331
332                                 local r = _ref( TYPE_SECTION, v )
333
334                                 self.packages[r[1]] =
335                                         self.packages[r[1]] or {
336                                                 ["sections"]  = { };
337                                                 ["variables"] = { };
338                                         }
339
340                                 local p = self.packages[r[1]]
341                                           p.sections[v.name]  = p.sections[v.name]  or { }
342                                           p.variables[v.name] = p.variables[v.name] or { }
343
344                                 local s = p.sections[v.name]
345
346                                 for k, v2 in pairs(v) do
347                                         if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
348                                                 if k:match("^depends") then
349                                                         s["depends"] = _assert(
350                                                                 self:_read_dependency( v2, s["depends"] ),
351                                                                 "Section '%s' in scheme '%s' has malformed " ..
352                                                                 "dependency specification in '%s'",
353                                                                 v.name or '<nil>', scheme or '<nil>', k
354                                                         )
355                                                 elseif k == "dynamic" or k == "unique" or k == "required" then
356                                                         s[k] = _bool(v2)
357                                                 else
358                                                         s[k] = v2
359                                                 end
360                                         end
361                                 end
362
363                                 s.dynamic  = s.dynamic  or false
364                                 s.unique   = s.unique   or false
365                                 s.required = s.required or false
366                         end
367                 end
368         end
369
370         -- Step 2: get all variables
371         for i, conf in ipairs( schemes ) do
372                 for k, v in pairs( conf ) do
373                         if v['.type'] == "variable" then
374
375                                 _req( TYPE_VARIABLE, v, { "name", "section" } )
376
377                                 local r = _ref( TYPE_VARIABLE, v )
378
379                                 local p = _assert( self.packages[r[1]],
380                                         "Variable '%s' in scheme '%s' references unknown package '%s'",
381                                         v.name, scheme, r[1] )
382
383                                 local s = _assert( p.variables[r[2]],
384                                         "Variable '%s' in scheme '%s' references unknown section '%s'",
385                                         v.name, scheme, r[2] )
386
387                                 s[v.name] = s[v.name] or { }
388
389                                 local t = s[v.name]
390
391                                 for k, v2 in pairs(v) do
392                                         if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
393                                                 if k:match("^depends") then
394                                                         t["depends"] = _assert(
395                                                                 self:_read_dependency( v2, t["depends"] ),
396                                                                 'Invalid reference "%s" in "%s.%s.%s"',
397                                                                 v2, v.name, scheme, k
398                                                         )
399                                                 elseif k:match("^validator") then
400                                                         t["validators"] = _assert(
401                                                                 self:_read_validator( v2, t["validators"] ),
402                                                                 "Variable '%s' in scheme '%s' has malformed " ..
403                                                                 "validator specification in '%s'",
404                                                                 v.name, scheme, k
405                                                         )
406                                                 elseif k == "required" then
407                                                         t[k] = _bool(v2)
408                                                 else
409                                                         t[k] = v2
410                                                 end
411                                         end
412                                 end
413
414                                 t.type     = t.type     or "variable"
415                                 t.datatype = t.datatype or "string"
416                                 t.required = t.required or false
417                         end
418                 end
419         end
420
421         -- Step 3: get all enums
422         for i, conf in ipairs( schemes ) do
423                 for k, v in pairs( conf ) do
424                         if v['.type'] == "enum" then
425
426                                 _req( TYPE_ENUM, v, { "value", "variable" } )
427
428                                 local r = _ref( TYPE_ENUM, v )
429
430                                 local p = _assert( self.packages[r[1]],
431                                         "Enum '%s' in scheme '%s' references unknown package '%s'",
432                                         v.value, scheme, r[1] )
433
434                                 local s = _assert( p.variables[r[2]],
435                                         "Enum '%s' in scheme '%s' references unknown section '%s'",
436                                         v.value, scheme, r[2] )
437
438                                 local t = _assert( s[r[3]],
439                                         "Enum '%s' in scheme '%s', section '%s' references " ..
440                                         "unknown variable '%s'",
441                                         v.value, scheme, r[2], r[3] )
442
443                                 _assert( t.type == "enum",
444                                         "Enum '%s' in scheme '%s', section '%s' references " ..
445                                         "variable '%s' with non enum type '%s'",
446                                         v.value, scheme, r[2], r[3], t.type )
447
448                                 if not t.values then
449                                         t.values = { [v.value] = v.title or v.value }
450                                 else
451                                         t.values[v.value] = v.title or v.value
452                                 end
453
454                                 if v.default then
455                                         _assert( not t.default,
456                                                 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
457                                                 "the default value of variable '%s'",
458                                                 v.value, scheme, r[2], v.variable )
459
460                                         t.default = v.value
461                                 end
462                         end
463                 end
464         end
465
466         return self
467 end
468
469 -- Read a dependency specification
470 function UVL._read_dependency( self, value, deps )
471         local parts     = luci.util.split( value, "%s*,%s*", nil, true )
472         local condition = { }
473
474         for i, val in ipairs(parts) do
475                 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
476
477                 if k and (
478                         k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
479                         k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
480                         k:match("^%$?[a-zA-Z0-9_]+$")
481                 ) then
482                         condition[k] = v or true
483                 else
484                         return nil
485                 end
486         end
487
488         if not deps then
489                 deps = { condition }
490         else
491                 table.insert( deps, condition )
492         end
493
494         return deps
495 end
496
497 -- Read a validator specification
498 function UVL._read_validator( self, value, validators )
499         if value then
500                 local validator
501
502                 if value:match("^exec:") then
503                         validator = value:gsub("^exec:","")
504                 elseif value:match("^lua:") then
505                         validator = self:_resolve_function( (value:gsub("^lua:","") ) )
506                 end
507
508                 if validator then
509                         if not validators then
510                                 validators = { validator }
511                         else
512                                 table.insert( validators, validator )
513                         end
514
515                         return validators
516                 end
517         end
518 end
519
520 -- Resolve given path
521 function UVL._resolve_function( self, value )
522         local path = luci.util.split(value, ".")
523
524         for i=1, #path-1 do
525                 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
526                 if stat and mod then
527                         for j=i+1, #path-1 do
528                                 if not type(mod) == "table" then
529                                         break
530                                 end
531                                 mod = mod[path[j]]
532                                 if not mod then
533                                         break
534                                 end
535                         end
536                         mod = type(mod) == "table" and mod[path[#path]] or nil
537                         if type(mod) == "function" then
538                                 return mod
539                         end
540                 end
541         end
542 end
543
544
545 section = luci.util.class()
546
547 function section.__init__(self, scheme, co, st, c, s)
548         self.csection = co[s]
549         self.ssection = scheme.packages[c].sections[st]
550         self.cref     = { c, s }
551         self.sref     = { c, st }
552         self.scheme   = scheme
553         self.config   = co
554         self.type     = luci.uvl.TYPE_SECTION
555 end
556
557 function section.cid(self)
558         return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
559 end
560
561 function section.sid(self)
562         return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
563 end
564
565 function section.values(self)
566         return self.csection
567 end
568
569 function section.section(self)
570         return self.ssection
571 end
572
573 function section.variables(self)
574         local v = { }
575         if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
576                 for o, _ in pairs(
577                         self.scheme.packages[self.sref[1]].variables[self.sref[2]]
578                 ) do
579                         table.insert( v, luci.uvl.option(
580                                 self.scheme, self.config, self.sref[2],
581                                 self.cref[1], self.cref[2], o
582                         ) )
583                 end
584         end
585         return v
586 end
587
588
589 option = luci.util.class()
590
591 function option.__init__(self, scheme, co, st, c, s, o)
592         self.coption = co[s] and co[s][o] or nil
593         self.soption = scheme.packages[c].variables[st][o]
594         self.cref    = { c, s, o }
595         self.sref    = { c, st, o }
596         self.scheme  = scheme
597         self.config  = co
598         self.type    = luci.uvl.TYPE_OPTION
599 end
600
601 function option.cid(self)
602         return ( self.cref[1] or '?' ) .. '.' ..
603                    ( self.cref[2] or '?' ) .. '.' ..
604                    ( self.cref[3] or '?' )
605 end
606
607 function option.sid(self)
608         return ( self.sref[1] or '?' ) .. '.' ..
609                    ( self.sref[2] or '?' ) .. '.' ..
610                    ( self.sref[3] or '?' )
611 end
612
613 function option.value(self)
614         return self.coption
615 end
616
617 function option.option(self)
618         return self.soption
619 end
620
621 function option.section(self)
622         return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
623 end