Files
haiku-beta6/src/add-ons/kernel/drivers/tty/tty.cpp
T
Ingo Weinhold 06b7c7ffe2 * Set reasonable terminal flag defaults. This makes telnetd fully usable
again.
* Publish /dev/ptmx. Opening it will open a fresh pty master. In
  principle /dev/pt/ is obsolete now, but I guess we keep it around for
  compatibility with BeOS. Though there shouldn't be many applications
  opening a pty, that we might be interested in...
* New ioctls B_IOCTL_GET_TTY_INDEX (returns the tty index) and
  B_IOCTL_GRANT_TTY (grants tty slave access rights).



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25133 a95241bf-73f2-0310-859d-f6bbb57e9c96
2008-04-24 21:26:35 +00:00

1927 lines
45 KiB
C++

/*
* Copyright 2007-2008, Ingo Weinhold, [email protected].
* Copyright 2004-2006, Axel Dörfler, [email protected]. All rights reserved.
* Distributed under the terms of the MIT License.
*/
// This file could be moved into a generic tty module.
// The whole hardware signaling stuff is missing, though - it's currently
// tailored for pseudo-TTYs. Have a look at Be's TTY includes (drivers/tty/*)
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <util/AutoLock.h>
#include <util/kernel_cpp.h>
#include <team.h>
#include <tty.h>
#include "tty_private.h"
//#define TTY_TRACE
#ifdef TTY_TRACE
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
/*
Locking
-------
There are four locks involved. If more than one needs to be held at a
time, they must be acquired in the order they are listed here.
gGlobalTTYLock: Guards open/close operations. When held, tty_open(),
tty_close(), tty_close_cookie() etc. won't be invoked by other threads,
cookies won't be added to/removed from TTYs, and tty::open_count,
tty_cookie::closed won't change.
gTTYCookieLock: Guards the access to the fields
tty_cookie::{thread_count,closed}, or more precisely makes access to them
atomic. thread_count is the number of threads currently using the cookie
(i.e. read(), write(), ioctl() operations in progress). Together with
blocking_semaphore this serves the purpose to make sure that all pending
operations are done at a certain point when closing a cookie
(cf. tty_close_cookie() and TTYReference).
tty::lock: Guards the access to tty::{input_buffer,settings::{termios,
window_size,pgrp_id}}. Moreover when held guarantees that tty::open_count
won't drop to zero (both gGlobalTTYLock and tty::lock must be held to
decrement it). A tty and the tty connected to it (master and slave) share
the same lock. tty::lock is only valid when tty::open_count is > 0. So
before accessing tty::lock, it must be made sure that it is still valid.
Given a tty_cookie, TTYReference can be used to do that, or otherwise
gGlobalTTYLock can be acquired and tty::open_count be checked.
gTTYRequestLock: Guards access to tty::{reader,writer}_queue (most
RequestQueue methods do the locking themselves (the lock is a
recursive_lock)), queued Requests and associated RequestOwners.
Reading/Writing
---------------
Most of the dirty work when dealing with reading/writing is done by the
{Reader,Writer}Locker classes. Upon construction they lock the tty,
(tty::lock) create a RequestOwner and queue Requests in the respective
reader/writer queues (tty::{reader,writer}_queue). The
Acquire{Reader,Writer}() methods need to be called before being allowed to
read/write. They ensure that there is actually something to read/space for
writing -- in blocking mode they wait, if necessary. When destroyed the
{Reader,Writer}Locker() remove the formerly enqueued Requests and notify
waiting reader/writer and/or send out select events, whatever is appropiate.
Acquire{Reader,Writer}() never return without an actual event being
occurred. Either an error has occurred (return value) -- in this case the
caller should terminate -- or bytes are available for reading/space for
writing (cf. AvailableBytes()).
*/
tty_settings gTTYSettings[kNumTTYs];
static void tty_notify_select_event(struct tty *tty, uint8 event);
static void tty_notify_if_available(struct tty *tty, struct tty *otherTTY,
bool notifySelect);
class AbstractLocker {
public:
AbstractLocker(tty_cookie *cookie) : fCookie(cookie), fBytes(0) {}
size_t AvailableBytes() const { return fBytes; }
protected:
void Lock() { mutex_lock(fCookie->tty->lock); }
void Unlock() { mutex_unlock(fCookie->tty->lock); }
tty_cookie *fCookie;
size_t fBytes;
};
class WriterLocker : public AbstractLocker {
public:
WriterLocker(tty_cookie *sourceCookie);
~WriterLocker();
status_t AcquireWriter(bool dontBlock, size_t bytesNeeded);
private:
size_t _CheckAvailableBytes() const;
status_t _CheckBackgroundWrite() const;
struct tty *fSource;
struct tty *fTarget;
RequestOwner fRequestOwner;
bool fEcho;
};
class ReaderLocker : public AbstractLocker {
public:
ReaderLocker(tty_cookie *cookie);
~ReaderLocker();
status_t AcquireReader(bool dontBlock);
private:
size_t _CheckAvailableBytes() const;
status_t _CheckBackgroundRead() const;
struct tty *fTTY;
RequestOwner fRequestOwner;
};
class TTYReferenceLocking {
public:
inline bool Lock(tty_cookie *cookie)
{
MutexLocker _(gTTYCookieLock);
if (cookie->closed)
return false;
cookie->thread_count++;
return true;
}
inline void Unlock(tty_cookie *cookie)
{
MutexLocker locker(gTTYCookieLock);
sem_id semaphore = -1;
if (--cookie->thread_count == 0 && cookie->closed)
semaphore = cookie->blocking_semaphore;
locker.Unlock();
if (semaphore >= 0) {
TRACE(("TTYReference: cookie %p closed, last operation done, "
"releasing blocking sem %ld\n", cookie, semaphore));
release_sem(semaphore);
}
}
};
typedef AutoLocker<tty_cookie, TTYReferenceLocking> TTYReference;
// #pragma mark -
Request::Request()
:
fOwner(NULL),
fCookie(NULL),
fBytesNeeded(0),
fNotified(false),
fError(false)
{
}
void
Request::Init(RequestOwner *owner, tty_cookie *cookie, size_t bytesNeeded)
{
fOwner = owner;
fCookie = cookie;
fBytesNeeded = bytesNeeded;
fNotified = false;
fError = false;
}
void
Request::Notify(size_t bytesAvailable)
{
if (!fNotified && bytesAvailable >= fBytesNeeded && fOwner) {
fOwner->Notify(this);
fNotified = true;
}
}
void
Request::NotifyError(status_t error)
{
if (!fError && fOwner) {
fOwner->NotifyError(this, error);
fError = true;
fNotified = true;
}
}
void
Request::Dump(const char* prefix)
{
kprintf("%srequest: %p\n", prefix, this);
kprintf("%s owner: %p\n", prefix, fOwner);
kprintf("%s cookie: %p\n", prefix, fCookie);
kprintf("%s bytes needed: %lu\n", prefix, fBytesNeeded);
kprintf("%s notified: %s\n", prefix, fNotified ? "true" : "false");
kprintf("%s error: %s\n", prefix, fError ? "true" : "false");
}
// #pragma mark -
RequestQueue::RequestQueue()
:
fRequests()
{
}
void
RequestQueue::Add(Request *request)
{
if (request) {
RecursiveLocker _(gTTYRequestLock);
fRequests.Add(request, true);
}
}
void
RequestQueue::Remove(Request *request)
{
if (request) {
RecursiveLocker _(gTTYRequestLock);
fRequests.Remove(request);
}
}
void
RequestQueue::NotifyFirst(size_t bytesAvailable)
{
RecursiveLocker _(gTTYRequestLock);
if (Request *first = First())
first->Notify(bytesAvailable);
}
void
RequestQueue::NotifyError(status_t error)
{
RecursiveLocker _(gTTYRequestLock);
for (RequestList::Iterator it = fRequests.GetIterator(); it.HasNext();) {
Request *request = it.Next();
request->NotifyError(error);
}
}
void
RequestQueue::NotifyError(tty_cookie *cookie, status_t error)
{
RecursiveLocker _(gTTYRequestLock);
for (RequestList::Iterator it = fRequests.GetIterator(); it.HasNext();) {
Request *request = it.Next();
if (request->TTYCookie() == cookie)
request->NotifyError(error);
}
}
void
RequestQueue::Dump(const char* prefix)
{
RequestList::Iterator it = fRequests.GetIterator();
while (Request* request = it.Next())
request->Dump(prefix);
}
// #pragma mark -
RequestOwner::RequestOwner()
:
fConditionVariable(NULL),
fCookie(NULL),
fError(B_OK),
fBytesNeeded(1)
{
fRequestQueues[0] = NULL;
fRequestQueues[1] = NULL;
}
/**
* The caller must already hold the request lock.
*/
void
RequestOwner::Enqueue(tty_cookie *cookie, RequestQueue *queue1,
RequestQueue *queue2)
{
TRACE(("%p->RequestOwner::Enqueue(%p, %p, %p)\n", this, cookie, queue1,
queue2));
fCookie = cookie;
fRequestQueues[0] = queue1;
fRequestQueues[1] = queue2;
fRequests[0].Init(this, cookie, fBytesNeeded);
if (queue1)
queue1->Add(&fRequests[0]);
else
fRequests[0].Notify(fBytesNeeded);
fRequests[1].Init(this, cookie, fBytesNeeded);
if (queue2)
queue2->Add(&fRequests[1]);
else
fRequests[1].Notify(fBytesNeeded);
}
/**
* The caller must already hold the request lock.
*/
void
RequestOwner::Dequeue()
{
TRACE(("%p->RequestOwner::Dequeue()\n", this));
if (fRequestQueues[0])
fRequestQueues[0]->Remove(&fRequests[0]);
if (fRequestQueues[1])
fRequestQueues[1]->Remove(&fRequests[1]);
fRequestQueues[0] = NULL;
fRequestQueues[1] = NULL;
}
void
RequestOwner::SetBytesNeeded(size_t bytesNeeded)
{
if (fRequestQueues[0])
fRequests[0].Init(this, fCookie, bytesNeeded);
if (fRequestQueues[1])
fRequests[1].Init(this, fCookie, bytesNeeded);
}
/**
* The request lock MUST NOT be held!
*/
status_t
RequestOwner::Wait(bool interruptable)
{
TRACE(("%p->RequestOwner::Wait(%d)\n", this, interruptable));
status_t error = B_OK;
RecursiveLocker locker(gTTYRequestLock);
// check, if already done
if (fError == B_OK
&& (!fRequests[0].WasNotified() || !fRequests[1].WasNotified())) {
// not yet done
// publish the condition variable
ConditionVariable conditionVariable;
conditionVariable.Publish(this, "tty request");
fConditionVariable = &conditionVariable;
// add an entry to wait on
ConditionVariableEntry entry;
entry.Add(this, interruptable ? B_CAN_INTERRUPT : 0);
locker.Unlock();
// wait
TRACE(("%p->RequestOwner::Wait(): waiting for condition...\n", this));
error = entry.Wait();
TRACE(("%p->RequestOwner::Wait(): condition occurred: %lx\n", this,
error));
// remove the condition variable
locker.Lock();
fConditionVariable = NULL;
conditionVariable.Unpublish();
}
// get the result
if (error == B_OK)
error = fError;
return error;
}
bool
RequestOwner::IsFirstInQueues()
{
RecursiveLocker locker(gTTYRequestLock);
for (int i = 0; i < 2; i++) {
if (fRequestQueues[i] && fRequestQueues[i]->First() != &fRequests[i])
return false;
}
return true;
}
void
RequestOwner::Notify(Request *request)
{
TRACE(("%p->RequestOwner::Notify(%p)\n", this, request));
if (fError == B_OK && !request->WasNotified()) {
bool notify = false;
if (&fRequests[0] == request) {
notify = fRequests[1].WasNotified();
} else if (&fRequests[1] == request) {
notify = fRequests[0].WasNotified();
} else {
// spurious call
}
if (notify && fConditionVariable)
fConditionVariable->NotifyOne();
}
}
void
RequestOwner::NotifyError(Request *request, status_t error)
{
TRACE(("%p->RequestOwner::NotifyError(%p, %lx)\n", this, request, error));
if (fError == B_OK) {
fError = error;
if (!fRequests[0].WasNotified() || !fRequests[1].WasNotified()) {
if (fConditionVariable)
fConditionVariable->NotifyOne();
}
}
}
// #pragma mark -
WriterLocker::WriterLocker(tty_cookie *sourceCookie)
: AbstractLocker(sourceCookie),
fSource(fCookie->tty),
fTarget(fCookie->other_tty),
fRequestOwner(),
fEcho(false)
{
Lock();
// Now that the tty pair is locked, we can check, whether the target is
// open at all.
if (fTarget->open_count > 0) {
// The target tty is open. As soon as we have appended a request to
// the writer queue of the target, it is guaranteed to remain valid
// until we have removed the request (and notified the
// tty_close_cookie() pseudo request).
// get the echo mode
fEcho = (fSource->is_master
&& fSource->settings->termios.c_lflag & ECHO) != 0;
// enqueue ourselves in the respective request queues
RecursiveLocker locker(gTTYRequestLock);
fRequestOwner.Enqueue(fCookie, &fTarget->writer_queue,
(fEcho ? &fSource->writer_queue : NULL));
} else {
// target is not open: we set it to NULL; all further operations on
// this locker will fail
fTarget = NULL;
}
}
WriterLocker::~WriterLocker()
{
// dequeue from request queues
RecursiveLocker locker(gTTYRequestLock);
fRequestOwner.Dequeue();
// check the tty queues and notify the next in line, and send out select
// events
if (fTarget)
tty_notify_if_available(fTarget, fSource, true);
if (fEcho)
tty_notify_if_available(fSource, fTarget, true);
locker.Unlock();
Unlock();
}
size_t
WriterLocker::_CheckAvailableBytes() const
{
size_t writable = line_buffer_writable(fTarget->input_buffer);
if (fEcho) {
// we can only write as much as is available on both ends
size_t locallyWritable = line_buffer_writable(fSource->input_buffer);
if (locallyWritable < writable)
writable = locallyWritable;
}
return writable;
}
status_t
WriterLocker::AcquireWriter(bool dontBlock, size_t bytesNeeded)
{
if (!fTarget)
return B_FILE_ERROR;
if (fEcho && fCookie->closed)
return B_FILE_ERROR;
RecursiveLocker requestLocker(gTTYRequestLock);
// check, if we're first in queue, and if there is space to write
if (fRequestOwner.IsFirstInQueues()) {
fBytes = _CheckAvailableBytes();
if (fBytes >= bytesNeeded)
return B_OK;
}
// We are not the first in queue or currently there's no space to write:
// bail out, if we shall not block.
if (dontBlock)
return B_WOULD_BLOCK;
// set the number of bytes we need and notify, just in case we're first in
// one of the queues (RequestOwner::SetBytesNeeded() resets the notification
// state)
if (bytesNeeded != fRequestOwner.BytesNeeded()) {
fRequestOwner.SetBytesNeeded(bytesNeeded);
if (fTarget)
tty_notify_if_available(fTarget, fSource, false);
if (fEcho)
tty_notify_if_available(fSource, fTarget, false);
}
requestLocker.Unlock();
// block until something happens
Unlock();
status_t status = fRequestOwner.Wait(true);
Lock();
// RequestOwner::Wait() returns the error, but to avoid a race condition
// when closing a tty, we re-get the error with the tty lock being held.
if (status == B_OK) {
RecursiveLocker _(gTTYRequestLock);
status = fRequestOwner.Error();
}
if (status == B_OK)
status = _CheckBackgroundWrite();
if (status == B_OK) {
if (fTarget->open_count > 0)
fBytes = _CheckAvailableBytes();
else
status = B_FILE_ERROR;
}
return status;
}
status_t
WriterLocker::_CheckBackgroundWrite() const
{
// only relevant for the slave end and only when TOSTOP is set
if (fSource->is_master
|| (fSource->settings->termios.c_lflag & TOSTOP) == 0) {
return B_OK;
}
pid_t processGroup = getpgid(0);
if (fSource->settings->pgrp_id != 0
&& processGroup != fSource->settings->pgrp_id) {
if (team_get_controlling_tty() == fSource->index)
send_signal(-processGroup, SIGTTOU);
return EIO;
}
return B_OK;
}
// #pragma mark -
ReaderLocker::ReaderLocker(tty_cookie *cookie)
: AbstractLocker(cookie),
fTTY(cookie->tty),
fRequestOwner()
{
Lock();
// enqueue ourselves in the reader request queue
RecursiveLocker locker(gTTYRequestLock);
fRequestOwner.Enqueue(fCookie, &fTTY->reader_queue);
}
ReaderLocker::~ReaderLocker()
{
// dequeue from reader request queue
RecursiveLocker locker(gTTYRequestLock);
fRequestOwner.Dequeue();
// check the tty queues and notify the next in line, and send out select
// events
struct tty *otherTTY = fCookie->other_tty;
tty_notify_if_available(fTTY, (otherTTY->open_count > 0 ? otherTTY : NULL),
true);
locker.Unlock();
Unlock();
}
status_t
ReaderLocker::AcquireReader(bool dontBlock)
{
if (fCookie->closed)
return B_FILE_ERROR;
status_t status = _CheckBackgroundRead();
if (status != B_OK)
return status;
// check, if we're first in queue, and if there is something to read
if (fRequestOwner.IsFirstInQueues()) {
fBytes = _CheckAvailableBytes();
if (fBytes > 0)
return B_OK;
}
// We are not the first in queue or currently there's nothing to read:
// bail out, if we shall not block.
if (dontBlock)
return B_WOULD_BLOCK;
// block until something happens
Unlock();
status = fRequestOwner.Wait(true);
Lock();
if (status == B_OK)
status = _CheckBackgroundRead();
if (status == B_OK)
fBytes = _CheckAvailableBytes();
return status;
}
size_t
ReaderLocker::_CheckAvailableBytes() const
{
// Reading from the slave with canonical input processing enabled means
// that we read at max until hitting a line end or EOF.
if (!fTTY->is_master && (fTTY->settings->termios.c_lflag & ICANON) != 0) {
return line_buffer_readable_line(fTTY->input_buffer,
fTTY->settings->termios.c_cc[VEOL],
fTTY->settings->termios.c_cc[VEOF]);
}
return line_buffer_readable(fTTY->input_buffer);
}
status_t
ReaderLocker::_CheckBackgroundRead() const
{
// only relevant for the slave end
if (fTTY->is_master)
return B_OK;
pid_t processGroup = getpgid(0);
if (fTTY->settings->pgrp_id != 0
&& processGroup != fTTY->settings->pgrp_id) {
if (team_get_controlling_tty() == fTTY->index)
send_signal(-processGroup, SIGTTIN);
return EIO;
}
return B_OK;
}
// #pragma mark -
int32
get_tty_index(const char *name)
{
// device names follow this form: "pt/%c%x"
int8 digit = name[4];
if (digit >= 'a') {
// hexadecimal digits
digit -= 'a' - 10;
} else
digit -= '0';
return (name[3] - 'p') * 16 + digit;
}
static void
reset_termios(struct termios &termios)
{
memset(&termios, 0, sizeof(struct termios));
termios.c_iflag = ICRNL;
termios.c_oflag = OPOST | ONLCR;
termios.c_cflag = B19200 | CS8 | CREAD | HUPCL;
// enable receiver, hang up on last close
termios.c_lflag = ECHO | ISIG | ICANON;
// control characters
termios.c_cc[VINTR] = CTRL('C');
termios.c_cc[VQUIT] = CTRL('\\');
termios.c_cc[VERASE] = CTRL('H');
termios.c_cc[VKILL] = CTRL('U');
termios.c_cc[VEOF] = CTRL('D');
termios.c_cc[VEOL] = '\0';
termios.c_cc[VEOL2] = '\0';
termios.c_cc[VSTART] = CTRL('S');
termios.c_cc[VSTOP] = CTRL('Q');
termios.c_cc[VSUSP] = CTRL('Z');
}
void
reset_tty_settings(tty_settings *settings, int32 index)
{
reset_termios(settings->termios);
settings->pgrp_id = 0;
// this value prevents any signal of being sent
settings->session_id = -1;
// some initial window size - the TTY in question should set these values
settings->window_size.ws_col = 80;
settings->window_size.ws_row = 25;
settings->window_size.ws_xpixel = settings->window_size.ws_col * 8;
settings->window_size.ws_ypixel = settings->window_size.ws_row * 8;
}
void
reset_tty(struct tty *tty, int32 index, bool isMaster)
{
tty->open_count = 0;
tty->index = index;
tty->lock = NULL;
tty->settings = &gTTYSettings[index];
tty->select_pool = NULL;
tty->is_master = isMaster;
tty->pending_eof = 0;
}
status_t
tty_output_getc(struct tty *tty, int *_c)
{
return B_ERROR;
}
/** Processes the input character and puts it into the TTY's input buffer.
* Depending on the termios flags set, signals may be sent, the input
* character changed or removed, etc.
*/
static void
tty_input_putc_locked(struct tty *tty, int c)
{
// process signals if needed
if ((tty->settings->termios.c_lflag & ISIG) != 0) {
// enable signals, process INTR, QUIT, and SUSP
int signal = -1;
if (c == tty->settings->termios.c_cc[VINTR])
signal = SIGINT;
else if (c == tty->settings->termios.c_cc[VQUIT])
signal = SIGQUIT;
else if (c == tty->settings->termios.c_cc[VSUSP])
signal = SIGTSTP;
// do we need to deliver a signal?
if (signal != -1) {
// we may have to flush the input buffer
if ((tty->settings->termios.c_lflag & NOFLSH) == 0)
clear_line_buffer(tty->input_buffer);
if (tty->settings->pgrp_id != 0)
send_signal(-tty->settings->pgrp_id, signal);
return;
}
}
// process special canonical input characters
if ((tty->settings->termios.c_lflag & ICANON) != 0) {
// canonical mode, process ERASE and KILL
cc_t* controlChars = tty->settings->termios.c_cc;
if (c == controlChars[VERASE]) {
// erase one character
char lastChar;
if (line_buffer_tail_getc(tty->input_buffer, &lastChar)) {
if (lastChar == controlChars[VEOF]
|| lastChar == controlChars[VEOL]
|| lastChar == '\n' || lastChar == '\r') {
// EOF or end of line -- put it back
line_buffer_putc(tty->input_buffer, lastChar);
}
}
return;
} else if (c == controlChars[VKILL]) {
// erase line
char lastChar;
while (line_buffer_tail_getc(tty->input_buffer, &lastChar)) {
if (lastChar == controlChars[VEOF]
|| lastChar == controlChars[VEOL]
|| lastChar == '\n' || lastChar == '\r') {
// EOF or end of line -- put it back
line_buffer_putc(tty->input_buffer, lastChar);
break;
}
}
return;
} else if (c == controlChars[VEOF]) {
// we still write the EOF to the stream -- tty_input_read() needs
// to recognize it
tty->pending_eof++;
}
}
// Input character conversions have already been done. What reaches this
// point can directly be written to the line buffer.
line_buffer_putc(tty->input_buffer, c);
}
#if 0
status_t
tty_input_putc(struct tty *tty, int c)
{
status_t status = acquire_sem_etc(tty->write_sem, 1, B_CAN_INTERRUPT, 0);
if (status != B_OK)
return status;
MutexLocker locker(&tty->lock);
bool wasEmpty = line_buffer_readable(tty->input_buffer) == 0;
tty_input_putc_locked(tty, c);
// If the buffer was empty before, we can now start other readers on it.
// We assume that most of the time more than one character will be written
// using this function, so we don't want to reschedule after every character
if (wasEmpty)
release_sem_etc(tty->read_sem, 1, B_DO_NOT_RESCHEDULE);
// We only wrote one char - we give others the opportunity
// to write if there is still space left in the buffer
if (line_buffer_writable(tty->input_buffer))
release_sem_etc(tty->write_sem, 1, B_DO_NOT_RESCHEDULE);
return B_OK;
}
#endif // 0
/**
* The global lock must be held.
*/
status_t
init_tty_cookie(tty_cookie *cookie, struct tty *tty, struct tty *otherTTY,
uint32 openMode)
{
cookie->blocking_semaphore = create_sem(0, "wait for tty close");
if (cookie->blocking_semaphore < 0)
return cookie->blocking_semaphore;
cookie->tty = tty;
cookie->other_tty = otherTTY;
cookie->open_mode = openMode;
cookie->thread_count = 0;
cookie->closed = false;
return B_OK;
}
void
uninit_tty_cookie(tty_cookie *cookie)
{
if (cookie->blocking_semaphore >= 0) {
delete_sem(cookie->blocking_semaphore);
cookie->blocking_semaphore = -1;
}
cookie->tty = NULL;
cookie->thread_count = 0;
cookie->closed = false;
}
/**
* The global lock must be held.
*/
void
add_tty_cookie(tty_cookie *cookie)
{
MutexLocker locker(cookie->tty->lock);
// add to the TTY's cookie list
cookie->tty->cookies.Add(cookie);
cookie->tty->open_count++;
}
/**
* The global lock must be held.
*/
void
tty_close_cookie(struct tty_cookie *cookie)
{
MutexLocker locker(gTTYCookieLock);
// Already closed? This can happen for slaves that have been closed when
// the master was closed.
if (cookie->closed)
return;
// set the cookie's `closed' flag
cookie->closed = true;
bool unblock = (cookie->thread_count > 0);
// unblock blocking threads
if (unblock) {
cookie->tty->reader_queue.NotifyError(cookie, B_FILE_ERROR);
cookie->tty->writer_queue.NotifyError(cookie, B_FILE_ERROR);
if (cookie->other_tty->open_count > 0) {
cookie->other_tty->reader_queue.NotifyError(cookie, B_FILE_ERROR);
cookie->other_tty->writer_queue.NotifyError(cookie, B_FILE_ERROR);
}
}
locker.Unlock();
// wait till all blocking (and now unblocked) threads have left the
// critical code
if (unblock) {
TRACE(("tty_close_cookie(): cookie %p, there're still pending "
"operations, acquire blocking sem %ld\n", cookie,
cookie->blocking_semaphore));
acquire_sem(cookie->blocking_semaphore);
}
// For the removal of the cookie acquire the TTY's lock. This ensures, that
// cookies will not be removed from a TTY (or added -- cf. add_tty_cookie())
// as long as the TTY's lock is being held. This is required for the select
// support, since we need to iterate through the cookies of a TTY without
// having to acquire the global lock.
MutexLocker ttyLocker(cookie->tty->lock);
// remove the cookie from the TTY's cookie list
cookie->tty->cookies.Remove(cookie);
// close the tty, if no longer used
if (--cookie->tty->open_count == 0) {
// The last cookie of this tty has been closed. We're going to close
// the TTY and need to unblock all write requests before. There should
// be no read requests, since only a cookie of this TTY issues those.
// We do this by first notifying all queued requests of the error
// condition. We then clear the line buffer for the TTY and queue
// an own request.
// Notify the other TTY first; it doesn't accept any read/writes
// while there is only one end.
cookie->other_tty->reader_queue.NotifyError(B_FILE_ERROR);
cookie->other_tty->writer_queue.NotifyError(B_FILE_ERROR);
RecursiveLocker requestLocker(gTTYRequestLock);
// we only need to do all this, if the writer queue is not empty
if (!cookie->tty->writer_queue.IsEmpty()) {
// notify the blocking writers
cookie->tty->writer_queue.NotifyError(B_FILE_ERROR);
// enqueue our request
RequestOwner requestOwner;
requestOwner.Enqueue(cookie, &cookie->tty->writer_queue);
requestLocker.Unlock();
// clear the line buffer
clear_line_buffer(cookie->tty->input_buffer);
ttyLocker.Unlock();
// wait for our turn
requestOwner.Wait(false);
// re-lock
ttyLocker.SetTo(cookie->tty->lock, false);
requestLocker.SetTo(gTTYRequestLock, false);
// dequeue our request
requestOwner.Dequeue();
}
requestLocker.Unlock();
// finally close the tty
tty_close(cookie->tty);
}
// notify a select write event on the other tty, if we've closed this tty
if (cookie->tty->open_count == 0 && cookie->other_tty->open_count > 0)
tty_notify_select_event(cookie->other_tty, B_SELECT_WRITE);
}
static void
tty_notify_select_event(struct tty *tty, uint8 event)
{
TRACE(("tty_notify_select_event(%p, %u)\n", tty, event));
if (tty->select_pool)
notify_select_event_pool(tty->select_pool, event);
}
/** \brief Checks whether bytes can be read from/written to the line buffer of
* the given TTY and notifies the respective queues.
*
* Also sends out \c B_SELECT_READ and \c B_SELECT_WRITE events as needed.
*
* The TTY and the request lock must be held.
*
* \param tty The TTY.
* \param otherTTY The connected TTY.
*/
static void
tty_notify_if_available(struct tty *tty, struct tty *otherTTY,
bool notifySelect)
{
if (!tty)
return;
// Check, if something is readable (depending on whether canonical input
// processing is enabled).
int32 readable;
if (!tty->is_master && (tty->settings->termios.c_lflag & ICANON) != 0) {
readable = line_buffer_readable_line(tty->input_buffer,
tty->settings->termios.c_cc[VEOL],
tty->settings->termios.c_cc[VEOF]);
} else
readable = line_buffer_readable(tty->input_buffer);
if (readable > 0) {
// if nobody is waiting send select events, otherwise notify the waiter
if (!tty->reader_queue.IsEmpty())
tty->reader_queue.NotifyFirst(readable);
else if (notifySelect)
tty_notify_select_event(tty, B_SELECT_READ);
}
int32 writable = line_buffer_writable(tty->input_buffer);
if (writable > 0) {
// if nobody is waiting send select events, otherwise notify the waiter
if (!tty->writer_queue.IsEmpty()) {
tty->writer_queue.NotifyFirst(writable);
} else if (notifySelect) {
if (otherTTY && otherTTY->open_count > 0)
tty_notify_select_event(otherTTY, B_SELECT_WRITE);
}
}
}
/*!
\brief Performs input character conversion and writes the result to
\a buffer.
\param tty The master tty.
\param c The input character.
\param buffer The buffer to which to write the converted character.
\param _bytesNeeded The number of bytes needed in the target tty's
line buffer.
\return \c true, if the character shall be processed further, \c false, if
it shall be skipped.
*/
static bool
process_input_char(struct tty* tty, char c, char* buffer,
size_t* _bytesNeeded)
{
tcflag_t flags = tty->settings->termios.c_iflag;
// signals
if (tty->settings->termios.c_lflag & ISIG) {
if (c == tty->settings->termios.c_cc[VINTR]
|| c == tty->settings->termios.c_cc[VQUIT]
|| c == tty->settings->termios.c_cc[VSUSP]) {
*buffer = c;
*_bytesNeeded = 0;
return true;
}
}
// canonical input characters
if (tty->settings->termios.c_lflag & ICANON) {
if (c == tty->settings->termios.c_cc[VERASE]
|| c == tty->settings->termios.c_cc[VKILL]) {
*buffer = c;
*_bytesNeeded = 0;
return true;
}
}
// convert chars
if (c == '\r') {
if (flags & IGNCR) // ignore CR
return false;
if (flags & ICRNL) // CR -> NL
c = '\n';
} else if (c == '\n') {
if (flags & INLCR) // NL -> CR
c = '\r';
} else if (flags & ISTRIP) // strip of eighth bit
c &= 0x7f;
*buffer = c;
*_bytesNeeded = 1;
return true;
}
/*!
\brief Performs output character conversion and writes the result to
\a buffer.
\param tty The master tty.
\param c The output character.
\param buffer The buffer to which to write the converted character(s).
\param _bytesWritten The number of bytes written to the output buffer
(max 3).
\param echoed \c true if the output char to be processed has been echoed
from the input.
*/
static void
process_output_char(struct tty* tty, char c, char* buffer,
size_t *_bytesWritten, bool echoed)
{
tcflag_t flags = tty->settings->termios.c_oflag;
if (flags & OPOST) {
if (echoed && c == tty->settings->termios.c_cc[VERASE]) {
if (tty->settings->termios.c_lflag & ECHOE) {
// ERASE -> ERASE SPACE ERASE
buffer[0] = tty->settings->termios.c_cc[VERASE];
buffer[1] = ' ';
buffer[2] = tty->settings->termios.c_cc[VERASE];
*_bytesWritten = 3;
return;
}
} else if (echoed && c == tty->settings->termios.c_cc[VKILL]) {
if (!(tty->settings->termios.c_lflag & ECHOK)) {
// don't echo KILL
*_bytesWritten = 0;
return;
}
} else if (echoed && c == tty->settings->termios.c_cc[VEOF]) {
// don't echo EOF
*_bytesWritten = 0;
return;
} else if (c == '\n') {
if (echoed && !(tty->settings->termios.c_lflag & ECHONL)) {
// don't echo NL
*_bytesWritten = 0;
return;
}
if (flags & ONLCR) { // NL -> CR-NL
buffer[0] = '\r';
buffer[1] = '\n';
*_bytesWritten = 2;
return;
}
} else if (c == '\r') {
if (flags & OCRNL) { // CR -> NL
c = '\n';
} else if (flags & ONLRET) { // NL also does RET, ignore CR
*_bytesWritten = 0;
return;
} else if (flags & ONOCR) { // don't output CR at column 0
// TODO: We can't decide that here.
}
} else {
if (flags & OLCUC) // lower case -> upper case
c = toupper(c);
}
}
*buffer = c;
*_bytesWritten = 1;
}
// #pragma mark -
// device functions
status_t
tty_close(struct tty *tty)
{
// destroy the queues
tty->reader_queue.~RequestQueue();
tty->writer_queue.~RequestQueue();
tty->cookies.~TTYCookieList();
uninit_line_buffer(tty->input_buffer);
return B_OK;
}
status_t
tty_open(struct tty *tty, tty_service_func func)
{
if (init_line_buffer(tty->input_buffer, TTY_BUFFER_SIZE) < B_OK)
return B_NO_MEMORY;
tty->lock = NULL;
tty->service_func = func;
// construct the queues
new(&tty->reader_queue) RequestQueue;
new(&tty->writer_queue) RequestQueue;
new(&tty->cookies) TTYCookieList;
return B_OK;
}
status_t
tty_ioctl(tty_cookie *cookie, uint32 op, void *buffer, size_t length)
{
struct tty *tty = cookie->tty;
// bail out, if already closed
TTYReference ttyReference(cookie);
if (!ttyReference.IsLocked())
return B_FILE_ERROR;
TRACE(("tty_ioctl: tty %p, op %lu, buffer %p, length %lu\n", tty, op, buffer, length));
MutexLocker locker(tty->lock);
switch (op) {
/* blocking/non-blocking mode */
case B_SET_BLOCKING_IO:
cookie->open_mode &= ~O_NONBLOCK;
return B_OK;
case B_SET_NONBLOCKING_IO:
cookie->open_mode |= O_NONBLOCK;
return B_OK;
/* get and set TTY attributes */
case TCGETA:
TRACE(("tty: get attributes\n"));
return user_memcpy(buffer, &tty->settings->termios,
sizeof(struct termios));
case TCSETA:
case TCSETAW:
case TCSETAF:
TRACE(("tty: set attributes (iflag = %lx, oflag = %lx, "
"cflag = %lx, lflag = %lx)\n", tty->settings->termios.c_iflag,
tty->settings->termios.c_oflag, tty->settings->termios.c_cflag,
tty->settings->termios.c_lflag));
return user_memcpy(&tty->settings->termios, buffer,
sizeof(struct termios));
/* get and set process group ID */
case TIOCGPGRP:
TRACE(("tty: get pgrp_id\n"));
return user_memcpy(buffer, &tty->settings->pgrp_id, sizeof(pid_t));
case TIOCSPGRP:
case 'pgid':
{
TRACE(("tty: set pgrp_id\n"));
pid_t groupID;
if (user_memcpy(&groupID, buffer, sizeof(pid_t)) != B_OK)
return B_BAD_ADDRESS;
status_t error = team_set_foreground_process_group(tty->index,
groupID);
if (error == B_OK)
tty->settings->pgrp_id = groupID;
return error;
}
/* get and set window size */
case TIOCGWINSZ:
TRACE(("tty: set window size\n"));
return user_memcpy(buffer, &tty->settings->window_size,
sizeof(struct winsize));
case TIOCSWINSZ:
{
uint16 oldColumns = tty->settings->window_size.ws_col;
uint16 oldRows = tty->settings->window_size.ws_row;
TRACE(("tty: set window size\n"));
if (user_memcpy(&tty->settings->window_size, buffer,
sizeof(struct winsize)) < B_OK) {
return B_BAD_ADDRESS;
}
// send a signal only if the window size has changed
if ((oldColumns != tty->settings->window_size.ws_col
|| oldRows != tty->settings->window_size.ws_row)
&& tty->settings->pgrp_id != 0) {
send_signal(-tty->settings->pgrp_id, SIGWINCH);
}
return B_OK;
}
case B_IOCTL_GET_TTY_INDEX:
if (user_memcpy(buffer, &tty->index, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
case B_IOCTL_GRANT_TTY:
{
if (!tty->is_master)
return B_BAD_VALUE;
// get slave path
char path[64];
snprintf(path, sizeof(path), "/dev/%s",
gDeviceNames[kNumTTYs + tty->index]);
// set owner and permissions respectively
if (chown(path, getuid(), getgid()) != 0
|| chmod(path, S_IRUSR | S_IWUSR | S_IWGRP) != 0) {
return errno;
}
return B_OK;
}
}
TRACE(("tty: unsupported opcode %lu\n", op));
return B_BAD_VALUE;
}
status_t
tty_input_read(tty_cookie *cookie, void *buffer, size_t *_length)
{
struct tty *tty = cookie->tty;
uint32 mode = cookie->open_mode;
bool dontBlock = (mode & O_NONBLOCK) != 0;
size_t length = *_length;
ssize_t bytesRead = 0;
TRACE(("tty_input_read(tty = %p, length = %lu, mode = %lu)\n", tty, length, mode));
if (length == 0)
return B_OK;
// bail out, if the TTY is already closed
TTYReference ttyReference(cookie);
if (!ttyReference.IsLocked())
return B_FILE_ERROR;
ReaderLocker locker(cookie);
while (bytesRead == 0) {
status_t status = locker.AcquireReader(dontBlock);
if (status != B_OK) {
*_length = 0;
return status;
}
size_t toRead = locker.AvailableBytes();
if (toRead == 0)
continue;
if (toRead > length)
toRead = length;
bool _hitEOF = false;
bool* hitEOF = (tty->pending_eof > 0 ? &_hitEOF : NULL);
bytesRead = line_buffer_user_read(tty->input_buffer, (char *)buffer,
toRead, tty->settings->termios.c_cc[VEOF], hitEOF);
if (bytesRead < B_OK) {
*_length = 0;
return bytesRead;
}
// we hit an EOF char -- bail out, whatever amount of data we have
if (hitEOF && *hitEOF) {
tty->pending_eof--;
break;
}
}
*_length = bytesRead;
return B_OK;
}
static status_t
tty_write_to_tty_master_unsafe(tty_cookie *sourceCookie, const char *data,
size_t *_length)
{
struct tty *source = sourceCookie->tty;
struct tty *target = sourceCookie->other_tty;
size_t length = *_length;
size_t bytesWritten = 0;
uint32 mode = sourceCookie->open_mode;
bool dontBlock = (mode & O_NONBLOCK) != 0;
// bail out, if source is already closed
TTYReference sourceTTYReference(sourceCookie);
if (!sourceTTYReference.IsLocked())
return B_FILE_ERROR;
if (length == 0)
return B_OK;
WriterLocker locker(sourceCookie);
// if the target is not open, fail now
if (target->open_count <= 0)
return B_FILE_ERROR;
bool echo = (source->settings->termios.c_lflag & ECHO) != 0;
TRACE(("tty_write_to_tty_master(source = %p, target = %p, "
"length = %lu%s)\n", source, target, length,
(echo ? ", echo mode" : "")));
// Make sure we are first in the writer queue(s) and AvailableBytes() is
// initialized.
status_t status = locker.AcquireWriter(dontBlock, 0);
if (status != B_OK) {
*_length = 0;
return status;
}
size_t writable = locker.AvailableBytes();
size_t writtenSinceLastNotify = 0;
while (bytesWritten < length) {
// fetch next char and do input processing
char c;
size_t bytesNeeded;
if (!process_input_char(source, *data, &c, &bytesNeeded)) {
// input char shall be skipped
data++;
bytesWritten++;
continue;
}
// If in echo mode, we do the output conversion and need to update
// the needed bytes count.
char echoBuffer[3];
size_t echoBytes;
if (echo) {
process_output_char(source, c, echoBuffer, &echoBytes, true);
if (echoBytes > bytesNeeded)
bytesNeeded = echoBytes;
}
// If there's not enough space to write what we have, we need to wait
// until it is available.
if (writable < bytesNeeded) {
if (writtenSinceLastNotify > 0) {
tty_notify_if_available(target, source, true);
if (echo)
tty_notify_if_available(source, target, true);
writtenSinceLastNotify = 0;
}
status = locker.AcquireWriter(dontBlock, bytesNeeded);
if (status != B_OK) {
*_length = bytesWritten;
return status;
}
writable = locker.AvailableBytes();
// We need to restart the loop, since the termios flags might have
// changed in the meantime (while we've unlocked the tty). Note,
// that we don't re-get "echo" -- maybe we should.
continue;
}
// write the bytes
tty_input_putc_locked(target, c);
if (echo) {
for (size_t i = 0; i < echoBytes; i++)
line_buffer_putc(source->input_buffer, echoBuffer[i]);
}
writable -= bytesNeeded;
data++;
bytesWritten++;
writtenSinceLastNotify++;
}
return B_OK;
}
status_t
tty_write_to_tty_master(tty_cookie *sourceCookie, const void *_buffer,
size_t *_length)
{
const char* buffer = (const char*)_buffer;
size_t bytesRemaining = *_length;
*_length = 0;
while (bytesRemaining > 0) {
// copy data to stack
char safeBuffer[256];
size_t toWrite = min_c(sizeof(safeBuffer), bytesRemaining);
status_t error = user_memcpy(safeBuffer, buffer, toWrite);
if (error != B_OK)
return error;
// write them
size_t written = toWrite;
error = tty_write_to_tty_master_unsafe(sourceCookie, safeBuffer,
&written);
if (error != B_OK)
return error;
buffer += written;
bytesRemaining -= written;
*_length += written;
if (written < toWrite)
return B_OK;
}
return B_OK;
}
static status_t
tty_write_to_tty_slave_unsafe(tty_cookie *sourceCookie, const char *data,
size_t *_length)
{
struct tty *target = sourceCookie->other_tty;
size_t length = *_length;
size_t bytesWritten = 0;
uint32 mode = sourceCookie->open_mode;
bool dontBlock = (mode & O_NONBLOCK) != 0;
// bail out, if source is already closed
TTYReference sourceTTYReference(sourceCookie);
if (!sourceTTYReference.IsLocked())
return B_FILE_ERROR;
if (length == 0)
return B_OK;
WriterLocker locker(sourceCookie);
// if the target is not open, fail now
if (target->open_count <= 0)
return B_FILE_ERROR;
TRACE(("tty_write_to_tty_slave(source = %p, target = %p, length = %lu)\n",
sourceCookie->tty, target, length));
// Make sure we are first in the writer queue(s) and AvailableBytes() is
// initialized.
status_t status = locker.AcquireWriter(dontBlock, 0);
if (status != B_OK) {
*_length = 0;
return status;
}
size_t writable = locker.AvailableBytes();
size_t writtenSinceLastNotify = 0;
while (bytesWritten < length) {
// fetch next char and do output processing
char buffer[3];
size_t bytesNeeded;
process_output_char(target, *data, buffer, &bytesNeeded, false);
// If there's not enough space to write what we have, we need to wait
// until it is available.
if (writable < bytesNeeded) {
if (writtenSinceLastNotify > 0) {
tty_notify_if_available(target, sourceCookie->tty, true);
writtenSinceLastNotify = 0;
}
status = locker.AcquireWriter(dontBlock, bytesNeeded);
if (status != B_OK) {
*_length = bytesWritten;
return status;
}
writable = locker.AvailableBytes();
// We need to restart the loop, since the termios flags might have
// changed in the meantime (while we've unlocked the tty).
continue;
}
// write the bytes
for (size_t i = 0; i < bytesNeeded; i++)
tty_input_putc_locked(target, buffer[i]);
writable -= bytesNeeded;
data++;
bytesWritten++;
writtenSinceLastNotify++;
}
return B_OK;
}
status_t
tty_write_to_tty_slave(tty_cookie *sourceCookie, const void *_buffer,
size_t *_length)
{
const char* buffer = (const char*)_buffer;
size_t bytesRemaining = *_length;
*_length = 0;
while (bytesRemaining > 0) {
// copy data to stack
char safeBuffer[256];
size_t toWrite = min_c(sizeof(safeBuffer), bytesRemaining);
status_t error = user_memcpy(safeBuffer, buffer, toWrite);
if (error != B_OK)
return error;
// write them
size_t written = toWrite;
error = tty_write_to_tty_slave_unsafe(sourceCookie, safeBuffer,
&written);
if (error != B_OK)
return error;
buffer += written;
bytesRemaining -= written;
*_length += written;
if (written < toWrite)
return B_OK;
}
return B_OK;
}
status_t
tty_select(tty_cookie *cookie, uint8 event, uint32 ref, selectsync *sync)
{
struct tty *tty = cookie->tty;
TRACE(("tty_select(cookie = %p, event = %u, ref = %lu, sync = %p)\n",
cookie, event, ref, sync));
// we don't support all kinds of events
if (event < B_SELECT_READ || event > B_SELECT_ERROR)
return B_BAD_VALUE;
// if the TTY is already closed, we notify immediately
TTYReference ttyReference(cookie);
if (!ttyReference.IsLocked()) {
TRACE(("tty_select() done: cookie %p already closed\n", cookie));
notify_select_event(sync, event);
return B_OK;
}
// lock the TTY (allows us to freely access the cookie lists of this and
// the other TTY)
MutexLocker ttyLocker(tty->lock);
// get the other TTY -- needed for `write' events
struct tty *otherTTY = cookie->other_tty;
if (otherTTY->open_count <= 0)
otherTTY = NULL;
// add the event to the TTY's pool
status_t error = add_select_sync_pool_entry(&tty->select_pool, sync, event);
if (error != B_OK) {
TRACE(("tty_select() done: add_select_sync_pool_entry() failed: %lx\n",
error));
return error;
}
// finally also acquire the request mutex, for access to the reader/writer
// queues
RecursiveLocker requestLocker(gTTYRequestLock);
// check, if the event is already present
switch (event) {
case B_SELECT_READ:
if (tty->reader_queue.IsEmpty()
&& line_buffer_readable(tty->input_buffer) > 0) {
notify_select_event(sync, event);
}
break;
case B_SELECT_WRITE:
{
// writes go to the other TTY
if (!otherTTY) {
notify_select_event(sync, event);
break;
}
// In case input is echoed, we have to check, whether we can
// currently can write to our TTY as well.
bool echo = (tty->is_master
&& tty->settings->termios.c_lflag & ECHO);
if (otherTTY->writer_queue.IsEmpty()
&& line_buffer_writable(otherTTY->input_buffer) > 0) {
if (!echo
|| (tty->writer_queue.IsEmpty()
&& line_buffer_writable(tty->input_buffer) > 0)) {
notify_select_event(sync, event);
}
}
break;
}
case B_SELECT_ERROR:
default:
break;
}
return B_OK;
}
status_t
tty_deselect(tty_cookie *cookie, uint8 event, selectsync *sync)
{
struct tty *tty = cookie->tty;
TRACE(("tty_deselect(cookie = %p, event = %u, sync = %p)\n", cookie, event,
sync));
// we don't support all kinds of events
if (event < B_SELECT_READ || event > B_SELECT_ERROR)
return B_BAD_VALUE;
// If the TTY is already closed, we're done. Note that we don't use a
// TTYReference here, but acquire the global lock, since we don't want
// return before tty_close_cookie() is done (it sends out the select
// events on close and our select() could miss one, if we don't wait).
MutexLocker globalLocker(gGlobalTTYLock);
if (cookie->closed)
return B_OK;
// lock the TTY (guards the select sync pool, among other things)
MutexLocker ttyLocker(tty->lock);
return remove_select_sync_pool_entry(&tty->select_pool, sync, event);
}
static void
dump_tty_settings(struct tty_settings& settings)
{
kprintf(" pgrp_id: %ld\n", settings.pgrp_id);
kprintf(" session_id: %ld\n", settings.session_id);
// struct termios termios;
// struct winsize window_size;
}
static void
dump_tty_struct(struct tty& tty)
{
kprintf(" index: %ld\n", tty.index);
kprintf(" is_master: %s\n", tty.is_master ? "true" : "false");
kprintf(" open_count: %ld\n", tty.open_count);
kprintf(" select_pool: %p\n", tty.select_pool);
kprintf(" pending_eof: %lu\n", tty.pending_eof);
kprintf(" lock.sem: %ld\n", tty.lock->sem);
kprintf(" input_buffer:\n");
kprintf(" first: %ld\n", tty.input_buffer.first);
kprintf(" in: %lu\n", tty.input_buffer.in);
kprintf(" size: %lu\n", tty.input_buffer.size);
kprintf(" buffer: %p\n", tty.input_buffer.buffer);
kprintf(" reader queue:\n");
tty.reader_queue.Dump(" ");
kprintf(" writer queue:\n");
tty.writer_queue.Dump(" ");
kprintf(" cookies: ");
TTYCookieList::Iterator it = tty.cookies.GetIterator();
while (tty_cookie* cookie = it.Next())
kprintf(" %p", cookie);
kprintf("\n");
}
static int
dump_tty(int argc, char** argv)
{
if (argc < 2) {
kprintf("Usage: %s <tty index>\n", argv[0]);
return 0;
}
int32 index = atol(argv[1]);
if (index < 0 || index >= (int32)kNumTTYs) {
kprintf("Invalid tty index.\n");
return 0;
}
kprintf("master:\n");
dump_tty_struct(gMasterTTYs[index]);
kprintf("slave:\n");
dump_tty_struct(gSlaveTTYs[index]);
kprintf("settings:\n");
dump_tty_settings(gTTYSettings[index]);
return 0;
}
void
tty_add_debugger_commands()
{
add_debugger_command("tty", &dump_tty, "Dump info on a tty");
}
void
tty_remove_debugger_commands()
{
remove_debugger_command("tty", &dump_tty);
}