#!/bin/sh

if [ -r /etc/rc.d/rc.common ]; then
  source /etc/rc.d/rc.common
  navico_env_init
  stdio_syslog_start
else
  exec 2>/tmp/navico-coredump.log >&2
fi

adj_limits() {
  # Attempt to raise the file-descriptor limit:
  # - First to twice the hard limit
  # - Failing that, to the hard limit (typically 4096)
  local h_fd=$(ulimit -H -n || echo 4096)

  if ! ulimit -n $((2*h_fd)) && ! ulimit -n $h_fd; then
    echo "ulimit -n failed" >&2
  fi
}

adj_limits

GetConfiguration()
{
    TRY_TO_SAVE_ON_EXTERNAL_MEDIA=1
    TRY_TO_SAVE_ON_INTERNAL_PARTITION=0
    MINIMAL_CRASH_LOG=0

    EXTERNAL_MEDIA_IGNORE=
    INTERNAL_STORAGE_LIST=
    # 20 MB
    MINIMUM_SPACE=20000
    FILE_GRAB_LIST=
    USERFEEDBACK=
    TEMPDIR=/tmp/navico-coredump-${coredump_pid}
    SEQUENCE_FILE=/home/nos/sequence.ini
    MINIMUM_THRESHOLD=100000
    NAVICO_COREDUMP_FLAG=/tmp/navico_coredump_active

    if [ -e /etc/navico-coredump.conf ]; then
        . /etc/navico-coredump.conf
    fi

    NAVICO_COREDUMP_FLAG="${NAVICO_COREDUMP_FLAG}-${coredump_pid}"
}

GetCommandLineArguments()
{
  while [ $# -gt 0 ]; do
    case $1 in
      --exec=*)
      coredump_thread_name=${1#--exec=}
      coredump_thread_name=${coredump_thread_name//:/_}
      ;;
      --pid=*)
      coredump_pid=${1#--pid=}
      ;;
      --signal=*)
      coredump_signal=${1#--signal=}
      ;;
      --time=*)
      coredump_time=${1#--time=}
      ;;
    esac
    shift
  done
}

DumpSysLog()
{
  { logread || cat /var/log/messages || cat /tmp/messages; } 2>/dev/null | tail -c 32768 > ${TEMPDIR}/messages
}

DumpDmesg()
{
    /bin/dmesg > $TEMPDIR/dmesg.log
}

DumpBootVer()
{
  strings -n7 </dev/mtd0ro | sed -ne 's/.*\(U-Boot [^)]*)\).*/\1/p' | sort | uniq > $TEMPDIR/uboot
}

create_readme() {
  # If remote logging is enabled, include the real time the log was created
  if [ -e /run/sysconfig/log_remote ]; then
    local prev_date=$(date +'%s')
    local time_server

    if [ -e /run/sysconfig/static_ip ]; then
      time_server=10.255.255.254
    else
      time_server=169.254.254.254
    fi

    if rdate -s $time_server; then
      export CRASHLOG_TIMESTAMP=$(date +'%s')
      date -s @$prev_date  # Restore previous date

      {
	printf "Crashlog created %s\n\n" "$(date --date=@$CRASHLOG_TIMESTAMP)"
	printf "More information is available at:\n"
	printf "http://mfd-gw-aklnz/crashlog?mac=%s;date=%s\n\n" "$(tr -d ':' </sys/class/net/eth0/address)" $CRASHLOG_TIMESTAMP
      } | tee -a "$TEMPDIR/README"

      if [ -x /usr/sbin/debugservices ]; then
	/usr/sbin/debugservices fax-signed crashlog start && CRASHLOG_REPORTING=true
      fi
    fi
  fi
}

SaveProcfsDir() {
  local dest=${1:-.}

  while read; do
    local file=$REPLY f_base=${REPLY##*/}

    if [ -L "$file" ]; then
      # Symlinks - just store what they're pointing to
      readlink "$file" > "$dest/$f_base"

    elif [ -r "$file" ]; then
      # Readable files (some are write-only)
      case "$f_base" in
        mem|clear_refs|pagemap|kpageflags|kpagecount|kmsg|kallsyms|sahara)
          : # Files to ignore
          ;;
        cmdline|environ)
          strings "$file" > "$dest/$f_base"
          ;;
        *)
          cat "$file" > "$dest/$f_base"
          ;;
      esac
    fi
  done
}

DumpProcfs()
{
    local dest="$TEMPDIR/proc"
    mkdir -p "$dest/net" "$dest/app"

    cp -a /proc/navico_platform "$dest"

    find /proc      -maxdepth 1 -type f | SaveProcfsDir "$dest"
    find /proc/net/ -maxdepth 1 -type f | SaveProcfsDir "$dest/net"

    # Store open file list
    ls -la --color=never /proc/$coredump_pid/fd > "$dest/app/fd"

    # Files and links in /proc/PID/
    find "/proc/$coredump_pid" -maxdepth 1 -type f -o -type l | SaveProcfsDir "$dest/app"
}

DumpSysfs()
{
    local dest="$TEMPDIR/sys"
    mkdir -p ${dest}/kernel/debug
    mount debugfs
    if [ -d /sys/kernel/debug/gc ]; then
        echo "$coredump_pid" > /sys/kernel/debug/gc/vidmem
        find /sys/kernel/debug/gc -type d -exec mkdir -p ${dest}/kernel/debug/gc/{} +
        find /sys/kernel/debug/gc ! -name galcore_trace -type f -exec cp {} ${dest}/kernel/debug/gc/{} \;
    fi
    umount /sys/kernel/debug
}

SpaceAvailable()
{
    space_available=$(df -kP $1 | awk 'NR==2 {print $4}')
    echo $space_available
    if [ $space_available -gt $MINIMUM_SPACE ]; then
        return 1
    fi
    return 2
}

core_signal_txt() {
    local txt=${coredump_signal:-}

    case $txt in
        3)  txt="QUIT" ;;
        4)  txt="ILL"  ;;
        6)  txt="ABRT" ;;
        8)  txt="FPE"  ;;
        11) txt="SEGV" ;;
    esac

    [ -n "$txt" ] && printf 'SIG%s' "$txt"
}

UserFeedback()
{
    if [ -x "${USERFEEDBACK}" -a $# -gt 0 ]; then
        "${USERFEEDBACK}" "$@" ${coredump_exe_name} $(core_signal_txt)
    fi
}

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

GetCommandLineArguments $*
GetConfiguration

if [ "${MINIMAL_CRASH_LOG}" = "0" ]; then
    set -x
fi

coredump_exe_name=$(basename $(readlink /proc/${coredump_pid}/exe))

touch $NAVICO_COREDUMP_FLAG

CORE_DUMP_ROOT_DIRECTORY=

# check sd cards first
if [ "${TRY_TO_SAVE_ON_EXTERNAL_MEDIA}" = "1" ]; then
    # The SD card may be remote NFS-mounted in the case of Hatchetfish CPU B.
    ext_paths="/media/*"
    if [ "$FAMILYID" = "Hatchetfish" -a "$SUBFAMILYID" = "2" ]; then
        ext_paths="$ext_paths /mnt/*"
    fi
    for sdpath in $ext_paths; do
        echo "$sdpath"
        IGNORE_PATH=0
        for IGNORE_MEDIA in ${EXTERNAL_MEDIA_IGNORE}; do
            if [ "$sdpath" == $IGNORE_MEDIA ]; then
                IGNORE_PATH=1
                break
            fi
        done

        if [ "${IGNORE_PATH}" = "0" ]; then
            IGNORE_PATH=0
            if [ -d "$sdpath" ]; then
                if grep "shimfs /mnt" /proc/mounts; then
                    sdpath_mount="$(echo $sdpath | sed 's/mnt/nfs/')"
                else
                    sdpath_mount="$sdpath"
                fi
                rwflag=$(cat /proc/mounts | grep " $sdpath_mount " | cut -d " " -f 4 | cut -d "," -f 1)
                echo "$rwflag"
                if [ "$rwflag" = "rw" ]; then
                    SpaceAvailable "$sdpath_mount"
                    if [ "$?" = "1" ]; then
                        CORE_DUMP_ROOT_DIRECTORY="$sdpath"
                        break;
                    fi
                fi
            fi
        fi
    done
fi

if [ -z "${CORE_DUMP_ROOT_DIRECTORY}" ]; then
    if [ "${TRY_TO_SAVE_ON_INTERNAL_PARTITION}" = "1" ] || [ "${MINIMAL_CRASH_LOG}" = "1" ]; then
        for dir in ${INTERNAL_STORAGE_LIST}; do
            SpaceAvailable "$dir"
            if [ $? = "1" ]; then
                CORE_DUMP_ROOT_DIRECTORY=${dir}
                break
            fi
        done
    fi
fi

# Does the requested directory actually exist? If not then bail
if [ -z "${CORE_DUMP_ROOT_DIRECTORY}" ]; then
    rm $NAVICO_COREDUMP_FLAG
    UserFeedback error "Insufficient free space."
    exit
fi

UserFeedback start

CORE_DUMP_DIRECTORY=${CORE_DUMP_ROOT_DIRECTORY}/crashlog
mkdir -p ${CORE_DUMP_DIRECTORY}
chmod a+rwx ${CORE_DUMP_DIRECTORY}
chown $(stat -c %u:%g ${CORE_DUMP_ROOT_DIRECTORY}) ${CORE_DUMP_DIRECTORY}

fs_free_kib() {
  # Return free KiB (available to non-superuser) on specified file system
  local fs=${1:-'.'}
  local blkfr_blksz

  if blkfr_blksz=$(stat -c '%a:%S' -f "$fs"); then
    local blkfr=${blkfr_blksz%:*} blksz=${blkfr_blksz#*:}
    echo $(( blkfr * (blksz / 1024) ))
  else
    echo 0
    return 1
  fi
}

get_crashlog_seq_ctr() {
  # Return the next available crashlog sequence counter and remove
  # the oldest logs until MINIMUM_THRESHOLD free KiB is available.
  find "${CORE_DUMP_DIRECTORY}/" -type f -maxdepth 1 |
  sed -ne 's:^.*/_\([0-9]\+_.*\(core\|extra\).*$\):\1:p' |
  sort -n |
  {
    local seq_ctr=0 free_kib=""

    while read; do
      seq_ctr=${REPLY%%_*}

      if [ -z "$free_kib" ] &&
        free_kib=$(fs_free_kib "$CORE_DUMP_DIRECTORY") &&
        [ $free_kib -lt $((MINIMUM_THRESHOLD)) ]
      then
        local file="${CORE_DUMP_DIRECTORY}/_${REPLY}"
        printf "%dk free, rm %s\n" $free_kib "$file"
        rm "$file"
        free_kib=""
      fi
    done >&2

    echo $((seq_ctr+1))
  }
}

coredump_seq=$(get_crashlog_seq_ctr)

EXTRAS_FILE=$CORE_DUMP_DIRECTORY/_${coredump_seq}_${coredump_exe_name}_${coredump_thread_name}_${coredump_time}_${coredump_signal}_extra.tar.gz
CORE_FILE=$CORE_DUMP_DIRECTORY/_${coredump_seq}_${coredump_exe_name}_${coredump_thread_name}_${coredump_time}_${coredump_signal}_core
if [ "${MINIMAL_CRASH_LOG}" = "0" ]; then
   CORE_FILE="${CORE_FILE}.gz"
else
   CORE_FILE="${CORE_FILE}.txt"
fi

rm -fR ${TEMPDIR}
mkdir ${TEMPDIR}

# Disable hung-task checking
if [ -w /proc/sys/kernel/hung_task_timeout_secs ]; then
    echo 0 >/proc/sys/kernel/hung_task_timeout_secs
fi

# Collect data before app is closed, in case we die during real core dump
create_readme
DumpBootVer
if [ "${MINIMAL_CRASH_LOG}" = "0" ]; then
    DumpProcfs
    DumpSysfs
fi
# Save the data that cant (or wont) be re-captured after application has exited
cp -al ${TEMPDIR} ${TEMPDIR%%/}.saved
DumpSysLog
DumpDmesg

# Agregate all the files into a single tar file
tar -C ${TEMPDIR} -czf ${EXTRAS_FILE} . ${FILE_GRAB_LIST}
chown $(stat -c %U:%G ${INTERNAL_STORAGE_LIST}) ${EXTRAS_FILE}
chmod a+rw ${EXTRAS_FILE}
sync

# Restore non-changing (or now unavailable) data captured while app was still running
rm -fr ${TEMPDIR}
mv ${TEMPDIR%%/}.saved ${TEMPDIR}

if [ "${MINIMAL_CRASH_LOG}" = "0" ]; then
   # zip core dump
   gzip -f > ${CORE_FILE}
else
   echo "Sequence=${coredump_seq}
ExecName=${coredump_exe_name}
ThreadName=${coredump_thread_name}
Timestamp=${coredump_time}
Signal=${coredump_signal}" > "${CORE_FILE}"
fi
sync

# Wait a bit, so any dmesg info from app exiting ends up in logs
sleep 1

# Re-collect any data that may have changed after application has exited.
DumpSysLog
DumpDmesg

# Agregate all the files into a single tar file
tar -C ${TEMPDIR} -czf ${EXTRAS_FILE}.new . ${FILE_GRAB_LIST}
rm -fr ${TEMPDIR}
sync
mv ${EXTRAS_FILE}.new ${EXTRAS_FILE}

# Especially for the case where this ends up in the userdata files, make it possible for
# someone to delete the crash logs through NOSApp gui
chown $(stat -c %u:%g "${CORE_DUMP_ROOT_DIRECTORY}") "${CORE_FILE}"
chmod a+rw ${CORE_FILE}
chown $(stat -c %u:%g "${CORE_DUMP_ROOT_DIRECTORY}") "${EXTRAS_FILE}"
chmod a+rw ${EXTRAS_FILE}
sync

if [ "${CRASHLOG_REPORTING:-}" = "true" ]; then
  export CORE_FILE EXTRAS_FILE
  /usr/sbin/debugservices fax-signed crashlog exit
fi

rm $NAVICO_COREDUMP_FLAG

UserFeedback stop
