1 /* pty.c - pseudo terminal driver Author: Kees J. Bot 2 * 30 Dec 1995 3 * PTYs can be seen as a bidirectional pipe with TTY 4 * input and output processing. For example a simple rlogin session: 5 * 6 * keyboard -> rlogin -> in.rld -> /dev/ptypX -> /dev/ttypX -> shell 7 * shell -> /dev/ttypX -> /dev/ptypX -> in.rld -> rlogin -> screen 8 * 9 * This file takes care of copying data between the tty/pty device pairs and 10 * the open/read/write/close calls on the pty devices. The TTY task takes 11 * care of the input and output processing (interrupt, backspace, raw I/O, 12 * etc.) using the pty_slave_read() and pty_slave_write() functions as the 13 * "keyboard" and "screen" functions of the ttypX devices. 14 * Be careful when reading this code, the terms "reading" and "writing" are 15 * used both for the tty (slave) and the pty (master) end of the pseudo tty. 16 * Writes to one end are to be read at the other end and vice-versa. 17 */ 18 19 #include <minix/drivers.h> 20 #include <termios.h> 21 #include <assert.h> 22 #include <sys/termios.h> 23 #include <signal.h> 24 #include "tty.h" 25 26 /* PTY bookkeeping structure, one per pty/tty pair. */ 27 typedef struct pty { 28 tty_t *tty; /* associated TTY structure */ 29 char state; /* flags: busy, closed, ... */ 30 31 /* Read call on master (/dev/ptypX). */ 32 endpoint_t rdcaller; /* process making the call, or NONE if none */ 33 cdev_id_t rdid; /* ID of suspended read request */ 34 cp_grant_id_t rdgrant; /* grant for reader's address space */ 35 size_t rdleft; /* # bytes yet to be read */ 36 size_t rdcum; /* # bytes written so far */ 37 38 /* Write call to master (/dev/ptypX). */ 39 endpoint_t wrcaller; /* process making the call, or NONE if none*/ 40 cdev_id_t wrid; /* ID of suspended write request */ 41 cp_grant_id_t wrgrant; /* grant for writer's address space */ 42 size_t wrleft; /* # bytes yet to be written */ 43 size_t wrcum; /* # bytes written so far */ 44 45 /* Output buffer. */ 46 int ocount; /* # characters in the buffer */ 47 char *ohead, *otail; /* head and tail of the circular buffer */ 48 char obuf[2048]; /* buffer for bytes going to the pty reader */ 49 50 /* select() data. */ 51 unsigned int select_ops; /* Which operations do we want to know about? */ 52 endpoint_t select_proc; /* Who wants to know about it? */ 53 } pty_t; 54 55 #define TTY_ACTIVE 0x01 /* tty is open/active */ 56 #define PTY_ACTIVE 0x02 /* pty is open/active */ 57 #define TTY_CLOSED 0x04 /* tty side has closed down */ 58 #define PTY_CLOSED 0x08 /* pty side has closed down */ 59 60 static pty_t pty_table[NR_PTYS]; /* PTY bookkeeping */ 61 62 static void pty_start(pty_t *pp); 63 static void pty_finish(pty_t *pp); 64 65 static int pty_master_open(devminor_t minor, int access, 66 endpoint_t user_endpt); 67 static int pty_master_close(devminor_t minor); 68 static ssize_t pty_master_read(devminor_t minor, u64_t position, 69 endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags, 70 cdev_id_t id); 71 static ssize_t pty_master_write(devminor_t minor, u64_t position, 72 endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags, 73 cdev_id_t id); 74 static int pty_master_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id); 75 static int pty_master_select(devminor_t minor, unsigned int ops, 76 endpoint_t endpt); 77 78 static struct chardriver pty_master_tab = { 79 .cdr_open = pty_master_open, 80 .cdr_close = pty_master_close, 81 .cdr_read = pty_master_read, 82 .cdr_write = pty_master_write, 83 .cdr_cancel = pty_master_cancel, 84 .cdr_select = pty_master_select 85 }; 86 87 /*===========================================================================* 88 * pty_master_open * 89 *===========================================================================*/ 90 static int pty_master_open(devminor_t minor, int UNUSED(access), 91 endpoint_t UNUSED(user_endpt)) 92 { 93 tty_t *tp; 94 pty_t *pp; 95 96 assert(minor >= PTYPX_MINOR && minor < PTYPX_MINOR + NR_PTYS); 97 98 if ((tp = line2tty(minor)) == NULL) 99 return ENXIO; 100 pp = tp->tty_priv; 101 102 if (pp->state & PTY_ACTIVE) 103 return EIO; 104 105 pp->state |= PTY_ACTIVE; 106 pp->rdcum = 0; 107 pp->wrcum = 0; 108 109 return OK; 110 } 111 112 /*===========================================================================* 113 * pty_master_close * 114 *===========================================================================*/ 115 static int pty_master_close(devminor_t minor) 116 { 117 tty_t *tp; 118 pty_t *pp; 119 120 if ((tp = line2tty(minor)) == NULL) 121 return ENXIO; 122 pp = tp->tty_priv; 123 124 if ((pp->state & (TTY_ACTIVE | TTY_CLOSED)) != TTY_ACTIVE) { 125 pp->state = 0; 126 } else { 127 pp->state |= PTY_CLOSED; 128 sigchar(tp, SIGHUP, 1); 129 } 130 131 return OK; 132 } 133 134 /*===========================================================================* 135 * pty_master_read * 136 *===========================================================================*/ 137 static ssize_t pty_master_read(devminor_t minor, u64_t UNUSED(position), 138 endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags, 139 cdev_id_t id) 140 { 141 tty_t *tp; 142 pty_t *pp; 143 ssize_t r; 144 145 if ((tp = line2tty(minor)) == NULL) 146 return ENXIO; 147 pp = tp->tty_priv; 148 149 /* Check, store information on the reader, do I/O. */ 150 if (pp->state & TTY_CLOSED) 151 return 0; /* EOF */ 152 153 if (pp->rdcaller != NONE || pp->rdleft != 0 || pp->rdcum != 0) 154 return EIO; 155 156 if (size <= 0) 157 return EINVAL; 158 159 pp->rdcaller = endpt; 160 pp->rdid = id; 161 pp->rdgrant = grant; 162 pp->rdleft = size; 163 pty_start(pp); 164 165 handle_events(tp); 166 167 if (pp->rdleft == 0) { 168 pp->rdcaller = NONE; 169 return EDONTREPLY; /* already done */ 170 } 171 172 if (flags & CDEV_NONBLOCK) { 173 r = pp->rdcum > 0 ? pp->rdcum : EAGAIN; 174 pp->rdleft = pp->rdcum = 0; 175 pp->rdcaller = NONE; 176 return r; 177 } 178 179 return EDONTREPLY; /* do suspend */ 180 } 181 182 /*===========================================================================* 183 * pty_master_write * 184 *===========================================================================*/ 185 static ssize_t pty_master_write(devminor_t minor, u64_t UNUSED(position), 186 endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags, 187 cdev_id_t id) 188 { 189 tty_t *tp; 190 pty_t *pp; 191 ssize_t r; 192 193 if ((tp = line2tty(minor)) == NULL) 194 return ENXIO; 195 pp = tp->tty_priv; 196 197 /* Check, store information on the writer, do I/O. */ 198 if (pp->state & TTY_CLOSED) 199 return EIO; 200 201 if (pp->wrcaller != NONE || pp->wrleft != 0 || pp->wrcum != 0) 202 return EIO; 203 204 if (size <= 0) 205 return EINVAL; 206 207 pp->wrcaller = endpt; 208 pp->wrid = id; 209 pp->wrgrant = grant; 210 pp->wrleft = size; 211 212 handle_events(tp); 213 214 if (pp->wrleft == 0) { 215 pp->wrcaller = NONE; 216 return EDONTREPLY; /* already done */ 217 } 218 219 if (flags & CDEV_NONBLOCK) { 220 r = pp->wrcum > 0 ? pp->wrcum : EAGAIN; 221 pp->wrleft = pp->wrcum = 0; 222 pp->wrcaller = NONE; 223 return r; 224 } 225 226 return EDONTREPLY; /* do suspend */ 227 } 228 229 /*===========================================================================* 230 * pty_master_cancel * 231 *===========================================================================*/ 232 static int pty_master_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id) 233 { 234 tty_t *tp; 235 pty_t *pp; 236 int r; 237 238 if ((tp = line2tty(minor)) == NULL) 239 return ENXIO; 240 pp = tp->tty_priv; 241 242 if (pp->rdcaller == endpt && pp->rdid == id) { 243 /* Cancel a read from a PTY. */ 244 r = pp->rdcum > 0 ? pp->rdcum : EINTR; 245 pp->rdleft = pp->rdcum = 0; 246 pp->rdcaller = NONE; 247 return r; 248 } 249 250 if (pp->wrcaller == endpt && pp->wrid == id) { 251 /* Cancel a write to a PTY. */ 252 r = pp->wrcum > 0 ? pp->wrcum : EINTR; 253 pp->wrleft = pp->wrcum = 0; 254 pp->wrcaller = NONE; 255 return r; 256 } 257 258 /* Request not found. */ 259 return EDONTREPLY; 260 } 261 262 /*===========================================================================* 263 * select_try_pty * 264 *===========================================================================*/ 265 static int select_try_pty(tty_t *tp, int ops) 266 { 267 pty_t *pp = tp->tty_priv; 268 int r = 0; 269 270 if (ops & CDEV_OP_WR) { 271 /* Write won't block on error. */ 272 if (pp->state & TTY_CLOSED) r |= CDEV_OP_WR; 273 else if (pp->wrleft != 0 || pp->wrcum != 0) r |= CDEV_OP_WR; 274 else if (tp->tty_incount < buflen(tp->tty_inbuf)) r |= CDEV_OP_WR; 275 } 276 277 if (ops & CDEV_OP_RD) { 278 /* Read won't block on error. */ 279 if (pp->state & TTY_CLOSED) r |= CDEV_OP_RD; 280 else if (pp->rdleft != 0 || pp->rdcum != 0) r |= CDEV_OP_RD; 281 else if (pp->ocount > 0) r |= CDEV_OP_RD; /* Actual data. */ 282 } 283 284 return r; 285 } 286 287 /*===========================================================================* 288 * select_retry_pty * 289 *===========================================================================*/ 290 void select_retry_pty(tty_t *tp) 291 { 292 pty_t *pp = tp->tty_priv; 293 devminor_t minor; 294 int r; 295 296 /* See if the pty side of a pty is ready to return a select. */ 297 if (pp->select_ops && (r = select_try_pty(tp, pp->select_ops))) { 298 minor = PTYPX_MINOR + (int) (pp - pty_table); 299 chardriver_reply_select(pp->select_proc, minor, r); 300 pp->select_ops &= ~r; 301 } 302 } 303 304 /*===========================================================================* 305 * pty_master_select * 306 *===========================================================================*/ 307 static int pty_master_select(devminor_t minor, unsigned int ops, 308 endpoint_t endpt) 309 { 310 tty_t *tp; 311 pty_t *pp; 312 int ready_ops, watch; 313 314 if ((tp = line2tty(minor)) == NULL) 315 return ENXIO; 316 pp = tp->tty_priv; 317 318 watch = (ops & CDEV_NOTIFY); 319 ops &= (CDEV_OP_RD | CDEV_OP_WR | CDEV_OP_ERR); 320 321 ready_ops = select_try_pty(tp, ops); 322 323 ops &= ~ready_ops; 324 if (ops && watch) { 325 pp->select_ops |= ops; 326 pp->select_proc = endpt; 327 } 328 329 return ready_ops; 330 } 331 332 /*===========================================================================* 333 * do_pty * 334 *===========================================================================*/ 335 void do_pty(message *m_ptr, int ipc_status) 336 { 337 /* Process a request for a PTY master (/dev/ptypX) device. */ 338 339 chardriver_process(&pty_master_tab, m_ptr, ipc_status); 340 } 341 342 /*===========================================================================* 343 * pty_slave_write * 344 *===========================================================================*/ 345 static int pty_slave_write(tty_t *tp, int try) 346 { 347 /* (*dev_write)() routine for PTYs. Transfer bytes from the writer on 348 * /dev/ttypX to the output buffer. 349 */ 350 pty_t *pp = tp->tty_priv; 351 int count, ocount, s; 352 353 /* PTY closed down? */ 354 if (pp->state & PTY_CLOSED) { 355 if (try) return 1; 356 if (tp->tty_outleft > 0) { 357 chardriver_reply_task(tp->tty_outcaller, tp->tty_outid, EIO); 358 tp->tty_outleft = tp->tty_outcum = 0; 359 tp->tty_outcaller = NONE; 360 } 361 return 0; 362 } 363 364 /* While there is something to do. */ 365 for (;;) { 366 ocount = buflen(pp->obuf) - pp->ocount; 367 if (try) return (ocount > 0); 368 count = bufend(pp->obuf) - pp->ohead; 369 if (count > ocount) count = ocount; 370 if (count > tp->tty_outleft) count = tp->tty_outleft; 371 if (count == 0 || tp->tty_inhibited) 372 break; 373 374 /* Copy from user space to the PTY output buffer. */ 375 if (tp->tty_outcaller == KERNEL) { 376 /* We're trying to print on kernel's behalf */ 377 memcpy(pp->ohead, (void *) tp->tty_outgrant + tp->tty_outcum, 378 count); 379 } else { 380 if ((s = sys_safecopyfrom(tp->tty_outcaller, tp->tty_outgrant, 381 tp->tty_outcum, (vir_bytes) pp->ohead, 382 count)) != OK) { 383 break; 384 } 385 } 386 387 /* Perform output processing on the output buffer. */ 388 out_process(tp, pp->obuf, pp->ohead, bufend(pp->obuf), &count, &ocount); 389 if (count == 0) break; 390 391 /* Assume echoing messed up by output. */ 392 tp->tty_reprint = TRUE; 393 394 /* Bookkeeping. */ 395 pp->ocount += ocount; 396 if ((pp->ohead += ocount) >= bufend(pp->obuf)) 397 pp->ohead -= buflen(pp->obuf); 398 pty_start(pp); 399 400 tp->tty_outcum += count; 401 if ((tp->tty_outleft -= count) == 0) { 402 /* Output is finished, reply to the writer. */ 403 chardriver_reply_task(tp->tty_outcaller, tp->tty_outid, 404 tp->tty_outcum); 405 tp->tty_outcum = 0; 406 tp->tty_outcaller = NONE; 407 } 408 } 409 pty_finish(pp); 410 return 1; 411 } 412 413 /*===========================================================================* 414 * pty_slave_echo * 415 *===========================================================================*/ 416 static void pty_slave_echo(tty_t *tp, int c) 417 { 418 /* Echo one character. (Like pty_write, but only one character, optionally.) */ 419 420 pty_t *pp = tp->tty_priv; 421 int count, ocount; 422 423 ocount = buflen(pp->obuf) - pp->ocount; 424 if (ocount == 0) return; /* output buffer full */ 425 count = 1; 426 *pp->ohead = c; /* add one character */ 427 428 out_process(tp, pp->obuf, pp->ohead, bufend(pp->obuf), &count, &ocount); 429 if (count == 0) return; 430 431 pp->ocount += ocount; 432 if ((pp->ohead += ocount) >= bufend(pp->obuf)) pp->ohead -= buflen(pp->obuf); 433 pty_start(pp); 434 } 435 436 /*===========================================================================* 437 * pty_start * 438 *===========================================================================*/ 439 static void pty_start(pty_t *pp) 440 { 441 /* Transfer bytes written to the output buffer to the PTY reader. */ 442 int count; 443 444 /* While there are things to do. */ 445 for (;;) { 446 int s; 447 count = bufend(pp->obuf) - pp->otail; 448 if (count > pp->ocount) count = pp->ocount; 449 if (count > pp->rdleft) count = pp->rdleft; 450 if (count == 0) break; 451 452 /* Copy from the output buffer to the readers address space. */ 453 if((s = sys_safecopyto(pp->rdcaller, pp->rdgrant, pp->rdcum, 454 (vir_bytes) pp->otail, count)) != OK) { 455 break; 456 } 457 458 /* Bookkeeping. */ 459 pp->ocount -= count; 460 if ((pp->otail += count) == bufend(pp->obuf)) pp->otail = pp->obuf; 461 pp->rdcum += count; 462 pp->rdleft -= count; 463 } 464 } 465 466 /*===========================================================================* 467 * pty_finish * 468 *===========================================================================*/ 469 static void pty_finish(pty_t *pp) 470 { 471 /* Finish the read request of a PTY reader if there is at least one byte 472 * transferred. 473 */ 474 475 if (pp->rdcum > 0) { 476 chardriver_reply_task(pp->rdcaller, pp->rdid, pp->rdcum); 477 pp->rdleft = pp->rdcum = 0; 478 pp->rdcaller = NONE; 479 } 480 } 481 482 /*===========================================================================* 483 * pty_slave_read * 484 *===========================================================================*/ 485 static int pty_slave_read(tty_t *tp, int try) 486 { 487 /* Offer bytes from the PTY writer for input on the TTY. (Do it one byte at 488 * a time, 99% of the writes will be for one byte, so no sense in being smart.) 489 */ 490 pty_t *pp = tp->tty_priv; 491 char c; 492 493 if (pp->state & PTY_CLOSED) { 494 if (try) return 1; 495 if (tp->tty_inleft > 0) { 496 chardriver_reply_task(tp->tty_incaller, tp->tty_inid, 497 tp->tty_incum); 498 tp->tty_inleft = tp->tty_incum = 0; 499 tp->tty_incaller = NONE; 500 } 501 return 1; 502 } 503 504 if (try) { 505 if (pp->wrleft > 0) 506 return 1; 507 return 0; 508 } 509 510 while (pp->wrleft > 0) { 511 int s; 512 513 /* Transfer one character to 'c'. */ 514 if ((s = sys_safecopyfrom(pp->wrcaller, pp->wrgrant, pp->wrcum, 515 (vir_bytes) &c, 1)) != OK) { 516 printf("pty: safecopy failed (error %d)\n", s); 517 break; 518 } 519 520 /* Input processing. */ 521 if (in_process(tp, &c, 1) == 0) break; 522 523 /* PTY writer bookkeeping. */ 524 pp->wrcum++; 525 if (--pp->wrleft == 0) { 526 chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum); 527 pp->wrcum = 0; 528 pp->wrcaller = NONE; 529 } 530 } 531 532 return 0; 533 } 534 535 /*===========================================================================* 536 * pty_slave_open * 537 *===========================================================================*/ 538 static int pty_slave_open(tty_t *tp, int UNUSED(try)) 539 { 540 /* The tty side has been opened. */ 541 pty_t *pp = tp->tty_priv; 542 543 assert(tp->tty_minor >= TTYPX_MINOR && tp->tty_minor < TTYPX_MINOR + NR_PTYS); 544 545 /* TTY_ACTIVE may already be set, which would indicate that the slave is 546 * reopened after being fully closed while the master is still open. In that 547 * case TTY_CLOSED will also be set, so clear that one. 548 */ 549 pp->state |= TTY_ACTIVE; 550 pp->state &= ~TTY_CLOSED; 551 552 return 0; 553 } 554 555 /*===========================================================================* 556 * pty_slave_close * 557 *===========================================================================*/ 558 static int pty_slave_close(tty_t *tp, int UNUSED(try)) 559 { 560 /* The tty side has closed, so shut down the pty side. */ 561 pty_t *pp = tp->tty_priv; 562 563 if (!(pp->state & PTY_ACTIVE)) return 0; 564 565 if (pp->rdleft > 0) { 566 chardriver_reply_task(pp->rdcaller, pp->rdid, pp->rdcum); 567 pp->rdleft = pp->rdcum = 0; 568 pp->rdcaller = NONE; 569 } 570 571 if (pp->wrleft > 0) { 572 chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum); 573 pp->wrleft = pp->wrcum = 0; 574 pp->wrcaller = NONE; 575 } 576 577 if (pp->state & PTY_CLOSED) pp->state = 0; 578 else pp->state |= TTY_CLOSED; 579 580 return 0; 581 } 582 583 /*===========================================================================* 584 * pty_slave_icancel * 585 *===========================================================================*/ 586 static int pty_slave_icancel(tty_t *tp, int UNUSED(try)) 587 { 588 /* Discard waiting input. */ 589 pty_t *pp = tp->tty_priv; 590 591 if (pp->wrleft > 0) { 592 chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum + pp->wrleft); 593 pp->wrcum = pp->wrleft = 0; 594 pp->wrcaller = NONE; 595 } 596 597 return 0; 598 } 599 600 /*===========================================================================* 601 * pty_slave_ocancel * 602 *===========================================================================*/ 603 static int pty_slave_ocancel(tty_t *tp, int UNUSED(try)) 604 { 605 /* Drain the output buffer. */ 606 pty_t *pp = tp->tty_priv; 607 608 pp->ocount = 0; 609 pp->otail = pp->ohead; 610 611 return 0; 612 } 613 614 /*===========================================================================* 615 * pty_init * 616 *===========================================================================*/ 617 void pty_init(tty_t *tp) 618 { 619 pty_t *pp; 620 int line; 621 622 /* Associate PTY and TTY structures. */ 623 line = tp - tty_table; 624 pp = tp->tty_priv = &pty_table[line]; 625 pp->tty = tp; 626 pp->select_ops = 0; 627 pp->rdcaller = NONE; 628 pp->wrcaller = NONE; 629 630 /* Set up output queue. */ 631 pp->ohead = pp->otail = pp->obuf; 632 633 /* Fill in TTY function hooks. */ 634 tp->tty_devread = pty_slave_read; 635 tp->tty_devwrite = pty_slave_write; 636 tp->tty_echo = pty_slave_echo; 637 tp->tty_icancel = pty_slave_icancel; 638 tp->tty_ocancel = pty_slave_ocancel; 639 tp->tty_open = pty_slave_open; 640 tp->tty_close = pty_slave_close; 641 tp->tty_select_ops = 0; 642 } 643