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);