
/*
 * Copyright (C) 2010-2012 Alibaba Group Holding Limited
 */


#include <ngx_config.h>
#include <ngx_core.h>

#include <execinfo.h>


#define NGX_BACKTRACE_DEFAULT_STACK_MAX_SIZE 30


static char *ngx_backtrace_files(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
static void ngx_error_signal_handler(int signo);
static ngx_int_t ngx_backtrace_init_worker(ngx_cycle_t *cycle);
static void *ngx_backtrace_create_conf(ngx_cycle_t *cycle);


typedef struct {
    int     signo;
    char   *signame;
    char   *name;
    void  (*handler)(int signo);
} ngx_signal_t;


typedef struct {
    ngx_log_t              *log;
    ngx_int_t               max_stack_size;
} ngx_backtrace_conf_t;


static ngx_signal_t  ngx_backtrace_signals[] = {
    { SIGABRT, "SIGABRT", "", ngx_error_signal_handler },

#ifdef SIGBUS
    { SIGBUS, "SIGBUS", "", ngx_error_signal_handler },
#endif

    { SIGFPE, "SIGFPE", "", ngx_error_signal_handler },

    { SIGILL, "SIGILL", "", ngx_error_signal_handler },

    { SIGIOT, "SIGIOT", "", ngx_error_signal_handler },

    { SIGSEGV, "SIGSEGV", "", ngx_error_signal_handler },

    { 0, NULL, "", NULL }
};


static ngx_command_t  ngx_backtrace_commands[] = {

    { ngx_string("backtrace_log"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_backtrace_files,
      0,
      0,
      NULL },

    { ngx_string("backtrace_max_stack_size"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_backtrace_conf_t, max_stack_size),
      NULL },

      ngx_null_command
};


static ngx_core_module_t  ngx_backtrace_module_ctx = {
    ngx_string("backtrace"),
    ngx_backtrace_create_conf,
    NULL
};


ngx_module_t  ngx_backtrace_module = {
    NGX_MODULE_V1,
    &ngx_backtrace_module_ctx,             /* module context */
    ngx_backtrace_commands,                /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    ngx_backtrace_init_worker,             /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_int_t
ngx_init_error_signals(ngx_log_t *log)
{
    ngx_signal_t      *sig;
    struct sigaction   sa;

    for (sig = ngx_backtrace_signals; sig->signo != 0; sig++) {
        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          "sigaction(%s) failed", sig->signame);
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}


static void
ngx_error_signal_handler(int signo)
{
    void                 *buffer;
    size_t                size;
    ngx_log_t            *log;
    ngx_signal_t         *sig;
    struct sigaction      sa;
    ngx_backtrace_conf_t *bcf;

    for (sig = ngx_backtrace_signals; sig->signo != 0; sig++) {
        if (sig->signo == signo) {
            break;
        }
    }

    bcf = (ngx_backtrace_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
                                                ngx_backtrace_module);

    log = bcf->log ? bcf->log : ngx_cycle->log;
    ngx_log_error(NGX_LOG_ERR, log, 0,
                  "nginx coredump by signal %d (%s)", signo, sig->signame);

    ngx_memzero(&sa, sizeof(struct sigaction));
    sa.sa_handler = SIG_DFL;
    sigemptyset(&sa.sa_mask);
    if (sigaction(signo, &sa, NULL) == -1) {
        ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
                      "sigaction(%s) failed", sig->signame);
    }

    if (bcf->max_stack_size == NGX_CONF_UNSET) {
        bcf->max_stack_size = NGX_BACKTRACE_DEFAULT_STACK_MAX_SIZE;
    }

    buffer = ngx_pcalloc(ngx_cycle->pool, sizeof(void *) * bcf->max_stack_size);
    if (buffer == NULL) {
        goto invalid;
    }

    size = backtrace(buffer, bcf->max_stack_size);
    backtrace_symbols_fd(buffer, size, log->file->fd);

invalid:

    kill(ngx_getpid(), signo);
}


static char *
ngx_backtrace_files(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf)
{
    ngx_str_t             file, *value;
    ngx_log_t            *log;
    ngx_backtrace_conf_t *bcf;

    bcf = (ngx_backtrace_conf_t *) ngx_get_conf(cf->cycle->conf_ctx,
                                                ngx_backtrace_module);

    value = cf->args->elts;
    file = value[1];

    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    log = ngx_log_create(cf->cycle, &file);
    if (log == NULL) {
        return NGX_CONF_ERROR;
    }

    bcf->log = log;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_backtrace_init_worker(ngx_cycle_t *cycle)
{
    if (ngx_init_error_signals(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }

    return NGX_OK;
}


static void *
ngx_backtrace_create_conf(ngx_cycle_t *cycle)
{
    ngx_backtrace_conf_t  *bcf;

    bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_backtrace_conf_t));
    if (bcf == NULL) {
        return NULL;
    }

    /*
     * set by ngx_pcalloc()
     *
     *     bcf->log = NULL;
     */

    bcf->max_stack_size = NGX_CONF_UNSET;

    return bcf;
}
