From 696c54d0673fe190dbbae4323b48bf1b8d9c2beb Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 29 Jul 2007 01:09:39 +0000 Subject: [PATCH] * 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 --- src/add-ons/kernel/drivers/tty/driver.cpp | 5 +- .../kernel/drivers/tty/line_buffer.cpp | 33 +- src/add-ons/kernel/drivers/tty/line_buffer.h | 4 +- src/add-ons/kernel/drivers/tty/master.cpp | 2 +- src/add-ons/kernel/drivers/tty/slave.cpp | 2 +- src/add-ons/kernel/drivers/tty/tty.cpp | 557 ++++++++++++++---- src/add-ons/kernel/drivers/tty/tty_private.h | 24 +- 7 files changed, 489 insertions(+), 138 deletions(-) diff --git a/src/add-ons/kernel/drivers/tty/driver.cpp b/src/add-ons/kernel/drivers/tty/driver.cpp index 911fe1f0dd..dd7b76f58c 100644 --- a/src/add-ons/kernel/drivers/tty/driver.cpp +++ b/src/add-ons/kernel/drivers/tty/driver.cpp @@ -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(); diff --git a/src/add-ons/kernel/drivers/tty/line_buffer.cpp b/src/add-ons/kernel/drivers/tty/line_buffer.cpp index 8b2182c951..fe3d683987 100644 --- a/src/add-ons/kernel/drivers/tty/line_buffer.cpp +++ b/src/add-ons/kernel/drivers/tty/line_buffer.cpp @@ -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; +} diff --git a/src/add-ons/kernel/drivers/tty/line_buffer.h b/src/add-ons/kernel/drivers/tty/line_buffer.h index 43a6b51bd2..0d5ac31177 100644 --- a/src/add-ons/kernel/drivers/tty/line_buffer.h +++ b/src/add-ons/kernel/drivers/tty/line_buffer.h @@ -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 */ diff --git a/src/add-ons/kernel/drivers/tty/master.cpp b/src/add-ons/kernel/drivers/tty/master.cpp index 504b9601b9..62acbecf25 100644 --- a/src/add-ons/kernel/drivers/tty/master.cpp +++ b/src/add-ons/kernel/drivers/tty/master.cpp @@ -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)); diff --git a/src/add-ons/kernel/drivers/tty/slave.cpp b/src/add-ons/kernel/drivers/tty/slave.cpp index 7d68289ef3..563c854c6c 100644 --- a/src/add-ons/kernel/drivers/tty/slave.cpp +++ b/src/add-ons/kernel/drivers/tty/slave.cpp @@ -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)); diff --git a/src/add-ons/kernel/drivers/tty/tty.cpp b/src/add-ons/kernel/drivers/tty/tty.cpp index 63a3d75e86..71b0742ad5 100644 --- a/src/add-ons/kernel/drivers/tty/tty.cpp +++ b/src/add-ons/kernel/drivers/tty/tty.cpp @@ -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 -#include +#include #include #include +#include +#include + #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) { diff --git a/src/add-ons/kernel/drivers/tty/tty_private.h b/src/add-ons/kernel/drivers/tty/tty_private.h index 1ea2e476b5..8236b01a6b 100644 --- a/src/add-ons/kernel/drivers/tty/tty_private.h +++ b/src/add-ons/kernel/drivers/tty/tty_private.h @@ -118,24 +118,31 @@ struct tty_cookie : DoublyLinkedListLinkImpl { typedef DoublyLinkedList 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);