#!/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:-}

    # List all the signals that might cause a coredump according to signal(7)
    case $txt in
        3)  txt="QUIT" ;;
        4)  txt="ILL"  ;;
        5)  txt="TRAP" ;;
        6)  txt="ABRT" ;;
        7)  txt="BUS"  ;;
        8)  txt="FPE"  ;;
        11) txt="SEGV" ;;
        24) txt="XCPU" ;;
        25) txt="XFSZ" ;;
        31) txt="SYS"  ;;
    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
    # or Komodo Smart Display connected to Komodo Hub.
    ext_paths="/media/*"
    if [ "$FAMILYID" = "Hatchetfish" -a "$SUBFAMILYID" = "2" -o "$FAMILYID" = "Komodo" ]; 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 1
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

ERROR_CODE=
CORE_INFO=/tmp/core-info.txt

# Agregate all the files into a single tar file
# Use -h to follow symlinks (e.g. /home/nos/paths.ini)
tar -C "$TEMPDIR" -czhf "$EXTRAS_FILE" . $FILE_GRAB_LIST
# Ignore return value 1 which means some files were changed while being archived
if [ $? -ne 0 -a $? -ne 1 ]; then
    ERROR_CODE="1"
    rm -f "$EXTRAS_FILE"
fi
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
    if ! gzip -f > "$CORE_FILE"; then
        ERROR_CODE="2"
        rm -f "$CORE_FILE"
    fi
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

# Log error code into last extra file
if [ -n "$ERROR_CODE" ]; then
    echo "error_code=$ERROR_CODE Insufficient free space" > "$CORE_INFO"
    FILE_GRAB_LIST="$FILE_GRAB_LIST $CORE_INFO"
fi

# Agregate all the files into a single tar file
tar -C "$TEMPDIR" -czhf "${EXTRAS_FILE}.new" . $FILE_GRAB_LIST
if [ $? -ne 0 -a $? -ne 1 ]; then
    ERROR_CODE="3"
    rm -f "${EXTRAS_FILE}.new"
else
    mv -f "${EXTRAS_FILE}.new" "$EXTRAS_FILE"
fi
rm -fr "$TEMPDIR"
rm -f "$CORE_INFO"
sync

# 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
if [ -e "$CORE_FILE" ]; then
    chown $(stat -c %u:%g "$CORE_DUMP_ROOT_DIRECTORY") "$CORE_FILE"
    chmod a+rw "$CORE_FILE"
fi
if [ -e "$EXTRAS_FILE" ]; then
    chown $(stat -c %u:%g "$CORE_DUMP_ROOT_DIRECTORY") "$EXTRAS_FILE"
    chmod a+rw "$EXTRAS_FILE"
fi
sync

if [ -n "$ERROR_CODE" ]; then
    # Indicate the usage according to the file existence
    ERROR_STR="Insufficient free space."
    if [ ! -e "$CORE_FILE" ]; then
        if [ ! -e "$EXTRAS_FILE" ]; then
            ERROR_STR="$ERROR_STR No core/extras files created."
        else
            ERROR_STR="$ERROR_STR Use extras file only."
        fi
    else
        if [ ! -e "$EXTRAS_FILE" ]; then
            ERROR_STR="$ERROR_STR Use core file only."
        fi
    fi

    UserFeedback error "$ERROR_STR"
    rm "$NAVICO_COREDUMP_FLAG"
    exit 2
fi

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

rm "$NAVICO_COREDUMP_FLAG"

UserFeedback stop
