#!/bin/sh

CUSTOMIZATION="digitalwatchdog"

# Settings for killing mediaserver if it uses too much memory.
SECONDS_TO_WAIT_BEFORE_KILL_9=120
KILL_MEDIASERVER_IF_HIGH_MEM_USAGE=1
HIGH_MEM_USAGE_KEEP_FREE_KB=44272 #< Total memory minus the maximum allowed mediaserver memory.
MEM_TOTAL_KB=$(cat /proc/meminfo |grep 'MemTotal' |awk '{print $2}') #< Total memory in the system.
MAX_ALLOWED_MEM_USAGE_KB=$(expr "$MEM_TOTAL_KB" - "$HIGH_MEM_USAGE_KEEP_FREE_KB")

SWAP_FILE="/sdcard/swapfile" #< GDB requires much RAM to process core files.
SWAP_FILE_SIZE_MB=512

SERVICE_NAME="mediaserver"
INSTALL_DIR="/opt/$CUSTOMIZATION"
MEDIASERVER_DIR="$INSTALL_DIR/mediaserver"
LIBS_DIR="$MEDIASERVER_DIR/lib"
BIN_NAME="mediaserver"
BIN_FILE="$MEDIASERVER_DIR/bin/$BIN_NAME"
LOGS_DIR="/sdcard/mserver.data/log"
STORAGE_PATH="/sdcard"
HTTP_LOG_LEVEL="none"
CORE_PATH="$STORAGE_PATH/cores"

# Touch .flag file to capture all mediaserver output to a file.
MEDIASERVER_OUT_FLAG="$LOGS_DIR/mediaserver-out.flag"
MEDIASERVER_OUT_LOG="$LOGS_DIR/mediaserver-out.log"

# Touch .flag file to capture all output of this script to a file.
OUT_FLAG="$LOGS_DIR/$(basename "$0")-out.flag"
OUT_LOG="$LOGS_DIR/$(basename "$0")-out.log"

export VMS_PLUGIN_DIR="$MEDIASERVER_DIR/plugins"
export SASL_PATH="$LIBS_DIR/sasl2"

# ATTENTION: The args should not contain spaces, because busybox shell does not support arrays.
RUN_ARGS="\
-e \
--conf-file=$MEDIASERVER_DIR/etc/mediaserver.conf \
--runtime-conf-file=$MEDIASERVER_DIR/etc/running_time.conf \
--msg-log-level=$HTTP_LOG_LEVEL \
"

source "$INSTALL_DIR/build_info.txt"
FULL_VERSION="$version-$changeSet-$customization"
[ ! -z "$beta" ] && FULL_VERSION="${FULL_VERSION}-beta"

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

# Redirect all output of this script to the log file.
redirectOutput() # log_file
{
    LOG_FILE="$1"

    # If the old log file exists, rename it to first available "filename_#.log", stopping at 9.
    OLD_LOG_FILE_MESSAGE=""
    if [ -f "$LOG_FILE" ]
    then
        for i in $(seq 1 9)
        do
            OLD_LOG_FILE="${LOG_FILE%.*}_$i.log" #< Remove the extension and add the new suffix.
            if [ ! -f "$OLD_LOG_FILE" ]
            then # Unused filename found.
                break
            fi
        done
        mv "$LOG_FILE" "$OLD_LOG_FILE"
        OLD_LOG_FILE_MESSAGE=" (old log file renamed to $(basename "$OLD_LOG_FILE"))"
    fi

    echo "$0: See the log in $LOG_FILE$OLD_LOG_FILE_MESSAGE"

    exec 1<&- #< Close stdout fd.
    exec 2<&- #< Close stderr fd.
    exec 1<>"$LOG_FILE" #< Open stdout as $LOG_FILE for reading and writing.
    exec 2>&1 #< Redirect stderr to stdout.
}

logArgs() # "$@"
{
    echo "Script [$0] started with pid $$ (parent pid $PPID), with $# arg(s):"
    echo "{"
    for ARG in "$@"
    do
        echo "    [$ARG]"
    done
    echo "}"
    echo ""
}

createSwapFile()
{
    dd if=/dev/zero of="$SWAP_FILE" bs=$MEGABYTE count="$SWAP_FILE_SIZE_MB" || return $?
    if ! chmod 0600 "$SWAP_FILE"
    then
        echo "WARNING: Unable to chmod swap file."
    fi
    if ! mkswap "$SWAP_FILE"
    then
        echo "ERROR: Unable to prepare swap file with mkswap; deleting it."
        rm -rf "$SWAP_FILE"
        return 1
    fi
    return 0
}

recreateSwapFileInBackground()
{
    if ps aux |grep dd |grep "$SWAP_FILE"
    then
        echo "WARNING: Swap file at $SWAP_FILE is currently being created"
        return 0
    fi

    echo "Creating swap file of $SWAP_FILE_SIZE_MB MB at $SWAP_FILE"
    rm -f "$SWAP_FILE"
    createSwapFile & #< Run in background.
}

# [in] SWAP_FILE, SWAP_FILE_SIZE_MB
# @return 1 if the swap file has to be (re)created, 0 if it exists and has the proper size.
createSwapFileIfNeeded()
{
    MEGABYTE=1048576

    if [ ! -f "$SWAP_FILE" ]
    then
        recreateSwapFileInBackground
        return 1
    fi

    EXISTING_SWAP_FILE_SIZE_BYTES=$(stat -c '%s' "$SWAP_FILE")
    if [ $EXISTING_SWAP_FILE_SIZE_BYTES != $(expr $MEGABYTE \* $SWAP_FILE_SIZE_MB) ]
    then
        echo "WARNING: Swap file $SWAP_FILE exists but has the wrong size:" \
            "$EXISTING_SWAP_FILE_SIZE_BYTES bytes"

        echo "Disabling swap (if any)"
        swapoff -a #< Just in case. Ignore errors (if any), error message(s) should already appear.

        recreateSwapFileInBackground
        return 1
    fi

    return 0
}

# [in] GDB, BIN_FILE, CORE, REPORT
generateCrashReport()
{
    CRASH_REPORT_GDB_COMMAND="t apply all bt 25"
    echo "Generating crash report in \"/root/$REPORT\":"
    echo "\"$CRASH_REPORT_GDB_COMMAND\" |\"$GDB\" \"$BIN_FILE\" \"$CORE\""
    echo "$CRASH_REPORT_GDB_COMMAND" |"$GDB" "$BIN_FILE" "$CORE" 2>/dev/null >"/tmp/$REPORT" \
        && mv "/tmp/$REPORT" "/root/$REPORT"

    swapoff -a #< Ignore errors (if any), error message(s) should already appear.
}

processCoreDumpIfNeeded()
{
    if pidof gdb
    then
        return 1 #< gdb is already running.
    fi

    GDB="$(dirname "$BIN_FILE")/gdb"
    CORE_ORIG="$CORE_PATH/core"
    TIME=$(date +"%s")

    # Do not process the core if the previous core has appeared less than a day before.
    OLD_CORE=$(\ls "$CORE_ORIG".*)
    OLD_CORE_TIME="${OLD_CORE#$CORE_ORIG.}" #< Remove "core." prefix.
    # Check whether exactly one core file exists with a time in a name.
    if [ $(expr match "$OLD_CORE_TIME" "[0-9]*\$") != 0 ]
    then
        if [ $(expr $TIME - $OLD_CORE_TIME) -gt $(expr 24 \* 60 \* 60) ]
        then
            echo "Old core is more than a day older"
            return 1
        fi
    fi

    CORE="$CORE_ORIG.$TIME"
    if ! mv "$CORE_ORIG" "$CORE" 2>/dev/null
    then
        echo "No core file found at $CORE_ORIG"
        return 1
    fi

    echo "Enabling swap at $SWAP_FILE"
    if ! swapon "$SWAP_FILE"
    then
        echo "ERROR: Unable to enable swap; trying to move core file back and recreate swap file."
        mv "$CORE" "$CORE_ORIG"
        recreateSwapFileInBackground
        return 1
    fi

    REPORT="${BIN_NAME}_${FULL_VERSION}_$TIME.gdb-bt"

    echo "Generate crash report (in background) $REPORT"
    generateCrashReport &

    \ls "$CORE_ORIG".* |grep -v "$CORE" |xargs rm 2>/dev/null #< Delete all other core files.

    return 0
}

checkSdCard()
{
    # Make sure that STORAGE_PATH dir exists and is in /proc/mounts.
    if ! grep "$STORAGE_PATH" /proc/mounts >/dev/null 2>&1 || [ ! -d "$STORAGE_PATH" ]
    then
        echo "ERROR: SD card is not inserted."
        return 1
    fi
}

start_server()
{
    echo "Starting Media Server, outputting to $REDIRECT_OUTPUT, with command:"
    set -x #< Log the next command.
    "$BIN_FILE" $RUN_ARGS >>"$REDIRECT_OUTPUT" 2>&1
    EXIT_STATUS=$?
    { set +x; } 2>/dev/null
    echo "Server process has finished with exit status [$EXIT_STATUS]"
}

start()
{
    SERVICE_PID=$(pidof "$BIN_NAME")
    if [ ! -z "$SERVICE_PID" ]
    then
       echo "$SERVICE_NAME is already running with pid $SERVICE_PID"
       return 0
    fi

    if ! touch "$STORAGE_PATH" >/dev/null 2>&1
    then
        #echo "RO"
        mount -o rw,remount "$STORAGE_PATH"
    fi

    # Workaround for /usr/local/apps being mounted a few dozen seconds after the kernel boots.
    # ATTENTION: Do no perform any advanced actions in this script before this check succeeds.
    while [ ! -f "$BIN_FILE" ]
    do
        sleep 5
    done

    checkSdCard

    createSwapFileIfNeeded && processCoreDumpIfNeeded

    if [ -f "$MEDIASERVER_OUT_FLAG" ]
    then
        mkdir -p "$(dirname "$MEDIASERVER_OUT_LOG")"
        REDIRECT_OUTPUT="$MEDIASERVER_OUT_LOG"
    else
        REDIRECT_OUTPUT="/dev/null"
    fi

    start_server&

    sleep 3
    # See if mediaserver instantly crashes.
    if [ ! -z "$(pidof "$BIN_NAME")" ]
    then
        echo "OK"
    else
        echo "FAILED"
        return 1
    fi
}

startquiet()
{
    start
}

stop() # [signal]
{
    SIGNAL="$1" #< Can be empty.

    echo "Stopping $BIN_NAME..."
    killall $SIGNAL "$BIN_NAME" >/dev/null 2>&1
    i=0
    while [ ! -z "$(pidof "$BIN_NAME")" ]
    do
        sleep 1
        let i++
        if [ $i -gt $SECONDS_TO_WAIT_BEFORE_KILL_9 ]
        then
            echo "$SERVICE_NAME not stopped after $i seconds; doing killall -9"
            killall -9 "$BIN_NAME" >/dev/null 2>&1
        fi
    done
    echo "Done"
}

printWatchdogPid()
{
    pgrep -f -- "$0 run_watchdog"
}

start_watchdog()
{
    WATCHDOG_PID=$(printWatchdogPid)
    if [ ! -z "$WATCHDOG_PID" ]
    then
        echo "$SERVICE_NAME watchdog is already running with pid $WATCHDOG_PID"
        return 0
    fi

    "$0" "run_watchdog" >/dev/null 2>&1 &
    echo "$SERVICE_NAME watchdog has been started"
}

stop_watchdog()
{
    WATCHDOG_PID=$(printWatchdogPid)
    if [ -z "$WATCHDOG_PID" ]
    then
        echo "$SERVICE_NAME watchdog is not running"
        return 0
    fi

    echo "Stopping $SERVICE_NAME watchdog..."
    kill "$WATCHDOG_PID"
    sleep 1
    WATCHDOG_PID=$(printWatchdogPid)
    if [ ! -z "$WATCHDOG_PID" ]
    then
        echo "$SERVICE_NAME watchdog still not killed; doing kill -9"
        kill -9 "$WATCHDOG_PID"
        sleep 1
    fi
    echo "Done"
}

kill_mediaserver_if_high_mem_usage()
{
    MSERVER_PID=$(pidof "$BIN_NAME")
    if [ -z "$MSERVER_PID" ]
    then
        exit 0
    fi

    MEM_USAGE_KB=$(cat /proc/$MSERVER_PID/status \
        |grep VmRSS |cut -d ":" -f2 |tr -d "\t " |cut -d "k" -f 1)

    if [ "$MEM_USAGE_KB" -gt "$MAX_ALLOWED_MEM_USAGE_KB" ]
    then
        echo "High mem usage detected: $MEM_USAGE_KB KB; killing mediaserver"
        kill -FPE "$MSERVER_PID"
    fi
}

run_watchdog()
{
    while true
    do
        start
        if [ ! -z "$KILL_MEDIASERVER_IF_HIGH_MEM_USAGE" ]
        then
            kill_mediaserver_if_high_mem_usage
        fi
        sleep 20
    done
}

help()
{
    cat <<EOF
Usage: $0 <command>
Supported commands:
 start # Start $SERVICE_NAME and watchdog, outputting to $MEDIASERVER_OUT_LOG if $MEDIASERVER_OUT_FLAG exists.
 startquiet # Start $SERVICE_NAME.
 start_console # Run $SERVICE_NAME in foreground without output redirection.
 start_gdb # Run $SERVICE_NAME under gdb.
 stop # Stop watchdog and kill $SERVICE_NAME with SIGINT.
 kill # Stop watchdog and kill $SERVICE_NAME with SIGKILL.
 status # Report whether $SERVICE_NAME is running, and its pid. Exit status is non-zero if stopped.
 server_status # Output 1 if $SERVICE_NAME is running, otherwise, do nothing.
 restart, reload, force-reload # Stop watchdog and $SERVICE_NAME, start $SERVICE_NAME and watchdog.
 start_watchdog # Start watchdog in background.
 run_watchdog # Run watchdog in foreground.
 stop_watchdog # Stop watchdog, if it is running.
EOF
}

#--------------------------------------------------------------------------------------------------
# main

if [ -f "$OUT_FLAG" ]
then
    redirectOutput "$OUT_LOG"
    logArgs "$@"
    set -x #< Log each command.
fi

echo "$CORE_PATH/core" >/proc/sys/kernel/core_pattern
if [ ! -e "$CORE_PATH" ]
then
    mkdir "$CORE_PATH"
fi
ulimit -c unlimited

case "$1" in
    start)
        start || true
        start_watchdog
        ;;
    startquiet)
        start
        ;;
    start_console)
        set -x #< Log each command.
        checkSdCard
        "$BIN_FILE" $RUN_ARGS
        ;;
    start_gdb)
        set -x #< Log each command.
        checkSdCard
        gdbserver 0.0.0.0:12345 "$BIN_FILE" $RUN_ARGS
        ;;
    stop)
        stop_watchdog
        stop
        ;;
    kill)
        stop_watchdog
        stop -9
        ;;
    status)
        SERVICE_PID=$(pidof "$BIN_NAME")
        if [ ! -z "$SERVICE_PID" ]
        then
            echo "$SERVICE_NAME is running with pid $SERVICE_PID"
            exit 0
        else
            echo "$SERVICE_NAME is stopped"
            exit 1
        fi
        ;;
    server_status)
        if [ ! -z "$(pidof "$BIN_NAME")" ]
        then
            echo "1"
        fi
        ;;
    restart|reload|force-reload)
        stop_watchdog || true
        stop || true
        start || true
        start_watchdog
        ;;
    start_watchdog)
        start_watchdog
        ;;
    run_watchdog)
        run_watchdog
        ;;
    stop_watchdog)
        stop_watchdog
        ;;
    *)
        help
        exit 1
esac
