#include "fs/devices/ConsoleDevice.h" #include "Log.h" #include "arch/Keyboard.h" #include "fs/devices/DeviceRegistry.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" #include "video/TextConsole.h" #include #include #include #include #include #include static Buffer g_console_input; static bool g_eof { false }; static bool g_echo { true }; static bool g_stop_background_output { false }; static Option g_foreground_process_group; Result ConsoleDevice::create() { auto device = (SharedPtr)TRY(make_shared()); g_foreground_process_group = {}; return DeviceRegistry::register_special_device(DeviceRegistry::Console, 0, device); } static Result handle_background_process_group(bool can_succeed, int signo) { if (!g_foreground_process_group.has_value()) return {}; auto foreground_pgrp = g_foreground_process_group.value(); auto* current = Scheduler::current(); if ((pid_t)current->pgid == foreground_pgrp) return {}; if ((current->signal_mask & (1 << (signo - 1))) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN)) { if (can_succeed) return {}; return err(EIO); } current->send_signal(signo); if (can_succeed) return err(EINTR); return err(EIO); } Result ConsoleDevice::read(u8* buf, usize, usize length) const { TRY(handle_background_process_group(false, SIGTTIN)); if (length > g_console_input.size()) length = g_console_input.size(); memcpy(buf, g_console_input.data(), length); memmove(g_console_input.data(), g_console_input.data() + length, g_console_input.size() - length); g_console_input.try_resize(g_console_input.size() - length).release_value(); if (!length && g_eof) g_eof = false; return length; } Result ConsoleDevice::write(const u8* buf, usize, usize length) { if (g_stop_background_output) TRY(handle_background_process_group(true, SIGTTOU)); TextConsole::write((const char*)buf, length); return length; } bool ConsoleDevice::blocking() const { return g_eof ? false : g_console_input.size() == 0; } static Vector g_temp_input; void ConsoleDevice::did_press_key(char key) { if (key == '\b') { if (g_temp_input.try_pop().has_value()) { if (g_echo) TextConsole::putwchar(L'\b'); } return; } // Ctrl+D if (key == 'd' && (Keyboard::modifiers() == Keyboard::LeftControl)) { if (g_temp_input.size() == 0) g_eof = true; return; } if (key == 'c' && (Keyboard::modifiers() == Keyboard::LeftControl)) { g_temp_input.clear(); if (g_echo) TextConsole::wprintln(L"^C"); if (g_foreground_process_group.has_value()) { Scheduler::for_each_in_process_group(g_foreground_process_group.value(), [](Thread* thread) { thread->send_signal(SIGINT); return true; }); } return; } if (key == 'e' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) { Scheduler::dump_state(); return; } if (key == 'm' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) { kinfoln("Total memory: %s", to_dynamic_unit(MemoryManager::total()).release_value().chars()); kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars()); kinfoln("Used memory: %s", to_dynamic_unit(MemoryManager::used()).release_value().chars()); kinfoln("Reserved memory: %s", to_dynamic_unit(MemoryManager::reserved()).release_value().chars()); return; } if (key == 'h' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) { dump_heap_usage(); return; } g_temp_input.try_append((u8)key); if (key == '\n') { g_console_input.append_data(g_temp_input.data(), g_temp_input.size()); g_temp_input.clear(); } if (!g_echo) return; TextConsole::putchar(key); } Result ConsoleDevice::ioctl(int request, void* arg) { switch (request) { case TCGETS: { struct termios tc { .c_lflag = 0 }; if (g_echo) tc.c_lflag |= ECHO; if (g_stop_background_output) tc.c_lflag |= TOSTOP; return MemoryManager::copy_to_user_typed((struct termios*)arg, &tc) ? 0 : err(EFAULT); } case TCSETS: { TRY(handle_background_process_group(true, SIGTTOU)); struct termios tc; if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &tc)) return err(EFAULT); if (tc.c_lflag & ECHO) g_echo = true; else g_echo = false; if (tc.c_lflag & TOSTOP) g_stop_background_output = true; else g_stop_background_output = false; return 0; } case TIOCSPGRP: { TRY(handle_background_process_group(true, SIGTTOU)); pid_t pgid; if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT); bool pgid_exists = false; Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) { pgid_exists = true; return false; }); if (!pgid_exists) return err(EPERM); g_foreground_process_group = pgid; return 0; } case TIOCGPGRP: { pid_t pgid = g_foreground_process_group.value_or((pid_t)next_thread_id()); if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); return 0; } default: return err(EINVAL); } }