* Moved members pgrp_id, termios, window_size from the tty structure to

the new structure tty_settings of which there's only one instance per
  master/slave tty pair. Previously the existence of two instances of those
  members caused several kinds of problems, e.g. the Terminal setting
  the window size on the master and CLI programs readings the unchanged
  values from the slave. E.g. less correctly adjusts the display when the
  Terminal size changes, now.
* Reorganized writing to a TTY. We do no longer handle writes to master
  and slave the same way. Writes to the master are "input" and need to
  be processed differently from writes to the slave ("output"). Before,
  both were processed first as output then as input, which caused incorrect
  behavior. E.g. CRs were not echoed correctly.
* Added canonical ERASE (backspace) and KILL (clear line) processing.
  Couldn't really see it work. glibc's fgets() seems to read single
  chars, so that we never have anything in the line buffer.
* Added handling for EOF. Works well with Be's Terminal, ours seems to
  write an ESC sequence instead of the EOF char (Ctrl-D), though.
* Extended output processing support (ECHOE, ECHOK, ECHONL, OCRNL,
  ONLRET, OLCUC).
* Writes use user_memcpy() now.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21735 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold 2007-07-29 01:09:39 +00:00
parent 3ab19002fe
commit 696c54d067
7 changed files with 489 additions and 138 deletions

View File

@ -102,8 +102,9 @@ init_driver(void)
if (++digit > 15)
digit = 0, letter++;
reset_tty(&gMasterTTYs[i], i);
reset_tty(&gSlaveTTYs[i], i);
reset_tty(&gMasterTTYs[i], i, true);
reset_tty(&gSlaveTTYs[i], i, false);
reset_tty_settings(&gTTYSettings[i], i);
if (!sDeviceNames[i] || !sDeviceNames[i + kNumTTYs]) {
uninit_driver();

View File

@ -57,7 +57,8 @@ line_buffer_writable(struct line_buffer &buffer)
ssize_t
line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length)
line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length,
char eof, bool* hitEOF)
{
size_t available = buffer.in;
@ -67,6 +68,19 @@ line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length)
if (length == 0)
return 0;
// check for EOF, if the caller asked us to
if (hitEOF) {
*hitEOF = false;
for (size_t i = 0; i < available; i++) {
char c = buffer.buffer[(buffer.first + i) % buffer.size];
if (c == eof) {
*hitEOF = true;
length = i;
break;
}
}
}
ssize_t bytesRead = length;
if (buffer.first + length < buffer.size) {
@ -88,6 +102,12 @@ line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length)
buffer.in -= bytesRead;
}
// dispose of EOF char
if (hitEOF && *hitEOF) {
buffer.first = (buffer.first + 1) % buffer.size;
buffer.in--;
}
return bytesRead;
}
@ -116,3 +136,14 @@ line_buffer_ungetc(struct line_buffer &buffer, char *c)
}
#endif
bool
line_buffer_tail_getc(struct line_buffer &buffer, char *c)
{
if (buffer.in == 0)
return false;
*c = buffer.buffer[(buffer.first + --buffer.in) % buffer.size];
return true;
}

View File

@ -21,9 +21,11 @@ status_t uninit_line_buffer(struct line_buffer &buffer);
status_t clear_line_buffer(struct line_buffer &buffer);
int32 line_buffer_readable(struct line_buffer &buffer);
int32 line_buffer_writable(struct line_buffer &buffer);
ssize_t line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length);
ssize_t line_buffer_user_read(struct line_buffer &buffer, char *data,
size_t length, char eof = 0, bool* hitEOF = NULL);
status_t line_buffer_getc(struct line_buffer &buffer, char *_c);
status_t line_buffer_putc(struct line_buffer &buffer, char c);
status_t line_buffer_ungetc(struct line_buffer &buffer, char *c);
bool line_buffer_tail_getc(struct line_buffer &buffer, char *c);
#endif /* LINE_BUFFER_H */

View File

@ -173,7 +173,7 @@ master_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
TRACE(("master_write: cookie %p, offset %Ld, buffer %p, length %lu\n",
_cookie, offset, buffer, *_length));
status_t result = tty_write_to_tty(cookie, buffer, _length, true);
status_t result = tty_write_to_tty_master(cookie, buffer, _length);
TRACE(("master_write done: cookie %p, result: %lx, length %lu\n", _cookie,
result, *_length));

View File

@ -146,7 +146,7 @@ slave_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
TRACE(("slave_write: cookie %p, offset %Ld, buffer %p, length %lu\n",
_cookie, offset, buffer, *_length));
status_t result = tty_write_to_tty(cookie, buffer, _length, false);
status_t result = tty_write_to_tty_slave(cookie, buffer, _length);
TRACE(("slave_write done: cookie %p, result %lx, length %lu\n",
_cookie, result, *_length));

View File

@ -1,4 +1,5 @@
/*
* Copyright 2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. All rights reserved.
* Copyright 2004-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
* Distributed under the terms of the MIT License.
*/
@ -8,11 +9,13 @@
// tailored for pseudo-TTYs. Have a look at Be's TTY includes (drivers/tty/*)
#include <util/AutoLock.h>
#include <util/kernel_cpp.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <util/AutoLock.h>
#include <util/kernel_cpp.h>
#include "SemaphorePool.h"
#include "tty_private.h"
@ -45,13 +48,13 @@
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,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
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
@ -79,6 +82,9 @@
*/
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);
@ -252,6 +258,7 @@ RequestQueue::NotifyFirst(size_t bytesAvailable)
first->Notify(bytesAvailable);
}
void
RequestQueue::NotifyError(status_t error)
{
@ -263,6 +270,7 @@ RequestQueue::NotifyError(status_t error)
}
}
void
RequestQueue::NotifyError(tty_cookie *cookie, status_t error)
{
@ -369,7 +377,7 @@ RequestOwner::Wait(bool interruptable, Semaphore *sem)
// check, if already done
if (fError == B_OK
&& (!fRequests[0].WasNotified() || !fRequests[0].WasNotified())) {
&& (!fRequests[0].WasNotified() || !fRequests[1].WasNotified())) {
// not yet done
// set the semaphore
@ -478,7 +486,8 @@ WriterLocker::WriterLocker(tty_cookie *sourceCookie)
// tty_close_cookie() pseudo request).
// get the echo mode
fEcho = (fTarget->termios.c_lflag & ECHO) != 0;
fEcho = (fSource->is_master
&& fSource->settings->termios.c_lflag & ECHO) != 0;
// enqueue ourselves in the respective request queues
RecursiveLocker locker(gTTYRequestLock);
@ -542,7 +551,7 @@ WriterLocker::AcquireWriter(bool dontBlock, size_t bytesNeeded)
return B_OK;
}
// We are not the first in queue or currently there's nothing to read:
// 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;
@ -665,7 +674,7 @@ get_tty_index(const char *name)
}
void
static void
reset_termios(struct termios &termios)
{
memset(&termios, 0, sizeof(struct termios));
@ -682,6 +691,8 @@ reset_termios(struct termios &termios)
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');
@ -689,22 +700,30 @@ reset_termios(struct termios &termios)
void
reset_tty(struct tty *tty, int32 index)
reset_tty_settings(tty_settings *settings, int32 index)
{
reset_termios(tty->termios);
reset_termios(settings->termios);
tty->open_count = 0;
tty->index = index;
tty->lock = NULL;
tty->pgrp_id = 0;
settings->pgrp_id = 0;
// this value prevents any signal of being sent
// some initial window size - the TTY in question should set these values
tty->window_size.ws_col = 80;
tty->window_size.ws_row = 25;
tty->window_size.ws_xpixel = tty->window_size.ws_col * 8;
tty->window_size.ws_ypixel = tty->window_size.ws_row * 8;
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->is_master = isMaster;
tty->pending_eof = 0;
}
@ -719,63 +738,75 @@ tty_output_getc(struct tty *tty, int *_c)
* 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)
{
tcflag_t flags = tty->termios.c_iflag;
// process signals if needed
if ((tty->termios.c_lflag & ISIG) != 0) {
if ((tty->settings->termios.c_lflag & ISIG) != 0) {
// enable signals, process INTR, QUIT, and SUSP
int signal = -1;
if (c == tty->termios.c_cc[VINTR])
if (c == tty->settings->termios.c_cc[VINTR])
signal = SIGINT;
else if (c == tty->termios.c_cc[VQUIT])
else if (c == tty->settings->termios.c_cc[VQUIT])
signal = SIGQUIT;
else if (c == tty->termios.c_cc[VSUSP])
else if (c == tty->settings->termios.c_cc[VSUSP])
// ToDo: what to do here?
signal = -1;
// do we need to deliver a signal?
if (signal != -1) {
// we may have to flush the input buffer
if ((tty->termios.c_lflag & NOFLSH) == 0)
if ((tty->settings->termios.c_lflag & NOFLSH) == 0)
clear_line_buffer(tty->input_buffer);
if (tty->pgrp_id != 0)
send_signal(-tty->pgrp_id, signal);
if (tty->settings->pgrp_id != 0)
send_signal(-tty->settings->pgrp_id, signal);
return;
}
}
// process special canonical input characters
if ((tty->termios.c_lflag & ICANON) != 0) {
if ((tty->settings->termios.c_lflag & ICANON) != 0) {
// canonical mode, process ERASE and KILL
if (c == tty->termios.c_cc[VERASE]) {
// ToDo: erase one character
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 == tty->termios.c_cc[VKILL]) {
// ToDo: erase line
} 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++;
}
}
// character conversions
if (c == '\n' && (flags & INLCR) != 0) {
// map NL to CR
c = '\r';
} else if (c == '\r') {
if (flags & IGNCR) // ignore CR
return;
if (flags & ICRNL) // map CR to NL
c = '\n';
} if (flags & ISTRIP) // strip the highest bit
c &= 0x7f;
// 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);
}
@ -1033,6 +1064,130 @@ tty_notify_if_available(struct tty *tty, struct tty *otherTTY,
}
/*!
\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
@ -1096,44 +1251,54 @@ tty_ioctl(tty_cookie *cookie, uint32 op, void *buffer, size_t length)
case TCGETA:
TRACE(("tty: get attributes\n"));
return user_memcpy(buffer, &tty->termios, sizeof(struct termios));
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->termios.c_iflag, tty->termios.c_oflag, tty->termios.c_cflag, tty->termios.c_lflag));
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->termios, buffer, sizeof(struct termios));
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->pgrp_id, sizeof(pid_t));
return user_memcpy(buffer, &tty->settings->pgrp_id, sizeof(pid_t));
case TIOCSPGRP:
case 'pgid':
TRACE(("tty: set pgrp_id\n"));
return user_memcpy(&tty->pgrp_id, buffer, sizeof(pid_t));
return user_memcpy(&tty->settings->pgrp_id, buffer, sizeof(pid_t));
/* get and set window size */
case TIOCGWINSZ:
TRACE(("tty: set window size\n"));
return user_memcpy(buffer, &tty->window_size, sizeof(struct winsize));
return user_memcpy(buffer, &tty->settings->window_size,
sizeof(struct winsize));
case TIOCSWINSZ:
{
uint16 oldColumns = tty->window_size.ws_col, oldRows = tty->window_size.ws_row;
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->window_size, buffer, sizeof(struct winsize)) < B_OK)
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->window_size.ws_col || oldRows != tty->window_size.ws_row)
&& tty->pgrp_id != 0)
send_signal(-tty->pgrp_id, SIGWINCH);
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;
}
@ -1172,14 +1337,21 @@ tty_input_read(tty_cookie *cookie, void *buffer, size_t *_length)
return status;
}
// ToDo: add support for ICANON mode
bool _hitEOF = false;
bool* hitEOF = (tty->pending_eof > 0 ? &_hitEOF : NULL);
bytesRead = line_buffer_user_read(tty->input_buffer, (char *)buffer,
length);
length, 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;
@ -1187,13 +1359,12 @@ tty_input_read(tty_cookie *cookie, void *buffer, size_t *_length)
}
status_t
tty_write_to_tty(tty_cookie *sourceCookie, const void *buffer, size_t *_length,
bool sourceIsMaster)
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;
const char *data = (const char *)buffer;
size_t length = *_length;
size_t bytesWritten = 0;
uint32 mode = sourceCookie->open_mode;
@ -1213,72 +1384,207 @@ tty_write_to_tty(tty_cookie *sourceCookie, const void *buffer, size_t *_length,
if (target->open_count <= 0)
return B_FILE_ERROR;
bool echo = (target->termios.c_lflag & ECHO) != 0;
// Confusingly enough, we need to echo when the target's ECHO flag is
// set. That's because our target is supposed to echo back at us, not
// to itself.
bool echo = (source->settings->termios.c_lflag & ECHO) != 0;
TRACE(("tty_write_to_tty(source = %p, target = %p, length = %lu, %s%s)\n", source, target, length,
sourceIsMaster ? "master" : "slave", echo ? ", echo mode" : ""));
TRACE(("tty_write_to_tty_master(source = %p, target = %p, "
"length = %lu%s)\n", source, target, length,
(echo ? ", echo mode" : "")));
// ToDo: "buffer" is not yet copied or accessed in a safe way!
size_t bytesNeeded = 1;
status_t status = locker.AcquireWriter(dontBlock, 0);
if (status != B_OK) {
*_length = 0;
return status;
}
size_t writable = locker.AvailableBytes();
while (bytesWritten < length) {
status_t status = locker.AcquireWriter(dontBlock, bytesNeeded);
if (status != B_OK) {
*_length = bytesWritten;
return status;
}
bytesNeeded = 1; // reset
size_t writable = locker.AvailableBytes();
if (writable == 0) {
// should never happen
// 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;
}
while (writable > 0 && bytesWritten < length) {
char c = data[0];
// 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 (c == '\n' && (source->termios.c_oflag & (OPOST | ONLCR)) == OPOST | ONLCR) {
// post-process output and transfrom '\n' to '\r\n'
// If we can't write both '\r' and '\n', we won't write either,
// or we would write the '\r' again.
if (writable < 2) {
// try acquiring the writer again, when enough space is available
bytesNeeded = 2;
writable = 0;
// make sure we're breaking out of this loop
continue;
}
#if 1
// ToDo: this doesn't fix the bug that is responsible for the doubled shell prompt
// and it's all wrong, too - but it cures the symptoms, and that's good enough
// for now :)
// Apparently, the shell reads only single bytes and handles both, '\r' and '\n'
// as a newline.
if (!sourceIsMaster)
#endif
tty_input_putc_locked(target, '\r');
if (echo)
tty_input_putc_locked(source, '\r');
if (--writable == 0)
continue;
// If there's not enough space to write what we have, we need to wait
// until it is available.
if (writable < bytesNeeded) {
status = locker.AcquireWriter(dontBlock, bytesNeeded);
if (status != B_OK) {
*_length = bytesWritten;
return status;
}
tty_input_putc_locked(target, c);
if (echo)
tty_input_putc_locked(source, c);
writable = locker.AvailableBytes();
data++;
bytesWritten++;
// 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++;
}
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));
status_t status = locker.AcquireWriter(dontBlock, 0);
if (status != B_OK) {
*_length = 0;
return status;
}
size_t writable = locker.AvailableBytes();
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) {
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++;
}
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;
@ -1346,9 +1652,10 @@ tty_select(tty_cookie *cookie, uint8 event, uint32 ref, selectsync *sync)
break;
}
// In case the other TTY echos, we have to check, whether we can
// In case input is echoed, we have to check, whether we can
// currently can write to our TTY as well.
bool echo = (otherTTY->termios.c_lflag & ECHO);
bool echo = (tty->is_master
&& tty->settings->termios.c_lflag & ECHO);
if (otherTTY->writer_queue.IsEmpty()
&& line_buffer_writable(otherTTY->input_buffer) > 0) {

View File

@ -118,24 +118,31 @@ struct tty_cookie : DoublyLinkedListLinkImpl<tty_cookie> {
typedef DoublyLinkedList<tty_cookie> TTYCookieList;
struct tty_settings {
pid_t pgrp_id;
struct termios termios;
struct winsize window_size;
};
struct tty {
int32 open_count;
int32 index;
struct mutex *lock;
struct mutex* lock;
tty_settings* settings;
RequestQueue reader_queue;
RequestQueue writer_queue;
TTYCookieList cookies;
pid_t pgrp_id;
line_buffer input_buffer;
tty_service_func service_func;
struct termios termios;
struct winsize window_size;
uint32 pending_eof;
bool is_master;
};
static const uint32 kNumTTYs = 64;
extern tty gMasterTTYs[kNumTTYs];
extern tty gSlaveTTYs[kNumTTYs];
extern tty_settings gTTYSettings[kNumTTYs];
extern device_hooks gMasterTTYHooks;
extern device_hooks gSlaveTTYHooks;
@ -149,13 +156,16 @@ extern SemaphorePool *gSemaphorePool;
// functions available for master/slave TTYs
extern int32 get_tty_index(const char *name);
extern void reset_tty(struct tty *tty, int32 index);
extern void reset_tty(struct tty *tty, int32 index, bool isMaster);
extern void reset_tty_settings(tty_settings *settings, int32 index);
//extern status_t tty_input_putc(struct tty *tty, int c);
extern status_t tty_input_read(tty_cookie *cookie, void *buffer,
size_t *_length);
extern status_t tty_output_getc(struct tty *tty, int *_c);
extern status_t tty_write_to_tty(tty_cookie *sourceCookie, const void *buffer,
size_t *_length, bool sourceIsMaster);
extern status_t tty_write_to_tty_master(tty_cookie *sourceCookie,
const void *buffer, size_t *_length);
extern status_t tty_write_to_tty_slave(tty_cookie *sourceCookie,
const void *buffer, size_t *_length);
extern status_t init_tty_cookie(tty_cookie *cookie, struct tty *tty,
struct tty *otherTTY, uint32 openMode);