#!/bin/sh -e
#
# Functions for qos class definitions
#
ARGS_REQ='argument(s) required'
CLASSES=
#
# Print error message and exit with exit code 1
#
_error() {
echo "ERROR: $*" >&2
exit 1
}
#
# Get given option from a string
#
_get_option() {
[ $# -ge 3 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] ||
_error "_get_option: At least three $ARGS_REQ"
local _var _opt _arg
# Variable name
_var=$1
# Option name
_opt=$2
shift 2
# Search...
for _arg in "$@"; do
case $_arg in
$_opt=?*)
# Has value.
eval $_var='"${_arg#*=}"'
return 0
;
$_opt)
# Treat option-name only (without following
# '=') as if "yes" was specified as value.
eval $_var=yes
return 0
;
esac
done
# Not found
return 1
}
#
# Return 0 if class is available in class list
#
_class_available() {
[ $# -eq 1 ] && [ "$1" ] ||
_error "_class_available: Exactly one $ARGS_REQ"
local _class=$1
# Class available
case "$CLASSES" in
*$_class*)
return 0
;
esac
return 1
}
#
# Return 0 if protocol assigned to a class
#
_protocol_available() {
[ $# -eq 1 ] && [ "$1" ] ||
_error "_protocol_available: Exactly one $ARGS_REQ"
local _class _members _protocol
_protocol=$1
# Search class members for protocol
for _class in $CLASSES; do
eval _members=\"\$_${_class}_members\"
case "$_members" in
*$_protocol*)
return 0
;
esac
done
return 1
}
#
# Print number of classes in list
#
_count_classes() {
set -- $CLASSES
printf %d $#
}
#
# Add a class to the list
#
_qos_add() {
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
_error "_qos_add: Exactly two $ARGS_REQ: (classname) (desc)"
local _class=${1#class=}
shift
# Check that class name is unique
! _class_available $_class ||
_error "_qos_add: Class '$_class' already defined"
_get_option _desc desc "$@" || :
CLASSES="$CLASSES $_class"
# Initialize variables
eval _${_class}_desc=\$_desc
eval unset _${_class}_members
}
#
# Attach info to the class
#
_qos_add_info() {
[ $# -ge 2 ] && [ "$1" ] && [ "$2" ] ||
_error "_qos_add_info: At least two $ARGS_REQ:" \
'(classname) (info) ...'
local _class=${1#class=}
shift
# Check that class is available
_class_available $_class ||
_error "_qos_add_info: Class '$_class' not defined"
# Loop through info
while [ $# -gt 0 ]; do
_info=${1%=*}
_value=${1#*=}
eval _${_class}_$_info=\"$_value\"
shift
done
}
#
# Add a protocol to a class
#
_qos_prot() {
[ $# -ge 2 ] && [ "$1" ] && [ "$2" ] ||
_error "_qos_prot: At least two $ARGS_REQ:" \
'(classname) (protocol) ...'
local _class _protocol
_class=${1#class=}
shift
# Check if class defined
_class_available $_class ||
_error "_qos_prot: Class '$_class' not defined"
# Append protocols
for _protocol in $@ do
# Check that protocol not already available elsewhere
! _protocol_available $_protocol ||
_error "_qos_prot: Protocol '$_protocol' already" \
'assigned to a class'
eval _${_class}_members=\"\$_${_class}_members \$_protocol\"
done
}
#
# Get QoS info for given protocol
#
_get_protocol_info() {
local _ret_val _found _info _protocol _class _protocols
_ret_val=
_found=0
_info=$1
_protocol=$2
shift 2
# Search in classes
for _class in $CLASSES; do
eval _protocols=\"\$_${_class}_members\"
case "$_protocols" in
*$_protocol*)
# Found it
eval _ret_val=\"\$_${_class}_$_info\"
_found=1
;
esac
done
[ $_found -ne 0 ] ||
_error "_get_protocol_info: Protocol '$_protocol' not found"
printf %s "$_ret_val"
}
#
# Exec command for each member in given class
#
_exec_member_command() {
local _f=_exec_member_command
[ $# -ge 2 ] && [ "$1" ] && [ "$2" ] ||
_error "$_f: Exactly two $ARGS_REQ: (info) (classname)"
local _info _class _info_val _members _member _cmd _skip _output
_info=$1
_class=$2
# Check that class is available
_class_available $_class ||
_error "$_f: class '$_class' not defined"
# Iterate through all members of this protocol
# and see if they have any attached parameters
eval _info_val=\$_${_class}_$_info
eval _members=\$_${_class}_members
for _member in $_members; do
eval _cmd=\$${_member}_COMMAND
eval _skip=\$${_member}_SKIP
eval _skip=\"$_skip\"
[ $_skip ] || [ -z "$_cmd" ] || {
_cmd=$(echo $_cmd | sed -e 's/<info>/$_info_val/')
eval _cmd=\"$_cmd\"
_output=$($_cmd 2>&1) ||
echo "$_f: Command '$_cmd' failed with" \
"'$_output'" >&2
}
done
}
#
# Dump class/protocol structure
#
_dump() {
local _class _members _member _cmd
for _class in $CLASSES; do
echo "Class: $_class"
eval echo "Desc: "\$_${_class}_desc
eval echo "DSCP: "\$_${_class}_dscp
eval _members=\$_${_class}_members
echo "Proto: $_members"
for _member in $_members; do
eval _cmd=\$${_member}_COMMAND
echo "Command: $_member -> $_cmd"
done
echo
done
}
#
# Print usage text
#
_usage() {
printf %s "\
Usage: $0 <command>
commands:
classes Print number of registered classes.
dump Print dumped QoS table.
exec <info> <class> Execute class member commands.
get <info> <protocol> Print info for protocol.
help Print this usage string.
"
}
#
# Parse command-line input
#
_input_parser() {
local _f _command _info _proto
f=_input_parser
while [ $# -gt 0 ]; do
_command=$1
shift
case $_command in
get)
[ $# -eq 2 ] ||
_error "$_f: Exactly two $ARGS_REQ" \
"by command '$_command':" \
'(info) (protocol)'
_info=$1
_proto=$2
shift 2
_get_protocol_info $_info $_proto
;
exec)
[ $# -eq 2 ] ||
_error "$_f: Exactly two $ARGS_REQ" \
"by command '$_command':" \
'(info) (class)'
_info=$1
_class=$2
shift 2
_exec_member_command $_info $_class
;
classes)
_count_classes
;
dump)
_dump
;
help)
_usage
exit 0
;
*)
_usage
_error "$_f: Unknown command '$_command'"
;
esac
# Add seperator if more output expected
[ $# -lt 1 ] || printf ' '
done
}
#
# Configuration interface
#
class() {
local _command
_command=$1
shift
case $_command in
create)
_qos_add "$@"
;
info)
_qos_add_info "$@"
;
member)
_qos_prot "$@"
;
*)
_error "class: Invalid command '$_command'"
;
esac
}
# Include configuration
CONF_NAME=qos.conf
CONF_PATH=/etc/qos/$CONF_NAME
[ -f $CONF_PATH ] || CONF_PATH=./$CONF_NAME
[ -f $CONF_PATH ] || CONF_PATH=${0%/*}/$CONF_NAME
[ -f $CONF_PATH ] || _error "Failed to locate '$CONF_NAME'"
. $CONF_PATH || _error "Failed to source '$CONF_PATH'"
# Parse input command, if any
[ $# -lt 1 ] || _input_parser "$@"