Integrate basic UCI config file validation support
authorFelix Fietkau <nbd@openwrt.org>
Mon, 26 Feb 2007 20:04:04 +0000 (20:04 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Mon, 26 Feb 2007 20:04:04 +0000 (20:04 +0000)
Needs more testing and validation is not enforced yet

Code contributed by Fraunhofer Fokus

SVN-Revision: 6391

package/base-files/files/bin/uci
package/base-files/files/etc/functions.sh
package/base-files/files/lib/config/parse_spec.awk [new file with mode: 0644]
package/base-files/files/lib/config/specs/network.spec [new file with mode: 0644]
package/base-files/files/lib/config/uci.sh [changed mode: 0755->0644]
package/base-files/files/lib/config/validate.sh [new file with mode: 0644]
package/base-files/files/lib/config/validate_config.awk [new file with mode: 0644]
package/base-files/files/lib/config/validate_spec.awk [new file with mode: 0644]

index 9b50380..f8e08f8 100755 (executable)
@@ -1,8 +1,8 @@
 #!/bin/sh
 # Shell script for interacting with config files
 #
-# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
-# Copyright (C) 2006 by Felix Fietkau <nbd@openwrt.org>
+# Copyright (C) 2006        Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+# Copyright (C) 2006,2007      Felix Fietkau <nbd@openwrt.org>
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,8 +18,8 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
-. /etc/functions.sh
-include /lib/config
+. $UCI_ROOT/etc/functions.sh
+include $UCI_ROOT/lib/config
 
 SEP="[^0-9A-Za-z_]"
 
@@ -101,7 +101,7 @@ do_show() {
                exit 1
        }
        
-       for package in ${PACKAGE:-$(cd /etc/config; ls)}; do
+       for package in ${PACKAGE:-$(cd $UCI_ROOT/etc/config; ls)}; do
                SECTION=""
        
                config_cb() {
@@ -136,6 +136,14 @@ do_show() {
        done
 }
 
+do_validate() {
+       [ "$#" -ne 1 ] && {
+               uci_usage validate
+               exit 1
+       }
+       uci_validate "$1" || exit "$?"
+}
+
 uci_usage() {
        case "$1" in
                show) echo "$0 show [<package>[.<config>]]";;
@@ -144,6 +152,7 @@ uci_usage() {
                del) echo "$0 del <package>.<config>[.<option>]";;
                rename) echo "$0 rename <package> <config> <name>";;
                commit) echo "$0 commit [<package> ... ]";;
+               validate) echo "$0 validate <package>";;
                *) 
                        echo "Syntax: $0 <command> <arguments...>"
                        echo
@@ -153,6 +162,7 @@ uci_usage() {
                        uci_usage del
                        uci_usage rename
                        uci_usage commit
+                       uci_usage validate 
                        echo
                        exit 1
                ;;
@@ -164,7 +174,7 @@ if [ $# -eq 0 ] ; then
        exit 0
 fi
 
-local CMD="$1"
+CMD="$1"
 shift
 case "$CMD" in
        set) do_set "$@";;
@@ -173,6 +183,7 @@ case "$CMD" in
        get) do_get "$@";;
        show) do_show "$@";;
        commit) do_commit "$@";;
+       validate) do_validate "$@";;
        *) uci_usage;;
 esac
 exit 0
index 5c353a3..84121c5 100755 (executable)
@@ -9,6 +9,7 @@ N="
 "
 
 _C=0
+NO_EXPORT=1
 
 hotplug_dev() {
        env -i ACTION=$1 INTERFACE=$2 /sbin/hotplug net
@@ -19,7 +20,7 @@ append() {
        local value="$2"
        local sep="${3:- }"
        
-       eval "export -n -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
+       eval "export ${NO_EXPORT:+-n} -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
 }
 
 reset_cb() {
@@ -32,19 +33,19 @@ config () {
        local cfgtype="$1"
        local name="$2"
        
-       CONFIG_NUM_SECTIONS=$(($CONFIG_NUM_SECTIONS + 1))
+       export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=$(($CONFIG_NUM_SECTIONS + 1))
        name="${name:-cfg$CONFIG_NUM_SECTIONS}"
        append CONFIG_SECTIONS "$name"
        config_cb "$cfgtype" "$name"
-       CONFIG_SECTION="$name"
-       export -n "CONFIG_${CONFIG_SECTION}_TYPE=$cfgtype"
+       export ${NO_EXPORT:+-n} CONFIG_SECTION="$name"
+       export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_TYPE=$cfgtype"
 }
 
 option () {
        local varname="$1"; shift
        local value="$*"
        
-       export -n "CONFIG_${CONFIG_SECTION}_${varname}=$value"
+       export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_${varname}=$value"
        option_cb "$varname" "$*"
 }
 
@@ -58,12 +59,12 @@ config_rename() {
        for oldvar in `set | grep ^CONFIG_${OLD}_ | \
                sed -e 's/\(.*\)=.*$/\1/'` ; do
                newvar="CONFIG_${NEW}_${oldvar##CONFIG_${OLD}_}"
-               eval "export -n \"$newvar=\${$oldvar}\""
+               eval "export ${NO_EXPORT:+-n} \"$newvar=\${$oldvar}\""
                unset "$oldvar"
        done
-       CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , $NEW ,")"
+       export ${NO_EXPORT:+-n} CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , $NEW ,")"
        
-       [ "$CONFIG_SECTION" = "$OLD" ] && CONFIG_SECTION="$NEW"
+       [ "$CONFIG_SECTION" = "$OLD" ] && export ${NO_EXPORT:+-n} CONFIG_SECTION="$NEW"
 }
 
 config_unset() {
@@ -74,8 +75,8 @@ config_clear() {
        local SECTION="$1"
        local oldvar
        
-       CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , ,")"
-       CONFIG_SECTIONS="${SECTION:+$CONFIG_SECTIONS}"
+       export ${NO_EXPORT:+-n} CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , ,")"
+       export ${NO_EXPORT:+-n} CONFIG_SECTIONS="${SECTION:+$CONFIG_SECTIONS}"
 
        for oldvar in `set | grep ^CONFIG_${SECTION:+${SECTION}_} | \
                sed -e 's/\(.*\)=.*$/\1/'` ; do 
@@ -84,11 +85,11 @@ config_clear() {
 }
 
 config_load() {
-       local file="/etc/config/$1"
+       local file="$UCI_ROOT/etc/config/$1"
        _C=0
-       CONFIG_SECTIONS=
-       CONFIG_NUM_SECTIONS=0
-       CONFIG_SECTION=
+       export ${NO_EXPORT:+-n} CONFIG_SECTIONS=
+       export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=0
+       export ${NO_EXPORT:+-n} CONFIG_SECTION=
        
        [ -e "$file" ] && {
                . $file
@@ -100,7 +101,7 @@ config_load() {
 config_get() {
        case "$3" in
                "") eval "echo \"\${CONFIG_${1}_${2}}\"";;
-               *)  eval "export -n -- \"$1=\${CONFIG_${2}_${3}}\"";;
+               *)  eval "export ${NO_EXPORT:+-n} -- \"$1=\${CONFIG_${2}_${3}}\"";;
        esac
 }
 
@@ -108,7 +109,7 @@ config_set() {
        local section="$1"
        local option="$2"
        local value="$3"
-       export -n "CONFIG_${section}_${option}=$value"
+       export ${NO_EXPORT:+-n} "CONFIG_${section}_${option}=$value"
 }
 
 config_foreach() {
@@ -155,12 +156,12 @@ strtok() { # <string> { <variable> [<separator>] ... }
 
                val="${val#$tmp$2}"
 
-               export -n "$1=$tmp"; count=$((count+1))
+               export ${NO_EXPORT:+-n} "$1=$tmp"; count=$((count+1))
                shift 2
        done
 
        if [ $# -gt 0 -a "$val" ]; then
-               export -n "$1=$val"; count=$((count+1))
+               export ${NO_EXPORT:+-n} "$1=$val"; count=$((count+1))
        fi
 
        return $count
diff --git a/package/base-files/files/lib/config/parse_spec.awk b/package/base-files/files/lib/config/parse_spec.awk
new file mode 100644 (file)
index 0000000..5eabc5a
--- /dev/null
@@ -0,0 +1,255 @@
+# AWK file for parsing uci specification files
+#
+# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+#
+# general: unfortunately, the development was done using gawk providing
+#  a different match() functions than e.g. mawk on debian systems
+#  - therefore, the script was changed to run on most awk's 
+#  - even things like [:space:] are not used
+#
+# - script  parses the config section definition contained in one 
+#   specification file
+# global variables:
+# * section  - contains the current config section name
+# * var      - contains the name of the current config option
+# * type     - contains the type of the current config option
+# * required - contains the requirements of the current config option
+# * optional - contains the optional scope of the current config option
+# * vars[]  - array, contains the name of all config options valid within
+#            a certain config section, format: csv
+#
+# XXX todo: more than one config option with the same in different section
+# will clash for the following tables
+# * types[] - contains the type of a config option
+# * reqs[]  - contains the requirements of a config option
+# * opts[]  - contains the optional scope of a config option
+#
+BEGIN {
+       section_count=1
+       section = ""
+       simple_types = "int|ip|netmask|string|wep|hostname|mac|port|ports|wpapsk"
+}
+
+# function print_specification
+# - prints all information about the created tables containing the
+#   specification 
+function print_specification() {
+       for (section in vars) {
+               printf("%s\n",section);
+               split(vars[section],arr,",")
+               for (idx in arr) {
+                       printf("\t%s[%s]",arr[idx],types[section "_" arr[idx]]); 
+                       if (length(reqs[section "_" arr[idx]])) {
+                               if (reqs[section "_" arr[idx]]==1) {
+                                       printf(",req");
+                               }else{
+                                       printf(", req(%s)", reqs[section "_" arr[idx]]);
+                               }
+                       }
+                       if (length(opts[section "_" arr[idx]])) {
+                               printf(", opt(%s)", opts[section "_" arr[idx]]);
+                       }
+                       printf("\n");
+               }
+       }
+}
+
+
+function reset_option() {
+       # just set global variables parsed on one line back to defaults
+       var = ""
+       type = ""
+       required = ""
+       optional = ""
+       found = 0
+}
+
+function store_option() {
+       # save all information about a config option parsed from the spec file
+       # to the relevant tables for future use
+
+       # first check minimum requirements for storing information
+       if (!length(section)) {
+               print STDERR "line " NR ": section definition missing"
+               exit 1
+       }
+       if (!length(var)) {
+               print STDERR "line " NR ": invalid config option name"
+               exit 1
+       }
+       if (!length(type)) {
+               print STDERR "line " NR ": invalid config option type"
+               exit 1
+       }
+
+       # add config options to the names of options available for this
+       # section
+       if (exists[section]!=1) {
+               section_names[section_count] = section
+               section_count++
+               exists[section] = 1
+               vars[section] = var
+       } else {
+               vars[section] = vars[section] "," var
+       }
+       
+       # save the type, the requirements and the optional scope of the 
+       # config option
+       types[section "_" var] = type
+       reqs[section "_" var] = required
+       opts[section "_" var] = optional
+}
+
+/^declare -x|^export/ { 
+       sub(/^declare -x /,"")
+       sub(/^export /,"")
+       split($0,arr,"=")
+       val=substr(arr[2],2,length(arr[2])-2)
+       ENVIRON[arr[1]] = val
+       next
+}
+
+# main parsing function
+# this is done in one function block to allow multiple semicolon separated
+# definitions on one line
+{
+       # replace leading/trailing white space
+       gsub("^[ \t\n]+","");
+       gsub("[ \t\n]+$","");
+
+       # comments are removed
+       # XXX todo: check for quoted comments??
+       if (match($0,/[^#]*/)) {
+               rest=substr($0,RSTART,RLENGTH)
+       } else {
+               rest=$0
+       }
+
+       # match the config section "<section> {"
+       if (match(rest,/^[^ \t\n{]+[ \t\n]*\{/)) {
+               match(rest,/^[^ \t\n{]+/)
+               section = substr(rest,RSTART,RLENGTH)
+               rest=substr($0,RSTART+RLENGTH);
+               match(rest,/[ \t\n]*\{/)
+               rest=substr(rest,RSTART+RLENGTH)
+               # check for array indication
+               if (match(section,/\[[ \t\n]*\]/)) {
+                       section=substr(section,1,RSTART-1)
+                       multiple[section] = 1
+               } else {
+                       multiple[section] = 0
+               }
+       }
+
+       reset_option()
+
+       # parse the remaing line as long as there is something to parse
+       while (rest ~ "[^ \t\n}]+") {
+               found = 0
+
+               # get option name and option type
+               # first, check for "simple" datatype definitions
+               if (match(rest,"[^: \t\n]+[ \t\n]*:[ \t\n]*(" \
+                                       simple_types ")")){
+                       match(rest,"[^: \t\n]+")
+                       var=substr(rest,RSTART,RLENGTH)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,"[ \t\n]*:[ \t\n]*")
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,"(" simple_types ")")
+                       type=substr(rest,RSTART,RLENGTH)
+                       rest = substr(rest,RSTART+RLENGTH)
+                       found = 1
+               # next, check for enum definitions
+               } else if (match(rest,/[^: \t\n]+[ \t\n]*:[ \t\n]*enum\([^\)]+\)/ )) {
+                       match(rest,"[^: \t\n]+")
+                       var=substr(rest,RSTART,RLENGTH)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/[ \t\n]*:[ \t\n]*enum\(/)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/[^\)]+/)
+                       type="enum," substr(rest,RSTART,RLENGTH)
+                       rest = substr(rest,RSTART+RLENGTH+1)
+                       found=1
+               }                       
+
+               # after the name and the type, 
+               # get the option requirements/scope
+               if (match(rest,/[^,]*,[ \t\n]*required\[[^]]+\]/)) {
+                       match(rest,"[^,]*")
+                       save=substr(rest,RSTART,RLENGTH)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/,[ \t\n]*required\[/);
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/[^]]+\]/)
+                       required=substr(rest,RSTART,RLENGTH-1)
+                       save=save substr(rest,RSTART+RLENGTH)
+                       rest=save
+                       found=1
+               } else if (match(rest,/[^,]*,[ \t\n]*required/)) {
+                       match(rest,"[^,]*")
+                       save=substr(rest,RSTART,RLENGTH)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,",[ \t\n]*required");
+                       rest=substr(rest,RSTART+RLENGTH)
+                       required=1
+                       save=save substr(rest,RSTART+RLENGTH)
+                       rest=save
+                       found=1
+               }
+               if (match(rest,/[^,]*,[ \t\n]*optional\[[^]]+\]/)) {
+                       match(rest,"[^,]*")
+                       save=substr(rest,RSTART,RLENGTH)
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/,[ \t\n]*optional\[/);
+                       rest=substr(rest,RSTART+RLENGTH)
+                       match(rest,/[^]]+\]/)
+                       optional=substr(rest,RSTART,RLENGTH-1)
+                       save=save substr(rest,RSTART+RLENGTH)
+                       rest=save
+                       found=1
+               }
+       
+               # if the remaining line contains a semicolon, complete the
+               # specification of the config options
+               if (match(rest, "^[ \t\n]*;(.*)")) {
+                       match(rest,"^[ \t\n]*;")
+                       rest=substr(rest,RSTART+RLENGTH)
+                       if (found==1) {
+                               store_option()
+                       }
+                       reset_option()
+
+               # if nothing matched on this line, clear the rest
+               } else if (!found) {
+                       rest = ""
+               }
+       }
+
+       # after the line is pared, store the configuration option in the
+       # table if any has been defined
+       if (length(var)) {
+               store_option()
+               reset_option()
+       }
+       # close the section if the line contained a closing section bracket, 
+       # XXX todo: check if this has to be done more intelligent
+       if ($0 ~ /\}/) {
+               section=""
+       }
+}
diff --git a/package/base-files/files/lib/config/specs/network.spec b/package/base-files/files/lib/config/specs/network.spec
new file mode 100644 (file)
index 0000000..3d19b5c
--- /dev/null
@@ -0,0 +1,7 @@
+interface[] {
+       proto: string, required;
+       ipaddr: ip, required[proto=static];
+       netmask: ip, required[proto=static];
+       gateway: ip;
+       dns: ip;
+}
old mode 100755 (executable)
new mode 100644 (file)
index 12795d8..43bb981
@@ -1,8 +1,8 @@
 #!/bin/sh
 # Shell script defining macros for manipulating config files
 #
-# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
-# Copyright (C) 2006 by Felix Fietkau <nbd@openwrt.org>
+# Copyright (C) 2006        Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+# Copyright (C) 2006,2007   Felix Fietkau <nbd@openwrt.org>
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -31,7 +31,7 @@ uci_load() {
 uci_do_update() {
        local FILENAME="$1"
        local UPDATE="$2"
-       awk -f /lib/config/uci-update.awk -f - <<EOF
+       awk -f $UCI_ROOT/lib/config/uci-update.awk -f - <<EOF
 BEGIN {
        config = read_file("$FILENAME")
        $UPDATE
@@ -94,9 +94,10 @@ uci_remove() {
 uci_commit() {
        local PACKAGE="$1"
        local PACKAGE_BASE="$(basename "$PACKAGE")"
-       
+
        mkdir -p /tmp/.uci
-       lock "/tmp/.uci/$PACKAGE_BASE.lock"
+       LOCK=`which lock` || LOCK=:
+       $LOCK "/tmp/.uci/$PACKAGE_BASE.lock"
        [ -f "/tmp/.uci/$PACKAGE_BASE" ] && (
                updatestr=""
                
@@ -128,13 +129,13 @@ uci_commit() {
                }
                
                config_load "$PACKAGE"
-               CONFIG_FILENAME="${CONFIG_FILENAME:-$ROOT/etc/config/$PACKAGE_BASE}"
+               CONFIG_FILENAME="${CONFIG_FILENAME:-$UCI_ROOT/etc/config/$PACKAGE_BASE}"
                uci_do_update "$CONFIG_FILENAME" "$updatestr" > "/tmp/.uci/$PACKAGE_BASE.new" && {
                        mv -f "/tmp/.uci/$PACKAGE_BASE.new" "$CONFIG_FILENAME" && \
                        rm -f "/tmp/.uci/$PACKAGE_BASE"
                } 
        )
-       lock -u "/tmp/.uci/$PACKAGE_BASE.lock"
+       $LOCK -u "/tmp/.uci/$PACKAGE_BASE.lock"
 }
 
 
diff --git a/package/base-files/files/lib/config/validate.sh b/package/base-files/files/lib/config/validate.sh
new file mode 100644 (file)
index 0000000..e16319d
--- /dev/null
@@ -0,0 +1,74 @@
+# Shell script defining validating configuration macros
+#
+# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+# Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+validate_spec() {
+       export | grep 'CONFIG_' | cat - "$@" | awk \
+               -f $UCI_ROOT/lib/config/validate_config.awk \
+               -f $UCI_ROOT/lib/config/parse_spec.awk \
+               -f $UCI_ROOT/lib/config/validate_spec.awk
+}
+
+validate_config_cb () {
+       local TYPE
+       local res=
+       
+       [ -n "${CONFIG_SECTION}" ] || return 0
+       
+       config_get TYPE ${CONFIG_SECTION} TYPE
+       [ -n "$TYPE" ] || return 0
+       
+       if type validate_${PACKAGE}_${TYPE} >/dev/null 2>&1; then
+               validate_${PACKAGE}_${TYPE}
+               res="$?"
+       else 
+               if [ -f $UCI_ROOT/lib/config/specs/${PACKAGE}.spec ]; then
+                       # no special defined, use default one
+                       validate_spec $UCI_ROOT/lib/config/specs/${PACKAGE}.spec
+                       res="$?"
+               fi
+       fi
+       
+       VALIDATE_RES="${VALIDATE_RES:-$res}"
+}
+
+uci_validate() {(
+       PACKAGE="$1"
+       FILE="$2"
+       VALIDATE_RES=
+
+       [ -z "${PACKAGE}" ] && {
+               echo "Error: no package defined"
+               return 1
+       }
+
+       reset_cb
+       config_cb() {
+               validate_config_cb "$@"
+       }
+       unset NO_EXPORT
+       if [ -n "$FILE" ]; then
+               . "$FILE"
+               config
+       else
+               config_load "$1"
+       fi
+
+       return ${VALIDATE_RES:-0}
+)}
diff --git a/package/base-files/files/lib/config/validate_config.awk b/package/base-files/files/lib/config/validate_config.awk
new file mode 100644 (file)
index 0000000..053694e
--- /dev/null
@@ -0,0 +1,105 @@
+# AWK file for validating uci specification files
+#
+# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+function is_int(value) {
+       valid = 1
+       if (value !~ /^[0-9]*$/) { valid = 0 }
+       return valid
+}
+
+function is_netmask(value) {
+       return is_ip(value)
+}
+
+function is_ip(value) {
+       valid = 1
+       if ((value != "") && (value !~ /^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$/)) valid = 0
+       else {
+               split(value, ipaddr, "\\.")
+               for (i = 1; i <= 4; i++) {
+                       if ((ipaddr[i] < 0) || (ipaddr[i] > 255)) valid = 0
+               }
+       }
+       return valid
+}
+
+function is_wep(value) {
+       valid = 1
+       if (value !~ /^[0-9A-Fa-f]*$/) {
+               valid = 0
+       } else if ((length(value) != 0) && (length(value) != 10) && (length(value) != 26)) {
+               valid = 0
+       } else if (value ~ /0$/) {
+               valid = 0
+       }
+       return valid
+}
+
+function is_hostname(value) {
+       valid = 1
+       if (value !~ /^[0-9a-zA-z\.\-]*$/) {
+               valid = 0
+       }
+       return valid;
+}
+
+function is_string(value) {
+       return 1;
+}
+
+function is_mac(value) {
+       valid = 1
+       if ((value != "") && (value !~ /^[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$/)) {
+               valid = 0
+       }
+       return valid
+}
+
+function is_port(value) {
+       valid = 1
+       if (value !~ /^[0-9]*$/) {
+               valid = 0
+       }
+       return valid
+}
+
+function is_ports(value) {
+       valid = 1
+       n = split(value ",", ports, ",")
+       for (i = 1; i <= n; i++) {
+               if ((ports[i] !~ /^[0-9]*$/) && (ports[i] !~ /^[0-9][0-9]*-[0-9][0-9]*$/)) {
+                       valid = 0
+               }
+       }
+       return valid
+}
+
+function is_wpapsk(value) {
+       valid = 1
+       if (length(value) > 64) {
+               valid = 0
+       }
+       if ((length(value) != 0) && (length(value) < 8)) {
+               valid = 0
+       }
+       if ((length(value) == 64) && (value ~ /[^0-9a-fA-F]/)) {
+               valid = 0
+       }
+       return valid
+}
+
diff --git a/package/base-files/files/lib/config/validate_spec.awk b/package/base-files/files/lib/config/validate_spec.awk
new file mode 100644 (file)
index 0000000..0816c08
--- /dev/null
@@ -0,0 +1,171 @@
+# AWK file for validating uci specification files
+#
+# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
+# Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+#
+# general: unfortunately, the development was done using gawk providing
+#  a different match() functions than e.g. mawk on debian systems
+#  - therefore, the script was changed to run on most awk's 
+#  - even things like [:space:] are not used
+#
+# - script  parses the config section definition contained in one 
+#   specification file
+# global variables:
+# * section  - contains the current config section name
+# * var      - contains the name of the current config option
+# * type     - contains the type of the current config option
+# * required - contains the requirements of the current config option
+# * optional - contains the optional scope of the current config option
+# * vars[]  - array, contains the name of all config options valid within
+#            a certain config section, format: csv
+#
+# XXX todo: more than one config option with the same in different section
+# will clash for the following tables
+# * types[] - contains the type of a config option
+# * reqs[]  - contains the requirements of a config option
+# * opts[]  - contains the optional scope of a config option
+#
+
+# - check requirement validates, if the config option is required in
+#   the config section type and if so, if it is defined
+# - the functions exits with error in case of non-conforming 
+#   behaviour
+# XXX todo: use return instead of exit
+#
+function check_requirements(vsec,var) {
+       # check, if config option is required in all cases
+       if (reqs[vsec "_" var] == 1) {
+               # option is always required, is it defined?
+               if (!length(ENVIRON["CONFIG_" vsec "_" var])) {
+                       print STDERR "Error: missing config option " var " in " vsec
+                       exit 1
+               }
+
+       # check, if config option is required only when other options
+       # have certain values
+       } else if (length(reqs[vsec "_" var])) {
+               # - check all requirements, e.g. proto=static,proto=pptp
+               # - note, that the required flag is tiggered if at least one
+               #   of the conditions is met
+               split(reqs[vsec "_" var],arr,",");
+               for (idx in arr) {
+                       # parse the condition space tolerant
+                       if (!match(arr[idx],"^[ \t\n]*[^ \t\n=]+"\
+                               "[ \t\n]*=.+")) {
+                               print STDERR "Error: invalid requirement "\
+                                       "in spec file for " var " : " arr[idx]
+                               exit 1
+                       }
+                       # get the name of the variable
+                       match(arr[idx],"[^ \t\n=]+");
+                       name=substr(arr[idx],RSTART,RLENGTH)
+                       mrest=substr(arr[idx],RSTART+RLENGTH)
+                       # get the spaces
+                       match(mrest,"[ \t\n]*=[ \t\n]*")
+                       val=substr(mrest,RSTART+RLENGTH)
+                       # check the condition
+                       if (ENVIRON["CONFIG_" vsec "_" name] == val) {
+                               # condition is met, check requirement
+                               if (!length(ENVIRON["CONFIG_" vsec "_" var])) {
+                                       print STDERR "Error: missing config " \
+                                               "option " var " in " vsec 
+                                       exit 1
+                               }
+                       }
+               }
+       }
+}
+
+# is_valid just returns true(1)/false(0) if the
+# given value is conform with the type definition 
+# NOTE: this function needs the type validating function from
+# validate_config.awk
+#
+function is_valid(type,value) {
+
+       # the enum type contains a definition of all allowed values as csv
+       # e.g. enum,alpha,beta,gamma
+       if (type ~ "enum" ) {
+               split(type,tarr,",")
+               for (num in tarr) {
+                       if (num > 0) {
+                               gsub("^[ \t\n]*","",tarr[num]);
+                               gsub("[ \t\n]*$","",tarr[num]);
+                               if (tarr[num] == value) {
+                                       return 1
+                               }       
+                       }
+               }
+               return 0;
+       }
+
+       # all other types are checked as defined in the former validate.awk
+       if (type ~ "int") return is_int(value)
+       if (type ~ "ip" ) return is_ip(value)
+       if (type ~ "netmask" ) return is_netmask(value)
+       if (type ~ "string" ) return is_string(value)
+       if (type ~ "wep" ) return is_wep(value)
+       if (type ~ "hostname" ) return is_hostname(value)
+       if (type ~ "mac" ) return is_mac(value)
+       if (type ~ "port" ) return is_port(value)
+       if (type ~ "ports" ) return is_ports(value)
+       if (type ~ "wpapsk" ) return is_wpapsk(value)
+}
+
+# validate_config compares the specification as parsed from the spec file
+# with the environment variables
+# CONFIG_SECTION contains the relevant config section name, e.g. wan
+# CONFIG_<section>_TYPE contains the type of the config, e.g. interface
+# CONFIG_<section>_<var> contains the value of the config option <var>
+#
+function validate_config() {
+       # get the config section name
+       vname=ENVIRON["CONFIG_SECTION"]
+       if (!length(vname)) {
+               print STDERR "Error: no current configuration"
+               exit 1
+       }
+       # get the config section type
+       vsec=ENVIRON["CONFIG_" vname "_TYPE"]
+       if (!length(vsec)) {
+               print STDERR "Error: section " vsec " not found"
+               exit 1
+       }
+
+       # loop through all config options specified for this section type
+       split(vars[vsec],options,",")
+       for (oidx in options) {
+               # first, look for all required attributes
+               var=options[oidx]
+               check_requirements(vname,var)
+
+               # next look at each option and validate it
+               val=ENVIRON["CONFIG_" vname "_" var]
+               if (length(val)) {
+                       if (!is_valid(types[vsec "_" var],val)) {
+                               print "Error: type validation error for '" var "' in section '" vname "'"
+                               exit 1
+                       }
+               }
+       }
+}
+
+
+END {
+       validate_config()
+}