diff options
Diffstat (limited to 'kernel/test/kshell')
| -rw-r--r-- | kernel/test/kshell/command.c | 46 | ||||
| -rw-r--r-- | kernel/test/kshell/command.h | 20 | ||||
| -rw-r--r-- | kernel/test/kshell/commands.c | 404 | ||||
| -rw-r--r-- | kernel/test/kshell/commands.h | 32 | ||||
| -rw-r--r-- | kernel/test/kshell/io.c | 78 | ||||
| -rw-r--r-- | kernel/test/kshell/kshell.c | 504 | ||||
| -rw-r--r-- | kernel/test/kshell/priv.h | 43 | ||||
| -rw-r--r-- | kernel/test/kshell/tokenizer.c | 74 | ||||
| -rw-r--r-- | kernel/test/kshell/tokenizer.h | 39 |
9 files changed, 1240 insertions, 0 deletions
diff --git a/kernel/test/kshell/command.c b/kernel/test/kshell/command.c new file mode 100644 index 0000000..836b743 --- /dev/null +++ b/kernel/test/kshell/command.c @@ -0,0 +1,46 @@ +#include "command.h" + +#include "mm/kmalloc.h" + +#include "util/debug.h" +#include "util/string.h" + +kshell_command_t *kshell_command_create(const char *name, + kshell_cmd_func_t cmd_func, + const char *desc) +{ + kshell_command_t *cmd; + size_t len; + + KASSERT(NULL != name); + KASSERT(NULL != cmd_func); + + cmd = (kshell_command_t *)kmalloc(sizeof(kshell_command_t)); + if (NULL == cmd) + { + return NULL; + } + + len = strnlen(name, KSH_CMD_NAME_LEN); + strncpy(cmd->kc_name, name, len); + cmd->kc_name[len] = '\0'; + + cmd->kc_cmd_func = cmd_func; + + if (NULL != desc) + { + len = strnlen(desc, KSH_DESC_LEN); + strncpy(cmd->kc_desc, desc, len); + cmd->kc_desc[len] = '\0'; + } + else + { + cmd->kc_desc[0] = '\0'; + } + + list_link_init(&cmd->kc_commands_link); + + return cmd; +} + +void kshell_command_destroy(kshell_command_t *cmd) { kfree(cmd); } diff --git a/kernel/test/kshell/command.h b/kernel/test/kshell/command.h new file mode 100644 index 0000000..96a5cb0 --- /dev/null +++ b/kernel/test/kshell/command.h @@ -0,0 +1,20 @@ +#pragma once + +#include "priv.h" + +#include "test/kshell/kshell.h" + +typedef struct kshell_command +{ + char kc_name[KSH_CMD_NAME_LEN + 1]; + kshell_cmd_func_t kc_cmd_func; + char kc_desc[KSH_DESC_LEN + 1]; + + list_link_t kc_commands_link; +} kshell_command_t; + +kshell_command_t *kshell_command_create(const char *name, + kshell_cmd_func_t cmd_func, + const char *desc); + +void kshell_command_destroy(kshell_command_t *cmd); diff --git a/kernel/test/kshell/commands.c b/kernel/test/kshell/commands.c new file mode 100644 index 0000000..5ad5b11 --- /dev/null +++ b/kernel/test/kshell/commands.c @@ -0,0 +1,404 @@ +#include "commands.h" +#include "errno.h" + +#include "command.h" + +#ifdef __VFS__ + +#include "fs/fcntl.h" +#include "fs/vfs_syscall.h" +#include "fs/vnode.h" + +#endif + +#include "test/kshell/io.h" + +#include "util/debug.h" +#include "util/string.h" + +list_t kshell_commands_list = LIST_INITIALIZER(kshell_commands_list); + +long kshell_help(kshell_t *ksh, size_t argc, char **argv) +{ + /* Print a list of available commands */ + char spaces[KSH_CMD_NAME_LEN]; + memset(spaces, ' ', KSH_CMD_NAME_LEN); + + kprintf(ksh, "Available commands:\n"); + list_iterate(&kshell_commands_list, cmd, kshell_command_t, + kc_commands_link) + { + KASSERT(NULL != cmd); + size_t namelen = strnlen(cmd->kc_name, KSH_CMD_NAME_LEN); + spaces[KSH_CMD_NAME_LEN - namelen] = '\0'; + kprintf(ksh, "%s%s%s\n", cmd->kc_name, spaces, cmd->kc_desc); + spaces[KSH_CMD_NAME_LEN - namelen] = ' '; + } + + return 0; +} + +long kshell_exit(kshell_t *ksh, size_t argc, char **argv) +{ + panic("kshell: kshell_exit should NEVER be called"); +} + +long kshell_clear(kshell_t *ksh, size_t argc, char **argv) +{ + kprintf(ksh, "\033[2J\033[1;1H"); + + // kprintf(ksh, "\033[10A"); + return 0; +} + +long kshell_halt(kshell_t *ksh, size_t argc, char **argv) +{ + proc_kill_all(); + return 0; +} + +long kshell_echo(kshell_t *ksh, size_t argc, char **argv) +{ + if (argc == 1) + { + kprintf(ksh, "\n"); + } + else + { + for (size_t i = 1; i < argc - 1; i++) + { + kprintf(ksh, "%s ", argv[i]); + } + kprintf(ksh, "%s\n", argv[argc - 1]); + } + + return 0; +} + +#ifdef __VFS__ + +long kshell_cat(kshell_t *ksh, size_t argc, char **argv) +{ + if (argc < 2) + { + kprintf(ksh, "Usage: cat <files>\n"); + return 0; + } + + char buf[KSH_BUF_SIZE]; + for (size_t i = 1; i < argc; i++) + { + int fd = (int)do_open(argv[i], O_RDONLY); + if (fd < 0) + { + kprintf(ksh, "Error opening file: %s\n", argv[i]); + continue; + } + + long retval; + while ((retval = do_read(fd, buf, KSH_BUF_SIZE)) > 0) + { + retval = kshell_write_all(ksh, buf, (size_t)retval); + if (retval < 0) + break; + } + if (retval < 0) + { + kprintf(ksh, "Error reading or writing %s: %s\n", argv[i], strerror((int)-retval)); + } + + retval = do_close(fd); + if (retval < 0) + { + panic("kshell: Error closing file %s: %s\n", argv[i], + strerror((int)-retval)); + } + } + + return 0; +} + +long kshell_ls(kshell_t *ksh, size_t argc, char **argv) +{ + size_t arglen; + long ret; + int fd; + dirent_t dirent; + stat_t statbuf; + char direntname[KSH_BUF_SIZE]; + + memset(direntname, '\0', KSH_BUF_SIZE); + + if (argc > 2) + { + kprintf(ksh, "Usage: ls <directory>\n"); + return 0; + } + else if (argc == 2) + { + if ((ret = do_stat(argv[1], &statbuf)) < 0) + { + if (ret == -ENOENT) + { + kprintf(ksh, "%s does not exist\n", argv[1]); + return 0; + } + else + { + return ret; + } + } + if (!S_ISDIR(statbuf.st_mode)) + { + kprintf(ksh, "%s is not a directory\n", argv[1]); + return 0; + } + + fd = (int)do_open(argv[1], O_RDONLY); + if (fd < 0) + { + kprintf(ksh, "Could not find directory: %s\n", argv[1]); + return 0; + } + arglen = strnlen(argv[1], KSH_BUF_SIZE); + } + else + { + KASSERT(argc == 1); + fd = (int)do_open(".", O_RDONLY); + if (fd < 0) + { + kprintf(ksh, "Could not find directory: .\n"); + return 0; + } + arglen = 1; + } + + if (argc == 2) + memcpy(direntname, argv[1], arglen); + else + direntname[0] = '.'; + + direntname[arglen] = '/'; + direntname[arglen + NAME_LEN + 1] = '\0'; + + while ((ret = do_getdent(fd, &dirent)) == sizeof(dirent_t)) + { + memcpy(direntname + arglen + 1, dirent.d_name, NAME_LEN + 1); + ret = do_stat(direntname, &statbuf); + if (ret < 0) + { + kprintf(ksh, "Error stat\'ing `%s`: %s\n", dirent.d_name, strerror((int)-ret)); + continue; + } + if (S_ISDIR(statbuf.st_mode)) + { + kprintf(ksh, "%s/\n", dirent.d_name); + } + else + { + kprintf(ksh, "%s\n", dirent.d_name); + } + } + + do_close(fd); + return ret; +} + +long kshell_cd(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + if (argc < 2) + { + kprintf(ksh, "Usage: cd <directory>\n"); + return 0; + } + + long ret = do_chdir(argv[1]); + if (ret < 0) + { + kprintf(ksh, "cd: `%s`: %s\n", argv[1], strerror((int)-ret)); + } + return 0; +} + +long kshell_rm(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + + if (argc < 2) + { + kprintf(ksh, "Usage: rm <file>\n"); + return 0; + } + + long ret = do_unlink(argv[1]); + if (ret < 0) + { + kprintf(ksh, "rm: `%s`: %s\n", argv[1], strerror((int)-ret)); + } + + return 0; +} + +long kshell_link(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + + if (argc < 3) + { + kprintf(ksh, "Usage: link <src> <dst>\n"); + return 0; + } + + long ret = do_link(argv[1], argv[2]); + if (ret < 0) + { + kprintf(ksh, "Error linking %s to %s: %s\n", argv[1], argv[2], strerror((int)-ret)); + } + + return 0; +} + +long kshell_rmdir(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + if (argc < 2) + { + kprintf(ksh, "Usage: rmdir DIRECTORY...\n"); + return 1; + } + + long exit_val = 0; + for (size_t i = 1; i < argc; i++) + { + long ret = do_rmdir(argv[i]); + if (ret < 0) + { + kprintf(ksh, "rmdir: failed to remove directory `%s': %s\n", + argv[i], strerror((int)-ret)); + exit_val = 1; + } + } + + return exit_val; +} + +long kshell_mkdir(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + if (argc < 2) + { + kprintf(ksh, "Usage: mkdir DIRECTORY...\n"); + return 1; + } + + long exit_val = 0; + for (size_t i = 1; i < argc; i++) + { + long ret = do_mkdir(argv[i]); + if (ret < 0) + { + kprintf(ksh, "mkdir: failed to create directory `%s': %s\n", + argv[i], strerror((int)-ret)); + exit_val = 1; + } + } + + return exit_val; +} + +static const char *get_file_type_str(int mode) +{ + if (S_ISCHR(mode)) + { + return "character special file"; + } + else if (S_ISDIR(mode)) + { + return "directory"; + } + else if (S_ISBLK(mode)) + { + return "block special file"; + } + else if (S_ISREG(mode)) + { + return "regular file"; + } + else if (S_ISLNK(mode)) + { + return "symbolic link"; + } + else + { + return "unknown"; + } +} + +long kshell_stat(kshell_t *ksh, size_t argc, char **argv) +{ + KASSERT(ksh && argc && argv); + long exit_val = 0; + + if (argc < 2) + { + kprintf(ksh, "Usage: stat FILE...\n"); + return 1; + } + + for (size_t i = 1; i < argc; i++) + { + stat_t buf; + long ret = do_stat(argv[i], &buf); + if (ret < 0) + { + kprintf(ksh, "Cannot stat `%s': %s\n", argv[i], + strerror((int)-ret)); + exit_val = 1; + continue; + } + const char *file_type_str = get_file_type_str(buf.st_mode); + kprintf(ksh, "File: `%s'\n", argv[i]); + kprintf(ksh, "Size: %d\n", buf.st_size); + kprintf(ksh, "Blocks: %d\n", buf.st_blocks); + kprintf(ksh, "IO Block: %d\n", buf.st_blksize); + kprintf(ksh, "%s\n", file_type_str); + kprintf(ksh, "Inode: %d\n", buf.st_ino); + kprintf(ksh, "Links: %d\n", buf.st_nlink); + } + + return exit_val; +} + +long vfstest_main(int, void *); + +long kshell_vfs_test(kshell_t *ksh, size_t argc, char **argv) +{ + kprintf(ksh, "TEST VFS: Testing... Please wait.\n"); + + long ret = vfstest_main(1, NULL); + + kprintf(ksh, "TEST VFS: testing complete, check console for results\n"); + + return ret; +} + +#endif + +#ifdef __S5FS__ + +long s5fstest_main(int, void *); + +long kshell_s5fstest(kshell_t *ksh, size_t argc, char **argv) +{ + kprintf(ksh, "TEST S5FS: Testing... Please wait.\n"); + + long ret = s5fstest_main(1, NULL); + + kprintf(ksh, "TEST S5FS: testing complete, check console for results\n"); + + return ret; +} + +#endif diff --git a/kernel/test/kshell/commands.h b/kernel/test/kshell/commands.h new file mode 100644 index 0000000..bf0bf1a --- /dev/null +++ b/kernel/test/kshell/commands.h @@ -0,0 +1,32 @@ +#pragma once + +#include "test/kshell/kshell.h" + +#define KSHELL_CMD(name) \ + long kshell_##name(kshell_t *ksh, size_t argc, char **argv) + +KSHELL_CMD(help); + +KSHELL_CMD(exit); + +KSHELL_CMD(halt); + +KSHELL_CMD(echo); + +KSHELL_CMD(clear); + +#ifdef __VFS__ +KSHELL_CMD(cat); +KSHELL_CMD(ls); +KSHELL_CMD(cd); +KSHELL_CMD(rm); +KSHELL_CMD(link); +KSHELL_CMD(rmdir); +KSHELL_CMD(mkdir); +KSHELL_CMD(stat); +KSHELL_CMD(vfs_test); +#endif + +#ifdef __S5FS__ +KSHELL_CMD(s5fstest); +#endif diff --git a/kernel/test/kshell/io.c b/kernel/test/kshell/io.c new file mode 100644 index 0000000..65d816d --- /dev/null +++ b/kernel/test/kshell/io.c @@ -0,0 +1,78 @@ +#include "test/kshell/io.h" +#include "util/debug.h" + +#include "priv.h" + +#ifndef __VFS__ + +#include "drivers/chardev.h" + +#endif + +#ifdef __VFS__ + +#include "fs/vfs_syscall.h" + +#endif + +#include "util/printf.h" +#include "util/string.h" + +/* + * If VFS is enabled, we can just use the syscalls. + * + * If VFS is not enabled, then we need to explicitly call the byte + * device. + */ + +#ifdef __VFS__ + +long kshell_write(kshell_t *ksh, const void *buf, size_t nbytes) +{ + long retval = do_write(ksh->ksh_out_fd, buf, nbytes); + KASSERT(retval < 0 || (size_t)retval == nbytes); + return retval; +} + +long kshell_read(kshell_t *ksh, void *buf, size_t nbytes) +{ + return do_read(ksh->ksh_in_fd, buf, nbytes); +} + +long kshell_write_all(kshell_t *ksh, void *buf, size_t nbytes) +{ + /* See comment in kshell_write */ + return kshell_write(ksh, buf, nbytes); +} + +#else + +long kshell_read(kshell_t *ksh, void *buf, size_t nbytes) +{ + return ksh->ksh_cd->cd_ops->read(ksh->ksh_cd, 0, buf, nbytes); +} + +long kshell_write(kshell_t *ksh, const void *buf, size_t nbytes) +{ + return ksh->ksh_cd->cd_ops->write(ksh->ksh_cd, 0, buf, nbytes); +} + +#endif + +void kprint(kshell_t *ksh, const char *fmt, va_list args) +{ + char buf[KSH_BUF_SIZE]; + size_t count; + + vsnprintf(buf, sizeof(buf), fmt, args); + count = strnlen(buf, sizeof(buf)); + kshell_write(ksh, buf, count); +} + +void kprintf(kshell_t *ksh, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + kprint(ksh, fmt, args); + va_end(args); +} diff --git a/kernel/test/kshell/kshell.c b/kernel/test/kshell/kshell.c new file mode 100644 index 0000000..a26c42c --- /dev/null +++ b/kernel/test/kshell/kshell.c @@ -0,0 +1,504 @@ +#include "test/kshell/kshell.h" +#include <util/printf.h> + +#include "config.h" + +#include "command.h" +#include "commands.h" +#include "tokenizer.h" + +#ifndef __VFS__ + +#include "drivers/chardev.h" +#include "drivers/tty/tty.h" + +#endif + +#include "mm/kmalloc.h" + +#include "proc/proc.h" + +#ifdef __VFS__ + +#include "fs/fcntl.h" +#include "fs/open.h" +#include "fs/vfs_syscall.h" + +#endif + +#include "test/kshell/io.h" + +#include "util/debug.h" +#include "util/string.h" + +void *kshell_proc_run(long tty, void *arg2) +{ + // Create kernel shell on given TTY + kshell_t *kshell = kshell_create((uint8_t)tty); + if (!kshell) + { + do_exit(-1); + } + + while (kshell_execute_next(kshell) > 0) + ; + kshell_destroy(kshell); + return NULL; +} + +void kshell_init() +{ + kshell_add_command("help", kshell_help, + "prints a list of available commands"); + kshell_add_command("echo", kshell_echo, "display a line of text"); + kshell_add_command("clear", kshell_clear, "clears the screen"); +#ifdef __VFS__ + kshell_add_command("cat", kshell_cat, + "concatenate files and print on the standard output"); + kshell_add_command("ls", kshell_ls, "list directory contents"); + kshell_add_command("cd", kshell_cd, "change the working directory"); + kshell_add_command("rm", kshell_rm, "remove files"); + kshell_add_command("link", kshell_link, + "call the link function to create a link to a file"); + kshell_add_command("rmdir", kshell_rmdir, "remove empty directories"); + kshell_add_command("mkdir", kshell_mkdir, "make directories"); + kshell_add_command("stat", kshell_stat, "display file status"); + kshell_add_command("vfstest", kshell_vfs_test, "runs VFS tests"); +#endif + +#ifdef __S5FS__ + kshell_add_command("s5fstest", kshell_s5fstest, "runs S5FS tests"); +#endif + + kshell_add_command("halt", kshell_halt, "halts the systems"); + kshell_add_command("exit", kshell_exit, "exits the shell"); +} + +void kshell_add_command(const char *name, kshell_cmd_func_t cmd_func, + const char *desc) +{ + kshell_command_t *cmd; + + cmd = kshell_command_create(name, cmd_func, desc); + KASSERT(NULL != cmd); + list_insert_tail(&kshell_commands_list, &cmd->kc_commands_link); + + dprintf("Added %s command\n", name); +} + +kshell_t *kshell_create(uint8_t ttyid) +{ + kshell_t *ksh; + + ksh = (kshell_t *)kmalloc(sizeof(kshell_t)); + if (NULL == ksh) + { + dprintf("Not enough memory to create kshell\n"); + return NULL; + } + +#ifdef __VFS__ + long fd; + char tty_path[MAXPATHLEN]; + + snprintf(tty_path, sizeof(tty_path), "/dev/tty%u", ttyid); + if ((fd = do_open(tty_path, O_RDWR)) < 0) + { + dprintf("Couldn't open %s\n", tty_path); + kfree(ksh); + return NULL; + } + ksh->ksh_out_fd = ksh->ksh_in_fd = ksh->ksh_fd = (int)fd; +#else + chardev_t *cd; + cd = chardev_lookup(MKDEVID(TTY_MAJOR, ttyid)); + if (NULL == cd) + { + dprintf("Couldn't find TTY with ID %u\n", ttyid); + kfree(ksh); + return NULL; + } + ksh->ksh_cd = cd; +#endif + + dprintf("kshell successfully created on TTY %u\n", ttyid); + return ksh; +} + +void kshell_destroy(kshell_t *ksh) +{ + KASSERT(NULL != ksh); + kprintf(ksh, "Bye!\n"); +#ifdef __VFS__ + if (do_close(ksh->ksh_fd) < 0) + { + panic("Error closing TTY file descriptor\n"); + } + dprintf("kshell with file descriptor %d destroyed\n", ksh->ksh_fd); +#else + dprintf("kshell on byte device %u destroyed\n", ksh->ksh_cd->cd_id); +#endif + kfree(ksh); +} + +/** + * Removes the token from the input line it came from, replacing it + * with spaces. + * + * @param ksh the kshell + * @param token the token to scrub + */ +static void kshell_scrub_token(kshell_t *ksh, kshell_token_t *token) +{ + KASSERT(NULL != ksh); + KASSERT(NULL != token); + KASSERT(NULL != token->kt_text); + + memset(token->kt_text, ' ', token->kt_textlen); +} + +/** + * Finds the redirection operators ('<' and '>') in the input line, + * stores the name of the file to redirect stdout in in redirect_out + * and the name of the file to redirect stdin in redirect_in, and + * removes any trace of the redirection from the input line. + * + * @param ksh the kshell + * @param line the input line + * @param redirect_in buffer to store the name of the file to redirect + * stdin from. Buffer size assumed to be at least MAXPATHLEN + * @param redirect_out buffer to store the name of the file to stdout + * to. Buffer size assumed to be at least MAXPATHLEN + * @param append out parameter containing true if the file stdout is + * being redirected to should be appeneded to + * @return 0 on success and <0 on error + */ +static long kshell_find_redirection(kshell_t *ksh, char *line, + char *redirect_in, char *redirect_out, + int *append) +{ + long retval; + kshell_token_t token; + + while ((retval = kshell_next_token(ksh, line, &token)) > 0) + { + KASSERT(token.kt_type != KTT_EOL); + line += retval; + + if (token.kt_type == KTT_WORD) + { + continue; + } + + char *redirect = NULL; + if (token.kt_type == KTT_REDIRECT_OUT) + { + redirect = redirect_out; + *append = 0; + } + else if (token.kt_type == KTT_REDIRECT_OUT_APPEND) + { + redirect = redirect_out; + *append = 1; + } + else if (token.kt_type == KTT_REDIRECT_IN) + { + redirect = redirect_in; + } + kshell_scrub_token(ksh, &token); + + if ((retval = kshell_next_token(ksh, line, &token)) == 0) + { + goto unexpected_token; + } + KASSERT(retval > 0); + + if (token.kt_type != KTT_WORD) + { + goto unexpected_token; + } + strncpy(redirect, token.kt_text, token.kt_textlen); + redirect[token.kt_textlen] = '\0'; + kshell_scrub_token(ksh, &token); + } + return 0; + +unexpected_token: + kprintf(ksh, "kshell: Unexpected token '%s'\n", + kshell_token_type_str(token.kt_type)); + return -1; +} + +/** + * Ignoring whitespace, finds the next argument from a string. + * + * @param ksh the kshell + * @param line the string to find arguments in + * @param arg out parameter which should point to the beginning of the + * next argument if any were found + * @param arglen the length of the argument if any were found + * @return 0 if no argument was found, and the number of bytes read + * otherwise + */ +static long kshell_find_next_arg(kshell_t *ksh, char *line, char **arg, + size_t *arglen) +{ + long retval; + kshell_token_t token; + + if ((retval = kshell_next_token(ksh, line, &token)) == 0) + { + KASSERT(token.kt_type == KTT_EOL); + return retval; + } + KASSERT(token.kt_type == KTT_WORD); + *arg = token.kt_text; + *arglen = token.kt_textlen; + + /* + * This is a little hacky, but not awful. + * + * If we find a '\0', there are no more arguments + * left. However, we still need to return a nonzero value to + * alert the calling function about the argument we just + * found. Since there are no more arguments, we aren't + * overwriting anything by setting the next byte to '\0'. We + * also know that we aren't writing into invalid memory + * because in the struct definition for kshell_t, we declared + * ksh_buf to have KSH_BUF_SIZE + 1 bytes. + */ + if (line[retval] == '\0') + { + line[retval + 1] = '\0'; + } + else + { + line[retval] = '\0'; + } + return retval; +} + +/** + * Finds the arguments of the command just into a kshell. This should + * be called directly after returning from a read. + * + * @param buf the buffer to extract arguments from + * @param argv out parameter containing an array of null-terminated + * strings, one for each argument + * @param max_args the maximum number of arguments to find + * @param argc out parameter containing the number of arguments found + */ +static void kshell_get_args(kshell_t *ksh, char *buf, char **argv, + size_t max_args, size_t *argc) +{ + size_t arglen; + + KASSERT(NULL != buf); + KASSERT(NULL != argv); + KASSERT(max_args > 0); + KASSERT(NULL != argc); + + *argc = 0; + while (kshell_find_next_arg(ksh, buf, argv + *argc, &arglen) && + *argc < max_args) + { + buf = argv[*argc] + arglen + 1; + ++(*argc); + } + if (*argc >= max_args) + { + dprintf("Too many arguments\n"); + } +} + +kshell_command_t *kshell_lookup_command(const char *name, size_t namelen) +{ + if (namelen > KSH_CMD_NAME_LEN) + { + namelen = KSH_CMD_NAME_LEN; + } + + list_iterate(&kshell_commands_list, cmd, kshell_command_t, + kc_commands_link) + { + KASSERT(NULL != cmd); + if ((strncmp(cmd->kc_name, name, namelen) == 0) && + (namelen == strnlen(cmd->kc_name, KSH_CMD_NAME_LEN))) + { + return cmd; + } + } + return NULL; +} + +#ifdef __VFS__ + +/** + * If stdin or stdout has been redirected to a file, closes the file + * and directs I/O back to stdin and stdout. + * + * @param the kshell + */ +void kshell_undirect(kshell_t *ksh) +{ + KASSERT(NULL != ksh); + + if (ksh->ksh_in_fd != ksh->ksh_fd) + { + if (do_close(ksh->ksh_in_fd) < 0) + { + panic("kshell: Error closing file descriptor %d\n", ksh->ksh_in_fd); + } + ksh->ksh_in_fd = ksh->ksh_fd; + } + if (ksh->ksh_out_fd != ksh->ksh_fd) + { + if (do_close(ksh->ksh_out_fd) < 0) + { + panic("kshell: Error closing file descriptor %d\n", + ksh->ksh_out_fd); + } + ksh->ksh_out_fd = ksh->ksh_fd; + } +} + +/** + * Redirects stdin and stdout. + * + * @param ksh the kshell + * @param redirect_in the name of the file to redirect stdin from + * @param redirect_out the name of the file to redirect stdout to + * @param append if true, output will be appended + * @return 0 on sucess and <0 on error. If returns with <0, no streams + * will be redirected. + */ +long kshell_redirect(kshell_t *ksh, const char *redirect_in, + const char *redirect_out, int append) +{ + long fd; + + KASSERT(NULL != ksh); + KASSERT(NULL != redirect_in); + KASSERT(NULL != redirect_out); + + if (redirect_in[0] != '\0') + { + if ((fd = do_open(redirect_in, O_RDONLY | O_CREAT)) < 0) + { + kprintf(ksh, "kshell: %s: Error opening file\n", redirect_in); + goto error; + } + ksh->ksh_in_fd = (int)fd; + } + if (redirect_out[0] != '\0') + { + int flags = append ? O_WRONLY | O_CREAT | O_APPEND : O_WRONLY | O_CREAT | O_TRUNC; + if ((fd = do_open(redirect_out, flags)) < 0) + { + kprintf(ksh, "kshell: %s: Error opening file\n", redirect_out); + goto error; + } + ksh->ksh_out_fd = fd; + } + return 0; + +error: + kshell_undirect(ksh); + return fd; +} + +#endif + +long kshell_execute_next(kshell_t *ksh) +{ + static const char *kshell_prompt = "kshell$"; + + long nbytes, retval; + kshell_command_t *cmd; + char *args[KSH_MAX_ARGS]; + size_t argc; + char redirect_in[MAXPATHLEN]; + char redirect_out[MAXPATHLEN]; + int append; + + /* + * Need that extra byte at the end. See comment in + * kshell_find_next_arg. + */ + char buf[KSH_BUF_SIZE + 1]; + + KASSERT(NULL != ksh); + + kprintf(ksh, "%s ", kshell_prompt); + + if ((nbytes = kshell_read(ksh, buf, KSH_BUF_SIZE)) <= 0) + { + return nbytes; + } + if (nbytes == 1) + { + return 1; + } + if (buf[nbytes - 1] == '\n') + { + /* Overwrite the newline with a null terminator */ + buf[--nbytes] = '\0'; + } + else + { + /* Add the null terminator to the end */ + buf[nbytes] = '\0'; + } + + /* Even though we can't redirect I/O to files before VFS, we + * still want to scrub out any reference to redirection before + * passing the line off to kshell_get_args */ + redirect_in[0] = redirect_out[0] = '\0'; + if (kshell_find_redirection(ksh, buf, redirect_in, redirect_out, &append) < + 0) + { + goto done; + } +#ifdef __VFS__ + if ((retval = kshell_redirect(ksh, redirect_in, redirect_out, append)) < + 0) + { + dprintf("Error redirecting I/O\n"); + goto done; + } +#endif + + kshell_get_args(ksh, buf, args, KSH_MAX_ARGS, &argc); + if (argc == 0) + { + goto done; + } + + dprintf("Attempting to execute command '%s'\n", args[0]); + + if (strncmp(args[0], "exit", strlen("exit")) == 0) + { + nbytes = 0; + goto done; + } + + if ((cmd = kshell_lookup_command(args[0], strlen(args[0]))) == NULL) + { + kprintf(ksh, "kshell: %s not a valid command\n", args[0]); + } + else + { + if ((retval = cmd->kc_cmd_func(ksh, argc, args)) < 0) + { + nbytes = retval; + goto done; + } + } + goto done; + +done: +#ifdef __VFS__ + kshell_undirect(ksh); +#endif + return nbytes; +} diff --git a/kernel/test/kshell/priv.h b/kernel/test/kshell/priv.h new file mode 100644 index 0000000..65c9493 --- /dev/null +++ b/kernel/test/kshell/priv.h @@ -0,0 +1,43 @@ +#pragma once + +#include "test/kshell/kshell.h" + +#include "util/list.h" + +#define dprintf(x, args...) dbg(DBG_TEST, x, ##args) + +#define KSH_BUF_SIZE \ + 1024 /* This really just needs to be as large as \ + * the line discipline buffer */ +#define KSH_CMD_NAME_LEN 16 +#define KSH_MAX_ARGS 128 +#define KSH_DESC_LEN 64 + +struct chardev; +struct kshell_command; + +struct kshell +{ + /* If we have a filesystem, we can write to the file + * descriptor. Otherwise, we need to write to a byte device */ +#ifdef __VFS__ + int ksh_fd; + + /* Used for redirection */ + int ksh_out_fd; + int ksh_in_fd; +#else + struct chardev *ksh_cd; +#endif +}; + +extern list_t kshell_commands_list; + +/** + * Searches for a shell command with a specified name. + * + * @param name name of the command to search for + * @param namelen length of name + * @return the command, if it exists, or NULL + */ +struct kshell_command *kshell_lookup_command(const char *name, size_t namelen); diff --git a/kernel/test/kshell/tokenizer.c b/kernel/test/kshell/tokenizer.c new file mode 100644 index 0000000..9406668 --- /dev/null +++ b/kernel/test/kshell/tokenizer.c @@ -0,0 +1,74 @@ +#include "tokenizer.h" + +#include <ctype.h> + +#include "util/debug.h" + +#define EOL '\0' + +const char *ksh_tok_type_str[] = {"text", "<", ">", ">>", "end of line", ""}; + +long kshell_next_token(kshell_t *ksh, char *line, kshell_token_t *token) +{ + KASSERT(NULL != ksh); + KASSERT(NULL != line); + KASSERT(NULL != token); + + size_t i = 0; + while (line[i] != EOL && isspace(line[i])) + ++i; + token->kt_text = line + i; + + /* Determine the token type */ + switch (line[i]) + { + case EOL: + token->kt_type = KTT_EOL; + token->kt_textlen = 0; + break; + case '<': + token->kt_type = KTT_REDIRECT_IN; + token->kt_textlen = i = 1; + break; + case '>': + if (line[i + 1] == '>') + { + token->kt_type = KTT_REDIRECT_OUT_APPEND; + token->kt_textlen = i = 2; + } + else + { + token->kt_type = KTT_REDIRECT_OUT; + token->kt_textlen = i = 1; + } + break; + default: + token->kt_type = KTT_WORD; + token->kt_textlen = 0; + break; + } + + switch (token->kt_type) + { + case KTT_WORD: + while (!isspace(line[i]) && line[i] != '<' && line[i] != '>' && + line[i] != EOL) + { + ++i; + ++token->kt_textlen; + } + break; + case KTT_EOL: + return 0; + default: + break; + } + + return i; +} + +const char *kshell_token_type_str(kshell_token_type_t type) +{ + KASSERT(type < KTT_MAX); + return ksh_tok_type_str[type]; +} diff --git a/kernel/test/kshell/tokenizer.h b/kernel/test/kshell/tokenizer.h new file mode 100644 index 0000000..9c49026 --- /dev/null +++ b/kernel/test/kshell/tokenizer.h @@ -0,0 +1,39 @@ +#pragma once + +#include "types.h" + +#include "test/kshell/kshell.h" + +typedef enum kshell_token_type +{ + KTT_WORD, + KTT_REDIRECT_IN, /* '<' */ + KTT_REDIRECT_OUT, /* '>' */ + KTT_REDIRECT_OUT_APPEND, /* '>>' */ + KTT_EOL, + + KTT_MAX /* Number of token types */ +} kshell_token_type_t; + +typedef struct kshell_token +{ + kshell_token_type_t kt_type; + char *kt_text; + size_t kt_textlen; +} kshell_token_t; + +/** + * Finds the next token in the input line. + * + * Note: To find multiple tokens from the same line, you increment the + * line pointer by the number of bytes processed before the next call + * to kshell_next token. + * + * @param ksh the kshell + * @param line the input line to tokenize + * @param token out parameter containing the next token found + * @return 0 if no more tokens, otherwise, number of bytes processed + */ +long kshell_next_token(kshell_t *ksh, char *line, kshell_token_t *token); + +const char *kshell_token_type_str(kshell_token_type_t type); |
