From 3c5c92c7c30d1ba5a3fca89f94ea1649e9aadeac Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 19 Oct 2022 19:42:05 +0200 Subject: [PATCH] sh: Add a simple interactive shell --- apps/Makefile | 2 +- apps/src/init.c | 131 +-------------------- apps/src/sh.c | 134 +++++++++++++++++++++ kernel/include/fs/devices/Keyboard.h | 11 ++ kernel/include/misc/Scancodes.h | 6 + kernel/src/fs/devices/DeviceFS.cpp | 2 + kernel/src/fs/devices/Keyboard.cpp | 44 +++++++ kernel/src/interrupts/IRQ.cpp | 12 +- kernel/src/misc/Scancodes.cpp | 168 +++++++++++++++++++++++++++ 9 files changed, 377 insertions(+), 133 deletions(-) create mode 100644 apps/src/sh.c create mode 100644 kernel/include/fs/devices/Keyboard.h create mode 100644 kernel/include/misc/Scancodes.h create mode 100644 kernel/src/fs/devices/Keyboard.cpp create mode 100644 kernel/src/misc/Scancodes.cpp diff --git a/apps/Makefile b/apps/Makefile index 89299fd9..f3910471 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -1,4 +1,4 @@ -APPS := init sym +APPS := init sym sh APPS_DIR := $(LUNA_ROOT)/apps APPS_SRC := $(APPS_DIR)/src diff --git a/apps/src/init.c b/apps/src/init.c index e3ef8414..cd96475d 100644 --- a/apps/src/init.c +++ b/apps/src/init.c @@ -46,116 +46,15 @@ int main() return 1; } - FILE* serial = fopen("/dev/serial", "r"); - if (!serial) - { - perror("fopen"); - return 1; - } - if (fputs("Hello from init!\n", serial) < 0) - { - perror("fputs"); - return 1; - } - if (fclose(serial) < 0) - { - perror("fclose"); - return 1; - } - printf("Welcome to Luna!\n"); - printf("Running as PID %ld, PPID %ld\n\n", getpid(), getppid()); - - sleep(1); + printf("Running as PID %ld\n", getpid()); if (print_version()) return 1; - sleep(2); - - const char* filename = "/sys/config"; - - printf("Opening %s for reading...\n", filename); - - FILE* config = fopen(filename, "r"); - if (!config) - { - perror("fopen"); - return 1; - } - - if (fseek(config, 0, SEEK_END) < 0) - { - perror("fseek"); - return 1; - } - - long offset = ftell(config); - if (offset < 0) - { - perror("ftell"); - return 1; - } - - printf("%s is %ld bytes long\n", filename, offset); - - rewind(config); - - char buf[4096]; - - size_t nread = fread(buf, sizeof(buf), 1, config); - if (ferror(config)) { perror("fread"); } - else - { - buf[nread] = 0; - - printf("Read %zd bytes\n\n", nread); - - printf("%s", buf); - } - - if (fclose(config) < 0) { perror("fclose"); } - - printf("\n\nGot random number %d\n\n", rand()); - - sleep(2); - - printf("Press any key to restart.\n\n"); - - int stderr_fd = fileno(stderr); - int new_stderr_fd = dup(stderr_fd); - if (new_stderr_fd < 0) - { - perror("dup"); - return 1; - } - FILE* new_stderr = fdopen(new_stderr_fd, "rw"); - if (!new_stderr) - { - perror("fdopen"); - return 1; - } - - fprintf(new_stderr, "Bye!\n\n"); - - fclose(new_stderr); - - const char* pathname = "/etc"; - - printf("Creating directory %s\n", pathname); - - if (mkdir(pathname, 0) < 0) - { - perror("mkdir"); - return 1; - } - - printf("Success!!\n"); - - printf("Forking...\n"); + sleep(1); pid_t child = fork(); - if (child < 0) { perror("fork"); @@ -163,38 +62,16 @@ int main() } if (child == 0) { - sleep(1); - pid_t child = fork(); - printf("I am the child (PID %ld), my parent is PID %ld!!\n", getpid(), getppid()); - if (child) execv("/bin/sym", NULL); - else - execv("/bin/init", NULL); + execv("/bin/sh", NULL); perror("execv"); return 1; } - else { printf("Success!! Got PID %ld\n", child); } - child = fork(); - if (child < 0) - { - perror("fork"); - return 1; - } - if (child == 0) { *(int*)(0xdeadbeef) = 1234; } - - int status; for (;;) { - pid_t result; - while ((result = wait(&status)) == 0) // No child has exited yet + while (wait(NULL) == 0) // No child has exited yet { msleep(100); } - if (result < 0) - { - perror("wait"); - return 1; - } - if (WIFEXITED(status)) { printf("Child process %ld exited with code %d\n", result, WEXITSTATUS(status)); } } } diff --git a/apps/src/sh.c b/apps/src/sh.c new file mode 100644 index 00000000..f27fd9e1 --- /dev/null +++ b/apps/src/sh.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +typedef struct +{ + char* buffer; + long size; + long capacity; +} command; + +void show_prompt() +{ + printf("> "); +} + +void command_expand(command* cmd, long new_capacity) +{ + char* buffer = realloc(cmd->buffer, new_capacity); + if (!buffer) + { + perror("realloc"); + exit(1); + } + cmd->buffer = buffer; + cmd->capacity = new_capacity; +} + +void command_push(command* cmd, char c) +{ + if (cmd->size == cmd->capacity) command_expand(cmd, cmd->capacity + 8); + cmd->buffer[cmd->size] = c; + cmd->size++; +} + +void command_pop(command* cmd) +{ + cmd->size--; +} + +void command_init(command* cmd) +{ + cmd->buffer = malloc(5); + cmd->capacity = 5; + cmd->size = 0; +} + +void command_clear(command* cmd) +{ + free(cmd->buffer); + return command_init(cmd); +} + +void command_execute(command* cmd) +{ + command_push(cmd, '\0'); + pid_t child = fork(); + if (child < 0) + { + perror(cmd->buffer); + command_clear(cmd); + show_prompt(); + return; + } + if (child == 0) + { + execv(cmd->buffer, NULL); + perror(cmd->buffer); + exit(127); + } + int status; + pid_t result; + while ((result = waitpid(child, &status, 0)) == 0) { msleep(20); } + if (result < 0) + { + perror("waitpid"); + command_clear(cmd); + show_prompt(); + return; + } + if (WEXITSTATUS(status) != 0) { printf("Exited with code %d\n", WEXITSTATUS(status)); } + command_clear(cmd); + show_prompt(); +} + +void command_concat(command* cmd, const char* str) +{ + while (*str) + { + putchar(*str); + if (*str == '\b') { command_pop(cmd); } + else if (*str == '\n') { command_execute(cmd); } + else + command_push(cmd, *str); + str++; + } +} + +int main() +{ + FILE* fp = fopen("/dev/kbd", "r"); + if (!fp) + { + perror("fopen"); + return 1; + } + + show_prompt(); + + char buffer[33]; + + command shell_command; + command_init(&shell_command); + + while (1) + { + size_t nread = fread(buffer, sizeof(buffer) - 1, 1, fp); + if (ferror(fp)) + { + perror("fread"); + return 1; + } + if (feof(fp)) + { + clearerr(fp); + msleep(20); + continue; + } + buffer[nread] = 0; + command_concat(&shell_command, buffer); + } +} \ No newline at end of file diff --git a/kernel/include/fs/devices/Keyboard.h b/kernel/include/fs/devices/Keyboard.h new file mode 100644 index 00000000..f77c25b1 --- /dev/null +++ b/kernel/include/fs/devices/Keyboard.h @@ -0,0 +1,11 @@ +#pragma once +#include "fs/VFS.h" + +namespace KeyboardDevice +{ + VFS::Node* create_new(const char* devname); + + ssize_t read(VFS::Node* node, size_t offset, size_t size, char* buffer); + + void append(char c); +} \ No newline at end of file diff --git a/kernel/include/misc/Scancodes.h b/kernel/include/misc/Scancodes.h new file mode 100644 index 00000000..439b2678 --- /dev/null +++ b/kernel/include/misc/Scancodes.h @@ -0,0 +1,6 @@ +#pragma once + +// This should only be used for a keyboard TTY interface. Userspace should translate keyboard scancodes by themselves. +char translate_scancode(unsigned char scancode, bool* ignore); + +bool scancode_filter_released(unsigned char* scancode); \ No newline at end of file diff --git a/kernel/src/fs/devices/DeviceFS.cpp b/kernel/src/fs/devices/DeviceFS.cpp index e6de7470..edb9a72a 100644 --- a/kernel/src/fs/devices/DeviceFS.cpp +++ b/kernel/src/fs/devices/DeviceFS.cpp @@ -1,5 +1,6 @@ #include "fs/devices/DeviceFS.h" #include "fs/devices/Console.h" +#include "fs/devices/Keyboard.h" #include "fs/devices/Random.h" #include "fs/devices/Serial.h" #include "fs/devices/Version.h" @@ -27,6 +28,7 @@ VFS::Node* DeviceFS::get() devfs_files[devfs_file_count++] = ConsoleDevice::create_new("console"); devfs_files[devfs_file_count++] = SerialDevice::create_new("serial"); devfs_files[devfs_file_count++] = RandomDevice::create_new("random"); + devfs_files[devfs_file_count++] = KeyboardDevice::create_new("kbd"); return devfs_root; } diff --git a/kernel/src/fs/devices/Keyboard.cpp b/kernel/src/fs/devices/Keyboard.cpp new file mode 100644 index 00000000..1a4af295 --- /dev/null +++ b/kernel/src/fs/devices/Keyboard.cpp @@ -0,0 +1,44 @@ +#define MODULE "kbd" + +#include "fs/devices/Keyboard.h" +#include "config.h" +#include "log/Log.h" +#include "render/TextRenderer.h" +#include "std/stdio.h" +#include "std/stdlib.h" +#include "std/string.h" + +char* kbd_buffer = nullptr; +uint64_t kbd_bufsize = 0; + +VFS::Node* KeyboardDevice::create_new(const char* devname) +{ + VFS::Node* dev = new VFS::Node; + dev->read_func = KeyboardDevice::read; + dev->inode = 0; + dev->length = 0; + dev->type = VFS_DEVICE; + dev->flags = 0; + strncpy(dev->name, devname, sizeof(dev->name)); + return dev; +} + +ssize_t KeyboardDevice::read(VFS::Node* node, size_t, size_t size, char* buffer) +{ + if (!node) return -1; + if (!kbd_buffer) return 0; + if (size > kbd_bufsize) size = kbd_bufsize; + memcpy(buffer, kbd_buffer, size); + memmove(kbd_buffer, kbd_buffer + size, kbd_bufsize - size); + kbd_bufsize -= size; + kbd_buffer = (char*)krealloc(kbd_buffer, kbd_bufsize); + return (ssize_t)size; +} + +void KeyboardDevice::append(char c) +{ + kbd_bufsize++; + kbd_buffer = (char*)krealloc( + kbd_buffer, kbd_bufsize); // FIXME: We should probably not be calling realloc every time a key is pressed. + kbd_buffer[kbd_bufsize - 1] = c; +} \ No newline at end of file diff --git a/kernel/src/interrupts/IRQ.cpp b/kernel/src/interrupts/IRQ.cpp index 3402cc5d..22142735 100644 --- a/kernel/src/interrupts/IRQ.cpp +++ b/kernel/src/interrupts/IRQ.cpp @@ -1,10 +1,11 @@ #define MODULE "irq" #include "interrupts/IRQ.h" +#include "fs/devices/Keyboard.h" #include "io/IO.h" #include "io/PIC.h" #include "log/Log.h" -#include "misc/reboot.h" +#include "misc/Scancodes.h" #include "rand/Init.h" #include "std/stdio.h" #include "thread/PIT.h" @@ -19,10 +20,11 @@ void IRQ::interrupt_handler(Context* context) Scheduler::task_tick(context); break; case 1: { - [[maybe_unused]] volatile unsigned char scancode = IO::inb(0x60); - kdbgln("Keyboard key pressed/released, seconds since boot: %ld.%ld", PIT::ms_since_boot / 1000, - PIT::ms_since_boot % 1000); - reboot(); + unsigned char scancode = IO::inb(0x60); + bool ignore = false; + char key = translate_scancode(scancode, &ignore); + if (ignore) break; + KeyboardDevice::append(key); break; } default: kwarnln("Unhandled IRQ: %ld", context->irq_number); break; diff --git a/kernel/src/misc/Scancodes.cpp b/kernel/src/misc/Scancodes.cpp new file mode 100644 index 00000000..c694a4d5 --- /dev/null +++ b/kernel/src/misc/Scancodes.cpp @@ -0,0 +1,168 @@ +#include "misc/Scancodes.h" + +#define SCANCODE_EXTENDED 0xE0 + +bool scancode_filter_released(unsigned char* scancode) +{ + if (*scancode > 0x80) + { + *scancode -= 0x80; + return true; + } + return false; +} + +static bool next_key_ignored = false; // FIXME: Do not ignore extended scancodes. + +static bool left_shifted = false; +static bool right_shifted = false; +static bool capslock = false; + +static bool should_shift() +{ + if (capslock) return !(left_shifted || right_shifted); + return left_shifted || right_shifted; +} + +#define SCANCODE_LEFT_SHIFT 0x2A +#define SCANCODE_RIGHT_SHIFT 0x36 +#define SCANCODE_CAPS_LOCK 0x3A + +#define SCANCODE_LEFT_CONTROL 0x1D +#define SCANCODE_TAB 0x0F +#define SCANCODE_LEFT_ALT 0x38 +#define SCANCODE_F11 0x57 +#define SCANCODE_F12 0x58 + +static bool should_ignore_key(char scancode) +{ + return (scancode > 0x3A && scancode < 0x47) || scancode == SCANCODE_LEFT_CONTROL || scancode == SCANCODE_TAB || + scancode == SCANCODE_LEFT_ALT || scancode == SCANCODE_F11 || scancode == SCANCODE_F12; +} + +char keys_normal[] = { + '\0', + '\1', // escape + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', + '\0', // tab + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', + '\0', // left ctrl + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', + '\0', // left shift + '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', + '\0', // right shift + '*', // keypad * + '\0', // left alt + ' ', + '\0', // caps lock + '\0', // f1 + '\0', // f2 + '\0', // f3 + '\0', // f4 + '\0', // f5 + '\0', // f6 + '\0', // f7 + '\0', // f8 + '\0', // f9 + '\0', // f10 + '\0', // num lock + '\0', // scroll lock + '7', // keypad 7 + '8', // keypad 8 + '9', // keypad 9 + '-', // keypad - + '4', // keypad 4 + '5', // keypad 5 + '6', // keypad 6 + '+', // keypad + + '1', // keypad 1 + '2', // keypad 2 + '3', // keypad 3 + '0', // keypad 0 + '.', // keypad . +}; + +char keys_shifted[] = { + '\0', + '\1', // escape + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', + '\0', // tab + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', + '\0', // left ctrl + 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', + '\0', // left shift + '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', + '\0', // right shift + '*', // keypad * + '\0', // left alt + ' ', + '\0', // caps lock + '\0', // f1 + '\0', // f2 + '\0', // f3 + '\0', // f4 + '\0', // f5 + '\0', // f6 + '\0', // f7 + '\0', // f8 + '\0', // f9 + '\0', // f10 + '\0', // num lock + '\0', // scroll lock + '7', // keypad 7 + '8', // keypad 8 + '9', // keypad 9 + '-', // keypad - + '4', // keypad 4 + '5', // keypad 5 + '6', // keypad 6 + '+', // keypad + + '1', // keypad 1 + '2', // keypad 2 + '3', // keypad 3 + '0', // keypad 0 + '.', // keypad . +}; + +char translate_scancode(unsigned char scancode, bool* ignore) +{ + if (next_key_ignored) + { + next_key_ignored = false; + *ignore = true; + return 0; + } + if (scancode == SCANCODE_EXTENDED) + { + next_key_ignored = true; + *ignore = true; + return 0; + } + bool released = scancode_filter_released(&scancode); + if (scancode == SCANCODE_CAPS_LOCK) + { + if (!released) { capslock = !capslock; } + *ignore = true; + return 0; + } + if (scancode == SCANCODE_LEFT_SHIFT) + { + left_shifted = !released; + *ignore = true; + return 0; + } + if (scancode == SCANCODE_RIGHT_SHIFT) + { + right_shifted = !released; + *ignore = true; + return 0; + } + if (released || should_ignore_key(scancode)) + { + *ignore = true; + return 0; + } + *ignore = false; + if (should_shift()) { return keys_shifted[scancode]; } + return keys_normal[scancode]; +} \ No newline at end of file