blob: c432beea1229fb2b05e433123de87a389ef1ba9c [file] [log] [blame]
/*
* 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_CLI)
#include "shell/shell.h"
#endif
struct log_info g_log_info;
static STAILQ_HEAD(, log) g_log_list = STAILQ_HEAD_INITIALIZER(g_log_list);
static const char *g_log_module_list[ MYNEWT_VAL(LOG_MAX_USER_MODULES) ];
static uint8_t log_written;
#if MYNEWT_VAL(LOG_CLI)
int shell_log_dump_all_cmd(int, char **);
struct shell_cmd g_shell_log_cmd = {
.sc_cmd = "log",
.sc_cmd_func = shell_log_dump_all_cmd
};
#if MYNEWT_VAL(LOG_FCB_SLOT1)
int shell_log_slot1_cmd(int, char **);
struct shell_cmd g_shell_slot1_cmd = {
.sc_cmd = "slot1",
.sc_cmd_func = shell_log_slot1_cmd,
};
#endif
#if MYNEWT_VAL(LOG_STORAGE_INFO)
int shell_log_storage_cmd(int, char **);
struct shell_cmd g_shell_storage_cmd = {
.sc_cmd = "log-storage",
.sc_cmd_func = shell_log_storage_cmd,
};
#endif
#endif
#if MYNEWT_VAL(LOG_STATS)
STATS_NAME_START(logs)
STATS_NAME(logs, writes)
STATS_NAME(logs, drops)
STATS_NAME(logs, errs)
STATS_NAME(logs, lost)
STATS_NAME_END(logs)
#endif
#if MYNEWT_VAL(LOG_STORAGE_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
void
log_init(void)
{
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
(void)rc;
memset(g_log_module_list, 0, sizeof(g_log_module_list));
log_written = 0;
STAILQ_INIT(&g_log_list);
g_log_info.li_version = MYNEWT_VAL(LOG_VERSION);
g_log_info.li_next_index = 0;
#if MYNEWT_VAL(LOG_CLI)
shell_cmd_register(&g_shell_log_cmd);
#if MYNEWT_VAL(LOG_FCB_SLOT1)
shell_cmd_register(&g_shell_slot1_cmd);
#endif
#if MYNEWT_VAL(LOG_STORAGE_INFO)
shell_cmd_register(&g_shell_storage_cmd);
#endif
#endif
#if MYNEWT_VAL(LOG_NEWTMGR)
rc = log_nmgr_register_group();
SYSINIT_PANIC_ASSERT(rc == 0);
#endif
#if MYNEWT_VAL(LOG_CONSOLE)
log_console_init();
#endif
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
rc = conf_register(&log_conf);
SYSINIT_PANIC_ASSERT(rc == 0);
#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);
}
uint8_t
log_module_register(uint8_t id, const char *name)
{
uint8_t idx;
if (id == 0) {
/* Find free idx */
for (idx = 0;
idx < MYNEWT_VAL(LOG_MAX_USER_MODULES) && g_log_module_list[idx];
idx++) {
}
if (idx == MYNEWT_VAL(LOG_MAX_USER_MODULES)) {
/* No free idx */
return 0;
}
} else {
if ((id < LOG_MODULE_PERUSER) ||
(id >= LOG_MODULE_PERUSER + MYNEWT_VAL(LOG_MAX_USER_MODULES))) {
/* Invalid id */
return 0;
}
idx = id - LOG_MODULE_PERUSER;
}
if (g_log_module_list[idx]) {
/* Already registered with selected id */
return 0;
}
g_log_module_list[idx] = name;
return idx + LOG_MODULE_PERUSER;
}
const char *
log_module_get_name(uint8_t module)
{
if (module < LOG_MODULE_PERUSER) {
switch (module) {
case LOG_MODULE_DEFAULT:
return "DEFAULT";
case LOG_MODULE_OS:
return "OS";
case LOG_MODULE_NEWTMGR:
return "NEWTMGR";
case LOG_MODULE_NIMBLE_CTLR:
return "NIMBLE_CTLR";
case LOG_MODULE_NIMBLE_HOST:
return "NIMBLE_HOST";
case LOG_MODULE_NFFS:
return "NFFS";
case LOG_MODULE_REBOOT:
return "REBOOT";
case LOG_MODULE_IOTIVITY:
return "IOTIVITY";
case LOG_MODULE_TEST:
return "TEST";
}
} else if (module - LOG_MODULE_PERUSER < MYNEWT_VAL(LOG_MAX_USER_MODULES)) {
return g_log_module_list[module - LOG_MODULE_PERUSER];
}
return NULL;
}
/**
* 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;
do {
log = log_list_get_next(log);
if (strcmp(log->l_name, name) == 0) {
break;
}
} while (log != NULL);
return log;
}
struct log_read_hdr_arg {
struct log_entry_hdr *hdr;
int read_success;
};
static int
log_read_hdr_walk(struct log *log, struct log_offset *log_offset, 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, sizeof *arg->hdr);
if (rc >= sizeof *arg->hdr) {
arg->read_success = 1;
}
/* 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;
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
*/
int
log_register(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);
log->l_name = name;
log->l_log = lh;
log->l_arg = arg;
log->l_level = level;
log->l_append_cb = NULL;
if (!log_registered(log)) {
STAILQ_INSERT_TAIL(&g_log_list, log, l_next);
#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
}
/* Call registered handler now - log structure is set and put on list */
if (log->l_log->log_registered) {
log->l_log->log_registered(log);
}
/* 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 (hdr.ue_index >= g_log_info.li_next_index) {
g_log_info.li_next_index = hdr.ue_index + 1;
}
OS_EXIT_CRITICAL(sr);
}
}
return (0);
}
void
log_set_append_cb(struct log *log, log_append_cb *cb)
{
log->l_append_cb = cb;
}
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;
rc = 0;
if (log->l_name == NULL || log->l_log == NULL) {
rc = -1;
goto err;
}
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);
idx = g_log_info.li_next_index++;
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;
#if MYNEWT_VAL(LOG_VERSION) > 2
ue->ue_etype = etype;
#else
assert(etype == LOG_ETYPE_STRING);
#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);
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_ENTRY_HDR_SIZE);
if (rc != 0) {
LOG_STATS_INC(log, errs);
goto err;
}
log_call_append_cb(log, hdr->ue_index);
return (0);
err:
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_append_prepare(log, module, level, etype, &hdr);
if (rc != 0) {
LOG_STATS_INC(log, drops);
return rc;
}
rc = log->l_log->log_append_body(log, &hdr, body, body_len);
if (rc != 0) {
LOG_STATS_INC(log, errs);
return rc;
}
log_call_append_cb(log, hdr.ue_index);
return 0;
}
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;
int rc;
/* Remove a loyer of indirection for convenience. */
om = *om_ptr;
LOG_STATS_INC(log, writes);
if (!log->l_log->log_append_mbuf) {
rc = SYS_ENOTSUP;
goto err;
}
om = os_mbuf_pullup(om, sizeof(struct log_entry_hdr));
if (!om) {
rc = -1;
goto err;
}
hdr = (struct log_entry_hdr *)om->om_data;
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);
*om_ptr = om;
return 0;
err:
LOG_STATS_INC(log, errs);
drop:
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;
int rc;
LOG_STATS_INC(log, writes);
if (!log->l_log->log_append_mbuf_body) {
rc = SYS_ENOTSUP;
goto err;
}
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);
return 0;
err:
LOG_STATS_INC(log, errs);
drop:
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, 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 -= sizeof 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;
}
/**
* Reads from the specified log.
*
* @return The number of bytes read; 0 on failure.
*/
int
log_read(struct log *log, 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, void *dptr, struct log_entry_hdr *hdr)
{
int bytes_read;
bytes_read = log_read(log, dptr, hdr, 0, LOG_ENTRY_HDR_SIZE);
if (bytes_read != LOG_ENTRY_HDR_SIZE) {
return SYS_EIO;
}
return 0;
}
int
log_read_body(struct log *log, void *dptr, void *buf, uint16_t off,
uint16_t len)
{
return log_read(log, dptr, buf, LOG_ENTRY_HDR_SIZE + off, len);
}
int
log_read_mbuf(struct log *log, 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, void *dptr, struct os_mbuf *om,
uint16_t off, uint16_t len)
{
return log_read_mbuf(log, dptr, om, LOG_ENTRY_HDR_SIZE + off, len);
}
int
log_flush(struct log *log)
{
int rc;
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)
{
char log_path[CONF_MAX_NAME_LEN];
char mark_val[10]; /* fits uint32_t + \0 */
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;
}
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);
return (0);
err:
return (rc);
}
#endif