#! /usr/bin/env bash
#
# crash-diag [-c] <dir>
#
# -c: if this flag is present, then this script was run from post-terminate
#     because Zeek crashed.
# <dir> is the node's working directory.

. `dirname $0`/zeekctl-config.sh

postterminate=0
if [ "$1" = "-c" ]; then
    postterminate=1
    shift
fi

if [ $# -ne 1 ]; then
    echo "crash-diag: wrong number of arguments"
    exit 1
fi

if [ ! -d "$1" ]; then
    echo "No work dir found"
    exit 0
fi

cd "$1"
if [ $? -ne 0 ]; then
    exit 1
fi

# Identify all core files in the current directory.  We assume these
# filenames contain the word "core" and do not end in ".log".
core=`ls -t *core* 2> /dev/null | grep -v '\.log$'`

# Choose which debugger to use.
gdb_name="gdb"
if [ "${os}" = "Darwin" ]; then
    gdb_name="lldb"
elif [ "${os}" = "OpenBSD" ]; then
    # The default gdb fails when trying to read Zeek core files.
    gdb_name="egdb"
fi
gdb_path=`which $gdb_name 2> /dev/null`

if [ -f "${zeek}" ];then
    zeek_version=`"${zeek}" -v 2>/dev/null | awk '{print $3}'`
else
    zeek_version="(file not found: ${zeek})"
fi

# If Zeek crashed and if a core file exists, then copy the Zeek binary so that
# the user has the ability to generate a backtrace in the future.
if [ $postterminate -eq 1 ]; then
    if [ -n "$core" ]; then
        myzeek=${zeek}
        if [ "${havenfs}" = "1" ]; then
            myzeek=${tmpexecdir}/`basename "${zeek}"`
        fi
        cp "$myzeek" .
    fi
fi

# Output the crash report.
echo

# If no backtrace can be generated, then output an explanation.  However,
# skip this if there's no .status file, because in that case Zeek didn't run.
if [ -f .status ]; then
    if [ -n "$gdb_path" ]; then
        if [ -z "$core" ]; then
            # Check if the system allows setting core file size to unlimited,
            # because this is what the run-zeek script does when it starts Zeek.
            ulimit -c unlimited 2> /dev/null
            coresize=`ulimit -c`

            if [ "$coresize" != "unlimited" ]; then
                echo "No core file found.  You may need to change your system settings to"
                echo "allow core files."
            else
                echo "No core file found."
            fi

            echo
        fi
    else
        if [ -n "$core" ]; then
            echo "Unable to output a backtrace because $gdb_name is not installed."
            echo "It is recommended to install $gdb_name so that ZeekControl can output a"
            echo "backtrace the next time Zeek crashes."
        else
            echo "No core file found and $gdb_name is not installed.  It is recommended to"
            echo "install $gdb_name so that ZeekControl can output a backtrace if Zeek crashes."
        fi
        echo
    fi
fi

echo Zeek $zeek_version
uname -sr
echo

zeekplugins=`"${zeek}" -N 2>/dev/null | grep -v "(built-in)"`
if [ -z "$zeekplugins" ]; then
    echo "Zeek plugins: (none found)"
else
    echo "Zeek plugins:"
    echo "$zeekplugins"
fi

# Output a backtrace if we have a debugger and a core file.
if [ -n "$gdb_path" ]; then
    if [ -n "$core" ]; then
        if [ "$gdb_name" = "gdb" ] || [ "$gdb_name" = "egdb" ]; then
            echo "thread apply all bt" >.gdb_cmds
        elif [ "$gdb_name" = "lldb" ]; then
            echo "bt all" >.gdb_cmds
        fi
        for c in "$core"; do
            if [ -f "$c" ]; then
                echo
                # Note: zeekctl looks for this string in order to determine
                # if a backtrace was output.
                echo "Core file: $c"
                if [ "$gdb_name" = "gdb" ] || [ "$gdb_name" = "egdb" ]; then
                    $gdb_path --batch -x .gdb_cmds "${zeek}" "$c" 2>/dev/null
                elif [ "$gdb_name" = "lldb" ]; then
                    $gdb_path --batch -s .gdb_cmds -f "${zeek}" -c "$c" 2>/dev/null
                fi
            fi
        done
        rm -f .gdb_cmds
    fi
fi

# Usage:
#   show_log <filename> <num>
# Output the last <num> lines of <filename>.  If <num> is -1, then output the
# entire file.
show_log() {
    filename=$1
    num=$2

    echo

    if [ -f $filename ]; then
       echo ==== $filename
       if [ $num -ge 0 ]; then
           tail -$num $filename
       else
           cat $filename
       fi
    else
       echo ==== No $filename
    fi
}

show_log reporter.log 10
show_log stderr.log 10
show_log stdout.log 10
show_log .cmdline 30
show_log .env_vars 30
show_log .status 10
show_log prof.log 10
show_log packet_filter.log 30
show_log loaded_scripts.log -1
