/* This file is part of Fennix Kernel. Fennix Kernel is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Fennix Kernel is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Fennix Kernel. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "../kernel.h" #include "cmds.hpp" NewLock(ShellLock); struct Command { const char *Name; void (*Function)(const char *); }; bool ignoreBuiltin = false; void __cmd_builtin(const char *) { ignoreBuiltin = !ignoreBuiltin; if (ignoreBuiltin) printf("Builtin commands are now ignored.\n"); else printf("Builtin commands are now accepted.\n"); } static Command commands[] = { {"ls", cmd_ls}, {"tree", cmd_tree}, {"cd", cmd_cd}, {"cat", cmd_cat}, {"echo", cmd_echo}, {"clear", cmd_clear}, {"help", nullptr}, {"exit", cmd_exit}, {"reboot", cmd_reboot}, {"shutdown", cmd_shutdown}, {"ps", cmd_ps}, {"kill", cmd_kill}, {"killall", cmd_killall}, {"top", cmd_top}, {"mem", cmd_mem}, {"uname", cmd_uname}, {"whoami", cmd_whoami}, {"uptime", cmd_uptime}, {"lspci", cmd_lspci}, {"lsacpi", cmd_lsacpi}, {"lsmod", cmd_lsmod}, {"modinfo", cmd_modinfo}, {"insmod", nullptr}, {"rmmod", nullptr}, {"modprobe", nullptr}, {"depmod", nullptr}, {"panic", cmd_panic}, {"dump", cmd_dump}, {"theme", cmd_theme}, {"builtin", __cmd_builtin}, }; void KShellThread() { assert(!ShellLock.Locked()); ShellLock.Lock(__FUNCTION__); KPrint("Starting kernel shell..."); thisThread->SetPriority(Tasking::TaskPriority::Normal); thisProcess->CWD = fs->GetRoot(0); std::string strBuf = ""; std::vector history; size_t hIdx = 0; bool ctrlDown = false; bool upperCase = false; bool tabDblPress = false; const char *keyDevPath = "/dev/input/keyboard"; Node root = fs->GetRoot(0); Node kfd = fs->Lookup(root, keyDevPath); if (kfd == nullptr) { KPrint("Failed to open keyboard device!"); return; } /* This makes debugging easier. */ auto strBufBck = [&]() { for (size_t i = 0; i < strBuf.size(); i++) { putchar('\b'); putchar(' '); putchar('\b'); } }; printf("Using \x1b[1;34m%s\x1b[0m for keyboard input.\n", keyDevPath); while (true) { size_t bsCount = 0; uint32_t homeX = 0, homeY = 0; uint32_t unseekX = 0, unseekY = 0; size_t seekCount = 0; debug("clearing strBuf(\"%s\")", strBuf.c_str()); strBuf.clear(); Node cwd = thisProcess->CWD; if (!cwd) cwd = fs->GetRoot(0); debug("cwd: %s", cwd->Path.c_str()); printf("\x1b[1;34m%s@%s:%s$ \x1b[0m", "kernel", "fennix", cwd->Path.c_str()); KeyboardReport scBuf{}; ssize_t nBytes; while (true) { nBytes = fs->Read(kfd, &scBuf, sizeof(KeyboardReport), 0); if (nBytes == 0) { debug("Empty read from keyboard device!"); continue; } if (nBytes < (ssize_t)sizeof(KeyboardReport)) { KPrint("Failed to read from keyboard device: %s", strerror((int)nBytes)); return; } const KeyScanCodes &sc = scBuf.Key; switch (sc & ~KEY_PRESSED) { case KEY_LEFT_CTRL: case KEY_RIGHT_CTRL: { if (sc & KEY_PRESSED) ctrlDown = true; else ctrlDown = false; continue; } case KEY_LEFT_SHIFT: case KEY_RIGHT_SHIFT: { if (sc & KEY_PRESSED) upperCase = true; else upperCase = false; continue; } case KEY_TAB: { if (!(sc & KEY_PRESSED)) continue; if (!tabDblPress) { tabDblPress = true; continue; } tabDblPress = false; if (strBuf.size() == 0) { for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) printf("%s ", commands[i].Name); putchar('\n'); goto SecLoopEnd; } strBufBck(); for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { if (strncmp(strBuf.c_str(), commands[i].Name, strBuf.size()) != 0) continue; strBuf = commands[i].Name; for (size_t i = 0; i < strlen(strBuf.c_str()); i++) putchar(strBuf[i]); seekCount = bsCount = strBuf.size(); break; } continue; } case KEY_BACKSPACE: { if (!(sc & KEY_PRESSED)) continue; if (bsCount == 0) continue; if (seekCount == bsCount) { debug("seekCount == bsCount (%d == %d)", seekCount, bsCount); putchar('\b'); putchar(' '); putchar('\b'); strBuf.pop_back(); seekCount = --bsCount; continue; } strBufBck(); size_t strSeek = seekCount ? seekCount - 1 : 0; seekCount = strSeek; debug("strSeek: %d: %s", strSeek, strBuf.c_str()); strBuf.erase(strSeek); printf("%s", strBuf.c_str()); debug("after strBuf: %s", strBuf.c_str()); bsCount--; continue; } case KEY_DELETE: { if (!(sc & KEY_PRESSED)) continue; if (bsCount == 0) continue; if (seekCount == bsCount) { debug("seekCount == bsCount (%d == %d)", seekCount, bsCount); continue; } strBufBck(); debug("seekCount: %d: %s", seekCount, strBuf.c_str()); strBuf.erase(seekCount); printf("%s", strBuf.c_str()); debug("after strBuf: %s", strBuf.c_str()); bsCount--; continue; } case KEY_UP_ARROW: { if (!(sc & KEY_PRESSED)) continue; if (history.size() == 0 || hIdx == 0) continue; hIdx--; if (unseekX != 0 || unseekY != 0) { unseekX = unseekY = 0; } strBufBck(); strBuf = history[hIdx]->c_str(); for (size_t i = 0; i < strlen(strBuf.c_str()); i++) putchar(strBuf[i]); seekCount = bsCount = strBuf.size(); continue; } case KEY_DOWN_ARROW: { if (!(sc & KEY_PRESSED)) continue; if (history.size() == 0 || hIdx == history.size()) continue; if (hIdx == history.size() - 1) { if (unseekX != 0 || unseekY != 0) unseekX = unseekY = 0; hIdx++; strBufBck(); seekCount = bsCount = strBuf.size(); continue; } if (unseekX != 0 || unseekY != 0) unseekX = unseekY = 0; strBufBck(); hIdx++; strBuf = history[hIdx]->c_str(); for (size_t i = 0; i < strlen(strBuf.c_str()); i++) putchar(strBuf[i]); seekCount = bsCount = strBuf.size(); continue; } case KEY_LEFT_ARROW: { if (!(sc & KEY_PRESSED)) continue; if (seekCount == 0) continue; debug("orig seekCount: %d", seekCount); seekCount--; if (ctrlDown) { uint32_t offset = 0; /* We use unsigned so this will underflow to SIZE_MAX and it is safe because we add 1 to it. */ while (seekCount != SIZE_MAX && strBuf[seekCount] == ' ') { seekCount--; offset++; } while (seekCount != SIZE_MAX && strBuf[seekCount] != ' ') { seekCount--; offset++; } seekCount++; debug("offset: %d; seekCount: %d", offset, seekCount); continue; } continue; } case KEY_RIGHT_ARROW: { if (!(sc & KEY_PRESSED)) continue; if (seekCount == bsCount) continue; seekCount++; debug("orig seekCount: %d", seekCount); if (ctrlDown) { uint32_t offset = 0; while (seekCount <= bsCount && strBuf[seekCount] != ' ') { seekCount++; offset++; } while (seekCount <= bsCount && strBuf[seekCount] == ' ') { seekCount++; offset++; } seekCount--; debug("offset: %d; seekCount: %d", offset, seekCount); continue; } continue; } case KEY_HOME: { if (!(sc & KEY_PRESSED)) continue; if (homeX == 0 || homeY == 0) continue; seekCount = 0; debug("seekCount set to 0"); continue; } case KEY_END: { if (!(sc & KEY_PRESSED)) continue; if (unseekX == 0 || unseekY == 0) continue; seekCount = bsCount; debug("seekCount set to bsCount (%d)", bsCount); continue; } default: break; } if (!(sc & KEY_PRESSED)) continue; if (!Driver::IsValidChar(sc)) continue; char c = Driver::GetScanCode(sc, upperCase); debug("sc: %#lx, uc: %d -> %c", sc, upperCase, c); if (ctrlDown) { switch (std::toupper((char)c)) { case 'C': { putchar('^'); putchar('C'); putchar('\n'); fixme("No SIGINT handler yet."); goto SecLoopEnd; } case 'D': { putchar('^'); putchar('D'); putchar('\n'); fixme("No SIGKILL handler yet."); goto SecLoopEnd; } default: continue; } } if (c == '\n') { putchar(c); if (strBuf.length() > 0) { std::string *hBuff = new std::string(strBuf.c_str()); debug("cloned strBuf(\"%s\") to hBuff(\"%s\")", strBuf.c_str(), hBuff->c_str()); history.push_back(hBuff); hIdx = history.size(); debug("pushed \"%s\" to history; index: %d", hBuff->c_str(), hIdx); } break; } else if (seekCount >= bsCount) { putchar(c); debug("BEFORE strBuf(\"%s\") %ld %ld", strBuf.c_str(), strBuf.size(), strBuf.capacity()); strBuf += c; debug("AFTER strBuf(\"%s\") %ld %ld", strBuf.c_str(), strBuf.size(), strBuf.capacity()); seekCount = ++bsCount; } else { strBufBck(); debug("seekCount: %d; \"%s\"", seekCount, strBuf.c_str()); strBuf.insert(seekCount, (size_t)1, c); printf("%s", strBuf.c_str()); debug("after strBuf: %s (seek and bs is +1 [seek: %d; bs: %d])", strBuf.c_str(), seekCount + 1, bsCount + 1); seekCount++; bsCount++; } } SecLoopEnd: debug("strBuf.length(): %d", strBuf.length()); if (strBuf.length() == 0) continue; bool Found = false; for (size_t i = 0; i < sizeof(commands) / sizeof(Command); i++) { if (unlikely(strncmp(strBuf.c_str(), "builtin", strBuf.length()) == 0)) { __cmd_builtin(nullptr); Found = true; break; } if (ignoreBuiltin) break; std::string cmd_extracted; for (size_t i = 0; i < strBuf.length(); i++) { if (strBuf[i] == ' ') break; cmd_extracted += strBuf[i]; } // debug("cmd: %s, array[%d]: %s", cmd_extracted.c_str(), i, commands[i].Name); if (strncmp(commands[i].Name, cmd_extracted.c_str(), cmd_extracted.size()) == 0) { if (strlen(commands[i].Name) != cmd_extracted.size()) continue; Found = true; std::string arg_only = ""; const char *cmd_name = commands[i].Name; for (size_t i = strlen(cmd_name) + 1; i < strBuf.length(); i++) arg_only += strBuf[i]; if (commands[i].Function) commands[i].Function(arg_only.c_str()); else { std::string cmd_only; for (size_t i = 0; i < strBuf.length(); i++) { if (strBuf[i] == ' ') break; cmd_only += strBuf[i]; } printf("%s: command not implemented\n", cmd_only.c_str()); } break; } } if (Found) continue; std::string cmd_only; for (size_t i = 0; i < strBuf.length(); i++) { if (strBuf[i] == ' ') break; cmd_only += strBuf[i]; } debug("cmd_only: %s", cmd_only.c_str()); std::string path = "/bin/"; if (fs->PathIsRelative(cmd_only.c_str())) { path += cmd_only; if (!fs->Lookup(root, path.c_str())) path = "/usr/bin/" + cmd_only; } else path = cmd_only; debug("path: %s", path.c_str()); if (fs->Lookup(root, path.c_str())) { const char *envp[5] = { "PATH=/bin:/usr/bin", "TERM=tty", "HOME=/root", "USER=root", nullptr}; const char **argv; if (strBuf.length() > cmd_only.length()) { std::string arg_only; for (size_t i = cmd_only.length() + 1; i < strBuf.length(); i++) arg_only += strBuf[i]; argv = new const char *[3]; argv[0] = path.c_str(); argv[1] = new char[arg_only.length() + 1]; strcpy((char *)argv[1], arg_only.c_str()); argv[2] = nullptr; debug("argv[0]: %s; argv[1]: %s", argv[0], argv[1]); } else { argv = new const char *[2]; argv[0] = path.c_str(); argv[1] = nullptr; } Tasking::TaskCompatibility compat = Tasking::Native; if (Config.LinuxSubsystem) compat = Tasking::Linux; int ret = Execute::Spawn(path.c_str(), argv, envp, nullptr, false, compat, false); if (argv[1]) delete argv[1]; delete argv; if (ret >= 0) { Tasking::TCB *tcb; Tasking::PCB *pcb; pcb = TaskManager->GetProcessByID(ret); if (pcb == nullptr) { printf("KShell: Failed to get process by ID\n"); continue; } pcb->SetWorkingDirectory(cwd); tcb = TaskManager->GetThreadByID(ret, pcb); if (tcb == nullptr) { printf("KShell: Failed to get thread by ID\n"); continue; } TaskManager->WaitForThread(tcb); continue; } } printf("%s: command not found\n", cmd_only.c_str()); } inf_loop; }