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