Viewing: kconsole.c
📄 kconsole.c (Read Only) ⬅ To go back
#include "kconsole.h"
#include "console.h"
#include "vga_console.h"
#include "utils.h"
#include "fs.h"
#include "heap.h"
#include "pmm.h"
#include "process.h"
#include "keyboard.h"
#include "arch/arch_platform.h"
#include "hal/system.h"
#include "hal/cpu.h"
#include "kernel/init.h"
#include "ata_pio.h"

#define KCMD_MAX 128

/* ---- Output helpers ---- */

static void kc_puts(const char* s) {
    console_write(s);
}

static void kc_putc(char c) {
    console_put_char(c);
}

/* ---- Command history ---- */

#define HIST_MAX 16
static char hist_buf[HIST_MAX][KCMD_MAX];
static int hist_head  = 0;  /* next write slot */
static int hist_count = 0;  /* entries stored */

static void hist_add(const char* line) {
    if (line[0] == '\0') return;
    /* Don't add duplicates of the last command */
    if (hist_count > 0) {
        int prev = (hist_head - 1 + HIST_MAX) % HIST_MAX;
        if (strcmp(hist_buf[prev], line) == 0) return;
    }
    strncpy(hist_buf[hist_head], line, KCMD_MAX - 1);
    hist_buf[hist_head][KCMD_MAX - 1] = '\0';
    hist_head = (hist_head + 1) % HIST_MAX;
    if (hist_count < HIST_MAX) hist_count++;
}

static const char* hist_get(int idx) {
    /* idx 0 = most recent, 1 = second most recent, etc. */
    if (idx < 0 || idx >= hist_count) return 0;
    int slot = (hist_head - 1 - idx + HIST_MAX) % HIST_MAX;
    return hist_buf[slot];
}

/* ---- Line editing helpers ---- */

static void kc_cursor_left(int n) {
    for (int i = 0; i < n; i++) kc_putc('\b');
}

static void kc_erase_line(char* buf, int* len, int* cur) {
    kc_cursor_left(*cur);
    for (int i = 0; i < *len; i++) kc_putc(' ');
    kc_cursor_left(*len);
    *len = 0;
    *cur = 0;
    buf[0] = '\0';
}

static void kc_replace_line(char* buf, int* len, int* cur, const char* text) {
    kc_erase_line(buf, len, cur);
    int nlen = (int)strlen(text);
    if (nlen >= KCMD_MAX) nlen = KCMD_MAX - 1;
    memcpy(buf, text, (size_t)nlen);
    buf[nlen] = '\0';
    *len = nlen;
    *cur = nlen;
    for (int i = 0; i < nlen; i++) kc_putc(buf[i]);
}

/* ---- Readline with VT100 escape parsing ---- */

static int kc_readline(char* buf, int maxlen) {
    int len = 0;
    int cur = 0;
    int hist_nav = -1;    /* -1 = editing current, 0+ = history index */
    char saved[KCMD_MAX]; /* saved current line when navigating history */
    saved[0] = '\0';

    enum { ST_NORMAL, ST_ESC, ST_CSI } state = ST_NORMAL;
    char csi_buf[8];
    int csi_len = 0;

    buf[0] = '\0';

    for (;;) {
        int ch = kgetc();
        if (ch < 0) continue;

        if (state == ST_ESC) {
            if (ch == '[') {
                state = ST_CSI;
                csi_len = 0;
            } else {
                state = ST_NORMAL;
            }
            continue;
        }

        if (state == ST_CSI) {
            if (ch >= '0' && ch <= '9' && csi_len < 7) {
                csi_buf[csi_len++] = (char)ch;
                continue;
            }
            csi_buf[csi_len] = '\0';
            state = ST_NORMAL;

            switch (ch) {
            case 'A': /* Up arrow — previous history */
                if (hist_nav + 1 < hist_count) {
                    if (hist_nav == -1) {
                        memcpy(saved, buf, (size_t)(len + 1));
                    }
                    hist_nav++;
                    kc_replace_line(buf, &len, &cur, hist_get(hist_nav));
                }
                break;
            case 'B': /* Down arrow — next history / restore */
                if (hist_nav > 0) {
                    hist_nav--;
                    kc_replace_line(buf, &len, &cur, hist_get(hist_nav));
                } else if (hist_nav == 0) {
                    hist_nav = -1;
                    kc_replace_line(buf, &len, &cur, saved);
                }
                break;
            case 'C': /* Right arrow */
                if (cur < len) {
                    kc_putc(buf[cur]);
                    cur++;
                }
                break;
            case 'D': /* Left arrow */
                if (cur > 0) {
                    cur--;
                    kc_putc('\b');
                }
                break;
            case 'H': /* Home */
                kc_cursor_left(cur);
                cur = 0;
                break;
            case 'F': /* End */
                for (int i = cur; i < len; i++) kc_putc(buf[i]);
                cur = len;
                break;
            case '~': { /* Delete (CSI 3 ~) */
                int param = 0;
                for (int i = 0; i < csi_len; i++)
                    param = param * 10 + (csi_buf[i] - '0');
                if (param == 3 && cur < len) {
                    memmove(&buf[cur], &buf[cur + 1], (size_t)(len - cur - 1));
                    len--;
                    buf[len] = '\0';
                    for (int i = cur; i < len; i++) kc_putc(buf[i]);
                    kc_putc(' ');
                    kc_cursor_left(len - cur + 1);
                }
                break;
            }
            default:
                break;
            }
            continue;
        }

        /* ST_NORMAL */
        if (ch == 0x1B) {
            state = ST_ESC;
            continue;
        }

        if (ch == '\n' || ch == '\r') {
            kc_putc('\n');
            buf[len] = '\0';
            return len;
        }

        if (ch == '\b' || ch == 127) {
            if (cur > 0) {
                cur--;
                memmove(&buf[cur], &buf[cur + 1], (size_t)(len - cur - 1));
                len--;
                buf[len] = '\0';
                kc_putc('\b');
                for (int i = cur; i < len; i++) kc_putc(buf[i]);
                kc_putc(' ');
                kc_cursor_left(len - cur + 1);
            }
            continue;
        }

        /* Ctrl-A = Home, Ctrl-E = End, Ctrl-U = kill line, Ctrl-K = kill to end */
        if (ch == 0x01) { /* Ctrl-A */
            kc_cursor_left(cur);
            cur = 0;
            continue;
        }
        if (ch == 0x05) { /* Ctrl-E */
            for (int i = cur; i < len; i++) kc_putc(buf[i]);
            cur = len;
            continue;
        }
        if (ch == 0x15) { /* Ctrl-U: kill whole line */
            kc_erase_line(buf, &len, &cur);
            continue;
        }
        if (ch == 0x0B) { /* Ctrl-K: kill to end of line */
            for (int i = cur; i < len; i++) kc_putc(' ');
            kc_cursor_left(len - cur);
            len = cur;
            buf[len] = '\0';
            continue;
        }

        if (ch < ' ' || ch > '~') continue;

        /* Insert printable character at cursor */
        if (len >= maxlen - 1) continue;
        memmove(&buf[cur + 1], &buf[cur], (size_t)(len - cur));
        buf[cur] = (char)ch;
        len++;
        buf[len] = '\0';
        for (int i = cur; i < len; i++) kc_putc(buf[i]);
        cur++;
        kc_cursor_left(len - cur);
    }
}

/* ---- Commands ---- */

static void kconsole_help(void) {
    kc_puts("kconsole commands:\n");
    kc_puts("  help                              - Show this list\n");
    kc_puts("  clear                             - Clear screen\n");
    kc_puts("  ls [path]                         - List files in directory\n");
    kc_puts("  cat <file>                        - Read file content\n");
    kc_puts("  mem                               - Show memory stats\n");
    kc_puts("  dmesg                             - Show kernel log buffer\n");
    kc_puts("  lsblk                             - List detected ATA drives\n");
    kc_puts("  mount -t <type> /dev/<hd> <mnt>   - Mount filesystem\n");
    kc_puts("  reboot                            - Restart system\n");
    kc_puts("  halt                              - Halt the CPU\n");
}

static void kconsole_ls(const char* path) {
    fs_node_t* dir = NULL;

    if (!path || path[0] == '\0') {
        dir = fs_root;
    } else {
        dir = vfs_lookup(path);
    }

    if (!dir) {
        kprintf("ls: cannot access '%s': not found\n", path ? path : "/");
        return;
    }

    int (*fn_readdir)(struct fs_node*, uint32_t*, void*, uint32_t) = NULL;
    if (dir->i_ops && dir->i_ops->readdir) fn_readdir = dir->i_ops->readdir;
    if (!fn_readdir) {
        kprintf("ls: not a directory\n");
        return;
    }

    uint32_t idx = 0;
    struct vfs_dirent ent;
    while (1) {
        int rc = fn_readdir(dir, &idx, &ent, sizeof(ent));
        if (rc != 0) break;
        kprintf("  %s\n", ent.d_name);
    }
}

/* mount -t <fstype> /dev/<hdX> <mountpoint> */
static void kconsole_mount(const char* args) {
    const char* p = args;

    /* Expect: -t <type> /dev/<hd> <mnt> */
    while (*p == ' ') p++;
    if (p[0] != '-' || p[1] != 't' || p[2] != ' ') {
        kprintf("Usage: mount -t <type> /dev/<hd> <mountpoint>\n");
        return;
    }
    p += 3;
    while (*p == ' ') p++;

    /* Extract fstype */
    const char* fs_start = p;
    while (*p && *p != ' ') p++;
    if (*p == '\0') {
        kprintf("Usage: mount -t <type> /dev/<hd> <mountpoint>\n");
        return;
    }
    char fstype[16];
    size_t fs_len = (size_t)(p - fs_start);
    if (fs_len >= sizeof(fstype)) fs_len = sizeof(fstype) - 1;
    memcpy(fstype, fs_start, fs_len);
    fstype[fs_len] = '\0';

    while (*p == ' ') p++;

    /* Extract device */
    const char* dev_start = p;
    while (*p && *p != ' ') p++;
    if (*p == '\0') {
        kprintf("Usage: mount -t <type> /dev/<hd> <mountpoint>\n");
        return;
    }
    char device[32];
    size_t dev_len = (size_t)(p - dev_start);
    if (dev_len >= sizeof(device)) dev_len = sizeof(device) - 1;
    memcpy(device, dev_start, dev_len);
    device[dev_len] = '\0';

    while (*p == ' ') p++;

    /* Extract mountpoint */
    const char* mp_start = p;
    while (*p && *p != ' ') p++;
    char mountpoint[64];
    size_t mp_len = (size_t)(p - mp_start);
    if (mp_len >= sizeof(mountpoint)) mp_len = sizeof(mountpoint) - 1;
    memcpy(mountpoint, mp_start, mp_len);
    mountpoint[mp_len] = '\0';

    if (mountpoint[0] != '/') {
        kprintf("mount: mountpoint must be absolute path\n");
        return;
    }

    /* Resolve device to drive ID */
    int drive = -1;
    if (strncmp(device, "/dev/", 5) == 0) {
        drive = ata_name_to_drive(device + 5);
    }
    if (drive < 0) {
        kprintf("mount: unknown device: %s\n", device);
        return;
    }
    if (!ata_pio_drive_present(drive)) {
        kprintf("mount: device %s not present\n", device);
        return;
    }

    (void)init_mount_fs(fstype, drive, 0, mountpoint);
}

static void kconsole_exec(const char* cmd) {
    if (strcmp(cmd, "help") == 0) {
        kconsole_help();
    }
    else if (strcmp(cmd, "clear") == 0) {
        vga_clear();
    }
    else if (strcmp(cmd, "ls") == 0) {
        kconsole_ls(NULL);
    }
    else if (strncmp(cmd, "ls ", 3) == 0) {
        kconsole_ls(cmd + 3);
    }
    else if (strncmp(cmd, "cat ", 4) == 0) {
        const char* fname = cmd + 4;
        fs_node_t* file = NULL;
        if (fname[0] == '/') {
            file = vfs_lookup(fname);
        } else {
            char abs[132];
            abs[0] = '/';
            abs[1] = 0;
            strcpy(abs + 1, fname);
            file = vfs_lookup(abs);
        }
        if (file) {
            uint8_t* buf = (uint8_t*)kmalloc(file->length + 1);
            if (buf) {
                uint32_t sz = vfs_read(file, 0, file->length, buf);
                buf[sz] = 0;
                kprintf("%s\n", (char*)buf);
                kfree(buf);
            } else {
                kprintf("cat: out of memory\n");
            }
        } else {
            kprintf("cat: %s: not found\n", fname);
        }
    }
    else if (strcmp(cmd, "mem") == 0) {
        kprintf("Memory Stats:\n");
        pmm_print_stats();
    }
    else if (strcmp(cmd, "dmesg") == 0) {
        char buf[4096];
        size_t n = klog_read(buf, sizeof(buf));
        if (n > 0) {
            console_write(buf);
            console_write("\n");
        } else {
            kc_puts("(empty)\n");
        }
    }
    else if (strcmp(cmd, "reboot") == 0) {
        hal_system_reboot();
    }
    else if (strcmp(cmd, "halt") == 0) {
        kc_puts("System halted.\n");
        hal_cpu_disable_interrupts();
        for (;;) hal_cpu_idle();
    }
    else if (strcmp(cmd, "lsblk") == 0) {
        for (int i = 0; i < ATA_MAX_DRIVES; i++) {
            const char* name = ata_drive_to_name(i);
            if (!name) continue;
            kprintf("  /dev/%s  %s\n", name,
                    ata_pio_drive_present(i) ? "present" : "not detected");
        }
    }
    else if (strncmp(cmd, "mount ", 6) == 0) {
        kconsole_mount(cmd + 6);
    }
    else if (cmd[0] != '\0') {
        kprintf("unknown command: %s\n", cmd);
    }
}

/* ---- Main entry ---- */

void kconsole_enter(void) {
    keyboard_set_callback(0);

    kc_puts("\n[PANIC] Userspace init failed -- dropping to kconsole.\n");
    kc_puts("        Type 'help' for commands, 'reboot' to restart.\n\n");

    char line[KCMD_MAX];

    for (;;) {
        kc_puts("kconsole> ");
        kc_readline(line, KCMD_MAX);

        if (line[0] != '\0') {
            hist_add(line);
        }

        klog_set_suppress(1);
        kconsole_exec(line);
        klog_set_suppress(0);
    }
}