#! /bin/sh
# SPDX-License-Identifier: Apache-2.0
#
# (c) 2019, Google

progname=${0##*/}
NUM=XX
USAGE="USAGE: ${progname} [-s <serialno>] [-D] [-f [<input>]] [-F [-o <output> [-d <input>]]]

Collect filtered /dev and /sys details, along with dmesg and probe list.

-o <output> will drop the collection into a set of files, but will not
overwrite existing content.  -F will overwrite.

-D will wait for the display.

if <output> is empty ('' or last option), will not collect dmesg or probe
list.  If no -o option is specified, then <output> will be default of -
(stdout) and all pieces will go to the standard output separated by a cut
and snip header.  If specified, <output> will contain the filtered /dev/
and /sys/ dumps, <output>.probed the subset filter of just the probed drivers,
<output>.dmesg the kernel logs and <output>.config the uncompressed
/proc/config.gz.

-d <input> will take the dropped collection specified to -o <output> and
produce a diff -U1 output compared against the <input>.

-f <input> allows one to utilize the filter to an existing find /dev /sys
output from a device.  No dmesg will be collected.

-s <serialno> will allow one to specify a device to connect to when multiples
are available, otherwise will default to one available or ANDROID_SERIAL
environment variable.

NB: The /dev/ and /sys/ dumps can be compared with something like a diff -U1
    and report a generally noiseless result so it is easy to spot the side
    effects from one build to the next.  Basically nothing else should be
    added or removed from the differences other than the expectations, save
    for normal noise from the dynamic nature of the kernel, thus making it
    beneficial to run this script collection at the same point in the boot
    process to keep noise down.

NB: for default standard output, the probed list will be post-filter so
    some details may be altered to make it easier to discover differences.
    So if bus enumerations change, some of the enumerators will be replaced
    with an '${NUM}' so that they continue to match when moved.  For a
    specified output, the <output>.probed file will retain the details."

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

##
##  Helper Functions
##

[ "USAGE: inFastboot

Returns: true if device is in fastboot mode" ]
inFastboot() {
  fastboot devices |
    if [ -n "${ANDROID_SERIAL}" ]; then
      grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null
    else
      wc -l | grep '^1$' >/dev/null
    fi
}

[ "USAGE: inAdb

Returns: true if device is in adb mode" ]
inAdb() {
  adb devices </dev/null |
    grep -v -e 'List of devices attached' -e '^$' -e "[${SPACE}${TAB}]recovery\$" |
    if [ -n "${ANDROID_SERIAL}" ]; then
      grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null
    else
      wc -l | grep '^1$' >/dev/null
    fi
}

[ "USAGE: inRecovery

Returns: true if device is in recovery mode" ]
inRecovery() {
  local list="`adb devices |
              grep -v -e 'List of devices attached' -e '^$'`"
  if [ -n "${ANDROID_SERIAL}" ]; then
    echo "${list}" |
      grep "^${ANDROID_SERIAL}[${SPACE}${TAB}][${SPACE}${TAB}]*recovery\$" >/dev/null
    return ${?}
  fi
  if echo "${list}" | wc -l | grep '^1$' >/dev/null; then
    echo "${list}" |
      grep "[${SPACE}${TAB}]recovery\$" >/dev/null
    return ${?}
  fi
  false
}

[ "USAGE: USB_DEVICE=\`usb_devnum [--next]\`

USB_DEVICE contains cache. Update if system changes.

Returns: the devnum for the USB_SERIAL device" ]
usb_devnum() {
  if [ -n "${USB_SERIAL}" ]; then
    local usb_device=`cat ${USB_SERIAL%/serial}/devnum 2>/dev/null | tr -d ' \t\r\n'`
    if [ -n "${usb_device}" ]; then
      USB_DEVICE=dev${usb_device}
    elif [ -n "${USB_DEVICE}" -a "${1}" ]; then
      USB_DEVICE=dev`expr ${USB_DEVICE#dev} + 1`
    fi
    echo "${USB_DEVICE}"
  fi
}

[ "USAGE: adb_sh <commands> </dev/stdin >/dev/stdout 2>/dev/stderr

Returns: true if the command succeeded" ]
adb_sh() {
  local args=
  for i in "${@}"; do
    [ -z "${args}" ] || args="${args} "
    if [ X"${i}" != X"${i#\'}" ]; then
      args="${args}${i}"
    elif [ X"${i}" != X"${i#*\\}" ]; then
      args="${args}`echo ${i} | sed 's/\\\\/\\\\\\\\/g'`"
    elif [ X"${i}" != X"${i#* }" ]; then
      args="${args}'${i}'"
    elif [ X"${i}" != X"${i#*${TAB}}" ]; then
      args="${args}'${i}'"
    else
      args="${args}${i}"
    fi
  done
  adb shell "${args}"
}

[ "USAGE: adb_su <commands> </dev/stdin >/dev/stdout 2>/dev/stderr

Returns: true if the command running as root succeeded" ]
adb_su() {
  adb_sh su root "${@}"
}

[ "USAGE: get_property <prop>

Returns the property value" ]
get_property() {
  adb_sh getprop ${1} 2>/dev/null </dev/null
}

[ "USAGE: format_duration [<seconds>|<seconds>s|<minutes>m|<hours>h|<days>d]

human readable output whole seconds, whole minutes or mm:ss" ]
format_duration() {
  if [ -z "${1}" ]; then
    echo unknown
    return
  fi
  local duration="${1}"
  if [ X"${duration}" != X"${duration%s}" ]; then
    duration=${duration%s}
  elif [ X"${duration}" != X"${duration%m}" ]; then
    duration=`expr ${duration%m} \* 60`
  elif [ X"${duration}" != X"${duration%h}" ]; then
    duration=`expr ${duration%h} \* 3600`
  elif [ X"${duration}" != X"${duration%d}" ]; then
    duration=`expr ${duration%d} \* 86400`
  fi
  local seconds=`expr ${duration} % 60`
  local minutes=`expr \( ${duration} / 60 \) % 60`
  local hours=`expr ${duration} / 3600`
  if [ 0 -eq ${minutes} -a 0 -eq ${hours} ]; then
    if [ 1 -eq ${duration} ]; then
      echo 1 second
      return
    fi
    echo ${duration} seconds
    return
  elif [ 60 -eq ${duration} ]; then
    echo 1 minute
    return
  elif [ 0 -eq ${seconds} -a 0 -eq ${hours} ]; then
    echo ${minutes} minutes
    return
  fi
  if [ 0 -eq ${hours} ]; then
    echo ${minutes}:`expr ${seconds} / 10``expr ${seconds} % 10`
    return
  fi
  echo ${hours}:`expr ${minutes} / 10``expr ${minutes} % 10`:`expr ${seconds} / 10``expr ${seconds} % 10`
}

[ "USAGE: adb_wait [timeout]

Returns: waits until the device has returned for adb or optional timeout" ]
adb_wait() {
  local start=`date +%s`
  local duration=
  local ret
  if [ -n "${1}" ]; then
    USB_DEVICE=`usb_devnum --next`
    duration=`format_duration ${1}`
    echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
    timeout --preserve-status --signal=KILL ${1} adb wait-for-device 2>/dev/null
    ret=${?}
    echo -n "                                                                             ${CR}"
  else
    adb wait-for-device
    ret=${?}
  fi
  USB_DEVICE=`usb_devnum`
  if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
    local active_slot=`get_active_slot`
    if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" >&2
    fi
  fi
  local end=`date +%s`
  local diff_time=`expr ${end} - ${start}`
  local _print_time=${print_time}
  if [ ${diff_time} -lt 15 ]; then
    _print_time=false
  fi
  diff_time=`format_duration ${diff_time}`
  if [ "${diff_time}" = "${duration}" ]; then
    _print_time=false
  fi

  local reason=
  if inAdb; then
    reason=`get_property ro.boot.bootreason`
  fi
  case ${reason} in
    reboot*)
      reason=
      ;;
    ${EMPTY})
      ;;
    *)
      reason=" for boot reason ${reason}"
      ;;
  esac
  if ${_print_time} || [ -n "${reason}" ]; then
    echo "${BLUE}[     INFO ]${NORMAL} adb wait duration ${diff_time}${reason}"
  fi >&2

  return ${ret}
}

[ "USAGE: fastboot_wait [timeout]

Returns: waits until the device has returned for fastboot or optional timeout" ]
fastboot_wait() {
  local ret
  # fastboot has no wait-for-device, but it does an automatic
  # wait and requires (even a nonsensical) command to do so.
  if [ -n "${1}" ]; then
    USB_DEVICE=`usb_devnum --next`
    echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
    timeout --preserve-status --signal=KILL ${1} fastboot wait-for-device >/dev/null 2>/dev/null
    ret=${?}
    echo -n "                                                                             ${CR}"
    ( exit ${ret} )
  else
    fastboot wait-for-device >/dev/null 2>/dev/null
  fi ||
    inFastboot
  ret=${?}
  USB_DEVICE=`usb_devnum`
  if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
    local active_slot=`get_active_slot`
    if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
    fi >&2
  fi
  return ${ret}
}

[ "USAGE: recovery_wait [timeout]

Returns: waits until the device has returned for recovery or optional timeout" ]
recovery_wait() {
  local ret
  if [ -n "${1}" ]; then
    USB_DEVICE=`usb_devnum --next`
    echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
    timeout --preserve-status --signal=KILL ${1} adb wait-for-recovery 2>/dev/null
    ret=${?}
    echo -n "                                                                             ${CR}"
  else
    adb wait-for-recovery
    ret=${?}
  fi
  USB_DEVICE=`usb_devnum`
  if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
    local active_slot=`get_active_slot`
    if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
    fi >&2
  fi
  return ${ret}
}

[ "any_wait [timeout]

Returns: waits until a device has returned or optional timeout" ]
any_wait() {
  (
    adb_wait ${1} &
    adb_pid=${!}
    fastboot_wait ${1} &
    fastboot_pid=${!}
    recovery_wait ${1} &
    recovery_pid=${!}
    wait -n
    kill "${adb_pid}" "${fastboot_pid}" "${recovery_pid}"
  ) >/dev/null 2>/dev/null
  inFastboot || inAdb || inRecovery
}

##
##  RUNTIME
##

filter=
output=-
input=
display=false
force=false
while [ ${#} -gt 0 ]; do
  case ${1} in
    -f=* | --filter=*)
      filter="${1#-f=}"
      filter="${filter#--filter=}"
      ;;
    -f | --filter)
      if [ ${#} = 1 -o -z "${2}" ]; then
        filter=-
      else
        shift
        filter="${1}"
      fi
      ;;
    -F | --force)
      force=true
      ;;
    -h | -\? | --help)
      echo "${USAGE}"
      exit 0
      ;;
    -o=* | --output=*)
      output="${1#-o=}"
      output="${output#--output=}"
      ;;
    -o | --output)
      if [ ${#} = 1 -o -z "${2}" ]; then
        # empty output results in no dmesg or probed collection
        output=
      else
        shift
        output="${1}"
      fi
      ;;
    -d=* | --diff=*)
      input="${1#-d=}"
      input="${input#--diff=}"
      ;;
    -d | --diff)
      shift
      input="${1}"
      ;;
    -D | --wait-for-display)
      display=true
      ;;
    -s=* | --serial=*)
      ANDROID_SERIAL="${1#-s=}"
      export ANDROID_SERIAL="${ANDROID_SERIAL#--serial=}"
      ;;
    -s | --serial)
      shift
      export ANDROID_SERIAL="${1}"
      ;;
    *)
      echo "${RED}[    ERROR ]${NORMAL} unknown parm ${1}" >&2
      echo "${USAGE}" >&2
      exit 1
      ;;
  esac
  shift
done

if [ -n "${input}" -a -z "${output#-}" ]; then
  echo "${RED}[    ERROR ]${NORMAL} must specify an -o <output> along with the -d ${input}" >&2
  echo "${USAGE}" >&2
  exit 1
fi

USB_SERIAL=
[ -z "${ANDROID_SERIAL}" ] || USB_SERIAL=`find /sys/devices -name serial |
                                          grep usb |
                                          xargs grep -l ${ANDROID_SERIAL}`
USB_ADDRESS=
if [ -n "${USB_SERIAL}" ]; then
  USB_ADDRESS=${USB_SERIAL%/serial}
  USB_ADDRESS=usb${USB_ADDRESS##*/}
fi

[ "USAGE: input= BLUE= output= NORMAL= perform_diff < ${output}

Reports the differences between stdin and \${input}" ]
perform_diff() {
  diff -U1 "${input}" - |
    # Squash noise, replacing long stretches of changes with trailing ...
    sed '/^@@ -1,[0-9]\{4,\} [+]0,0 @@$/ q
         /^@@ -0,0 [+]1,[0-9]\{4,\} @@$/ q
         /^-\/sys\/module\/[^\/]*\/sections\/[.]bss$/ {
           N
           /^\(-\/sys\/module\/[^\/]*\/sections\/[.]\)bss\n\1data$/ {
             : loop
             N
             /^-\(\/sys\/module\/[^\/]*\/sections\/[.]\)bss\n-\1data.*\n[+]\1bss[.][^\n]*$/ b loop
             s/^-\(\/sys\/module\/[^\/]*\/sections\/[.]\)bss\n-\1data\(.*\n[+]\1bss[.][^\n]*\)\(\n\)\([^\n]*\)$/-\1bss\2\3-\1data\3\4/
           }
         }' |
    sed '/^[-+ ]\/sys\/module\/[^\/]*$/ {
           : loop1
           N
           /^ \([^\n]*\)\n[+]\1\/\(coresize\|parameters\)$/ b loop2
           /^\([-+][^\n]*\)\n\1\/\(coresize\|parameters\)$/ {
             : loop2
             N
             /^\([-+][^\n]*\)\n.*\n\1\/uevent$/ b skip1
             /^ \([^\n]*\)\n.*\n[+]\1\/uevent$/ b skip2
             /^ \([^\n]*\)\n.*\n[+]\1\/[^\n]*$/ b loop2
             /^\([-+][^\n]*\)\n.*\n\1\/[^\n]*$/ b loop2
             h
             s/^\([-+][^\n]*\)\n.*\n\1\/[^\n]*\n[^\n]*$/\1\/.../
             s/^ \([^\n]*\)\(\n\).*\n[+]\1\/[^\n]*\n[^\n]*$/ \1\2+\1\/.../
             p
             g
             s/^.*\n\([^\n]*\)$/\1/
             /^[-+ ]\/sys\/module\/[^\/]*$/ b loop1
             b exit
             : skip1
             s/^\([^\n]*\)\n.*\n\1\/uevent$/\1\/.../
             b exit
             : skip2
             s/^ \([^\n]*\)\(\n\).*\n[+]\1\/uevent$/\1\2\1\/.../
             : exit
           }
         }
         /^[-+]\/sys\/module\/[^\/]*\/refcnt$/ {
           : loop3
           N
           /^\([-+][^\n]*\/\)refcnt.*\n\1[^\n]*$/ b loop3
           h
           s/^\([-+][^\n]*\/\)\(refcnt\n\).*\n\1[^\n]*\n[^\n]*$/\1\2\1.../
           p
           g
           s/^.*\n\([^\n]*\)$/\1/
           /^[-+ ]\/sys\/module\/[^\/]*$/ b loop1
         }
         /^-\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)$/ {
           : loop4
           N
           /^-\(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\).*\n[+]\1[.][^\n]*$/ b loop4
           h
           s/^-\(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\)\n.*\(\n\)[+]\1[.][^\n]*\n[^\n]*$/-\1\3+\1.../
           s/^-\(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\)\n[^\n]*[^.][^.]$/-\1/
           s/^-\(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\)\(\n[+]\1[.][^\n]*\)\n[^\n]*[^.][^.]$/-\1\3/
           p
           g
           s/^.*\n\([^\n]*\)$/\1/
           /^-\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)$/ b loop4
           /^[-+ ]\/sys\/module\/[^\/]*$/ b loop1
         }
         /^ \/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)[.]/ {
           : loop5
           N
           /^ \(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\)[.][^\n].*\n[+]\1[.][^\n]*$/ b loop5
           h
           s/^ \(\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)\)\([.][^\n]*\n\).*\n[+]\1[.][^\n]*\n[^\n]*$/ \1\3+\1.../
           s/^\( \/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)[.][^\n]*\)\n[^\n]*[^.][^.]$/\1/
           p
           g
           s/^.*\n\([^\n]*\)$/\1/
           /^ \/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)[.]/ b loop5
           /^-\/sys\/module\/[^\/]*\/sections\/[.]\(bss\|data\|rodata\|text\)$/ b loop4
           /^[-+ ]\/sys\/module\/[^\/]*$/ b loop1
         }' |
    grep '[-+ ]' ||
    echo "${BLUE}[     INFO ]${NORMAL} no differences in /dev/ and /sys/ since last device snapshot" >&2
  if [ -s "${output}.probed" -a -s "${input}.probed" ]; then
    diff -U1 "${input}.probed" "${output}.probed" |
    grep '[-+ ]' ||
    echo "${BLUE}[     INFO ]${NORMAL} no differences in probed devices and drivers" >&2
  fi
  if [ -s "${output}.dmesg" -a -s "${input}.dmesg" ]; then
    TMP=`mktemp -d`
    trap "rm -rf ${TMP} ; exit 1" 1 2 3 15
    mkfifo ${TMP}/input
    sed -n 's@^.*init: Loaded kernel module /lib/modules/@@p' ${input}.dmesg > ${TMP}/input &
    sed -n 's@^.*init: Loaded kernel module /lib/modules/@@p' ${output}.dmesg |
      diff -U1 ${TMP}/input - |
      sed '/^@@ -1,[0-9]\{3,\} [+]0,0 @@$/ q
           /^@@ -0,0 [+]1,[0-9]\{3,\} @@$/ q' |
      grep '[-+ ]' ||
      echo "${BLUE}[     INFO ]${NORMAL} no differences in module load order" >&2
    wait
    rm -rf ${TMP}
    trap "exit 1" 1 2 3 15
  fi
  if [ -s "${output}.config" -a -s "${input}.config" ]; then
    diff -U1 "${input}.config" "${output}.config" |
      grep '[-+ ]' ||
      echo "${BLUE}[     INFO ]${NORMAL} no differences in config" >&2
  fi
  if [ -s "${output}.getprop" -a -s "${input}.getprop" ]; then
    TMP=`mktemp -d`
    trap "rm -rf ${TMP} ; exit 1" 1 2 3 15
    sed -n 's/[[]ro[.]boottime[.]\([^]]*\)[]]: [[]\([0-9]*\)[]]$/\1 \2/p' "${input}.getprop" >${TMP}/old.getprop
    sed -n 's/[[]ro[.]boottime[.]\([^]]*\)[]]: [[]\([0-9]*\)[]]$/\2 \1/p' "${output}.getprop" |
      sort -un |
      while read monotonic action; do
        [ -n "${action}" ] || continue
        [ -n "${monotonic}" ] || continue
        old=`sed -n "s/^${action} //p" ${TMP}/old.getprop`
        [ -n "${old}" ] || continue
        old="$((${monotonic} - ${old}))"
        [ -n "${old}" -a 0 != "${old}" ] || continue
        sign=+
        if [ -z "${old##-*}" ]; then
          sign=-
          old=${old#-}
        fi
        printf "${action}\t${sign}%u.%09us\n" $((${old} / 1000000000)) $((${old} % 1000000000))
      done |
      grep '[0-9][.][0-9]' ||
      echo "${BLUE}[     INFO ]${NORMAL} no differences in boottime" >&2
    rm -rf ${TMP}
    trap "exit 1" 1 2 3 15
  fi
}

if [ -n "${output#-}" -a -s "${output}" ]; then
  if ! ${force}; then
    if [ -n "${input}" ]; then
      perform_diff <${output}
      exit 0
    fi
    echo "${RED}[    ERROR ]${NORMAL} will not overwrite existing content" >&2
    exit 1
  fi
fi

if [ -z "${filter}" ]; then
  if inFastboot; then
    echo "${RED}[    ERROR ]${NORMAL} in fastboot mode" >&2
    exit 1
  fi
  if inRecovery; then
    echo "${RED}[    ERROR ]${NORMAL} in recovery mode" >&2
    echo "${BLUE}[     INFO ]${NORMAL} waiting 2 more minutes for adb ..."
    adb_wait 2m || exit 1
  fi
  if ! inAdb; then
    any_wait 2m
    if ! inAdb; then
      if [ -n "${ANDROID_SERIAL}" ]; then
        echo "${RED}[    ERROR ]${NORMAL} not in adb mode" >&2
        exit 1
      fi
      COUNT=`adb devices </dev/null |
               grep -v -e 'List of devices attached' -e '^$' |
               wc -l`
      if [ 1 -ne ${COUNT} ]; then
        echo "${RED}[    ERROR ]${NORMAL} ${COUNT} devices attached" >&2
        exit 1
      fi
    fi
  fi
  counter=0
  while ${display}; do
    if inFastboot; then
      fastboot reboot
    elif inAdb; then
      if [ "1" = "`get_property sys.boot_completed`" ]; then
        break
      fi
    fi
    counter=$((${counter} + 1))
    if [ ${counter} -gt 120 ]; then
      break
    fi
    sleep 1
  done
fi

if [ -n "${filter}" ]; then
  cat ${filter}
else
  adb_su find /dev /sys </dev/null 2>/dev/null |
  if [ -n "${output}" -a X"${output}" != X"-" ]; then
    # Generate ${output}.probed:
    #
    # Report all the instantiated devices and which driver is responsible.
    # If drivers do not have any instantiated devices, report them at end.
    # Drivers are of a form driver:<subsystem>:<name>, if <subsystem> is
    # empty, it is short hand for platform.
    #
    DRIVERS=
    FOUND=
    TMP=`mktemp -d`
    trap "rm -rf ${TMP} ; exit 1" 1 2 3 15
    mkfifo ${TMP}/99
    (
      tee ${TMP}/99 |
        sed -n \
            -e 's@/sys/devices/platform/.*/driver$@&@p' \
            -e 's@/sys/bus/\([^/]*\)/drivers/\([^/]*\)$@driver:\1:\2@p' |
        sort -u
        echo DONE
    ) | (
      cat ${TMP}/99 &
      while read driver; do
        [ -n "${driver}" ] || continue
        case ${driver} in
          DONE)
            [ -n "${DRIVERS}" ] || break
            if [ -z "${FOUND}" ]; then
              echo "${DRIVERS}"
            else
              echo "${DRIVERS}" |
                grep -ve "${FOUND}\)\$" ||
                true
            fi |
              sed 's/driver:platform:/driver::/'
            break
            ;;
          driver:*)
            [ -z "${DRIVERS}" ] || DRIVERS="${DRIVERS}
"
            DRIVERS="${DRIVERS}${driver}"
            ;;
          *)
            INSTANCE="${driver#/sys/devices/platform/}"
            INSTANCE="${INSTANCE%/driver}"
            DRIVER="`adb_su ls -l ${driver} </dev/null 2>/dev/null |
                     sed -n 's@.* -> .*/bus/\([^/]*\)/drivers/\(.*\)@driver:\1:\2@p'`"
            if [ -z "${DRIVER}" ]; then
              echo "${INSTANCE}"
            else
              if [ -z "${FOUND}" ]; then
                FOUND="^\(${DRIVER}"
              else
                FOUND="${FOUND}\|${DRIVER}"
              fi
              DRIVER="${DRIVER#driver:}"
              echo "${INSTANCE} -> driver:${DRIVER#platform}"
            fi
            ;;
        esac
      done >"${output}.probed"
      wait
    )
    rm -rf ${TMP}
    trap 'exit 1' 1 2 3 15
    [ -s "${output}.probed" ] || rm -f "${output}.probed"
    # Generate ${output}.dmesg
    adb_su dmesg </dev/null 2>/dev/null >"${output}.dmesg"
    [ -s "${output}.dmesg" ] || rm -f "${output}.dmesg"
    # Generate ${output}.config if kernel provides it.
    adb_su cat /proc/config.gz </dev/null 2>/dev/null |
      gunzip >"${output}.config"
    [ -s "${output}.config" ] || rm -f "${output}.config"
    # Generate ${output}.getprop
    adb $QD shell su root getprop </dev/null 2>/dev/null |
      sort -u >"${output}.getprop"
    [ -s "${output}.getprop" ] || rm -f "${output}.getprop"
  fi
fi |
  grep -v '^\(/dev/ashmem\|/dev/block/mapper/by-uuid/\|/dev/fscklogs/log\)' |
  grep -v '^\(/dev/socket/location/\(socket_hal\|cld80211\)\|/sys/class/hwmon/\)' |
  grep -v '^/dev/socket/netmgr/netmgr_connect_socket$' |
  grep -v '^/dev/socket/qdma/qdma-\(file\|campmgr\)-s$' |
  grep -v '^/dev/socket/qmux_radio/' |
  grep -v '^/dev/socket/vendor_wpa_wlan' |
  grep -v '^/dev/socket/wifihal/wifihal_ctrlsock' |
  grep -v '^/sys/dev/char/[0-9]*:[0-9]*$' |
  grep -v '^/sys/devices/virtual/thermal/tz-by-name/wsatz[.][0-9]*$' |
  grep -v '^/sys/kernel/debug/tracing/instances/wifi' |
  grep -v '^/sys/kernel/debug/\(wlan\|WMI\|p2p\|msm-bus-dbg/client-data/npu_bwmon_cdsp\)' |
  grep -v '^/sys/class/net/\(wlan\|p2p\)' |
  grep -v '^/sys/class/ieee80211/phy' |
  grep -v '^/sys/devices/platform/soc/[0-9a-f]*.qcom,icnss/\(net\|firmware\|ieee80211\)' |
  grep -v '^/sys/devices/platform/soc/[0-9a-f]*.*@[0-9][0-9]*-cam_\(vana\|vdig\|vio\|clk\)$' |
  grep -v '^/sys/kernel/wlan/' |
  grep -v '^/sys/kernel/slab/\(f2fs_fsync_inode_entry\|zs\|:[0-9a-f]\{0,7\}\)' |
  grep -v '^/sys/devices/system/cpu/cpufreq/policy[0-9]*/schedutil' |
  grep -v '/__trace_printk_fmt$' |
  sed "s@\(/regulator[.]\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/regulator[.]\)[0-9][0-9]*\(/\|-SUPPLY\)@\1${NUM}\2@g
       s@\(/vppservice_\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/msi_irqs/\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/sys/class/rfkill/rfkill\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/class\|/sys/devices/platform/soc/[0-9a-f]*[.]qcom,gpi-dma\)\(/dma/dma\)[0-9]*\(chan[0-9]*\(\|/.*\)\)\$@\1\2${NUM}\3@
       s@\(/i2c-\)\([0-9]\)\(/[^/]*/i2c-\)\2\(\|/.*\)\$@\1${NUM}\3${NUM}\4@
       s@\(/i2c-\)[0-9]\(\|/[^/]*\)\$@\1${NUM}\2@
       s@-\([0-9]\)/\1\(-[0-9][0-9][0-9][0-9]/\)@-${NUM}/${NUM}\2@g
       s@/[0-9]\(-[0-9][0-9][0-9][0-9]\(\|-vdd\|-vdd/.*\|-avdd\|-avdd/.*\|-vio\|-vio/.*\|/.*\)\)\$@${NUM}\1@
       s@\(/sys/devices/platform/soc/[0-9a-z]*[.]qcom,[^/]*/subsys\)[0-9][0-9]*@\1${NUM}@
       s@\(rpmsg_chrdev[.]0[.]0/rpmsg/rpmsg_ctrl\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/devices/virtual/kgsl/kgsl/pagetables/\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/devices/virtual/kgsl/kgsl/proc/\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/devices/virtual/thermal/thermal_zone\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/devices/virtual/net/\)\(r_\|\)\(rmnet_data\)[0-9][0-9]*\(/queues/tx-\)[0-9][0-9]*@\1\3${NUM}\4${NUM}@
       s@\(/sys/devices/platform/soc/soc:qcom,msm-audio-apr/soc:qcom,msm-audio-apr:qcom,q6core-audio/soc:qcom,msm-audio-apr:qcom,q6core-audio:lpi_pinctrl[@][0-9a-f]*/gpiochip\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/kernel/debug/adsprpc/\(adsp\|cdsp\|ssc\)rpcd_\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/sys/kernel/debug/binder/proc/\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/sys/kernel/debug/kgsl/proc/\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/kernel/debug/kgsl/kgsl-[0-9a-f][0-9a-f]*/ctx/\)[0-9][0-9]*@\1${NUM}@
       s@\(/sys/module/[^/]*/sections/.*[.]llvm[.]\)[0-9][0-9]*\$@\1${NUM}@
       s@\(/sys/kernel/debug/msm_cvp/core0/inst_\)[0-9a-f][0-9a-f]*@\1${NUM}@
       s@\(/sys/kernel/iommu_groups/\)[0-9][0-9]*@\1${NUM}@
       s@\(/devices/soc:qcom,msm_fastrpc:qcom,msm_fastrpc_compute_cb\)[0-9][0-9]*@\1${NUM}@
       s@/mmc[0-9][0-9]*\($\|/\|:\)@/mmc${NUM}\1@
       s@\(/sys/kernel/irq/\)[0-9][0-9]*@\1${NUM}@" |
  if [ -n "${output}" -a X"${output}" != X"-" ]; then
    sort -u |
      if [ -n "${input}" ]; then
        tee "${output}" | perform_diff
      else
        cat - >"${output}"
      fi
    [ -s "${output}" ] || rm -f "${output}"
  else
    TMP=`mktemp -d`
    trap "rm -rf ${TMP} ; exit 1" 1 2 3 15
    if [ X"${output}" = X"-" ]; then
      mkfifo ${TMP}/99
      echo "------------------------> cut dump < ------------------------"
      sort -u |
      tee ${TMP}/99 |
        (
          cat ${TMP}/99 &
          sed -n 's@/sys/devices/platform/\(.*\)/driver$@\1@p' >${TMP}/probed
          wait
          rm ${TMP}/99
        )
    else
      sort -u
    fi
    if [ -s "${TMP}/probed" ]; then
      echo "-----------------------> cut probed <------------------------"
      cat ${TMP}/probed
    fi
    rm -rf ${TMP}
    trap "exit 1" 1 2 3 15
  fi
if [ -z "${filter}" -a X"${output}" = X"-" ]; then
  echo "------------------------> cut dmesg <------------------------"
  adb_su dmesg </dev/null 2>/dev/null
  echo "-----------------------> cut .config <-----------------------"
  adb_su cat /proc/config.gz </dev/null 2>/dev/null | gunzip
fi
