
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Valentin V. Bartenev
 * Copyright (C) NGINX, Inc.
 */

#include <nxt_router.h>
#include <nxt_conf.h>
#include <nxt_http.h>


typedef struct {
    nxt_str_t                 path;
    nxt_str_t                 format;
    nxt_conf_value_t          *expr;
} nxt_router_access_log_conf_t;


typedef struct {
    nxt_str_t                 text;
    nxt_router_access_log_t   *access_log;
} nxt_router_access_log_ctx_t;


static void nxt_router_access_log_writer(nxt_task_t *task,
    nxt_http_request_t *r, nxt_router_access_log_t *access_log,
    nxt_tstr_t *format);
static void nxt_router_access_log_write(nxt_task_t *task, nxt_http_request_t *r,
    nxt_router_access_log_ctx_t *ctx);
static void nxt_router_access_log_ready(nxt_task_t *task,
    nxt_port_recv_msg_t *msg, void *data);
static void nxt_router_access_log_error(nxt_task_t *task,
    nxt_port_recv_msg_t *msg, void *data);
static void nxt_router_access_log_reopen_completion(nxt_task_t *task, void *obj,
    void *data);
static void nxt_router_access_log_reopen_ready(nxt_task_t *task,
    nxt_port_recv_msg_t *msg, void *data);
static void nxt_router_access_log_reopen_error(nxt_task_t *task,
    nxt_port_recv_msg_t *msg, void *data);


static nxt_conf_map_t  nxt_router_access_log_conf[] = {
    {
        nxt_string("path"),
        NXT_CONF_MAP_STR,
        offsetof(nxt_router_access_log_conf_t, path),
    },

    {
        nxt_string("format"),
        NXT_CONF_MAP_STR,
        offsetof(nxt_router_access_log_conf_t, format),
    },

    {
        nxt_string("if"),
        NXT_CONF_MAP_PTR,
        offsetof(nxt_router_access_log_conf_t, expr),
    },
};


nxt_int_t
nxt_router_access_log_create(nxt_task_t *task, nxt_router_conf_t *rtcf,
    nxt_conf_value_t *value)
{
    u_char                        *p;
    nxt_int_t                     ret;
    nxt_str_t                     str;
    nxt_tstr_t                    *format;
    nxt_router_t                  *router;
    nxt_router_access_log_t       *access_log;
    nxt_router_access_log_conf_t  alcf;

    static const nxt_str_t  log_format_str = nxt_string("$remote_addr - - "
        "[$time_local] \"$request_line\" $status $body_bytes_sent "
        "\"$header_referer\" \"$header_user_agent\"");

    nxt_memzero(&alcf, sizeof(nxt_router_access_log_conf_t));

    alcf.format = log_format_str;

    if (nxt_conf_type(value) == NXT_CONF_STRING) {
        nxt_conf_get_string(value, &alcf.path);

    } else {
        ret = nxt_conf_map_object(rtcf->mem_pool, value,
                                  nxt_router_access_log_conf,
                                  nxt_nitems(nxt_router_access_log_conf),
                                  &alcf);
        if (ret != NXT_OK) {
            nxt_alert(task, "access log map error");
            return NXT_ERROR;
        }
    }

    router = nxt_router;

    access_log = router->access_log;

    if (access_log != NULL && nxt_strstr_eq(&alcf.path, &access_log->path)) {
        nxt_router_access_log_use(&router->lock, access_log);

    } else {
        access_log = nxt_malloc(sizeof(nxt_router_access_log_t)
                                + alcf.path.length);
        if (access_log == NULL) {
            nxt_alert(task, "failed to allocate access log structure");
            return NXT_ERROR;
        }

        access_log->fd = -1;
        access_log->handler = &nxt_router_access_log_writer;
        access_log->count = 1;

        access_log->path.length = alcf.path.length;
        access_log->path.start = (u_char *) access_log
                                 + sizeof(nxt_router_access_log_t);

        nxt_memcpy(access_log->path.start, alcf.path.start, alcf.path.length);
    }

    str.length = alcf.format.length + 1;

    str.start = nxt_malloc(str.length);
    if (str.start == NULL) {
        nxt_alert(task, "failed to allocate log format structure");
        return NXT_ERROR;
    }

    p = nxt_cpymem(str.start, alcf.format.start, alcf.format.length);
    *p = '\n';

    format = nxt_tstr_compile(rtcf->tstr_state, &str, NXT_TSTR_LOGGING);
    if (nxt_slow_path(format == NULL)) {
        return NXT_ERROR;
    }

    rtcf->access_log = access_log;
    rtcf->log_format = format;

    if (alcf.expr != NULL) {
        nxt_conf_get_string(alcf.expr, &str);

        ret = nxt_tstr_cond_compile(rtcf->tstr_state, &str, &rtcf->log_cond);
        if (nxt_slow_path(ret != NXT_OK)) {
            return NXT_ERROR;
        }
    }

    return NXT_OK;
}


static void
nxt_router_access_log_writer(nxt_task_t *task, nxt_http_request_t *r,
    nxt_router_access_log_t *access_log, nxt_tstr_t *format)
{
    nxt_int_t                    ret;
    nxt_router_conf_t            *rtcf;
    nxt_router_access_log_ctx_t  *ctx;

    ctx = nxt_mp_get(r->mem_pool, sizeof(nxt_router_access_log_ctx_t));
    if (nxt_slow_path(ctx == NULL)) {
        return;
    }

    ctx->access_log = access_log;

    if (nxt_tstr_is_const(format)) {
        nxt_tstr_str(format, &ctx->text);

    } else {
        rtcf = r->conf->socket_conf->router_conf;

        ret = nxt_tstr_query_init(&r->tstr_query, rtcf->tstr_state,
                                  &r->tstr_cache, r, r->mem_pool);
        if (nxt_slow_path(ret != NXT_OK)) {
            return;
        }

        ret = nxt_tstr_query(task, r->tstr_query, format, &ctx->text);
        if (nxt_slow_path(ret != NXT_OK)) {
            return;
        }
    }

    nxt_router_access_log_write(task, r, ctx);
}


static void
nxt_router_access_log_write(nxt_task_t *task, nxt_http_request_t *r,
    nxt_router_access_log_ctx_t *ctx)
{
    nxt_fd_write(ctx->access_log->fd, ctx->text.start, ctx->text.length);

    nxt_http_request_close_handler(task, r, r->proto.any);
}


void
nxt_router_access_log_open(nxt_task_t *task, nxt_router_temp_conf_t *tmcf)
{
    uint32_t                 stream;
    nxt_int_t                ret;
    nxt_buf_t                *b;
    nxt_port_t               *main_port, *router_port;
    nxt_runtime_t            *rt;
    nxt_router_access_log_t  *access_log;

    access_log = tmcf->router_conf->access_log;

    b = nxt_buf_mem_alloc(tmcf->mem_pool, access_log->path.length + 1, 0);
    if (nxt_slow_path(b == NULL)) {
        goto fail;
    }

    b->completion_handler = nxt_buf_dummy_completion;

    nxt_buf_cpystr(b, &access_log->path);
    *b->mem.free++ = '\0';

    rt = task->thread->runtime;
    main_port = rt->port_by_type[NXT_PROCESS_MAIN];
    router_port = rt->port_by_type[NXT_PROCESS_ROUTER];

    stream = nxt_port_rpc_register_handler(task, router_port,
                                           nxt_router_access_log_ready,
                                           nxt_router_access_log_error,
                                           -1, tmcf);
    if (nxt_slow_path(stream == 0)) {
        goto fail;
    }

    ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_ACCESS_LOG, -1,
                                stream, router_port->id, b);

    if (nxt_slow_path(ret != NXT_OK)) {
        nxt_port_rpc_cancel(task, router_port, stream);
        goto fail;
    }

    return;

fail:

    nxt_router_conf_error(task, tmcf);
}


static void
nxt_router_access_log_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg,
    void *data)
{
    nxt_router_temp_conf_t   *tmcf;
    nxt_router_access_log_t  *access_log;

    tmcf = data;

    access_log = tmcf->router_conf->access_log;

    access_log->fd = msg->fd[0];

    nxt_work_queue_add(&task->thread->engine->fast_work_queue,
                       nxt_router_conf_apply, task, tmcf, NULL);
}


static void
nxt_router_access_log_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
    void *data)
{
    nxt_router_temp_conf_t  *tmcf;

    tmcf = data;

    nxt_router_conf_error(task, tmcf);
}


void
nxt_router_access_log_use(nxt_thread_spinlock_t *lock,
    nxt_router_access_log_t *access_log)
{
    if (access_log == NULL) {
        return;
    }

    nxt_thread_spin_lock(lock);

    access_log->count++;

    nxt_thread_spin_unlock(lock);
}


void
nxt_router_access_log_release(nxt_task_t *task, nxt_thread_spinlock_t *lock,
    nxt_router_access_log_t *access_log)
{
    if (access_log == NULL) {
        return;
    }

    nxt_thread_spin_lock(lock);

    if (--access_log->count != 0) {
        access_log = NULL;
    }

    nxt_thread_spin_unlock(lock);

    if (access_log != NULL) {

        if (access_log->fd != -1) {
            nxt_fd_close(access_log->fd);
        }

        nxt_free(access_log);
    }
}


typedef struct {
    nxt_mp_t                 *mem_pool;
    nxt_router_access_log_t  *access_log;
} nxt_router_access_log_reopen_t;


void
nxt_router_access_log_reopen_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
{
    nxt_mp_t                        *mp;
    uint32_t                        stream;
    nxt_int_t                       ret;
    nxt_buf_t                       *b;
    nxt_port_t                      *main_port, *router_port;
    nxt_runtime_t                   *rt;
    nxt_router_access_log_t         *access_log;
    nxt_router_access_log_reopen_t  *reopen;

    access_log = nxt_router->access_log;

    if (access_log == NULL) {
        return;
    }

    mp = nxt_mp_create(1024, 128, 256, 32);
    if (nxt_slow_path(mp == NULL)) {
        return;
    }

    reopen = nxt_mp_get(mp, sizeof(nxt_router_access_log_reopen_t));
    if (nxt_slow_path(reopen == NULL)) {
        goto fail;
    }

    reopen->mem_pool = mp;
    reopen->access_log = access_log;

    b = nxt_buf_mem_alloc(mp, access_log->path.length + 1, 0);
    if (nxt_slow_path(b == NULL)) {
        goto fail;
    }

    b->completion_handler = nxt_router_access_log_reopen_completion;

    nxt_buf_cpystr(b, &access_log->path);
    *b->mem.free++ = '\0';

    rt = task->thread->runtime;
    main_port = rt->port_by_type[NXT_PROCESS_MAIN];
    router_port = rt->port_by_type[NXT_PROCESS_ROUTER];

    stream = nxt_port_rpc_register_handler(task, router_port,
                                           nxt_router_access_log_reopen_ready,
                                           nxt_router_access_log_reopen_error,
                                           -1, reopen);
    if (nxt_slow_path(stream == 0)) {
        goto fail;
    }

    ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_ACCESS_LOG, -1,
                                stream, router_port->id, b);

    if (nxt_slow_path(ret != NXT_OK)) {
        nxt_port_rpc_cancel(task, router_port, stream);
        goto fail;
    }

    nxt_mp_retain(mp);

    return;

fail:

    nxt_mp_destroy(mp);
}


static void
nxt_router_access_log_reopen_completion(nxt_task_t *task, void *obj, void *data)
{
    nxt_mp_t   *mp;
    nxt_buf_t  *b;

    b = obj;
    mp = b->data;

    nxt_mp_release(mp);
}


static void
nxt_router_access_log_reopen_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg,
    void *data)
{
    nxt_router_access_log_t         *access_log;
    nxt_router_access_log_reopen_t  *reopen;

    reopen = data;

    access_log = reopen->access_log;

    if (access_log == nxt_router->access_log) {

        if (nxt_slow_path(dup2(msg->fd[0], access_log->fd) == -1)) {
            nxt_alert(task, "dup2(%FD, %FD) failed %E",
                      msg->fd[0], access_log->fd, nxt_errno);
        }
    }

    nxt_fd_close(msg->fd[0]);
    nxt_mp_release(reopen->mem_pool);
}


static void
nxt_router_access_log_reopen_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
    void *data)
{
    nxt_router_access_log_reopen_t  *reopen;

    reopen = data;

    nxt_mp_release(reopen->mem_pool);
}
