#!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # to be sourced # General shell helpers # exit with non-zero exit code; if there is only one param: # exit with msg $1 and exit code from last command (or 99 if = 0) # otherwise, exit with $1 and use remaining arguments as msg function die() { rc=$? if [ $# -gt 1 ]; then rc="$1" shift fi [ "$rc" -ne 0 ] || rc=99 echo >&2 "$@" exit $rc } # filter out paths that are not files # input $@, output via echo; # note: pass `-` for stdin # note: outputs nothing if all input files are "bad" (eg. not existing), but it # is left for caller to decide if this is an erorr condition; # note: whitespaces are considered "bad" as part of filename, it's an error. function filter-out-bad-files() { if [[ $# = 1 && "$1" = '-' ]]; then echo - return 0 fi if [ $# = 0 ]; then die 10 "no files passed, use '-' when reading from pipe (|)" fi local any=0 diagmsgs=/dev/stderr re=$'[\t \n]' [ -n "${QUIET_COMPAT-}" ] && diagmsgs=/dev/null for x in "$@"; do if [ -e "$x" ]; then if [[ "$x" =~ $re ]]; then die 11 "err: filename contains whitespaces: $x." fi echo "$x" any=1 else echo >&"$diagmsgs" filtering "$x" out fi done if [ $any = 0 ]; then echo >&"$diagmsgs" 'all files (for given query) filtered out' fi } # Basics of regexp explained, as a reference for mostly-C programmers: # (bash) "regexp-$VAR-regexp" - bash' VARs are placed into "QUOTED" strings # /\);?$/ - match end of function declaration, $ is end of string # ^[ \t]* - (heuristic), anything but comment, eg to exclude function docs # /STH/, /END/ - (awk), print all lines sice STH matched, up to END, inclusive # "Whitespace only" WB='[ \t\n]' # Helpers below print the thing that is looked for, for further grep'ping/etc. # That simplifies process of excluding comments or spares us state machine impl. # # We take advantage of current/common linux codebase formatting here. # # Functions in this section require input file/s passed as args # (usually one, but more could be supplied in case of renames in kernel), # '-' could be used as an (only) file argument to read from stdin/pipe. # wrapper over find-something-decl() functions below, to avoid repetition # pass $what as $1, $end as $2, and $files to look in as rest of args function find-decl() { test $# -ge 3 # ensure that there are at least 3 params local what end files what="$1" end="$2" shift 2 files="$(filter-out-bad-files "$@")" || die if [ -z "$files" ]; then return 0 fi # shellcheck disable=SC2086 awk " /^$WB*\*/ {next} $what, $end " $files } # yield $1 function declaration (signature), don't pass return type in $1 # looks only in files specified ($2, $3...) function find-fun-decl() { test $# -ge 2 local what end what="/$WB*([(]\*)?$1$WB*($|[()])/" end='/\);?$/' shift find-decl "$what" "$end" "$@" } # yield $1 enum declaration (type/body) function find-enum-decl() { test $# -ge 2 local what end what="/^$WB*enum$WB+$1"' \{$/' end='/\};$/' shift find-decl "$what" "$end" "$@" } # yield $1 struct declaration (type/body) function find-struct-decl() { test $# -ge 2 local what end what="/^$WB*struct$WB+$1"' \{$/' end='/^\};$/' # that's (^) different from enum-decl shift find-decl "$what" "$end" "$@" } # yield first line of $1 macro definition function find-macro-decl() { test $# -ge 2 local what end # only unindented defines, only whole-word match what="/^#define$WB+$1"'([ \t\(]|$)/' end=1 # only first line; use find-macro-implementation-decl for full body shift find-decl "$what" "$end" "$@" } # yield full macro implementation function find-macro-implementation-decl() { test $# -ge 2 local what end # only unindented defines, only whole-word match what="/^#define$WB+$1"'([ \t\(]|$)/' # full implementation, until a line not ending in a backslash. # Does not handle macros with comments embedded within the definition. end='/[^\\]$/' shift find-decl "$what" "$end" "$@" } # yield first line of $1 typedef definition (simple typedefs only) # this probably won't handle typedef struct { \n int foo;\n}; function find-typedef-decl() { test $# -ge 2 local what end what="/^typedef .* $1"';$/' end=1 shift find-decl "$what" "$end" "$@" } # gen() - DSL-like function to wrap around all the other # # syntax: # gen DEFINE if (KIND [METHOD of]) NAME [(matches|lacks) PATTERN|absent] in # where: # DEFINE is HAVE_ or NEED_ #define to print; # `if` is there to just read it easier and made syntax easier to check; # # NAME is the name for what we are looking for; # # KIND specifies what kind of declaration/definition we are looking for, # could be: fun, enum, struct, method, macro, typedef, # 'implementation of macro' # for KIND=method, we are looking for function ptr named METHOD in struct # named NAME (two optional args are then necessary (METHOD & of)); # # for KIND='implementation of macro' we are looking for the full # implementation of the macro, not just its first line. This is usually # combined with "matches" or "lacks". # # next [optional] args could be used: # matches PATTERN - use to grep for the PATTERN within definition # (eg, for ext_ack param) # lacks - use to add #define only if there is no match of the PATTERN, # *but* the NAME is *found* # absent - the NAME that we grep for must be not found # (ie: function not exisiting) # # without this optional params, behavior is the same as with # `matches .` - use to grep just for existence of NAME; # # `in` is there to ease syntax, similar to `if` before. # # is just space-separate list of files to look in, # single (-) for stdin. # # PATTERN is an awk pattern, will be wrapped by two slashes (/) function gen() { test $# -ge 6 || die 20 "too few arguments, $# given, at least 6 needed" local define if_kw kind name in_kw # mandatory local of_kw method_name operator pattern # optional local src_line="${BASH_SOURCE[0]}:${BASH_LINENO[0]}" define="$1" if_kw="$2" kind="$3" local orig_args_cnt=$# shift 3 [ "$if_kw" != if ] && die 21 "$src_line: 'if' keyword expected, '$if_kw' given" case "$kind" in fun|enum|struct|macro|typedef) name="$1" shift ;; method) test $# -ge 5 || die 22 "$src_line: too few arguments, $orig_args_cnt given, at least 8 needed" method_name="$1" of_kw="$2" name="$3" shift 3 [ "$of_kw" != of ] && die 23 "$src_line: 'of' keyword expected, '$of_kw' given" ;; implementation) test $# -ge 5 || die 28 "$src_line: too few arguments, $orig_args_cnt given, at least 8 needed" of_kw="$1" kind="$2" name="$3" shift 3 [ "$of_kw" != of ] && die 29 "$src_line: 'of' keyword expected, '$of_kw' given" [ "$kind" != macro ] && die 30 "$src_line: implementation only supports 'macro', '$kind' given" kind=macro-implementation ;; *) die 24 "$src_line: unknown KIND ($kind) to look for" ;; esac operator="$1" case "$operator" in absent) pattern='.' in_kw="$2" shift 2 ;; matches|lacks) pattern="$2" in_kw="$3" shift 3 ;; in) operator=matches pattern='.' in_kw=in shift ;; *) die 25 "$src_line: unknown OPERATOR ($operator) to look for" ;; esac [ "$in_kw" != in ] && die 26 "$src_line: 'in' keyword expected, '$in_kw' given" test $# -ge 1 || die 27 "$src_line: too few arguments, at least one filename expected" local first_decl= if [ "$kind" = method ]; then first_decl="$(find-struct-decl "$name" "$@")" || exit 40 # prepare params for next lookup phase set -- - # overwrite $@ to be single dash (-) name="$method_name" kind=fun elif [[ $# = 1 && "$1" = '-' ]]; then # avoid losing stdin provided to gen() due to redirection (<<<) first_decl="$(cat -)" fi # lookup the NAME local body body="$(find-$kind-decl "$name" "$@" <<< "$first_decl")" || exit 41 awk -v define="$define" -v pattern="$pattern" -v "$operator"=1 ' BEGIN { # prepend "identifier boundary" to pattern, also append # it, but only for patterns not ending with such already # # eg: "foo" -> "\bfoo\b" # "struct foo *" -> "\bstruct foo *" # Note that mawk does not support "\b", so we have our # own approximation, NI NI = "[^A-Za-z0-9_]" # "Not an Indentifier" if (!match(pattern, NI "$")) pattern = pattern "(" NI "|$)" pattern = "(^|" NI ")" pattern } /./ { not_empty = 1 } $0 ~ pattern { found = 1 } END { if (lacks && !found && not_empty || matches && found || absent && !found) print "#define", define } ' <<< "$body" } # tell if given flag is enabled in .config # return 0 if given flag is enabled, 1 otherwise # inputs: # $1 - flag to check (whole word, without _MODULE suffix) # env flag $CONFFILE # # there are two "config" formats supported, to ease up integrators lifes # .config (without leading #~ prefix): #~ # CONFIG_ACPI_EC_DEBUGFS is not set #~ CONFIG_ACPI_AC=y #~ CONFIG_ACPI_VIDEO=m # and autoconf.h, which would be: #~ #define CONFIG_ACPI_AC 1 #~ #define CONFIG_ACPI_VIDEO_MODULE 1 function config_has() { grep -qE "^(#define )?$1((_MODULE)? 1|=m|=y)$" "$CONFFILE" }