/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
#include "base/crash/crash_handler.h"

#include <dlfcn.h>
#include <fcntl.h>
#include <memory>
#include <ostream>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ucontext.h>
#include <unistd.h>
#include <pthread.h>
#if defined(__arm__)
#include "base/crash/backtrace.h"
#else
#include <unwind.h>
#endif
#include "base/android/weex_log.h"

namespace crash_handler {

// global pointer
CrashHandlerInfo* g_crashHandler = NULL;

pthread_mutex_t g_handlerMutex = PTHREAD_MUTEX_INITIALIZER;

CrashHandlerInfo::SignalInfo s_hookSignals[] = {
#define DECLARE_SIGNAL(sig) { sig, #sig },
DECLARE_SIGNAL(SIGFPE)
DECLARE_SIGNAL(SIGBUS)
DECLARE_SIGNAL(SIGSEGV)
DECLARE_SIGNAL(SIGABRT)
DECLARE_SIGNAL(SIGILL)
DECLARE_SIGNAL(SIGTRAP)
{ 0, nullptr }
};

volatile sig_atomic_t is_sig_after_crash = 0;

/* static functions */
static bool isFileAccess(const char* file)
{
    int ret;

    struct stat mystat;
    ret = stat(file, &mystat);
    if (ret == -1) {
        LOG(INFO) << "CrashHandlerInfo file:" << file << " not access errno:" << errno;
        return false;
    }
    return true;
}

static void handleExitAlarm(int num, siginfo_t* info, void* ucontext){
    if (is_sig_after_crash){
        _exit(0);
    }
}

static void crashSigAction(int num, siginfo_t* info, void* ucontext)
{
    bool handled = false;
    handled = g_crashHandler->handleSignal(num, info, ucontext);

    //If nothing to do here, we exit current process normally
    _exit(0);
}

#if defined(__arm__)
static int traceFunction(uintptr_t ip, void* arg)
{
    if (g_crashHandler->printIP(reinterpret_cast<void*>(ip)))
        return BACKTRACE_CONTINUE;
    return BACKTRACE_ABORT;
}
#else
static _Unwind_Reason_Code traceFunction(_Unwind_Context* context, void* arg)
{
    void* ip = (void*)_Unwind_GetIP(context);
    if (g_crashHandler->printIP(ip))
        return _URC_NO_REASON;
    return _URC_NORMAL_STOP;
}
#endif

/* Class CrashHandlerInfo */
CrashHandlerInfo::CrashHandlerInfo(std::string fileName)
            : m_dumpFileFd(-1),
              m_mapsFileFd(-1),
              m_crash_occurs(false),
              m_dumpFileName(fileName)
{
    if (!g_crashHandler)
        g_crashHandler = this;
}

CrashHandlerInfo::~CrashHandlerInfo()
{
    // Close fds
    if (m_dumpFileFd != -1)
        close(m_dumpFileFd);
    if (m_mapsFileFd != -1)
        close(m_mapsFileFd);

    g_crashHandler = nullptr;
}

void CrashHandlerInfo::parpareFds()
{
    // open dump file
    uint32_t flags = 0;
    if (!isFileAccess(m_dumpFileName.c_str()))
        flags = O_CREAT;
    flags |= O_WRONLY;
    uint32_t mode = S_IRUSR | S_IWUSR;
    m_dumpFileFd = ::open(m_dumpFileName.c_str(), flags, mode);
    if (m_dumpFileFd < 0) {
        LOG(ERROR) << "CrashHandlerInfo open dump file:" << m_dumpFileName.c_str() << " error: " << errno;
    }

    // open maps file
    m_mapsFileFd = ::open("/proc/self/maps", O_RDONLY);
    if (m_mapsFileFd < 0) {
        LOG(ERROR) << "CrashHandlerInfo open /proc/self/maps failed errno:" << errno;
    }
}

bool CrashHandlerInfo::initializeCrashHandler()
{
    // we open the file here because the crash maybe damage the thread stack
    parpareFds();

    // Install signal handler
    for (int i = 0; s_hookSignals[i].signum; ++i) {
        struct sigaction mysigaction = { 0 };
        mysigaction.sa_sigaction = crashSigAction;
        mysigaction.sa_flags = SA_SIGINFO;
        if (-1 == sigaction(s_hookSignals[i].signum, &mysigaction, &m_sigaction[i])) {
          LOG(ERROR) << "CrashHandlerInfo install signal(" << s_hookSignals[i].signum << ") failed errno:" << errno;
          return false;
        }
    }
    return true;
}

bool CrashHandlerInfo::handleSignal(int signum, siginfo_t* siginfo, void* ucontext)
{
    //TODO return false: add condition to filter which CrashHandlerInfo to handler the
    //signal

    m_crash_occurs = true;

    if (handle_alarm_signal){
        //handleSignal maybe cause sig delivered deadLock,and maybe block weexCoreThread on ipc，then cause whiteScreen or other issue unless restart app
        //cause 1:async-signal-safe http://man7.org/linux/man-pages/man7/signal-safety.7.html#top_of_page
        //cause 2:other logic crash or dl.
        //solution: google breakPad dump crash on a new process to avoid bugs.
        //tmp solution: setTimeout 1s,force exit on handleExitAlarm(if has deadLock)


        is_sig_after_crash =1;

        struct sigaction alarmSigaction = { 0 };
        alarmSigaction.sa_sigaction = handleExitAlarm;
        alarmSigaction.sa_flags = SA_SIGINFO;


        if (-1 == sigaction(SIGALRM, &alarmSigaction, nullptr)) {
            _exit(0);
        }
        alarm(1);
    }

    LOG(INFO) << "CrashHandlerInfo::handleSignal";
    const char* signalName = nullptr;
    for (int i = 0; s_hookSignals[i].signum; ++i) {
        if (s_hookSignals[i].signum == signum) {
            signalName = s_hookSignals[i].signame;
        }
    }
    if (!signalName)
        signalName = "unknown signal";

    printf("SIG: %s (%d), fault addr: %p\n", signalName, signum, siginfo->si_addr);

    if (siginfo->si_code == SI_USER) {
        printf("Killed by pid: %d, uid: %d\n", siginfo->si_pid, siginfo->si_uid);
    }
    m_mcontext = static_cast<ucontext_t*>(ucontext)->uc_mcontext;
    if (m_mapsFileFd < 0) {
        printf("\nmaps: read maps file error! no dump message here! \n");
    } else {
        printMaps();
        printUnwind();
        printContext();
        saveFileContent();
    }

    // close fds here
    if (m_dumpFileFd != -1)
        close(m_dumpFileFd);
    if (m_mapsFileFd != -1)
        close(m_mapsFileFd);

    return true;
}

void CrashHandlerInfo::printMaps()
{
    printf("\nmaps:\n");
    std::unique_ptr<char[]> buf(new char[CrashHandlerInfo::BUF_SIZE]);
    const char* line;
    while (true) {
        ssize_t byteRead = ::read(m_mapsFileFd, buf.get(), CrashHandlerInfo::BUF_SIZE);
        // check error.
        if (byteRead == -1 && errno != EINTR)
            return;
        // check eof.
        if (byteRead == 0)
            return;
        size_t fileContentOldSize = m_fileContent.size();
        m_fileContent.resize(fileContentOldSize + byteRead);
        memcpy(const_cast<char*>(m_fileContent.data()) + fileContentOldSize, buf.get(), byteRead);
    }
}

void CrashHandlerInfo::printContext()
{
    const mcontext_t& mcontext = m_mcontext;
#if defined(__arm__)
    printf("r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n",
        static_cast<unsigned long>(mcontext.arm_r0),
        static_cast<unsigned long>(mcontext.arm_r1),
        static_cast<unsigned long>(mcontext.arm_r2),
        static_cast<unsigned long>(mcontext.arm_r3));
    printf("r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n",
        static_cast<unsigned long>(mcontext.arm_r4),
        static_cast<unsigned long>(mcontext.arm_r5),
        static_cast<unsigned long>(mcontext.arm_r6),
        static_cast<unsigned long>(mcontext.arm_r7));
    printf("r8: %08lx r9: %08lx r10: %08lx fp: %08lx\n",
        static_cast<unsigned long>(mcontext.arm_r8),
        static_cast<unsigned long>(mcontext.arm_r9),
        static_cast<unsigned long>(mcontext.arm_r10),
        static_cast<unsigned long>(mcontext.arm_fp));
    printf("ip: %08lx sp: %08lx lr: %08lx pc: %08lx, cpsr: %08lx, arm_fault_addr: %08lx\n",
        static_cast<unsigned long>(mcontext.arm_ip),
        static_cast<unsigned long>(mcontext.arm_sp),
        static_cast<unsigned long>(mcontext.arm_lr),
        static_cast<unsigned long>(mcontext.arm_pc),
        static_cast<unsigned long>(mcontext.arm_cpsr),
        static_cast<unsigned long>(mcontext.fault_address));
    #define CALL_PRINT_REG_CONTENT(name) printRegContent(reinterpret_cast<void*>(mcontext.arm_##name), #name)
    printf("\n");
    CALL_PRINT_REG_CONTENT(r0);
    CALL_PRINT_REG_CONTENT(r1);
    CALL_PRINT_REG_CONTENT(r2);
    CALL_PRINT_REG_CONTENT(r3);
    CALL_PRINT_REG_CONTENT(r4);
    CALL_PRINT_REG_CONTENT(r5);
    CALL_PRINT_REG_CONTENT(r6);
    CALL_PRINT_REG_CONTENT(r7);
    CALL_PRINT_REG_CONTENT(r8);
    CALL_PRINT_REG_CONTENT(r9);
    CALL_PRINT_REG_CONTENT(r10);
    CALL_PRINT_REG_CONTENT(fp);
    CALL_PRINT_REG_CONTENT(ip);
    CALL_PRINT_REG_CONTENT(sp);
    CALL_PRINT_REG_CONTENT(lr);
    CALL_PRINT_REG_CONTENT(pc);
#elif defined(__i386__)
    printf("eax: %08lx ebx: %08lx ecx: %08lx edx: %08lx\n",
        static_cast<unsigned long>(mcontext.gregs[REG_EAX]),
        static_cast<unsigned long>(mcontext.gregs[REG_EBX]),
        static_cast<unsigned long>(mcontext.gregs[REG_ECX]),
        static_cast<unsigned long>(mcontext.gregs[REG_EDX]));

    printf("esi: %08lx edi: %08lx ebp: %08lx esp: %08lx\n",
        static_cast<unsigned long>(mcontext.gregs[REG_ESI]),
        static_cast<unsigned long>(mcontext.gregs[REG_EDI]),
        static_cast<unsigned long>(mcontext.gregs[REG_EBP]),
        static_cast<unsigned long>(mcontext.gregs[REG_ESP]));

    printf("eip: %08lx eflags: %08lx trapno: %08lx error: %08lx\n",
        static_cast<unsigned long>(mcontext.gregs[REG_EIP]),
        static_cast<unsigned long>(mcontext.gregs[REG_EFL]),
        static_cast<unsigned long>(mcontext.gregs[REG_TRAPNO]),
        static_cast<unsigned long>(mcontext.gregs[REG_ERR]));

    printf("cs: %04lx fs: %04lx gs:%04lx ss: %04lx ds: %04lx\n",
        static_cast<unsigned long>(mcontext.gregs[REG_CS]),
        static_cast<unsigned long>(mcontext.gregs[REG_FS]),
        static_cast<unsigned long>(mcontext.gregs[REG_GS]),
        static_cast<unsigned long>(mcontext.gregs[REG_SS]),
        static_cast<unsigned long>(mcontext.gregs[REG_DS]));

    #define CALL_PRINT_REG_CONTENT(name) printRegContent(reinterpret_cast<void*>(mcontext.gregs[REG_##name]), #name)
    printf("\n");
    CALL_PRINT_REG_CONTENT(EAX);
    CALL_PRINT_REG_CONTENT(EBX);
    CALL_PRINT_REG_CONTENT(ECX);
    CALL_PRINT_REG_CONTENT(EDX);
    CALL_PRINT_REG_CONTENT(ESI);
    CALL_PRINT_REG_CONTENT(EDI);
    CALL_PRINT_REG_CONTENT(EBP);
    CALL_PRINT_REG_CONTENT(ESP);
    CALL_PRINT_REG_CONTENT(EIP);
#else
    //TODO: other platform
#endif
}

void CrashHandlerInfo::printRegContent(void* addr, const char* name)
{
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define PAGE_START(addr) reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & ~(PAGE_SIZE - 1))
#define IS_PAGE_START(addr) !(reinterpret_cast<uintptr_t>(addr) & (PAGE_SIZE - 1))
    void* printStart = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(addr) - 0x20) & ~7);
    unsigned char vec;
    bool printZero = false;
    if (!IS_PAGE_START(printStart))
        printZero = mincore(PAGE_START(printStart), PAGE_SIZE, &vec);
    printf("\n%s:\n", name);
    for (int i = 0; i < 4 * 40; ++i) {
        if (!(i & 3)) {
            printf("%08lx: ", reinterpret_cast<unsigned long>(printStart));
        }
        if (IS_PAGE_START(printStart)) {
            printZero = mincore(printStart, PAGE_SIZE, &vec);
        }
        if (printZero)
            printf("%08lx", 0L);
        else
            printf("%08lx", *reinterpret_cast<unsigned long*>(printStart));
        if ((i & 3) == 3)
            printf("\n");
        else
            printf(" ");
        printStart = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(printStart) + sizeof(void*));
    }
    printf("\n");
}

void CrashHandlerInfo::printUnwind()
{
    printf("\nbacktrace:\n");
#if defined(__arm__)
    printIP(reinterpret_cast<void*>(m_mcontext.arm_pc));
    mybacktrace(traceFunction, nullptr, &m_mcontext);
#else
    _Unwind_Backtrace(traceFunction, nullptr);
#endif
    printf("\n");
}

bool CrashHandlerInfo::printIP(void* ip)
{
    Dl_info info;
    if (dladdr(ip, &info)) {
        printf("%s + %08lx(%s)\n", info.dli_fname,
            reinterpret_cast<unsigned long>(ip) - reinterpret_cast<unsigned long>(info.dli_fbase),
            info.dli_sname ? info.dli_sname : "unknown symbol");
    } else {
        printf("unknown dso: %p\n", ip);
    }

    // TODO: Does m_unwinded work?
    if (m_unwinded > maxUnwindCount)
        return false;
    return true;
}

bool CrashHandlerInfo::ensureDirectory(const char* base)
{
    int ret;
    struct stat mystat;

    ret = stat(base, &mystat);
    if (ret == -1) {
        int myerrno = errno;
        if (myerrno == ENOENT) {
            ret = mkdir(base, 0755);
            if (ret == -1)
                return false;
            return true;
        } else {
            return false;
        }
    }
    return true;
}

void CrashHandlerInfo::printf(const char* fmt, ...)
{
    va_list arg_list;
    int size;
    va_start(arg_list, fmt);
    size = vsnprintf(nullptr, 0, fmt, arg_list);
    va_end(arg_list);
    if (size > 0) {
        size_t oldSize = m_fileContent.size();
        m_fileContent.reserve(oldSize + size + 1);
        m_fileContent.resize(oldSize + size);
        va_start(arg_list, fmt);
        vsnprintf(const_cast<char*>(m_fileContent.data()) + oldSize, size + 1, fmt, arg_list);
        va_end(arg_list);
    }
}

void CrashHandlerInfo::saveFileContent()
{
    LOG(INFO) << "saveFileContent: " << std::endl <<  m_fileContent.data();
    const char* str = m_fileContent.data();
    size_t size = m_fileContent.size();
    while (size) {
        ssize_t byteWritten = ::write(m_dumpFileFd, str, size);
        if (byteWritten == -1 && errno != EINTR)
            return;
        str += byteWritten;
        size -= byteWritten;
    }
}
bool CrashHandlerInfo::is_crashed() {
    return m_crash_occurs;
}

}
