You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by je...@apache.org on 2019/12/05 12:36:21 UTC
[mynewt-core] 04/06: sys/console: Add log-back history provider
This is an automated email from the ASF dual-hosted git repository.
jerzy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
commit dadac27a303131673ee8b738beaefbb6b820771d
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Wed Nov 13 15:56:53 2019 +0100
sys/console: Add log-back history provider
This adds code that can store console history in system
log.
---
sys/console/full/{ => history_log}/pkg.yml | 16 +-
sys/console/full/history_log/src/history_log.c | 358 +++++++++++++++++++++
.../full/{pkg.yml => history_log/syscfg.yml} | 40 +--
sys/console/full/pkg.yml | 2 +
sys/console/full/syscfg.yml | 1 +
5 files changed, 387 insertions(+), 30 deletions(-)
diff --git a/sys/console/full/pkg.yml b/sys/console/full/history_log/pkg.yml
similarity index 72%
copy from sys/console/full/pkg.yml
copy to sys/console/full/history_log/pkg.yml
index a035e28..0b4f904 100644
--- a/sys/console/full/pkg.yml
+++ b/sys/console/full/history_log/pkg.yml
@@ -6,7 +6,7 @@
# 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,
@@ -17,8 +17,8 @@
# under the License.
#
-pkg.name: sys/console/full
-pkg.description: Text-based IO interface.
+pkg.name: sys/console/full/history_log
+pkg.description: Log-backed console history.
pkg.author: "Apache Mynewt <de...@mynewt.apache.org>"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:
@@ -26,13 +26,7 @@ pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/hw/hal"
- "@apache-mynewt-core/kernel/os"
-pkg.deps.CONSOLE_UART:
- - "@apache-mynewt-core/hw/drivers/uart"
-pkg.deps.CONSOLE_RTT:
- - "@apache-mynewt-core/hw/drivers/rtt"
-pkg.deps.'CONSOLE_HISTORY == "ram"':
- - "@apache-mynewt-core/sys/console/full/history_ram"
-pkg.apis: console
+ - "@apache-mynewt-core/sys/console/full"
pkg.init:
- console_pkg_init: 'MYNEWT_VAL(CONSOLE_SYSINIT_STAGE)'
+ console_history_pkg_init: 'MYNEWT_VAL(CONSOLE_HISTORY_LOG_SYSINIT_STAGE)'
diff --git a/sys/console/full/history_log/src/history_log.c b/sys/console/full/history_log/src/history_log.c
new file mode 100644
index 0000000..aceecaf
--- /dev/null
+++ b/sys/console/full/history_log/src/history_log.c
@@ -0,0 +1,358 @@
+/*
+ * 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 <stdint.h>
+#include <ctype.h>
+#include <os/mynewt.h>
+#if MYNEWT_VAL(LOG_FCB)
+#include <fcb/fcb.h>
+#endif
+#if MYNEWT_VAL(LOG_FCB2)
+#include <fcb/fcb2.h>
+#endif
+#include <log/log.h>
+#include <console/history.h>
+
+#define HISTORY_CACHE_SIZE MYNEWT_VAL(CONSOLE_HISTORY_LOG_CACHE_SIZE)
+
+#if MYNEWT_VAL(LOG_FCB2) && defined(FLASH_AREA_CONSOLE_HISTORY) && !defined(MYNEWT_VAL_CONSOLE_HISTORY_LOG_NAME)
+static struct fcb2 history_fcb;
+static struct log history_fcb_log;
+#endif
+static struct log *history_log;
+
+/*
+ * Circular buffer for commands.
+ * Lines are separated by '\0'.
+ * history_ptr points to place where new command will be stored. It always
+ * points to '\0'.
+ * history_cache[history_ptr] == 0
+ * history_cache[(history_ptr - 1) MOD CONSOLE_HISTORY_LOG_CACHE_SIZE] == 0
+ *
+ * COM1.COM2 ARG.COM3 -t...COMn.COMn-1 ARG.
+ * ^--- history_ptr
+ */
+static char history_cache[HISTORY_CACHE_SIZE];
+
+static size_t history_ptr;
+/* Helper macros to move pointer in circular buffer */
+#define PREV_PTR(p) ((p > history_cache) ? p - 1 : (history_cache + HISTORY_CACHE_SIZE - 1))
+#define NEXT_PTR(p) ((p < history_cache + HISTORY_CACHE_SIZE - 1) ? p + 1 : history_cache)
+#define PTR_ADD(p, a) (((p) + (a) < history_cache + HISTORY_CACHE_SIZE) ? (p) + (a) : (p) + (a) - HISTORY_CACHE_SIZE)
+#define PTR_SUB(p, a) (((p) - (a) >= history_cache) ? (p) - (a) : (p) - (a) + HISTORY_CACHE_SIZE)
+
+/*
+ * Given p that points to null terminator ending string stored in
+ * history change circular buffer, returns pointer to null terminator
+ * that is placed before string.
+ */
+static const char *
+move_back(const char *p)
+{
+ if (p == NULL) {
+ return NULL;
+ }
+
+ p = PREV_PTR(p);
+ /* If *p now is 0, function was called on oldest element in history */
+ if (*p == '\0') {
+ return NULL;
+ }
+ /* Move to the beginning and beyond (just one char) so p points to '\0' */
+ for (; *p; p = PREV_PTR(p)) ;
+
+ return p;
+}
+
+/*
+ * Given p that points to null terminator before string stored in
+ * history change circular buffer, returns pointer to null terminator
+ * that is just after string.
+ */
+static const char *
+move_forward(const char *p)
+{
+ if (p == NULL) {
+ return NULL;
+ }
+
+ p = NEXT_PTR(p);
+ if (*p == '\0') {
+ return NULL;
+ }
+
+ for (; *p; p = NEXT_PTR(p)) ;
+
+ /*
+ * If after moving forward to valid null terminator, next character is
+ * also 0, search found head of circular buffer. Just return NULL
+ * in this case meaning that there is nothing to retrieve.
+ */
+ if (*(NEXT_PTR(p)) == '\0') {
+ return NULL;
+ }
+ return p;
+}
+
+/*
+ * Helper to move in both directions.
+ */
+static const char *
+move(const char *p, history_find_type_t search_type)
+{
+ if (search_type & HFT_NEXT) {
+ return move_forward(p);
+ } else {
+ return move_back(p);
+ }
+}
+
+static history_handle_t
+console_history_add_to_cache(const char *line)
+{
+ char *cache_end = history_cache + history_ptr;
+ /* Let p1 point to last null-terminator */
+ char *p1 = PREV_PTR(cache_end);
+ int len;
+ const char *p2;
+ int entry_num = 0;
+ int found_at = -1;
+ history_handle_t result;
+
+ if (line == NULL) {
+ return SYS_EINVAL;
+ }
+
+ /* Trim from spaces */
+ while (isspace(*line)) {
+ line++;
+ };
+
+ len = strlen(line);
+ if (len == 0) {
+ return SYS_EINVAL;
+ }
+
+ /*
+ * Trim trailing spaces. It does not touch input buffer, it just
+ * corrects len variable.
+ */
+ while (isspace(line[len - 1])) {
+ len--;
+ }
+
+ assert(*cache_end == 0);
+ /* p1 should point to string terminator */
+ assert(*p1 == 0);
+ /* Now p1 point to last character of most recent history line */
+ p1 = PREV_PTR(p1);
+
+ while (*p1) {
+ /* Compare entry in cache with line starting from the end */
+ for (p2 = line + len - 1; p2 >= line && *p1 == *p2; --p2, p1 = PREV_PTR(p1)) ;
+ if (p2 < line && *p1 == '\0') {
+ /* Line was in history cache already */
+ if (entry_num == 0) {
+ /* Last entry matched no need to do anything */
+ return SYS_EALREADY;
+ }
+ found_at = entry_num;
+ break;
+ }
+ /* Line did no match entry_num line from cache, go to the start of
+ * entry */
+ while (*p1) {
+ p1 = PREV_PTR(p1);
+ }
+ /* Skip null terminator of previous entry */
+ p1 = PREV_PTR(p1);
+ entry_num++;
+ }
+
+ if (found_at < 0) {
+ /* p1 will be used to store new line in cache */
+ p1 = cache_end;
+ } else {
+ /*
+ * Line was in the cache, rotate old data.
+ * This will overwrite old copy of command.
+ * Line will be added a new.
+ */
+ p1 = NEXT_PTR(p1);
+ p2 = PTR_ADD(p1, len + 1);
+ while (p2 != cache_end) {
+ *p1 = *p2;
+ p1 = NEXT_PTR(p1);
+ p2 = NEXT_PTR(p2);
+ }
+ }
+ /* Copy current line to the end of cache (including null terminator) */
+ p2 = line;
+ /* Store result sine p1 will be modified */
+ result = (history_handle_t)PREV_PTR(p1);
+
+ /* Copy line to history */
+ for (; len > 0; --len) {
+ *p1 = *p2++;
+ p1 = NEXT_PTR(p1);
+ }
+ *p1 = '\0';
+ p1 = NEXT_PTR(p1);
+
+ /* New head */
+ history_ptr = p1 - history_cache;
+
+ /*
+ * History pointer should point to '\0', if it is not destroy oldest
+ * partial entry.
+ */
+ while (*p1) {
+ *p1 = '\0';
+ p1 = NEXT_PTR(p1);
+ }
+
+ return result;
+}
+
+/*
+ * Function will be called from log_walk and will add history lines.
+ */
+static int
+history_cache_from_log(struct log *log, struct log_offset *log_offset,
+ const struct log_entry_hdr *hdr,
+ const void *dptr, uint16_t len)
+{
+ char line[MYNEWT_VAL_CONSOLE_MAX_INPUT_LEN];
+
+ if (len >= MYNEWT_VAL_CONSOLE_MAX_INPUT_LEN) {
+ len = MYNEWT_VAL_CONSOLE_MAX_INPUT_LEN - 1;
+ }
+ if (hdr->ue_module == MYNEWT_VAL(CONSOLE_HISTORY_LOG_MODULE)) {
+ log_read_body(log, dptr, line, 0, len);
+ line[len] = '\0';
+ (void)console_history_add_to_cache(line);
+ }
+
+ return 0;
+}
+
+history_handle_t
+console_history_add(const char *line)
+{
+ history_handle_t added_line;
+
+ added_line = console_history_add_to_cache(line);
+
+ if (added_line > 0 && history_log) {
+ log_printf(history_log, MYNEWT_VAL(CONSOLE_HISTORY_LOG_MODULE),
+ LOG_LEVEL_MAX, line);
+ }
+ return added_line;
+}
+
+int
+console_history_get(history_handle_t handle, size_t offset, char *buf,
+ size_t buf_size)
+{
+ const char *p1 = (const char *)handle;
+ size_t copied;
+
+ if (p1 == 0 || p1 < history_cache ||
+ p1 >= history_cache + HISTORY_CACHE_SIZE ||
+ *p1 != '\0') {
+ return SYS_EINVAL;
+ }
+
+ p1 = NEXT_PTR(p1);
+ for (; offset && *p1; --offset, p1 = NEXT_PTR(p1)) ;
+ if (offset > 0) {
+ return 0;
+ }
+ for (copied = 0; buf_size && *p1 != '\0'; --buf_size, ++copied) {
+ *buf++ = *p1;
+ p1 = NEXT_PTR(p1);
+ }
+ return copied;
+}
+
+history_handle_t
+console_history_find(history_handle_t start, history_find_type_t search_type,
+ void *arg)
+{
+ const char *head = history_cache + history_ptr;
+ const char *p1 = (const char *)start;
+ const char *p2;
+ const char *lp;
+ const char *pattern_limit;
+ int num;
+ history_handle_t result = 0;
+
+ if (p1 == 0) {
+ p1 = PREV_PTR(head);
+ }
+ switch (search_type) {
+ case HFT_PREV:
+ case HFT_NEXT:
+ num = arg ? *(int *)arg : 1;
+ for (; num && p1 != NULL; --num) {
+ p1 = move(p1, search_type);
+ }
+ result = (history_handle_t)p1;
+ break;
+ case HFT_MATCH_PREV:
+ case HFT_MATCH_NEXT:
+ pattern_limit = (const char *)arg + strlen((const char *)arg);
+ for (lp = move(p1, search_type); lp != NULL; lp = move(lp, search_type)) {
+ p2 = arg;
+ p1 = NEXT_PTR(lp);
+ for (; p2 < pattern_limit && *p1 == *p2; ++p2, p1 = NEXT_PTR(p1) ) ;
+ if (p2 == pattern_limit) {
+ result = (history_handle_t)lp;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+int
+console_history_pkg_init(void)
+{
+ struct log_offset off = { 0 };
+
+#if (MYNEWT_VAL(LOG_FCB) || MYNEWT_VAL(LOG_FCB2)) && defined(MYNEWT_VAL_CONSOLE_HISTORY_LOG_NAME)
+ history_log = log_find(MYNEWT_VAL(CONSOLE_HISTORY_LOG_NAME));
+#elif MYNEWT_VAL(LOG_FCB2) && defined(FLASH_AREA_CONSOLE_HISTORY)
+ /* If there is dedicated flash area for shell history and FCB2 is enabled */
+ fcb2_init_flash_area(&history_fcb, FLASH_AREA_CONSOLE_HISTORY, 0x12C9985, 1);
+ if (log_register("con_hist", &history_fcb_log, &log_fcb_handler,
+ &history_fcb, 0) == 0) {
+ history_log = &history_fcb_log;
+ }
+#endif
+ if (history_log) {
+ log_module_register(MYNEWT_VAL(CONSOLE_HISTORY_LOG_MODULE), "CON-HIST");
+ log_walk_body(history_log, history_cache_from_log, &off);
+ }
+
+ return 0;
+}
diff --git a/sys/console/full/pkg.yml b/sys/console/full/history_log/syscfg.yml
similarity index 52%
copy from sys/console/full/pkg.yml
copy to sys/console/full/history_log/syscfg.yml
index a035e28..4089b2b 100644
--- a/sys/console/full/pkg.yml
+++ b/sys/console/full/history_log/syscfg.yml
@@ -6,7 +6,7 @@
# 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,
@@ -17,22 +17,24 @@
# under the License.
#
-pkg.name: sys/console/full
-pkg.description: Text-based IO interface.
-pkg.author: "Apache Mynewt <de...@mynewt.apache.org>"
-pkg.homepage: "http://mynewt.apache.org/"
-pkg.keywords:
-
-pkg.deps:
- - "@apache-mynewt-core/hw/hal"
- - "@apache-mynewt-core/kernel/os"
-pkg.deps.CONSOLE_UART:
- - "@apache-mynewt-core/hw/drivers/uart"
-pkg.deps.CONSOLE_RTT:
- - "@apache-mynewt-core/hw/drivers/rtt"
-pkg.deps.'CONSOLE_HISTORY == "ram"':
- - "@apache-mynewt-core/sys/console/full/history_ram"
-pkg.apis: console
+syscfg.defs:
+ CONSOLE_HISTORY_LOG_CACHE_SIZE:
+ description: >
+ Number of RAM bytes used for caching history.
+ value: 512
+ CONSOLE_HISTORY_LOG_NAME:
+ description: >
+ Log name to use for storing console history.
+ When there is no flash area that can be used for storing
+ console history, any existing log can be utilized to
+ store console history.
+ value:
+ CONSOLE_HISTORY_LOG_MODULE:
+ description: >
+ Numeric module ID to use for console history log messages.
+ value: 9
-pkg.init:
- console_pkg_init: 'MYNEWT_VAL(CONSOLE_SYSINIT_STAGE)'
+ CONSOLE_HISTORY_LOG_SYSINIT_STAGE:
+ description: >
+ Sysinit stage for the history log.
+ value: 1000
diff --git a/sys/console/full/pkg.yml b/sys/console/full/pkg.yml
index a035e28..2ef2a95 100644
--- a/sys/console/full/pkg.yml
+++ b/sys/console/full/pkg.yml
@@ -32,6 +32,8 @@ pkg.deps.CONSOLE_RTT:
- "@apache-mynewt-core/hw/drivers/rtt"
pkg.deps.'CONSOLE_HISTORY == "ram"':
- "@apache-mynewt-core/sys/console/full/history_ram"
+pkg.deps.'CONSOLE_HISTORY == "log"':
+ - "@apache-mynewt-core/sys/console/full/history_log"
pkg.apis: console
pkg.init:
diff --git a/sys/console/full/syscfg.yml b/sys/console/full/syscfg.yml
index 0aebad7..7163884 100644
--- a/sys/console/full/syscfg.yml
+++ b/sys/console/full/syscfg.yml
@@ -48,6 +48,7 @@ syscfg.defs:
choices:
- none # no history support
- ram # history kept in ram for current session only
+ - log # history kept in log
CONSOLE_MAX_PROMPT_LEN:
description: 'Maximum number of characters for prompt'
value: 16