Created
February 17, 2023 11:58
-
-
Save legionus/4ef966d9809ded7adc20e740560c7601 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash -efu | |
| # SPDX-License-Identifier: GPL-2.0-or-later | |
| . shell-error | |
| declare -A VARS=() LOCK=() GOTO=() LABEL=() | |
| declare -A NAMES=( [KEY]=0 [ATTR]=1 [OPERATOR]=2 [VALUE]=3 ) | |
| declare -a RULE_FILES=() | |
| rule_init_var() | |
| { | |
| set -- "$@" "VARS_${#VARS[@]}" | |
| VARS["$2"]="$3" | |
| eval "$3=()" | |
| eval "$1=\"\$3\"" | |
| } | |
| rule_get_var() | |
| { | |
| set -- "$@" "${VARS["$2${3:+ $3}"]-}" | |
| if [ -n "$3" ]; then | |
| eval "$1=\"\${$3[0]-}\"" | |
| else | |
| eval "$1=''" | |
| fi | |
| } | |
| rule_set_var() | |
| { | |
| local k="$1${2:+ $2}" | |
| [ -z "${LOCK[$k]-}" ] || return 0 | |
| local n="${VARS[$k]-}" | |
| [ -n "$n" ] || rule_init_var n "$k" | |
| case "$1" in | |
| RUN|PROGRAM) | |
| eval "$n=()" | |
| rule_list_add "$@" | |
| return 0 | |
| ;; | |
| esac | |
| # shellcheck disable=SC1087 | |
| eval "$n[0]=\"\$3\"" | |
| } | |
| rule_set_final_var() | |
| { | |
| rule_set_var "$1" "$2" "$3" | |
| LOCK["$1${2:+ $2}"]="1" | |
| } | |
| rule_list_add() | |
| { | |
| local k="$1${2:+ $2}" | |
| [ -z "${LOCK[$k]-}" ] || return 0 | |
| local n="${VARS[$k]-}" | |
| [ -n "$n" ] || rule_init_var n "$k" | |
| eval "$n+=(\"\$3\")" | |
| } | |
| rule_list_remove() | |
| { | |
| local n k a i | |
| k="$1${2:+ $2}" | |
| n="${VARS[$k]-}" | |
| [ -n "$n" ] || return 0 | |
| declare -n "a=$n" | |
| for (( i=0; i < ${#a[@]}; i++ )); do | |
| # shellcheck disable=SC1087 | |
| [ "${a[$i]}" != "$3" ] || eval "$n[$i]=\"\"" | |
| done | |
| } | |
| rule_action() | |
| { | |
| local key attr var op value rc | |
| key="$1"; shift | |
| attr="$1"; shift | |
| op="$1"; shift | |
| value="$1"; shift | |
| rc=0 | |
| case "$op" in | |
| ==) | |
| rule_get_var var "$key" "$attr" | |
| if [ -n "$value" ]; then | |
| # shellcheck disable=SC2295 | |
| [ -n "$var" ] && [ -z "${var##$value}" ] || rc=1 | |
| elif [ -n "$var" ]; then | |
| rc=1 | |
| fi | |
| ;; | |
| !=) | |
| rule_get_var var "$key" "$attr" | |
| # shellcheck disable=SC2295 | |
| [ -n "${var##$value}" ] || rc=1 | |
| ;; | |
| +=) rule_list_add "$key" "$attr" "$value" ;; | |
| -=) rule_list_remove "$key" "$attr" "$value" ;; | |
| :=) rule_set_final_var "$key" "$attr" "$value" ;; | |
| =) rule_set_var "$key" "$attr" "$value" ;; | |
| esac | |
| echo "RULE[$RULE]:. $key${attr:+[$attr]} $op $value -> $rc" | |
| return $rc | |
| } | |
| rule_log() | |
| { | |
| message "${RULE_FILES[-1]}:$LINE_NR: $*." | |
| return 1 | |
| } | |
| rule_log_invalid_attr() { rule_log "Invalid attribute for $1"; } | |
| rule_log_invalid_operator() { rule_log "$1 can not have \`$2' operator"; } | |
| in_list() | |
| { | |
| local v="$1"; shift | |
| while [ "$#" -gt 0 ]; do | |
| [ "$v" != "$1" ] || return 0 | |
| shift | |
| done | |
| return 1 | |
| } | |
| is_valid_pair() | |
| { | |
| local is_match=1 | |
| in_list "${pair[${NAMES[OPERATOR]}]}" '==' '!=' || is_match= | |
| case "${pair[${NAMES[KEY]}]}" in | |
| ACTION) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| ATTR) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" '+=' ':='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', or '=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| ATTRS) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| CONST) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| DEVPATH) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| DRIVER) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| DRIVERS) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| ENV) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or '+=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| GOTO) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| in_list "${pair[${NAMES[OPERATOR]}]}" '=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| GROUP) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| IMPORT) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| KERNEL) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| KERNELS) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| LABEL) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| in_list "${pair[${NAMES[OPERATOR]}]}" '=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| MODE) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| NAME) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" '+='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or ':=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| OPTIONS) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| OWNER) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| PROGRAM) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| RESULT) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ;; | |
| RUN) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| pair[${NAMES[ATTR]}]="program" | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| SECLABEL) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '=' or '+=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| SUBSYSTEM|SUBSYSTEMS) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| SYMLINK) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ;; | |
| SYSCTL) | |
| [ -n "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" '+=' ':='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', or '=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| TAG) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then | |
| rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or '+=' operator, assuming '='" | |
| pair[${NAMES[OPERATOR]}]='=' | |
| fi | |
| ;; | |
| TAGS) | |
| [ -z "${pair[${NAMES[ATTR]}]}" ] || | |
| rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1 | |
| ;; | |
| TEST) | |
| [ -n "$is_match" ] || | |
| rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1 | |
| ;; | |
| *) | |
| rule_log "Invalid key \`${pair[${NAMES[KEY]}]}'" | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| rules_parse_pair() | |
| { | |
| local i="$1" s="$2" | |
| local pair | |
| if [[ "$s" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]; then | |
| s="${BASH_REMATCH[1]}" | |
| fi | |
| if [[ "$s" =~ ^(.*[^{}])\{([^{}]+)\}[[:space:]]*([=\!+:-]?=)[[:space:]]*\"(.*)\"$ ]]; then | |
| pair=( "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" ) | |
| elif [[ "$s" =~ ^(.*[^ !=+:-])[[:space:]]*([=\!+:-]?=)[[:space:]]*\"(.*)\"$ ]]; then | |
| pair=( "${BASH_REMATCH[1]}" "" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" ) | |
| else | |
| rule_log "Unexpected statement: \`$s\`" | |
| return 1 | |
| fi | |
| is_valid_pair || | |
| return 1 | |
| #for s in "${!names[@]}"; do | |
| # eval "RULE_${s}_${RULE_NR}+=(\"\${pair[${NAMES[$s]}]}\")" | |
| #done | |
| case "${pair[${NAMES[KEY]}]}" in | |
| GOTO) GOTO[${pair[${NAMES[VALUE]}]}]="$RULE_NR $i $((${#RULE_FILES[*]}-1))" ;; | |
| LABEL) LABEL[${pair[${NAMES[VALUE]}]}]="$RULE_NR $((${#RULE_FILES[*]}-1))" ;; | |
| esac | |
| } | |
| rules_parse_line() | |
| { | |
| local r='' c='' i=0 | |
| ### backslash/double/single quote mode | |
| local bq='' dq='' | |
| while read -r -N 1 c; do | |
| case "$c" in | |
| \") | |
| if [ -z "$bq" ]; then | |
| [ -n "$dq" ] && dq="" || dq="$c" | |
| fi | |
| ;; | |
| \\) | |
| ### toggle backslash quote mode | |
| if [ -z "$bq" ]; then | |
| ### enter backslash quote mode | |
| bq=\\ | |
| continue | |
| else | |
| ### leave backslash quote mode | |
| r="$r\\" | |
| bq= | |
| fi | |
| ;; | |
| ,) | |
| if [ -z "$bq$dq" ]; then | |
| if [ -n "$r" ]; then | |
| rules_parse_pair "$i" "$r" || | |
| return 1 | |
| (( i+=1 )) | |
| fi | |
| r=''; c=''; | |
| fi | |
| ;; | |
| esac | |
| r="$r$bq$c" | |
| ### leave backslash quote mode if any | |
| bq= | |
| done <<<"$1," | |
| } | |
| LINE_NR= | |
| rules_parse_file() | |
| { | |
| local rule str s | |
| LINE_NR=0 | |
| rule="" | |
| while read -r str; do | |
| (( LINE_NR+=1 )) | |
| if [ "${str:0-1}" = \\ ]; then | |
| rule+="${str:0:-1} " | |
| continue | |
| fi | |
| rule+="$str" | |
| if [ -z "$rule" ] || [ "${rule:0:1}" = '#' ]; then | |
| rule= | |
| continue | |
| fi | |
| for s in "${NAMES[@]}"; do | |
| eval "RULE_${s}_${RULE_NR}=()" | |
| done | |
| #printf '{%s}\n' "$rule" | |
| ! rules_parse_line "$rule" || | |
| (( RULE_NR+=1 )) | |
| rule= | |
| done | |
| } | |
| rules_post_check() | |
| { | |
| for s in "${!GOTO[@]}"; do | |
| [ -z "${LABEL[$s]-}" ] || | |
| continue | |
| set -- ${GOTO[$s]} | |
| local rule_nr="${GOTO[$s]%:*}" | |
| local pair_nr="${GOTO[$s]#*:}" | |
| eval "RULE_VALUE_$1[$2]=\"\"" | |
| message "${RULE_FILES[$3]}: GOTO=\"$s\" has no matching label, ignoring" | |
| done | |
| for s in "${!LABEL[@]}"; do | |
| set -- ${LABEL[$s]} | |
| [ -n "${GOTO[$s]-}" ] || | |
| message "${RULE_FILES[$2]}: LABEL=\"$s\" takes no effect, ignoring" | |
| done | |
| } | |
| rules_run() | |
| { | |
| local RULE KEY ATTR OPERATOR VALUE goto | |
| for (( RULE=0; RULE < RULE_NR; RULE++ )); do | |
| rule_get_var goto GOTO | |
| if [ -n "$goto" ]; then | |
| set -- ${LABEL[$goto]} | |
| [ "$RULE" -lt "$1" ] || | |
| rule_set_var "GOTO" "" "" | |
| continue | |
| fi | |
| declare -n "KEY=RULE_KEY_${RULE}" | |
| declare -n "ATTR=RULE_ATTR_${RULE}" | |
| declare -n "OPERATOR=RULE_OPERATOR_${RULE}" | |
| declare -n "VALUE=RULE_VALUE_${RULE}" | |
| for i in "${!KEY[@]}"; do | |
| rule_action "${KEY[$i]}" "${ATTR[$i]}" "${OPERATOR[$i]}" "${VALUE[$i]}" || | |
| : break | |
| done | |
| done | |
| local i n a | |
| for n in "${!VARS[@]}"; do | |
| declare -n "a=${VARS[$n]}" | |
| for (( i=0; i < ${#a[@]}; i++ )); do | |
| printf '%s[%s]="%s"%s\n' "$n" "$i" "${a[$i]}" "${LOCK[$n]:+ (final)}" | |
| done | |
| done | |
| } | |
| RULE_NR=0 | |
| for fn in "$@"; do | |
| RULE_FILES+=("$fn") | |
| rules_parse_file < "$fn" | |
| done | |
| rules_post_check | |
| message "Parsed $RULE_NR rules" | |
| #rules_run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment