#! /bin/bash . /usr/src/k_framework/main.sh k_config_details # ---- SCRIPT VARS ----- VERSION="1.0.8" SCRIPT_NAME="Core Dump Report" # ---- SETTINGS ---- TEST_MODE=0 MAX_DUMPS_TO_REPORT=3 # how many core dumps to report per process MAX_DUMP_LINES_TO_REPORT=100 # how many lines of gdb output to report CORE_DUMP_DIR_ARRAY=(/ /tmp /root /var/lib/asterisk) # directories where to search for core dumps CORE_DUMP_NAME_WILDCARD_ARRAY=(core.) # set core dump names to search, for example =(core. dump. crash.), this will match files like core.2506, dump.1226, etc PROCESSES_TO_REPORT=(radiusd kamailio rtpengine asterisk) # which processes to report SKIP_OLDER_THAN=7 # skip core dumps older than N days # ---- GLOBAL VARS ---- CORE_DUMP_FILE_LIST=/tmp/core_dump_report_file_list.txt CORE_DUMP_FILE_OUTPUT=/tmp/core_dump_report_file_output.txt CORE_DUMP_FILE_OUTPUT_ENCODED=/tmp/core_dump_report_file_output_encoded.txt API_RESPONSE=/tmp/core_dump_report_api_response.txt COUNTER_PATH=/tmp/core_dump_report_counter # ---- FUNCTIONS ---- cleanup() { # Delete tmp files rm -f $CORE_DUMP_FILE_LIST &> /dev/null rm -f $CORE_DUMP_FILE_OUTPUT &> /dev/null rm -f $CORE_DUMP_FILE_OUTPUT_ENCODED &> /dev/null rm -f $API_RESPONSE &> /dev/null rm -f ${COUNTER_PATH}* &> /dev/null } text_blue() { echo -e "\033[0;34m$1\033[0m" } text_green() { echo -e "\033[0;32m$1\033[0m" } report_dump() { local file=$1 local file_basename=$(basename $file) local generated_by=$2 local generated_by_basename="$(basename $generated_by)" local generated_at=$3 local api_command="/usr/bin/curl --output $API_RESPONSE --silent --insecure --connect-timeout 30 -X POST \"https://support.kolmisoft.com/api/crash_report\" --data-urlencode \"core_dump_generated_by=$generated_by_basename\" --data-urlencode \"core_dump_generated_at=$generated_at\" --data-urlencode \"core_dump_filename=${file}.${generated_by_basename}.reported\" --data-binary \"@${CORE_DUMP_FILE_OUTPUT_ENCODED}\"" # Delete tmp files rm -f $CORE_DUMP_FILE_OUTPUT &> /dev/null rm -f $CORE_DUMP_FILE_OUTPUT_ENCODED &> /dev/null rm -f $API_RESPONSE &> /dev/null # Get first N lines from gdb output gdb --batch -ex "bt full" $generated_by $file 2>/dev/null | sed -n '/Core was generated by/,$p' | head -n $MAX_DUMP_LINES_TO_REPORT > $CORE_DUMP_FILE_OUTPUT if [ "$(cat $CORE_DUMP_FILE_OUTPUT | wc -l)" == "0" ]; then report "gdb output for core dump $(text_blue $file) is empty, skipping core dump" 1 set_file_skipped $file $generated_by_basename return 0 fi # Show core dump in log report "Dump file output:" 3 echo "" cat $CORE_DUMP_FILE_OUTPUT echo "" # Encode dump file echo -n "data=" > $CORE_DUMP_FILE_OUTPUT_ENCODED base64 -w 0 $CORE_DUMP_FILE_OUTPUT | tr '/+' '_-' >> $CORE_DUMP_FILE_OUTPUT_ENCODED # Send API request report "API request:" 3 echo "$api_command" if [ "$TEST_MODE" == "0" ]; then eval $api_command if [ -e $API_RESPONSE ]; then report "API response: $(cat $API_RESPONSE)" 3 else report "API response is empty" 3 fi fi set_file_reported $file $generated_by_basename # Increment counters (using tmp files) touch "${COUNTER_PATH}_${generated_by_basename}_$(uuidgen)" } set_file_reported() { local file=$1 local file_basename=$(basename $file) local generated_by=$2 if [[ $file_basename != *"reported"* ]] && [ "$TEST_MODE" == "0" ]; then mv $file ${file}.${generated_by}.reported fi } set_file_skipped() { local file=$1 local file_basename=$(basename $file) local generated_by=$2 if [[ $file_basename != *"skipped"* ]] && [ "$TEST_MODE" == "0" ]; then mv $file ${file}.${generated_by}.skipped fi } delete_old_file() { local file=$1 local generated_at=$(stat $file | grep "Modify") local generated_at="${generated_at:8:${#generated_at}-24}" local days_old=$((($(date +%s)-$(date +%s --date "$generated_at"))/(3600*24))) if [ $days_old -gt $SKIP_OLDER_THAN ]; then report "Core dump $(text_blue $file) created at $generated_at and is $days_old days old, deleting old core dump" 3 rm -f $file &> /dev/null return 1 else return 0 fi } process_dump() { local file=$1 local file_basename=$(basename $file) # Delete old files delete_old_file $file if [ ! -e $file ]; then return 0 fi if [[ $file_basename == *"skipped"* ]]; then report "Core dump $(text_blue $file) is skipped" 3 elif [[ $file_basename == *"reported"* ]]; then report "Core dump $(text_blue $file) is reported" 0 else # Get core dump data local generated_by=$(gdb --batch $file 2>/dev/null | grep "Core was generated by" | awk '{print $5}' | sed "s|[\.']||g") local generated_by="${generated_by:1:${#generated_by}}" local generated_by_basename="$(basename $generated_by 2>/dev/null)" local generated_at=$(stat $file | grep "Modify") local generated_at="${generated_at:8:${#generated_at}-24}" if [ "$generated_by_basename" == "" ]; then report "Can't determine which process generated core dump $(text_blue $file), skipping" 2 set_file_skipped $file "unknown" return 0 fi if [[ ${PROCESSES_TO_REPORT[*]} =~ "$generated_by_basename" ]]; then report "Core dump $(text_blue $file) is generated by $(text_green $generated_by_basename) at $generated_at, reporting it" 3 # Count how many times we have reported core dump for this process local reported_dumps=$(ls ${COUNTER_PATH}_${generated_by_basename}_* 2>/dev/null | wc -l) if (( reported_dumps >= MAX_DUMPS_TO_REPORT )); then report "Already reported $MAX_DUMPS_TO_REPORT $(text_green $generated_by_basename) core dumps, skipping core dump" 2 set_file_skipped $file $generated_by_basename return 0 fi else report "Core dump file $(text_blue $file) is not configured for reporting (generated by: $(text_green $generated_by_basename)), skipping" 3 set_file_skipped $file $generated_by_basename return 0 fi # Report dump report_dump "$file" "$generated_by" "$generated_at" fi } # ---- MAIN ---- k_start $@ if [ "$TEST_MODE" == "1" ]; then report "TEST MODE enabled, core dumps will not be reported" 8 fi PATH=$PATH:/usr/sbin:/sbin # Maybe another instance is already running? pidof -o %PPID -x $0 > /dev/null && echo "Script $0 is already running" && exit 1 # Reset variables cleanup # Set cleanup at exit trap cleanup EXIT # Iterate through every dir in array for dir in ${CORE_DUMP_DIR_ARRAY[@]}; do # Reset file rm -f $CORE_DUMP_FILE_LIST &> /dev/null # Check if directory exists if [ -e $dir ]; then report "-- Checking for core dumps in directory: $(text_green $dir)" 3 # Get every core dump file by wildcard for wildcard in ${CORE_DUMP_NAME_WILDCARD_ARRAY[@]}; do find $dir -maxdepth 1 -type f -name "*$wildcard*" >> $CORE_DUMP_FILE_LIST done # How many dumps found? core_dumps_found=$(cat $CORE_DUMP_FILE_LIST | wc -l) if (( core_dumps_found > 0 )); then report "Found $core_dumps_found core dumps in $(text_green $dir)" 3 # Process core dump files for dump in $(cat $CORE_DUMP_FILE_LIST); do process_dump $dump done else report "Core dumps not found in $(text_green $dir)" 0 fi fi done report "Script finished" 0