diff --git a/kernel/Makefile b/kernel/Makefile index a4c5d059..76448fee 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -149,6 +149,7 @@ textterminal.o \ thread.o \ time.o \ timer.o \ +tty.o \ uart.o \ user-timer.o \ vga.o \ diff --git a/kernel/logterminal.cpp b/kernel/logterminal.cpp index 72471b26..b5b4b24f 100644 --- a/kernel/logterminal.cpp +++ b/kernel/logterminal.cpp @@ -55,16 +55,6 @@ namespace Sortix { -static const unsigned int SUPPORTED_TERMMODES = TERMMODE_KBKEY - | TERMMODE_UNICODE - | TERMMODE_SIGNAL - | TERMMODE_UTF8 - | TERMMODE_LINEBUFFER - | TERMMODE_ECHO - | TERMMODE_NONBLOCK - | TERMMODE_TERMIOS - | TERMMODE_DISABLE; - static const int MODIFIER_ALT = 1 << 0; static const int MODIFIER_LSHIFT = 1 << 1; static const int MODIFIER_RSHIFT = 1 << 2; @@ -107,17 +97,6 @@ static const struct kbkey_sequence kbkey_sequences[] = { "\e[24~", KBKEY_F12, 0 }, }; -static inline bool IsByteUnescaped(unsigned char byte) -{ - return (32 <= byte && byte != 127) || - byte == '\t' || byte == '\n' || byte == '\r'; -} - -static inline bool IsUTF8Continuation(unsigned char byte) -{ - return (byte & 0b11000000) == 0b10000000; -} - static inline const struct kbkey_sequence* LookupKeystrokeSequence(int kbkey) { size_t count = sizeof(kbkey_sequences) / sizeof(kbkey_sequences[0]); @@ -129,40 +108,11 @@ static inline const struct kbkey_sequence* LookupKeystrokeSequence(int kbkey) LogTerminal::LogTerminal(dev_t dev, mode_t mode, uid_t owner, gid_t group, Keyboard* keyboard, KeyboardLayoutExecutor* kblayout) + : TTY(dev, mode, owner, group) { - 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->modifiers = 0; - memset(&tio, 0, sizeof(tio)); - tio.c_iflag = BRKINT | ICRNL | IXANY | IXON; - tio.c_oflag = OPOST; - tio.c_cflag = CS8 /*| CREAD*/ | HUPCL; // CREAD unset for boot security. - tio.c_lflag = ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; - tio.c_cc[VEOF] = CONTROL('D'); - tio.c_cc[VEOL] = M_CONTROL('?'); - tio.c_cc[VERASE] = CONTROL('?'); - tio.c_cc[VINTR] = CONTROL('C'); - tio.c_cc[VKILL] = CONTROL('U'); - tio.c_cc[VMIN] = 1; - tio.c_cc[VQUIT] = CONTROL('\\'); - tio.c_cc[VSTART] = CONTROL('Q'); - tio.c_cc[VSTOP] = CONTROL('S'); - tio.c_cc[VSUSP] = CONTROL('Z'); - tio.c_cc[VTIME] = 0; - tio.c_cc[VWERASE] = CONTROL('W'); - tio.c_ispeed = B38400; - tio.c_ospeed = B38400; - this->termlock = KTHREAD_MUTEX_INITIALIZER; - this->datacond = KTHREAD_COND_INITIALIZER; - this->numeofs = 0; - this->foreground_pgid = 0; keyboard->SetOwner(this, NULL); } @@ -172,142 +122,6 @@ LogTerminal::~LogTerminal() delete kblayout; } -int LogTerminal::settermmode(ioctx_t* /*ctx*/, unsigned int termmode) -{ - ScopedLock lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - if ( termmode & ~SUPPORTED_TERMMODES ) - return errno = EINVAL, -1; - tcflag_t old_cflag = tio.c_cflag; - tcflag_t new_cflag = old_cflag; - tcflag_t old_lflag = tio.c_lflag; - tcflag_t new_lflag = old_lflag; - if ( termmode & TERMMODE_KBKEY ) - new_lflag |= ISORTIX_KBKEY; - else - new_lflag &= ~ISORTIX_KBKEY; - if ( !(termmode & TERMMODE_UNICODE) ) - new_lflag |= ISORTIX_CHARS_DISABLE; - else - new_lflag &= ~ISORTIX_CHARS_DISABLE; - if ( termmode & TERMMODE_SIGNAL ) - new_lflag |= ISIG; - else - new_lflag &= ~ISIG; - if ( !(termmode & TERMMODE_UTF8) ) - new_lflag |= ISORTIX_32BIT; - else - new_lflag &= ~ISORTIX_32BIT; - if ( termmode & TERMMODE_LINEBUFFER ) - new_lflag |= ICANON; - else - new_lflag &= ~ICANON; - if ( termmode & TERMMODE_ECHO ) - new_lflag |= ECHO | ECHOE; - else - new_lflag &= ~(ECHO | ECHOE); - if ( termmode & TERMMODE_NONBLOCK ) - new_lflag |= ISORTIX_NONBLOCK; - else - new_lflag &= ~ISORTIX_NONBLOCK; - if ( !(termmode & TERMMODE_TERMIOS) ) - new_lflag |= ISORTIX_TERMMODE; - else - new_lflag &= ~ISORTIX_TERMMODE; - if ( !(termmode & TERMMODE_DISABLE) ) - new_cflag |= CREAD; - else - new_cflag &= ~CREAD; - bool oldnoutf8 = old_lflag & ISORTIX_32BIT; - bool newnoutf8 = new_lflag & ISORTIX_32BIT; - if ( oldnoutf8 != newnoutf8 ) - memset(&read_ps, 0, sizeof(read_ps)); - tio.c_cflag = new_cflag; - tio.c_lflag = new_lflag; - if ( !(tio.c_lflag & ICANON) ) - CommitLineBuffer(); - return 0; -} - -int LogTerminal::gettermmode(ioctx_t* ctx, unsigned int* mode) -{ - ScopedLock lock(&termlock); - unsigned int termmode = 0; - if ( tio.c_lflag & ISORTIX_KBKEY ) - termmode |= TERMMODE_KBKEY; - if ( !(tio.c_lflag & ISORTIX_CHARS_DISABLE) ) - termmode |= TERMMODE_UNICODE; - if ( tio.c_lflag & ISIG ) - termmode |= TERMMODE_SIGNAL; - if ( !(tio.c_lflag & ISORTIX_32BIT) ) - termmode |= TERMMODE_UTF8; - if ( tio.c_lflag & ICANON ) - termmode |= TERMMODE_LINEBUFFER; - if ( tio.c_lflag & (ECHO | ECHOE) ) - termmode |= TERMMODE_ECHO; - if ( tio.c_lflag & ISORTIX_NONBLOCK ) - termmode |= TERMMODE_NONBLOCK; - if ( !(tio.c_lflag & ISORTIX_TERMMODE) ) - termmode |= TERMMODE_TERMIOS; - if ( !(tio.c_cflag & CREAD) ) - termmode |= TERMMODE_DISABLE; - if ( !ctx->copy_to_dest(mode, &termmode, sizeof(termmode)) ) - return -1; - return 0; -} - -int LogTerminal::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp) -{ - ScopedLock lock(&termlock); - 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) -{ - ScopedLock lock(&termlock); - 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); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - if ( pgid <= 0 ) - return errno = ESRCH, -1; - Process* process = CurrentProcess()->GetPTable()->Get(pgid); - if ( !process ) - return errno = ESRCH, -1; - kthread_mutex_lock(&process->groupparentlock); - bool is_process_group = process->group == process; - kthread_mutex_unlock(&process->groupparentlock); - 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*/) { ScopedLock lock(&termlock); @@ -423,363 +237,6 @@ void LogTerminal::ProcessKeystroke(int kbkey) ProcessByte(str[len-1]); } -void LogTerminal::ProcessString(const char* string) -{ - for ( size_t i = 0; string[i]; i++ ) - ProcessByte((unsigned char) string[i]); -} - -void LogTerminal::ProcessUnicode(uint32_t unicode) -{ - mbstate_t ps; - memset(&ps, 0, sizeof(ps)); - char mb[MB_CUR_MAX]; - size_t amount = wcrtomb(mb, unicode, &ps); - for ( size_t i = 0; amount != (size_t) -1 && i < amount; i++ ) - ProcessByte((unsigned char) mb[i]); -} - -bool LogTerminal::CheckHandledByte(tcflag_t lflags, - unsigned char key, - unsigned char byte) -{ - return (tio.c_lflag & lflags) == lflags && key && key == byte; -} - -void LogTerminal::ProcessByte(unsigned char byte, uint32_t control_unicode) -{ - if ( byte == '\r' && tio.c_iflag & IGNCR ) - return; - if ( byte == '\r' && tio.c_iflag & ICRNL ) - byte = '\n'; - else if ( byte == '\n' && tio.c_iflag & INLCR ) - byte = '\r'; - - // TODO: tio.c_cc[VEOL] - // TODO: tio.c_cc[VSTART] - // TODO: tio.c_cc[VSTOP] - // TODO: tio.c_cc[VSUSP] - - if ( CheckHandledByte(ISIG, tio.c_cc[VQUIT], byte) ) - { - while ( linebuffer.CanBackspace() ) - linebuffer.Backspace(); - if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) - process->DeliverGroupSignal(SIGQUIT); - return; - } - - if ( CheckHandledByte(ISIG, tio.c_cc[VINTR], byte) ) - { - while ( linebuffer.CanBackspace() ) - linebuffer.Backspace(); - if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) - process->DeliverGroupSignal(SIGINT); - return; - } - - if ( CheckHandledByte(ISIG | ICANON, tio.c_cc[VEOF], byte) ) - { - if ( !linebuffer.CanPop() ) - { - numeofs++; - kthread_cond_broadcast(&datacond); - poll_channel.Signal(POLLIN | POLLRDNORM); - } - return; - } - - if ( CheckHandledByte(ISIG, tio.c_cc[VQUIT], byte) ) - { - while ( linebuffer.CanBackspace() ) - linebuffer.Backspace(); - if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) - process->DeliverGroupSignal(SIGQUIT); - return; - } - - if ( CheckHandledByte(ICANON, tio.c_cc[VERASE], byte) || - CheckHandledByte(ICANON | ISORTIX_TERMMODE, '\b', byte) ) - { - while ( linebuffer.CanBackspace() ) - { - uint32_t delchar = linebuffer.Backspace(); - if ( 256 <= delchar ) - continue; - if ( IsUTF8Continuation(delchar) ) - continue; - if ( tio.c_lflag & ECHOE ) - { - // TODO: Handle tab specially. (Is that even possible without - // knowing cursor position?). - Log::Print("\b \b"); - if ( !IsByteUnescaped(delchar) ) - Log::Print("\b \b"); - } - break; - } - return; - } - - if ( CheckHandledByte(ICANON, tio.c_cc[VWERASE], byte) ) - { - bool had_non_whitespace = false; - while ( linebuffer.CanBackspace() ) - { - uint32_t delchar = linebuffer.WouldBackspace(); - if ( 256 <= delchar ) - continue; - if ( IsUTF8Continuation(delchar) ) - continue; - if ( delchar == L' ' || delchar == L'\t' || delchar == L'\n' ) - { - if ( had_non_whitespace ) - break; - } - else - had_non_whitespace = true; - linebuffer.Backspace(); - if ( tio.c_lflag & ECHOE ) - { - Log::Print("\b \b"); - if ( !IsByteUnescaped(delchar) ) - Log::Print("\b \b"); - } - } - return; - } - - if ( CheckHandledByte(ICANON, tio.c_cc[VKILL], byte) ) - { - while ( linebuffer.CanBackspace() ) - { - uint32_t delchar = linebuffer.Backspace(); - if ( 256 <= delchar ) - continue; - if ( IsUTF8Continuation(delchar) ) - continue; - if ( tio.c_lflag & ECHOE ) - { - Log::Print("\b \b"); - if ( !IsByteUnescaped(delchar) ) - Log::Print("\b \b"); - } - } - return; - } - - if ( CheckHandledByte(ICANON | ISORTIX_TERMMODE, CONTROL('L'), byte) ) - { - while ( linebuffer.CanBackspace() ) - linebuffer.Backspace(); - ProcessUnicode(KBKEY_ENCODE(KBKEY_ENTER)); - ProcessByte('\n'); - ProcessUnicode(KBKEY_ENCODE(-KBKEY_ENTER)); - Log::PrintF("\e[H\e[2J"); - return; - } - - if ( tio.c_lflag & ISORTIX_CHARS_DISABLE ) - return; - - if ( control_unicode && - !(tio.c_lflag & (ICANON | ISIG)) && - tio.c_lflag & ISORTIX_KBKEY ) - { - ProcessUnicode(control_unicode); - return; - } - - if ( !linebuffer.Push(byte) ) - return; - - if ( tio.c_lflag & ECHO ) - { - if ( IsByteUnescaped(byte) ) - Log::PrintData(&byte, 1); - else - Log::PrintF("^%c", CONTROL(byte)); - } - - if ( !(tio.c_lflag & ICANON) || byte == '\n' ) - CommitLineBuffer(); -} - -void LogTerminal::CommitLineBuffer() -{ - linebuffer.Commit(); - if ( linebuffer.CanPop() || numeofs ) - { - 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() ) - return errno = EINTR, -1; - if ( !RequireForeground(SIGTTIN) ) - return errno = EINTR, -1; - size_t sofar = 0; - size_t left = count; - bool nonblocking = tio.c_lflag & ISORTIX_NONBLOCK || - ctx->dflags & O_NONBLOCK; - while ( left ) - { - while ( !(linebuffer.CanPop() || numeofs) ) - { - if ( tio.c_lflag & ICANON ) - { - if ( sofar ) - return sofar; - if ( nonblocking ) - return errno = EWOULDBLOCK, -1; - if ( !kthread_cond_wait_signal(&datacond, &termlock) ) - return sofar ? sofar : (errno = EINTR, -1); - } - else - { - // TODO: Study POSIX 11.1.7 Non-Canonical Mode Input Processing. - if ( tio.c_cc[VMIN] <= sofar ) - return sofar; - if ( 0 < tio.c_cc[VMIN] ) - { - if ( nonblocking ) - return errno = EWOULDBLOCK, -1; - if ( 0 < sofar && 0 < tio.c_cc[VTIME] ) - { - // TODO: Only wait up until tio.c_cc[VTIME] * 0.1 - // seconds between bytes. - } - if ( !kthread_cond_wait_signal(&datacond, &termlock) ) - return sofar ? sofar : (errno = EINTR, -1); - } - else if ( tio.c_cc[VMIN] == 0 && 0 < tio.c_cc[VTIME] ) - { - if ( sofar || true /* tio.c_cc[VTIME] * 0.1 seconds passed - since start of read function */ ) - return sofar; - if ( nonblocking ) - return errno = EWOULDBLOCK, -1; - // TODO: Only wait up until tio.c_cc[VTIME] * 0.1 seconds. - if ( !kthread_cond_wait_signal(&datacond, &termlock) ) - return sofar ? sofar : (errno = EINTR, -1); - } - else - return sofar; - } - if ( !RequireForeground(SIGTTIN) ) - return sofar ? sofar : (errno = EINTR, -1); - } - if ( numeofs ) - { - if ( sofar ) - return sofar; - return numeofs--, 0; - } - uint32_t codepoint = linebuffer.Peek(); - if ( tio.c_lflag & ISORTIX_32BIT ) - { - if ( left < sizeof(codepoint) ) - return sofar; - linebuffer.Pop(); - if ( 256 <= codepoint && !(tio.c_lflag & ISORTIX_KBKEY) ) - continue; - if ( codepoint < 256 && tio.c_lflag & ISORTIX_CHARS_DISABLE ) - continue; - if ( codepoint < 256 ) - { - char c = codepoint; - wchar_t wc; - size_t amount = mbrtowc(&wc, &c, 1, &read_ps); - if ( amount == (size_t) -2 ) - continue; - if ( amount == (size_t) -1 ) - { - memset(&read_ps, 0, sizeof(read_ps)); - wc = 0xFFFD; /* REPLACEMENT CHARACTER */ - } - codepoint = wc; - } - if ( !ctx->copy_to_dest(userbuf + sofar, &codepoint, - sizeof(codepoint)) ) - return sofar ? sofar : -1; - left -= sizeof(codepoint); - sofar += sizeof(codepoint); - continue; - } - if ( 256 <= codepoint ) - { - linebuffer.Pop(); - continue; - } - unsigned char c = codepoint; - if ( !ctx->copy_to_dest(userbuf + sofar, &c, 1) ) - return sofar ? sofar : -1; - linebuffer.Pop(); - left -= 1; - sofar += 1; - if ( tio.c_lflag & ICANON && c == '\n' ) - break; - } - return sofar; -} - -ssize_t LogTerminal::write(ioctx_t* ctx, const uint8_t* io_buffer, size_t count) -{ - ScopedLockSignal lock(&termlock); - if ( tio.c_lflag & TOSTOP && !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - // TODO: Add support for ioctx to the kernel log. - const size_t BUFFER_SIZE = 64UL; - char buffer[BUFFER_SIZE]; - size_t sofar = 0; - while ( sofar < count ) - { - size_t amount = count - sofar; - if ( BUFFER_SIZE < amount ) - amount = BUFFER_SIZE; - if ( !ctx->copy_from_src(buffer, io_buffer + sofar, amount) ) - return -1; - Log::PrintData(buffer, amount); - sofar += amount; - if ( sofar < count ) - { - kthread_mutex_unlock(&termlock); - kthread_yield(); - kthread_mutex_lock(&termlock); - if ( Signal::IsPending() ) - return sofar; - } - } - return (ssize_t) sofar; -} - -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 ) @@ -830,98 +287,4 @@ ssize_t LogTerminal::tcsetblob(ioctx_t* ctx, const char* name, const void* buffe return errno = ENOENT, -1; } -int LogTerminal::tcdrain(ioctx_t* /*ctx*/) -{ - ScopedLockSignal lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - return 0; -} - -int LogTerminal::tcflow(ioctx_t* /*ctx*/, int action) -{ - ScopedLockSignal lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - switch ( action ) - { - case TCOOFF: break; // TODO: Suspend output. - case TCOON: break; // TODO: Resume suspended output. - case TCIOFF: break; // TODO: Transmit STOP character. - case TCION: break; // TODO: Transmit START character. - default: return errno = EINVAL -1; - } - return 0; -} - -int LogTerminal::tcflush(ioctx_t* /*ctx*/, int queue_selector) -{ - if ( queue_selector & ~TCIOFLUSH ) - return errno = EINVAL, -1; - ScopedLockSignal lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - if ( queue_selector & TCIFLUSH ) - linebuffer.Flush(); - return 0; -} - -int LogTerminal::tcgetattr(ioctx_t* ctx, struct termios* io_tio) -{ - ScopedLockSignal lock(&termlock); - if ( !ctx->copy_to_dest(io_tio, &tio, sizeof(tio)) ) - return -1; - return 0; -} - -pid_t LogTerminal::tcgetsid(ioctx_t* /*ctx*/) -{ - // TODO: Implement sessions. - return 1; -} - -int LogTerminal::tcsendbreak(ioctx_t* /*ctx*/, int /*duration*/) -{ - ScopedLockSignal lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - return 0; -} - -int LogTerminal::tcsetattr(ioctx_t* ctx, int actions, const struct termios* io_tio) -{ - ScopedLockSignal lock(&termlock); - if ( !RequireForeground(SIGTTOU) ) - return errno = EINTR, -1; - switch ( actions ) - { - case TCSANOW: break; - case TCSADRAIN: break; - case TCSAFLUSH: linebuffer.Flush(); break; - default: return errno = EINVAL, -1; - } - if ( !ctx->copy_from_src(&tio, io_tio, sizeof(tio)) ) - return -1; - // TODO: Potentially take action here if something changed. - return 0; -} - -bool LogTerminal::RequireForeground(int sig) -{ - Thread* thread = CurrentThread(); - Process* process = thread->process; - ScopedLock group_lock(&process->groupparentlock); - if ( process->group->pid == foreground_pgid ) - return true; - if ( sigismember(&thread->signal_mask, sig) ) - return true; - ScopedLock signal_lock(&process->signal_lock); - if ( process->signal_actions[sig].sa_handler == SIG_IGN ) - return true; - signal_lock.Reset(); - group_lock.Reset(); - process->group->DeliverGroupSignal(sig); - return false; -} - } // namespace Sortix diff --git a/kernel/logterminal.h b/kernel/logterminal.h index 5aa5218d..4327de68 100644 --- a/kernel/logterminal.h +++ b/kernel/logterminal.h @@ -20,22 +20,11 @@ #ifndef SORTIX_LOGTERMINAL_H #define SORTIX_LOGTERMINAL_H -#include - -#include - -#include -#include -#include -#include - -#include "kb/kblayout.h" - -#include "linebuffer.h" +#include "tty.h" namespace Sortix { -class LogTerminal : public AbstractInode, public KeyboardOwner +class LogTerminal : public TTY, public KeyboardOwner { public: LogTerminal(dev_t dev, mode_t mode, uid_t owner, gid_t group, @@ -44,50 +33,18 @@ public: public: virtual int sync(ioctx_t* ctx); - virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); - virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); - virtual int tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp); - virtual int tcgetwinsize(ioctx_t* ctx, struct winsize* ws); - virtual int tcsetpgrp(ioctx_t* ctx, pid_t pgid); - virtual pid_t tcgetpgrp(ioctx_t* ctx); - virtual int settermmode(ioctx_t* ctx, unsigned termmode); - virtual int gettermmode(ioctx_t* ctx, unsigned* termmode); - virtual int poll(ioctx_t* ctx, PollNode* node); virtual ssize_t tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count); virtual ssize_t tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count); - virtual int tcdrain(ioctx_t* ctx); - virtual int tcflow(ioctx_t* ctx, int action); - virtual int tcflush(ioctx_t* ctx, int queue_selector); - virtual int tcgetattr(ioctx_t* ctx, struct termios* tio); - virtual pid_t tcgetsid(ioctx_t* ctx); - virtual int tcsendbreak(ioctx_t* ctx, int duration); - virtual int tcsetattr(ioctx_t* ctx, int actions, const struct termios* tio); public: virtual void OnKeystroke(Keyboard* keyboard, void* user); private: void ProcessKeystroke(int kbkey); - void ProcessString(const char* string); - void ProcessUnicode(uint32_t unicode); - void ProcessByte(unsigned char byte, uint32_t control_unicode = 0); - void CommitLineBuffer(); - short PollEventStatus(); - bool CheckForeground(); - bool RequireForeground(int sig); - bool CheckHandledByte(tcflag_t lflags, unsigned char key, unsigned char byte); private: - PollChannel poll_channel; - mutable kthread_mutex_t termlock; - kthread_cond_t datacond; - mbstate_t read_ps; - size_t numeofs; Keyboard* keyboard; KeyboardLayoutExecutor* kblayout; - LineBuffer linebuffer; - struct termios tio; - pid_t foreground_pgid; int modifiers; }; diff --git a/kernel/tty.cpp b/kernel/tty.cpp new file mode 100644 index 00000000..6f6defd8 --- /dev/null +++ b/kernel/tty.cpp @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * tty.cpp + * Terminal line discipline. + */ + +#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 +#include +#include + +#include "tty.h" + +#define CONTROL(x) (((x) - 64) & 127) +#define M_CONTROL(x) (128 + CONTROL(x)) + +namespace Sortix { + +static const unsigned int SUPPORTED_TERMMODES = TERMMODE_KBKEY + | TERMMODE_UNICODE + | TERMMODE_SIGNAL + | TERMMODE_UTF8 + | TERMMODE_LINEBUFFER + | TERMMODE_ECHO + | TERMMODE_NONBLOCK + | TERMMODE_TERMIOS + | TERMMODE_DISABLE; + +static inline bool IsByteUnescaped(unsigned char byte) +{ + return (32 <= byte && byte != 127) || + byte == '\t' || byte == '\n' || byte == '\r'; +} + +static inline bool IsUTF8Continuation(unsigned char byte) +{ + return (byte & 0b11000000) == 0b10000000; +} + +TTY::TTY(dev_t dev, mode_t mode, uid_t owner, gid_t group) +{ + if ( !dev ) + dev = (dev_t) this; + 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; + memset(&tio, 0, sizeof(tio)); + tio.c_iflag = BRKINT | ICRNL | IXANY | IXON; + tio.c_oflag = OPOST; + tio.c_cflag = CS8 /*| CREAD*/ | HUPCL; // CREAD unset for boot security. + tio.c_lflag = ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; + tio.c_cc[VEOF] = CONTROL('D'); + tio.c_cc[VEOL] = M_CONTROL('?'); + tio.c_cc[VERASE] = CONTROL('?'); + tio.c_cc[VINTR] = CONTROL('C'); + tio.c_cc[VKILL] = CONTROL('U'); + tio.c_cc[VMIN] = 1; + tio.c_cc[VQUIT] = CONTROL('\\'); + tio.c_cc[VSTART] = CONTROL('Q'); + tio.c_cc[VSTOP] = CONTROL('S'); + tio.c_cc[VSUSP] = CONTROL('Z'); + tio.c_cc[VTIME] = 0; + tio.c_cc[VWERASE] = CONTROL('W'); + tio.c_ispeed = B38400; + tio.c_ospeed = B38400; + this->termlock = KTHREAD_MUTEX_INITIALIZER; + this->datacond = KTHREAD_COND_INITIALIZER; + this->numeofs = 0; + this->foreground_pgid = 0; +} + +TTY::~TTY() +{ +} + +int TTY::settermmode(ioctx_t* /*ctx*/, unsigned int termmode) +{ + ScopedLock lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + if ( termmode & ~SUPPORTED_TERMMODES ) + return errno = EINVAL, -1; + tcflag_t old_cflag = tio.c_cflag; + tcflag_t new_cflag = old_cflag; + tcflag_t old_lflag = tio.c_lflag; + tcflag_t new_lflag = old_lflag; + if ( termmode & TERMMODE_KBKEY ) + new_lflag |= ISORTIX_KBKEY; + else + new_lflag &= ~ISORTIX_KBKEY; + if ( !(termmode & TERMMODE_UNICODE) ) + new_lflag |= ISORTIX_CHARS_DISABLE; + else + new_lflag &= ~ISORTIX_CHARS_DISABLE; + if ( termmode & TERMMODE_SIGNAL ) + new_lflag |= ISIG; + else + new_lflag &= ~ISIG; + if ( !(termmode & TERMMODE_UTF8) ) + new_lflag |= ISORTIX_32BIT; + else + new_lflag &= ~ISORTIX_32BIT; + if ( termmode & TERMMODE_LINEBUFFER ) + new_lflag |= ICANON; + else + new_lflag &= ~ICANON; + if ( termmode & TERMMODE_ECHO ) + new_lflag |= ECHO | ECHOE; + else + new_lflag &= ~(ECHO | ECHOE); + if ( termmode & TERMMODE_NONBLOCK ) + new_lflag |= ISORTIX_NONBLOCK; + else + new_lflag &= ~ISORTIX_NONBLOCK; + if ( !(termmode & TERMMODE_TERMIOS) ) + new_lflag |= ISORTIX_TERMMODE; + else + new_lflag &= ~ISORTIX_TERMMODE; + if ( !(termmode & TERMMODE_DISABLE) ) + new_cflag |= CREAD; + else + new_cflag &= ~CREAD; + bool oldnoutf8 = old_lflag & ISORTIX_32BIT; + bool newnoutf8 = new_lflag & ISORTIX_32BIT; + if ( oldnoutf8 != newnoutf8 ) + memset(&read_ps, 0, sizeof(read_ps)); + tio.c_cflag = new_cflag; + tio.c_lflag = new_lflag; + if ( !(tio.c_lflag & ICANON) ) + CommitLineBuffer(); + return 0; +} + +int TTY::gettermmode(ioctx_t* ctx, unsigned int* mode) +{ + ScopedLock lock(&termlock); + unsigned int termmode = 0; + if ( tio.c_lflag & ISORTIX_KBKEY ) + termmode |= TERMMODE_KBKEY; + if ( !(tio.c_lflag & ISORTIX_CHARS_DISABLE) ) + termmode |= TERMMODE_UNICODE; + if ( tio.c_lflag & ISIG ) + termmode |= TERMMODE_SIGNAL; + if ( !(tio.c_lflag & ISORTIX_32BIT) ) + termmode |= TERMMODE_UTF8; + if ( tio.c_lflag & ICANON ) + termmode |= TERMMODE_LINEBUFFER; + if ( tio.c_lflag & (ECHO | ECHOE) ) + termmode |= TERMMODE_ECHO; + if ( tio.c_lflag & ISORTIX_NONBLOCK ) + termmode |= TERMMODE_NONBLOCK; + if ( !(tio.c_lflag & ISORTIX_TERMMODE) ) + termmode |= TERMMODE_TERMIOS; + if ( !(tio.c_cflag & CREAD) ) + termmode |= TERMMODE_DISABLE; + if ( !ctx->copy_to_dest(mode, &termmode, sizeof(termmode)) ) + return -1; + return 0; +} + +int TTY::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp) +{ + ScopedLock lock(&termlock); + 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 TTY::tcgetwinsize(ioctx_t* ctx, struct winsize* ws) +{ + ScopedLock lock(&termlock); + 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 TTY::tcsetpgrp(ioctx_t* /*ctx*/, pid_t pgid) +{ + ScopedLock lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + if ( pgid <= 0 ) + return errno = ESRCH, -1; + Process* process = CurrentProcess()->GetPTable()->Get(pgid); + if ( !process ) + return errno = ESRCH, -1; + kthread_mutex_lock(&process->groupparentlock); + bool is_process_group = process->group == process; + kthread_mutex_unlock(&process->groupparentlock); + if ( !is_process_group ) + return errno = EINVAL, -1; + foreground_pgid = pgid; + return 0; +} + +pid_t TTY::tcgetpgrp(ioctx_t* /*ctx*/) +{ + ScopedLock lock(&termlock); + return foreground_pgid; +} + +void TTY::ProcessUnicode(uint32_t unicode) +{ + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + char mb[MB_CUR_MAX]; + size_t amount = wcrtomb(mb, unicode, &ps); + for ( size_t i = 0; amount != (size_t) -1 && i < amount; i++ ) + ProcessByte((unsigned char) mb[i]); +} + +bool TTY::CheckHandledByte(tcflag_t lflags, + unsigned char key, + unsigned char byte) +{ + return (tio.c_lflag & lflags) == lflags && key && key == byte; +} + +void TTY::ProcessByte(unsigned char byte, uint32_t control_unicode) +{ + if ( byte == '\r' && tio.c_iflag & IGNCR ) + return; + if ( byte == '\r' && tio.c_iflag & ICRNL ) + byte = '\n'; + else if ( byte == '\n' && tio.c_iflag & INLCR ) + byte = '\r'; + + // TODO: tio.c_cc[VEOL] + // TODO: tio.c_cc[VSTART] + // TODO: tio.c_cc[VSTOP] + // TODO: tio.c_cc[VSUSP] + + if ( CheckHandledByte(ISIG, tio.c_cc[VQUIT], byte) ) + { + while ( linebuffer.CanBackspace() ) + linebuffer.Backspace(); + if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) + process->DeliverGroupSignal(SIGQUIT); + return; + } + + if ( CheckHandledByte(ISIG, tio.c_cc[VINTR], byte) ) + { + while ( linebuffer.CanBackspace() ) + linebuffer.Backspace(); + if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) + process->DeliverGroupSignal(SIGINT); + return; + } + + if ( CheckHandledByte(ISIG | ICANON, tio.c_cc[VEOF], byte) ) + { + if ( !linebuffer.CanPop() ) + { + numeofs++; + kthread_cond_broadcast(&datacond); + poll_channel.Signal(POLLIN | POLLRDNORM); + } + return; + } + + if ( CheckHandledByte(ISIG, tio.c_cc[VQUIT], byte) ) + { + while ( linebuffer.CanBackspace() ) + linebuffer.Backspace(); + if ( Process* process = CurrentProcess()->GetPTable()->Get(foreground_pgid) ) + process->DeliverGroupSignal(SIGQUIT); + return; + } + + if ( CheckHandledByte(ICANON, tio.c_cc[VERASE], byte) || + CheckHandledByte(ICANON | ISORTIX_TERMMODE, '\b', byte) ) + { + while ( linebuffer.CanBackspace() ) + { + uint32_t delchar = linebuffer.Backspace(); + if ( 256 <= delchar ) + continue; + if ( IsUTF8Continuation(delchar) ) + continue; + if ( tio.c_lflag & ECHOE ) + { + // TODO: Handle tab specially. (Is that even possible without + // knowing cursor position?). + Log::Print("\b \b"); + if ( !IsByteUnescaped(delchar) ) + Log::Print("\b \b"); + } + break; + } + return; + } + + if ( CheckHandledByte(ICANON, tio.c_cc[VWERASE], byte) ) + { + bool had_non_whitespace = false; + while ( linebuffer.CanBackspace() ) + { + uint32_t delchar = linebuffer.WouldBackspace(); + if ( 256 <= delchar ) + continue; + if ( IsUTF8Continuation(delchar) ) + continue; + if ( delchar == L' ' || delchar == L'\t' || delchar == L'\n' ) + { + if ( had_non_whitespace ) + break; + } + else + had_non_whitespace = true; + linebuffer.Backspace(); + if ( tio.c_lflag & ECHOE ) + { + Log::Print("\b \b"); + if ( !IsByteUnescaped(delchar) ) + Log::Print("\b \b"); + } + } + return; + } + + if ( CheckHandledByte(ICANON, tio.c_cc[VKILL], byte) ) + { + while ( linebuffer.CanBackspace() ) + { + uint32_t delchar = linebuffer.Backspace(); + if ( 256 <= delchar ) + continue; + if ( IsUTF8Continuation(delchar) ) + continue; + if ( tio.c_lflag & ECHOE ) + { + Log::Print("\b \b"); + if ( !IsByteUnescaped(delchar) ) + Log::Print("\b \b"); + } + } + return; + } + + if ( CheckHandledByte(ICANON | ISORTIX_TERMMODE, CONTROL('L'), byte) ) + { + while ( linebuffer.CanBackspace() ) + linebuffer.Backspace(); + ProcessUnicode(KBKEY_ENCODE(KBKEY_ENTER)); + ProcessByte('\n'); + ProcessUnicode(KBKEY_ENCODE(-KBKEY_ENTER)); + Log::PrintF("\e[H\e[2J"); + return; + } + + if ( tio.c_lflag & ISORTIX_CHARS_DISABLE ) + return; + + if ( control_unicode && + !(tio.c_lflag & (ICANON | ISIG)) && + tio.c_lflag & ISORTIX_KBKEY ) + { + ProcessUnicode(control_unicode); + return; + } + + if ( !linebuffer.Push(byte) ) + return; + + if ( tio.c_lflag & ECHO ) + { + if ( IsByteUnescaped(byte) ) + Log::PrintData(&byte, 1); + else + Log::PrintF("^%c", CONTROL(byte)); + } + + if ( !(tio.c_lflag & ICANON) || byte == '\n' ) + CommitLineBuffer(); +} + +void TTY::CommitLineBuffer() +{ + linebuffer.Commit(); + if ( linebuffer.CanPop() || numeofs ) + { + kthread_cond_broadcast(&datacond); + poll_channel.Signal(POLLIN | POLLRDNORM); + } +} + +ssize_t TTY::read(ioctx_t* ctx, uint8_t* userbuf, size_t count) +{ + ScopedLockSignal lock(&termlock); + if ( !lock.IsAcquired() ) + return errno = EINTR, -1; + if ( !RequireForeground(SIGTTIN) ) + return errno = EINTR, -1; + size_t sofar = 0; + size_t left = count; + bool nonblocking = tio.c_lflag & ISORTIX_NONBLOCK || + ctx->dflags & O_NONBLOCK; + while ( left ) + { + while ( !(linebuffer.CanPop() || numeofs) ) + { + if ( tio.c_lflag & ICANON ) + { + if ( sofar ) + return sofar; + if ( nonblocking ) + return errno = EWOULDBLOCK, -1; + if ( !kthread_cond_wait_signal(&datacond, &termlock) ) + return sofar ? sofar : (errno = EINTR, -1); + } + else + { + // TODO: Study POSIX 11.1.7 Non-Canonical Mode Input Processing. + if ( tio.c_cc[VMIN] <= sofar ) + return sofar; + if ( 0 < tio.c_cc[VMIN] ) + { + if ( nonblocking ) + return errno = EWOULDBLOCK, -1; + if ( 0 < sofar && 0 < tio.c_cc[VTIME] ) + { + // TODO: Only wait up until tio.c_cc[VTIME] * 0.1 + // seconds between bytes. + } + if ( !kthread_cond_wait_signal(&datacond, &termlock) ) + return sofar ? sofar : (errno = EINTR, -1); + } + else if ( tio.c_cc[VMIN] == 0 && 0 < tio.c_cc[VTIME] ) + { + if ( sofar || true /* tio.c_cc[VTIME] * 0.1 seconds passed + since start of read function */ ) + return sofar; + if ( nonblocking ) + return errno = EWOULDBLOCK, -1; + // TODO: Only wait up until tio.c_cc[VTIME] * 0.1 seconds. + if ( !kthread_cond_wait_signal(&datacond, &termlock) ) + return sofar ? sofar : (errno = EINTR, -1); + } + else + return sofar; + } + if ( !RequireForeground(SIGTTIN) ) + return sofar ? sofar : (errno = EINTR, -1); + } + if ( numeofs ) + { + if ( sofar ) + return sofar; + return numeofs--, 0; + } + uint32_t codepoint = linebuffer.Peek(); + if ( tio.c_lflag & ISORTIX_32BIT ) + { + if ( left < sizeof(codepoint) ) + return sofar; + linebuffer.Pop(); + if ( 256 <= codepoint && !(tio.c_lflag & ISORTIX_KBKEY) ) + continue; + if ( codepoint < 256 && tio.c_lflag & ISORTIX_CHARS_DISABLE ) + continue; + if ( codepoint < 256 ) + { + char c = codepoint; + wchar_t wc; + size_t amount = mbrtowc(&wc, &c, 1, &read_ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + { + memset(&read_ps, 0, sizeof(read_ps)); + wc = 0xFFFD; /* REPLACEMENT CHARACTER */ + } + codepoint = wc; + } + if ( !ctx->copy_to_dest(userbuf + sofar, &codepoint, + sizeof(codepoint)) ) + return sofar ? sofar : -1; + left -= sizeof(codepoint); + sofar += sizeof(codepoint); + continue; + } + if ( 256 <= codepoint ) + { + linebuffer.Pop(); + continue; + } + unsigned char c = codepoint; + if ( !ctx->copy_to_dest(userbuf + sofar, &c, 1) ) + return sofar ? sofar : -1; + linebuffer.Pop(); + left -= 1; + sofar += 1; + if ( tio.c_lflag & ICANON && c == '\n' ) + break; + } + return sofar; +} + +ssize_t TTY::write(ioctx_t* ctx, const uint8_t* io_buffer, size_t count) +{ + ScopedLockSignal lock(&termlock); + if ( tio.c_lflag & TOSTOP && !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + // TODO: Add support for ioctx to the kernel log. + const size_t BUFFER_SIZE = 64UL; + char buffer[BUFFER_SIZE]; + size_t sofar = 0; + while ( sofar < count ) + { + size_t amount = count - sofar; + if ( BUFFER_SIZE < amount ) + amount = BUFFER_SIZE; + if ( !ctx->copy_from_src(buffer, io_buffer + sofar, amount) ) + return -1; + Log::PrintData(buffer, amount); + sofar += amount; + if ( sofar < count ) + { + kthread_mutex_unlock(&termlock); + kthread_yield(); + kthread_mutex_lock(&termlock); + if ( Signal::IsPending() ) + return sofar; + } + } + return (ssize_t) sofar; +} + +short TTY::PollEventStatus() +{ + short status = 0; + if ( linebuffer.CanPop() || numeofs ) + status |= POLLIN | POLLRDNORM; + if ( true /* can always write */ ) + status |= POLLOUT | POLLWRNORM; + return status; +} + +int TTY::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; +} + +int TTY::tcdrain(ioctx_t* /*ctx*/) +{ + ScopedLockSignal lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + return 0; +} + +int TTY::tcflow(ioctx_t* /*ctx*/, int action) +{ + ScopedLockSignal lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + switch ( action ) + { + case TCOOFF: break; // TODO: Suspend output. + case TCOON: break; // TODO: Resume suspended output. + case TCIOFF: break; // TODO: Transmit STOP character. + case TCION: break; // TODO: Transmit START character. + default: return errno = EINVAL -1; + } + return 0; +} + +int TTY::tcflush(ioctx_t* /*ctx*/, int queue_selector) +{ + if ( queue_selector & ~TCIOFLUSH ) + return errno = EINVAL, -1; + ScopedLockSignal lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + if ( queue_selector & TCIFLUSH ) + linebuffer.Flush(); + return 0; +} + +int TTY::tcgetattr(ioctx_t* ctx, struct termios* io_tio) +{ + ScopedLockSignal lock(&termlock); + if ( !ctx->copy_to_dest(io_tio, &tio, sizeof(tio)) ) + return -1; + return 0; +} + +pid_t TTY::tcgetsid(ioctx_t* /*ctx*/) +{ + // TODO: Implement sessions. + return 1; +} + +int TTY::tcsendbreak(ioctx_t* /*ctx*/, int /*duration*/) +{ + ScopedLockSignal lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + return 0; +} + +int TTY::tcsetattr(ioctx_t* ctx, int actions, const struct termios* io_tio) +{ + ScopedLockSignal lock(&termlock); + if ( !RequireForeground(SIGTTOU) ) + return errno = EINTR, -1; + switch ( actions ) + { + case TCSANOW: break; + case TCSADRAIN: break; + case TCSAFLUSH: linebuffer.Flush(); break; + default: return errno = EINVAL, -1; + } + if ( !ctx->copy_from_src(&tio, io_tio, sizeof(tio)) ) + return -1; + // TODO: Potentially take action here if something changed. + return 0; +} + +bool TTY::RequireForeground(int sig) +{ + Thread* thread = CurrentThread(); + Process* process = thread->process; + ScopedLock group_lock(&process->groupparentlock); + if ( process->group->pid == foreground_pgid ) + return true; + if ( sigismember(&thread->signal_mask, sig) ) + return true; + ScopedLock signal_lock(&process->signal_lock); + if ( process->signal_actions[sig].sa_handler == SIG_IGN ) + return true; + signal_lock.Reset(); + group_lock.Reset(); + process->group->DeliverGroupSignal(sig); + return false; +} + +} // namespace Sortix diff --git a/kernel/tty.h b/kernel/tty.h new file mode 100644 index 00000000..5dcb41ca --- /dev/null +++ b/kernel/tty.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * tty.h + * Terminal line discipline. + */ + +#ifndef SORTIX_TTY_H +#define SORTIX_TTY_H + +#include + +#include + +#include +#include +#include +#include + +#include "kb/kblayout.h" + +#include "linebuffer.h" + +namespace Sortix { + +class TTY : public AbstractInode +{ +public: + TTY(dev_t dev, mode_t mode, uid_t owner, gid_t group); + virtual ~TTY(); + +public: + virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); + virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); + virtual int tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp); + virtual int tcgetwinsize(ioctx_t* ctx, struct winsize* ws); + virtual int tcsetpgrp(ioctx_t* ctx, pid_t pgid); + virtual pid_t tcgetpgrp(ioctx_t* ctx); + virtual int settermmode(ioctx_t* ctx, unsigned termmode); + virtual int gettermmode(ioctx_t* ctx, unsigned* termmode); + virtual int poll(ioctx_t* ctx, PollNode* node); + virtual int tcdrain(ioctx_t* ctx); + virtual int tcflow(ioctx_t* ctx, int action); + virtual int tcflush(ioctx_t* ctx, int queue_selector); + virtual int tcgetattr(ioctx_t* ctx, struct termios* tio); + virtual pid_t tcgetsid(ioctx_t* ctx); + virtual int tcsendbreak(ioctx_t* ctx, int duration); + virtual int tcsetattr(ioctx_t* ctx, int actions, const struct termios* tio); + +protected: + void ProcessUnicode(uint32_t unicode); + void ProcessByte(unsigned char byte, uint32_t control_unicode = 0); + void CommitLineBuffer(); + short PollEventStatus(); + bool CheckForeground(); + bool RequireForeground(int sig); + bool CheckHandledByte(tcflag_t lflags, unsigned char key, unsigned char byte); + +protected: + PollChannel poll_channel; + mutable kthread_mutex_t termlock; + kthread_cond_t datacond; + mbstate_t read_ps; + size_t numeofs; + LineBuffer linebuffer; + struct termios tio; + pid_t foreground_pgid; + +}; + +} // namespace Sortix + +#endif