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