/* * 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 #include #include #include #include #include #include #include #include #include #include #if !defined(TTY_NAME_MAX) #include #endif #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 | 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 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(NULL); Ref tty_desc = process->session->GetTTY(); if ( !tty_desc ) return errno = ENOTTY, Ref(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(new Vnode(Ref(this), Ref(NULL), 0, 0)); if ( !vnode ) return -1; Ref 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