libs/uvl:
[project/luci.git] / libs / uvl / luasrc / uvl.lua
1 --[[
2
3 UCI Validation Layer - Main Library
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11                 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17
18 --- UVL - UCI Validation Layer
19 -- @class       module
20 -- @cstyle      instance
21
22 local fs = require "luci.fs"
23 local uci = require "luci.model.uci"
24 local util = require "luci.util"
25 local table = require "table"
26 local string = require "string"
27
28 local require, pcall, ipairs, pairs = require, pcall, ipairs, pairs
29 local type, error, tonumber, tostring = type, error, tonumber, tostring
30 local unpack, loadfile = unpack, loadfile
31
32 module "luci.uvl"
33
34 local ERR = require "luci.uvl.errors"
35 local datatypes = require "luci.uvl.datatypes"
36 local validation = require "luci.uvl.validation"
37 local dependencies = require "luci.uvl.dependencies"
38
39 local TYPE_SCHEME   = 0x00
40 local TYPE_CONFIG   = 0x01
41 local TYPE_SECTION  = 0x02
42 local TYPE_OPTION   = 0x03
43 local TYPE_ENUM     = 0x04
44
45 --- Boolean; default true;
46 -- treat sections found in config but not in scheme as error
47 STRICT_UNKNOWN_SECTIONS    = true
48
49 --- Boolean; default true;
50 -- treat options found in config but not in scheme as error
51 STRICT_UNKNOWN_OPTIONS     = true
52
53 --- Boolean; default true;
54 -- treat failed external validators as error
55 STRICT_EXTERNAL_VALIDATORS = true
56
57 --- Boolean; default true;
58 -- treat list values stored as options like errors
59 STRICT_LIST_TYPE           = true
60
61
62 local default_schemedir = "/lib/uci/schema"
63 local default_savedir = "/tmp/.uvl"
64
65
66 --- Object constructor
67 -- @class                       function
68 -- @name                        UVL
69 -- @param schemedir     Path to the scheme directory (optional)
70 -- @return                      Instance object
71 UVL = util.class()
72
73 function UVL.__init__( self, schemedir, configdir )
74         self.schemedir  = schemedir or default_schemedir
75         self.configdir  = configdir
76         self.packages   = { }
77         self.beenthere  = { }
78         self.depseen    = { }
79         self.uci                = uci
80         self.err                = ERR
81         self.dep                = dependencies
82         self.datatypes  = datatypes
83 end
84
85
86 --- Parse given scheme and return the scheme tree.
87 -- @param scheme        Name of the scheme to parse
88 -- @return                      Table containing the parsed scheme or nil on error
89 -- @return                      String containing the reason for errors (if any)
90 function UVL.get_scheme( self, scheme )
91         if not self.packages[scheme] then
92                 local ok, err = self:read_scheme( scheme )
93                 if not ok then
94                         return nil, err
95                 end
96         end
97         return self.packages[scheme], nil
98 end
99
100 --- Validate given configuration, section or option.
101 -- @param config        Name of the configuration to validate
102 -- @param section       Name of the section to validate (optional)
103 -- @param option        Name of the option to validate (optional)
104 -- @return                      Boolean indicating whether the given config validates
105 -- @return                      String containing the reason for errors (if any)
106 function UVL.validate( self, config, section, option )
107         if config and section and option then
108                 return self:validate_option( config, section, option )
109         elseif config and section then
110                 return self:validate_section( config, section )
111         elseif config then
112                 return self:validate_config( config )
113         end
114 end
115
116 --- Validate given configuration.
117 -- @param cfg   Name of the configuration to validate
118 -- @return                      Boolean indicating whether the given config validates
119 -- @return                      String containing the reason for errors (if any)
120 function UVL.validate_config( self, cfg, uci )
121
122         if not self.packages[cfg] then
123                 local ok, err = self:read_scheme(cfg)
124                 if not ok then
125                         return false, err
126                 end
127         end
128
129         local co = config( self, uci or cfg, uci and cfg )
130         local sc = { }
131
132         self.beenthere = { }
133         self.depseen   = { }
134
135         if not co:config() then
136                 return false, co:errors()
137         end
138
139         local function _uci_foreach( type, func )
140                 for k, v in pairs(co:config()) do
141                         if v['.type'] == type then
142                                 sc[type] = sc[type] + 1
143                                 local ok, err = func( k, v )
144                                 if not ok then co:error(err) end
145                         end
146                 end
147         end
148
149         for k, v in pairs( self.packages[cfg].sections ) do
150                 sc[k] = 0
151                 _uci_foreach( k,
152                         function(s)
153                                 return self:_validate_section( co:section(s) )
154                         end
155                 )
156         end
157
158         if STRICT_UNKNOWN_SECTIONS then
159                 for k, v in pairs(co:config()) do
160                         local so = co:section(k)
161                         if not self.beenthere[so:cid()] then
162                                 co:error(ERR.SECT_UNKNOWN(so))
163                         end
164                 end
165         end
166
167         for _, k in ipairs(util.keys(sc)) do
168                 local so = co:section(k)
169                 if so:scheme('required') and sc[k] == 0 then
170                         co:error(ERR.SECT_REQUIRED(so))
171                 elseif so:scheme('unique') and sc[k] > 1 then
172                         co:error(ERR.SECT_UNIQUE(so))
173                 end
174         end
175
176         return co:ok(), co:errors()
177 end
178
179 --- Validate given config section.
180 -- @param config        Name of the configuration to validate
181 -- @param section       Name of the section to validate
182 -- @return                      Boolean indicating whether the given config validates
183 -- @return                      String containing the reason for errors (if any)
184 function UVL.validate_section( self, cfg, section, uci )
185
186         if not self.packages[cfg] then
187                 local ok, err = self:read_scheme( cfg )
188                 if not ok then
189                         return false, err
190                 end
191         end
192
193         local co = config( self, uci or cfg, uci and cfg )
194         local so = co:section( section )
195
196         self.beenthere = { }
197         self.depseen   = { }
198
199         if not co:config() then
200                 return false, co:errors()
201         end
202
203         if so:config() then
204                 return self:_validate_section( so )
205         else
206                 return false, ERR.SECT_NOTFOUND(so)
207         end
208 end
209
210 --- Validate given config option.
211 -- @param config        Name of the configuration to validate
212 -- @param section       Name of the section to validate
213 -- @param option        Name of the option to validate
214 -- @return                      Boolean indicating whether the given config validates
215 -- @return                      String containing the reason for errors (if any)
216 function UVL.validate_option( self, cfg, section, option, uci )
217
218         if not self.packages[cfg] then
219                 local ok, err = self:read_scheme( cfg )
220                 if not ok then
221                         return false, err
222                 end
223         end
224
225         local co = config( self, uci or cfg, uci and cfg )
226         local so = co:section( section )
227         local oo = so:option( option )
228
229         if not co:config() then
230                 return false, co:errors()
231         end
232
233         if so:config() and oo:config() then
234                 return self:_validate_option( oo )
235         else
236                 return false, ERR.OPT_NOTFOUND(oo)
237         end
238 end
239
240
241 function UVL._validate_section( self, section )
242
243         self.beenthere[section:cid()] = true
244
245         if section:config() then
246                 if section:scheme('named') == true and
247                    section:config('.anonymous') == true
248                 then
249                         return false, ERR.SECT_NAMED(section)
250                 end
251
252                 for _, v in ipairs(section:variables()) do
253                         local ok, err = self:_validate_option( v )
254                         if not ok and (
255                                 v:scheme('required') or v:scheme('type') == "enum" or (
256                                         not err:is(ERR.ERR_DEP_NOTEQUAL) and
257                                         not err:is(ERR.ERR_DEP_NOVALUE)
258                                 )
259                         ) then
260                                 section:error(err)
261                         end
262                 end
263
264                 local ok, err = dependencies.check( self, section )
265                 if not ok then
266                         section:error(err)
267                 end
268         else
269                 return false, ERR.SECT_NOTFOUND(section)
270         end
271
272         if STRICT_UNKNOWN_OPTIONS and not section:scheme('dynamic') then
273                 for k, v in pairs(section:config()) do
274                         local oo = section:option(k)
275                         if k:sub(1,1) ~= "." and not self.beenthere[oo:cid()] then
276                                 section:error(ERR.OPT_UNKNOWN(oo))
277                         end
278                 end
279         end
280
281         return section:ok(), section:errors()
282 end
283
284 function UVL._validate_option( self, option, nodeps )
285
286         self.beenthere[option:cid()] = true
287
288         if not option:scheme() and not option:parent():scheme('dynamic') then
289                 if STRICT_UNKNOWN_OPTIONS then
290                         return false, option:error(ERR.OPT_UNKNOWN(option))
291                 else
292                         return true
293                 end
294
295         elseif option:scheme() then
296                 if not nodeps then
297                         local ok, err = dependencies.check( self, option )
298                         if not ok then
299                                 if not err:is_all(
300                                         ERR.ERR_OPT_REQUIRED,
301                                         ERR.ERR_DEP_NOTEQUAL,
302                                         ERR.ERR_DEP_NOVALUE
303                                 ) then
304                                         option:error(err)
305                                         return false, option:errors()
306                                 else
307                                         return true
308                                 end
309                         end
310                 end
311
312                 if option:scheme('required') and not option:value() then
313                         return false, option:error(ERR.OPT_REQUIRED(option))
314
315                 elseif option:value() then
316                         local val = option:value()
317
318                         if option:scheme('type') == "reference" or
319                            option:scheme('type') == "enum"
320                         then
321                                 local scheme_values = option:scheme('values') or { }
322                                 local config_values = ( type(val) == "table" and val or { val } )
323                                 for _, v in ipairs(config_values) do
324                                         if not scheme_values[v] then
325                                                 return false, option:error( ERR.OPT_BADVALUE(
326                                                         option, { v, util.serialize_data(
327                                                                 util.keys(scheme_values)
328                                                         ) }
329                                                 ) )
330                                         end
331                                 end
332                         elseif option:scheme('type') == "list" then
333                                 if type(val) ~= "table" and STRICT_LIST_TYPE then
334                                         return false, option:error(ERR.OPT_NOTLIST(option))
335                                 end
336                         end
337
338                         if option:scheme('datatype') then
339                                 local dt = option:scheme('datatype')
340
341                                 if self.datatypes[dt] then
342                                         val = ( type(val) == "table" and val or { val } )
343                                         for i, v in ipairs(val) do
344                                                 if not self.datatypes[dt]( v ) then
345                                                         return false, option:error(
346                                                                 ERR.OPT_INVVALUE(option, { v, dt })
347                                                         )
348                                                 end
349                                         end
350                                 else
351                                         return false, option:error(ERR.OPT_DATATYPE(option, dt))
352                                 end
353                         end
354
355                         val = ( type(val) == "table" and val or { val } )
356                         for _, v in ipairs(val) do
357                                 if option:scheme('minlength') then
358                                         if #v < option:scheme('minlength') then
359                                                 return false, option:error(ERR.OPT_RANGE(option))
360                                         end
361                                 end
362
363                                 if option:scheme('maxlength') then
364                                         if #v > option:scheme('maxlength') then
365                                                 return false, option:error(ERR.OPT_RANGE(option))
366                                         end
367                                 end
368
369                                 local w = tonumber(v)
370
371                                 if option:scheme('minimum') then
372                                         if not w or w < option:scheme('minimum') then
373                                                 return false, option:error(ERR.OPT_RANGE(option))
374                                         end
375                                 end
376
377                                 if option:scheme('maximum') then
378                                         if not w or w > option:scheme('maximum') then
379                                                 return false, option:error(ERR.OPT_RANGE(option))
380                                         end
381                                 end
382                         end
383                 end
384
385                 local ok, err = validation.check( self, option )
386                 if not ok and STRICT_EXTERNAL_VALIDATORS then
387                         return false, option:error(err)
388                 end
389         end
390
391         return option:ok(), option:errors()
392 end
393
394 --- Find all parts of given scheme and construct validation tree.
395 -- This is normally done on demand, so you don't have to call this function
396 -- by yourself.
397 -- @param shm   Name of the scheme to parse
398 -- @param alias         Create an alias for the loaded scheme
399 function UVL.read_scheme( self, shm, alias )
400
401         local so = scheme( self, shm )
402         local bc = "%s/bytecode/%s.lua" %{ self.schemedir, shm }
403
404         if not fs.access(bc) then
405                 local files = fs.glob(self.schemedir .. '/*/' .. shm)
406
407                 if files then
408                         local ok, err
409                         for i, file in ipairs( files ) do
410                                 if not fs.access(file) then
411                                         return false, so:error(ERR.SME_READ(so,file))
412                                 end
413
414                                 local uci = uci.cursor( fs.dirname(file), default_savedir )
415
416                                 local sname = fs.basename(file)
417                                 local sd, err = uci:load( sname )
418
419                                 if not sd then
420                                         return false, ERR.UCILOAD(so, err)
421                                 end
422
423                                 ok, err = pcall(function()
424                                         uci:foreach(sname, "package", function(s)
425                                                 self:_parse_package(so, s[".name"], s)
426                                         end)
427                                         uci:foreach(sname, "section", function(s)
428                                                 self:_parse_section(so, s[".name"], s)
429                                         end)
430                                         uci:foreach(sname, "variable", function(s)
431                                                 self:_parse_var(so, s[".name"], s)
432                                         end)
433                                         uci:foreach(sname, "enum", function(s)
434                                                 self:_parse_enum(so, s[".name"], s)
435                                         end)
436
437                                 end)
438                         end
439
440                         if ok and alias then self.packages[alias] = self.packages[shm] end
441                         return ok and self, err
442                 else
443                         return false, so:error(ERR.SME_FIND(so, self.schemedir))
444                 end
445         else
446                 local sc = loadfile(bc)
447                 if sc then
448                         self.packages[shm] = sc()
449                         return true
450                 else
451                         return false, so:error(ERR.SME_READ(so,bc))
452                 end
453         end
454 end
455
456 -- helper function to check for required fields
457 local function _req( t, n, c, r )
458         for i, v in ipairs(r) do
459                 if not c[v] then
460                         local p, o = scheme:sid(), nil
461
462                         if t == TYPE_SECTION then
463                                 o = section( scheme, nil, p, n )
464                         elseif t == TYPE_OPTION then
465                                 o = option( scheme, nil, p, '(nil)', n )
466                         elseif t == TYPE_ENUM then
467                                 o = enum( scheme, nil, p, '(nil)', '(nil)', n )
468                         end
469
470                         return false, ERR.SME_REQFLD(o,v)
471                 end
472         end
473         return true
474 end
475
476 -- helper function to validate references
477 local function _ref( c, t )
478         local r, k, n = {}
479         if c == TYPE_SECTION then
480                 k = "package"
481                 n = 1
482         elseif c == TYPE_OPTION then
483                 k = "section"
484                 n = 2
485         elseif c == TYPE_ENUM then
486                 k = "variable"
487                 n = 3
488         end
489
490         for o in t[k]:gmatch("[^.]+") do
491                 r[#r+1] = o
492         end
493         r[1] = ( #r[1] > 0 and r[1] or scheme:sid() )
494
495         if #r ~= n then
496                 return false, ERR.SME_BADREF(scheme, k)
497         end
498
499         return r
500 end
501
502 -- helper function to read bools
503 local function _bool( v )
504         return ( v == "true" or v == "yes" or v == "on" or v == "1" )
505 end
506
507 -- Step 0: get package meta information
508 function UVL._parse_package(self, scheme, k, v)
509         local sid = scheme:sid()
510         local pkg = self.packages[sid] or {
511                 ["name"]      = sid;
512                 ["sections"]  = { };
513                 ["variables"] = { };
514         }
515
516         pkg.title = v.title
517         pkg.description = v.description
518
519         self.packages[sid] = pkg
520 end
521
522 -- Step 1: get all sections
523 function UVL._parse_section(self, scheme, k, v)
524         local ok, err = _req( TYPE_SECTION, k, v, { "name", "package" } )
525         if err then error(scheme:error(err)) end
526
527         local r, err = _ref( TYPE_SECTION, v )
528         if err then error(scheme:error(err)) end
529
530         local p = self.packages[r[1]] or {
531                 ["name"]      = r[1];
532                 ["sections"]  = { };
533                 ["variables"] = { };
534         }
535         p.sections[v.name]  = p.sections[v.name]  or { }
536         p.variables[v.name] = p.variables[v.name] or { }
537         self.packages[r[1]] = p
538
539         local s  = p.sections[v.name]
540         local so = scheme:section(v.name)
541
542         for k, v2 in pairs(v) do
543                 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
544                         if k == "depends" then
545                                 s.depends = self:_read_dependency( v2, s.depends )
546                                 if not s.depends then
547                                         return false, scheme:error(
548                                                 ERR.SME_BADDEP(so, util.serialize_data(s.depends))
549                                         )
550                                 end
551                         elseif k == "dynamic" or k == "unique" or
552                                k == "required" or k == "named"
553                         then
554                                 s[k] = _bool(v2)
555                         else
556                                 s[k] = v2
557                         end
558                 end
559         end
560
561         s.dynamic  = s.dynamic  or false
562         s.unique   = s.unique   or false
563         s.required = s.required or false
564         s.named    = s.named    or false
565 end
566
567 -- Step 2: get all variables
568 function UVL._parse_var(self, scheme, k, v)
569         local ok, err = _req( TYPE_OPTION, k, v, { "name", "section" } )
570         if err then error(scheme:error(err)) end
571
572         local r, err = _ref( TYPE_OPTION, v )
573         if err then error(scheme:error(err)) end
574
575         local p = self.packages[r[1]]
576         if not p then
577                 error(scheme:error(
578                         ERR.SME_VBADPACK({scheme:sid(), '', v.name}, r[1])
579                 ))
580         end
581
582         local s = p.variables[r[2]]
583         if not s then
584                 error(scheme:error(
585                         ERR.SME_VBADSECT({scheme:sid(), '', v.name}, r[2])
586                 ))
587         end
588
589         s[v.name] = s[v.name] or { }
590
591         local t  = s[v.name]
592         local so = scheme:section(r[2])
593         local to = so:option(v.name)
594
595         for k, v2 in pairs(v) do
596                 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
597                         if k == "depends" then
598                                 t.depends = self:_read_dependency( v2, t.depends )
599                                 if not t.depends then
600                                         error(scheme:error(so:error(
601                                                 ERR.SME_BADDEP(to, util.serialize_data(v2))
602                                         )))
603                                 end
604                         elseif k == "validator" then
605                                 t.validators = self:_read_validator( v2, t.validators )
606                                 if not t.validators then
607                                         error(scheme:error(so:error(
608                                                 ERR.SME_BADVAL(to, util.serialize_data(v2))
609                                         )))
610                                 end
611                         elseif k == "valueof" then
612                                 local values, err = self:_read_reference( v2 )
613                                 if err then
614                                         error(scheme:error(so:error(
615                                                 ERR.REFERENCE(to, util.serialize_data(v2)):child(err)
616                                         )))
617                                 end
618                                 t.type   = "reference"
619                                 t.values = values
620                                 t.valueof = type(v2) == "table" and v2 or {v2}
621                         elseif k == "required" then
622                                 t[k] = _bool(v2)
623                         elseif k == "minlength" or k == "maxlength" or
624                    k == "minimum" or k == "maximum"
625             then
626                                 t[k] = tonumber(v2)
627                         else
628                                 t[k] = t[k] or v2
629                         end
630                 end
631         end
632
633         t.type     = t.type     or "variable"
634         t.datatype = t.datatype or "string"
635         t.required = t.required or false
636 end
637
638 -- Step 3: get all enums
639 function UVL._parse_enum(self, scheme, k, v)
640         local ok, err = _req( TYPE_ENUM, k, v, { "value", "variable" } )
641         if err then error(scheme:error(err)) end
642
643         local r, err = _ref( TYPE_ENUM, v )
644         if err then error(scheme:error(err)) end
645
646         local p = self.packages[r[1]]
647         if not p then
648                 error(scheme:error(
649                         ERR.SME_EBADPACK({scheme:sid(), '', '', v.value}, r[1])
650                 ))
651         end
652
653         local s = p.variables[r[2]]
654         if not s then
655                 error(scheme:error(
656                         ERR.SME_EBADSECT({scheme:sid(), '', '', v.value}, r[2])
657                 ))
658         end
659
660         local t = s[r[3]]
661         if not t then
662                 error(scheme:error(
663                         ERR.SME_EBADOPT({scheme:sid(), '', '', v.value}, r[3])
664                 ))
665         end
666
667
668         local so = scheme:section(r[2])
669         local oo = so:option(r[3])
670         local eo = oo:enum(v.value)
671
672         if t.type ~= "enum" and t.type ~= "reference" then
673                 error(scheme:error(ERR.SME_EBADTYPE(eo)))
674         end
675
676         if not t.values then
677                 t.values = { [v.value] = v.title or v.value }
678                 t.valuelist = { {value = v.value, title = v.title} }
679         else
680                 t.values[v.value] = v.title or v.value
681                 t.valuelist[#t.valuelist + 1] = {value = v.value, title = v.title}
682         end
683
684         if not t.enum_depends then
685                 t.enum_depends = { }
686         end
687
688         if v.default then
689                 if t.default then
690                         error(scheme:error(ERR.SME_EBADDEF(eo)))
691                 end
692                 t.default = v.value
693         end
694
695         if v.depends then
696                 t.enum_depends[v.value] = self:_read_dependency(
697                         v.depends, t.enum_depends[v.value]
698                 )
699
700                 if not t.enum_depends[v.value] then
701                         error(scheme:error(so:error(oo:error(
702                                 ERR.SME_BADDEP(eo, util.serialize_data(v.depends))
703                         ))))
704                 end
705         end
706 end
707
708 -- Read a dependency specification
709 function UVL._read_dependency( self, values, deps )
710         local expr = "%$?[%w_]+"
711         if values then
712                 values = ( type(values) == "table" and values or { values } )
713                 for _, value in ipairs(values) do
714                         local condition = { }
715                         for val in value:gmatch("[^,]+") do
716                                 local k, e, v = val:match("%s*([%w$_.]+)%s*(=?)%s*(.*)")
717
718                                 if k and (
719                                         k:match("^"..expr.."%."..expr.."%."..expr.."$") or
720                                         k:match("^"..expr.."%."..expr.."$") or
721                                         k:match("^"..expr.."$")
722                                 ) then
723                                         condition[k] = (e == '=') and v or true
724                                 else
725                                         return nil
726                                 end
727                         end
728
729                         if not deps then
730                                 deps = { condition }
731                         else
732                                 deps[#deps+1] = condition
733                         end
734                 end
735         end
736
737         return deps
738 end
739
740 -- Read a validator specification
741 function UVL._read_validator( self, values, validators )
742         if values then
743                 values = ( type(values) == "table" and values or { values } )
744                 for _, value in ipairs(values) do
745                         local validator
746
747                         if value:match("^exec:") then
748                                 validator = value:gsub("^exec:","")
749                         elseif value:match("^lua:") then
750                                 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
751                         elseif value:match("^regexp:") then
752                                 local pattern = value:gsub("^regexp:","")
753                                 validator = function( type, dtype, pack, sect, optn, ... )
754                                         local values = { ... }
755                                         for _, v in ipairs(values) do
756                                                 local ok, match =
757                                                         pcall( string.match, v, pattern )
758
759                                                 if not ok then
760                                                         return false, match
761                                                 elseif not match then
762                                                         return false,
763                                                                 'Value "%s" does not match pattern "%s"' % {
764                                                                         v, pattern
765                                                                 }
766                                                 end
767                                         end
768                                         return true
769                                 end
770                         end
771
772                         if validator then
773                                 if not validators then
774                                         validators = { validator }
775                                 else
776                                         validators[#validators+1] = validator
777                                 end
778                         else
779                                 return nil
780                         end
781                 end
782
783                 return validators
784         end
785 end
786
787 -- Read a reference specification (XXX: We should validate external configs too...)
788 function UVL._read_reference( self, values )
789         local val = { }
790         values = ( type(values) == "table" and values or { values } )
791
792         for _, value in ipairs(values) do
793                 local ref = util.split(value, ".")
794
795                 if #ref == 2 or #ref == 3 then
796                         local co = config( self, ref[1] )
797                         if not co:config() then return false, co:errors() end
798
799                         for k, v in pairs(co:config()) do
800                                 if v['.type'] == ref[2] then
801                                         if #ref == 2 then
802                                                 if v['.anonymous'] == true then
803                                                         return false, ERR.SME_INVREF('', value)
804                                                 end
805                                                 val[k] = k      -- XXX: title/description would be nice
806                                         elseif v[ref[3]] then
807                                                 val[v[ref[3]]] = v[ref[3]]  -- XXX: dito
808                                         end
809                                 end
810                         end
811                 else
812                         return false, ERR.SME_BADREF('', value)
813                 end
814         end
815
816         return val, nil
817 end
818
819 -- Resolve given path
820 function UVL._resolve_function( self, value )
821         local path = util.split(value, ".")
822
823         for i=1, #path-1 do
824                 local stat, mod = pcall(
825                         require, table.concat(path, ".", 1, i)
826                 )
827
828                 if stat and mod then
829                         for j=i+1, #path-1 do
830                                 if not type(mod) == "table" then
831                                         break
832                                 end
833                                 mod = mod[path[j]]
834                                 if not mod then
835                                         break
836                                 end
837                         end
838                         mod = type(mod) == "table" and mod[path[#path]] or nil
839                         if type(mod) == "function" then
840                                 return mod
841                         end
842                 end
843         end
844 end
845
846
847 --- Object representation of an uvl item - base class.
848 uvlitem = util.class()
849
850 function uvlitem.cid(self)
851         if #self.cref == 1 then
852                 return self.cref[1]
853         else
854                 local r = { unpack(self.cref) }
855                 local c = self.c
856                 if c and c[r[2]] and c[r[2]]['.anonymous'] and c[r[2]]['.index'] then
857                         r[2] = '@' .. c[r[2]]['.type'] ..
858                                    '[' .. tostring(c[r[2]]['.index']) .. ']'
859                 end
860                 return table.concat( r, '.' )
861         end
862 end
863
864 function uvlitem.sid(self)
865         return table.concat( self.sref, '.' )
866 end
867
868 function uvlitem.scheme(self, opt)
869         local s = self.s and self.s.packages
870         s = s      and s[self.sref[1]]
871         if #self.sref == 4 or #self.sref == 3 then
872                 s = s      and s.variables
873                 s = s      and s[self.sref[2]]
874                 s = s      and s[self.sref[3]]
875         elseif #self.sref == 2 then
876                 s = s      and s.sections
877                 s = s      and s[self.sref[2]]
878         end
879
880         if s and opt then
881                 return s[opt]
882         elseif s then
883                 return s
884         end
885 end
886
887 function uvlitem.config(self, opt)
888         local c = self.c
889
890         if #self.cref >= 2 and #self.cref <= 4 then
891                 c = c and self.c[self.cref[2]] or nil
892                 if #self.cref >= 3 then
893                         c = c and c[self.cref[3]] or nil
894                 end
895         end
896
897         if c and opt then
898                 return c[opt]
899         elseif c then
900                 return c
901         end
902 end
903
904 function uvlitem.title(self)
905         return self:scheme() and self:scheme('title') or
906                 self.cref[3] or self.cref[2] or self.cref[1]
907 end
908
909 function uvlitem.type(self)
910         if self.t == TYPE_CONFIG then
911                 return 'config'
912         elseif self.t == TYPE_SECTION then
913                 return 'section'
914         elseif self.t == TYPE_OPTION then
915                 return 'option'
916         elseif self.t == TYPE_ENUM then
917                 return 'enum'
918         end
919 end
920
921 function uvlitem.error(self, ...)
922         if not self.e then
923                 local errconst = { ERR.CONFIG, ERR.SECTION, ERR.OPTION, ERR.OPTION }
924                 self.e = errconst[#self.cref]( self )
925         end
926
927         return self.e:child( ... )
928 end
929
930 function uvlitem.errors(self)
931         return self.e
932 end
933
934 function uvlitem.ok(self)
935         return not self:errors()
936 end
937
938 function uvlitem.parent(self)
939         if self.p then
940                 return self.p
941         elseif #self.cref == 3 or #self.cref == 4 then
942                 return section( self.s, self.c, self.cref[1], self.cref[2] )
943         elseif #self.cref == 2 then
944                 return config( self.s, self.c, self.cref[1] )
945         else
946                 return nil
947         end
948 end
949
950 function uvlitem._loadconf(self, co, c, configdir)
951         co = co or self._configcache
952         if not co then
953                 local err
954                 co, err = uci.cursor(configdir):get_all(c)
955
956                 if err then
957                         self:error(ERR.UCILOAD(self, err))
958                 end
959
960                 self._configcache = co
961         end
962         return co
963 end
964
965
966 --- Object representation of a scheme.
967 -- @class       scheme
968 -- @cstyle      instance
969 -- @name        luci.uvl.scheme
970
971 --- Scheme instance constructor.
972 -- @class                       function
973 -- @name                        scheme
974 -- @param scheme        Scheme instance
975 -- @param co            Configuration data
976 -- @param c                     Configuration name
977 -- @return                      Config instance
978 scheme = util.class(uvlitem)
979
980 function scheme.__init__(self, scheme, co, c)
981         if not c then
982                 c, co = co, nil
983         end
984
985         self.cref = { c }
986         self.sref = { c }
987         self.c    = self:_loadconf(co, c, scheme.configdir)
988         self.s    = scheme
989         self.t    = TYPE_SCHEME
990 end
991
992 --- Add an error to scheme.
993 -- @return      Scheme error context
994 function scheme.error(self, ...)
995         if not self.e then self.e = ERR.SCHEME( self ) end
996         return self.e:child( ... )
997 end
998
999 --- Get an associated config object.
1000 -- @return      Config instance
1001 function scheme.config(self)
1002         local co = config( self.s, self.cref[1] )
1003               co.p = self
1004
1005         return co
1006 end
1007
1008 --- Get all section objects associated with this scheme.
1009 -- @return      Table containing all associated luci.uvl.section instances
1010 function scheme.sections(self)
1011         local v = { }
1012         if self.s.packages[self.sref[1]].sections then
1013                 for o, _ in pairs( self.s.packages[self.sref[1]].sections ) do
1014                         v[#v+1] = option(
1015                                 self.s, self.c, self.cref[1], self.cref[2], o
1016                         )
1017                 end
1018         end
1019         return v
1020 end
1021
1022 --- Get an associated section object.
1023 -- @param s     Section to select
1024 -- @return      Section instance
1025 function scheme.section(self, s)
1026         local so = section( self.s, self.c, self.cref[1], s )
1027               so.p = self
1028
1029         return so
1030 end
1031
1032
1033 --- Object representation of a config.
1034 -- @class       config
1035 -- @cstyle      instance
1036 -- @name        luci.uvl.config
1037
1038 --- Config instance constructor.
1039 -- @class                       function
1040 -- @name                        config
1041 -- @param scheme        Scheme instance
1042 -- @param co            Configuration data
1043 -- @param c                     Configuration name
1044 -- @return                      Config instance
1045 config = util.class(uvlitem)
1046
1047 function config.__init__(self, scheme, co, c)
1048         if not c then
1049                 c, co = co, nil
1050         end
1051         self.cref = { c }
1052         self.sref = { c }
1053         self.c    = self:_loadconf(co, c, scheme.configdir)
1054         self.s    = scheme
1055         self.t    = TYPE_CONFIG
1056 end
1057
1058 --- Get all section objects associated with this config.
1059 -- @return      Table containing all associated luci.uvl.section instances
1060 function config.sections(self)
1061         local v = { }
1062         if self.s.packages[self.sref[1]].sections then
1063                 for o, _ in pairs( self.s.packages[self.sref[1]].sections ) do
1064                         v[#v+1] = option(
1065                                 self.s, self.c, self.cref[1], self.cref[2], o
1066                         )
1067                 end
1068         end
1069         return v
1070 end
1071
1072 --- Get an associated section object.
1073 -- @param s     Section to select
1074 -- @return      Section instance
1075 function config.section(self, s)
1076         local so = section( self.s, self.c, self.cref[1], s )
1077               so.p = self
1078
1079         return so
1080 end
1081
1082
1083 --- Object representation of a scheme/config section.
1084 -- @class       module
1085 -- @cstyle      instance
1086 -- @name        luci.uvl.section
1087
1088 --- Section instance constructor.
1089 -- @class                       function
1090 -- @name                        section
1091 -- @param scheme        Scheme instance
1092 -- @param co            Configuration data
1093 -- @param c                     Configuration name
1094 -- @param s                     Section name
1095 -- @return                      Section instance
1096 section = util.class(uvlitem)
1097
1098 function section.__init__(self, scheme, co, c, s)
1099         self.cref = { c, s }
1100         self.sref = { c, co and co[s] and co[s]['.type'] or s }
1101         self.c    = self:_loadconf(co, c, scheme.configdir)
1102         self.s    = scheme
1103         self.t    = TYPE_SECTION
1104 end
1105
1106 --- Get all option objects associated with this section.
1107 -- @return      Table containing all associated luci.uvl.option instances
1108 function section.variables(self)
1109         local v = { }
1110         if self.s.packages[self.sref[1]].variables[self.sref[2]] then
1111                 for o, _ in pairs(
1112                         self.s.packages[self.sref[1]].variables[self.sref[2]]
1113                 ) do
1114                         v[#v+1] = option(
1115                                 self.s, self.c, self.cref[1], self.cref[2], o
1116                         )
1117                 end
1118         end
1119         return v
1120 end
1121
1122 --- Get an associated option object.
1123 -- @param o     Option to select
1124 -- @return      Option instance
1125 function section.option(self, o)
1126         local oo = option( self.s, self.c, self.cref[1], self.cref[2], o )
1127               oo.p = self
1128
1129         return oo
1130 end
1131
1132
1133 --- Object representation of a scheme/config option.
1134 -- @class       module
1135 -- @cstyle      instance
1136 -- @name        luci.uvl.option
1137
1138 --- Section instance constructor.
1139 -- @class                       function
1140 -- @name                        option
1141 -- @param scheme        Scheme instance
1142 -- @param co            Configuration data
1143 -- @param c                     Configuration name
1144 -- @param s                     Section name
1145 -- @param o                     Option name
1146 -- @return                      Option instance
1147 option = util.class(uvlitem)
1148
1149 function option.__init__(self, scheme, co, c, s, o)
1150         self.cref = { c, s, o }
1151         self.sref = { c, co and co[s] and co[s]['.type'] or s, o }
1152         self.c    = self:_loadconf(co, c, scheme.configdir)
1153         self.s    = scheme
1154         self.t    = TYPE_OPTION
1155 end
1156
1157 --- Get the value of this option.
1158 -- @return      The associated configuration value
1159 function option.value(self)
1160         local v = self:config() or self:scheme('default')
1161         if v and self:scheme('multival') then
1162                 v = util.split( v, "%s+", nil, true )
1163         end
1164         return v
1165 end
1166
1167 --- Get the associated section information in scheme.
1168 -- @return      Table containing the scheme properties
1169 function option.section(self)
1170         return self.s.packages[self.sref[1]].sections[self.sref[2]]
1171 end
1172
1173 --- Construct an enum object instance from given or default value.
1174 -- @param v     Value to select
1175 -- @return      Enum instance for selected value
1176 function option.enum(self, val)
1177         return enum(
1178                 self.s, self.c,
1179                 self.cref[1], self.cref[2], self.cref[3],
1180                 val or self:value()
1181         )
1182 end
1183
1184
1185 --- Object representation of a enum value.
1186 -- @class       module
1187 -- @cstyle      instance
1188 -- @name        luci.uvl.enum
1189
1190 --- Section instance constructor.
1191 -- @class                       function
1192 -- @name                        enum
1193 -- @param scheme        Scheme instance
1194 -- @param co            Configuration data
1195 -- @param c                     Configuration name
1196 -- @param s                     Section name
1197 -- @param o                     Enum name
1198 -- @param v                     Enum value
1199 -- @return                      Enum value instance
1200 enum = util.class(option)
1201
1202 function enum.__init__(self, scheme, co, c, s, o, v)
1203         self.cref = { c, s, o, v }
1204         self.sref = { c, co and co[s] and co[s]['.type'] or s, o, v }
1205         self.c    = self:_loadconf(co, c, scheme.configdir)
1206         self.s    = scheme
1207         self.t    = TYPE_ENUM
1208 end