/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2012, 2013, 2014. This file is part of Sortix. Sortix 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. Sortix 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 Sortix. If not, see . logterminal.cpp A simple terminal that writes to the kernel log. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logterminal.h" namespace Sortix { const unsigned SUPPORTED_MODES = TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 | TERMMODE_LINEBUFFER | TERMMODE_ECHO | TERMMODE_NONBLOCK; LogTerminal::LogTerminal(dev_t dev, mode_t mode, uid_t owner, gid_t group, Keyboard* keyboard, KeyboardLayoutExecutor* kblayout) { inode_type = INODE_TYPE_TTY; this->dev = dev; this->ino = (ino_t) this; this->type = S_IFCHR; this->stat_mode = (mode & S_SETABLE) | this->type; this->stat_uid = owner; this->stat_gid = group; this->keyboard = keyboard; this->kblayout = kblayout; this->partiallywritten = 0; this->control = false; this->termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 | TERMMODE_LINEBUFFER | TERMMODE_ECHO; this->termlock = KTHREAD_MUTEX_INITIALIZER; this->datacond = KTHREAD_COND_INITIALIZER; this->numwaiting = 0; this->numeofs = 0; this->foreground_pgid = 0; keyboard->SetOwner(this, NULL); } LogTerminal::~LogTerminal() { delete keyboard; delete kblayout; } int LogTerminal::settermmode(ioctx_t* /*ctx*/, unsigned newtermmode) { if ( newtermmode & ~SUPPORTED_MODES ) { errno = EINVAL; return -1; } ScopedLock lock(&termlock); unsigned oldtermmode = termmode; bool oldutf8 = oldtermmode & TERMMODE_UTF8; bool newutf8 = newtermmode & TERMMODE_UTF8; if ( oldutf8 != newutf8 ) partiallywritten = 0; termmode = newtermmode; if ( !(newtermmode & TERMMODE_LINEBUFFER) ) CommitLineBuffer(); partiallywritten = 0; return 0; } int LogTerminal::gettermmode(ioctx_t* ctx, unsigned* mode) { ScopedLock lock(&termlock); if ( !ctx->copy_to_dest(mode, &termmode, sizeof(termmode)) ) return -1; return 0; } int LogTerminal::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp) { struct wincurpos retwcp; memset(&retwcp, 0, sizeof(retwcp)); size_t cursor_column, cursor_row; Log::GetCursor(&cursor_column, &cursor_row); retwcp.wcp_col = cursor_column; retwcp.wcp_row = cursor_row; if ( !ctx->copy_to_dest(wcp, &retwcp, sizeof(retwcp)) ) return -1; return 0; } int LogTerminal::tcgetwinsize(ioctx_t* ctx, struct winsize* ws) { struct winsize retws; memset(&retws, 0, sizeof(retws)); retws.ws_col = Log::Width(); retws.ws_row = Log::Height(); if ( !ctx->copy_to_dest(ws, &retws, sizeof(retws)) ) return -1; return 0; } int LogTerminal::tcsetpgrp(ioctx_t* /*ctx*/, pid_t pgid) { ScopedLock lock(&termlock); Process* process = CurrentProcess()->GetPTable()->Get(pgid); if ( !pgid || !process ) return errno = ESRCH, -1; kthread_mutex_lock(&process->groupchildlock); bool is_process_group = process->group == process; kthread_mutex_unlock(&process->groupchildlock); if ( !is_process_group ) return errno = EINVAL, -1; foreground_pgid = pgid; return 0; } pid_t LogTerminal::tcgetpgrp(ioctx_t* /*ctx*/) { ScopedLock lock(&termlock); return foreground_pgid; } int LogTerminal::sync(ioctx_t* /*ctx*/) { return Log::Sync() ? 0 : -1; } void LogTerminal::OnKeystroke(Keyboard* kb, void* /*user*/) { while ( kb->HasPending() ) ProcessKeystroke(kb->Read()); } void LogTerminal::ProcessKeystroke(int kbkey) { if ( !kbkey ) return; ScopedLock lock(&termlock); if ( kbkey == KBKEY_LCTRL ) { control = true; } if ( kbkey == -KBKEY_LCTRL ) { control = false; } if ( termmode & TERMMODE_SIGNAL && control && kbkey == KBKEY_C ) { while ( linebuffer.CanBackspace() ) linebuffer.Backspace(); if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) process->DeliverGroupSignal(SIGINT); return; } if ( termmode & TERMMODE_SIGNAL && control && kbkey == KBKEY_D ) { if ( !linebuffer.CanPop() ) { numeofs++; if ( numwaiting ) kthread_cond_broadcast(&datacond); poll_channel.Signal(POLLIN | POLLRDNORM); } return; } if ( termmode & TERMMODE_LINEBUFFER && control && kbkey == KBKEY_W ) { bool had_non_whitespace = false; c_w_delete_more: if ( !linebuffer.CanBackspace() ) { return; } uint32_t delchar = linebuffer.WouldBackspace(); bool waskbkey = KBKEY_DECODE(delchar); bool wasuni = !waskbkey; bool whitespace = (wasuni && (delchar == ' ' || delchar == '\t' || delchar == '\n')) || (waskbkey && (abs(KBKEY_DECODE(delchar)) == -KBKEY_SPACE || abs(KBKEY_DECODE(delchar)) == -KBKEY_TAB || abs(KBKEY_DECODE(delchar)) == -KBKEY_ENTER)); if ( wasuni && whitespace && had_non_whitespace ) return; if ( wasuni && !whitespace ) had_non_whitespace = true; linebuffer.Backspace(); if ( (!waskbkey || termmode & TERMMODE_KBKEY) && (!wasuni || termmode & TERMMODE_UNICODE) && termmode & TERMMODE_ECHO && wasuni ) Log::Print("\b"); goto c_w_delete_more; } if ( termmode & TERMMODE_LINEBUFFER && control && kbkey == KBKEY_U ) { while ( linebuffer.CanBackspace() ) { uint32_t delchar = linebuffer.Backspace(); bool waskbkey = KBKEY_DECODE(delchar); bool wasuni = !waskbkey; if ( (!waskbkey || termmode & TERMMODE_KBKEY) && (!wasuni || termmode & TERMMODE_UNICODE) && termmode & TERMMODE_ECHO && wasuni ) Log::Print("\b"); } return; } if ( termmode & TERMMODE_LINEBUFFER && control && kbkey == KBKEY_L ) { while ( linebuffer.CanBackspace() ) linebuffer.Backspace(); QueueUnicode(KBKEY_ENCODE(KBKEY_ENTER)); QueueUnicode('\n'); QueueUnicode(KBKEY_ENCODE(-KBKEY_ENTER)); Log::PrintF("\e[H\e[2J"); return; } uint32_t unikbkey = KBKEY_ENCODE(kbkey); QueueUnicode(unikbkey); uint32_t unicode = kblayout->Translate(kbkey); if ( 0 < kbkey ) QueueUnicode(unicode); } void LogTerminal::QueueUnicode(uint32_t unicode) { if ( !unicode ) return; int kbkey = KBKEY_DECODE(unicode); int abskbkey = (kbkey < 0) ? -kbkey : kbkey; bool wasenter = kbkey == KBKEY_ENTER || unicode == '\n'; bool kbkeymode = termmode & TERMMODE_KBKEY; bool unicodemode = termmode & TERMMODE_UNICODE; bool linemode = termmode & TERMMODE_LINEBUFFER; bool echomode = termmode & TERMMODE_ECHO; if ( linemode && abskbkey == KBKEY_BKSPC ) { return; } while ( linemode && unicode == '\b' ) { if ( !linebuffer.CanBackspace() ) { return; } uint32_t delchar = linebuffer.Backspace(); bool waskbkey = KBKEY_DECODE(delchar); bool wasuni = !waskbkey; if ( waskbkey && !kbkeymode ) { continue; } if ( wasuni && !unicodemode ) { continue; } if ( !echomode ) { return; } if ( wasuni ) { Log::Print("\b"); } return; } if ( !linebuffer.Push(unicode) ) { Log::PrintF("Warning: LogTerminal driver dropping keystroke due " "to insufficient buffer space\n"); return; } // TODO: Could it be the right thing to print the unicode character even // if it is unprintable (it's an encoded keystroke)? if ( !KBKEY_DECODE(unicode) && echomode ) { mbstate_t ps; memset(&ps, 0, sizeof(ps)); char utf8buf[MB_CUR_MAX]; size_t num_bytes = wcrtomb(utf8buf, (wchar_t) unicode, &ps); Log::PrintData(utf8buf, num_bytes); } bool commit = !linemode || wasenter; if ( commit ) CommitLineBuffer(); } void LogTerminal::CommitLineBuffer() { linebuffer.Commit(); if ( numwaiting ) kthread_cond_broadcast(&datacond); poll_channel.Signal(POLLIN | POLLRDNORM); } ssize_t LogTerminal::read(ioctx_t* ctx, uint8_t* userbuf, size_t count) { ScopedLockSignal lock(&termlock); if ( !lock.IsAcquired() ) { errno = EINTR; return -1; } size_t sofar = 0; size_t left = count; bool blocking = !(termmode & TERMMODE_NONBLOCK); while ( left && !linebuffer.CanPop() && blocking && !numeofs ) { if ( ctx->dflags & O_NONBLOCK ) return errno = EWOULDBLOCK, -1; numwaiting++; bool abort = !kthread_cond_wait_signal(&datacond, &termlock); numwaiting--; if ( abort ) { errno = EINTR; return -1; } } if ( left && !linebuffer.CanPop() && !blocking && !numeofs ) { errno = EWOULDBLOCK; return -1; } if ( numeofs ) { numeofs--; return 0; } while ( left && linebuffer.CanPop() ) { uint32_t codepoint = linebuffer.Peek(); int kbkey = KBKEY_DECODE(codepoint); bool ignore = false; if ( !(termmode & TERMMODE_KBKEY) && kbkey ) { ignore = true; } if ( !(termmode & TERMMODE_UNICODE) && !kbkey ) { ignore = true; } if ( ignore ) { linebuffer.Pop(); continue; } uint8_t* buf; size_t bufsize; uint8_t codepointbuf[4]; char utf8buf[MB_CUR_MAX]; if ( termmode & TERMMODE_UTF8 ) { mbstate_t ps; memset(&ps, 0, sizeof(ps)); size_t num_bytes = wcrtomb(utf8buf, (wchar_t) codepoint, &ps); if ( num_bytes == (size_t) -1) { Log::PrintF("Warning: logterminal driver dropping invalid " "codepoint 0x%x\n", codepoint); num_bytes = 0; } buf = (uint8_t*) utf8buf; bufsize = num_bytes; } else { codepointbuf[0] = (codepoint >> 24U) & 0xFFU; codepointbuf[1] = (codepoint >> 16U) & 0xFFU; codepointbuf[2] = (codepoint >> 8U) & 0xFFU; codepointbuf[3] = (codepoint >> 0U) & 0xFFU; buf = (uint8_t*) &codepointbuf; bufsize = sizeof(codepointbuf); // TODO: Whoops, the above is big-endian and the user programs // expect the data to be in the host endian. That's bad. For now // just send the data in host endian, but this will break when // terminals are accessed over the network. buf = (uint8_t*) &codepoint; } if ( bufsize < partiallywritten ) { partiallywritten = bufsize; } buf += partiallywritten; bufsize -= partiallywritten; if ( sofar && left < bufsize ) { return sofar; } size_t amount = left < bufsize ? left : bufsize; ctx->copy_to_dest(userbuf + sofar, buf, amount); partiallywritten = (amount < bufsize) ? partiallywritten + amount : 0; left -= amount; sofar += amount; if ( !partiallywritten ) { linebuffer.Pop(); } } return sofar; } ssize_t LogTerminal::write(ioctx_t* ctx, const uint8_t* io_buffer, size_t count) { // TODO: Add support for ioctx to the kernel log. const size_t BUFFER_SIZE = 64UL; if ( BUFFER_SIZE < count ) count = BUFFER_SIZE; char buffer[BUFFER_SIZE]; if ( !ctx->copy_from_src(buffer, io_buffer, count) ) return -1; Log::PrintData(buffer, count); return count; } short LogTerminal::PollEventStatus() { short status = 0; if ( linebuffer.CanPop() || numeofs ) status |= POLLIN | POLLRDNORM; if ( true /* can always write */ ) status |= POLLOUT | POLLWRNORM; return status; } int LogTerminal::poll(ioctx_t* /*ctx*/, PollNode* node) { ScopedLockSignal lock(&termlock); short ret_status = PollEventStatus() & node->events; if ( ret_status ) { node->master->revents |= ret_status; return 0; } poll_channel.Register(node); return errno = EAGAIN, -1; } ssize_t LogTerminal::tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count) { if ( !name ) { static const char index[] = "kblayout\0"; size_t index_size = sizeof(index) - 1; if ( buffer && count < index_size ) return errno = ERANGE, -1; if ( buffer && !ctx->copy_to_dest(buffer, &index, index_size) ) return -1; return (ssize_t) index_size; } else if ( !strcmp(name, "kblayout") ) { ScopedLockSignal lock(&termlock); const uint8_t* data; size_t size; if ( !kblayout->Download(&data, &size) ) return -1; if ( buffer && count < size ) return errno = ERANGE, -1; if ( buffer && !ctx->copy_to_dest(buffer, data, size) ) return -1; return (ssize_t) size; } else return errno = ENOENT, -1; } ssize_t LogTerminal::tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count) { if ( !name ) return errno = EPERM, -1; else if ( !strcmp(name, "kblayout") ) { uint8_t* data = new uint8_t[count]; if ( !data ) return -1; if ( !ctx->copy_from_src(data, buffer, count) ) return -1; ScopedLockSignal lock(&termlock); if ( !kblayout->Upload(data, count) ) return -1; delete[] data; return (ssize_t) count; } else return errno = ENOENT, -1; } } // namespace Sortix