zephyr/subsys/shell/shell.c

573 lines
11 KiB
C

/*
* Copyright (c) 2015 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Console handler implementation of shell.h API
*/
#include <zephyr.h>
#include <stdio.h>
#include <string.h>
#include <console/console.h>
#include <misc/printk.h>
#include <misc/util.h>
#ifdef CONFIG_UART_CONSOLE
#include <console/uart_console.h>
#endif
#ifdef CONFIG_TELNET_CONSOLE
#include <console/telnet_console.h>
#endif
#include <shell/shell.h>
#define ARGC_MAX 10
#define COMMAND_MAX_LEN 50
#define MODULE_NAME_MAX_LEN 20
/* additional chars are " >" (include '\0' )*/
#define PROMPT_SUFFIX 3
#define PROMPT_MAX_LEN (MODULE_NAME_MAX_LEN + PROMPT_SUFFIX)
/* command table is located in the dedicated memory segment (.shell_) */
extern struct shell_module __shell_cmd_start[];
extern struct shell_module __shell_cmd_end[];
#define NUM_OF_SHELL_ENTITIES (__shell_cmd_end - __shell_cmd_start)
static const char *prompt;
static char default_module_prompt[PROMPT_MAX_LEN];
static struct shell_module *default_module;
#define STACKSIZE CONFIG_CONSOLE_SHELL_STACKSIZE
static K_THREAD_STACK_DEFINE(stack, STACKSIZE);
static struct k_thread shell_thread;
#define MAX_CMD_QUEUED CONFIG_CONSOLE_SHELL_MAX_CMD_QUEUED
static struct console_input buf[MAX_CMD_QUEUED];
static struct k_fifo avail_queue;
static struct k_fifo cmds_queue;
static shell_cmd_function_t app_cmd_handler;
static shell_prompt_function_t app_prompt_handler;
static const char *get_prompt(void)
{
if (app_prompt_handler) {
const char *str;
str = app_prompt_handler();
if (str) {
return str;
}
}
if (default_module) {
if (default_module->prompt) {
const char *ret;
ret = default_module->prompt();
if (ret) {
return ret;
}
}
return default_module_prompt;
}
return prompt;
}
static void line_queue_init(void)
{
int i;
for (i = 0; i < MAX_CMD_QUEUED; i++) {
k_fifo_put(&avail_queue, &buf[i]);
}
}
static size_t line2argv(char *str, char *argv[], size_t size)
{
size_t argc = 0;
if (!strlen(str)) {
return 0;
}
while (*str && *str == ' ') {
str++;
}
if (!*str) {
return 0;
}
argv[argc++] = str;
while ((str = strchr(str, ' '))) {
*str++ = '\0';
while (*str && *str == ' ') {
str++;
}
if (!*str) {
break;
}
argv[argc++] = str;
if (argc == size) {
printk("Too many parameters (max %zu)\n", size - 1);
return 0;
}
}
/* keep it POSIX style where argv[argc] is required to be NULL */
argv[argc] = NULL;
return argc;
}
static struct shell_module *get_destination_module(const char *module_str)
{
int i;
for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) {
if (!strncmp(module_str,
__shell_cmd_start[i].module_name,
MODULE_NAME_MAX_LEN)) {
return &__shell_cmd_start[i];
}
}
return NULL;
}
static int show_cmd_help(const struct shell_cmd *cmd, bool full)
{
printk("Usage: %s %s\n", cmd->cmd_name, cmd->help ? cmd->help : "");
if (full && cmd->desc) {
printk("%s\n", cmd->desc);
}
return 0;
}
static void print_module_commands(struct shell_module *module)
{
int i;
printk("help\n");
for (i = 0; module->commands[i].cmd_name; i++) {
printk("%-28s %s\n",
module->commands[i].cmd_name,
module->commands[i].help ?
module->commands[i].help : "");
}
}
static const struct shell_cmd *get_cmd(const struct shell_cmd cmds[],
const char *cmd_str)
{
int i;
for (i = 0; cmds[i].cmd_name; i++) {
if (!strcmp(cmd_str, cmds[i].cmd_name)) {
return &cmds[i];
}
}
return NULL;
}
static const struct shell_cmd *get_mod_cmd(struct shell_module *module,
const char *cmd_str)
{
return get_cmd(module->commands, cmd_str);
}
static int cmd_help(int argc, char *argv[])
{
struct shell_module *module = default_module;
/* help per command */
if (argc > 1) {
const struct shell_cmd *cmd;
const char *cmd_str;
module = get_destination_module(argv[1]);
if (module) {
if (argc == 2) {
goto module_help;
}
cmd_str = argv[2];
} else {
cmd_str = argv[1];
module = default_module;
}
if (!module) {
printk("No help found for '%s'\n", cmd_str);
return -EINVAL;
}
cmd = get_mod_cmd(module, cmd_str);
if (cmd) {
return show_cmd_help(cmd, true);
} else {
printk("Unknown command '%s'\n", cmd_str);
return -EINVAL;
}
}
module_help:
/* help per module */
if (module) {
print_module_commands(module);
printk("\nEnter 'exit' to leave current module.\n");
} else { /* help for all entities */
int i;
printk("Available modules:\n");
for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) {
printk("%s\n", __shell_cmd_start[i].module_name);
}
printk("\nTo select a module, enter 'select <module name>'.\n");
}
return 0;
}
static int set_default_module(const char *name)
{
struct shell_module *module;
if (strlen(name) > MODULE_NAME_MAX_LEN) {
printk("Module name %s is too long, default is not changed\n",
name);
return -EINVAL;
}
module = get_destination_module(name);
if (!module) {
printk("Illegal module %s, default is not changed\n", name);
return -EINVAL;
}
default_module = module;
strncpy(default_module_prompt, name, MODULE_NAME_MAX_LEN);
strcat(default_module_prompt, "> ");
return 0;
}
static int cmd_select(int argc, char *argv[])
{
if (argc == 1) {
default_module = NULL;
return 0;
}
return set_default_module(argv[1]);
}
static int cmd_exit(int argc, char *argv[])
{
if (argc == 1) {
default_module = NULL;
}
return 0;
}
static const struct shell_cmd *get_internal(const char *command)
{
static const struct shell_cmd internal_commands[] = {
{ "help", cmd_help, "[command]" },
{ "select", cmd_select, "[module]" },
{ "exit", cmd_exit, NULL },
{ NULL },
};
return get_cmd(internal_commands, command);
}
int shell_exec(char *line)
{
char *argv[ARGC_MAX + 1], **argv_start = argv;
const struct shell_cmd *cmd;
int argc, err;
argc = line2argv(line, argv, ARRAY_SIZE(argv));
if (!argc) {
return -EINVAL;
}
cmd = get_internal(argv[0]);
if (cmd) {
goto done;
}
if (argc == 1 && !default_module) {
printk("No module selected. Use 'select' or 'help'.\n");
return -EINVAL;
}
if (default_module) {
cmd = get_mod_cmd(default_module, argv[0]);
}
if (!cmd && argc > 1) {
struct shell_module *module;
module = get_destination_module(argv[0]);
if (module) {
cmd = get_mod_cmd(module, argv[1]);
if (cmd) {
argc--;
argv_start++;
}
}
}
if (!cmd) {
if (app_cmd_handler) {
return app_cmd_handler(argc, argv);
}
printk("Unrecognized command: %s\n", argv[0]);
printk("Type 'help' for list of available commands\n");
return -EINVAL;
}
done:
err = cmd->cb(argc, argv_start);
if (err < 0) {
show_cmd_help(cmd, false);
}
return err;
}
static void shell(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
while (1) {
struct console_input *cmd;
printk("%s", get_prompt());
cmd = k_fifo_get(&cmds_queue, K_FOREVER);
shell_exec(cmd->line);
k_fifo_put(&avail_queue, cmd);
}
}
static struct shell_module *get_completion_module(char *str,
char **command_prefix)
{
char dest_str[MODULE_NAME_MAX_LEN];
struct shell_module *dest;
char *start;
/* remove ' ' at the beginning of the line */
while (*str && *str == ' ') {
str++;
}
if (!*str) {
return NULL;
}
start = str;
if (default_module) {
dest = default_module;
/* caller function already checks str len and put '\0' */
*command_prefix = str;
} else {
dest = NULL;
}
/*
* In case of a default module: only one parameter is possible.
* Otherwise, only two parameters are possibles.
*/
str = strchr(str, ' ');
if (default_module) {
return str ? dest : NULL;
}
if (!str) {
return NULL;
}
if ((str - start + 1) >= MODULE_NAME_MAX_LEN) {
return NULL;
}
strncpy(dest_str, start, (str - start + 1));
dest_str[str - start] = '\0';
dest = get_destination_module(dest_str);
if (!dest) {
return NULL;
}
str++;
/* caller func has already checked str len and put '\0' at the end */
*command_prefix = str;
str = strchr(str, ' ');
/* only two parameters are possibles in case of no default module */
return str ? dest : NULL;
}
static u8_t completion(char *line, u8_t len)
{
const char *first_match = NULL;
int common_chars = -1, space = 0;
int i, command_len;
const struct shell_module *module;
char *command_prefix;
if (len >= (MODULE_NAME_MAX_LEN + COMMAND_MAX_LEN - 1)) {
return 0;
}
/*
* line to completion is not ended by '\0' as the line that gets from
* k_fifo_get function
*/
line[len] = '\0';
module = get_completion_module(line, &command_prefix);
if (!module) {
return 0;
}
command_len = strlen(command_prefix);
for (i = 0; module->commands[i].cmd_name; i++) {
int j;
if (strncmp(command_prefix,
module->commands[i].cmd_name, command_len)) {
continue;
}
if (!first_match) {
first_match = module->commands[i].cmd_name;
continue;
}
/* more commands match, print first match */
if (first_match && (common_chars < 0)) {
printk("\n%s\n", first_match);
common_chars = strlen(first_match);
}
/* cut common part of matching names */
for (j = 0; j < common_chars; j++) {
if (first_match[j] != module->commands[i].cmd_name[j]) {
break;
}
}
common_chars = j;
printk("%s\n", module->commands[i].cmd_name);
}
/* no match, do nothing */
if (!first_match) {
return 0;
}
if (common_chars >= 0) {
/* multiple match, restore prompt */
printk("%s", get_prompt());
printk("%s", line);
} else {
common_chars = strlen(first_match);
space = 1;
}
/* complete common part */
for (i = command_len; i < common_chars; i++) {
printk("%c", first_match[i]);
line[len++] = first_match[i];
}
/* for convenience add space after command */
if (space) {
printk(" ");
line[len] = ' ';
}
return common_chars - command_len + space;
}
void shell_init(const char *str)
{
k_fifo_init(&cmds_queue);
k_fifo_init(&avail_queue);
line_queue_init();
prompt = str ? str : "";
k_thread_create(&shell_thread, stack, STACKSIZE, shell, NULL, NULL,
NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
/* Register serial console handler */
#ifdef CONFIG_UART_CONSOLE
uart_register_input(&avail_queue, &cmds_queue, completion);
#endif
#ifdef CONFIG_TELNET_CONSOLE
telnet_register_input(&avail_queue, &cmds_queue, completion);
#endif
}
/** @brief Optionally register an app default cmd handler.
*
* @param handler To be called if no cmd found in cmds registered with
* shell_init.
*/
void shell_register_app_cmd_handler(shell_cmd_function_t handler)
{
app_cmd_handler = handler;
}
void shell_register_prompt_handler(shell_prompt_function_t handler)
{
app_prompt_handler = handler;
}
void shell_register_default_module(const char *name)
{
int err = set_default_module(name);
if (!err) {
printk("\n%s", default_module_prompt);
}
}