Integrate basic UCI config file validation support
Integrate basic UCI config file validation support
Needs more testing and validation is not enforced yet

Code contributed by Fraunhofer Fokus


git-svn-id: svn://svn.openwrt.org/openwrt/trunk@6391 3c298f89-4303-0410-b956-a3cf2f4a3e73

--- a/package/base-files/files/bin/uci
+++ b/package/base-files/files/bin/uci
@@ -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 @@
 		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 @@
 	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 @@
 		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 del
 			uci_usage rename
 			uci_usage commit
+			uci_usage validate 
 			echo
 			exit 1
 		;;
@@ -164,7 +174,7 @@
 	exit 0
 fi
 
-local CMD="$1"
+CMD="$1"
 shift
 case "$CMD" in
 	set) do_set "$@";;
@@ -173,6 +183,7 @@
 	get) do_get "$@";;
 	show) do_show "$@";;
 	commit) do_commit "$@";;
+	validate) do_validate "$@";;
 	*) uci_usage;;
 esac
 exit 0

--- a/package/base-files/files/etc/functions.sh
+++ b/package/base-files/files/etc/functions.sh
@@ -9,6 +9,7 @@
 "
 
 _C=0
+NO_EXPORT=1
 
 hotplug_dev() {
 	env -i ACTION=$1 INTERFACE=$2 /sbin/hotplug net
@@ -19,7 +20,7 @@
 	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 @@
 	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 @@
 	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 @@
 	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_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_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 @@
 	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 @@
 
 		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

--- /dev/null
+++ b/package/base-files/files/lib/config/parse_spec.awk
@@ -1,1 +1,256 @@
-
+# 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=""
+	}
+}
+

--- /dev/null
+++ b/package/base-files/files/lib/config/specs/network.spec
@@ -1,1 +1,8 @@
+interface[] {
+	proto: string, required;
+	ipaddr: ip, required[proto=static];
+	netmask: ip, required[proto=static];
+	gateway: ip;
+	dns: ip;
+}
 

--- a/package/base-files/files/lib/config/uci.sh
+++ b/package/base-files/files/lib/config/uci.sh
@@ -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_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_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 @@
 		}
 		
 		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"
 }
 
 

--- /dev/null
+++ b/package/base-files/files/lib/config/validate.sh
@@ -1,1 +1,75 @@
+# 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}
+)}
+

--- /dev/null
+++ b/package/base-files/files/lib/config/validate_config.awk
@@ -1,1 +1,106 @@
+# 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
+}
+
+

--- /dev/null
+++ b/package/base-files/files/lib/config/validate_spec.awk
@@ -1,1 +1,172 @@
+# 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()
+}
+

comments