sortix-mirror/kernel/tty.cpp

919 lines
23 KiB
C++

/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2021 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 <sys/ioctl.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sortix/fcntl.h>
#include <sortix/keycodes.h>
#if !defined(TTY_NAME_MAX)
#include <sortix/limits.h>
#endif
#include <sortix/poll.h>
#include <sortix/signal.h>
#include <sortix/stat.h>
#include <sortix/termios.h>
#include <sortix/termmode.h>
#include <sortix/winsize.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/interlock.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/keyboard.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/poll.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/ptable.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/thread.h>
#include <sortix/kernel/vnode.h>
#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
| TERMMODE_NOOPOST
| TERMMODE_NOONLCR
| TERMMODE_OCRNL;
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;
}
DevTTY::DevTTY(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_IFFACTORY;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->stat_uid = owner;
this->stat_gid = group;
}
DevTTY::~DevTTY()
{
}
Ref<Inode> DevTTY::factory(ioctx_t* ctx, const char* filename, int flags,
mode_t mode)
{
(void) ctx;
(void) filename;
(void) flags;
(void) mode;
ScopedLock lock(&process_family_lock);
Process* process = CurrentProcess();
if ( !process->session )
return errno = ENOTTY, Ref<Inode>(NULL);
Ref<Descriptor> tty_desc = process->session->GetTTY();
if ( !tty_desc )
return errno = ENOTTY, Ref<Inode>(NULL);
return tty_desc->vnode->inode;
}
TTY::TTY(dev_t dev, ino_t ino, mode_t mode, uid_t owner, gid_t group,
const char* name)
{
if ( !dev )
dev = (dev_t) this;
if ( !ino )
ino = (ino_t) this;
inode_type = INODE_TYPE_TTY;
this->dev = dev;
this->ino = ino;
this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->stat_uid = owner;
this->stat_gid = group;
// Keep this in sync with utils/stty.c.
memset(&tio, 0, sizeof(tio));
tio.c_iflag = BRKINT | ICRNL | IXANY | IXON;
tio.c_oflag = OPOST | ONLCR;
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] = 0;
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;
termlock = KTHREAD_MUTEX_INITIALIZER;
datacond = KTHREAD_COND_INITIALIZER;
numeofs = 0;
foreground_pgid = -1;
sid = -1;
hungup = false;
snprintf(ttyname, sizeof(ttyname), "%s", name);
}
TTY::~TTY()
{
}
int TTY::settermmode(ioctx_t* /*ctx*/, unsigned int termmode)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTOU) )
return -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;
tcflag_t old_oflag = tio.c_oflag;
tcflag_t new_oflag = old_oflag;
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;
if ( !(termmode & TERMMODE_NOOPOST) )
new_oflag |= OPOST;
else
new_oflag &= ~OPOST;
if ( !(termmode & TERMMODE_NOONLCR) )
new_oflag |= ONLCR;
else
new_oflag &= ~ONLCR;
if ( termmode & TERMMODE_OCRNL )
new_oflag |= OCRNL;
else
new_oflag &= ~OCRNL;
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;
tio.c_oflag = new_oflag;
if ( !(tio.c_lflag & ICANON) )
CommitLineBuffer();
return 0;
}
int TTY::gettermmode(ioctx_t* ctx, unsigned int* mode)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
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 ( !(tio.c_oflag & OPOST) )
termmode |= TERMMODE_NOOPOST;
if ( !(tio.c_oflag & ONLCR) )
termmode |= TERMMODE_NOONLCR;
if ( tio.c_oflag & OCRNL )
termmode |= TERMMODE_OCRNL;
if ( !ctx->copy_to_dest(mode, &termmode, sizeof(termmode)) )
return -1;
return 0;
}
int TTY::tcgetwincurpos(ioctx_t* /*ctx*/, struct wincurpos* /*wcp*/)
{
return errno = ENOTSUP, -1;
}
int TTY::tcsetpgrp(ioctx_t* /*ctx*/, pid_t pgid)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
ScopedLock family_lock(&process_family_lock);
if ( !RequireForegroundUnlocked(SIGTTOU) )
return -1;
if ( pgid <= 0 )
return errno = ESRCH, -1;
Process* process = CurrentProcess()->GetPTable()->Get(pgid);
if ( !process )
return errno = ESRCH, -1;
if ( !process->groupfirst )
return errno = EINVAL, -1;
foreground_pgid = pgid;
return 0;
}
pid_t TTY::tcgetpgrp(ioctx_t* /*ctx*/)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
return foreground_pgid;
}
void TTY::hup()
{
ScopedLock lock(&termlock);
ScopedLock family_lock(&process_family_lock);
hungup = true;
if ( 0 < sid )
{
Process* process = CurrentProcess()->GetPTable()->Get(sid);
if ( process )
process->DeliverSessionSignal(SIGHUP);
}
kthread_cond_broadcast(&datacond);
poll_channel.Signal(POLLHUP);
}
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();
ScopedLock lock(&process_family_lock);
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();
ScopedLock lock(&process_family_lock);
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();
ScopedLock lock(&process_family_lock);
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?).
tty_output("\b \b");
if ( !IsByteUnescaped(delchar) )
tty_output("\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 )
{
tty_output("\b \b");
if ( !IsByteUnescaped(delchar) )
tty_output("\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 )
{
tty_output("\b \b");
if ( !IsByteUnescaped(delchar) )
tty_output("\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));
tty_output("\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 ( byte == '\n' )
{
if ( tio.c_oflag & OPOST && tio.c_oflag & ONLCR )
tty_output("\r\n");
else if ( tio.c_oflag & OPOST && tio.c_oflag & OCRNL )
tty_output("\r");
else
tty_output("\n");
}
else if ( IsByteUnescaped(byte) )
tty_output(&byte, 1);
else
{
unsigned char cs[2] = { '^', (unsigned char) CONTROL(byte) };
tty_output(cs, sizeof(cs));
}
}
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 ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTIN) )
return -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);
if ( hungup )
return sofar ? sofar : (errno = EIO, -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);
if ( hungup )
return sofar ? sofar : (errno = EIO, -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);
if ( hungup )
return sofar ? sofar : (errno = EIO, -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 ( !lock.IsAcquired() )
return errno = EINTR, -1;
if ( hungup )
return errno = EIO, -1;
if ( tio.c_lflag & TOSTOP && !RequireForeground(SIGTTOU) )
return -1;
// TODO: Add support for ioctx to the kernel log.
unsigned char buffer[256];
size_t max_incoming = sizeof(buffer);
if ( tio.c_oflag & OPOST && tio.c_oflag & ONLCR )
max_incoming = sizeof(buffer) / 2;
size_t sofar = 0;
size_t rounds = 0;
while ( sofar < count )
{
size_t incoming = count - sofar;
if ( max_incoming < incoming )
incoming = max_incoming;
if ( !ctx->copy_from_src(buffer, io_buffer + sofar, incoming) )
return -1;
size_t offset;
size_t outgoing;
if ( tio.c_oflag & OPOST && tio.c_oflag & ONLCR )
{
offset = sizeof(buffer);
outgoing = incoming;
for ( size_t ii = incoming; ii; ii-- )
{
size_t i = ii - 1;
if ( buffer[i] == '\n' )
{
buffer[--offset] = '\n';
buffer[--offset] = '\r';
outgoing++;
}
else
buffer[--offset] = buffer[i];
}
}
else
{
offset = 0;
outgoing = incoming;
if ( tio.c_oflag & OPOST && tio.c_oflag & OCRNL )
{
for ( size_t i = 0; i < incoming; i++ )
if ( buffer[i] == '\r' )
buffer[i] = '\n';
}
}
tty_output(buffer + offset, outgoing);
sofar += incoming;
if ( ++rounds % 16 == 0 )
{
kthread_mutex_unlock(&termlock);
kthread_yield();
kthread_mutex_lock(&termlock);
if ( hungup )
return sofar;
}
if ( Signal::IsPending() )
return sofar;
}
return (ssize_t) sofar;
}
short TTY::PollEventStatus()
{
short status = 0;
if ( hungup )
status |= POLLHUP;
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)
{
ScopedLock 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*/)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTOU) )
return -1;
return 0;
}
int TTY::tcflow(ioctx_t* /*ctx*/, int action)
{
ScopedLock lock(&termlock);
if ( !RequireForeground(SIGTTOU) )
return -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;
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTOU) )
return -1;
if ( queue_selector & TCIFLUSH )
linebuffer.Flush();
return 0;
}
int TTY::tcgetattr(ioctx_t* ctx, struct termios* io_tio)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !ctx->copy_to_dest(io_tio, &tio, sizeof(tio)) )
return -1;
return 0;
}
pid_t TTY::tcgetsid(ioctx_t* /*ctx*/)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
return sid;
}
int TTY::tcsendbreak(ioctx_t* /*ctx*/, int /*duration*/)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTOU) )
return -1;
return 0;
}
int TTY::tcsetattr(ioctx_t* ctx, int actions, const struct termios* io_tio)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( !RequireForeground(SIGTTOU) )
return -1;
switch ( actions )
{
case TCSANOW: break;
case TCSADRAIN: break;
case TCSAFLUSH: linebuffer.Flush(); break;
default: return errno = EINVAL, -1;
}
tcflag_t old_lflag = tio.c_lflag;
if ( !ctx->copy_from_src(&tio, io_tio, sizeof(tio)) )
return -1;
tcflag_t new_lflag = tio.c_lflag;
bool oldnoutf8 = old_lflag & ISORTIX_32BIT;
bool newnoutf8 = new_lflag & ISORTIX_32BIT;
if ( oldnoutf8 != newnoutf8 )
memset(&read_ps, 0, sizeof(read_ps));
if ( !(tio.c_lflag & ICANON) )
CommitLineBuffer();
return 0;
}
int TTY::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( cmd == TIOCSCTTY )
{
ScopedLock family_lock(&process_family_lock);
if ( 0 <= sid )
return errno = EPERM, -1;
Process* process = CurrentProcess();
if ( !process->sessionfirst )
return errno = EPERM, -1;
if ( (ctx->dflags & (O_READ | O_WRITE)) != (O_READ | O_WRITE) )
return errno = EPERM, -1;
Ref<Vnode> vnode(new Vnode(Ref<Inode>(this), Ref<Vnode>(NULL), 0, 0));
if ( !vnode )
return -1;
Ref<Descriptor> desc(new Descriptor(vnode, O_READ | O_WRITE));
if ( !desc )
return -1;
sid = process->pid;
foreground_pgid = process->pid;
process->SetTTY(desc);
return 0;
}
else if ( cmd == TIOCSPTLCK )
{
// TODO: Figure out what locked ptys are and implement it if sensible.
const int* arg_ptr = (const int*) arg;
int new_locked;
if ( !ctx->copy_from_src(&new_locked, arg_ptr, sizeof(new_locked)) )
return -1;
return 0;
}
else if ( cmd == TIOCGPTLCK )
{
// TODO: Figure out what locked ptys are and implement it if sensible.
int* arg_ptr = (int*) arg;
int locked = 0;
if ( !ctx->copy_to_dest(arg_ptr, &locked, sizeof(locked)) )
return -1;
return 0;
}
else if ( cmd == TIOCGNAME )
{
char* arg_ptr = (char*) arg;
size_t ttynamesize = strlen(ttyname) + 1;
if ( !ctx->copy_to_dest(arg_ptr, ttyname, ttynamesize) )
return -1;
return 0;
}
lock.Reset();
return AbstractInode::ioctl(ctx, cmd, arg);
}
bool TTY::RequireForeground(int sig)
{
ScopedLock family_lock(&process_family_lock);
return RequireForegroundUnlocked(sig);
}
bool TTY::RequireForegroundUnlocked(int sig) // process_family_lock held
{
Thread* thread = CurrentThread();
Process* process = thread->process;
if ( !process->session || process->session->pid != sid || !process->group )
return true;
if ( foreground_pgid < 1 || 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();
process->group->DeliverGroupSignal(sig);
errno = EINTR;
return false;
}
} // namespace Sortix