c2cb60f613
Shell history module reworked to use ring buffer for storing commands. Dedicated buffer is used to story all command lineary. History capacity is in bytes not in number of entries, e.g. many short commands can be stored or few long (depending on CONFIG_SHELL_HISTORY_BUFFER). Removed implicit command null termination from shell_history and added it to shell after fetching command line from the history. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
206 lines
5.7 KiB
C
206 lines
5.7 KiB
C
/*
|
|
* Copyright (c) 2018 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <shell/shell_history.h>
|
|
#include <string.h>
|
|
|
|
/*
|
|
* History must store strings (commands) and allow traversing them and adding
|
|
* new string. When new item is added then first it is compared if it is not
|
|
* the same as the last one (then it is not stored). If there is no room in the
|
|
* buffer to store the new item, oldest one is removed until there is a room.
|
|
*
|
|
* Items are allocated and stored in the ring buffer. Items then a linked in
|
|
* the list.
|
|
*
|
|
* Because stored strings must be copied and compared, it is more convenient to
|
|
* store them in the ring buffer in a way that they are not split into two
|
|
* chunks (when ring buffer wraps). To ensure that item is in a single chunk,
|
|
* item includes padding. If continues area for new item cannot be allocated
|
|
* then allocated space is increased by the padding.
|
|
*
|
|
* If item does not fit at the end of the ring buffer padding is added: *
|
|
* +-----------+----------------+-----------------------------------+---------+
|
|
* | header | "history item" | | padding |
|
|
* | padding | | | |
|
|
* +-----------+----------------+-----------------------------------+---------+
|
|
*
|
|
* If item fits in the ring buffer available space then there is no padding:
|
|
* +-----------------+------------+----------------+--------------------------+
|
|
* | | header | "history item" | |
|
|
* | | no padding | | |
|
|
* +-----------------+------------+----------------+--------------------------+
|
|
*/
|
|
struct shell_history_item {
|
|
sys_dnode_t dnode;
|
|
u16_t len;
|
|
u16_t padding;
|
|
char data[0];
|
|
};
|
|
|
|
void shell_history_mode_exit(struct shell_history *history)
|
|
{
|
|
history->current = NULL;
|
|
}
|
|
|
|
bool shell_history_get(struct shell_history *history, bool up,
|
|
u8_t *dst, u16_t *len)
|
|
{
|
|
struct shell_history_item *h_item; /* history item */
|
|
sys_dnode_t *l_item; /* list item */
|
|
|
|
if (sys_dlist_is_empty(&history->list)) {
|
|
*len = 0U;
|
|
return false;
|
|
}
|
|
|
|
if (!up) { /* button down */
|
|
if (history->current == NULL) {
|
|
/* Not in history mode. It is started by up button. */
|
|
*len = 0U;
|
|
|
|
return false;
|
|
}
|
|
|
|
l_item = sys_dlist_peek_prev_no_check(&history->list,
|
|
history->current);
|
|
} else { /* button up */
|
|
l_item = (history->current == NULL) ?
|
|
sys_dlist_peek_head_not_empty(&history->list) :
|
|
sys_dlist_peek_next_no_check(&history->list, history->current);
|
|
|
|
}
|
|
|
|
history->current = l_item;
|
|
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
|
|
|
|
if (l_item) {
|
|
memcpy(dst, h_item->data, h_item->len);
|
|
*len = h_item->len;
|
|
dst[*len] = '\0';
|
|
return true;
|
|
}
|
|
|
|
*len = 0U;
|
|
return false;
|
|
}
|
|
|
|
static void add_to_head(struct shell_history *history,
|
|
struct shell_history_item *item,
|
|
u8_t *src, size_t len, u16_t padding)
|
|
{
|
|
item->len = len;
|
|
item->padding = padding;
|
|
memcpy(item->data, src, len);
|
|
sys_dlist_prepend(&history->list, &item->dnode);
|
|
}
|
|
|
|
/* Returns true if element was removed. */
|
|
static bool remove_from_tail(struct shell_history *history)
|
|
{
|
|
sys_dnode_t *l_item; /* list item */
|
|
struct shell_history_item *h_item;
|
|
u32_t total_len;
|
|
|
|
if (sys_dlist_is_empty(&history->list)) {
|
|
return false;
|
|
}
|
|
|
|
l_item = sys_dlist_peek_tail(&history->list);
|
|
sys_dlist_remove(l_item);
|
|
|
|
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
|
|
|
|
total_len = offsetof(struct shell_history_item, data) +
|
|
h_item->len + h_item->padding;
|
|
ring_buf_get_finish(history->ring_buf, total_len);
|
|
|
|
return true;
|
|
}
|
|
|
|
void shell_history_purge(struct shell_history *history)
|
|
{
|
|
while (remove_from_tail(history)) {
|
|
}
|
|
}
|
|
|
|
void shell_history_put(struct shell_history *history, u8_t *line, size_t len)
|
|
{
|
|
sys_dnode_t *l_item; /* list item */
|
|
struct shell_history_item *h_item;
|
|
u32_t total_len = len + offsetof(struct shell_history_item, data);
|
|
u32_t claim_len;
|
|
u32_t claim2_len;
|
|
u16_t padding = (~total_len + 1) & (sizeof(void *) - 1);
|
|
|
|
/* align to word. */
|
|
total_len += padding;
|
|
|
|
if (total_len > ring_buf_capacity_get(history->ring_buf)) {
|
|
return;
|
|
}
|
|
|
|
shell_history_mode_exit(history);
|
|
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
l_item = sys_dlist_peek_head(&history->list);
|
|
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
|
|
|
|
if (l_item &&
|
|
(h_item->len == len) &&
|
|
(memcmp(h_item->data, line, len) == 0)) {
|
|
/* Same command as before, do not store */
|
|
return;
|
|
}
|
|
|
|
do {
|
|
claim_len = ring_buf_put_claim(history->ring_buf,
|
|
(u8_t **)&h_item, total_len);
|
|
/* second allocation may succeed if we were at the end of the
|
|
* buffer.
|
|
*/
|
|
if (claim_len < total_len) {
|
|
claim2_len =
|
|
ring_buf_put_claim(history->ring_buf,
|
|
(u8_t **)&h_item, total_len);
|
|
if (claim2_len == total_len) {
|
|
ring_buf_put_finish(history->ring_buf,
|
|
claim_len);
|
|
padding += claim_len;
|
|
claim_len = total_len;
|
|
}
|
|
}
|
|
|
|
if (claim_len == total_len) {
|
|
add_to_head(history, h_item, line, len, padding);
|
|
ring_buf_put_finish(history->ring_buf, claim_len);
|
|
break;
|
|
}
|
|
|
|
ring_buf_put_finish(history->ring_buf, 0);
|
|
if (remove_from_tail(history) == false) {
|
|
__ASSERT_NO_MSG(ring_buf_is_empty(history->ring_buf));
|
|
/* if history is empty reset ring buffer. Even when
|
|
* ring buffer is empty, it is possible that available
|
|
* continues memory in worst case equals half of the
|
|
* ring buffer capacity. By reseting ring buffer we
|
|
* ensure that it is capable to provide continues memory
|
|
* of ring buffer capacity length.
|
|
*/
|
|
ring_buf_reset(history->ring_buf);
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
void shell_history_init(struct shell_history *history)
|
|
{
|
|
sys_dlist_init(&history->list);
|
|
history->current = NULL;
|
|
}
|