#! /bin/sh
# SPDX-License-Identifier: Apache-2.0
#
# (c) 2019, Google
progname="${0##*/}"

# Change to EXPORT_SYMBOL, EXPORT_SYMBOL_GPL or other namespace variant default
EXPORT_SYMBOL=${EXPORT_SYMBOL_GPL:-${progname#add_}}

USAGE="USAGE: ${progname} [--no-skip-arch] < kernel_build_error_log
       ${progname} [--no-skip-arch] kernel_build_error_log
       grep /<module>[.]ko build_error_log | ${progname} [--no-skip-arch]
       ${EDITOR:-vi} \`${progname} [--no-skip-arch] < kernel_build_error_log\`

To acquire the kernel_build_error_log eg:
\$ ./build_sm8250.sh -j50 2>&1 | tee kernel_build_error_log

To only create commit related to symbols needed for cam_spec.ko module:
\$ grep /cam_spec[.]ko kernel_build_error_log | ${progname}

To only create commit related to a specific list of symbols, there is
the option to land just the symbols, no spaces, one per line, into a
manufactured or edited kernel_build_error_log and feed that to the script.

The script will only affect the current directory level and downward,
this allows one to segregate the adjusted content.  Any symbols that
are needed outside the range of that directory will result in errors
and the git commit phase will not be performed.

Add ${EXPORT_SYMBOL} for any noted missing symbols, output the list of files
modified to stdout (so it can be passed to an editor command line should you
need to check or adjust the results). Automatically commit the list of files
into git.

Deals as simply as it can to handle __trace_<symbols>, sorting the result.

Keep in mind exports can change, be added or subtracted, and that preliminary
work may expose or remove required symbols to resolve during later work.  As
such this script only adds, so you may need to revert the results and try
again to get the most up to date set.  By making this part automated it can
deal with the tens or thousands of exports that need to be discovered or
added.  If you need to adjust a subsystem, run this script in the subsystem
directory, and it will only adjust from that point downwards leaving other
higher up trees alone."

if [ X"--help" = X"${1}" -o X"{-h}" = X"${1}" ]; then
  echo "${USAGE}" >&2
  exit
fi
skip_arch=true
if [ X"--no-skip-arch" = X"${1}" ]; then
  skip_arch=false
  shift
fi
INPUT=
if [ 1 = ${#} ]; then
  INPUT=${1}
  shift
fi
if [ 0 != ${#} ]; then
  echo "Unexpected Argument: ${*}" >&2
  echo >&2
  echo "${USAGE}" >&2
  exit 1
fi

# A _real_ embedded tab character
TAB="`echo | tr '\n' '\t'`"
# A _real_ embedded escape character
ESCAPE="`echo | tr '\n' '\033'`"
# A _real_ embedded carriage return character
CR="`echo | tr '\n' '\r'`"
# Colours
RED="${ESCAPE}[38;5;196m"
BLUE="${ESCAPE}[35m"
NORMAL="${ESCAPE}[0m"

# Common grep/sed regex expressions
STRUCT_TYPE="struct[ ${TAB}][^ ${TAB}]\{1,\}"
VAR_TYPE="\(${STRUCT_TYPE}\|bool\|char\|int\|long\|long long\|u\{0,1\}int[0-9]*t\)[ ${TAB}]\{1,\}[*]\{0,1\}"
STRUCT_TYPE="${STRUCT_TYPE}[ ${TAB}]\{1,\}[*]\{0,1\}"

# Files that exports should never exist, or need to be carefully considered
skip_arch_files() {
  if ${skip_arch}; then
    grep -v '\(^\|^[^:]*/\)arch/[^a][^r][^m][^:]*:'
  else
    cat -
  fi
}

# Following sucks in all the data from stdin or ${INPUT}
#  Check for depmod output (undefined!) or built-in kernel complaints (undefined
#  references).  The later is not advised because it means the kernel may be
#  dependent on a module, we collect it to provide a signal for future work.
echo "Reading symbols" >&2
MODULES="`
  cat ${INPUT:--} |
    tr -d \"${CR}\" |
    sed -n \
        -e '/^abi_gki_.*_whitelist$/d' \
        -e 's/^[_a-zA-Z][_a-zA-Z0-9]\{6,\}$/&/p' \
        -e 's/^ERROR: "\([^"]*\)" [[]\(.*[.]ko\)[]] undefined!$/\1 \2/p' \
        -e 's/^[^:]*:[0-9]*: undefined reference to \`\([^'\'']*\)'\''$/\1/p' |
    sort -u`"
SYMBOLS="`echo \"${MODULES}\" | sed 's/ .*$//' | sort -u`"
if [ -z "${SYMBOLS}" ]; then
  echo "${BLUE}WARNING${NORMAL}: no symbols found" >&2
  exit
fi

SYMBOLS_PLUS_TRACE="${SYMBOLS}"
TRACE_MODULES="`echo \"${MODULES}\" |
                  sed -n 's/__tracepoint_.* //p' |
                  sort -u`"
TRACE_MODULES_2=
c=`echo "${TRACE_MODULES}" | wc -l`
if [ 1 -eq ${c} ];then
  TRACE_MODULES="${TRACE_MODULES%/*.ko}"
  if [ -z "${INPUT}" ]; then
    TRACE_MODULES_2=*
  else
    TRACE_MODULES_2=${INPUT}
  fi
elif [ -z "${INPUT}" ]; then
  TRACE_MODULES=*
else
  TRACE_MODULES=${INPUT}
fi
TRACE_FILE="`grep -rl '^#define CREATE_TRACE_POINTS' ${TRACE_MODULES}`"
if [ -z "${TRACE_FILE}" -a -n "${TRACE_MODULES_2}" ]; then
  TRACE_FILE="`grep -rl '^#define CREATE_TRACE_POINTS' ${TRACE_MODULES_2}`"
fi
c=0
if [ -n "${TRACE_FILE}" ]; then
  c=`echo "${TRACE_FILE}" | wc -l`
fi
if [ 1 -eq ${c} ]; then
  SYMBOLS_PLUS_TRACE="${SYMBOLS}
`echo \"${SYMBOLS}\" |
   sed -n 's/^__tracepoint_\(.*\)/EXPORT_TRACEPOINT_SYMBOL_GPL(\1);/p'`"
else
  TRACE_FILE=
fi

echo "Finding symbols in the current tree" >&2
DATA="`grep -Fr \"${SYMBOLS_PLUS_TRACE}\" * |
         grep \"^[^:]*[.][cS]:[^ ${TAB}]\" |
         skip_arch_files`"
echo "Editing the files that contain the symbols" >&2
TMP=`mktemp -d`

[ 'USAGE: report_strip_of_tag tag

Report if a problematic function segment tag is stripped from the file

expects ${F}, ${TMP}, ${TAB}, ${RED} and ${NORMAL} to be defined' ]
report_strip_of_tag() {
  if diff --side-by-side --suppress-common-lines ${F} ${TMP}/${F##*/} |
    sed -n "s/\(.*[ ${TAB}]\)\(__${1}\)\([^a-zA-Z0-9_].*[^ ${TAB}]\)[ ${TAB}]\{1,\}|[ ${TAB}]\{1,\}/\1${BLUE}\2${NORMAL}\3 -> /p" |
       grep __${1} >&2; then
    echo "${RED}ERROR${NORMAL}: scrubbing __${1} from function in ${F}" >&2
    echo "       Compile check, possibly edit ${F} and friends." >&2
    echo "       Then re-run ${progname} to complete." >&2
    echo FAILED
  fi
}

for s in ${SYMBOLS}; do
  # Already there?
  if echo "${DATA}" |
       grep "EXPORT_\(TRACEPOINT_SYMBOL_GPL*(${s#__tracepoint_}\|SYMBOL\(_GPL\)*(${s}\))" >/dev/null; then
    echo INFO: ${s} found and already exported >&2
    continue
  fi
  m="`echo \"${DATA}\" | grep \"^[^:]*:\([^ ${TAB}].*[ *]\|\)${s}(\"`"
  c=0
  if [ -n "${m}" ]; then
    c=`echo "${m}" | wc -l`
  fi
  if [ 0 -eq ${c} ]; then
    m="`echo \"${DATA}\" | grep \"^[^:]*:\(const[ ${TAB}]\{1,\}\|\)${STRUCT_TYPE}${s}\([[][]]\|\) = {\"`"
    c=0
    if [ -n "${m}" ]; then
      c=`echo "${m}" | wc -l`
    fi
    if [ 0 -eq ${c} ]; then
      m="`echo \"${DATA}\" | grep \"^[^:]*:\(const[ ${TAB}]\{1,\}\|\)${VAR_TYPE}${s}[\[ ${TAB};=]\"`"
      c=0
      if [ -n "${m}" ]; then
        c=`echo "${m}" | wc -l`
      fi
      if [ 0 -eq ${c} ]; then
        if [ -z "${TRACE_FILE}" -o "${s}" = "${s#__tracepoint_}" ]; then
          echo "${RED}ERROR${NORMAL}: ${s} not found" >&2
          if [ "${s}" != "${s#__tracepoint_}" -o "${s}" = "${s#__}" ]; then
            echo FAILED
          else
            echo "${RED}WARNING${NORMAL}: ${s} might be a linker variable? skipping marking as failure" >&2
          fi
        else
          echo ${TRACE_FILE} ${s}
        fi
      elif [ 1 -eq ${c} ]; then
        echo "${DATA}" |
          sed -n "s/^\([^:]*\):\(const[ $TAB]\{1,\}\|\)${VAR_TYPE}${s}.*/\1 ${s}/p"
      else
        echo "${RED}ERROR${NORMAL}: ${c} matches: `echo \"${m}\" |
                                                   tr '\n' '&' |
                                                   sed -e 's/&$//' \
                                                       -e 's/&/ & /g'`" >&2
        echo FAILED
      fi
    elif [ 1 -eq ${c} ]; then
      echo "${DATA}" |
        sed -n "s/^\([^:]*\):\(const[ $TAB]\{1,\}\|\)${STRUCT_TYPE}\(${s}\)\([[][]]\|\) = {.*/\1 \3/p"
    else
      echo "${RED}ERROR${NORMAL}: ${c} matches: `echo \"${m}\" |
                                                 tr '\n' '&' |
                                                 sed -e 's/&$//' \
                                                     -e 's/&/ & /g'`" >&2
      echo FAILED
    fi
  elif [ 1 -eq ${c} ]; then
    echo "${DATA}" |
      sed -n "s/^\([^:]*\):\([^ ${TAB}].*[ *]\|\)\(${s}\)(.*/\1 \3/p"
  else
    echo "${RED}ERROR${NORMAL}: ${c} matches: `echo \"${m}\" |
                                               tr '\n' '&' |
                                               sed -e 's/&$//' \
                                                   -e 's/&/ & /g'`" >&2
    echo FAILED
  fi
done |
  sort -u |
  while read F s; do
    if [ "FAILED" = "${F}" ]; then
      echo FAILED
      continue
    fi
    if [ "${s}" != "${s#__tracepoint_}" ]; then
      sed "\$ {
             /^[^E]/ {
               a \

             }
             a \
               newEXPORT_TRACEPOINT_SYMBOL_GPL(${s#__tracepoint_});
           }" ${F}
    else
      cat ${F}
    fi |
    sed "/^\([^ ${TAB}].*[ *]\|\)${s}(/ {
           : loop1
           N
           /\(\n}\);*$/ {
             s//\1/
             s/^\([^\n]*[^\n ${TAB}]\|\)[ ${TAB}]\{1,\}__\(init\|exit\)[ ${TAB}]\{1,\}\([^\n]*(\)/\1 \3/
             s/^\([^\n]*[^\n ${TAB}]\|\)[ ${TAB}]\{1,\}__\(init\|exit\)\([^ ${TAB}a-zA-Z0-9_][^\n]*(\)/\1 \3/
             s/^__\(init\|exit\)[ ${TAB}]\{1,\}\([^\n]*(\)/\2/
             s/^__\(init\|exit\)\([^ ${TAB}a-zA-Z0-9_][^\n]*(\)/\2/
             a \
               new${EXPORT_SYMBOL}(${s});
             b next1
           }
           b loop1
           : next1
         }
         /^\(const[ ${TAB}]\{1,\}\|\)${STRUCT_TYPE}${s}\([[][]]\|\)[ ${TAB}]*=[ ${TAB}]*{/ {
           : loop2
           N
           /\n};$/ {
             a \
               new${EXPORT_SYMBOL}(${s});
             b next2
           }
           b loop2
           : next2
         }
         /^\(const[ ${TAB}]\{1,\}\|\)${VAR_TYPE}${s}\(\([[][]]\|\)[ ${TAB}]*=.*\|\);/ {
           a \
             new${EXPORT_SYMBOL}(${s});
         }
         /^\(const[ ${TAB}]\{1,\}\|\)${VAR_TYPE}${s}\([[][]]\|\)[ ${TAB}]*=[ ${TAB}]*{\{0,1\}\$/ {
           : loop3
           N
           /;$/ {
             a \
               new${EXPORT_SYMBOL}(${s});
             b next3
           }
           b loop3
           : next3
         }" |
      sed '/^newEXPORT/ {
             s//EXPORT/
             N
             : loop
             N
             s/\(\n\)\n$/\1/
             t loop
           }' >${TMP}/${F##*/} &&
      ! cmp -s ${F} ${TMP}/${F##*/} &&
      [ -s ${TMP}/${F##*/} ] &&
      report_strip_of_tag init &&
      report_strip_of_tag exit &&
      cp ${TMP}/${F##*/} ${F} &&
      echo INFO: export ${s} in ${F} >&2 ||
      (
        echo "${RED}ERROR${NORMAL}: export ${s} in ${F}" >&2
        echo FAILED
      )
    echo ${F}
    rm ${TMP}/${F##*/}
  done >${TMP}/${progname}.out
FILES="`grep -v '^FAILED$' ${TMP}/${progname}.out | sort -u`"
echo "${FILES}"
grep '^FAILED$' ${TMP}/${progname}.out >/dev/null
ret=${?}
rm -rf ${TMP}
if [ 0 -ne ${ret} ]; then
  echo "DONE" >&2
  GIT_FILES="`git diff |
                sed -n 's@^diff --git a/\(.*\) b/\1$@\1@p' |
                sort -u`"
  if [ -z "${GIT_FILES}" ]; then
    echo "${BLUE}WARNING${NORMAL}: no changes to commit" >&2
    if [ -n "${FILES}" ]; then
      echo "         but we altered files!" >&2
    fi
    exit
  fi
  SEMI=""
  if [ X"${GIT_FILES}" != X"`echo ${FILES} | tr ' ' '\n' | sort -u`" ]; then
    SEMI="Semi-"
    FILES=`echo ${FILES} ${GIT_FILES} |
             tr ' ' '\n' |
             sort -u`
    echo "${BLUE}WARNING${NORMAL}: list of files in git different from files we expected to edit, continuation?" >&2
  fi

  AUTHOR_NAME="`git config user.name`"
  AUTHOR_EMAIL="`git config user.email`"
  if [ -z "${AUTHOR_EMAIL}" ]; then
    AUTHOR_EMAIL="`${USER}@google.com`"
  fi
  if [ -z "${AUTHOR_NAME}" ]; then
    convert_ldap_to_name() {
      echo "${1##*/}" |
        sed "s@[-_]@ @g
             s@   *@ @g" |
        sed 's@\(^\| \)a@\1A@g
             s@\(^\| \)b@\1B@g
             s@\(^\| \)c@\1C@g
             s@\(^\| \)d@\1D@g
             s@\(^\| \)e@\1E@g
             s@\(^\| \)f@\1F@g
             s@\(^\| \)g@\1G@g
             s@\(^\| \)h@\1H@g
             s@\(^\| \)i@\1I@g
             s@\(^\| \)j@\1J@g
             s@\(^\| \)k@\1K@g
             s@\(^\| \)l@\1L@g
             s@\(^\| \)m@\1M@g
             s@\(^\| \)n@\1N@g
             s@\(^\| \)o@\1O@g
             s@\(^\| \)p@\1P@g
             s@\(^\| \)q@\1Q@g
             s@\(^\| \)r@\1R@g
             s@\(^\| \)s@\1S@g
             s@\(^\| \)t@\1T@g
             s@\(^\| \)u@\1U@g
             s@\(^\| \|Mc\)v@\1V@g
             s@\(^\| \)w@\1W@g
             s@\(^\| \)x@\1X@g
             s@\(^\| \)y@\1Y@g
             s@\(^\| \)z@\1Z@g'
    }
    AUTHOR_NAME="`convert_ldap_to_name ${USER}`"
  fi

  git commit ${FILES} -m "GKI: add missing exports for <config>=m for <compatible>

<edit>

${SEMI}Automatically generated by ${0##*/}

The following symbols are exported:
`echo \"${SYMBOLS}\" |
   sed 's/^/ - /'`

Signed-off-by: ${AUTHOR_NAME} <${AUTHOR_EMAIL}>
Test: compile" ||
    echo "${RED}ERROR${NORMAL}: failed to commit changes to git" >&2
  exit
fi
echo "${RED}FAILED${NORMAL}: could not complete task" >&2
exit 1
