forked from xuos/xiuos
1852 lines
51 KiB
C
1852 lines
51 KiB
C
/**
|
||
* @file shell.c
|
||
* @author Letter (NevermindZZT@gmail.com)
|
||
* @version 3.0.0
|
||
* @date 2019-12-30
|
||
*
|
||
* @copyright (c) 2020 Letter
|
||
*
|
||
*/
|
||
#include <stdarg.h>
|
||
#include <string.h>
|
||
|
||
#include "libserial.h"
|
||
#include "usyscall.h"
|
||
|
||
#include "shell.h"
|
||
#include "shell_cfg.h"
|
||
#include "shell_ext.h"
|
||
|
||
#include "libfs.h"
|
||
#include "libipc.h"
|
||
|
||
#if SHELL_USING_CMD_EXPORT == 1
|
||
/**
|
||
* @brief 默认用户
|
||
*/
|
||
const char shellCmdDefaultUser[] = SHELL_DEFAULT_USER;
|
||
const char shellPasswordDefaultUser[] = SHELL_DEFAULT_USER_PASSWORD;
|
||
const char shellDesDefaultUser[] = "default user";
|
||
SHELL_USED const ShellCommand shellUserDefault SHELL_SECTION("shellCommand") = {
|
||
.attr.value = SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_USER),
|
||
.data.user.name = shellCmdDefaultUser,
|
||
.data.user.password = shellPasswordDefaultUser,
|
||
.data.user.desc = shellDesDefaultUser
|
||
};
|
||
#endif
|
||
|
||
#if SHELL_USING_CMD_EXPORT == 1
|
||
#if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
|
||
extern const unsigned int shellCommand$$Base;
|
||
extern const unsigned int shellCommand$$Limit;
|
||
#elif defined(__ICCARM__) || defined(__ICCRX__)
|
||
#pragma section = "shellCommand"
|
||
#elif defined(__GNUC__)
|
||
extern const unsigned int _shell_command_start;
|
||
extern const unsigned int _shell_command_end;
|
||
#endif
|
||
#else
|
||
extern const ShellCommand shellCommandList[];
|
||
extern const unsigned short shellCommandCount;
|
||
#endif
|
||
|
||
extern struct Session session_fs;
|
||
|
||
/**
|
||
* @brief shell 常量文本索引
|
||
*/
|
||
enum {
|
||
#if SHELL_SHOW_INFO == 1
|
||
SHELL_TEXT_INFO, /**< shell信息 */
|
||
#endif
|
||
SHELL_TEXT_CMD_TOO_LONG, /**< 命令过长 */
|
||
SHELL_TEXT_CMD_LIST, /**< 可执行命令列表标题 */
|
||
SHELL_TEXT_VAR_LIST, /**< 变量列表标题 */
|
||
SHELL_TEXT_USER_LIST, /**< 用户列表标题 */
|
||
SHELL_TEXT_KEY_LIST, /**< 按键列表标题 */
|
||
SHELL_TEXT_CMD_NOT_FOUND, /**< 命令未找到 */
|
||
SHELL_TEXT_POINT_CANNOT_MODIFY, /**< 指针变量不允许修改 */
|
||
SHELL_TEXT_VAR_READ_ONLY_CANNOT_MODIFY, /**< 只读变量不允许修改 */
|
||
SHELL_TEXT_NOT_VAR, /**< 命令不是变量 */
|
||
SHELL_TEXT_VAR_NOT_FOUND, /**< 变量未找到 */
|
||
SHELL_TEXT_HELP_HEADER, /**< help头 */
|
||
SHELL_TEXT_PASSWORD_HINT, /**< 密码输入提示 */
|
||
SHELL_TEXT_PASSWORD_ERROR, /**< 密码错误 */
|
||
SHELL_TEXT_CLEAR_CONSOLE, /**< 清空控制台 */
|
||
SHELL_TEXT_CLEAR_LINE, /**< 清空当前行 */
|
||
SHELL_TEXT_TYPE_CMD, /**< 命令类型 */
|
||
SHELL_TEXT_TYPE_VAR, /**< 变量类型 */
|
||
SHELL_TEXT_TYPE_USER, /**< 用户类型 */
|
||
SHELL_TEXT_TYPE_KEY, /**< 按键类型 */
|
||
SHELL_TEXT_TYPE_NONE, /**< 非法类型 */
|
||
#if SHELL_EXEC_UNDEF_FUNC == 1
|
||
SHELL_TEXT_PARAM_ERROR, /**< 参数错误 */
|
||
#endif
|
||
};
|
||
|
||
static const char* shellText[] = {
|
||
#if SHELL_SHOW_INFO == 1
|
||
[SHELL_TEXT_INFO] = " _ _ _ _ _ _ \r\n"
|
||
"| | ___| |_| |_ ___ _ __ ___| |__ ___| | |\r\n"
|
||
"| | / _ \\ __| __/ _ \\ '__| / __| '_ \\ / _ \\ | |\r\n"
|
||
"| |__| __/ |_| || __/ | \\__ \\ | | | __/ | |\r\n"
|
||
"|_____\\___|\\__|\\__\\___|_| |___/_| |_|\\___|_|_|\r\n"
|
||
"\r\n"
|
||
"Build: "__DATE__
|
||
" "__TIME__
|
||
"\r\n"
|
||
"Version: " SHELL_VERSION "\r\n"
|
||
"Copyright: (c) 2020 Letter\r\n",
|
||
#endif
|
||
[SHELL_TEXT_CMD_TOO_LONG] = "\r\nWarning: Command is too long\r\n",
|
||
[SHELL_TEXT_CMD_LIST] = "\r\nCommand List:\r\n",
|
||
[SHELL_TEXT_VAR_LIST] = "\r\nVar List:\r\n",
|
||
[SHELL_TEXT_USER_LIST] = "\r\nUser List:\r\n",
|
||
[SHELL_TEXT_KEY_LIST] = "\r\nKey List:\r\n",
|
||
[SHELL_TEXT_CMD_NOT_FOUND] = "Command not Found\r\n",
|
||
[SHELL_TEXT_POINT_CANNOT_MODIFY] = "can't set pointer\r\n",
|
||
[SHELL_TEXT_VAR_READ_ONLY_CANNOT_MODIFY] = "can't set read only var\r\n",
|
||
[SHELL_TEXT_NOT_VAR] = " is not a var\r\n",
|
||
[SHELL_TEXT_VAR_NOT_FOUND] = "Var not Fount\r\n",
|
||
[SHELL_TEXT_HELP_HEADER] = "command help of ",
|
||
[SHELL_TEXT_PASSWORD_HINT] = "Please input password:",
|
||
[SHELL_TEXT_PASSWORD_ERROR] = "\r\npassword error\r\n",
|
||
[SHELL_TEXT_CLEAR_CONSOLE] = "\033[2J\033[1H",
|
||
[SHELL_TEXT_CLEAR_LINE] = "\033[2K\r",
|
||
[SHELL_TEXT_TYPE_CMD] = "CMD ",
|
||
[SHELL_TEXT_TYPE_VAR] = "VAR ",
|
||
[SHELL_TEXT_TYPE_USER] = "USER",
|
||
[SHELL_TEXT_TYPE_KEY] = "KEY ",
|
||
[SHELL_TEXT_TYPE_NONE] = "NONE",
|
||
#if SHELL_EXEC_UNDEF_FUNC == 1
|
||
[SHELL_TEXT_PARAM_ERROR] = "Parameter error\r\n",
|
||
#endif
|
||
};
|
||
|
||
unsigned char pairedChars[][2] = {
|
||
{ '\"', '\"' },
|
||
#if SHELL_SUPPORT_ARRAY_PARAM == 1
|
||
{ '[', ']' },
|
||
#endif /** SHELL_SUPPORT_ARRAY_PARAM == 1 */
|
||
// {'(', ')'},
|
||
// {'{', '}'},
|
||
// {'<', '>'},
|
||
// {'\'', '\''},
|
||
// {'`', '`'},
|
||
};
|
||
|
||
/**
|
||
* @brief shell对象表
|
||
*/
|
||
static Shell* shellList[SHELL_MAX_NUMBER] = { NULL };
|
||
|
||
static void shellAdd(Shell* shell);
|
||
static void shellWritePrompt(Shell* shell, unsigned char newline);
|
||
static void shellWriteReturnValue(Shell* shell, int value);
|
||
static int shellShowVar(Shell* shell, ShellCommand* command);
|
||
void shellSetUser(Shell* shell, const ShellCommand* user);
|
||
ShellCommand* shellSeekCommand(Shell* shell,
|
||
const char* cmd,
|
||
ShellCommand* base,
|
||
unsigned short compareLength);
|
||
static void shellWriteCommandHelp(Shell* shell, char* cmd);
|
||
|
||
/**
|
||
* @brief shell 初始化
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellInit(Shell* shell, char* buffer, unsigned short size)
|
||
{
|
||
shell->parser.length = 0;
|
||
shell->parser.cursor = 0;
|
||
shell->info.user = NULL;
|
||
shell->status.isChecked = 1;
|
||
|
||
shell->parser.buffer = buffer;
|
||
shell->parser.bufferSize = size / (SHELL_HISTORY_MAX_NUMBER + 1);
|
||
|
||
#if SHELL_HISTORY_MAX_NUMBER > 0
|
||
shell->history.offset = 0;
|
||
shell->history.number = 0;
|
||
shell->history.record = 0;
|
||
for (short i = 0; i < SHELL_HISTORY_MAX_NUMBER; i++) {
|
||
shell->history.item[i] = buffer + shell->parser.bufferSize * (i + 1);
|
||
}
|
||
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
|
||
|
||
#if SHELL_USING_CMD_EXPORT == 1
|
||
#if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
|
||
shell->commandList.base = (ShellCommand*)(&shellCommand$$Base);
|
||
shell->commandList.count = ((size_t)(&shellCommand$$Limit)
|
||
- (size_t)(&shellCommand$$Base))
|
||
/ sizeof(ShellCommand);
|
||
|
||
#elif defined(__ICCARM__) || defined(__ICCRX__)
|
||
shell->commandList.base = (ShellCommand*)(__section_begin("shellCommand"));
|
||
shell->commandList.count = ((size_t)(__section_end("shellCommand"))
|
||
- (size_t)(__section_begin("shellCommand")))
|
||
/ sizeof(ShellCommand);
|
||
#elif defined(__GNUC__)
|
||
shell->commandList.base = (ShellCommand*)(&_shell_command_start);
|
||
shell->commandList.count = ((size_t)(&_shell_command_end)
|
||
- (size_t)(&_shell_command_start))
|
||
/ sizeof(ShellCommand);
|
||
#else
|
||
#error not supported compiler, please use command table mode
|
||
#endif
|
||
#else
|
||
shell->commandList.base = (ShellCommand*)shellCommandList;
|
||
shell->commandList.count = shellCommandCount;
|
||
#endif
|
||
|
||
shellAdd(shell);
|
||
|
||
shellSetUser(shell, shellSeekCommand(shell, SHELL_DEFAULT_USER, shell->commandList.base, 0));
|
||
shellWritePrompt(shell, 1);
|
||
}
|
||
|
||
/**
|
||
* @brief 添加shell
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
static void shellAdd(Shell* shell)
|
||
{
|
||
for (short i = 0; i < SHELL_MAX_NUMBER; i++) {
|
||
if (shellList[i] == NULL) {
|
||
shellList[i] = shell;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 移除shell
|
||
*
|
||
* @param shell shell对象
|
||
*
|
||
*/
|
||
void shellRemove(Shell* shell)
|
||
{
|
||
for (short i = 0; i < SHELL_MAX_NUMBER; i++) {
|
||
if (shellList[i] == shell) {
|
||
shellList[i] = NULL;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前活动shell
|
||
*
|
||
* @return Shell* 当前活动shell对象
|
||
*/
|
||
Shell* shellGetCurrent(void)
|
||
{
|
||
for (short i = 0; i < SHELL_MAX_NUMBER; i++) {
|
||
if (shellList[i] && shellList[i]->status.isActive) {
|
||
return shellList[i];
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* @brief shell写字符
|
||
*
|
||
* @param shell shell对象
|
||
* @param data 字符数据
|
||
*/
|
||
static void shellWriteByte(Shell* shell, char data)
|
||
{
|
||
shell->write(&data, 1);
|
||
}
|
||
|
||
/**
|
||
* @brief shell 写字符串
|
||
*
|
||
* @param shell shell对象
|
||
* @param string 字符串数据
|
||
*
|
||
* @return unsigned short 写入字符的数量
|
||
*/
|
||
unsigned short shellWriteString(Shell* shell, const char* string)
|
||
{
|
||
unsigned short count = 0;
|
||
const char* p = string;
|
||
SHELL_ASSERT(shell->write, return 0);
|
||
while (*p++) {
|
||
count++;
|
||
}
|
||
return shell->write((char*)string, count);
|
||
}
|
||
|
||
/**
|
||
* @brief shell 写命令描述字符串
|
||
*
|
||
* @param shell shell对象
|
||
* @param string 字符串数据
|
||
*
|
||
* @return unsigned short 写入字符的数量
|
||
*/
|
||
static unsigned short shellWriteCommandDesc(Shell* shell, const char* string)
|
||
{
|
||
unsigned short count = 0;
|
||
const char* p = string;
|
||
SHELL_ASSERT(shell->write, return 0);
|
||
while (*p && *p != '\r' && *p != '\n') {
|
||
p++;
|
||
count++;
|
||
}
|
||
|
||
if (count > 36) {
|
||
shell->write((char*)string, 36);
|
||
shell->write("...", 3);
|
||
} else {
|
||
shell->write((char*)string, count);
|
||
}
|
||
return count > 36 ? 36 : 39;
|
||
}
|
||
|
||
/**
|
||
* @brief shell写命令提示符
|
||
*
|
||
* @param shell shell对象
|
||
* @param newline 新行
|
||
*
|
||
*/
|
||
static void shellWritePrompt(Shell* shell, unsigned char newline)
|
||
{
|
||
if (shell->status.isChecked) {
|
||
if (newline) {
|
||
shellWriteString(shell, "\r\n");
|
||
}
|
||
shellWriteString(shell, shell->info.user->data.user.name);
|
||
shellWriteString(shell, ":");
|
||
shellWriteString(shell, shell->info.path ? shell->info.path : "/");
|
||
shellWriteString(shell, "$ ");
|
||
} else {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_PASSWORD_HINT]);
|
||
}
|
||
}
|
||
|
||
#if SHELL_PRINT_BUFFER > 0
|
||
/**
|
||
* @brief shell格式化输出
|
||
*
|
||
* @param shell shell对象
|
||
* @param fmt 格式化字符串
|
||
* @param ... 参数
|
||
*/
|
||
void shellPrint(Shell* shell, const char* fmt, ...)
|
||
{
|
||
char buffer[SHELL_PRINT_BUFFER];
|
||
va_list vargs;
|
||
int len;
|
||
|
||
SHELL_ASSERT(shell, return);
|
||
|
||
va_start(vargs, fmt);
|
||
len = vsnprintf(buffer, SHELL_PRINT_BUFFER, fmt, vargs);
|
||
va_end(vargs);
|
||
if (len > SHELL_PRINT_BUFFER) {
|
||
len = SHELL_PRINT_BUFFER;
|
||
}
|
||
shell->write(buffer, len);
|
||
}
|
||
#endif
|
||
|
||
#if SHELL_SCAN_BUFFER > 0
|
||
/**
|
||
* @brief shell格式化输入
|
||
*
|
||
* @param shell shell对象
|
||
* @param fmt 格式化字符串
|
||
* @param ... 参数
|
||
*/
|
||
void shellScan(Shell* shell, char* fmt, ...)
|
||
{
|
||
char buffer[SHELL_SCAN_BUFFER];
|
||
va_list vargs;
|
||
short index = 0;
|
||
|
||
SHELL_ASSERT(shell, return);
|
||
|
||
if (shell->read) {
|
||
do {
|
||
if (shell->read(&buffer[index], 1) == 1) {
|
||
shell->write(&buffer[index], 1);
|
||
index++;
|
||
}
|
||
} while (buffer[index - 1] != '\r' && buffer[index - 1] != '\n' && index < SHELL_SCAN_BUFFER);
|
||
shellWriteString(shell, "\r\n");
|
||
buffer[index] = '\0';
|
||
}
|
||
|
||
va_start(vargs, fmt);
|
||
vsscanf(buffer, fmt, vargs);
|
||
va_end(vargs);
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* @brief shell 检查命令权限
|
||
*
|
||
* @param shell shell对象
|
||
* @param command ShellCommand
|
||
*
|
||
* @return signed char 0 当前用户具有该命令权限
|
||
* @return signec char -1 当前用户不具有该命令权限
|
||
*/
|
||
signed char shellCheckPermission(Shell* shell, ShellCommand* command)
|
||
{
|
||
return ((!command->attr.attrs.permission
|
||
|| command->attr.attrs.type == SHELL_TYPE_USER
|
||
|| (shell->info.user
|
||
&& (command->attr.attrs.permission
|
||
& shell->info.user->attr.attrs.permission)))
|
||
&& (shell->status.isChecked
|
||
|| command->attr.attrs.enableUnchecked))
|
||
? 0
|
||
: -1;
|
||
}
|
||
|
||
/**
|
||
* @brief int转16进制字符串
|
||
*
|
||
* @param value 数值
|
||
* @param buffer 缓冲
|
||
*
|
||
* @return signed char 转换后有效数据长度
|
||
*/
|
||
signed char shellToHex(unsigned int value, char* buffer)
|
||
{
|
||
char byte;
|
||
unsigned char i = 8;
|
||
buffer[8] = 0;
|
||
while (value) {
|
||
byte = value & 0x0000000F;
|
||
buffer[--i] = (byte > 9) ? (byte + 87) : (byte + 48);
|
||
value >>= 4;
|
||
}
|
||
return 8 - i;
|
||
}
|
||
|
||
/**
|
||
* @brief int转10进制字符串
|
||
*
|
||
* @param value 数值
|
||
* @param buffer 缓冲
|
||
*
|
||
* @return signed char 转换后有效数据长度
|
||
*/
|
||
signed char shellToDec(int value, char* buffer)
|
||
{
|
||
unsigned char i = 11;
|
||
int v = value;
|
||
if (value < 0) {
|
||
v = -value;
|
||
}
|
||
buffer[11] = 0;
|
||
while (v) {
|
||
buffer[--i] = v % 10 + 48;
|
||
v /= 10;
|
||
}
|
||
if (value < 0) {
|
||
buffer[--i] = '-';
|
||
}
|
||
if (value == 0) {
|
||
buffer[--i] = '0';
|
||
}
|
||
return 11 - i;
|
||
}
|
||
|
||
/**
|
||
* @brief shell字符串复制
|
||
*
|
||
* @param dest 目标字符串
|
||
* @param src 源字符串
|
||
* @return unsigned short 字符串长度
|
||
*/
|
||
static unsigned short shellStringCopy(char* dest, char* src)
|
||
{
|
||
unsigned short count = 0;
|
||
while (*(src + count)) {
|
||
*(dest + count) = *(src + count);
|
||
count++;
|
||
}
|
||
*(dest + count) = 0;
|
||
return count;
|
||
}
|
||
|
||
/**
|
||
* @brief shell字符串比较
|
||
*
|
||
* @param dest 目标字符串
|
||
* @param src 源字符串
|
||
* @return unsigned short 匹配长度
|
||
*/
|
||
static unsigned short shellStringCompare(char* dest, char* src)
|
||
{
|
||
unsigned short match = 0;
|
||
unsigned short i = 0;
|
||
|
||
while (*(dest + i) && *(src + i)) {
|
||
if (*(dest + i) != *(src + i)) {
|
||
break;
|
||
}
|
||
match++;
|
||
i++;
|
||
}
|
||
return match;
|
||
}
|
||
|
||
/**
|
||
* @brief shell获取命令名
|
||
*
|
||
* @param command 命令
|
||
* @return const char* 命令名
|
||
*/
|
||
static const char* shellGetCommandName(ShellCommand* command)
|
||
{
|
||
static char buffer[9];
|
||
for (unsigned char i = 0; i < 9; i++) {
|
||
buffer[i] = '0';
|
||
}
|
||
if (command->attr.attrs.type <= SHELL_TYPE_CMD_FUNC) {
|
||
return command->data.cmd.name;
|
||
} else if (command->attr.attrs.type <= SHELL_TYPE_VAR_NODE) {
|
||
return command->data.var.name;
|
||
} else if (command->attr.attrs.type <= SHELL_TYPE_USER) {
|
||
return command->data.user.name;
|
||
}
|
||
#if SHELL_USING_FUNC_SIGNATURE == 1
|
||
else if (command->attr.attrs.type == SHELL_TYPE_PARAM_PARSER) {
|
||
return command->data.paramParser.type;
|
||
}
|
||
#endif
|
||
else {
|
||
shellToHex(command->data.key.value, buffer);
|
||
return buffer;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell获取命令描述
|
||
*
|
||
* @param command 命令
|
||
* @return const char* 命令描述
|
||
*/
|
||
static const char* shellGetCommandDesc(ShellCommand* command)
|
||
{
|
||
if (command->attr.attrs.type <= SHELL_TYPE_CMD_FUNC) {
|
||
return command->data.cmd.desc;
|
||
} else if (command->attr.attrs.type <= SHELL_TYPE_VAR_NODE) {
|
||
return command->data.var.desc;
|
||
} else if (command->attr.attrs.type <= SHELL_TYPE_USER) {
|
||
return command->data.user.desc;
|
||
} else {
|
||
return command->data.key.desc;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell 列出命令条目
|
||
*
|
||
* @param shell shell对象
|
||
* @param item 命令条目
|
||
*/
|
||
void shellListItem(Shell* shell, ShellCommand* item)
|
||
{
|
||
short spaceLength;
|
||
|
||
spaceLength = 22 - shellWriteString(shell, shellGetCommandName(item));
|
||
spaceLength = (spaceLength > 0) ? spaceLength : 4;
|
||
do {
|
||
shellWriteByte(shell, ' ');
|
||
} while (--spaceLength);
|
||
if (item->attr.attrs.type <= SHELL_TYPE_CMD_FUNC) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_TYPE_CMD]);
|
||
} else if (item->attr.attrs.type <= SHELL_TYPE_VAR_NODE) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_TYPE_VAR]);
|
||
} else if (item->attr.attrs.type <= SHELL_TYPE_USER) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_TYPE_USER]);
|
||
} else if (item->attr.attrs.type <= SHELL_TYPE_KEY) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_TYPE_KEY]);
|
||
} else {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_TYPE_NONE]);
|
||
}
|
||
#if SHELL_HELP_SHOW_PERMISSION == 1
|
||
shellWriteString(shell, " ");
|
||
for (signed char i = 7; i >= 0; i--) {
|
||
shellWriteByte(shell, item->attr.attrs.permission & (1 << i) ? 'x' : '-');
|
||
}
|
||
#endif
|
||
shellWriteString(shell, " ");
|
||
shellWriteCommandDesc(shell, shellGetCommandDesc(item));
|
||
shellWriteString(shell, "\r\n");
|
||
}
|
||
|
||
/**
|
||
* @brief shell列出可执行命令
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellListCommand(Shell* shell)
|
||
{
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_LIST]);
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
if (base[i].attr.attrs.type <= SHELL_TYPE_CMD_FUNC
|
||
&& shellCheckPermission(shell, &base[i]) == 0) {
|
||
shellListItem(shell, &base[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell列出变量
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellListVar(Shell* shell)
|
||
{
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
shellWriteString(shell, shellText[SHELL_TEXT_VAR_LIST]);
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
if (base[i].attr.attrs.type > SHELL_TYPE_CMD_FUNC
|
||
&& base[i].attr.attrs.type <= SHELL_TYPE_VAR_NODE
|
||
&& shellCheckPermission(shell, &base[i]) == 0) {
|
||
shellListItem(shell, &base[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell列出用户
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellListUser(Shell* shell)
|
||
{
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
shellWriteString(shell, shellText[SHELL_TEXT_USER_LIST]);
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
if (base[i].attr.attrs.type > SHELL_TYPE_VAR_NODE
|
||
&& base[i].attr.attrs.type <= SHELL_TYPE_USER
|
||
&& shellCheckPermission(shell, &base[i]) == 0) {
|
||
shellListItem(shell, &base[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell列出按键
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellListKey(Shell* shell)
|
||
{
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
shellWriteString(shell, shellText[SHELL_TEXT_KEY_LIST]);
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
if (base[i].attr.attrs.type > SHELL_TYPE_USER
|
||
&& base[i].attr.attrs.type <= SHELL_TYPE_KEY
|
||
&& shellCheckPermission(shell, &base[i]) == 0) {
|
||
shellListItem(shell, &base[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell列出所有命令
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellListAll(Shell* shell)
|
||
{
|
||
#if SHELL_HELP_LIST_USER == 1
|
||
shellListUser(shell);
|
||
#endif
|
||
shellListCommand(shell);
|
||
#if SHELL_HELP_LIST_VAR == 1
|
||
shellListVar(shell);
|
||
#endif
|
||
#if SHELL_HELP_LIST_KEY == 1
|
||
shellListKey(shell);
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief shell删除命令行数据
|
||
*
|
||
* @param shell shell对象
|
||
* @param length 删除长度
|
||
*/
|
||
void shellDeleteCommandLine(Shell* shell, unsigned char length)
|
||
{
|
||
while (length--) {
|
||
shellWriteString(shell, "\b \b");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell 清空命令行输入
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellClearCommandLine(Shell* shell)
|
||
{
|
||
for (short i = shell->parser.length - shell->parser.cursor; i > 0; i--) {
|
||
shellWriteByte(shell, ' ');
|
||
}
|
||
shellDeleteCommandLine(shell, shell->parser.length);
|
||
}
|
||
|
||
/**
|
||
* @brief shell插入一个字符到光标位置
|
||
*
|
||
* @param shell shell对象
|
||
* @param data 字符数据
|
||
*/
|
||
void shellInsertByte(Shell* shell, char data)
|
||
{
|
||
/* 判断输入数据是否过长 */
|
||
if (shell->parser.length >= shell->parser.bufferSize - 1) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_TOO_LONG]);
|
||
shellWritePrompt(shell, 1);
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
return;
|
||
}
|
||
|
||
/* 插入数据 */
|
||
if (shell->parser.cursor == shell->parser.length) {
|
||
shell->parser.buffer[shell->parser.length++] = data;
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
shell->parser.cursor++;
|
||
shellWriteByte(shell, shell->status.isChecked ? data : '*');
|
||
} else if (shell->parser.cursor < shell->parser.length) {
|
||
for (short i = shell->parser.length - shell->parser.cursor; i > 0; i--) {
|
||
shell->parser.buffer[shell->parser.cursor + i] = shell->parser.buffer[shell->parser.cursor + i - 1];
|
||
}
|
||
shell->parser.buffer[shell->parser.cursor++] = data;
|
||
shell->parser.buffer[++shell->parser.length] = 0;
|
||
for (short i = shell->parser.cursor - 1; i < shell->parser.length; i++) {
|
||
shellWriteByte(shell,
|
||
shell->status.isChecked ? shell->parser.buffer[i] : '*');
|
||
}
|
||
for (short i = shell->parser.length - shell->parser.cursor; i > 0; i--) {
|
||
shellWriteByte(shell, '\b');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell 删除字节
|
||
*
|
||
* @param shell shell对象
|
||
* @param direction 删除方向 {@code 1}删除光标前字符 {@code -1}删除光标处字符
|
||
*/
|
||
void shellDeleteByte(Shell* shell, signed char direction)
|
||
{
|
||
char offset = (direction == -1) ? 1 : 0;
|
||
|
||
if ((shell->parser.cursor == 0 && direction == 1)
|
||
|| (shell->parser.cursor == shell->parser.length && direction == -1)) {
|
||
return;
|
||
}
|
||
if (shell->parser.cursor == shell->parser.length && direction == 1) {
|
||
shell->parser.cursor--;
|
||
shell->parser.length--;
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
shellDeleteCommandLine(shell, 1);
|
||
} else {
|
||
for (short i = offset; i < shell->parser.length - shell->parser.cursor; i++) {
|
||
shell->parser.buffer[shell->parser.cursor + i - 1] = shell->parser.buffer[shell->parser.cursor + i];
|
||
}
|
||
shell->parser.length--;
|
||
if (!offset) {
|
||
shell->parser.cursor--;
|
||
shellWriteByte(shell, '\b');
|
||
}
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
for (short i = shell->parser.cursor; i < shell->parser.length; i++) {
|
||
shellWriteByte(shell, shell->parser.buffer[i]);
|
||
}
|
||
shellWriteByte(shell, ' ');
|
||
for (short i = shell->parser.length - shell->parser.cursor + 1; i > 0; i--) {
|
||
shellWriteByte(shell, '\b');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell 字符串分割
|
||
*
|
||
* @param string 字符串
|
||
* @param array 分割后保存的字符串数组
|
||
* @param splitKey 分隔符
|
||
* @param maxNum 最大分割数量
|
||
*
|
||
* @return int 分割得到的字串数量
|
||
*/
|
||
int shellSplit(char* string, unsigned short strLen, char* array[], char splitKey, short maxNum)
|
||
{
|
||
unsigned char record = 1;
|
||
unsigned char pairedLeft[16] = { 0 };
|
||
unsigned char pariedCount = 0;
|
||
int count = 0;
|
||
|
||
for (short i = 0; i < maxNum; i++) {
|
||
array[i] = NULL;
|
||
}
|
||
|
||
for (unsigned short i = 0; i < strLen; i++) {
|
||
if (pariedCount == 0) {
|
||
if (string[i] != splitKey && record == 1 && count < maxNum) {
|
||
array[count++] = &(string[i]);
|
||
record = 0;
|
||
} else if ((string[i] == splitKey || string[i] == ' ') && record == 0) {
|
||
string[i] = 0;
|
||
if (string[i + 1] != ' ') {
|
||
record = 1;
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
|
||
for (unsigned char j = 0; j < sizeof(pairedChars) / 2; j++) {
|
||
if (pariedCount > 0
|
||
&& string[i] == pairedChars[j][1]
|
||
&& pairedLeft[pariedCount - 1] == pairedChars[j][0]) {
|
||
--pariedCount;
|
||
break;
|
||
} else if (string[i] == pairedChars[j][0]) {
|
||
pairedLeft[pariedCount++] = pairedChars[j][0];
|
||
pariedCount &= 0x0F;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (string[i] == '\\' && string[i + 1] != 0) {
|
||
i++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/**
|
||
* @brief shell 解析参数
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
static void shellParserParam(Shell* shell)
|
||
{
|
||
shell->parser.paramCount = shellSplit(shell->parser.buffer, shell->parser.length,
|
||
shell->parser.param, ' ', SHELL_PARAMETER_MAX_NUMBER);
|
||
}
|
||
|
||
/**
|
||
* @brief shell去除字符串参数头尾的双引号
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
static void shellRemoveParamQuotes(Shell* shell)
|
||
{
|
||
unsigned short paramLength;
|
||
for (unsigned short i = 0; i < shell->parser.paramCount; i++) {
|
||
if (shell->parser.param[i][0] == '\"') {
|
||
shell->parser.param[i][0] = 0;
|
||
shell->parser.param[i] = &shell->parser.param[i][1];
|
||
}
|
||
paramLength = strlen(shell->parser.param[i]);
|
||
if (shell->parser.param[i][paramLength - 1] == '\"') {
|
||
shell->parser.param[i][paramLength - 1] = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell匹配命令
|
||
*
|
||
* @param shell shell对象
|
||
* @param cmd 命令
|
||
* @param base 匹配命令表基址
|
||
* @param compareLength 匹配字符串长度
|
||
* @return ShellCommand* 匹配到的命令
|
||
*/
|
||
ShellCommand* shellSeekCommand(Shell* shell,
|
||
const char* cmd,
|
||
ShellCommand* base,
|
||
unsigned short compareLength)
|
||
{
|
||
const char* name;
|
||
unsigned short count = shell->commandList.count - ((size_t)base - (size_t)shell->commandList.base) / sizeof(ShellCommand);
|
||
for (unsigned short i = 0; i < count; i++) {
|
||
if (base[i].attr.attrs.type == SHELL_TYPE_KEY
|
||
|| shellCheckPermission(shell, &base[i]) != 0) {
|
||
continue;
|
||
}
|
||
name = shellGetCommandName(&base[i]);
|
||
if (!compareLength) {
|
||
if (strcmp(cmd, name) == 0) {
|
||
return &base[i];
|
||
}
|
||
} else {
|
||
if (strncmp(cmd, name, compareLength) == 0) {
|
||
return &base[i];
|
||
}
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* @brief shell 获取变量值
|
||
*
|
||
* @param shell shell对象
|
||
* @param command 命令
|
||
* @return int 变量值
|
||
*/
|
||
int shellGetVarValue(Shell* shell, ShellCommand* command)
|
||
{
|
||
int value = 0;
|
||
switch (command->attr.attrs.type) {
|
||
case SHELL_TYPE_VAR_INT:
|
||
value = *((int*)(command->data.var.value));
|
||
break;
|
||
case SHELL_TYPE_VAR_SHORT:
|
||
value = *((short*)(command->data.var.value));
|
||
break;
|
||
case SHELL_TYPE_VAR_CHAR:
|
||
value = *((char*)(command->data.var.value));
|
||
break;
|
||
case SHELL_TYPE_VAR_STRING:
|
||
case SHELL_TYPE_VAR_POINT:
|
||
value = (size_t)(command->data.var.value);
|
||
break;
|
||
case SHELL_TYPE_VAR_NODE: {
|
||
int (*func)(void*) = ((ShellNodeVarAttr*)command->data.var.value)->get;
|
||
value = func ? func(((ShellNodeVarAttr*)command->data.var.value)->var) : 0;
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* @brief shell设置变量值
|
||
*
|
||
* @param shell shell对象
|
||
* @param command 命令
|
||
* @param value 值
|
||
* @return int 返回变量值
|
||
*/
|
||
int shellSetVarValue(Shell* shell, ShellCommand* command, int value)
|
||
{
|
||
if (command->attr.attrs.readOnly) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_VAR_READ_ONLY_CANNOT_MODIFY]);
|
||
} else {
|
||
switch (command->attr.attrs.type) {
|
||
case SHELL_TYPE_VAR_INT:
|
||
*((int*)(command->data.var.value)) = value;
|
||
break;
|
||
case SHELL_TYPE_VAR_SHORT:
|
||
*((short*)(command->data.var.value)) = value;
|
||
break;
|
||
case SHELL_TYPE_VAR_CHAR:
|
||
*((char*)(command->data.var.value)) = value;
|
||
break;
|
||
case SHELL_TYPE_VAR_STRING:
|
||
shellStringCopy(((char*)(command->data.var.value)), (char*)(size_t)value);
|
||
break;
|
||
case SHELL_TYPE_VAR_POINT:
|
||
shellWriteString(shell, shellText[SHELL_TEXT_POINT_CANNOT_MODIFY]);
|
||
break;
|
||
case SHELL_TYPE_VAR_NODE:
|
||
if (((ShellNodeVarAttr*)command->data.var.value)->set) {
|
||
if (((ShellNodeVarAttr*)command->data.var.value)->var) {
|
||
int (*func)(void*, int) = ((ShellNodeVarAttr*)command->data.var.value)->set;
|
||
func(((ShellNodeVarAttr*)command->data.var.value)->var, value);
|
||
} else {
|
||
int (*func)(int) = ((ShellNodeVarAttr*)command->data.var.value)->set;
|
||
func(value);
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return shellShowVar(shell, command);
|
||
}
|
||
|
||
/**
|
||
* @brief shell变量输出
|
||
*
|
||
* @param shell shell对象
|
||
* @param command 命令
|
||
* @return int 返回变量值
|
||
*/
|
||
static int shellShowVar(Shell* shell, ShellCommand* command)
|
||
{
|
||
char buffer[12] = "00000000000";
|
||
int value = shellGetVarValue(shell, command);
|
||
|
||
shellWriteString(shell, command->data.var.name);
|
||
shellWriteString(shell, " = ");
|
||
|
||
switch (command->attr.attrs.type) {
|
||
case SHELL_TYPE_VAR_STRING:
|
||
shellWriteString(shell, "\"");
|
||
shellWriteString(shell, (char*)(size_t)value);
|
||
shellWriteString(shell, "\"");
|
||
break;
|
||
// case SHELL_TYPE_VAR_INT:
|
||
// case SHELL_TYPE_VAR_SHORT:
|
||
// case SHELL_TYPE_VAR_CHAR:
|
||
// case SHELL_TYPE_VAR_POINT:
|
||
default:
|
||
shellWriteString(shell, &buffer[11 - shellToDec(value, buffer)]);
|
||
shellWriteString(shell, ", 0x");
|
||
for (short i = 0; i < 11; i++) {
|
||
buffer[i] = '0';
|
||
}
|
||
shellToHex(value, buffer);
|
||
shellWriteString(shell, buffer);
|
||
break;
|
||
}
|
||
|
||
shellWriteString(shell, "\r\n");
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* @brief shell设置变量
|
||
*
|
||
* @param name 变量名
|
||
* @param value 变量值
|
||
* @return int 返回变量值
|
||
*/
|
||
int shellSetVar(char* name, int value)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell == NULL) {
|
||
return 0;
|
||
}
|
||
ShellCommand* command = shellSeekCommand(shell,
|
||
name,
|
||
shell->commandList.base,
|
||
0);
|
||
if (!command) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_VAR_NOT_FOUND]);
|
||
return 0;
|
||
}
|
||
if (command->attr.attrs.type < SHELL_TYPE_VAR_INT
|
||
|| command->attr.attrs.type > SHELL_TYPE_VAR_NODE) {
|
||
shellWriteString(shell, name);
|
||
shellWriteString(shell, shellText[SHELL_TEXT_NOT_VAR]);
|
||
return 0;
|
||
}
|
||
return shellSetVarValue(shell, command, value);
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
setVar, shellSetVar, set var);
|
||
|
||
/**
|
||
* @brief shell运行命令
|
||
*
|
||
* @param shell shell对象
|
||
* @param command 命令
|
||
*
|
||
* @return unsigned int 命令返回值
|
||
*/
|
||
unsigned int shellRunCommand(Shell* shell, ShellCommand* command)
|
||
{
|
||
int returnValue = 0;
|
||
shell->status.isActive = 1;
|
||
if (command->attr.attrs.type == SHELL_TYPE_CMD_MAIN) {
|
||
shellRemoveParamQuotes(shell);
|
||
int (*func)(int, char**) = command->data.cmd.function;
|
||
returnValue = func(shell->parser.paramCount, shell->parser.param);
|
||
if (!command->attr.attrs.disableReturn) {
|
||
shellWriteReturnValue(shell, returnValue);
|
||
}
|
||
} else if (command->attr.attrs.type == SHELL_TYPE_CMD_FUNC) {
|
||
returnValue = shellExtRun(shell,
|
||
command,
|
||
shell->parser.paramCount,
|
||
shell->parser.param);
|
||
if (!command->attr.attrs.disableReturn) {
|
||
shellWriteReturnValue(shell, returnValue);
|
||
}
|
||
} else if (command->attr.attrs.type >= SHELL_TYPE_VAR_INT
|
||
&& command->attr.attrs.type <= SHELL_TYPE_VAR_NODE) {
|
||
shellShowVar(shell, command);
|
||
} else if (command->attr.attrs.type == SHELL_TYPE_USER) {
|
||
shellSetUser(shell, command);
|
||
}
|
||
shell->status.isActive = 0;
|
||
|
||
return returnValue;
|
||
}
|
||
|
||
/**
|
||
* @brief shell校验密码
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
static void shellCheckPassword(Shell* shell)
|
||
{
|
||
if (strcmp(shell->parser.buffer, shell->info.user->data.user.password) == 0) {
|
||
shell->status.isChecked = 1;
|
||
#if SHELL_SHOW_INFO == 1
|
||
shellWriteString(shell, shellText[SHELL_TEXT_INFO]);
|
||
#endif
|
||
} else {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_PASSWORD_ERROR]);
|
||
}
|
||
shell->parser.length = 0;
|
||
shell->parser.cursor = 0;
|
||
}
|
||
|
||
/**
|
||
* @brief shell设置用户
|
||
*
|
||
* @param shell shell对象
|
||
* @param user 用户
|
||
*/
|
||
void shellSetUser(Shell* shell, const ShellCommand* user)
|
||
{
|
||
shell->info.user = user;
|
||
shell->status.isChecked = ((user->data.user.password && strlen(user->data.user.password) != 0)
|
||
&& (shell->parser.paramCount < 2
|
||
|| strcmp(user->data.user.password, shell->parser.param[1]) != 0))
|
||
? 0
|
||
: 1;
|
||
|
||
#if SHELL_CLS_WHEN_LOGIN == 1
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CLEAR_CONSOLE]);
|
||
#endif
|
||
#if SHELL_SHOW_INFO == 1
|
||
if (shell->status.isChecked) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_INFO]);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief shell写返回值
|
||
*
|
||
* @param shell shell对象
|
||
* @param value 返回值
|
||
*/
|
||
static void shellWriteReturnValue(Shell* shell, int value)
|
||
{
|
||
char buffer[12] = "00000000000";
|
||
shellWriteString(shell, "Return: ");
|
||
shellWriteString(shell, &buffer[11 - shellToDec(value, buffer)]);
|
||
shellWriteString(shell, ", 0x");
|
||
for (short i = 0; i < 11; i++) {
|
||
buffer[i] = '0';
|
||
}
|
||
shellToHex(value, buffer);
|
||
shellWriteString(shell, buffer);
|
||
shellWriteString(shell, "\r\n");
|
||
#if SHELL_KEEP_RETURN_VALUE == 1
|
||
shell->info.retVal = value;
|
||
#endif
|
||
}
|
||
|
||
#if SHELL_HISTORY_MAX_NUMBER > 0
|
||
/**
|
||
* @brief shell历史记录添加
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
static void shellHistoryAdd(Shell* shell)
|
||
{
|
||
shell->history.offset = 0;
|
||
if (shell->history.number > 0
|
||
&& strcmp(shell->history.item[(shell->history.record == 0 ? SHELL_HISTORY_MAX_NUMBER : shell->history.record) - 1],
|
||
shell->parser.buffer)
|
||
== 0) {
|
||
return;
|
||
}
|
||
if (shellStringCopy(shell->history.item[shell->history.record],
|
||
shell->parser.buffer)
|
||
!= 0) {
|
||
shell->history.record++;
|
||
}
|
||
if (++shell->history.number > SHELL_HISTORY_MAX_NUMBER) {
|
||
shell->history.number = SHELL_HISTORY_MAX_NUMBER;
|
||
}
|
||
if (shell->history.record >= SHELL_HISTORY_MAX_NUMBER) {
|
||
shell->history.record = 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell历史记录查找
|
||
*
|
||
* @param shell shell对象
|
||
* @param dir 方向 {@code <0}往上查找 {@code >0}往下查找
|
||
*/
|
||
static void shellHistory(Shell* shell, signed char dir)
|
||
{
|
||
if (dir > 0) {
|
||
if (shell->history.offset-- <= -((shell->history.number > shell->history.record) ? shell->history.number : shell->history.record)) {
|
||
shell->history.offset = -((shell->history.number > shell->history.record)
|
||
? shell->history.number
|
||
: shell->history.record);
|
||
}
|
||
} else if (dir < 0) {
|
||
if (++shell->history.offset > 0) {
|
||
shell->history.offset = 0;
|
||
return;
|
||
}
|
||
} else {
|
||
return;
|
||
}
|
||
shellClearCommandLine(shell);
|
||
if (shell->history.offset == 0) {
|
||
shell->parser.cursor = shell->parser.length = 0;
|
||
} else {
|
||
if ((shell->parser.length = shellStringCopy(shell->parser.buffer,
|
||
shell->history.item[(shell->history.record + SHELL_HISTORY_MAX_NUMBER
|
||
+ shell->history.offset)
|
||
% SHELL_HISTORY_MAX_NUMBER]))
|
||
== 0) {
|
||
return;
|
||
}
|
||
shell->parser.cursor = shell->parser.length;
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
}
|
||
}
|
||
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
|
||
|
||
/**
|
||
* @brief shell 常规输入
|
||
*
|
||
* @param shell shell 对象
|
||
* @param data 输入字符
|
||
*/
|
||
void shellNormalInput(Shell* shell, char data)
|
||
{
|
||
shell->status.tabFlag = 0;
|
||
shellInsertByte(shell, data);
|
||
}
|
||
|
||
/**
|
||
* @brief shell运行命令
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellExec(Shell* shell)
|
||
{
|
||
|
||
if (shell->parser.length == 0) {
|
||
return;
|
||
}
|
||
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
|
||
if (shell->status.isChecked) {
|
||
#if SHELL_HISTORY_MAX_NUMBER > 0
|
||
shellHistoryAdd(shell);
|
||
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
|
||
shellParserParam(shell);
|
||
shell->parser.length = shell->parser.cursor = 0;
|
||
if (shell->parser.paramCount == 0) {
|
||
return;
|
||
}
|
||
shellWriteString(shell, "\r\n");
|
||
|
||
// exec shell command
|
||
ShellCommand* command = shellSeekCommand(shell,
|
||
shell->parser.param[0],
|
||
shell->commandList.base,
|
||
0);
|
||
if (command != NULL) {
|
||
shellRunCommand(shell, command);
|
||
} else {
|
||
// exec user apps
|
||
int paramNum = shell->parser.paramCount > SHELL_PARAMETER_MAX_NUMBER ? SHELL_PARAMETER_MAX_NUMBER : shell->parser.paramCount;
|
||
shell->parser.param[paramNum] = 0;
|
||
int fd = -1;
|
||
fd = open(&session_fs, shell->parser.param[0]);
|
||
if (fd < 0) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_NOT_FOUND]);
|
||
} else {
|
||
if (spawn(&session_fs, fd, read, fsize, shell->parser.param[0], shell->parser.param) < 0) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_NOT_FOUND]);
|
||
}
|
||
close(&session_fs, fd);
|
||
}
|
||
}
|
||
} else {
|
||
shellCheckPassword(shell);
|
||
}
|
||
}
|
||
|
||
#if SHELL_HISTORY_MAX_NUMBER > 0
|
||
/**
|
||
* @brief shell上方向键输入
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellUp(Shell* shell)
|
||
{
|
||
shellHistory(shell, 1);
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x1B5B4100, shellUp, up);
|
||
|
||
/**
|
||
* @brief shell下方向键输入
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellDown(Shell* shell)
|
||
{
|
||
shellHistory(shell, -1);
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x1B5B4200, shellDown, down);
|
||
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
|
||
|
||
/**
|
||
* @brief shell右方向键输入
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellRight(Shell* shell)
|
||
{
|
||
if (shell->parser.cursor < shell->parser.length) {
|
||
shellWriteByte(shell, shell->parser.buffer[shell->parser.cursor++]);
|
||
}
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x1B5B4300, shellRight, right);
|
||
|
||
/**
|
||
* @brief shell左方向键输入
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellLeft(Shell* shell)
|
||
{
|
||
if (shell->parser.cursor > 0) {
|
||
shellWriteByte(shell, '\b');
|
||
shell->parser.cursor--;
|
||
}
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x1B5B4400, shellLeft, left);
|
||
|
||
/**
|
||
* @brief shell Tab按键处理
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellTab(Shell* shell)
|
||
{
|
||
unsigned short maxMatch = shell->parser.bufferSize;
|
||
unsigned short lastMatchIndex = 0;
|
||
unsigned short matchNum = 0;
|
||
unsigned short length;
|
||
|
||
if (shell->parser.length == 0) {
|
||
shellListAll(shell);
|
||
shellWritePrompt(shell, 1);
|
||
} else if (shell->parser.length > 0) {
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
if (shellCheckPermission(shell, &base[i]) == 0
|
||
&& shellStringCompare(shell->parser.buffer,
|
||
(char*)shellGetCommandName(&base[i]))
|
||
== shell->parser.length) {
|
||
if (matchNum != 0) {
|
||
if (matchNum == 1) {
|
||
shellWriteString(shell, "\r\n");
|
||
}
|
||
shellListItem(shell, &base[lastMatchIndex]);
|
||
length = shellStringCompare((char*)shellGetCommandName(&base[lastMatchIndex]),
|
||
(char*)shellGetCommandName(&base[i]));
|
||
maxMatch = (maxMatch > length) ? length : maxMatch;
|
||
}
|
||
lastMatchIndex = i;
|
||
matchNum++;
|
||
}
|
||
}
|
||
if (matchNum == 0) {
|
||
return;
|
||
}
|
||
if (matchNum == 1) {
|
||
shellClearCommandLine(shell);
|
||
}
|
||
if (matchNum != 0) {
|
||
shell->parser.length = shellStringCopy(shell->parser.buffer,
|
||
(char*)shellGetCommandName(&base[lastMatchIndex]));
|
||
}
|
||
if (matchNum > 1) {
|
||
shellListItem(shell, &base[lastMatchIndex]);
|
||
shellWritePrompt(shell, 1);
|
||
shell->parser.length = maxMatch;
|
||
}
|
||
shell->parser.buffer[shell->parser.length] = 0;
|
||
shell->parser.cursor = shell->parser.length;
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
}
|
||
|
||
if (SHELL_GET_TICK()) {
|
||
if (matchNum == 1
|
||
&& shell->status.tabFlag
|
||
&& SHELL_GET_TICK() - shell->info.activeTime < SHELL_DOUBLE_CLICK_TIME) {
|
||
#if SHELL_QUICK_HELP == 1
|
||
shellWriteString(shell, "\r\n");
|
||
shellWriteCommandHelp(shell, shell->parser.buffer);
|
||
shellWritePrompt(shell, 1);
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
#else
|
||
shellClearCommandLine(shell);
|
||
for (short i = shell->parser.length; i >= 0; i--) {
|
||
shell->parser.buffer[i + 5] = shell->parser.buffer[i];
|
||
}
|
||
shellStringCopy(shell->parser.buffer, "help");
|
||
shell->parser.buffer[4] = ' ';
|
||
shell->parser.length += 5;
|
||
shell->parser.cursor = shell->parser.length;
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
#endif
|
||
} else {
|
||
shell->status.tabFlag = 1;
|
||
}
|
||
}
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x09000000, shellTab, tab);
|
||
|
||
/**
|
||
* @brief shell 退格
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellBackspace(Shell* shell)
|
||
{
|
||
shellDeleteByte(shell, 1);
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x08000000, shellBackspace, backspace);
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x7F000000, shellBackspace, backspace);
|
||
|
||
/**
|
||
* @brief shell 删除
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellDelete(Shell* shell)
|
||
{
|
||
shellDeleteByte(shell, -1);
|
||
}
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x1B5B337E, shellDelete, delete);
|
||
|
||
/**
|
||
* @brief shell 回车处理
|
||
*
|
||
* @param shell shell对象
|
||
*/
|
||
void shellEnter(Shell* shell)
|
||
{
|
||
shellExec(shell);
|
||
shellWritePrompt(shell, 1);
|
||
}
|
||
#if SHELL_ENTER_LF == 1
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x0A000000, shellEnter, enter);
|
||
#endif
|
||
#if SHELL_ENTER_CR == 1
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x0D000000, shellEnter, enter);
|
||
#endif
|
||
#if SHELL_ENTER_CRLF == 1
|
||
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0) | SHELL_CMD_ENABLE_UNCHECKED,
|
||
0x0D0A0000, shellEnter, enter);
|
||
#endif
|
||
|
||
/**
|
||
* @brief shell 写命令帮助信息
|
||
*
|
||
* @param shell shell对象
|
||
* @param cmd 命令字符串
|
||
*/
|
||
static void shellWriteCommandHelp(Shell* shell, char* cmd)
|
||
{
|
||
ShellCommand* command = shellSeekCommand(shell,
|
||
cmd,
|
||
shell->commandList.base,
|
||
0);
|
||
if (command) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_HELP_HEADER]);
|
||
shellWriteString(shell, shellGetCommandName(command));
|
||
shellWriteString(shell, "\r\n");
|
||
shellWriteString(shell, shellGetCommandDesc(command));
|
||
shellWriteString(shell, "\r\n");
|
||
} else {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_NOT_FOUND]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief shell help
|
||
*
|
||
* @param argc 参数个数
|
||
* @param argv 参数
|
||
*/
|
||
void shellHelp(int argc, char* argv[])
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
SHELL_ASSERT(shell, return);
|
||
if (argc == 1) {
|
||
shellListAll(shell);
|
||
} else if (argc > 1) {
|
||
shellWriteCommandHelp(shell, argv[1]);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN) | SHELL_CMD_DISABLE_RETURN,
|
||
help, shellHelp, show command info\r\nhelp[cmd]);
|
||
|
||
/**
|
||
* @brief shell 输入处理
|
||
*
|
||
* @param shell shell对象
|
||
* @param data 输入数据
|
||
*/
|
||
void shellHandler(Shell* shell, char data)
|
||
{
|
||
CHECK_DATA(data, return);
|
||
SHELL_LOCK(shell);
|
||
|
||
#if SHELL_LOCK_TIMEOUT > 0
|
||
if (shell->info.user->data.user.password
|
||
&& strlen(shell->info.user->data.user.password) != 0
|
||
&& SHELL_GET_TICK()) {
|
||
if (SHELL_GET_TICK() - shell->info.activeTime > SHELL_LOCK_TIMEOUT) {
|
||
shell->status.isChecked = 0;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/* 根据记录的按键键值计算当前字节在按键键值中的偏移 */
|
||
char keyByteOffset = 24;
|
||
int keyFilter = 0x00000000;
|
||
if ((shell->parser.keyValue & 0x0000FF00) != 0x00000000) {
|
||
keyByteOffset = 0;
|
||
keyFilter = 0xFFFFFF00;
|
||
} else if ((shell->parser.keyValue & 0x00FF0000) != 0x00000000) {
|
||
keyByteOffset = 8;
|
||
keyFilter = 0xFFFF0000;
|
||
} else if ((shell->parser.keyValue & 0xFF000000) != 0x00000000) {
|
||
keyByteOffset = 16;
|
||
keyFilter = 0xFF000000;
|
||
}
|
||
|
||
/* 遍历ShellCommand列表,尝试进行按键键值匹配 */
|
||
ShellCommand* base = (ShellCommand*)shell->commandList.base;
|
||
for (short i = 0; i < shell->commandList.count; i++) {
|
||
/* 判断是否是按键定义并验证权限 */
|
||
if (base[i].attr.attrs.type == SHELL_TYPE_KEY
|
||
&& shellCheckPermission(shell, &(base[i])) == 0) {
|
||
/* 对输入的字节同按键键值进行匹配 */
|
||
if ((base[i].data.key.value & keyFilter) == shell->parser.keyValue
|
||
&& (base[i].data.key.value & (0xFF << keyByteOffset)) == (data << keyByteOffset)) {
|
||
shell->parser.keyValue |= data << keyByteOffset;
|
||
data = 0x00;
|
||
if (keyByteOffset == 0
|
||
|| (base[i].data.key.value & (0xFF << (keyByteOffset - 8))) == 0x00000000) {
|
||
if (base[i].data.key.function) {
|
||
base[i].data.key.function(shell);
|
||
}
|
||
shell->parser.keyValue = 0x00000000;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (data != 0x00) {
|
||
shell->parser.keyValue = 0x00000000;
|
||
shellNormalInput(shell, data);
|
||
}
|
||
|
||
if (SHELL_GET_TICK()) {
|
||
shell->info.activeTime = SHELL_GET_TICK();
|
||
}
|
||
SHELL_UNLOCK(shell);
|
||
}
|
||
|
||
#if SHELL_SUPPORT_END_LINE == 1
|
||
void shellWriteEndLine(Shell* shell, char* buffer, int len)
|
||
{
|
||
SHELL_LOCK(shell);
|
||
if (!shell->status.isActive) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CLEAR_LINE]);
|
||
}
|
||
shell->write(buffer, len);
|
||
|
||
if (!shell->status.isActive) {
|
||
shellWritePrompt(shell, 0);
|
||
if (shell->parser.length > 0) {
|
||
shellWriteString(shell, shell->parser.buffer);
|
||
for (short i = 0; i < shell->parser.length - shell->parser.cursor; i++) {
|
||
shellWriteByte(shell, '\b');
|
||
}
|
||
}
|
||
}
|
||
SHELL_UNLOCK(shell);
|
||
}
|
||
#endif /** SHELL_SUPPORT_END_LINE == 1 */
|
||
|
||
/**
|
||
* @brief shell 任务
|
||
*
|
||
* @param param 参数(shell对象)
|
||
*
|
||
*/
|
||
void shellTask(void* param)
|
||
{
|
||
Shell* shell = (Shell*)param;
|
||
char data;
|
||
#if SHELL_TASK_WHILE == 1
|
||
while (1) {
|
||
#endif
|
||
if (shell->read && shell->read(&data, 1) == 1) {
|
||
shellHandler(shell, data);
|
||
}
|
||
#if SHELL_TASK_WHILE == 1
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief shell 输出用户列表(shell调用)
|
||
*/
|
||
void shellUsers(void)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell) {
|
||
shellListUser(shell);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
users, shellUsers, list all user);
|
||
|
||
/**
|
||
* @brief shell 输出命令列表(shell调用)
|
||
*/
|
||
void shellCmds(void)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell) {
|
||
shellListCommand(shell);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
cmds, shellCmds, list all cmd);
|
||
|
||
/**
|
||
* @brief shell 输出变量列表(shell调用)
|
||
*/
|
||
void shellVars(void)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell) {
|
||
shellListVar(shell);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
vars, shellVars, list all var);
|
||
|
||
/**
|
||
* @brief shell 输出按键列表(shell调用)
|
||
*/
|
||
void shellKeys(void)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell) {
|
||
shellListKey(shell);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
keys, shellKeys, list all key);
|
||
|
||
/**
|
||
* @brief shell 清空控制台(shell调用)
|
||
*/
|
||
void shellClear(void)
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CLEAR_CONSOLE]);
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
|
||
clear, shellClear, clear console);
|
||
|
||
/**
|
||
* @brief shell执行命令
|
||
*
|
||
* @param shell shell对象
|
||
* @param cmd 命令字符串
|
||
* @return int 返回值
|
||
*/
|
||
int shellRun(Shell* shell, const char* cmd)
|
||
{
|
||
SHELL_ASSERT(shell && cmd, return -1);
|
||
char active = shell->status.isActive;
|
||
if (strlen(cmd) > shell->parser.bufferSize - 1) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_CMD_TOO_LONG]);
|
||
return -1;
|
||
} else {
|
||
shell->parser.length = shellStringCopy(shell->parser.buffer, (char*)cmd);
|
||
shellExec(shell);
|
||
shell->status.isActive = active;
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
void shellKill(int pid)
|
||
{
|
||
kill(pid);
|
||
}
|
||
|
||
/**
|
||
* @brief ls 打印当前路径下所有文件(文件系统调用)
|
||
*/
|
||
void shellLs(const char* path, ...)
|
||
{
|
||
if ((uintptr_t*)path == (uintptr_t*)shellLs || *path == '\0') {
|
||
path = ".";
|
||
}
|
||
ls(&session_fs, (char*)path);
|
||
}
|
||
|
||
/**
|
||
* @brief cd 切换到指定路径下(文件系统调用)
|
||
*/
|
||
void shellCd(const char* path)
|
||
{
|
||
cd(&session_fs, (char*)path);
|
||
}
|
||
|
||
/**
|
||
* @brief mkdir 创建指定路径的一个目录(文件系统调用)
|
||
*/
|
||
void shellMkdir(const char* path)
|
||
{
|
||
mkdir(&session_fs, (char*)path);
|
||
}
|
||
|
||
/**
|
||
* @brief Delete 删除指定路径的一个文件或者目录(文件系统调用)。如果是目录,默认删除目录下全部文件
|
||
*/
|
||
void shellRm(const char* path)
|
||
{
|
||
rm(&session_fs, (char*)path);
|
||
}
|
||
|
||
/**
|
||
* @brief Cat 将指定文件内容在控制台上打印出来(文件系统调用)
|
||
*/
|
||
void shellCat(const char* path)
|
||
{
|
||
cat(&session_fs, (char*)path);
|
||
}
|
||
|
||
/**
|
||
* @brief 展示内核中全部tasks状态
|
||
*/
|
||
void shellShowTasks()
|
||
{
|
||
show_task();
|
||
}
|
||
|
||
/**
|
||
* @brief 展示当前内存信息
|
||
*/
|
||
void shellShowMemInfo()
|
||
{
|
||
show_mem();
|
||
}
|
||
|
||
/**
|
||
* @brief 展示当前cpu状态
|
||
*/
|
||
void shellShowCpusInfo()
|
||
{
|
||
show_cpu();
|
||
}
|
||
|
||
#if SHELL_EXEC_UNDEF_FUNC == 1
|
||
/**
|
||
* @brief shell执行未定义函数
|
||
*
|
||
* @param argc 参数个数
|
||
* @param argv 参数
|
||
* @return int 返回值
|
||
*/
|
||
int shellExecute(int argc, char* argv[])
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
if (shell && argc >= 2) {
|
||
size_t result;
|
||
if (shellExtParsePara(shell, argv[1], NULL, &result) != 0) {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_PARAM_ERROR]);
|
||
return -1;
|
||
}
|
||
int (*func)() = (int (*)())result;
|
||
ShellCommand command = {
|
||
.attr.value = SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)
|
||
| SHELL_CMD_DISABLE_RETURN,
|
||
.data.cmd.function = func,
|
||
};
|
||
return shellExtRun(shell, &command, argc - 1, &argv[1]);
|
||
} else {
|
||
shellWriteString(shell, shellText[SHELL_TEXT_PARAM_ERROR]);
|
||
return -1;
|
||
}
|
||
}
|
||
SHELL_EXPORT_CMD(
|
||
SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN) | SHELL_CMD_DISABLE_RETURN,
|
||
exec, shellExecute, execute function undefined);
|
||
#endif
|
||
|
||
#if SHELL_KEEP_RETURN_VALUE == 1
|
||
/**
|
||
* @brief shell返回值获取
|
||
* 获取上一次执行的命令的返回值
|
||
*
|
||
* @return int 返回值
|
||
*/
|
||
static int shellRetValGet()
|
||
{
|
||
Shell* shell = shellGetCurrent();
|
||
return shell ? shell->info.retVal : 0;
|
||
}
|
||
static ShellNodeVarAttr shellRetVal = {
|
||
.get = shellRetValGet
|
||
};
|
||
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_VAR_NODE) | SHELL_CMD_READ_ONLY,
|
||
RETVAL, &shellRetVal, return value of last command);
|
||
#endif /** SHELL_KEEP_RETURN_VALUE == 1 */
|