1 /**************************************************************************** 2 * Copyright 2019-2023,2024 Thomas E. Dickey * 3 * Copyright 2016,2017 Free Software Foundation, Inc. * 4 * * 5 * Permission is hereby granted, free of charge, to any person obtaining a * 6 * copy of this software and associated documentation files (the * 7 * "Software"), to deal in the Software without restriction, including * 8 * without limitation the rights to use, copy, modify, merge, publish, * 9 * distribute, distribute with modifications, sublicense, and/or sell * 10 * copies of the Software, and to permit persons to whom the Software is * 11 * furnished to do so, subject to the following conditions: * 12 * * 13 * The above copyright notice and this permission notice shall be included * 14 * in all copies or substantial portions of the Software. * 15 * * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 * * 24 * Except as contained in this notice, the name(s) of the above copyright * 25 * holders shall not be used in advertising or otherwise to promote the * 26 * sale, use or other dealings in this Software without prior written * 27 * authorization. * 28 ****************************************************************************/ 29 30 /**************************************************************************** 31 * Author: Thomas E. Dickey * 32 ****************************************************************************/ 33 34 #include <reset_cmd.h> 35 #include <tty_settings.h> 36 37 #include <errno.h> 38 #include <stdio.h> 39 #include <fcntl.h> 40 41 #if HAVE_SIZECHANGE 42 # if !defined(sun) || !TERMIOS 43 # if HAVE_SYS_IOCTL_H 44 # include <sys/ioctl.h> 45 # endif 46 # endif 47 #endif 48 49 #if NEED_PTEM_H 50 /* they neglected to define struct winsize in termios.h -- it is only 51 in termio.h */ 52 #include <sys/stream.h> 53 #include <sys/ptem.h> 54 #endif 55 56 MODULE_ID("$Id: reset_cmd.c,v 1.37 2024/04/08 17:29:34 tom Exp $") 57 58 /* 59 * SCO defines TIOCGSIZE and the corresponding struct. Other systems (SunOS, 60 * Solaris, IRIX) define TIOCGWINSZ and struct winsize. 61 */ 62 #ifdef TIOCGSIZE 63 # define IOCTL_GET_WINSIZE TIOCGSIZE 64 # define IOCTL_SET_WINSIZE TIOCSSIZE 65 # define STRUCT_WINSIZE struct ttysize 66 # define WINSIZE_ROWS(n) n.ts_lines 67 # define WINSIZE_COLS(n) n.ts_cols 68 #else 69 # ifdef TIOCGWINSZ 70 # define IOCTL_GET_WINSIZE TIOCGWINSZ 71 # define IOCTL_SET_WINSIZE TIOCSWINSZ 72 # define STRUCT_WINSIZE struct winsize 73 # define WINSIZE_ROWS(n) n.ws_row 74 # define WINSIZE_COLS(n) n.ws_col 75 # endif 76 #endif 77 78 #define set_flags(target, mask) target |= mask 79 #define clear_flags(target, mask) target &= ~((unsigned)(mask)) 80 81 static FILE *my_file; 82 83 static bool use_reset = FALSE; /* invoked as reset */ 84 static bool use_init = FALSE; /* invoked as init */ 85 86 static GCC_NORETURN void 87 failed(const char *msg) 88 { 89 int code = errno; 90 91 (void) fprintf(stderr, "%s: %s: %s\n", _nc_progname, msg, strerror(code)); 92 restore_tty_settings(); 93 (void) fprintf(my_file, "\n"); 94 fflush(my_file); 95 ExitProgram(ErrSystem(code)); 96 /* NOTREACHED */ 97 } 98 99 static bool 100 cat_file(char *file) 101 { 102 FILE *fp; 103 size_t nr; 104 char buf[BUFSIZ]; 105 bool sent = FALSE; 106 107 if (file != 0) { 108 if ((fp = safe_fopen(file, "r")) == 0) 109 failed(file); 110 111 while ((nr = fread(buf, sizeof(char), sizeof(buf), fp)) != 0) { 112 if (fwrite(buf, sizeof(char), nr, my_file) != nr) { 113 failed(file); 114 } 115 sent = TRUE; 116 } 117 fclose(fp); 118 } 119 return sent; 120 } 121 122 static int 123 out_char(int c) 124 { 125 return putc(c, my_file); 126 } 127 128 /************************************************************************** 129 * Mode-setting logic 130 **************************************************************************/ 131 132 /* some BSD systems have these built in, some systems are missing 133 * one or more definitions. The safest solution is to override unless the 134 * commonly-altered ones are defined. 135 */ 136 #if !(defined(CERASE) && defined(CINTR) && defined(CKILL) && defined(CQUIT)) 137 #undef CEOF 138 #undef CERASE 139 #undef CINTR 140 #undef CKILL 141 #undef CLNEXT 142 #undef CRPRNT 143 #undef CQUIT 144 #undef CSTART 145 #undef CSTOP 146 #undef CSUSP 147 #endif 148 149 /* control-character defaults */ 150 #ifndef CEOF 151 #define CEOF CTRL('D') 152 #endif 153 #ifndef CERASE 154 #define CERASE CTRL('H') 155 #endif 156 #ifndef CINTR 157 #define CINTR 127 /* ^? */ 158 #endif 159 #ifndef CKILL 160 #define CKILL CTRL('U') 161 #endif 162 #ifndef CLNEXT 163 #define CLNEXT CTRL('v') 164 #endif 165 #ifndef CRPRNT 166 #define CRPRNT CTRL('r') 167 #endif 168 #ifndef CQUIT 169 #define CQUIT CTRL('\\') 170 #endif 171 #ifndef CSTART 172 #define CSTART CTRL('Q') 173 #endif 174 #ifndef CSTOP 175 #define CSTOP CTRL('S') 176 #endif 177 #ifndef CSUSP 178 #define CSUSP CTRL('Z') 179 #endif 180 181 #if defined(_POSIX_VDISABLE) 182 #define DISABLED(val) (((_POSIX_VDISABLE != -1) \ 183 && ((val) == _POSIX_VDISABLE)) \ 184 || ((val) <= 0)) 185 #else 186 #define DISABLED(val) ((int)(val) <= 0) 187 #endif 188 189 #define CHK(val, dft) (unsigned char) (DISABLED(val) ? dft : val) 190 191 #define reset_char(item, value) \ 192 tty_settings->c_cc[item] = CHK(tty_settings->c_cc[item], value) 193 194 /* 195 * Simplify ifdefs 196 */ 197 #ifndef BSDLY 198 #define BSDLY 0 199 #endif 200 #ifndef CRDLY 201 #define CRDLY 0 202 #endif 203 #ifndef ECHOCTL 204 #define ECHOCTL 0 205 #endif 206 #ifndef ECHOKE 207 #define ECHOKE 0 208 #endif 209 #ifndef ECHOPRT 210 #define ECHOPRT 0 211 #endif 212 #ifndef FFDLY 213 #define FFDLY 0 214 #endif 215 #ifndef IMAXBEL 216 #define IMAXBEL 0 217 #endif 218 #ifndef IUCLC 219 #define IUCLC 0 220 #endif 221 #ifndef IXANY 222 #define IXANY 0 223 #endif 224 #ifndef NLDLY 225 #define NLDLY 0 226 #endif 227 #ifndef OCRNL 228 #define OCRNL 0 229 #endif 230 #ifndef OFDEL 231 #define OFDEL 0 232 #endif 233 #ifndef OFILL 234 #define OFILL 0 235 #endif 236 #ifndef OLCUC 237 #define OLCUC 0 238 #endif 239 #ifndef ONLCR 240 #define ONLCR 0 241 #endif 242 #ifndef ONLRET 243 #define ONLRET 0 244 #endif 245 #ifndef ONOCR 246 #define ONOCR 0 247 #endif 248 #ifndef OXTABS 249 #define OXTABS 0 250 #endif 251 #ifndef TAB3 252 #define TAB3 0 253 #endif 254 #ifndef TABDLY 255 #define TABDLY 0 256 #endif 257 #ifndef TOSTOP 258 #define TOSTOP 0 259 #endif 260 #ifndef VTDLY 261 #define VTDLY 0 262 #endif 263 #ifndef XCASE 264 #define XCASE 0 265 #endif 266 267 /* 268 * Reset the terminal mode bits to a sensible state. Very useful after 269 * a child program dies in raw mode. 270 */ 271 void 272 reset_tty_settings(int fd, TTY * tty_settings, int noset) 273 { 274 unsigned mask; 275 #ifdef TIOCMGET 276 int modem_bits; 277 #endif 278 279 GET_TTY(fd, tty_settings); 280 281 #ifdef TERMIOS 282 #if defined(VDISCARD) && defined(CDISCARD) 283 reset_char(VDISCARD, CDISCARD); 284 #endif 285 reset_char(VEOF, CEOF); 286 reset_char(VERASE, CERASE); 287 #if defined(VERASE2) && defined(CERASE2) 288 reset_char(VERASE2, CERASE2); 289 #endif 290 #if defined(VFLUSH) && defined(CFLUSH) 291 reset_char(VFLUSH, CFLUSH); 292 #endif 293 reset_char(VINTR, CINTR); 294 reset_char(VKILL, CKILL); 295 #if defined(VLNEXT) && defined(CLNEXT) 296 reset_char(VLNEXT, CLNEXT); 297 #endif 298 reset_char(VQUIT, CQUIT); 299 #if defined(VREPRINT) && defined(CRPRNT) 300 reset_char(VREPRINT, CRPRNT); 301 #endif 302 #if defined(VSTART) && defined(CSTART) 303 reset_char(VSTART, CSTART); 304 #endif 305 #if defined(VSTOP) && defined(CSTOP) 306 reset_char(VSTOP, CSTOP); 307 #endif 308 #if defined(VSUSP) && defined(CSUSP) 309 reset_char(VSUSP, CSUSP); 310 #endif 311 #if defined(VWERASE) && defined(CWERASE) 312 reset_char(VWERASE, CWERASE); 313 #endif 314 315 clear_flags(tty_settings->c_iflag, (IGNBRK 316 | PARMRK 317 | INPCK 318 | ISTRIP 319 | INLCR 320 | IGNCR 321 | IUCLC 322 | IXANY 323 | IXOFF)); 324 325 set_flags(tty_settings->c_iflag, (BRKINT 326 | IGNPAR 327 | ICRNL 328 | IXON 329 | IMAXBEL)); 330 331 clear_flags(tty_settings->c_oflag, (0 332 | OLCUC 333 | OCRNL 334 | ONOCR 335 | ONLRET 336 | OFILL 337 | OFDEL 338 | NLDLY 339 | CRDLY 340 | TABDLY 341 | BSDLY 342 | VTDLY 343 | FFDLY)); 344 345 set_flags(tty_settings->c_oflag, (OPOST 346 | ONLCR)); 347 348 mask = (CSIZE | CSTOPB | PARENB | PARODD); 349 #ifdef TIOCMGET 350 /* leave clocal alone if this appears to use a modem */ 351 if (ioctl(fd, TIOCMGET, &modem_bits) == -1) 352 mask |= CLOCAL; 353 #else 354 /* cannot check - use the behavior from tset */ 355 mask |= CLOCAL; 356 #endif 357 clear_flags(tty_settings->c_cflag, mask); 358 359 set_flags(tty_settings->c_cflag, (CS8 | CREAD)); 360 clear_flags(tty_settings->c_lflag, (ECHONL 361 | NOFLSH 362 | TOSTOP 363 | ECHOPRT 364 | XCASE)); 365 366 set_flags(tty_settings->c_lflag, (ISIG 367 | ICANON 368 | ECHO 369 | ECHOE 370 | ECHOK 371 | ECHOCTL 372 | ECHOKE)); 373 #endif /* TERMIOS */ 374 375 if (!noset) { 376 SET_TTY(fd, tty_settings); 377 } 378 } 379 380 /* 381 * Returns a "good" value for the erase character. This is loosely based on 382 * the BSD4.4 logic. 383 */ 384 static int 385 default_erase(void) 386 { 387 int result; 388 389 if (over_strike 390 && VALID_STRING(key_backspace) 391 && strlen(key_backspace) == 1) { 392 result = key_backspace[0]; 393 } else { 394 result = CERASE; 395 } 396 397 return result; 398 } 399 400 /* 401 * Update the values of the erase, interrupt, and kill characters in the TTY 402 * parameter. 403 * 404 * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase 405 * characters if they're unset, or if we specify them as options. This differs 406 * from BSD 4.4 tset, which always sets erase. 407 */ 408 void 409 set_control_chars(TTY * tty_settings, int my_erase, int my_intr, int my_kill) 410 { 411 #if defined(EXP_WIN32_DRIVER) 412 /* noop */ 413 (void) tty_settings; 414 (void) my_erase; 415 (void) my_intr; 416 (void) my_kill; 417 #else 418 if (DISABLED(tty_settings->c_cc[VERASE]) || my_erase >= 0) { 419 tty_settings->c_cc[VERASE] = UChar((my_erase >= 0) 420 ? my_erase 421 : default_erase()); 422 } 423 424 if (DISABLED(tty_settings->c_cc[VINTR]) || my_intr >= 0) { 425 tty_settings->c_cc[VINTR] = UChar((my_intr >= 0) 426 ? my_intr 427 : CINTR); 428 } 429 430 if (DISABLED(tty_settings->c_cc[VKILL]) || my_kill >= 0) { 431 tty_settings->c_cc[VKILL] = UChar((my_kill >= 0) 432 ? my_kill 433 : CKILL); 434 } 435 #endif 436 } 437 438 /* 439 * Set up various conversions in the TTY parameter, including parity, tabs, 440 * returns, echo, and case, according to the termcap entry. 441 */ 442 void 443 set_conversions(TTY * tty_settings) 444 { 445 #if defined(EXP_WIN32_DRIVER) 446 /* FIXME */ 447 #else 448 set_flags(tty_settings->c_oflag, ONLCR); 449 set_flags(tty_settings->c_iflag, ICRNL); 450 set_flags(tty_settings->c_lflag, ECHO); 451 set_flags(tty_settings->c_oflag, OXTABS); 452 453 /* test used to be tgetflag("NL") */ 454 if (VALID_STRING(newline) && newline[0] == '\n' && !newline[1]) { 455 /* Newline, not linefeed. */ 456 clear_flags(tty_settings->c_oflag, ONLCR); 457 clear_flags(tty_settings->c_iflag, ICRNL); 458 } 459 #if OXTABS 460 /* test used to be tgetflag("pt") */ 461 if (VALID_STRING(set_tab) && VALID_STRING(clear_all_tabs)) 462 clear_flags(tty_settings->c_oflag, OXTABS); 463 #endif /* OXTABS */ 464 set_flags(tty_settings->c_lflag, (ECHOE | ECHOK)); 465 #endif 466 } 467 468 static bool 469 sent_string(const char *s) 470 { 471 bool sent = FALSE; 472 if (VALID_STRING(s)) { 473 tputs(s, 0, out_char); 474 sent = TRUE; 475 } 476 return sent; 477 } 478 479 static bool 480 to_left_margin(void) 481 { 482 if (VALID_STRING(carriage_return)) { 483 sent_string(carriage_return); 484 } else { 485 out_char('\r'); 486 } 487 return TRUE; 488 } 489 490 /* 491 * Set the hardware tabs on the terminal, using the 'ct' (clear all tabs), 492 * 'st' (set one tab) and 'ch' (horizontal cursor addressing) capabilities. 493 * This is done before 'if' and 'is', so they can recover in case of error. 494 * 495 * Return TRUE if we set any tab stops, FALSE if not. 496 */ 497 static bool 498 reset_tabstops(int wide) 499 { 500 if ((init_tabs != 8) 501 && VALID_NUMERIC(init_tabs) 502 && VALID_STRING(set_tab) 503 && VALID_STRING(clear_all_tabs)) { 504 int c; 505 506 to_left_margin(); 507 tputs(clear_all_tabs, 0, out_char); 508 if (init_tabs > 1) { 509 if (init_tabs > wide) 510 init_tabs = (short) wide; 511 for (c = init_tabs; c < wide; c += init_tabs) { 512 fprintf(my_file, "%*s", init_tabs, " "); 513 tputs(set_tab, 0, out_char); 514 } 515 to_left_margin(); 516 } 517 return (TRUE); 518 } 519 return (FALSE); 520 } 521 522 /* Output startup string. */ 523 bool 524 send_init_strings(int fd GCC_UNUSED, TTY * old_settings) 525 { 526 int i; 527 bool need_flush = FALSE; 528 529 (void) old_settings; 530 #if TAB3 531 if (old_settings != 0 && 532 old_settings->c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) { 533 old_settings->c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET); 534 SET_TTY(fd, old_settings); 535 } 536 #endif 537 if (use_reset || use_init) { 538 if (VALID_STRING(init_prog)) { 539 IGNORE_RC(system(init_prog)); 540 } 541 542 need_flush |= sent_string((use_reset && (reset_1string != 0)) 543 ? reset_1string 544 : init_1string); 545 546 need_flush |= sent_string((use_reset && (reset_2string != 0)) 547 ? reset_2string 548 : init_2string); 549 550 if (VALID_STRING(clear_margins)) { 551 need_flush |= sent_string(clear_margins); 552 } 553 #if defined(set_lr_margin) 554 else if (VALID_STRING(set_lr_margin)) { 555 need_flush |= sent_string(TIPARM_2(set_lr_margin, 0, columns - 1)); 556 } 557 #endif 558 #if defined(set_left_margin_parm) && defined(set_right_margin_parm) 559 else if (VALID_STRING(set_left_margin_parm) 560 && VALID_STRING(set_right_margin_parm)) { 561 need_flush |= sent_string(TIPARM_1(set_left_margin_parm, 0)); 562 need_flush |= sent_string(TIPARM_1(set_right_margin_parm, 563 columns - 1)); 564 } 565 #endif 566 else if (VALID_STRING(set_left_margin) 567 && VALID_STRING(set_right_margin)) { 568 need_flush |= to_left_margin(); 569 need_flush |= sent_string(set_left_margin); 570 if (VALID_STRING(parm_right_cursor)) { 571 need_flush |= sent_string(TIPARM_1(parm_right_cursor, 572 columns - 1)); 573 } else { 574 for (i = 0; i < columns - 1; i++) { 575 out_char(' '); 576 need_flush = TRUE; 577 } 578 } 579 need_flush |= sent_string(set_right_margin); 580 need_flush |= to_left_margin(); 581 } 582 583 need_flush |= reset_tabstops(columns); 584 585 need_flush |= cat_file((use_reset && reset_file) ? reset_file : init_file); 586 587 need_flush |= sent_string((use_reset && (reset_3string != 0)) 588 ? reset_3string 589 : init_3string); 590 } 591 592 return need_flush; 593 } 594 595 /* 596 * Tell the user if a control key has been changed from the default value. 597 */ 598 static void 599 show_tty_change(TTY * old_settings, 600 TTY * new_settings, 601 const char *name, 602 int which, 603 unsigned def) 604 { 605 unsigned older = 0, newer = 0; 606 char *p; 607 608 #if defined(EXP_WIN32_DRIVER) 609 /* noop */ 610 (void) old_settings; 611 (void) new_settings; 612 (void) name; 613 (void) which; 614 (void) def; 615 #else 616 newer = new_settings->c_cc[which]; 617 older = old_settings->c_cc[which]; 618 619 if (older == newer && older == def) 620 return; 621 #endif 622 (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to"); 623 624 if (DISABLED(newer)) { 625 (void) fprintf(stderr, "undef.\n"); 626 /* 627 * Check 'delete' before 'backspace', since the key_backspace value 628 * is ambiguous. 629 */ 630 } else if (newer == 0177) { 631 (void) fprintf(stderr, "delete.\n"); 632 } else if ((p = key_backspace) != 0 633 && newer == (unsigned char) p[0] 634 && p[1] == '\0') { 635 (void) fprintf(stderr, "backspace.\n"); 636 } else if (newer < 040) { 637 newer ^= 0100; 638 (void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer)); 639 } else 640 (void) fprintf(stderr, "%c.\n", UChar(newer)); 641 } 642 643 /************************************************************************** 644 * Miscellaneous. 645 **************************************************************************/ 646 647 void 648 reset_start(FILE *fp, bool is_reset, bool is_init) 649 { 650 my_file = fp; 651 use_reset = is_reset; 652 use_init = is_init; 653 } 654 655 void 656 reset_flush(void) 657 { 658 if (my_file != 0) 659 fflush(my_file); 660 } 661 662 void 663 print_tty_chars(TTY * old_settings, TTY * new_settings) 664 { 665 #if defined(EXP_WIN32_DRIVER) 666 /* noop */ 667 #else 668 show_tty_change(old_settings, new_settings, "Erase", VERASE, CERASE); 669 show_tty_change(old_settings, new_settings, "Kill", VKILL, CKILL); 670 show_tty_change(old_settings, new_settings, "Interrupt", VINTR, CINTR); 671 #endif 672 } 673 674 #if HAVE_SIZECHANGE 675 /* 676 * Set window size if not set already, but update our copy of the values if the 677 * size was set. 678 */ 679 void 680 set_window_size(int fd, NCURSES_INT2 *high, NCURSES_INT2 *wide) 681 { 682 STRUCT_WINSIZE win; 683 (void) ioctl(fd, IOCTL_GET_WINSIZE, &win); 684 if (WINSIZE_ROWS(win) == 0 && 685 WINSIZE_COLS(win) == 0) { 686 if (*high > 0 && *wide > 0) { 687 WINSIZE_ROWS(win) = (unsigned short) *high; 688 WINSIZE_COLS(win) = (unsigned short) *wide; 689 (void) ioctl(fd, IOCTL_SET_WINSIZE, &win); 690 } 691 } else if (WINSIZE_ROWS(win) > 0 && 692 WINSIZE_COLS(win) > 0) { 693 *high = (short) WINSIZE_ROWS(win); 694 *wide = (short) WINSIZE_COLS(win); 695 } 696 } 697 #endif 698