ChibiOS/os/sb/apps/msh/main.c

498 lines
9.7 KiB
C

/*
ChibiOS - Copyright (C) 2006..2022 Giovanni Di Sirio
Licensed 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 <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include "sbuser.h"
#include "paths.h"
#include "sglob.h"
#define SHELL_MAX_LINE_LENGTH 128
#define SHELL_MAX_ARGUMENTS 20
#define SHELL_PROMPT_STR "> "
#define SHELL_NEWLINE_STR "\r\n"
#define SHELL_WELCOME_STR "ChibiOS/SB Mini Shell"
#define SHELL_DEFAULT_PATH "/bin"
#define SHELL_EXECUTABLE_EXTENSION ".elf"
static const char *prompt;
static char pathbuf[1024];
static void shell_write(const char *s) {
size_t n = strlen(s);
(void) write(STDOUT_FILENO, s, n);
}
static void shell_error(const char *s) {
size_t n = strlen(s);
(void) write(STDERR_FILENO, s, n);
}
static void shell_putc(char c) {
(void) write(STDOUT_FILENO, &c, 1);
}
static void shell_usage(const char *s) {
shell_error("usage: ");
shell_error(s);
shell_error(SHELL_NEWLINE_STR);
}
bool shell_getline(char *line, size_t size) {
char *p = line;
while (true) {
char c;
if (read(STDIN_FILENO, &c, 1) == 0)
return true;
if ((c == 4) && (p == line)) {
return true;
}
if ((c == 8) || (c == 127)) {
if (p != line) {
shell_putc(8);
shell_putc(' ');
shell_putc(8);
p--;
}
continue;
}
if (c == '\r') {
shell_write(SHELL_NEWLINE_STR);
*p = 0;
return false;
}
if (c < 0x20)
continue;
if (p < line + size - 1) {
shell_putc(c);
*p++ = c;
}
}
}
static char *fetch_argument(char **pp) {
char *p, *ap;
/* Skipping white space.*/
ap = *pp;
ap += strspn(ap, " \t");
if (*ap == '\0') {
return NULL;
}
if (*ap == '"') {
/* If an argument starts with a double quote then its delimiter is another
quote.*/
ap++;
p = strpbrk(ap, "\"");
}
else {
/* The delimiter is white space.*/
p = strpbrk(ap, " \t");
}
if (p != NULL) {
/* Replacing the delimiter with a zero.*/
*p++ = '\0';
}
else {
/* Final one, pointing on the final zero.*/
p = ap + strlen(ap);
}
*pp = p;
return ap;
}
static void cmd_cd(int argc, char *argv[]) {
int ret;
if (argc != 2) {
shell_usage("cd <path>");
return;
}
ret = chdir(argv[1]);
if (ret == -1) {
shell_error(argv[1]);
shell_error(": No such file or directory" SHELL_NEWLINE_STR);
}
}
static void cmd_pwd(int argc, char *argv[]) {
char *p;
(void)argv;
if (argc != 1) {
shell_usage("pwd");
return;
}
p = getcwd(pathbuf, sizeof pathbuf);
if (p != NULL) {
shell_write(pathbuf);
shell_write(SHELL_NEWLINE_STR);
}
}
static void cmd_echo(int argc, char *argv[]) {
int i;
for (i = 1; i < argc; i++) {
shell_write(argv[i]);
shell_write(" ");
}
shell_write(SHELL_NEWLINE_STR);
}
static void cmd_env(int argc, char *argv[]) {
extern char **environ;
char **pp;
(void)argv;
if (argc != 1) {
shell_usage("env");
return;
}
pp = environ;
while (*pp != NULL) {
shell_write(*pp++);
shell_write(SHELL_NEWLINE_STR);
}
}
static void cmd_exit(int argc, char *argv[]) {
msg_t msg;
if (argc == 1) {
msg = 0;
}
else if (argc == 2) {
msg = atoi(argv[1]);
}
else {
shell_usage("exit [n]");
return;
}
_exit(msg);
}
static void cmd_mkdir(int argc, char *argv[]) {
int ret;
if (argc != 2) {
shell_usage("mkdir <dirname>");
return;
}
ret = mkdir(argv[1], 0777);
if (ret == -1) {
shell_error(argv[1]);
shell_error(": Creation failed" SHELL_NEWLINE_STR);
}
}
static void cmd_mv(int argc, char *argv[]) {
int ret;
if (argc != 3) {
shell_usage("mv <old> <new>");
return;
}
ret = rename(argv[1], argv[2]);
if (ret == -1) {
shell_error(argv[1]);
shell_error(": No such file or directory" SHELL_NEWLINE_STR);
}
}
static void cmd_path(int argc, char *argv[]) {
char *s;
(void)argv;
if (argc != 1) {
shell_usage("path");
return;
}
s = getenv("PATH");
if (s != NULL) {
shell_write(s);
shell_write(SHELL_NEWLINE_STR);
}
}
static void cmd_rm(int argc, char *argv[]) {
int ret;
if (argc != 2) {
shell_usage("rm <filename>");
return;
}
ret = unlink(argv[1]);
if (ret == -1) {
shell_error(argv[1]);
shell_error(": No such file or directory" SHELL_NEWLINE_STR);
}
}
static void cmd_rmdir(int argc, char *argv[]) {
int ret;
if (argc != 2) {
shell_usage("rmdir <dirname>");
return;
}
ret = rmdir(argv[1]);
if (ret == -1) {
shell_error(argv[1]);
shell_error(": Remove failed" SHELL_NEWLINE_STR);
}
}
static bool shell_execute(int argc, char *argv[]) {
extern int runelf(int argc, char *argv[], char *envp[]);
char *fname = argv[0];
int i, ret;
static const struct {
const char *name;
void (*cmdf)(int argc, char *argv[]);
} builtins[] = {
{"cd", cmd_cd},
{"echo", cmd_echo},
{"env", cmd_env},
{"exit", cmd_exit},
{"mkdir", cmd_mkdir},
{"mv", cmd_mv},
{"path", cmd_path},
{"pwd", cmd_pwd},
{"rm", cmd_rm},
{"rmdir", cmd_rmdir},
{NULL, NULL}
};
i = 0;
while (builtins[i].name != NULL) {
if (strcmp(builtins[i].name, fname) == 0) {
builtins[i].cmdf(argc, argv);
return false;
}
i++;
}
if (index(fname, '/') != NULL) {
/* It is a path, executing as-is without scanning the PATH variable.*/
ret = runelf(argc, argv, environ);
if (ret != -1) {
return false;
}
}
else {
char *p;
p = getenv("PATH");
if (p == NULL) {
p = SHELL_DEFAULT_PATH;
}
/* Searching for executable.*/
while (true) {
size_t n;
/* Getting next path, exit if there is an empty entry.*/
n = strcspn(p, ":");
if (n == 0U) {
errno = ENOENT;
break;
}
/* Error if the path is too long.*/
if (n >= sizeof pathbuf) {
errno = ERANGE;
break;
}
/* Non absolute paths are ignored.*/
if (*p == '/') {
/* Building combined path, error on path buffer overflow.*/
memmove(pathbuf, p, n);
pathbuf[n] = '\0';
if (path_append(pathbuf, fname, sizeof pathbuf) == 0U) {
errno = ERANGE;
break;
}
/* Enforcing an executable file extension, there is no eXecute
attribute to handle.*/
if (path_add_extension(pathbuf,
SHELL_EXECUTABLE_EXTENSION,
sizeof pathbuf) == 0U) {
errno = ERANGE;
break;
}
/* Trying to execute from, this path.*/
argv[0] = pathbuf;
ret = runelf(argc, argv, environ);
argv[0] = fname;
if (ret != -1) {
return false;
}
if (errno != ENOENT) {
break;
}
}
/* On the next path, if any.*/
p += n;
if (*p == '\0') {
errno = ENOENT;
break;
}
p++;
}
}
return true;
}
/*
* Application entry point.
*/
int main(int argc, char *argv[], char *envp[]) {
#if 0
/* Enable for RAM debug.*/
asm volatile ("bkpt");
#endif
(void)argc;
(void)argv;
(void)envp;
prompt = getenv("PROMPT");
if (prompt == NULL) {
prompt = SHELL_PROMPT_STR;
}
/* Welcome.*/
shell_write(SHELL_NEWLINE_STR SHELL_WELCOME_STR SHELL_NEWLINE_STR);
while (true) {
int i, n, ret;
char line[SHELL_MAX_LINE_LENGTH];
char *args[SHELL_MAX_ARGUMENTS + 1];
char *ap, *tokp;
sglob_t sglob;
/* Prompt.*/
shell_write(prompt);
/* Reading input line.*/
if (shell_getline(line, SHELL_MAX_LINE_LENGTH)) {
shell_write("exit" SHELL_NEWLINE_STR);
break;
}
/* Fetching arguments.*/
tokp = line;
n = 0;
while ((ap = fetch_argument(&tokp)) != NULL) {
if (n < SHELL_MAX_ARGUMENTS) {
args[n++] = ap;
}
else {
n = 0;
shell_error("too many arguments" SHELL_NEWLINE_STR);
break;
}
}
args[n] = NULL;
/* Special case for empty lines.*/
if (n == 0) {
continue;
}
/* Adding the command name as-is.*/
sglob_init(&sglob);
ret = sglob_add(&sglob, args[0]);
if (ret == SGLOB_NOSPACE) {
goto outofmem;
}
/* Arguments processing except those starting with a "-" which are
options, those are added without processing.*/
for (i = 1; i < n; i++) {
/* Accumulating arguments expansion into the "glob".*/
if (*args[i] != '-') {
ret = sglob_match(&sglob, args[i]);
}
else {
ret = sglob_add(&sglob, args[i]);
}
if (ret == SGLOB_NOSPACE) {
goto outofmem;
}
}
/* Building the full arguments array.*/
ret = sglob_build(&sglob, 0);
if (ret == SGLOB_NOSPACE) {
goto outofmem;
}
/* Executing the glob.*/
if (shell_execute(sglob.sgl_pathc, sglob.sgl_pathv)) {
shell_error("msh: ");
shell_error(args[0]);
shell_error(": command not found" SHELL_NEWLINE_STR);
}
/* Freeing memory allocated during processing.*/
sglob_free(&sglob);
continue;
outofmem:
shell_error("msh: out of memory" SHELL_NEWLINE_STR);
sglob_free(&sglob);
}
}