zephyr/subsys/shell/shell_wildcard.c
Jakub Rzeszutko 46a02322ec shell: allow commands to suspend shell thread
It was possible to deadlock the shell when command
suspended shell's thread and next another thread wanted
to print something on the shell.

To avoid that shell releases mutex before entering command
handler. Due to this change some  adapations to shell
internal print functions have been applied.

This change addresses following usecase:
1. A command handler needs to call a (system) function which
communicate results via a callback, and this callback is expected
to print these results. The callback is called by the system from
another thread.
2. To achieve that, the handler needs to pass `struct shell *`
to callbacks, but also some other data specific to callback.
Thus, handles allocates some structure will those fields on
the stack.
3. The handler schedules this callback to be called.
4. As a reference to stack structure is passed to the callback,
the handler can't return immediately (or stack data will go out
of scope and will be overwritten).
5. So, the handler blocks waiting for callback to finish.

Previously, this scenario led to deadlock when the callback
trying or print to shell. With these changes, it just works,
as long as main handler and callback serialize there access
to the shell structure (i.e. when callback prints, the main
handler is blocked waiting for its completion).

Signed-off-by: Jakub Rzeszutko <jakub.rzeszutko@nordicsemi.no>
2019-02-14 13:40:28 +01:00

220 lines
6.5 KiB
C

/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <fnmatch.h>
#include "shell_wildcard.h"
#include "shell_utils.h"
#include "shell_ops.h"
static void subcmd_get(const struct shell_cmd_entry *cmd,
size_t idx, const struct shell_static_entry **entry,
struct shell_static_entry *d_entry)
{
__ASSERT_NO_MSG(entry != NULL);
__ASSERT_NO_MSG(d_entry != NULL);
if (cmd == NULL) {
*entry = NULL;
return;
}
if (cmd->is_dynamic) {
cmd->u.dynamic_get(idx, d_entry);
*entry = (d_entry->syntax != NULL) ? d_entry : NULL;
} else {
*entry = (cmd->u.entry[idx].syntax != NULL) ?
&cmd->u.entry[idx] : NULL;
}
}
static enum shell_wildcard_status command_add(char *buff, u16_t *buff_len,
char const *cmd,
char const *pattern)
{
u16_t cmd_len = shell_strlen(cmd);
char *completion_addr;
u16_t shift;
/* +1 for space */
if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) {
return SHELL_WILDCARD_CMD_MISSING_SPACE;
}
completion_addr = strstr(buff, pattern);
if (!completion_addr) {
return SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
}
shift = shell_strlen(completion_addr);
/* make place for new command: + 1 for space + 1 for EOS */
memmove(completion_addr + cmd_len + 1, completion_addr, shift + 1);
memcpy(completion_addr, cmd, cmd_len);
/* adding space to not brake next command in the buffer */
completion_addr[cmd_len] = ' ';
*buff_len += cmd_len + 1; /* + 1 for space */
return SHELL_WILDCARD_CMD_ADDED;
}
/**
* @internal @brief Function for searching and adding commands to the temporary
* shell buffer matching to wildcard pattern.
*
* Function will search commands tree for commands matching wildcard pattern
* stored in argv[cmd_lvl]. When match is found wildcard pattern will be
* replaced by matching commands. If there is no space in the buffer to add all
* matching commands function will add as many as possible. Next it will
* continue to search for next wildcard pattern and it will try to add matching
* commands.
*
*
* This function is internal to shell module and shall be not called directly.
*
* @param[in/out] shell Pointer to the CLI instance.
* @param[in] cmd Pointer to command which will be processed
* @param[in] pattern Pointer to wildcard pattern.
*
* @retval WILDCARD_CMD_ADDED All matching commands added to the buffer.
* @retval WILDCARD_CMD_ADDED_MISSING_SPACE Not all matching commands added
* because CONFIG_SHELL_CMD_BUFF_SIZE
* is too small.
* @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found.
*/
static enum shell_wildcard_status commands_expand(const struct shell *shell,
const struct shell_cmd_entry *cmd,
const char *pattern)
{
enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
struct shell_static_entry const *p_static_entry = NULL;
struct shell_static_entry static_entry;
size_t cmd_idx = 0;
size_t cnt = 0;
do {
subcmd_get(cmd, cmd_idx++, &p_static_entry, &static_entry);
if (!p_static_entry) {
break;
}
if (fnmatch(pattern, p_static_entry->syntax, 0) == 0) {
ret_val = command_add(shell->ctx->temp_buff,
&shell->ctx->cmd_tmp_buff_len,
p_static_entry->syntax, pattern);
if (ret_val == SHELL_WILDCARD_CMD_MISSING_SPACE) {
shell_internal_fprintf(shell,
SHELL_WARNING,
"Command buffer is too short to"
" expand all commands matching"
" wildcard pattern: %s\n",
pattern);
break;
} else if (ret_val != SHELL_WILDCARD_CMD_ADDED) {
break;
}
cnt++;
}
} while (cmd_idx);
if (cnt > 0) {
shell_pattern_remove(shell->ctx->temp_buff,
&shell->ctx->cmd_tmp_buff_len, pattern);
}
return ret_val;
}
bool shell_wildcard_character_exist(const char *str)
{
u16_t str_len = shell_strlen(str);
for (size_t i = 0; i < str_len; i++) {
if ((str[i] == '?') || (str[i] == '*')) {
return true;
}
}
return false;
}
void shell_wildcard_prepare(const struct shell *shell)
{
/* Wildcard can be correctly handled under following conditions:
* - wildcard command does not have a handler
* - wildcard command is on the deepest commands level
* - other commands on the same level as wildcard command shall also not
* have a handler
*
* Algorithm:
* 1. Command buffer: ctx->cmd_buff is copied to temporary buffer:
* ctx->temp_buff.
* 2. Algorithm goes through command buffer to find handlers and
* subcommands.
* 3. If algorithm will find a wildcard character it switches to
* Temporary buffer.
* 4. In the Temporary buffer command containing wildcard character is
* replaced by matching command(s).
* 5. Algorithm switch back to Command buffer and analyzes next command.
* 6. When all arguments are analyzed from Command buffer, Temporary
* buffer with all expanded commands is copied to Command buffer.
* 7. Deepest found handler is executed and all lower level commands,
* including expanded commands, are passed as arguments.
*/
memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff));
memcpy(shell->ctx->temp_buff,
shell->ctx->cmd_buff,
shell->ctx->cmd_buff_len);
/* Function shell_spaces_trim must be used instead of shell_make_argv.
* At this point it is important to keep temp_buff as one string.
* It will allow to find wildcard commands easily with strstr function.
*/
shell_spaces_trim(shell->ctx->temp_buff);
/* +1 for EOS*/
shell->ctx->cmd_tmp_buff_len = shell_strlen(shell->ctx->temp_buff) + 1;
}
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
const struct shell_cmd_entry *cmd,
const char *pattern)
{
enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND;
if (cmd == NULL) {
return ret_val;
}
if (!shell_wildcard_character_exist(pattern)) {
return ret_val;
}
/* Function will search commands tree for commands matching wildcard
* pattern stored in argv[cmd_lvl]. When match is found wildcard pattern
* will be replaced by matching commands. If there is no space in the
* buffer to add all matching commands function will add as many as
* possible. Next it will continue to search for next wildcard pattern
* and it will try to add matching commands.
*/
ret_val = commands_expand(shell, cmd, pattern);
return ret_val;
}
void shell_wildcard_finalize(const struct shell *shell)
{
memcpy(shell->ctx->cmd_buff,
shell->ctx->temp_buff,
shell->ctx->cmd_tmp_buff_len);
shell->ctx->cmd_buff_len = shell->ctx->cmd_tmp_buff_len;
}