#!/bin/bash
# ==========================================================================
# Copyright (c) Fabasoft R&D GmbH, A-4020 Linz, 1988-2025.
#
# Alle Rechte vorbehalten. Alle verwendeten Hard- und Softwarenamen sind
# Handelsnamen und/oder Marken der jeweiligen Hersteller.
#
# Der Nutzer des Computerprogramms anerkennt, dass der oben stehende
# Copyright-Vermerk im Sinn des Welturheberrechtsabkommens an der vom
# Urheber festgelegten Stelle in der Funktion des Computerprogramms
# angebracht bleibt, um den Vorbehalt des Urheberrechtes genuegend zum
# Ausdruck zu bringen. Dieser Urheberrechtsvermerk darf weder vom Kunden,
# Nutzer und/oder von Dritten entfernt, veraendert oder disloziert werden.
# --------------------------------------------------------------------------
# Fabasoft Core Dump Analysis Tool
# ==========================================================================

declare -r SCRIPTNAME=$(basename "${BASH_SOURCE[0]}")
declare -r COPYRIGHT="Copyright (c) Fabasoft"
declare -r VERSION=

declare -r DEBUGROOT="/usr/lib/debug"
if [ -z "${SYMBOLPATH}" ]; then
  SYMBOLPATH="/mnt/symbols"
  if [ ! -d "${SYMBOLPATH}" ]; then
    SYMBOLPATH=
  fi
fi
if [ -z "${SYMBOLCACHE}" ]; then
  SYMBOLCACHE="/tmp/symbols"
fi
if [ -z "${SYMBOLS_URL}" ]; then
  if [[ -n "${ARTIFACTORY_HOST}" && -z "${ARTIFACTORY_URL}" ]]; then
    ARTIFACTORY_URL="https://${ARTIFACTORY_HOST}/artifactory"
  fi
  if [[ -n "${ARTIFACTORY_URL}" ]]; then
    SYMBOLS_URL="${ARTIFACTORY_URL}/fabasoft-symbols"
  fi
fi
if [ -z "${ELF_PHNUM}" ]; then
  ELF_PHNUM=32768
fi

declare -r E_SUCCESS=0
declare -r E_FAILURE=1
declare -r E_WARNING=64
declare -r E_SKIPPED=65

declare -ar RPMS_REQ=(binutils elfutils file findutils gawk tar which)
declare -ar RPMS_DBG=(bzip2 gdb)

# --------------------------------------------------------------------------

OLDIFS=${IFS}

function resetifs()
{
  IFS=${OLDIFS}
}

# --------------------------------------------------------------------------

function architecture()
{
  case $(file -b "$1") in
  "ELF 32-bit"* )
    echo "x86"
    return ${E_SUCCESS}
    ;;
  "ELF 64-bit"* )
    echo "x64"
    return ${E_SUCCESS}
    ;;
  * )
    echo "unknown"
    return ${E_FAILURE}
    ;;
  esac
}

function bindirname()
{
  case $(file -b "$1") in
  "ELF 32-bit"* | "ELF 64-bit"* )
    if dirname "${file}"; then
      return ${E_SUCCESS}
    else
      return ${E_FAILURE}
    fi
    ;;
  esac
  return ${E_SKIPPED}
}

function buildid()
{
  eu-readelf -n "$1" | fgrep 'Build ID:' | awk -F': ' '{print $2}'
}

function debugbinary()
{
  file -b "$1" | fgrep -q 'not stripped'
}

function exists
{
  [ -e "$1" ] && echo "$1"
}

function resolvelink
{
  if [[ -L $1 && -n $(readlink -e "$1") ]]; then
    local link=$1
    while [[ -L ${link} ]]; do
      local target=$(readlink "${link}")
      if [[ ${target:0:1} = "/" ]]; then
        link=$(realpath -s "${target}")
      else
        link=$(realpath -s "$(dirname "${link}")/${target}")
      fi
      echo "${link}"
    done
  fi
}

function resolvefiles
{
  while read file; do
    exists "${file}"
    resolvelink "${file}"
  done
}

function extension
{
  local fullname=$1
  local filename=${fullname##*/}
  local extension=${filename##*.}
  if [ "${extension}" != "${filename}" ]; then
    echo "${extension}"
  else
    echo ""
  fi
}

# --------------------------------------------------------------------------

function fscbindirs()
{
  for entry in $(fsclist | grep -v '^/etc'); do
    if [ -d "${entry}" ]; then
      for file in $(find "${entry}" -maxdepth 1 -type f -executable -name "*.so*" -o ! -name "*.*"); do
        bindirname "${file}"
        if [ $? -eq ${E_SUCCESS} ]; then
          break;
        fi
      done
      continue
    fi
    if [ -f "${entry}" ]; then
      bindirname "${entry}"
      continue
    fi
  done | sort -u
}

function fscinstalldir()
{
  local conf="/etc/fabasoft/base.conf"
  (
    if [ -f "${conf}" ]; then
      . "${conf}"
    fi
    if [ -n "${FSC_REGISTRY}" -a -n "${FSC_USER}" ]; then
      local regkey="${FSC_REGISTRY}/users/${FSC_USER}/Software/Fabasoft/Environment/INSTALLDIR/registry.default"
      if [ -f "${regkey}" ]; then
        cat "${regkey}"
      fi
    fi
  )
}

function fsclist()
{
  if [ -n "${ROOTPATH}" ]; then
    find \
      "${ROOTPATH}/opt/fabasoft" \
      "${ROOTPATH}/usr/sbin" \
      "${ROOTPATH}/var/opt/fabasoft" \
      -type d
  else
    for pkg in $(fscpackages); do
      rpm -ql "${pkg}"
    done | sort -u
    fscwebdirs
    local installdir=$(fscinstalldir)
    if [ -n "${installdir}" ]; then
      find "${installdir}" -mindepth 1 -type d
    fi
  fi
}

function fscpackages()
{
  rpm -qa | grep 'fsc-'
}

function fscpath()
{
  if [ -z "${FSCPATH}" ]; then
    FSCPATH=$(fscbindirs | tr '\n' ':')
  fi
  echo "${FSCPATH}"
}

function fscsymbolname()
{
  local result=0
  local tempfile=
  local out=
  local name=
  if [ ${result} -eq 0 ]; then
    tempfile=$(mktemp)
    result=$?
  fi
  if [ ${result} -eq 0 ]; then
    out=$(objcopy -j '.gnu_debuglink' "$1" "${tempfile}" 2>&1)
    result=$?
    if [ ${result} -ne 0 ]; then
      echo "${out}" >&2
    fi
  fi
  if [ ${result} -eq 0 ]; then
    name=$(strings "${tempfile}" | egrep "^fsc-[^-]+-[^-]+-[^\.]+\.debug$")
    result=$?
  fi
  if [ -f "${tempfile}" ]; then
    rm -f "${tempfile}"
  fi
  if [ ${result} -eq 0 ]; then
    echo "${name}"
  fi
  return ${result}
}

function fscwebdirs
{
  local webroot="/opt/fabasoft/share/web"
  local webdir=
  for webdir in "${webroot}/WebService_"*; do
    exists "${webdir}/asp/content/bin"
  done
}

# --------------------------------------------------------------------------

function copysymbol()
{
  local sourcefile=$1
  local targetfile=$2
  local buildid=$3
  if [ ! -f "${sourcefile}" ]; then
    return ${E_SKIPPED}
  fi
  local name=$(basename "${sourcefile}")
  local ext=$(extension "${sourcefile}")
  if [ "${ext}" = "debug" ]; then
    if [ -n "${buildid}" ]; then
      if [ "${name%%.*}" != "${buildid}" ]; then
        if [ "$(buildid "${sourcefile}")" != "${buildid}" ]; then
          return ${E_SKIPPED}
        fi
      fi
    fi
    if [ "${VERBOSE}" = "yes" ]; then
      echo "Linking [${sourcefile}] --> [${targetfile}]"
    fi
    ln -srf "${sourcefile}" "${targetfile}"
    if [ $? -eq 0 ]; then
      return ${E_SUCCESS}
    else
      return ${E_FAILURE}
    fi
  elif [ "${ext}" = "bz2" ]; then
    mkdir -p "${SYMBOLCACHE}" || return ${E_FAILURE}
    local bz2file="${SYMBOLCACHE}/${name}"
    ln -srf "${sourcefile}" "${bz2file}" || return ${E_FAILURE}
    bzip2 -df "${bz2file}" || {
      rm -f "${bz2file}"
      return ${E_FAILURE}
    }
    local cachefile="${SYMBOLCACHE}/$(basename "${bz2file}" ".bz2")"
    if [ -n "${buildid}" ]; then
      if [ "${name%%.*}" != "${buildid}" ]; then
        if [ "$(buildid "${cachefile}")" != "${buildid}" ]; then
          rm -f "${cachefile}"
          return ${E_SKIPPED}
        fi
      fi
    fi
    if [ "${VERBOSE}" = "yes" ]; then
      echo "Linking [${sourcefile}] --> [${targetfile}]"
    fi
    ln -srf "${cachefile}" "${targetfile}"
    if [ $? -eq 0 ]; then
      return ${E_SUCCESS}
    else
      return ${E_FAILURE}
    fi
  fi
  return ${E_SKIPPED}
}

function downloadsymbol()
{
  local buildid=$1
  local targetfile=$2
  local tmpfile="${SYMBOLCACHE}/${buildid}.tmp"
  local bz2file="${SYMBOLCACHE}/${buildid}.debug.bz2"
  local cachefile="${SYMBOLCACHE}/${buildid}.debug"
  /usr/bin/which --skip-alias --skip-functions "curl" &>/dev/null
  if [ $? -ne 0 ]; then
    return ${E_SKIPPED}
  fi
  mkdir -p "${SYMBOLCACHE}" || return ${E_FAILURE}
  local url="${SYMBOLS_URL}/${buildid}/[RELEASE]/${buildid}-[RELEASE]-rhel-x64.debug.bz2"
  if [ "${VERBOSE}" = "yes" ]; then
    echo "GET ${url}"
  fi
  local -a authn
  if [ -n "${ARTIFACTORY_USER}" ]; then
    authn=("--user" "${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD}")
  fi
  local response=
  response=$(
    curl --globoff --silent --show-error \
      "${authn[@]}" \
      --url "${url}" \
      --output "${tmpfile}" \
      --write-out '%{content_type}'
  )
  if [ $? -ne 0 ]; then
    rm -f "${tmpfile}"
    if [ -n "${response}" ]; then
      echo "Error: ${response}" >&2
    fi
    return ${E_FAILURE}
  fi
  if [ "${response:0:16}" = "application/json" ]; then
    response=$(cat "${tmpfile}")
    rm -f "${tmpfile}"
    if grep -q '"status" *: *\<404\>' <<< ${response}; then
      return ${E_SKIPPED}
    else
      return ${E_FAILURE}
    fi
  fi
  mv -f "${tmpfile}" "${bz2file}" || {
    rm -f "${tmpfile}"
    return ${E_FAILURE}
  }
  bzip2 -df "${bz2file}" || {
    rm -f "${bz2file}"
    return ${E_FAILURE}
  }
  if [ "${VERBOSE}" = "yes" ]; then
    echo "Linking [${cachefile}] --> [${targetfile}]"
  fi
  ln -srf "${cachefile}" "${targetfile}"
  if [ $? -eq 0 ]; then
    return ${E_SUCCESS}
  else
    return ${E_FAILURE}
  fi
}

# --------------------------------------------------------------------------

function linksymbolfile()
{
  local binary=$1
  local dir=$(dirname "${binary}")
  local ext=$(extension "${binary}")
  local arch=
  local name=
  local debugsymbol=
  if [ "${ext}" = "debug" ]; then
    return ${E_SKIPPED}
  fi
  arch=$(architecture "${binary}")
  if [ $? -ne 0 ]; then
    if [ "${VERBOSE}" = "yes" ]; then
      echo "Information: [${binary}] is not an ELF binary" >&2
    fi
    return ${E_SKIPPED}
  fi
  if debugbinary "${binary}"; then
    return ${E_SKIPPED}
  fi
  local buildid=$(buildid "${binary}")
  if [ -n "${buildid}" ]; then
    local debugpath="${ROOTPATH}${DEBUGROOT}/.build-id/${buildid:0:2}"
    local debugname="${buildid:2}"
    local debugbinary="${debugpath}/${debugname}"
    debugsymbol="${debugbinary}.debug"
    mkdir -p "${debugpath}" || return ${E_FAILURE}
    if [ ! -e "${debugbinary}" ]; then
      ln -srf "${binary}" "${debugbinary}"
    fi
    if [ -e "${debugsymbol}" ]; then
      return ${E_SKIPPED}
    fi
  fi
  name=$(fscsymbolname "${binary}")
  if [ $? -ne 0 -o -z "${name}" ]; then
    echo "Warning: Could not extract symbol name of binary [${binary}]" >&2
    return ${E_WARNING}
  fi
  local targetfile="${dir}/${name}"
  local fullversion=$(echo "${name}" | sed "s/^fsc-\([^-]*\).*$/\1/")
  local version=$(echo "${fullversion}" | awk -F'.' '{print $1 "." $2 "." $3}')
  local buildnr=$(echo "${fullversion}" | awk -F'.' '{print $4}')
  local versionpath="${DIST}-${arch}/${version}/${buildnr}"
  if [ -f "${targetfile}" ]; then
    if [ "${FORCE}" = "yes" ]; then
      if [ "${VERBOSE}" = "yes" ]; then
        echo "Removing existing symbol [${targetfile}]"
      fi
      rm -f "${targetfile}" || return ${E_FAILURE}
    fi
  fi
  if [ -n "${buildid}" ]; then
    if [ -f "${targetfile}" ]; then
      if [ "$(buildid "${targetfile}")" = "${buildid}" ]; then
        return ${E_SKIPPED}
      fi
      if [ "${VERBOSE}" = "yes" ]; then
        echo "Removing non-matching symbol [${targetfile}]"
      fi
      rm -f "${targetfile}" || return ${E_FAILURE}
    fi
    targetfile=${debugsymbol}
  else
    if [ -f "${targetfile}" ]; then
      return ${E_SKIPPED}
    fi
  fi
  if [ -n "${buildid}" ]; then
    local cachefile="${SYMBOLCACHE}/${buildid}.debug"
  else
    local cachefile="${SYMBOLCACHE}/${name}"
  fi
  if [ -f "${cachefile}" ]; then
    if [ "${VERBOSE}" = "yes" ]; then
      echo "Linking [${cachefile}] --> [${targetfile}]"
    fi
    ln -srf "${cachefile}" "${targetfile}"
    if [ $? -eq 0 ]; then
      return ${E_SUCCESS}
    else
      return ${E_FAILURE}
    fi
  fi
  local inx=0
  local -a sourcelist
  IFS=':' read -ra sourcelist <<< "${SYMBOLPATH}"
  for ((inx=0; inx < ${#sourcelist[@]}; inx++)); do
    local source=${sourcelist[${inx}]}
    local add=0
    while [ ${add} -lt 2 ]; do
      if [ ${add} -eq 1 ]; then
        source="${source}/${versionpath}"
      fi
      local -a sourcefilelist=(
        "${source}/${buildid}.debug"
        "${source}/${buildid}.debug.bz2"
        "${source}/${name}"
        "${source}/${name}.bz2"
      )
      local sourcefile=
      for sourcefile in "${sourcefilelist[@]}"; do
        copysymbol "${sourcefile}" "${targetfile}" "${buildid}"
        case $? in
        ( ${E_SUCCESS} ) return ${E_SUCCESS};;
        ( ${E_SKIPPED} ) continue           ;;
        ( *            ) return ${E_FAILURE};;
        esac
      done
      (( add++ ))
    done
  done
  if [[ -n "${SYMBOLS_URL}" && -n "${buildid}" ]]; then
    downloadsymbol "${buildid}" "${targetfile}"
    case $? in
    ( ${E_SUCCESS} ) return ${E_SUCCESS};;
    ( ${E_FAILURE} ) return ${E_FAILURE};;
    esac
  fi
  echo "Error: Could not find debugging symbols [${name}] for binary [${binary}]" >&2
  return ${E_FAILURE}
}

function cleanupsymbols()
{
  local okcnt=0
  local errcnt=0
  local inx=0
  local fscpath=$(fscpath)
  local -a targetlist
  IFS=':' targetlist=(${fscpath})
  resetifs
  local symbol=
  while read symbol; do
    if [ "${VERBOSE}" = "yes" ]; then
      echo "Removing symbol [${symbol}]"
    fi
    rm -f "${symbolfile}"
    if [ $? -eq 0 ]; then
      (( okcnt++ ))
    else
      (( errcnt++ ))
    fi
  done < <(find "${targetlist[@]}" -type f -name '*.debug' | fgrep -v "${DEBUGROOT}" | sort -u)
  if [ ${errcnt} -eq 0 ]; then
    echo "Removed ${okcnt} symbols"
    return ${E_SUCCESS}
  else
    echo "Removed ${okcnt} symbols (${errcnt} errors)"
    return ${E_FAILURE}
  fi
}

function linksymbols()
{
  local okcnt=0
  local errcnt=0
  local warncnt=0
  local inx=0
  local fscpath=$(fscpath)
  local -a targetlist
  IFS=':' targetlist=(${fscpath})
  resetifs
  local binary=
  while read binary; do
    local result=
    linksymbolfile "${binary}"
    result=$?
    if [ ${result} -eq ${E_SUCCESS} ]; then
      (( okcnt++ ))
    elif [ ${result} -eq ${E_WARNING} ]; then
      (( warncnt++ ))
    elif [ ${result} -ne ${E_SKIPPED} ]; then
      (( errcnt++ ))
    fi
  done < <(find "${targetlist[@]}" \( -type f -a -name '*.so*' \) -o \( -type f -a -executable -a ! -name '*.*' \) | fgrep -v "${DEBUGROOT}" | sort -u)
  if [ ${errcnt} -eq 0 ]; then
    if [ ${warncnt} -eq 0 ]; then
      echo "Linked ${okcnt} symbols"
    else
      echo "Linked ${okcnt} symbols (${warncnt} warnings)"
    fi
    return ${E_SUCCESS}
  else
    if [ ${warncnt} -eq 0 ]; then
      echo "Linked ${okcnt} symbols (${errcnt} errors)"
    else
      echo "Linked ${okcnt} symbols (${errcnt} errors, ${warncnt} warnings)"
    fi
    return ${E_FAILURE}
  fi
}

# --------------------------------------------------------------------------

function findcores()
{
  if [ -n "${ROOTPATH}" ]; then
    find "${ROOTPATH}" -type f -name 'core.*'
  else
    local corepath=$(dirname "$(cat /proc/sys/kernel/core_pattern)")
    if [ -z "${corepath}" -o "${corepath}" = "." ]; then
      corepath="/var/opt/fabasoft/lib/core"
    fi
    find "${corepath}" -type f -name 'core.*'
  fi
}

function pathlookup()
{
  local path=$(fscpath)
  if [ -z "${ROOTPATH}" ]; then
    path="${path}:${PATH}"
  fi
  PATH=${path} /usr/bin/which --skip-alias --skip-functions "$1" 2>/dev/null
}

function executable()
{
  local executable=$(file -b -P elf_phnum=${ELF_PHNUM} "$1" | sed -e 's/,/\n/g' | awk -F"'" '/execfn:/{print $2}')
  if [ -z "${executable}" ]; then
    executable=$(file -b -P elf_phnum=${ELF_PHNUM} "$1" | awk -F"'" '{print $2}' | awk '{print $1}')
  fi
  if [ "${executable:0:1}" = "/" ]; then
    executable="${ROOTPATH}${executable}"
  fi
  if [ -L "${executable}" ]; then
    executable=$(readlink "${executable}")
    if [ $? -eq 0 ]; then
      if [ "${executable:0:1}" = "/" ]; then
        executable="${ROOTPATH}${executable}"
      fi
    fi
  fi
  if [ -f "${executable}" ]; then
    echo "${executable}"
    return ${E_SUCCESS}
  fi
  basename=$(basename "${executable}")
  executable=$(pathlookup "${basename}")
  if [ $? -eq 0 ]; then
    echo "${executable}"
    return ${E_SUCCESS}
  fi
  if [ -d "${ROOTPATH}/var/opt/fabasoft/instances" ]; then
    symlink=$(find "${ROOTPATH}/var/opt/fabasoft/instances" -name "${basename}" -type l | head -n 1)
    if [ -L "${symlink}" ]; then
      executable=$(readlink "${symlink}")
      if [ $? -eq 0 ]; then
        if [ "${executable:0:1}" = "/" ]; then
          executable="${ROOTPATH}${executable}"
        fi
        if [ -f "${executable}" ]; then
          echo "${executable}"
          return ${E_SUCCESS}
        fi
      fi
    fi
  fi
  case "${basename}" in
  "COOManagement" )
    basename="coommd"
    ;;
  "COOATService_"* )
    basename="cooatd"
    ;;
  "COOService_"* )
    basename="coostd"
    ;;
  "MMCService_"* )
    basename="mmcstd"
    ;;
  "WebManagement" )
    basename="wmc"
    ;;
  "WebService_"* )
    if [ -e "${ROOTPATH}/etc/os-release" ]; then
      basename="httpd"
    else
      basename="httpd.async"
    fi
    ;;
  * )
    basename=
    ;;
  esac
  if [ -n "${basename}" ]; then
    executable=$(pathlookup "${basename}")
    if [ $? -eq 0 ]; then
      echo "${executable}"
      return ${E_SUCCESS}
    fi
  fi
  return ${E_FAILURE}
}

function listall()
{
  local executable=$(executable "$1")
  if [ -n "${executable}" ]; then
    local symbol="$(dirname "${executable}")/$(fscsymbolname "${executable}")"
    exists "${executable}"
    exists "${symbol}"
  fi
  while read file; do
    exists "${file}"
  done < <(
    eu-unstrip -n --core "$1" | awk '{print $3"\n"$4}' | grep '^/'
    eu-unstrip -n --core "$1" | awk '{print $2}' |
      sed -r -e "s#^([a-z0-9]{2})([a-z0-9]+)@.*#${DEBUGROOT}/.build-id/\1/\2\n${DEBUGROOT}/.build-id/\1/\2.debug#"
  )
}

function listmodules()
{
  while read file; do
    exists "${file}"
  done < <(
    eu-unstrip -n --core "$1" | awk '{print $3}' | grep '^/'
  )
}

# --------------------------------------------------------------------------

function createarchive()
{
  if [ "${ENVIRONMENT}" = "yes" ]; then
    (
      echo "${ROOTPATH}/etc/os-release"
      echo "${ROOTPATH}/etc/system-release"
      echo "${ROOTPATH}/lib64"
      echo "${ROOTPATH}/usr/sbin/httpd.async"
      echo "${ROOTPATH}/var/opt/fabasoft/instances"
      fscbindirs
      if [ "${LOGS}" = "yes" ]; then
        find "${ROOTPATH}/var/log/messages"* -type f
        find "${ROOTPATH}/var/opt/fabasoft/log" -type f -name '*.log' -o -name '*.trc'
      fi
      if [ "${REGISTRY}" = "yes" ]; then
        echo "${ROOTPATH}/etc/fabasoft/settings"
        echo "${ROOTPATH}/opt/fabasoft/Registry"
      fi
      if [ -n "${COREFILE}" ]; then
        echo "${COREFILE}"
        listall "${COREFILE}"
      fi
    ) | resolvefiles | sort -u | sed -e 's#^/##' | \
    tar c${COMPRESS}f "${ARCHIVE}" \
      -C "$(dirname "${BASH_SOURCE[0]}")" "$(basename "${BASH_SOURCE[0]}")" \
      -C / -T -
  else
    local -a corelist
    if [ -n "${COREFILE}" ]; then
      corelist[0]=${COREFILE}
    else
      IFS=$'\n'
      corelist=($(findcores))
      resetifs
    fi
    if [ ${#corelist[@]} -eq 0 ]; then
      echo "No cores found. You may use option '--core' to specify an explicit location."
    fi
    if [ -n "${SYMBOLPATH}" ]; then
      IFS=$'\n'
      local -a filelist=($(
        for ((inx = 0; inx < ${#corelist[@]}; inx++)); do
          local core=${corelist[${inx}]}
          executable "${core}"
          listmodules "${core}"
        done | sort -u
      ))
      resetifs
      for ((inx = 0; inx < ${#filelist[@]}; inx++)); do
        linksymbolfile "${filelist[${inx}]}"
      done
    fi
    (
      echo "${ROOTPATH}/etc/os-release"
      echo "${ROOTPATH}/etc/system-release"
      if [ "${LOGS}" = "yes" ]; then
        find "${ROOTPATH}/var/log/messages"* -type f
        find "${ROOTPATH}/var/opt/fabasoft/instances" -type f -name 'current'
        find "${ROOTPATH}/var/opt/fabasoft/log" -type f -name '*.log' -o -name '*.trc'
      else
        echo "${ROOTPATH}/var/log/messages"
      fi
      if [ "${REGISTRY}" = "yes" ]; then
        echo "${ROOTPATH}/etc/fabasoft/settings"
        echo "${ROOTPATH}/opt/fabasoft/Registry"
      fi
      for ((inx = 0; inx < ${#corelist[@]}; inx++)); do
        local core=${corelist[${inx}]}
        echo "${core}"
        listall "${core}"
      done
    ) | resolvefiles | sort -u | sed -e 's#^/##' | \
    tar c${COMPRESS}f "${ARCHIVE}" \
      -C "$(dirname "${BASH_SOURCE[0]}")" "$(basename "${BASH_SOURCE[0]}")" \
      -C / -T -
  fi
}

# --------------------------------------------------------------------------

function debugger()
{
  /usr/bin/which --skip-alias --skip-functions "gdb" &>/dev/null
  if [ $? -ne 0 ]; then
    echo "Error: Could not find gdb" >&2
    return ${E_FAILURE}
  fi
  local -a corelist
  if [ -n "${COREFILE}" ]; then
    corelist[0]=${COREFILE}
  else
    IFS=$'\n'
    corelist=($(findcores))
    resetifs
  fi
  if [ ${#corelist[@]} -eq 0 ]; then
    echo "No cores found. You may use option '--core' to specify an explicit location."
    return ${E_FAILURE}
  fi
  local corefile=
  if [ ${#corelist[@]} -gt 1 ]; then
    while [ -z "${corefile}" ]; do
      echo ""
      echo "Please select a core:"
      local inx=
      local cnt=${#corelist[@]}
      for ((inx = 0; inx < cnt; inx++)); do
        local core=${corelist[${inx}]}
        local executable=$(executable "${core}")
        echo "[${inx}] ${core:${#ROOTPATH}} (${executable:${#ROOTPATH}})"
      done
      echo "[a] Abort"
      echo ""
      read -p "> " -s -n ${#cnt} inx
      case "${inx}" in
      [0-9]* )
        corefile=${corelist[${inx}]}
        echo "${corefile}"
        ;;
      "a" )
        echo "Aborted"
        return ${E_SKIPPED}
        ;;
      * )
        echo "Unknown option"
        ;;
      esac
    done
  else
    corefile=${corelist[0]}
  fi
  local executable=$(executable "${corefile}")
  if [ -z "${executable}" ]; then
    echo "Error: Could not determine executable for core [${corefile}]" >&2
    return ${E_FAILURE}
  fi
  local diropts=()
  if [ ${#SOURCEPATHS[@]} -gt 0 ]; then
    while read file; do
      diropts+=("-d" "${file}")
    done < <(find "${SOURCEPATHS[@]}" -type d -a \( -name '.*' -prune -o -print \))
  fi
  gdb \
    -ex "set print thread-events off" \
    -ex "set sysroot ${ROOTPATH}" \
    -ex "set debug-file-directory ${ROOTPATH}${DEBUGROOT}" \
    -ex "show sysroot" \
    -ex "show debug-file-directory" \
    -ex "file ${executable}" \
    -ex "core ${corefile}" \
    "${diropts[@]}"
}

# --------------------------------------------------------------------------

function checkprerequs()
{
  local -a missing
  for pkg in "$@"; do
    rpm --quiet -q "${pkg}"
    if [ $? -ne 0 ]; then
      missing[${#missing[*]}]=${pkg}
    fi
  done
  if [ ${#missing[@]} -gt 0 ]; then
    echo "${SCRIPTNAME}: Error: The following prerequisites are missing:" >&2
    for pkg in ${missing[@]}; do
      echo "  ${pkg}" >&2
    done
    exit 1
  fi
}

function install()
{
  dnf install -y "${RPMS_REQ[@]}" "${RPMS_DBG[@]}"
  if [ -n "${COREFILE}" ]; then
    local executable=$(executable "${COREFILE}")
    if [ -n "${executable}" ]; then
      debuginfo-install -y "${executable}"
    fi
  fi
}

# --------------------------------------------------------------------------

function usage()
{
  cat << EOF >&2
Fabasoft Core Dump Analysis Tool ${VERSION}
${COPYRIGHT}

Usage: ${SCRIPTNAME} [...]

  -a, --archive             Create an archive
  -f, --force               Overwrite existing symbols
  -g, --gdb                 Start GNU Debugger
  -i, --install             Install required packages
  -l, --linksymbols         Link debugging symbols
  -p, --sourcepath <path>   Source path
  -r, --root <path>         Use specified path as root
  -s, --symbolpath <path>   Symbol path
  -v, --verbose             Verbose mode
  -V, --version             Version info

  --buildid <binary>        Determine build ID
  --cleanup                 Remove existing symbols
  --compress <mode>         Compress archive using
    bz2|bzip2                 Burrows-Wheeler
    gz|gzip                   Deflate (default)
    no|none                   Disable compression
    xz                        LZMA2
  --core <file>             Core dump file
  --env[ironment]           Archive environment
  --executable [core]       Determine executable name
  --logs, --logfiles        Include log files
  --reg[istry]              Include registry
  --symbol[name] <binary>   Determine symbol name

EOF
}

# --------------------------------------------------------------------------

if [ -e "/etc/redhat-release" ]; then
  DIST="rhel"
else
  echo "Error: Unsupported platform" >&2
  exit 1
fi

ACTION=
ARCHIVE=
BINARY=
COMPRESS="z"
COREFILE=
ENVIRONMENT="no"
FORCE="no"
FUNCTION=
LOGS="no"
REGISTRY="no"
ROOTPATH=
SOURCEPATHS=()
VERBOSE="no"
while [ -n "$1" ]; do
  case "$1" in
  "-a" | "--archive" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="archive"
    if [ -n "$2" ]; then
      ARCHIVE=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  "-f" | "--force" )
    FORCE="yes"
    ;;
  "-g" | "--gdb" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="gdb"
    ;;
  "-h" | "--help" )
    usage
    exit 0
    ;;
  "-i" | "--install" )
    ACTION="install"
    ;;
  "-l" | "--linksymbols" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="linksymbols"
    ;;
  "-p" | "--source" | "--sourcepath" )
    if [ -z "$2" ]; then
      usage
      exit 1
    fi
    if [ -d "$2" ]; then
      SOURCEPATHS+=("$2")
      shift
    else
      echo "Error: Invalid source directory [$2]" >&2
      exit 1
    fi
    ;;
  "-r" | "--root" )
    if [ -n "$2" ]; then
      ROOTPATH=$(readlink -f "$2")
      shift
    else
      usage
      exit 1
    fi
    ;;
  "-s" | "--symbolpath" )
    if [ -n "$2" ]; then
      SYMBOLPATH=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  "-v" | "--verbose" )
    VERBOSE="yes"
    ;;
  "-V" | "--version" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="version"
    ;;
  "--buildid" | "--build-id" | "--id" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="buildid"
    if [ -n "$2" ]; then
      BINARY=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  "--cleanup" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="cleanup"
    ;;
  "--compress" )
    case "$2" in
    "bz2" | "bzip2" )
      COMPRESS="j"
      shift
      ;;
    "gz" | "gzip" )
      COMPRESS="z"
      shift
      ;;
    "no" | "none" )
      COMPRESS=""
      shift
      ;;
    "xz" )
      COMPRESS="J"
      shift
      ;;
    * )
      usage
      exit 1
      ;;
    esac
    ;;
  "--core" )
    if [ -n "$2" ]; then
      COREFILE=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  "--env" | "--environment" )
    ENVIRONMENT="yes"
    ;;
  "--executable" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="executable"
    if [ -n "$2" ]; then
      if [ "${2:0:1}" != "-" ]; then
        COREFILE=$2
        shift
      fi
    fi
    ;;
  "--function" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="function"
    if [ -n "$2" ]; then
      FUNCTION=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  "--logs" | "--logfiles" )
    LOGS="yes"
    ;;
  "--reg" | "--registry" )
    REGISTRY="yes"
    ;;
  "--symbol" | "--symbolname" )
    if [ -n "${ACTION}" ]; then
      usage
      exit 1
    fi
    ACTION="symbolname"
    if [ -n "$2" ]; then
      BINARY=$2
      shift
    else
      usage
      exit 1
    fi
    ;;
  * )
    echo "Error: Invalid argument [$1]" >&2
    exit 1
    ;;
  esac
  shift
done

if [ "${ACTION}" != "install" ]; then
  checkprerequs "${RPMS_REQ[@]}"
fi

case "${ACTION}" in
"archive" )
  createarchive
  ;;
"buildid" )
  buildid "${BINARY}"
  ;;
"cleanup" )
  cleanupsymbols
  ;;
"executable" )
  if [ -z "${COREFILE}" ]; then
    usage
    exit 1
  fi
  executable "${COREFILE}"
  ;;
"function" )
  eval "${FUNCTION}"
  ;;
"install" )
  install
  ;;
"gdb" )
  checkprerequs gdb
  debugger
  ;;
"linksymbols" )
  checkprerequs bzip2
  linksymbols
  ;;
"symbolname" )
  fscsymbolname "${BINARY}"
  ;;
"version" )
  echo "${VERSION}"
  ;;
* )
  usage
  ;;
esac

# --------------------------------------------------------------------------
# EOF
# --------------------------------------------------------------------------
