xref: /haiku/src/add-ons/kernel/generic/tty/tty.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2007-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "tty_private.h"
9 
10 #include <ctype.h>
11 #include <errno.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <sys/ioctl.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 
19 #include <util/AutoLock.h>
20 #include <util/kernel_cpp.h>
21 
22 #include <team.h>
23 
24 #include <tty.h>
25 
26 
27 //#define TTY_TRACE
28 #ifdef TTY_TRACE
29 #	define TRACE(x) dprintf x
30 #else
31 #	define TRACE(x) ;
32 #endif
33 
34 
35 /*
36 	Locking
37 	-------
38 
39 	There are three locks involved. If more than one needs to be held at a
40 	time, they must be acquired in the order they are listed here.
41 
42 	gTTYCookieLock: Guards the access to the fields
43 	tty_cookie::{thread_count,closed}, or more precisely makes access to them
44 	atomic. thread_count is the number of threads currently using the cookie
45 	(i.e. read(), write(), ioctl() operations in progress). Together with
46 	blocking_semaphore this serves the purpose to make sure that all pending
47 	operations are done at a certain point when closing a cookie
48 	(cf. tty_close_cookie() and TTYReference).
49 
50 	tty::lock: Guards the access to tty::{input_buffer,settings::{termios,
51 	window_size,pgrp_id}}. Moreover when held guarantees that tty::open_count
52 	won't drop to zero. A tty and the tty connected to it (master and slave)
53 	share the same lock.
54 
55 	gTTYRequestLock: Guards access to tty::{reader,writer}_queue (most
56 	RequestQueue methods do the locking themselves (the lock is a
57 	recursive_lock)), queued Requests and associated RequestOwners.
58 
59 
60 	Reading/Writing
61 	---------------
62 
63 	Most of the dirty work when dealing with reading/writing is done by the
64 	{Reader,Writer}Locker classes. Upon construction they lock the tty,
65 	(tty::lock) create a RequestOwner and queue Requests in the respective
66 	reader/writer queues (tty::{reader,writer}_queue). The
67 	Acquire{Reader,Writer}() methods need to be called before being allowed to
68 	read/write. They ensure that there is actually something to read/space for
69 	writing -- in blocking mode they wait, if necessary. When destroyed the
70 	{Reader,Writer}Locker() remove the formerly enqueued Requests and notify
71 	waiting reader/writer and/or send out select events, whatever is appropiate.
72 
73 	Acquire{Reader,Writer}() never return without an actual event being
74 	occurred. Either an error has occurred (return value) -- in this case the
75 	caller should terminate -- or bytes are available for reading/space for
76 	writing (cf. AvailableBytes()).
77 */
78 
79 
80 static void tty_notify_select_event(struct tty* tty, uint8 event);
81 static void tty_notify_if_available(struct tty* tty, struct tty* otherTTY,
82 	bool notifySelect);
83 
84 
85 class AbstractLocker {
86 public:
87 	AbstractLocker(tty_cookie* cookie)
88 		:
89 		fCookie(cookie),
90 		fBytes(0)
91 	{
92 	}
93 
94 	size_t AvailableBytes() const
95 		{ return fBytes; }
96 
97 protected:
98 	void Lock()
99 		{ recursive_lock_lock(fCookie->tty->lock); }
100 	void Unlock()
101 		{ recursive_lock_unlock(fCookie->tty->lock); }
102 
103 	tty_cookie*	fCookie;
104 	size_t		fBytes;
105 };
106 
107 
108 class WriterLocker : public AbstractLocker {
109 public:
110 								WriterLocker(tty_cookie* sourceCookie);
111 								~WriterLocker();
112 
113 			status_t			AcquireWriter(bool dontBlock,
114 									size_t bytesNeeded);
115 
116 private:
117 			size_t				_CheckAvailableBytes() const;
118 
119 			struct tty*			fSource;
120 			struct tty*			fTarget;
121 			RequestOwner		fRequestOwner;
122 			bool				fEcho;
123 };
124 
125 
126 class ReaderLocker : public AbstractLocker {
127 public:
128 								ReaderLocker(tty_cookie* cookie);
129 								~ReaderLocker();
130 
131 			status_t			AcquireReader(bigtime_t timeout,
132 									size_t bytesNeeded);
133 
134 private:
135 			size_t				_CheckAvailableBytes() const;
136 
137 			struct tty*			fTTY;
138 			RequestOwner		fRequestOwner;
139 };
140 
141 
142 class TTYReferenceLocking {
143 public:
144 	inline bool Lock(tty_cookie* cookie)
145 	{
146 		MutexLocker _(gTTYCookieLock);
147 
148 		if (cookie->closed)
149 			return false;
150 
151 		cookie->thread_count++;
152 
153 		return true;
154 	}
155 
156 	inline void Unlock(tty_cookie* cookie)
157 	{
158 		MutexLocker locker(gTTYCookieLock);
159 
160 		sem_id semaphore = -1;
161 		if (--cookie->thread_count == 0 && cookie->closed)
162 			semaphore = cookie->blocking_semaphore;
163 
164 		locker.Unlock();
165 
166 		if (semaphore >= 0) {
167 			TRACE(("TTYReference: cookie %p closed, last operation done, "
168 				"releasing blocking sem %" B_PRId32 "\n", cookie, semaphore));
169 
170 			release_sem(semaphore);
171 		}
172 	}
173 };
174 
175 typedef AutoLocker<tty_cookie, TTYReferenceLocking> TTYReference;
176 
177 
178 // #pragma mark -
179 
180 
181 Request::Request()
182 	:
183 	fOwner(NULL),
184 	fCookie(NULL),
185 	fBytesNeeded(0),
186 	fNotified(false),
187 	fError(false)
188 {
189 }
190 
191 
192 void
193 Request::Init(RequestOwner* owner, tty_cookie* cookie, size_t bytesNeeded)
194 {
195 	fOwner = owner;
196 	fCookie = cookie;
197 	fBytesNeeded = bytesNeeded;
198 	fNotified = false;
199 	fError = false;
200 }
201 
202 
203 void
204 Request::Notify(size_t bytesAvailable)
205 {
206 	if (!fNotified && bytesAvailable >= fBytesNeeded && fOwner) {
207 		fOwner->Notify(this);
208 		fNotified = true;
209 	}
210 }
211 
212 
213 void
214 Request::NotifyError(status_t error)
215 {
216 	if (!fError && fOwner) {
217 		fOwner->NotifyError(this, error);
218 		fError = true;
219 		fNotified = true;
220 	}
221 }
222 
223 
224 void
225 Request::Dump(const char* prefix)
226 {
227 	kprintf("%srequest: %p\n", prefix, this);
228 	kprintf("%s  owner:        %p\n", prefix, fOwner);
229 	kprintf("%s  cookie:       %p\n", prefix, fCookie);
230 	kprintf("%s  bytes needed: %lu\n", prefix, fBytesNeeded);
231 	kprintf("%s  notified:     %s\n", prefix, fNotified ? "true" : "false");
232 	kprintf("%s  error:        %s\n", prefix, fError ? "true" : "false");
233 }
234 
235 
236 // #pragma mark -
237 
238 
239 RequestQueue::RequestQueue()
240 	:
241 	fRequests()
242 {
243 }
244 
245 
246 void
247 RequestQueue::Add(Request* request)
248 {
249 	if (request) {
250 		RecursiveLocker _(gTTYRequestLock);
251 
252 		fRequests.Add(request, true);
253 	}
254 }
255 
256 
257 void
258 RequestQueue::Remove(Request* request)
259 {
260 	if (request) {
261 		RecursiveLocker _(gTTYRequestLock);
262 
263 		fRequests.Remove(request);
264 	}
265 }
266 
267 
268 void
269 RequestQueue::NotifyFirst(size_t bytesAvailable)
270 {
271 	RecursiveLocker _(gTTYRequestLock);
272 
273 	if (Request* first = First())
274 		first->Notify(bytesAvailable);
275 }
276 
277 
278 void
279 RequestQueue::NotifyError(status_t error)
280 {
281 	RecursiveLocker _(gTTYRequestLock);
282 
283 	for (RequestList::Iterator it = fRequests.GetIterator(); it.HasNext();) {
284 		Request* request = it.Next();
285 		request->NotifyError(error);
286 	}
287 }
288 
289 
290 void
291 RequestQueue::NotifyError(tty_cookie* cookie, status_t error)
292 {
293 	RecursiveLocker _(gTTYRequestLock);
294 
295 	for (RequestList::Iterator it = fRequests.GetIterator(); it.HasNext();) {
296 		Request* request = it.Next();
297 		if (request->TTYCookie() == cookie)
298 			request->NotifyError(error);
299 	}
300 }
301 
302 
303 void
304 RequestQueue::Dump(const char* prefix)
305 {
306 	RequestList::Iterator it = fRequests.GetIterator();
307 	while (Request* request = it.Next())
308 		request->Dump(prefix);
309 }
310 
311 
312 // #pragma mark -
313 
314 
315 RequestOwner::RequestOwner()
316 	:
317 	fConditionVariable(NULL),
318 	fCookie(NULL),
319 	fError(B_OK),
320 	fBytesNeeded(1)
321 {
322 	fRequestQueues[0] = NULL;
323 	fRequestQueues[1] = NULL;
324 }
325 
326 
327 /*!	The caller must already hold the request lock.
328 */
329 void
330 RequestOwner::Enqueue(tty_cookie* cookie, RequestQueue* queue1,
331 	RequestQueue* queue2)
332 {
333 	TRACE(("%p->RequestOwner::Enqueue(%p, %p, %p)\n", this, cookie, queue1,
334 		queue2));
335 
336 	fCookie = cookie;
337 
338 	fRequestQueues[0] = queue1;
339 	fRequestQueues[1] = queue2;
340 
341 	fRequests[0].Init(this, cookie, fBytesNeeded);
342 	if (queue1)
343 		queue1->Add(&fRequests[0]);
344 	else
345 		fRequests[0].Notify(fBytesNeeded);
346 
347 	fRequests[1].Init(this, cookie, fBytesNeeded);
348 	if (queue2)
349 		queue2->Add(&fRequests[1]);
350 	else
351 		fRequests[1].Notify(fBytesNeeded);
352 }
353 
354 
355 /*!	The caller must already hold the request lock.
356 */
357 void
358 RequestOwner::Dequeue()
359 {
360 	TRACE(("%p->RequestOwner::Dequeue()\n", this));
361 
362 	if (fRequestQueues[0])
363 		fRequestQueues[0]->Remove(&fRequests[0]);
364 	if (fRequestQueues[1])
365 		fRequestQueues[1]->Remove(&fRequests[1]);
366 
367 	fRequestQueues[0] = NULL;
368 	fRequestQueues[1] = NULL;
369 }
370 
371 
372 void
373 RequestOwner::SetBytesNeeded(size_t bytesNeeded)
374 {
375 	if (fRequestQueues[0])
376 		fRequests[0].Init(this, fCookie, bytesNeeded);
377 
378 	if (fRequestQueues[1])
379 		fRequests[1].Init(this, fCookie, bytesNeeded);
380 }
381 
382 
383 /*!	The request lock MUST NOT be held!
384 */
385 status_t
386 RequestOwner::Wait(bool interruptable, bigtime_t timeout)
387 {
388 	TRACE(("%p->RequestOwner::Wait(%d)\n", this, interruptable));
389 
390 	status_t error = B_OK;
391 
392 	RecursiveLocker locker(gTTYRequestLock);
393 
394 	// check, if already done
395 	if (fError == B_OK
396 		&& (!fRequests[0].WasNotified() || !fRequests[1].WasNotified())) {
397 		// not yet done
398 
399 		// publish the condition variable
400 		ConditionVariable conditionVariable;
401 		conditionVariable.Init(this, "tty request");
402 		fConditionVariable = &conditionVariable;
403 
404 		// add an entry to wait on
405 		ConditionVariableEntry entry;
406 		conditionVariable.Add(&entry);
407 
408 		locker.Unlock();
409 
410 		// wait
411 		TRACE(("%p->RequestOwner::Wait(): waiting for condition...\n", this));
412 
413 		error = entry.Wait(
414 			(interruptable ? B_CAN_INTERRUPT : 0) | B_RELATIVE_TIMEOUT,
415 			timeout);
416 
417 		TRACE(("%p->RequestOwner::Wait(): condition occurred: %" B_PRIx32 "\n",
418 			this, error));
419 
420 		// remove the condition variable
421 		locker.Lock();
422 		fConditionVariable = NULL;
423 	}
424 
425 	// get the result
426 	if (error == B_OK)
427 		error = fError;
428 
429 	return error;
430 }
431 
432 
433 bool
434 RequestOwner::IsFirstInQueues()
435 {
436 	RecursiveLocker locker(gTTYRequestLock);
437 
438 	for (int i = 0; i < 2; i++) {
439 		if (fRequestQueues[i] && fRequestQueues[i]->First() != &fRequests[i])
440 			return false;
441 	}
442 
443 	return true;
444 }
445 
446 
447 void
448 RequestOwner::Notify(Request* request)
449 {
450 	TRACE(("%p->RequestOwner::Notify(%p)\n", this, request));
451 
452 	if (fError == B_OK && !request->WasNotified()) {
453 		bool notify = false;
454 
455 		if (&fRequests[0] == request) {
456 			notify = fRequests[1].WasNotified();
457 		} else if (&fRequests[1] == request) {
458 			notify = fRequests[0].WasNotified();
459 		} else {
460 			// spurious call
461 		}
462 
463 		if (notify && fConditionVariable)
464 			fConditionVariable->NotifyOne();
465 	}
466 }
467 
468 
469 void
470 RequestOwner::NotifyError(Request* request, status_t error)
471 {
472 	TRACE(("%p->RequestOwner::NotifyError(%p, %" B_PRIx32 ")\n", this, request,
473 		error));
474 
475 	if (fError == B_OK) {
476 		fError = error;
477 
478 		if (!fRequests[0].WasNotified() || !fRequests[1].WasNotified()) {
479 			if (fConditionVariable)
480 				fConditionVariable->NotifyOne();
481 		}
482 	}
483 }
484 
485 
486 // #pragma mark -
487 
488 
489 WriterLocker::WriterLocker(tty_cookie* sourceCookie)
490 	:
491 	AbstractLocker(sourceCookie),
492 	fSource(fCookie->tty),
493 	fTarget(fCookie->other_tty),
494 	fRequestOwner(),
495 	fEcho(false)
496 {
497 	Lock();
498 
499 	// Now that the tty pair is locked, we can check, whether the target is
500 	// open at all.
501 	if (fTarget->open_count > 0) {
502 		// The target tty is open. As soon as we have appended a request to
503 		// the writer queue of the target, it is guaranteed to remain valid
504 		// until we have removed the request (and notified the
505 		// tty_close_cookie() pseudo request).
506 
507 		// get the echo mode
508 		fEcho = (fSource->is_master
509 			&& fSource->settings.termios.c_lflag & ECHO) != 0;
510 
511 		// enqueue ourselves in the respective request queues
512 		RecursiveLocker locker(gTTYRequestLock);
513 		fRequestOwner.Enqueue(fCookie, &fTarget->writer_queue,
514 			(fEcho ? &fSource->writer_queue : NULL));
515 	} else {
516 		// target is not open: we set it to NULL; all further operations on
517 		// this locker will fail
518 		fTarget = NULL;
519 	}
520 }
521 
522 
523 WriterLocker::~WriterLocker()
524 {
525 	// dequeue from request queues
526 	RecursiveLocker locker(gTTYRequestLock);
527 	fRequestOwner.Dequeue();
528 
529 	// check the tty queues and notify the next in line, and send out select
530 	// events
531 	if (fTarget)
532 		tty_notify_if_available(fTarget, fSource, true);
533 	if (fEcho)
534 		tty_notify_if_available(fSource, fTarget, true);
535 
536 	locker.Unlock();
537 
538 	Unlock();
539 }
540 
541 
542 size_t
543 WriterLocker::_CheckAvailableBytes() const
544 {
545 	size_t writable = line_buffer_writable(fTarget->input_buffer);
546 	if (fEcho) {
547 		// we can only write as much as is available on both ends
548 		size_t locallyWritable = line_buffer_writable(fSource->input_buffer);
549 		if (locallyWritable < writable)
550 			writable = locallyWritable;
551 	}
552 	return writable;
553 }
554 
555 
556 status_t
557 WriterLocker::AcquireWriter(bool dontBlock, size_t bytesNeeded)
558 {
559 	if (!fTarget)
560 		return B_FILE_ERROR;
561 	if (fEcho && fCookie->closed)
562 		return B_FILE_ERROR;
563 
564 	RecursiveLocker requestLocker(gTTYRequestLock);
565 
566 	// check, if we're first in queue, and if there is space to write
567 	if (fRequestOwner.IsFirstInQueues()) {
568 		fBytes = _CheckAvailableBytes();
569 		if (fBytes >= bytesNeeded)
570 			return B_OK;
571 	}
572 
573 	// We are not the first in queue or currently there's no space to write:
574 	// bail out, if we shall not block.
575 	if (dontBlock)
576 		return B_WOULD_BLOCK;
577 
578 	// set the number of bytes we need and notify, just in case we're first in
579 	// one of the queues (RequestOwner::SetBytesNeeded() resets the notification
580 	// state)
581 	fRequestOwner.SetBytesNeeded(bytesNeeded);
582 	if (fTarget)
583 		tty_notify_if_available(fTarget, fSource, false);
584 	if (fEcho)
585 		tty_notify_if_available(fSource, fTarget, false);
586 
587 	requestLocker.Unlock();
588 
589 	// block until something happens
590 	Unlock();
591 	status_t status = fRequestOwner.Wait(true);
592 	Lock();
593 
594 	// RequestOwner::Wait() returns the error, but to avoid a race condition
595 	// when closing a tty, we re-get the error with the tty lock being held.
596 	if (status == B_OK) {
597 		RecursiveLocker _(gTTYRequestLock);
598 		status = fRequestOwner.Error();
599 	}
600 
601 	if (status == B_OK)
602 		fBytes = _CheckAvailableBytes();
603 
604 	return status;
605 }
606 
607 
608 //	#pragma mark -
609 
610 
611 ReaderLocker::ReaderLocker(tty_cookie* cookie)
612 	:
613 	AbstractLocker(cookie),
614 	fTTY(cookie->tty),
615 	fRequestOwner()
616 {
617 	Lock();
618 
619 	// enqueue ourselves in the reader request queue
620 	RecursiveLocker locker(gTTYRequestLock);
621 	fRequestOwner.Enqueue(fCookie, &fTTY->reader_queue);
622 }
623 
624 
625 ReaderLocker::~ReaderLocker()
626 {
627 	// dequeue from reader request queue
628 	RecursiveLocker locker(gTTYRequestLock);
629 	fRequestOwner.Dequeue();
630 
631 	// check the tty queues and notify the next in line, and send out select
632 	// events
633 	struct tty* otherTTY = fCookie->other_tty;
634 	tty_notify_if_available(fTTY, (otherTTY->open_count > 0 ? otherTTY : NULL),
635 		true);
636 
637 	locker.Unlock();
638 
639 	Unlock();
640 }
641 
642 
643 status_t
644 ReaderLocker::AcquireReader(bigtime_t timeout, size_t bytesNeeded)
645 {
646 	if (fCookie->closed)
647 		return B_FILE_ERROR;
648 
649 	// check, if we're first in queue, and if there is something to read
650 	if (fRequestOwner.IsFirstInQueues()) {
651 		fBytes = _CheckAvailableBytes();
652 		if (fBytes >= bytesNeeded)
653 			return B_OK;
654 	}
655 
656 	if (fCookie->other_tty->open_count == 0
657 		&& fCookie->other_tty->opened_count > 0) {
658 		TRACE(("ReaderLocker::AcquireReader() opened_count %" B_PRId32 "\n",
659 			fCookie->other_tty->opened_count));
660 		return B_FILE_ERROR;
661 	}
662 
663 	// We are not the first in queue or currently there's nothing to read:
664 	// bail out, if we shall not block.
665 	if (timeout <= 0)
666 		return B_WOULD_BLOCK;
667 
668 	// reset the number of bytes we need
669 	fRequestOwner.SetBytesNeeded(bytesNeeded);
670 
671 	// block until something happens
672 	Unlock();
673 	status_t status = fRequestOwner.Wait(true, timeout);
674 	Lock();
675 
676 	fBytes = _CheckAvailableBytes();
677 
678 	TRACE(("ReaderLocker::AcquireReader() ended status 0x%" B_PRIx32 "\n",
679 		status));
680 
681 	return status;
682 }
683 
684 
685 size_t
686 ReaderLocker::_CheckAvailableBytes() const
687 {
688 	// Reading from the slave with canonical input processing enabled means
689 	// that we read at max until hitting a line end or EOF.
690 	if (!fTTY->is_master && (fTTY->settings.termios.c_lflag & ICANON) != 0) {
691 		return line_buffer_readable_line(fTTY->input_buffer,
692 			fTTY->settings.termios.c_cc[VEOL],
693 			fTTY->settings.termios.c_cc[VEOF]);
694 	}
695 
696 	return line_buffer_readable(fTTY->input_buffer);
697 }
698 
699 
700 // #pragma mark -
701 
702 
703 static void
704 reset_termios(struct termios& termios)
705 {
706 	memset(&termios, 0, sizeof(struct termios));
707 
708 	termios.c_iflag = ICRNL;
709 	termios.c_oflag = OPOST | ONLCR;
710 	termios.c_cflag = B19200 | CS8 | CREAD | HUPCL;
711 		// enable receiver, hang up on last close
712 	termios.c_lflag = ECHO | ISIG | ICANON;
713 	termios.c_ispeed = B19200;
714 	termios.c_ospeed = B19200;
715 
716 	// control characters
717 	termios.c_cc[VINTR] = CTRL('C');
718 	termios.c_cc[VQUIT] = CTRL('\\');
719 	termios.c_cc[VERASE] = 0x7f;
720 	termios.c_cc[VKILL] = CTRL('U');
721 	termios.c_cc[VEOF] = CTRL('D');
722 	termios.c_cc[VEOL] = '\0';
723 	termios.c_cc[VEOL2] = '\0';
724 	termios.c_cc[VSTART] = CTRL('S');
725 	termios.c_cc[VSTOP] = CTRL('Q');
726 	termios.c_cc[VSUSP] = CTRL('Z');
727 }
728 
729 
730 static void
731 reset_tty_settings(tty_settings& settings)
732 {
733 	reset_termios(settings.termios);
734 
735 	settings.pgrp_id = 0;
736 		// this value prevents any signal of being sent
737 	settings.session_id = -1;
738 
739 	// some initial window size - the TTY in question should set these values
740 	settings.window_size.ws_col = 80;
741 	settings.window_size.ws_row = 25;
742 	settings.window_size.ws_xpixel = settings.window_size.ws_col * 8;
743 	settings.window_size.ws_ypixel = settings.window_size.ws_row * 8;
744 }
745 
746 
747 /*!	Processes the input character and puts it into the TTY's input buffer.
748 	Depending on the termios flags set, signals may be sent, the input
749 	character changed or removed, etc.
750 */
751 static void
752 tty_input_putc_locked(struct tty* tty, int c)
753 {
754 	// process signals if needed
755 
756 	if ((tty->settings.termios.c_lflag & ISIG) != 0) {
757 		// enable signals, process INTR, QUIT, and SUSP
758 		int signal = -1;
759 
760 		if (c == tty->settings.termios.c_cc[VINTR])
761 			signal = SIGINT;
762 		else if (c == tty->settings.termios.c_cc[VQUIT])
763 			signal = SIGQUIT;
764 		else if (c == tty->settings.termios.c_cc[VSUSP])
765 			signal = SIGTSTP;
766 
767 		// do we need to deliver a signal?
768 		if (signal != -1) {
769 			// we may have to flush the input buffer
770 			if ((tty->settings.termios.c_lflag & NOFLSH) == 0)
771 				clear_line_buffer(tty->input_buffer);
772 
773 			if (tty->settings.pgrp_id != 0)
774 				send_signal(-tty->settings.pgrp_id, signal);
775 			return;
776 		}
777 	}
778 
779 	// process special canonical input characters
780 
781 	if ((tty->settings.termios.c_lflag & ICANON) != 0) {
782 		// canonical mode, process ERASE and KILL
783 		cc_t* controlChars = tty->settings.termios.c_cc;
784 
785 		if (c == controlChars[VERASE]) {
786 			// erase one character
787 			char lastChar;
788 			if (line_buffer_tail_getc(tty->input_buffer, &lastChar)) {
789 				if (lastChar == controlChars[VEOF]
790 					|| lastChar == controlChars[VEOL]
791 					|| lastChar == '\n' || lastChar == '\r') {
792 					// EOF or end of line -- put it back
793 					line_buffer_putc(tty->input_buffer, lastChar);
794 				}
795 			}
796 			return;
797 		} else if (c == controlChars[VKILL]) {
798 			// erase line
799 			char lastChar;
800 			while (line_buffer_tail_getc(tty->input_buffer, &lastChar)) {
801 				if (lastChar == controlChars[VEOF]
802 					|| lastChar == controlChars[VEOL]
803 					|| lastChar == '\n' || lastChar == '\r') {
804 					// EOF or end of line -- put it back
805 					line_buffer_putc(tty->input_buffer, lastChar);
806 					break;
807 				}
808 			}
809 			return;
810 		} else if (c == controlChars[VEOF]) {
811 			// we still write the EOF to the stream -- tty_input_read() needs
812 			// to recognize it
813 			tty->pending_eof++;
814 		}
815 	}
816 
817 	// Input character conversions have already been done. What reaches this
818 	// point can directly be written to the line buffer.
819 
820 	line_buffer_putc(tty->input_buffer, c);
821 }
822 
823 
824 static int32
825 tty_readable(struct tty* tty)
826 {
827 	if (!tty->is_master && (tty->settings.termios.c_lflag & ICANON) != 0) {
828 		return line_buffer_readable_line(tty->input_buffer,
829 			tty->settings.termios.c_cc[VEOL],
830 			tty->settings.termios.c_cc[VEOF]);
831 	}
832 
833 	return line_buffer_readable(tty->input_buffer);
834 }
835 
836 
837 /** \brief Notify anyone waiting on events for this TTY.
838  *
839  * The TTY lock must be held.
840  *
841  * Otherwise, the select_pool may be modified or deleted (which happens automatically when the last
842  * item is removed from it).
843  */
844 static void
845 tty_notify_select_event(struct tty* tty, uint8 event)
846 {
847 	TRACE(("tty_notify_select_event(%p, %u)\n", tty, event));
848 
849 	if (tty->select_pool)
850 		notify_select_event_pool(tty->select_pool, event);
851 }
852 
853 
854 /*!	\brief Checks whether bytes can be read from/written to the line buffer of
855 		   the given TTY and notifies the respective queues.
856 
857 	Also sends out \c B_SELECT_READ and \c B_SELECT_WRITE events as needed.
858 
859 	The TTY and the request lock must be held.
860 
861 	\param tty The TTY.
862 	\param otherTTY The connected TTY.
863 */
864 static void
865 tty_notify_if_available(struct tty* tty, struct tty* otherTTY,
866 	bool notifySelect)
867 {
868 	if (!tty)
869 		return;
870 
871 	// Check, if something is readable (depending on whether canonical input
872 	// processing is enabled).
873 	int32 readable = tty_readable(tty);
874 	if (readable > 0) {
875 		// if nobody is waiting send select events, otherwise notify the waiter
876 		if (!tty->reader_queue.IsEmpty())
877 			tty->reader_queue.NotifyFirst(readable);
878 		else if (notifySelect)
879 			tty_notify_select_event(tty, B_SELECT_READ);
880 	}
881 
882 	int32 writable = line_buffer_writable(tty->input_buffer);
883 	if (writable > 0) {
884 		// if nobody is waiting send select events, otherwise notify the waiter
885 		if (!tty->writer_queue.IsEmpty()) {
886 			tty->writer_queue.NotifyFirst(writable);
887 		} else if (notifySelect) {
888 			if (otherTTY && otherTTY->open_count > 0)
889 				tty_notify_select_event(otherTTY, B_SELECT_WRITE);
890 		}
891 	}
892 }
893 
894 
895 /*!	\brief Performs input character conversion and writes the result to
896 		\a buffer.
897 	\param tty The master tty.
898 	\param c The input character.
899 	\param buffer The buffer to which to write the converted character.
900 	\param _bytesNeeded The number of bytes needed in the target tty's
901 		line buffer.
902 	\return \c true, if the character shall be processed further, \c false, if
903 		it shall be skipped.
904 */
905 static bool
906 process_input_char(struct tty* tty, char c, char* buffer,
907 	size_t* _bytesNeeded)
908 {
909 	tcflag_t flags = tty->settings.termios.c_iflag;
910 
911 	// signals
912 	if (tty->settings.termios.c_lflag & ISIG) {
913 		if (c == tty->settings.termios.c_cc[VINTR]
914 			|| c == tty->settings.termios.c_cc[VQUIT]
915 			|| c == tty->settings.termios.c_cc[VSUSP]) {
916 			*buffer = c;
917 			*_bytesNeeded = 0;
918 			return true;
919 		}
920 	}
921 
922 	// canonical input characters
923 	if (tty->settings.termios.c_lflag & ICANON) {
924 		if (c == tty->settings.termios.c_cc[VERASE]
925 			|| c == tty->settings.termios.c_cc[VKILL]) {
926 			*buffer = c;
927 			*_bytesNeeded = 0;
928 			return true;
929 		}
930 	}
931 
932 	// convert chars
933 	if (c == '\r') {
934 		if (flags & IGNCR)		// ignore CR
935 			return false;
936 		if (flags & ICRNL)		// CR -> NL
937 			c = '\n';
938 	} else if (c == '\n') {
939 		if (flags & INLCR)		// NL -> CR
940 			c = '\r';
941 	} else if ((flags & ISTRIP)	!= 0)	// strip off eighth bit
942 		c &= 0x7f;
943 
944 	*buffer = c;
945 	*_bytesNeeded = 1;
946 	return true;
947 }
948 
949 
950 /*!	\brief Performs output character conversion and writes the result to
951 		\a buffer.
952 	\param tty The master tty.
953 	\param c The output character.
954 	\param buffer The buffer to which to write the converted character(s).
955 	\param _bytesWritten The number of bytes written to the output buffer
956 		(max 3).
957 	\param echoed \c true if the output char to be processed has been echoed
958 		from the input.
959 */
960 static void
961 process_output_char(struct tty* tty, char c, char* buffer,
962 	size_t* _bytesWritten, bool echoed)
963 {
964 	tcflag_t flags = tty->settings.termios.c_oflag;
965 
966 	if (flags & OPOST) {
967 		if (echoed && c == tty->settings.termios.c_cc[VERASE]) {
968 			if (tty->settings.termios.c_lflag & ECHOE) {
969 				// ERASE -> BS SPACE BS
970 				buffer[0] = CTRL('H');
971 				buffer[1] = ' ';
972 				buffer[2] = CTRL('H');;
973 				*_bytesWritten = 3;
974 				return;
975 			}
976 		} else if (echoed && c == tty->settings.termios.c_cc[VKILL]) {
977 			if (!(tty->settings.termios.c_lflag & ECHOK)) {
978 				// don't echo KILL
979 				*_bytesWritten = 0;
980 				return;
981 			}
982 		} else if (echoed && c == tty->settings.termios.c_cc[VEOF]) {
983 			// don't echo EOF
984 			*_bytesWritten = 0;
985 			return;
986 		} else if (c == '\n') {
987 			if (echoed && !(tty->settings.termios.c_lflag & ECHONL)) {
988 				// don't echo NL
989 				*_bytesWritten = 0;
990 				return;
991 			}
992 			if (flags & ONLCR) {			// NL -> CR-NL
993 				buffer[0] = '\r';
994 				buffer[1] = '\n';
995 				*_bytesWritten = 2;
996 				return;
997 			}
998 		} else if (c == '\r') {
999 			if (flags & OCRNL) {			// CR -> NL
1000 				c = '\n';
1001 			} else if (flags & ONLRET) {	// NL also does RET, ignore CR
1002 				*_bytesWritten = 0;
1003 				return;
1004 			} else if (flags & ONOCR) {		// don't output CR at column 0
1005 				// TODO: We can't decide that here.
1006 			}
1007 		} else {
1008 			if (flags & OLCUC)				// lower case -> upper case
1009 				c = toupper(c);
1010 		}
1011 	}
1012 
1013 	*buffer = c;
1014 	*_bytesWritten = 1;
1015 }
1016 
1017 
1018 static status_t
1019 tty_write_to_tty_master_unsafe(tty_cookie* sourceCookie, const char* data,
1020 	size_t* _length)
1021 {
1022 	struct tty* source = sourceCookie->tty;
1023 	struct tty* target = sourceCookie->other_tty;
1024 	size_t length = *_length;
1025 	size_t bytesWritten = 0;
1026 	uint32 mode = sourceCookie->open_mode;
1027 	bool dontBlock = (mode & O_NONBLOCK) != 0;
1028 
1029 	// bail out, if source is already closed
1030 	TTYReference sourceTTYReference(sourceCookie);
1031 	if (!sourceTTYReference.IsLocked())
1032 		return B_FILE_ERROR;
1033 
1034 	if (length == 0)
1035 		return B_OK;
1036 
1037 	WriterLocker locker(sourceCookie);
1038 
1039 	// if the target is not open, fail now
1040 	if (target->open_count <= 0)
1041 		return B_FILE_ERROR;
1042 
1043 	bool echo = (source->settings.termios.c_lflag & ECHO) != 0;
1044 
1045 	TRACE(("tty_write_to_tty_master(source = %p, target = %p, "
1046 		"length = %lu%s)\n", source, target, length,
1047 		(echo ? ", echo mode" : "")));
1048 
1049 	// Make sure we are first in the writer queue(s) and AvailableBytes() is
1050 	// initialized.
1051 	status_t status = locker.AcquireWriter(dontBlock, 0);
1052 	if (status != B_OK) {
1053 		*_length = 0;
1054 		return status;
1055 	}
1056 	size_t writable = locker.AvailableBytes();
1057 	size_t writtenSinceLastNotify = 0;
1058 
1059 	while (bytesWritten < length) {
1060 		// fetch next char and do input processing
1061 		char c;
1062 		size_t bytesNeeded;
1063 		if (!process_input_char(source, *data, &c, &bytesNeeded)) {
1064 			// input char shall be skipped
1065 			data++;
1066 			bytesWritten++;
1067 			continue;
1068 		}
1069 
1070 		// If in echo mode, we do the output conversion and need to update
1071 		// the needed bytes count.
1072 		char echoBuffer[3];
1073 		size_t echoBytes = 0;
1074 		if (echo) {
1075 			process_output_char(source, c, echoBuffer, &echoBytes, true);
1076 			if (echoBytes > bytesNeeded)
1077 				bytesNeeded = echoBytes;
1078 		}
1079 
1080 		// If there's not enough space to write what we have, we need to wait
1081 		// until it is available.
1082 		if (writable < bytesNeeded) {
1083 			if (writtenSinceLastNotify > 0) {
1084 				tty_notify_if_available(target, source, true);
1085 				if (echo)
1086 					tty_notify_if_available(source, target, true);
1087 				writtenSinceLastNotify = 0;
1088 			}
1089 
1090 			status = locker.AcquireWriter(dontBlock, bytesNeeded);
1091 			if (status != B_OK) {
1092 				*_length = bytesWritten;
1093 				return status;
1094 			}
1095 
1096 			writable = locker.AvailableBytes();
1097 
1098 			// XXX: do we need to support VMIN & VTIME for write() ?
1099 
1100 			// We need to restart the loop, since the termios flags might have
1101 			// changed in the meantime (while we've unlocked the tty). Note,
1102 			// that we don't re-get "echo" -- maybe we should.
1103 			continue;
1104 		}
1105 
1106 		// write the bytes
1107 		tty_input_putc_locked(target, c);
1108 
1109 		if (echo) {
1110 			for (size_t i = 0; i < echoBytes; i++)
1111 				line_buffer_putc(source->input_buffer, echoBuffer[i]);
1112 		}
1113 
1114 		writable -= bytesNeeded;
1115 		data++;
1116 		bytesWritten++;
1117 		writtenSinceLastNotify++;
1118 	}
1119 
1120 	return B_OK;
1121 }
1122 
1123 
1124 static status_t
1125 tty_write_to_tty_slave_unsafe(tty_cookie* sourceCookie, const char* data,
1126 	size_t* _length)
1127 {
1128 	struct tty* target = sourceCookie->other_tty;
1129 	size_t length = *_length;
1130 	size_t bytesWritten = 0;
1131 	uint32 mode = sourceCookie->open_mode;
1132 	bool dontBlock = (mode & O_NONBLOCK) != 0;
1133 
1134 	// bail out, if source is already closed
1135 	TTYReference sourceTTYReference(sourceCookie);
1136 	if (!sourceTTYReference.IsLocked())
1137 		return B_FILE_ERROR;
1138 
1139 	if (length == 0)
1140 		return B_OK;
1141 
1142 	WriterLocker locker(sourceCookie);
1143 
1144 	// if the target is not open, fail now
1145 	if (target->open_count <= 0)
1146 		return B_FILE_ERROR;
1147 
1148 	TRACE(("tty_write_to_tty_slave(source = %p, target = %p, length = %lu)\n",
1149 		sourceCookie->tty, target, length));
1150 
1151 	// Make sure we are first in the writer queue(s) and AvailableBytes() is
1152 	// initialized.
1153 	status_t status = locker.AcquireWriter(dontBlock, 0);
1154 	if (status != B_OK) {
1155 		*_length = 0;
1156 		return status;
1157 	}
1158 	size_t writable = locker.AvailableBytes();
1159 	size_t writtenSinceLastNotify = 0;
1160 
1161 	while (bytesWritten < length) {
1162 		// fetch next char and do output processing
1163 		char buffer[3];
1164 		size_t bytesNeeded;
1165 		process_output_char(target, *data, buffer, &bytesNeeded, false);
1166 
1167 		// If there's not enough space to write what we have, we need to wait
1168 		// until it is available.
1169 		if (writable < bytesNeeded) {
1170 			if (writtenSinceLastNotify > 0) {
1171 				tty_notify_if_available(target, sourceCookie->tty, true);
1172 				writtenSinceLastNotify = 0;
1173 			}
1174 
1175 			status = locker.AcquireWriter(dontBlock, bytesNeeded);
1176 			if (status != B_OK) {
1177 				*_length = bytesWritten;
1178 				return status;
1179 			}
1180 
1181 			writable = locker.AvailableBytes();
1182 
1183 			// We need to restart the loop, since the termios flags might have
1184 			// changed in the meantime (while we've unlocked the tty).
1185 			continue;
1186 		}
1187 
1188 		// write the bytes
1189 		for (size_t i = 0; i < bytesNeeded; i++)
1190 			line_buffer_putc(target->input_buffer, buffer[i]);
1191 
1192 		writable -= bytesNeeded;
1193 		data++;
1194 		bytesWritten++;
1195 		writtenSinceLastNotify++;
1196 	}
1197 
1198 	return B_OK;
1199 }
1200 
1201 
1202 static status_t
1203 tty_write_to_tty_master(tty_cookie* sourceCookie, const void* _buffer,
1204 	size_t* _length)
1205 {
1206 	const char* buffer = (const char*)_buffer;
1207 	size_t bytesRemaining = *_length;
1208 	*_length = 0;
1209 
1210 	while (bytesRemaining > 0) {
1211 		// copy data to stack
1212 		char safeBuffer[256];
1213 		size_t toWrite = min_c(sizeof(safeBuffer), bytesRemaining);
1214 		status_t error = user_memcpy(safeBuffer, buffer, toWrite);
1215 		if (error != B_OK)
1216 			return error;
1217 
1218 		// write them
1219 		size_t written = toWrite;
1220 		error = tty_write_to_tty_master_unsafe(sourceCookie, safeBuffer,
1221 			&written);
1222 		if (error != B_OK)
1223 			return error;
1224 
1225 		buffer += written;
1226 		bytesRemaining -= written;
1227 		*_length += written;
1228 
1229 		if (written < toWrite)
1230 			return B_OK;
1231 	}
1232 
1233 	return B_OK;
1234 }
1235 
1236 
1237 static status_t
1238 tty_write_to_tty_slave(tty_cookie* sourceCookie, const void* _buffer,
1239 	size_t* _length)
1240 {
1241 	const char* buffer = (const char*)_buffer;
1242 	size_t bytesRemaining = *_length;
1243 	*_length = 0;
1244 
1245 	while (bytesRemaining > 0) {
1246 		// copy data to stack
1247 		char safeBuffer[256];
1248 		size_t toWrite = min_c(sizeof(safeBuffer), bytesRemaining);
1249 		status_t error = user_memcpy(safeBuffer, buffer, toWrite);
1250 		if (error != B_OK)
1251 			return error;
1252 
1253 		// write them
1254 		size_t written = toWrite;
1255 		error = tty_write_to_tty_slave_unsafe(sourceCookie, safeBuffer,
1256 			&written);
1257 		if (error != B_OK)
1258 			return error;
1259 
1260 		buffer += written;
1261 		bytesRemaining -= written;
1262 		*_length += written;
1263 
1264 		if (written < toWrite)
1265 			return B_OK;
1266 	}
1267 
1268 	return B_OK;
1269 }
1270 
1271 
1272 // #pragma mark - public API
1273 
1274 
1275 struct tty*
1276 tty_create(tty_service_func func, struct tty* master)
1277 {
1278 	struct tty* tty = new(std::nothrow) (struct tty);
1279 	if (tty == NULL)
1280 		return NULL;
1281 
1282 	if (master == NULL) {
1283 		tty->is_master = true;
1284 		tty->lock = new(std::nothrow) recursive_lock;
1285 		if (tty->lock == NULL) {
1286 			delete tty;
1287 			return NULL;
1288 		}
1289 		recursive_lock_init(tty->lock, "tty lock");
1290 	} else {
1291 		tty->is_master = false;
1292 		tty->lock = master->lock;
1293 	}
1294 
1295 	tty->ref_count = 0;
1296 	tty->open_count = 0;
1297 	tty->opened_count = 0;
1298 	tty->select_pool = NULL;
1299 	tty->pending_eof = 0;
1300 	tty->hardware_bits = 0;
1301 
1302 	reset_tty_settings(tty->settings);
1303 
1304 	if (init_line_buffer(tty->input_buffer, TTY_BUFFER_SIZE) < B_OK) {
1305 		if (tty->is_master) {
1306 			recursive_lock_destroy(tty->lock);
1307 			delete tty->lock;
1308 		}
1309 		delete tty;
1310 		return NULL;
1311 	}
1312 
1313 	tty->service_func = func;
1314 
1315 	return tty;
1316 }
1317 
1318 
1319 void
1320 tty_destroy(struct tty* tty)
1321 {
1322 	TRACE(("tty_destroy(%p)\n", tty));
1323 	uninit_line_buffer(tty->input_buffer);
1324 	delete_select_sync_pool(tty->select_pool);
1325 	if (tty->is_master) {
1326 		recursive_lock_destroy(tty->lock);
1327 		delete tty->lock;
1328 	}
1329 
1330 	delete tty;
1331 }
1332 
1333 
1334 tty_cookie*
1335 tty_create_cookie(struct tty* tty, struct tty* otherTTY, uint32 openMode)
1336 {
1337 	tty_cookie* cookie = new(std::nothrow) tty_cookie;
1338 	if (cookie == NULL)
1339 		return NULL;
1340 
1341 	cookie->blocking_semaphore = create_sem(0, "wait for tty close");
1342 	if (cookie->blocking_semaphore < 0) {
1343 		delete cookie;
1344 		return NULL;
1345 	}
1346 
1347 	cookie->tty = tty;
1348 	cookie->other_tty = otherTTY;
1349 	cookie->open_mode = openMode;
1350 	cookie->thread_count = 0;
1351 	cookie->closed = false;
1352 
1353 
1354 	RecursiveLocker locker(cookie->tty->lock);
1355 
1356 	// add to the TTY's cookie list
1357 	tty->cookies.Add(cookie);
1358 	tty->open_count++;
1359 	tty->ref_count++;
1360 	tty->opened_count++;
1361 
1362 	return cookie;
1363 }
1364 
1365 
1366 void
1367 tty_close_cookie(tty_cookie* cookie)
1368 {
1369 	MutexLocker locker(gTTYCookieLock);
1370 
1371 	// Already closed? This can happen for slaves that have been closed when
1372 	// the master was closed.
1373 	if (cookie->closed)
1374 		return;
1375 
1376 	// set the cookie's `closed' flag
1377 	cookie->closed = true;
1378 	bool unblock = (cookie->thread_count > 0);
1379 
1380 	// unblock blocking threads
1381 	if (unblock) {
1382 		cookie->tty->reader_queue.NotifyError(cookie, B_FILE_ERROR);
1383 		cookie->tty->writer_queue.NotifyError(cookie, B_FILE_ERROR);
1384 
1385 		if (cookie->other_tty->open_count > 0) {
1386 			cookie->other_tty->reader_queue.NotifyError(cookie, B_FILE_ERROR);
1387 			cookie->other_tty->writer_queue.NotifyError(cookie, B_FILE_ERROR);
1388 		}
1389 	}
1390 
1391 	locker.Unlock();
1392 
1393 	// wait till all blocking (and now unblocked) threads have left the
1394 	// critical code
1395 	if (unblock) {
1396 		TRACE(("tty_close_cookie(): cookie %p, there're still pending "
1397 			"operations, acquire blocking sem %" B_PRId32 "\n", cookie,
1398 			cookie->blocking_semaphore));
1399 
1400 		acquire_sem(cookie->blocking_semaphore);
1401 	}
1402 
1403 	// For the removal of the cookie acquire the TTY's lock. This ensures, that
1404 	// cookies will not be removed from a TTY (or added -- cf. add_tty_cookie())
1405 	// as long as the TTY's lock is being held.
1406 	RecursiveLocker ttyLocker(cookie->tty->lock);
1407 
1408 	// remove the cookie from the TTY's cookie list
1409 	cookie->tty->cookies.Remove(cookie);
1410 
1411 	// close the tty, if no longer used
1412 	if (--cookie->tty->open_count == 0) {
1413 		// The last cookie of this tty has been closed. We're going to close
1414 		// the TTY and need to unblock all write requests before. There should
1415 		// be no read requests, since only a cookie of this TTY issues those.
1416 		// We do this by first notifying all queued requests of the error
1417 		// condition. We then clear the line buffer for the TTY and queue
1418 		// an own request.
1419 
1420 		// Notify the other TTY first; it doesn't accept any read/writes
1421 		// while there is only one end.
1422 		cookie->other_tty->reader_queue.NotifyError(B_FILE_ERROR);
1423 		cookie->other_tty->writer_queue.NotifyError(B_FILE_ERROR);
1424 
1425 		RecursiveLocker requestLocker(gTTYRequestLock);
1426 
1427 		// we only need to do all this, if the writer queue is not empty
1428 		if (!cookie->tty->writer_queue.IsEmpty()) {
1429 	 		// notify the blocking writers
1430 			cookie->tty->writer_queue.NotifyError(B_FILE_ERROR);
1431 
1432 			// enqueue our request
1433 			RequestOwner requestOwner;
1434 			requestOwner.Enqueue(cookie, &cookie->tty->writer_queue);
1435 
1436 			requestLocker.Unlock();
1437 
1438 			// clear the line buffer
1439 			clear_line_buffer(cookie->tty->input_buffer);
1440 
1441 			ttyLocker.Unlock();
1442 
1443 			// wait for our turn
1444 			requestOwner.Wait(false);
1445 
1446 			// re-lock
1447 			ttyLocker.Lock();
1448 			requestLocker.Lock();
1449 
1450 			// dequeue our request
1451 			requestOwner.Dequeue();
1452 		}
1453 
1454 		requestLocker.Unlock();
1455 
1456 		// notify a select write event on the other tty, if we've closed this tty
1457 		if (cookie->other_tty->open_count > 0)
1458 			tty_notify_select_event(cookie->other_tty, B_SELECT_WRITE);
1459 	}
1460 }
1461 
1462 
1463 void
1464 tty_destroy_cookie(tty_cookie* cookie)
1465 {
1466 	RecursiveLocker locker(cookie->tty->lock);
1467 	cookie->tty->ref_count--;
1468 	locker.Unlock();
1469 
1470 	if (cookie->blocking_semaphore >= 0)
1471 		delete_sem(cookie->blocking_semaphore);
1472 
1473 	delete cookie;
1474 }
1475 
1476 
1477 status_t
1478 tty_read(tty_cookie* cookie, void* _buffer, size_t* _length)
1479 {
1480 	char* buffer = (char*)_buffer;
1481 	struct tty* tty = cookie->tty;
1482 	uint32 mode = cookie->open_mode;
1483 	bool dontBlock = (mode & O_NONBLOCK) != 0;
1484 	size_t length = *_length;
1485 	bool canon = true;
1486 	bigtime_t timeout = dontBlock ? 0 : B_INFINITE_TIMEOUT;
1487 	bigtime_t interCharTimeout = 0;
1488 	size_t bytesNeeded = 1;
1489 
1490 	TRACE(("tty_input_read(tty = %p, length = %lu, mode = %" B_PRIu32 ")\n",
1491 		tty, length, mode));
1492 
1493 	if (length == 0)
1494 		return B_OK;
1495 
1496 	// bail out, if the TTY is already closed
1497 	TTYReference ttyReference(cookie);
1498 	if (!ttyReference.IsLocked())
1499 		return B_FILE_ERROR;
1500 
1501 	ReaderLocker locker(cookie);
1502 
1503 	// handle raw mode
1504 	if ((!tty->is_master) && ((tty->settings.termios.c_lflag & ICANON) == 0)) {
1505 		canon = false;
1506 		if (!dontBlock) {
1507 			// Non-blocking mode. Handle VMIN and VTIME.
1508 			bytesNeeded = tty->settings.termios.c_cc[VMIN];
1509 			bigtime_t vtime = tty->settings.termios.c_cc[VTIME] * 100000;
1510 			TRACE(("tty_input_read: icanon vmin %lu, vtime %" B_PRIdBIGTIME
1511 				"us\n", bytesNeeded, vtime));
1512 
1513 			if (bytesNeeded == 0) {
1514 				// In this case VTIME specifies a relative total timeout. We
1515 				// have no inter-char timeout, though.
1516 				timeout = vtime;
1517 			} else {
1518 				// VTIME specifies the inter-char timeout. 0 is indefinitely.
1519 				if (vtime == 0)
1520 					interCharTimeout = B_INFINITE_TIMEOUT;
1521 				else
1522 					interCharTimeout = vtime;
1523 
1524 				if (bytesNeeded > length)
1525 					bytesNeeded = length;
1526 			}
1527 		}
1528 	}
1529 
1530 	status_t status;
1531 	*_length = 0;
1532 
1533 	do {
1534 		TRACE(("tty_input_read: AcquireReader(%" B_PRIdBIGTIME "us, %ld)\n",
1535 			timeout, bytesNeeded));
1536 		status = locker.AcquireReader(timeout, bytesNeeded);
1537 		size_t toRead = locker.AvailableBytes();
1538 		if (status != B_OK && toRead == 0) {
1539 			TRACE(("tty_input_read() AcquireReader failed\n"));
1540 			break;
1541 		}
1542 
1543 		if (toRead > length)
1544 			toRead = length;
1545 
1546 		bool _hitEOF = false;
1547 		bool* hitEOF = canon && tty->pending_eof > 0 ? &_hitEOF : NULL;
1548 
1549 		ssize_t bytesRead = line_buffer_user_read(tty->input_buffer, buffer,
1550 			toRead, tty->settings.termios.c_cc[VEOF], hitEOF);
1551 		if (bytesRead < 0) {
1552 			status = bytesRead;
1553 			break;
1554 		}
1555 
1556 		buffer += bytesRead;
1557 		length -= bytesRead;
1558 		*_length += bytesRead;
1559 		bytesNeeded = (size_t)bytesRead > bytesNeeded
1560 			? 0 : bytesNeeded - bytesRead;
1561 
1562 		// we hit an EOF char -- bail out, whatever amount of data we have
1563 		if (hitEOF && *hitEOF) {
1564 			tty->pending_eof--;
1565 			break;
1566 		}
1567 
1568 		// Once we have read something reset the timeout to the inter-char
1569 		// timeout, if applicable.
1570 		if (!dontBlock && !canon && *_length > 0)
1571 			timeout = interCharTimeout;
1572 	} while (bytesNeeded > 0);
1573 
1574 	if (status == B_WOULD_BLOCK || status == B_TIMED_OUT) {
1575 		// In non-blocking non-canonical-input-processing mode never return
1576 		// timeout errors. Just return 0, if nothing has been read.
1577 		if (!dontBlock && !canon)
1578 			status = B_OK;
1579 	}
1580 
1581 	TRACE(("tty_input_read() status 0x%" B_PRIx32 "\n", status));
1582 
1583 	return *_length == 0 ? status : B_OK;
1584 }
1585 
1586 
1587 status_t
1588 tty_write(tty_cookie* sourceCookie, const void* _buffer, size_t* _length)
1589 {
1590 	if (sourceCookie->tty->is_master)
1591 		return tty_write_to_tty_master(sourceCookie, _buffer, _length);
1592 
1593 	return tty_write_to_tty_slave(sourceCookie, _buffer, _length);
1594 }
1595 
1596 
1597 status_t
1598 tty_control(tty_cookie* cookie, uint32 op, void* buffer, size_t length)
1599 {
1600 	struct tty* tty = cookie->tty;
1601 
1602 	// bail out, if already closed
1603 	TTYReference ttyReference(cookie);
1604 	if (!ttyReference.IsLocked())
1605 		return B_FILE_ERROR;
1606 
1607 	TRACE(("tty_ioctl: tty %p, op %" B_PRIu32 ", buffer %p, length %"
1608 		B_PRIuSIZE "\n", tty, op, buffer, length));
1609 	RecursiveLocker locker(tty->lock);
1610 
1611 	switch (op) {
1612 		// blocking/non-blocking mode
1613 
1614 		case B_SET_BLOCKING_IO:
1615 			cookie->open_mode &= ~O_NONBLOCK;
1616 			return B_OK;
1617 		case B_SET_NONBLOCKING_IO:
1618 			cookie->open_mode |= O_NONBLOCK;
1619 			return B_OK;
1620 
1621 		// get and set TTY attributes
1622 
1623 		case TCGETA:
1624 			TRACE(("tty: get attributes\n"));
1625 			return user_memcpy(buffer, &tty->settings.termios,
1626 				sizeof(struct termios));
1627 
1628 		case TCSETA:
1629 		case TCSETAW:
1630 		case TCSETAF:
1631 		{
1632 			TRACE(("tty: set attributes (iflag = %" B_PRIx32 ", oflag = %"
1633 				B_PRIx32 ", cflag = %" B_PRIx32 ", lflag = %" B_PRIx32 ")\n",
1634 				tty->settings.termios.c_iflag, tty->settings.termios.c_oflag,
1635 				tty->settings.termios.c_cflag,
1636 				tty->settings.termios.c_lflag));
1637 
1638 			status_t status = user_memcpy(&tty->settings.termios, buffer,
1639 				sizeof(struct termios));
1640 			if (status != B_OK)
1641 				return status;
1642 
1643 			tty->service_func(tty, TTYSETMODES, &tty->settings.termios,
1644 				sizeof(struct termios));
1645 			return status;
1646 		}
1647 
1648 		// get and set window size
1649 
1650 		case TIOCGWINSZ:
1651 			TRACE(("tty: get window size\n"));
1652 			return user_memcpy(buffer, &tty->settings.window_size,
1653 				sizeof(struct winsize));
1654 
1655 		case TIOCSWINSZ:
1656 		{
1657 			uint16 oldColumns = tty->settings.window_size.ws_col;
1658 			uint16 oldRows = tty->settings.window_size.ws_row;
1659 
1660 			TRACE(("tty: set window size\n"));
1661 			if (user_memcpy(&tty->settings.window_size, buffer,
1662 					sizeof(struct winsize)) < B_OK) {
1663 				return B_BAD_ADDRESS;
1664 			}
1665 
1666 			// send a signal only if the window size has changed
1667 			if ((oldColumns != tty->settings.window_size.ws_col
1668 					|| oldRows != tty->settings.window_size.ws_row)
1669 				&& tty->settings.pgrp_id != 0) {
1670 				send_signal(-tty->settings.pgrp_id, SIGWINCH);
1671 			}
1672 
1673 			return B_OK;
1674 		}
1675 
1676 		case FIONREAD:
1677 		{
1678 			int toRead = 0;
1679 
1680 			// release the mutex and grab a read lock
1681 			locker.Unlock();
1682 			ReaderLocker readLocker(cookie);
1683 
1684 			status_t status = readLocker.AcquireReader(0, 1);
1685 			if (status == B_OK)
1686 				toRead = readLocker.AvailableBytes();
1687 			else if (status != B_WOULD_BLOCK)
1688 				return status;
1689 
1690 			if (user_memcpy(buffer, &toRead, sizeof(int)) != B_OK)
1691 				return B_BAD_ADDRESS;
1692 
1693 			return B_OK;
1694 		}
1695 
1696 		case TCXONC:			// Unix, but even Linux doesn't handle it
1697 			//dprintf("tty: unsupported TCXONC\n");
1698 			break;
1699 
1700 		case TCSETDTR:
1701 		case TCSETRTS:
1702 		case TIOCMSET:
1703 		{
1704 			// control line state setting, we only support DTR and RTS
1705 			int value;
1706 			if (user_memcpy(&value, buffer, sizeof(value)) != B_OK)
1707 				return B_BAD_ADDRESS;
1708 
1709 			bool result = true;
1710 			bool dtr = (op == TCSETDTR  && value != 0)
1711 				|| (op == TIOCMSET && (value & TIOCM_DTR) != 0);
1712 			if (op == TCSETDTR || op == TIOCMSET)
1713 				result &= tty->service_func(tty, TTYSETDTR, &dtr, sizeof(dtr));
1714 
1715 			bool rts = (op == TCSETRTS && value != 0)
1716 				|| (op == TIOCMSET && (value & TIOCM_RTS) != 0);
1717 			if (op == TCSETRTS || op == TIOCMSET)
1718 				result &= tty->service_func(tty, TTYSETRTS, &rts, sizeof(rts));
1719 
1720 			return result ? B_OK : B_ERROR;
1721 		}
1722 
1723 		case TCGETBITS:
1724 		case TIOCMGET:
1725 		{
1726 			tty->service_func(tty, TTYGETSIGNALS, NULL, 0);
1727 			int bits = tty->hardware_bits;
1728 			return user_memcpy(buffer, &bits, sizeof(bits));
1729 		}
1730 
1731 		case TIOCMBIS:
1732 		case TIOCMBIC:
1733 		{
1734 			// control line state setting, we only support DTR and RTS
1735 			int value;
1736 			if (user_memcpy(&value, buffer, sizeof(value)) != B_OK)
1737 				return B_BAD_ADDRESS;
1738 
1739 			bool result = true;
1740 			bool dtr = (op == TIOCMBIS);
1741 			if (value & TIOCM_DTR)
1742 				result &= tty->service_func(tty, TTYSETDTR, &dtr, sizeof(dtr));
1743 
1744 			bool rts = (op == TIOCMBIS);
1745 			if (value & TIOCM_RTS)
1746 				result &= tty->service_func(tty, TTYSETRTS, &rts, sizeof(rts));
1747 
1748 			return result ? B_OK : B_ERROR;
1749 		}
1750 
1751 		case TIOCSBRK:
1752 		case TIOCCBRK:
1753 		case TCSBRK:
1754 		{
1755 			bool set;
1756 			if (op == TIOCSBRK)
1757 				set = true;
1758 			else if (op == TIOCCBRK)
1759 				set = false;
1760 			else {
1761 				int value = (int)(uintptr_t)buffer;
1762 				set = value != 0;
1763 			}
1764 
1765 			if (tty->service_func(tty, TTYSETBREAK, &set, sizeof(set)))
1766 				return B_OK;
1767 
1768 			return B_ERROR;
1769 		}
1770 
1771 		case TCFLSH:
1772 		{
1773 			int value = (int)(uintptr_t)buffer;
1774 			if (value & TCOFLUSH) {
1775 				struct tty* otherTTY = cookie->other_tty;
1776 				if (otherTTY->open_count <= 0)
1777 					return B_ERROR;
1778 
1779 				clear_line_buffer(otherTTY->input_buffer);
1780 			}
1781 
1782 			if (value & TCIFLUSH)
1783 				clear_line_buffer(tty->input_buffer);
1784 
1785 			if (tty->service_func(tty, TTYFLUSH, &value, sizeof(value)))
1786 				return B_OK;
1787 
1788 			return B_ERROR;
1789 		}
1790 	}
1791 
1792 	TRACE(("tty: unsupported opcode %" B_PRIu32 "\n", op));
1793 	return B_BAD_VALUE;
1794 }
1795 
1796 
1797 status_t
1798 tty_select(tty_cookie* cookie, uint8 event, uint32 ref, selectsync* sync)
1799 {
1800 	struct tty* tty = cookie->tty;
1801 
1802 	TRACE(("tty_select(cookie = %p, event = %u, ref = %" B_PRIu32 ", sync = "
1803 		"%p)\n", cookie, event, ref, sync));
1804 
1805 	// we don't support all kinds of events
1806 	if (event < B_SELECT_READ || event > B_SELECT_ERROR)
1807 		return B_BAD_VALUE;
1808 
1809 	// if the TTY is already closed, we notify immediately
1810 	TTYReference ttyReference(cookie);
1811 	if (!ttyReference.IsLocked()) {
1812 		TRACE(("tty_select() done: cookie %p already closed\n", cookie));
1813 
1814 		notify_select_event(sync, event);
1815 		return B_OK;
1816 	}
1817 
1818 	// lock the TTY (allows us to freely access the cookie lists of this and
1819 	// the other TTY)
1820 	RecursiveLocker ttyLocker(tty->lock);
1821 
1822 	// get the other TTY -- needed for `write' events
1823 	struct tty* otherTTY = cookie->other_tty;
1824 	if (otherTTY->open_count <= 0)
1825 		otherTTY = NULL;
1826 
1827 	// add the event to the TTY's pool
1828 	status_t error = add_select_sync_pool_entry(&tty->select_pool, sync, event);
1829 	if (error != B_OK) {
1830 		TRACE(("tty_select() done: add_select_sync_pool_entry() failed: %"
1831 			B_PRIx32 "\n", error));
1832 
1833 		return error;
1834 	}
1835 
1836 	// finally also acquire the request mutex, for access to the reader/writer
1837 	// queues
1838 	RecursiveLocker requestLocker(gTTYRequestLock);
1839 
1840 	// check, if the event is already present
1841 	switch (event) {
1842 		case B_SELECT_READ:
1843 			if (tty->reader_queue.IsEmpty() && tty_readable(tty) > 0)
1844 				notify_select_event(sync, event);
1845 			break;
1846 
1847 		case B_SELECT_WRITE:
1848 		{
1849 			// writes go to the other TTY
1850 			if (!otherTTY) {
1851 				notify_select_event(sync, event);
1852 				break;
1853 			}
1854 
1855 			// In case input is echoed, we have to check, whether we can
1856 			// currently can write to our TTY as well.
1857 			bool echo = (tty->is_master
1858 				&& tty->settings.termios.c_lflag & ECHO);
1859 
1860 			if (otherTTY->writer_queue.IsEmpty()
1861 				&& line_buffer_writable(otherTTY->input_buffer) > 0) {
1862 				if (!echo
1863 					|| (tty->writer_queue.IsEmpty()
1864 						&& line_buffer_writable(tty->input_buffer) > 0)) {
1865 					notify_select_event(sync, event);
1866 				}
1867 			}
1868 			break;
1869 		}
1870 
1871 		case B_SELECT_ERROR:
1872 		default:
1873 			break;
1874 	}
1875 
1876 	return B_OK;
1877 }
1878 
1879 
1880 status_t
1881 tty_deselect(tty_cookie* cookie, uint8 event, selectsync* sync)
1882 {
1883 	struct tty* tty = cookie->tty;
1884 
1885 	TRACE(("tty_deselect(cookie = %p, event = %u, sync = %p)\n", cookie, event,
1886 		sync));
1887 
1888 	// we don't support all kinds of events
1889 	if (event < B_SELECT_READ || event > B_SELECT_ERROR)
1890 		return B_BAD_VALUE;
1891 
1892 	// lock the TTY (guards the select sync pool, among other things)
1893 	RecursiveLocker ttyLocker(tty->lock);
1894 
1895 	return remove_select_sync_pool_entry(&tty->select_pool, sync, event);
1896 }
1897 
1898 
1899 status_t
1900 tty_hardware_signal(tty_cookie* cookie, int signal, bool set)
1901 {
1902 	int bit = 0;
1903 
1904 	switch (signal) {
1905 		case TTYHWDCD:
1906 			bit = TCGB_DCD;
1907 			break;
1908 		case TTYHWCTS:
1909 			bit = TCGB_CTS;
1910 			break;
1911 		case TTYHWDSR:
1912 			bit = TCGB_DSR;
1913 			break;
1914 		case TTYHWRI:
1915 			bit = TCGB_RI;
1916 			break;
1917 
1918 		default:
1919 			return B_BAD_VALUE;
1920 	}
1921 
1922 	if (set)
1923 		cookie->tty->hardware_bits |= bit;
1924 	else
1925 		cookie->tty->hardware_bits &= ~bit;
1926 
1927 	return B_ERROR;
1928 }
1929