/*
 * 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 <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#include "os/mynewt.h"
#include "cbmem/cbmem.h"
#include "log/log.h"
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
#include "config/config.h"
#endif

#if MYNEWT_VAL(LOG_FLAGS_IMAGE_HASH)
#include "imgmgr/imgmgr.h"
#endif

#if MYNEWT_VAL(LOG_CLI)
#include "shell/shell.h"
#endif

struct log_module_entry {
    int16_t id;
    const char *name;
};

struct log_info g_log_info;

static STAILQ_HEAD(, log) g_log_list = STAILQ_HEAD_INITIALIZER(g_log_list);
static struct log_module_entry g_log_module_list[
    MYNEWT_VAL(LOG_MAX_USER_MODULES)];
static int g_log_module_count;
static uint8_t log_written;

#if MYNEWT_VAL(LOG_STATS)
STATS_NAME_START(logs)
    STATS_NAME(logs, writes)
    STATS_NAME(logs, drops)
    STATS_NAME(logs, regerr)
    STATS_NAME(logs, errs)
    STATS_NAME(logs, lost)
    STATS_NAME(logs, too_long)
STATS_NAME_END(logs)
#endif

#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
static int log_conf_set(int argc, char **argv, char *val);

static struct conf_handler log_conf = {
    .ch_name = "log",
    .ch_get = NULL,
    .ch_set = log_conf_set,
    .ch_commit = NULL,
    .ch_export = NULL,
};

static int
log_conf_set(int argc, char **argv, char *val)
{
    struct log *cur;

    if (argc < 2) {
        return -1;
    }

    /* Only support log/<name>/mark entries for now */
    if (strcmp("mark", argv[1])) {
        return -1;
    }

    /* Find proper log */
    STAILQ_FOREACH(cur, &g_log_list, l_next) {
        if (!strcmp(argv[0], cur->l_name)) {
            break;
        }
    }

    if (!cur) {
        return -1;
    }

    /* Set watermark if supported */
    if (cur->l_log->log_set_watermark) {
        cur->l_log->log_set_watermark(cur, atoi(val));
    }

    return 0;
}
#endif
#endif

void
log_init(void)
{
    int rc;

    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    (void)rc;

    log_written = 0;

    STAILQ_INIT(&g_log_list);
    g_log_info.li_version = MYNEWT_VAL(LOG_VERSION);
#if MYNEWT_VAL(LOG_GLOBAL_IDX)
    g_log_info.li_next_index = 0;
#endif

#if MYNEWT_VAL(LOG_CONSOLE)
    log_console_init();
#endif

#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
    rc = conf_register(&log_conf);
    SYSINIT_PANIC_ASSERT(rc == 0);
#endif
#endif
}

struct log *
log_list_get_next(struct log *log)
{
    struct log *next;

    if (log == NULL) {
        next = STAILQ_FIRST(&g_log_list);
    } else {
        next = STAILQ_NEXT(log, l_next);
    }

    return (next);
}

static int
log_module_find_idx(uint8_t id)
{
    const struct log_module_entry *entry;
    int i;

    for (i = 0; i < g_log_module_count; i++) {
        entry = &g_log_module_list[i];
        if (entry->id == id) {
            return i;
        }
    }

    return -1;
}

uint8_t
log_module_register(uint8_t id, const char *name)
{
    int idx;

    if (g_log_module_count >= MYNEWT_VAL(LOG_MAX_USER_MODULES)) {
        /* No free entries. */
        return 0;
    }

    idx = log_module_find_idx(id);
    if (idx != -1) {
        /* Already registered. */
        return 0;
    }

    /* Write to first unused entry. */
    g_log_module_list[g_log_module_count] = (struct log_module_entry) {
        .id = id,
        .name = name,
    };
    g_log_module_count++;

    return id;
}

static const char *const default_modules[] = {
    "DEFAULT",
    "OS",
    "NEWTMGR",
    "NIMBLE_CTLR",
    "NIMBLE_HOST",
    "NFFS",
    "REBOOT",
    "IOTIVITY",
};

const char *
log_module_get_name(uint8_t module)
{
    int idx;
    const char *name;

    if (module < LOG_MODULE_TEST) {
        return default_modules[module];
    }

    /* Find module defined in syscfg.logcfg sections */
    name = logcfg_log_module_name(module);

    if (name == NULL) {
        /* not in syscfg.logcfg, find module registered with log_module_register() */
        idx = log_module_find_idx(module);
        if (idx != -1) {
            name = g_log_module_list[idx].name;
        }
    }

    return name;
}

/**
 * Indicates whether the specified log has been regiestered.
 */
static int
log_registered(struct log *log)
{
    struct log *cur;

    STAILQ_FOREACH(cur, &g_log_list, l_next) {
        if (cur == log) {
            return 1;
        }
    }

    return 0;
}

struct log *
log_find(const char *name)
{
    struct log *log;

    log = NULL;
    while ((log = log_list_get_next(log)) != NULL) {
        if (strcmp(log->l_name, name) == 0) {
            break;
        }
    }

    return log;
}

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
uint16_t
log_read_trailer(struct log *log, const void *dptr, uint8_t *buf)
{
    int rc = 0;
    uint16_t offset = 0;
    uint16_t trailer_len = 0;

    if (log->l_th) {
        /* This reads log entry length without alignment */
        offset = log_read_entry_len(log, dptr);
        if (offset) {
            rc = log_read(log, dptr, &trailer_len,
                          offset - LOG_TRAILER_LEN_SIZE,
                          LOG_TRAILER_LEN_SIZE);
            if (rc == LOG_TRAILER_LEN_SIZE) {
                offset -= LOG_TRAILER_LEN_SIZE;
                rc = log_read(log, dptr, buf, offset - trailer_len,
                              trailer_len);
                if (rc != trailer_len) {
                    /* Trailer read failed, return 0 length
                     * buffer remains untouched
                     */
                    return 0;
                }
            }
        } else {
            /* Entry length is 0, no trailer buffer remains untouched */
            return 0;
        }
    }

    /* No trailer, return 0 length */
    return trailer_len;
}

uint16_t
log_read_trailer_len(struct log *log, const void *dptr)
{
    int rc = 0;
    uint16_t offset = 0;
    uint16_t trailer_len = 0;

    if (log->l_th) {
        /* This reads log entry length without alignment */
        offset = log_read_entry_len(log, dptr);
        if (offset) {
            rc = log_read(log, dptr, &trailer_len,
                          offset - LOG_TRAILER_LEN_SIZE,
                          LOG_TRAILER_LEN_SIZE);
            if (rc == LOG_TRAILER_LEN_SIZE) {
                return trailer_len;
            } else {
                /* Trailer read failed, return 0 length */
                return 0;
            }
        } else {
            /* Entry length is 0, no trailer */
            return 0;
        }
    }

    return 0;
}
#endif /* MYNEWT_VAL(LOG_FLAGS_TRAILER) */

/* Argument for header/trailer walks */
struct log_read_hdr_arg {
    struct log_entry_hdr *hdr;
    int read_success;
    /* This is needed if the init_cb is called to read the trailer data */
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    bool trailer_exists;
    void *trailer_arg;
#endif
};

static int
log_read_hdr_walk(struct log *log, struct log_offset *log_offset, const void *dptr,
                  uint16_t len)
{
    struct log_read_hdr_arg *arg;
    int rc;

    arg = log_offset->lo_arg;

    rc = log_read(log, dptr, arg->hdr, 0, LOG_BASE_ENTRY_HDR_SIZE);
    if (rc >= LOG_BASE_ENTRY_HDR_SIZE) {
        arg->read_success = 1;
    }

    if (arg->hdr->ue_flags & LOG_FLAGS_IMG_HASH) {
        rc = log_fill_current_img_hash(arg->hdr);
        if (!rc || rc == SYS_ENOTSUP) {
            arg->read_success = 1;
        }
    }

#if MYNEWT_VAL(LOG_INIT_CB)
    if (log->l_init_cb) {
        /* If the log has an init callback, call it with the header. */
        log->l_init_cb(log, log_offset, dptr, len);
    }
#endif

    /* Abort the walk; only one header needed. */
    return 1;
}

/**
 * Reads the final log entry's header from the specified log.
 *
 * @param log                   The log to read from.
 * @param out_hdr               On success, the last entry header gets written
 *                                  here.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
log_read_last_hdr(struct log *log, struct log_entry_hdr *out_hdr)
{
    struct log_read_hdr_arg arg = {0};
    struct log_offset log_offset = {};

    arg.hdr = out_hdr;
    arg.read_success = 0;

    log_offset.lo_arg = &arg;
    log_offset.lo_ts = -1;
    log_offset.lo_index = 0;
    log_offset.lo_data_len = 0;

    log_walk(log, log_read_hdr_walk, &log_offset);

    if (!arg.read_success) {
        return -1;
    }

    return 0;
}

/*
 * Associate an instantiation of a log with the logging infrastructure
 * If trailer data needs to be read, the trailer callbacks should be
 * registered before calling this function. They are set using
 * log_trailer_cbs_register().
 */
int
log_register(const char *name, struct log *log, const struct log_handler *lh,
             void *arg, uint8_t level)
{
    struct log_entry_hdr hdr;
    int sr;
    int rc;

    assert(!log_written);

    if (level > LOG_LEVEL_MAX) {
        level = LOG_LEVEL_MAX;
    }

    log->l_name = name;
    log->l_log = lh;
    log->l_arg = arg;
    log->l_level = level;
    log->l_append_cb = NULL;
    log->l_max_entry_len = 0;
#if !MYNEWT_VAL(LOG_GLOBAL_IDX)
    log->l_idx = 0;
#endif

    if (!log_registered(log)) {
#if MYNEWT_VAL(LOG_STATS)
        stats_init(STATS_HDR(log->l_stats),
                   STATS_SIZE_INIT_PARMS(log->l_stats, STATS_SIZE_32),
                   STATS_NAME_INIT_PARMS(logs));
        stats_register(log->l_name, STATS_HDR(log->l_stats));
#endif
    }

    /* If this is a persisted log, read the index from its most recent entry.
     * We need to ensure the index of all subseqently written entries is
     * monotonically increasing.
     */
    if (log->l_log->log_type == LOG_TYPE_STORAGE) {
        rc = log_read_last_hdr(log, &hdr);
        if (rc == 0) {
            OS_ENTER_CRITICAL(sr);
#if MYNEWT_VAL(LOG_GLOBAL_IDX)
            if (hdr.ue_index > g_log_info.li_next_index) {
                g_log_info.li_next_index = hdr.ue_index;
            }
#else
            if (hdr.ue_index > log->l_idx) {
                log->l_idx = hdr.ue_index;
            }
#endif
            OS_EXIT_CRITICAL(sr);
        } else {
            LOG_STATS_INC(log, regerr);
        }
    }

    if (!log_registered(log)) {
        STAILQ_INSERT_TAIL(&g_log_list, log, l_next);
    }

    /* Call registered handler now - log structure is set and put on list */
    if (log->l_log->log_registered) {
        rc = log->l_log->log_registered(log);
        if (rc) {
            STAILQ_REMOVE(&g_log_list, log, log, l_next);
            return rc;
        }
    }

    return (0);
}

void
log_set_append_cb(struct log *log, log_append_cb *cb)
{
    log->l_append_cb = cb;
}

uint16_t
log_hdr_len(const struct log_entry_hdr *hdr)
{
    if (hdr->ue_flags & LOG_FLAGS_IMG_HASH) {
        return LOG_BASE_ENTRY_HDR_SIZE + LOG_IMG_HASHLEN;
    }

    return LOG_BASE_ENTRY_HDR_SIZE;
}

void
log_set_rotate_notify_cb(struct log *log, log_notify_rotate_cb *cb)
{
    log->l_rotate_notify_cb = cb;
}

static int
log_chk_type(uint8_t etype)
{
    int rc;

    rc = OS_OK;

    switch(etype) {
        case LOG_ETYPE_STRING:
        case LOG_ETYPE_BINARY:
        case LOG_ETYPE_CBOR:
            break;
        default:
            rc = OS_ERROR;
            break;
    }

    return rc;
}

static int
log_chk_max_entry_len(struct log *log, uint16_t len)
{
    int rc;

    rc = OS_OK;
    if (log->l_max_entry_len != 0) {
        if (len > log->l_max_entry_len) {
            LOG_STATS_INC(log, too_long);
            rc = OS_ENOMEM;
        }
    }

    return rc;
}

static int
log_append_prepare(struct log *log, uint8_t module, uint8_t level,
                   uint8_t etype, struct log_entry_hdr *ue)
{
    int rc;
    int sr;
    struct os_timeval tv;
    uint32_t idx;
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    struct os_mbuf *om = NULL;
#endif

    rc = 0;

    rc = log_chk_type(etype);
    assert(rc == OS_OK);

    if (log->l_name == NULL || log->l_log == NULL) {
        rc = -1;
        goto err;
    }

    if (level > LOG_LEVEL_MAX) {
        level = LOG_LEVEL_MAX;
    }

    if (log->l_log->log_type == LOG_TYPE_STORAGE) {
        /* Remember that a log entry has been persisted since boot. */
        log_written = 1;
    }

    /*
     * If the log message is below what this log instance is
     * configured to accept, then just drop it.
     */
    if (level < log->l_level) {
        rc = -1;
        goto err;
    }

    /* Check if this module has a minimum level. */
    if (level < log_level_get(module)) {
        rc = -1;
        goto err;
    }

    OS_ENTER_CRITICAL(sr);
#if MYNEWT_VAL(LOG_GLOBAL_IDX)
#if MYNEWT_VAL(LOG_SEQUENTIAL_IDX)
    /*
     * Avoid incrementing the global index for streamed logs in
     * order to keep it sequentially increasing for persisted logs.
     */
    if (log->l_log->log_type != LOG_TYPE_STREAM) {
        g_log_info.li_next_index++;
    }
    idx = g_log_info.li_next_index;
#else
    idx = g_log_info.li_next_index++;
#endif
#else
    idx = log->l_idx++;
#endif
    OS_EXIT_CRITICAL(sr);

    /* Try to get UTC Time */
    rc = os_gettimeofday(&tv, NULL);
    if (rc || tv.tv_sec < UTC01_01_2016) {
        ue->ue_ts = os_get_uptime_usec();
    } else {
        ue->ue_ts = tv.tv_sec * 1000000 + tv.tv_usec;
    }

    ue->ue_level = level;
    ue->ue_module = module;
    ue->ue_index = idx;
    ue->ue_etype = etype;
    /* Clear flags before assigning */
    ue->ue_flags = 0;
#if MYNEWT_VAL(LOG_FLAGS_IMAGE_HASH)
    rc = log_fill_current_img_hash(ue);
    if (rc == SYS_ENOTSUP) {
        rc = 0;
    }
#endif

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    if (log->l_th) {
        rc = log_trailer_get_data(log, &om, log->l_tr_arg);
        if (!rc && om) {
            ue->ue_flags |= LOG_FLAGS_TRAILER;
            log->l_tr_om = om;
            rc = 0;
        } else {
            rc = SYS_ENOMEM;
        }
    }
#endif

err:
    return (rc);
}

/**
 * Calls the given log's append callback, if it has one.
 */
static void
log_call_append_cb(struct log *log, uint32_t idx)
{
    /* Qualify this as `volatile` to prevent a race condition.  This prevents
     * the compiler from optimizing this temp variable away.  We copy the
     * original pointer value into this variable, then inspect and use the temp
     * variable.  This allows us to read the original pointer only once,
     * preventing a TOCTTOU race.
     * (This all assumes that function pointer reads and writes are atomic.)
     */
    log_append_cb * volatile cb;

    cb = log->l_append_cb;
    if (cb != NULL) {
        cb(log, idx);
    }
}

int
log_append_typed(struct log *log, uint8_t module, uint8_t level, uint8_t etype,
                 void *data, uint16_t len)
{
    struct log_entry_hdr *hdr;
    int rc;

    LOG_STATS_INC(log, writes);

    rc = log_chk_max_entry_len(log, len);
    if (rc != OS_OK) {
        goto err;
    }

    hdr = (struct log_entry_hdr *)data;
    rc = log_append_prepare(log, module, level, etype, hdr);
    if (rc != 0) {
        LOG_STATS_INC(log, drops);
        goto err;
    }

    rc = log->l_log->log_append(log, data, len + log_hdr_len(hdr));
    if (rc != 0) {
        LOG_STATS_INC(log, errs);
        goto err;
    }

    log_call_append_cb(log, hdr->ue_index);

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif

    return (0);
err:
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    return (rc);
}

int
log_append_body(struct log *log, uint8_t module, uint8_t level, uint8_t etype,
                const void *body, uint16_t body_len)
{
    struct log_entry_hdr hdr;
    int rc;

    LOG_STATS_INC(log, writes);

    rc = log_chk_max_entry_len(log, body_len);
    if (rc != OS_OK) {
        goto err;
    }

    rc = log_append_prepare(log, module, level, etype, &hdr);
    if (rc != 0) {
        LOG_STATS_INC(log, drops);
        goto err;
    }

    rc = log->l_log->log_append_body(log, &hdr, body, body_len);
    if (rc != 0) {
        LOG_STATS_INC(log, errs);
        goto err;
    }

    log_call_append_cb(log, hdr.ue_index);

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    return 0;
err:
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    return rc;
}

int
log_append_mbuf_typed_no_free(struct log *log, uint8_t module, uint8_t level,
                              uint8_t etype, struct os_mbuf **om_ptr)
{
    struct log_entry_hdr *hdr;
    struct os_mbuf *om;
    uint16_t len;
    uint16_t hdr_len;
    int rc;

    /* Remove a layer of indirection for convenience. */
    om = *om_ptr;

    LOG_STATS_INC(log, writes);
    if (!log->l_log->log_append_mbuf) {
        rc = SYS_ENOTSUP;
        goto err;
    }

    hdr = (struct log_entry_hdr *)om->om_data;

    /*
     * We do a pull up twice, once so that the base header is
     * contiguous, so that we read the flags correctly, second
     * time is so that we account for the image hash as well.
     */
    om = os_mbuf_pullup(om, LOG_BASE_ENTRY_HDR_SIZE);
    if (!om) {
        rc = -1;
        goto err;
    }

    hdr_len = log_hdr_len(hdr);

    om = os_mbuf_pullup(om, hdr_len);
    if (!om) {
        rc = -1;
        goto err;
    }

    /*
     * Check that the log body length is less than the maximum entry. This code
     * may appear a bit odd in that it checks that the length is greater than
     * a log entry header length. The reason for this check is to ensure any
     * error handling of this case to be the same as it was before the
     * maximum entry length was checked.
     */
    len = os_mbuf_len(om);
    if (len > hdr_len) {
        rc = log_chk_max_entry_len(log, len - hdr_len);
        if (rc != OS_OK) {
            goto drop;
        }
    }

    rc = log_append_prepare(log, module, level, etype, hdr);
    if (rc != 0) {
        LOG_STATS_INC(log, drops);
        goto drop;
    }

    rc = log->l_log->log_append_mbuf(log, om);
    if (rc != 0) {
        goto err;
    }

    log_call_append_cb(log, hdr->ue_index);

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    *om_ptr = om;

    return 0;

err:
    LOG_STATS_INC(log, errs);
drop:
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    if (om) {
        os_mbuf_free_chain(om);
        *om_ptr = NULL;
    }
    return rc;
}

int
log_append_mbuf_typed(struct log *log, uint8_t module, uint8_t level,
                      uint8_t etype, struct os_mbuf *om)
{
    int rc;

    rc = log_append_mbuf_typed_no_free(log, module, level, etype, &om);
    if (rc != 0) {
        return rc;
    }

    os_mbuf_free_chain(om);

    return 0;
}

int
log_append_mbuf_body_no_free(struct log *log, uint8_t module, uint8_t level,
                             uint8_t etype, struct os_mbuf *om)
{
    struct log_entry_hdr hdr;
    uint16_t len;
    int rc;

    LOG_STATS_INC(log, writes);
    if (!log->l_log->log_append_mbuf_body) {
        rc = SYS_ENOTSUP;
        goto err;
    }

    len = os_mbuf_len(om);
    rc = log_chk_max_entry_len(log, len);
    if (rc != OS_OK) {
        goto drop;
    }

    rc = log_append_prepare(log, module, level, etype, &hdr);
    if (rc != 0) {
        LOG_STATS_INC(log, drops);
        goto drop;
    }

    rc = log->l_log->log_append_mbuf_body(log, &hdr, om);
    if (rc != 0) {
        goto err;
    }

    log_call_append_cb(log, hdr.ue_index);

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif

    return 0;
err:
    LOG_STATS_INC(log, errs);
drop:
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    log_trailer_free(log, log->l_tr_om, log->l_tr_arg);
#endif
    return rc;
}

int
log_append_mbuf_body(struct log *log, uint8_t module, uint8_t level,
                     uint8_t etype, struct os_mbuf *om)
{
    int rc;

    rc = log_append_mbuf_body_no_free(log, module, level, etype, om);
    os_mbuf_free_chain(om);

    return rc;
}

void
log_printf(struct log *log, uint8_t module, uint8_t level,
           const char *msg, ...)
{
    va_list args;
    char buf[LOG_PRINTF_MAX_ENTRY_LEN];
    int len;

    va_start(args, msg);
    len = vsnprintf(buf, LOG_PRINTF_MAX_ENTRY_LEN, msg, args);
    va_end(args);
    if (len >= LOG_PRINTF_MAX_ENTRY_LEN) {
        len = LOG_PRINTF_MAX_ENTRY_LEN-1;
    }

    log_append_body(log, module, level, LOG_ETYPE_STRING, buf, len);
}

int
log_walk(struct log *log, log_walk_func_t walk_func,
         struct log_offset *log_offset)
{
    int rc;

    rc = log->l_log->log_walk(log, walk_func, log_offset);
    if (rc != 0) {
        goto err;
    }

    return (0);
err:
    return (rc);
}

/**
 * Argument passed to `log_walk` to perform a log body walk.  Wraps the
 * original walk argument and the body walk callback in a single object.
 */
struct log_walk_body_arg {
    /** The body walk function to call on each entry. */
    log_walk_body_func_t fn;

    /** The original argument passed to `log_walk`. */
    void *arg;
};

/**
 * Performs a body walk on a single log entry.  This function reads the entry
 * header, subtracts the header length from the total entry length, and
 * forwards the data to the body walk callback.
 */
static int
log_walk_body_fn(struct log *log, struct log_offset *log_offset, const void *dptr,
                 uint16_t len)
{
    struct log_walk_body_arg *lwba;
    struct log_entry_hdr ueh;
    int rc;

    lwba = log_offset->lo_arg;

    /* Read the log entry header.  This gets passed to the body walk
     * callback.
     */
    rc = log_read_hdr(log, dptr, &ueh);
    if (rc != 0) {
        return rc;
    }
    if (log_offset->lo_index <= ueh.ue_index) {
        len -= log_hdr_len(&ueh);

        /* Pass the wrapped callback argument to the body walk function. */
        log_offset->lo_arg = lwba->arg;
        rc = lwba->fn(log, log_offset, &ueh, dptr, len);

        /* Restore the original body walk argument. */
        log_offset->lo_arg = lwba;

        if (rc != 0) {
            return rc;
        }
    }

    return 0;
}

int
log_walk_body(struct log *log, log_walk_body_func_t walk_body_func,
              struct log_offset *log_offset)
{
    struct log_walk_body_arg lwba = {
        .fn = walk_body_func,
        .arg = log_offset->lo_arg,
    };
    int rc;

    log_offset->lo_arg = &lwba;
    rc = log->l_log->log_walk(log, log_walk_body_fn, log_offset);
    log_offset->lo_arg = lwba.arg;

    return rc;
}

int
log_walk_body_section(struct log *log, log_walk_body_func_t walk_body_func,
              struct log_offset *log_offset)
{
    struct log_walk_body_arg lwba = {
        .fn = walk_body_func,
        .arg = log_offset->lo_arg,
    };
    int rc;

    if (!log->l_log->log_walk_sector) {
        rc = SYS_ENOTSUP;
        goto err;
    }

    log_offset->lo_arg = &lwba;

    rc = log->l_log->log_walk_sector(log, log_walk_body_fn, log_offset);
    log_offset->lo_arg = lwba.arg;

err:
    return rc;
}

/**
 * Reads entry length from the specified log.
 *
 * @return                      The number of bytes of entry length; 0 on failure.
 */
uint16_t
log_read_entry_len(struct log *log, const void *dptr)
{
    if (log->l_log->log_read_entry_len) {
        return log->l_log->log_read_entry_len(log, dptr);
    }
    return 0;
}

/**
 * Reads from the specified log.
 *
 * @return                      The number of bytes read; 0 on failure.
 */
int
log_read(struct log *log, const void *dptr, void *buf, uint16_t off,
         uint16_t len)
{
    int rc;

    rc = log->l_log->log_read(log, dptr, buf, off, len);

    return (rc);
}

int
log_read_hdr(struct log *log, const void *dptr, struct log_entry_hdr *hdr)
{
    int bytes_read = 0;

    bytes_read = log_read(log, dptr, hdr, 0, LOG_BASE_ENTRY_HDR_SIZE);
    if (bytes_read != LOG_BASE_ENTRY_HDR_SIZE) {
        return SYS_EIO;
    }

    if (hdr->ue_flags & LOG_FLAGS_IMG_HASH) {
        bytes_read = log_read(log, dptr, hdr->ue_imghash,
                              LOG_BASE_ENTRY_HDR_SIZE, LOG_IMG_HASHLEN);
        if (bytes_read != LOG_IMG_HASHLEN) {
            return SYS_EIO;
        }
    }

    return 0;
}

int
log_read_body(struct log *log, const void *dptr, void *buf, uint16_t off,
              uint16_t len)
{
    int rc;
    struct log_entry_hdr hdr;

    rc = log_read_hdr(log, dptr, &hdr);
    if (rc) {
        return rc;
    }

    return log_read(log, dptr, buf, log_hdr_len(&hdr) + off, len);
}

int
log_read_mbuf(struct log *log, const void *dptr, struct os_mbuf *om, uint16_t off,
              uint16_t len)
{
    int rc;

    if (!om || !log->l_log->log_read_mbuf) {
        return 0;
    }

    rc = log->l_log->log_read_mbuf(log, dptr, om, off, len);

    return (rc);
}

int
log_read_mbuf_body(struct log *log, const void *dptr, struct os_mbuf *om,
                   uint16_t off, uint16_t len)
{
    int rc;
    struct log_entry_hdr hdr;

    rc = log_read_hdr(log, dptr, &hdr);
    if (rc) {
        return rc;
    }

    return log_read_mbuf(log, dptr, om, log_hdr_len(&hdr) + off, len);
}

int
log_flush(struct log *log)
{
    int rc;

#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
    /* Reset trailer data if it exists, if it does not exist
     * this function will return SYS_ENOTSUP. Currently,
     * we do not use the return value.
     */
    log_trailer_reset_data(log, log->l_tr_arg);
#endif

    rc = log->l_log->log_flush(log);
    if (rc != 0) {
        goto err;
    }

    return (0);
err:
    return (rc);
}

#if MYNEWT_VAL(LOG_STORAGE_INFO)
int
log_storage_info(struct log *log, struct log_storage_info *info)
{
    int rc;

    if (!log->l_log->log_storage_info) {
        rc = OS_ENOENT;
        goto err;
    }

    rc = log->l_log->log_storage_info(log, info);
    if (rc != 0) {
        goto err;
    }

    return (0);
err:
    return (rc);
}
#endif

#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
int
log_set_watermark(struct log *log, uint32_t index)
{
#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
    char log_path[CONF_MAX_NAME_LEN];
    char mark_val[10]; /* fits uint32_t + \0 */
#endif
    int rc;

    if (!log->l_log->log_set_watermark) {
        rc = OS_ENOENT;
        goto err;
    }

    rc = log->l_log->log_set_watermark(log, index);
    if (rc != 0) {
        goto err;
    }

#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
    snprintf(log_path, CONF_MAX_NAME_LEN, "log/%s/mark", log->l_name);
    log_path[CONF_MAX_NAME_LEN - 1] = '\0';

    sprintf(mark_val, "%u", (unsigned)index);

    conf_save_one(log_path, mark_val);
#endif

    return (0);
err:
    return (rc);
}
#endif

void
log_set_level(struct log *log, uint8_t level)
{
    assert(log);
    log->l_level = level;
}

uint8_t
log_get_level(const struct log *log)
{
    assert(log);
    return log->l_level;
}

void
log_set_max_entry_len(struct log *log, uint16_t max_entry_len)
{
    assert(log);
    log->l_max_entry_len = max_entry_len;
}

int
log_fill_current_img_hash(struct log_entry_hdr *hdr)
{
#if MYNEWT_VAL(LOG_FLAGS_IMAGE_HASH)
    hdr->ue_flags |= LOG_FLAGS_IMG_HASH;

    /* We have to account for LOG_IMG_HASHLEN bytes of hash */
    return imgr_get_current_hash(hdr->ue_imghash, LOG_IMG_HASHLEN);
#endif
    memset(hdr->ue_imghash, 0, LOG_IMG_HASHLEN);

    return SYS_ENOTSUP;
}

uint32_t
log_get_last_index(struct log *log)
{
#if MYNEWT_VAL(LOG_GLOBAL_IDX)
    return g_log_info.li_next_index;
#else
    return log->l_idx;
#endif
}
