aboutsummaryrefslogtreecommitdiff
path: root/kernel/test/kshell
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/test/kshell')
-rw-r--r--kernel/test/kshell/command.c46
-rw-r--r--kernel/test/kshell/command.h20
-rw-r--r--kernel/test/kshell/commands.c404
-rw-r--r--kernel/test/kshell/commands.h32
-rw-r--r--kernel/test/kshell/io.c78
-rw-r--r--kernel/test/kshell/kshell.c504
-rw-r--r--kernel/test/kshell/priv.h43
-rw-r--r--kernel/test/kshell/tokenizer.c74
-rw-r--r--kernel/test/kshell/tokenizer.h39
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);