toaruos/kernel/misc/shell.c

677 lines
17 KiB
C

/* vim: tabstop=4 shiftwidth=4 noexpandtab
*
* ToAruOS Kernel Debugger Shell
*
* Part of the ToAruOS Kernel, under the NCSA license
*
* Copyright 2011 Kevin Lange
*
* (Preliminary documentation based on intended future use; currently,
* this is just a file system explorer)
*
* This is a kernel debugging shell that allows basic, sh-like operation
* of the system while it is in use, without other tasks running in the
* background. While the debug shell is running, the tasker is disabled
* and the kernel will remainin on its current task, allowing users to
* display registry and memory information relavent to the current task.
*
*/
#include <system.h>
#include <fs.h>
#include <multiboot.h>
#include <ata.h>
#include <ext2.h>
#include <list.h>
#include <tree.h>
#include <process.h>
#include <logging.h>
#include <version.h>
struct {
char path[1024];
char * username;
char * hostname;
uint16_t month, day, hours, minutes, seconds;
fs_node_t * node;
} shell;
#define SHELL_COMMANDS 512
typedef uint32_t(*shell_command_t) (int argc, char ** argv);
char * shell_commands[SHELL_COMMANDS];
shell_command_t shell_pointers[SHELL_COMMANDS];
uint32_t shell_commands_len = 0;
#define SHELL_HISTORY_ENTRIES 128
char * shell_history[SHELL_HISTORY_ENTRIES];
size_t shell_history_count = 0;
size_t shell_history_offset = 0;
size_t shell_scroll = 0;
char shell_temp[1024];
void
shell_history_insert(char * str) {
if (shell_history_count == SHELL_HISTORY_ENTRIES) {
free(shell_history[shell_history_offset]);
shell_history[shell_history_offset] = str;
shell_history_offset = (shell_history_offset + 1) % SHELL_HISTORY_ENTRIES;
} else {
shell_history[shell_history_count] = str;
shell_history_count++;
}
}
char *
shell_history_get(size_t item) {
return shell_history[(item + shell_history_offset) % SHELL_HISTORY_ENTRIES];
}
char *
shell_history_prev(size_t item) {
return shell_history_get(shell_history_count - item);
}
void
redraw_shell() {
kprintf("\033[1m[\033[1;33m%s \033[1;32m%s \033[1;31m%d/%d \033[1;34m%d:%d:%d\033[0m \033[0m%s\033[1m]\033[0m\n\033[1;32m$\033[0m ",
shell.username, shell.hostname, shell.month, shell.day, shell.hours, shell.minutes, shell.seconds, shell.path);
}
void
init_shell() {
shell.node = fs_root;
shell.username = "kernel";
shell.hostname = "toaru";
shell.path[0] = '/';
shell.path[1] = '\0';
}
void
shell_install_command(char * name, shell_command_t func) {
if (shell_commands_len == SHELL_COMMANDS) {
kprintf("Ran out of space for static shell commands. The maximum number of commands is %d\n", SHELL_COMMANDS);
return;
}
shell_commands[shell_commands_len] = name;
shell_pointers[shell_commands_len] = func;
shell_commands_len++;
}
shell_command_t shell_find(char * str) {
for (uint32_t i = 0; i < shell_commands_len; ++i) {
if (!strcmp(str, shell_commands[i])) {
return shell_pointers[i];
}
}
return NULL;
}
void shell_update_time() {
get_date(&shell.month, &shell.day);
get_time(&shell.hours, &shell.minutes, &shell.seconds);
}
void shell_exec(char * buffer, int size) {
/*
* Tokenize the command
*/
char * pch;
char * cmd;
char * save;
/* First off, let's check if it's a history request */
if (buffer[0] == '!') {
uint32_t x = atoi((char *)((uint32_t)buffer + 1));
if (x <= shell_history_count) {
buffer = shell_history_get(x - 1);
size = strlen(buffer);
} else {
kprintf("history: invalid index %d\n", x);
return;
}
}
char * history = malloc(sizeof(char) * (size + 1));
memcpy(history, buffer, strlen(buffer) + 1);
pch = strtok_r(buffer," ",&save);
cmd = pch;
if (!cmd) {
free(history);
return;
}
shell_history_insert(history);
char * argv[1024]; /* Command tokens (space-separated elements) */
int tokenid = 0;
while (pch != NULL) {
argv[tokenid] = (char *)pch;
++tokenid;
pch = strtok_r(NULL," ",&save);
}
argv[tokenid] = NULL;
shell_command_t func = shell_find(argv[0]);
if (func) {
func(tokenid, argv);
} else {
/* Alright, here we go */
char * filename = malloc(sizeof(char) * 1024);
fs_node_t * chd = NULL;
if (argv[0][0] == '/') {
memcpy(filename, argv[0], strlen(argv[0]) + 1);
chd = kopen(filename, 0);
}
if (!chd) {
/* Alright, let's try this... */
char * search_path = "/bin/";
memcpy(filename, search_path, strlen(search_path));
memcpy((void*)((uintptr_t)filename + strlen(search_path)),argv[0],strlen(argv[0])+1);
chd = kopen(filename, 0);
}
if (!chd) {
kprintf("Unrecognized command: %s\n", cmd);
} else {
close_fs(chd);
system(filename, tokenid, argv);
}
free(filename);
}
}
uint32_t shell_cmd_cd(int argc, char * argv[]) {
if (argc < 2) {
kprintf("pwd: %s\n", current_process->wd_name);
return 1;
} else {
fs_node_t * chd = kopen(argv[1], 0);
char * path = canonicalize_path(shell.path, argv[1]);
if (chd) {
if ((chd->flags & FS_DIRECTORY) == 0) {
kprintf("%s: %s is not a directory\n", argv[0], argv[1]);
free(chd);
return 1;
}
if (shell.node != fs_root) {
free(shell.node);
}
shell.node = chd;
memcpy(shell.path, path, strlen(path) + 1);
free(path);
current_process->wd_name = malloc(strlen(shell.path) + 1);
memcpy(current_process->wd_name, shell.path, strlen(shell.path) + 1);
} else {
kprintf("%s: could not cd %s: no such file or directory\n", argv[0], argv[1]);
}
}
return 0;
}
uint32_t shell_cmd_info(int argc, char * argv[]) {
if (argc < 2) {
kprintf("%s: Expected argument\n", argv[0]);
return 1;
}
fs_node_t * file = kopen(argv[1], 0);
if (!file) {
kprintf("Could not open file `%s`\n", argv[1]);
return 1;
}
kprintf("flags: 0x%x\n", file->flags);
kprintf("mask: 0x%x\n", file->mask);
kprintf("inode: 0x%x\n", file->inode);
kprintf("uid: %d gid: %d\n", file->uid, file->gid);
kprintf("open(): 0x%x\n", file->open);
kprintf("read(): 0x%x\n", file->read);
kprintf("write(): 0x%x\n", file->write);
if ((file->mask & 0x001) || (file->mask & 0x008) || (file->mask & 0x040)) {
kprintf("File is executable.\n");
}
close_fs(file);
return 0;
}
uint32_t shell_cmd_ls(int argc, char * argv[]) {
/*
* List the files in the current working directory
*/
struct dirent * entry = NULL;
int i = 0;
fs_node_t * ls_node;
char * dir_path;
if (argc < 2) {
ls_node = shell.node;
dir_path = shell.path;
} else {
ls_node = kopen(argv[1], 0);
dir_path = argv[1];
if (!ls_node) {
kprintf("%s: Could not stat directory '%s'.\n", argv[0], argv[1]);
return 1;
}
}
entry = readdir_fs(ls_node, i);
while (entry != NULL) {
char * filename = malloc(sizeof(char) * 1024);
memcpy(filename, dir_path, strlen(dir_path));
if (!strcmp(dir_path,"/")) {
memcpy((void *)((uintptr_t)filename + strlen(dir_path)),entry->name,strlen(entry->name)+1);
} else {
filename[strlen(dir_path)] = '/';
memcpy((void *)((uintptr_t)filename + strlen(dir_path) + 1),entry->name,strlen(entry->name)+1);
}
fs_node_t * chd = kopen(filename, 0);
if (chd) {
if (chd->flags & FS_DIRECTORY) {
kprintf("\033[1;34m");
} else if ((chd->mask & 0x001) || (chd->mask & 0x008) || (chd->mask & 0x040)) {
kprintf("\033[1;32m");
}
close_fs(chd);
}
free(filename);
kprintf("%s\033[0m\n", entry->name);
free(entry);
i++;
entry = readdir_fs(ls_node, i);
}
if (ls_node != shell.node) {
close_fs(ls_node);
}
return 0;
}
uint32_t shell_cmd_out(int argc, char * argv[]) {
if (argc < 3) {
kprintf("Need a port and a character (both as numbers, please) to write...\n");
return 1;
} else {
int port;
port = atoi(argv[1]);
int val;
val = atoi(argv[2]);
kprintf("Writing %d (%c) to port %d\n", val, (unsigned char)val, port);
outportb((short)port, (unsigned char)val);
}
return 0;
}
uint32_t shell_cmd_cpudetect(int argc, char * argv[]) {
detect_cpu();
return 0;
}
uint32_t shell_cmd_multiboot(int argc, char * argv[]) {
dump_multiboot(mboot_ptr);
return 0;
}
uint32_t shell_cmd_screenshot(int argc, char * argv[]) {
if (argc < 2) {
bochs_screenshot(NULL);
} else {
bochs_screenshot(argv[1]);
}
return 0;
}
uint32_t shell_cmd_readsb(int argc, char * argv[]) {
extern void ext2_disk_read_superblock();
ext2_disk_read_superblock();
return 0;
}
uint32_t shell_cmd_readdisk(int argc, char * argv[]) {
uint8_t buf[512] = {1};
uint32_t i = 0;
uint8_t slave = 0;
if (argc >= 2) {
if (!strcmp(argv[1], "slave")) {
slave = 1;
}
}
while (buf[0]) {
ide_read_sector(0x1F0, slave, i, buf);
for (uint16_t j = 0; j < 512; ++j) {
ansi_put(buf[j]);
}
++i;
}
return 0;
}
uint32_t shell_cmd_writedisk(int argc, char * argv[]) {
uint8_t buf[512] = "Hello world!\n";
ide_write_sector(0x1F0, 0, 0x000000, buf);
return 0;
}
ext2_inodetable_t * ext2_disk_alloc_inode(ext2_inodetable_t * parent, char * name);
uint32_t shell_cmd_testing(int argc, char * argv[]) {
ext2_inodetable_t * derp = ext2_disk_alloc_inode(NULL, "test");
free(derp);
return 0;
}
uint32_t shell_cmd_history(int argc, char * argv[]) {
for (size_t i = 0; i < shell_history_count; ++i) {
kprintf("%d\t%s\n", i + 1, shell_history_get(i));
}
return 0;
}
uint32_t shell_cmd_testlist(int argc, char * argv[]) {
list_t * list = list_create();
int * i = malloc(sizeof(int));
*i = 32;
list_insert(list,i);
i = malloc(sizeof(int));
*i = 245252;
list_insert(list,i);
i = malloc(sizeof(int));
*i = 6432643;
list_insert(list,i);
i = malloc(sizeof(int));
*i = 9502;
list_insert(list,i);
kprintf("list: %d\n", list->length);
foreach(node, list) {
kprintf("0x%x ", node);
kprintf("-> %d\n", *(uint32_t *)node->value);
}
list_remove(list, 0);
kprintf("list: %d\n", list->length);
foreach(node, list) {
kprintf("0x%x ", node);
kprintf("-> %d\n", *(uint32_t *)node->value);
}
list_destroy(list);
list_free(list);
free(list);
return 0;
}
void debug_print_tree_node(tree_node_t * node, size_t height) {
if (!node) return;
for (uint32_t i = 0; i < height; ++i) { kprintf(" "); }
kprintf("%s\n", (char *)node->value);
foreach(child, node->children) {
debug_print_tree_node(child->value, height + 1);
}
}
void debug_print_tree(tree_t * tree) {
kprintf("Tree 0x%x; %d nodes\n", tree, tree->nodes);
debug_print_tree_node(tree->root, 0);
}
uint32_t shell_cmd_testtree(int argc, char * argv[]) {
tree_t * tree = tree_create();
tree_set_root(tree,"a");
tree_node_t * b = tree_node_insert_child(tree, tree->root, "b");
tree_node_t * c = tree_node_insert_child(tree, tree->root, "c");
tree_node_t * d = tree_node_insert_child(tree, b, "d");
tree_node_t * e = tree_node_insert_child(tree, c, "e");
tree_node_insert_child(tree, c, "f");
tree_node_insert_child(tree, e, "g");
tree_node_insert_child(tree, d, "h");
debug_print_tree(tree);
tree_node_remove(tree, d);
debug_print_tree(tree);
tree_remove(tree, e);
debug_print_tree(tree);
tree_destroy(tree);
tree_free(tree);
free(tree);
return 0;
}
uint32_t shell_cmd_ps(int argc, char * argv[]) {
debug_print_process_tree();
return 0;
}
uint32_t shell_cmd_dmesg(int argc, char * argv[]) {
debug_print_log();
return 0;
}
uint32_t shell_cmd_kill(int argc, char * argv[]) {
if (argc < 2) {
kprintf(" kill pid\n");
kprintf(" Remove the zombie process with PID `n`.\n");
return 1;
}
pid_t id = atoi(argv[1]);
process_t * child_task = process_from_pid(id);
if (!child_task) {
kprintf("No process with pid %d found.\n", id);
return 1;
} else if (!child_task->finished) {
kprintf("Process %d is not a zombie.\n", id);
return 1;
} else {
kprintf("Killing process %d [0x%x]\n", child_task->id, child_task);
delete_process(child_task);
return 0;
}
}
uint32_t shell_cmd_mem(int argc, char * argv[]) {
uintptr_t mem_use = memory_use();
uintptr_t mem_tot = memory_total();
kprintf("%dkB in use\n", mem_use);
kprintf("%dkB available\n", mem_tot - mem_use);
kprintf("%dkB total\n", mem_tot);
return 0;
}
uint32_t shell_cmd_uname(int argc, char *argv[]) {
kprintf("Notice: This is as hell builtin. uname() is not implemented.\n");
char version_number[1024];
sprintf(version_number, __kernel_version_format,
__kernel_version_major,
__kernel_version_minor,
__kernel_version_lower,
__kernel_version_suffix);
kprintf("%s %s %s %s %s %s\n",
__kernel_name,
version_number,
__kernel_version_codename,
__kernel_build_date,
__kernel_build_time,
__kernel_arch);
return 0;
}
void install_commands() {
shell_install_command("cd", shell_cmd_cd);
shell_install_command("ls", shell_cmd_ls);
shell_install_command("info", shell_cmd_info);
shell_install_command("out", shell_cmd_out);
shell_install_command("cpu-detect", shell_cmd_cpudetect);
shell_install_command("multiboot", shell_cmd_multiboot);
shell_install_command("screenshot", shell_cmd_screenshot);
shell_install_command("read-sb", shell_cmd_readsb);
shell_install_command("read-disk", shell_cmd_readdisk);
shell_install_command("write-disk", shell_cmd_writedisk);
shell_install_command("test-alloc-block", shell_cmd_testing);
shell_install_command("history", shell_cmd_history);
shell_install_command("test-list", shell_cmd_testlist);
shell_install_command("test-tree", shell_cmd_testtree);
shell_install_command("ps", shell_cmd_ps);
shell_install_command("dmesg", shell_cmd_dmesg);
shell_install_command("kill", shell_cmd_kill);
shell_install_command("mem", shell_cmd_mem);
shell_install_command("uname", shell_cmd_uname);
}
void add_path_contents() {
struct dirent * entry = NULL;
int i = 0;
fs_node_t * ls_node;
ls_node = kopen("/bin", 0);
char * dir_path = "/bin";
if (!ls_node) {
kprintf("Failed to open /bin\n");
return;
}
entry = readdir_fs(ls_node, i);
while (entry != NULL) {
char * filename = malloc(sizeof(char) * 1024);
memcpy(filename, dir_path, strlen(dir_path));
filename[strlen(dir_path)] = '/';
memcpy((void *)((uintptr_t)filename + strlen(dir_path) + 1),entry->name,strlen(entry->name)+1);
fs_node_t * chd = kopen(filename, 0);
if (chd) {
if (chd->flags & FS_DIRECTORY) {
} else if ((chd->mask & 0x001) || (chd->mask & 0x008) || (chd->mask & 0x040)) {
char * s = malloc(sizeof(char) * (strlen(entry->name) + 1));
memcpy(s, entry->name, strlen(entry->name) + 1);
shell_install_command(s, NULL);
}
close_fs(chd);
}
free(filename);
free(entry);
i++;
entry = readdir_fs(ls_node, i);
}
if (ls_node != shell.node) {
close_fs(ls_node);
}
}
void tab_complete_shell(char * buffer) {
char buf[1024];
memcpy(buf, buffer, 1024);
char * pch;
char * cmd;
char * save;
pch = strtok_r(buf," ",&save);
cmd = pch;
char * argv[1024]; /* Command tokens (space-separated elements) */
int argc = 0;
if (!cmd) {
argv[0] = "";
argc = 1;
} else {
while (pch != NULL) {
argv[argc] = (char *)pch;
++argc;
pch = strtok_r(NULL," ",&save);
}
}
argv[argc] = NULL;
if (argc < 2) {
if (buffer[strlen(buffer)-1] == ' ' || argc == 0) {
kprintf("\n");
for (uint32_t i = 0; i < shell_commands_len; ++i) {
kprintf(shell_commands[i]);
if (i < shell_commands_len - 1) {
kprintf(", ");
}
}
kprintf("\n");
redraw_shell();
kgets_redraw_buffer();
return;
} else {
uint32_t count = 0, j = 0;
for (uint32_t i = 0; i < shell_commands_len; ++i) {
if (startswith(shell_commands[i], argv[0])) {
count++;
}
}
if (count == 1) {
for (uint32_t i = 0; i < shell_commands_len; ++i) {
if (startswith(shell_commands[i], argv[0])) {
for (uint32_t j = 0; j < strlen(buffer); ++j) {
kprintf("\x08 \x08");
}
kprintf(shell_commands[i]);
memcpy(buffer, shell_commands[i], strlen(shell_commands[i]) + 1);
return;
}
}
}
kprintf("\n");
for (uint32_t i = 0; i < shell_commands_len; ++i) {
if (startswith(shell_commands[i], argv[0])) {
kprintf(shell_commands[i]);
++j;
if (j < count) {
kprintf(", ");
}
}
}
kprintf("\n");
redraw_shell();
kgets_redraw_buffer();
return;
}
} else {
/* Complete path names */
kprintf("%d\n", argc);
}
}
void key_up_shell(char * buffer) {
if (shell_scroll == 0) {
memcpy(shell_temp, buffer, strlen(buffer) + 1);
}
if (shell_scroll < shell_history_count) {
shell_scroll++;
for (size_t i = 0; i < strlen(buffer); ++i) {
kprintf("\x08 \x08");
}
char * h = shell_history_prev(shell_scroll);
memcpy(buffer, h, strlen(h) + 1);
kprintf(h);
}
}
void key_down_shell(char * buffer) {
if (shell_scroll > 1) {
shell_scroll--;
for (size_t i = 0; i < strlen(buffer); ++i) {
kprintf("\x08 \x08");
}
char * h = shell_history_prev(shell_scroll);
memcpy(buffer, h, strlen(h) + 1);
kprintf(h);
} else if (shell_scroll == 1) {
for (size_t i = 0; i < strlen(buffer); ++i) {
kprintf("\x08 \x08");
}
shell_scroll = 0;
memcpy(buffer, shell_temp, strlen(shell_temp) + 1);
kprintf(buffer);
}
}
void
start_shell() {
init_shell();
install_commands();
add_path_contents();
while (1) {
/* Read buffer */
shell_update_time();
redraw_shell();
char buffer[1024];
int size;
/* Read commands */
kgets_redraw_func = redraw_shell;
kgets_tab_complete_func = tab_complete_shell;
kgets_key_down = key_down_shell;
kgets_key_up = key_up_shell;
size = kgets((char *)&buffer, 1023);
if (size < 1) {
continue;
} else {
/* Execute command */
shell_exec(buffer, size);
shell_scroll = 0;
}
}
}