1 /* 2 ** Copyright (C) 2002-2008 Christophe Kalt 3 ** 4 ** This file is part of shmux, 5 ** see the LICENSE file for details on your rights. 6 */ 7 8 #include "os.h" 9 10 #include <fcntl.h> 11 #include <sys/ioctl.h> 12 #if defined(HAVE_TERMCAP_H) 13 # include <termcap.h> 14 #elif defined(HAVE_CURSES_H) 15 # include <curses.h> 16 # if defined(HAVE_TERM_H) && defined(sun) 17 # include <term.h> 18 # endif 19 #endif 20 #include <termios.h> 21 #include <signal.h> 22 #include <stdarg.h> 23 24 #include "term.h" 25 26 static char const rcsid[] = "@(#)$Id$"; 27 28 extern char *myname; 29 30 static int targets, internalmsgs, debugmsgs, padding, mypid; 31 static struct termios origt, shmuxt; 32 static int otty, etty, CO, got_sigwin, ttyin; 33 static char *MD, /* bold */ 34 *ME, /* turn off bold (and more) */ 35 *LE, /* move cursor left one position */ 36 *CE, /* clear to end of line */ 37 *CR, /* carriage return */ 38 *NL; /* newline character if not \n */ 39 static char status[512]; 40 static FILE *ttyout; 41 42 static void shmux_signal(int); 43 static void tty_init(int); 44 static int putchar2(int); 45 static int putchar3(int); 46 static void gprint(char *, char, char *, va_list); 47 48 /* 49 ** shmux_signal 50 ** signal handler 51 */ 52 static void 53 shmux_signal(sig) 54 int sig; 55 { 56 if (sig == SIGWINCH || sig == SIGCONT) 57 got_sigwin += 1; 58 if (sig == SIGCONT) 59 { 60 if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &shmuxt) < 0) 61 /* Calling eprint from here isn't so smart.. */ 62 eprint("tcsetattr() failed: %s", strerror(errno)); 63 } 64 if (sig == SIGINT || sig == SIGQUIT || sig == SIGABRT || sig == SIGTERM) 65 { 66 /* restore original tty settings */ 67 if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &origt) < 0) 68 /* Calling eprint from here isn't so smart.. */ 69 eprint("tcsetattr() failed: %s", strerror(errno)); 70 /* Resend the signal to ourselves */ 71 kill(getpid(), sig); 72 } 73 /* Nothing done for SIGTSTP, except ignoring it. */ 74 } 75 76 /* 77 ** term_init: 78 ** Initialize terminal handling system. 79 */ 80 void 81 term_init(maxlen, prefix, progress, internal, debug, interactive) 82 int maxlen, prefix, progress, internal, debug, interactive; 83 { 84 static char termcap[2048], area[1024]; 85 char *term, *ptr; 86 struct sigaction sa; 87 88 assert( maxlen != 0 ); 89 90 padding = prefix*maxlen; 91 targets = prefix; 92 internalmsgs = internal; 93 debugmsgs = debug; 94 95 mypid = getpid(); 96 97 /* Input initialization */ 98 ttyin = -1; 99 100 if (interactive != 0) 101 { 102 if (isatty(fileno(stdin)) == 1) 103 ttyin = fileno(stdin); 104 else 105 ttyin = open("/dev/tty", O_RDONLY, 0); 106 } 107 108 /* Output initializations */ 109 110 LE = NULL; 111 CE = NULL; 112 CR = "\r"; 113 NL = "\n"; 114 status[0] = '\0'; 115 116 otty = isatty(fileno(stdout)); 117 etty = isatty(fileno(stderr)); 118 119 if (otty == 1) 120 ttyout = stdout; 121 else if (etty == 1) 122 ttyout = stderr; 123 else 124 ttyout = fopen("/dev/tty", "a"); 125 126 term = getenv("TERM"); 127 128 if (debugmsgs != 0) 129 fprintf(stdout, 130 "%*s$ itty[%d] ttyin[%d] otty[%d] etty[%d] ttyout[%d] TERM[%s]\n", 131 padding, myname, isatty(fileno(stdin)), ttyin, 132 otty, etty, fileno(ttyout), (term != NULL) ? term : ""); 133 134 if (term == NULL) 135 { 136 if (progress != 0 && ttyout != NULL) 137 fprintf(stderr, "%*s: TERM variable is not set!\n", 138 padding, myname); 139 tty_init(interactive); 140 return; 141 } 142 if (tgetent(termcap, term) < 1) 143 { 144 if (progress != 0 && ttyout != NULL) 145 fprintf(stderr, "%*s: No TERMCAP entry for ``%s''.\n", 146 padding, myname, term); 147 tty_init(interactive); 148 return; 149 } 150 151 ptr = area; 152 153 /* Get terminal width and setup signal handler to keep track of it. */ 154 if ((CO = tgetnum("co")) == -1) CO = 80; 155 sigemptyset(&sa.sa_mask); 156 sa.sa_flags = 0; 157 sa.sa_handler = shmux_signal; 158 sigaction(SIGWINCH, &sa, NULL); 159 sigaction(SIGCONT, &sa, NULL); 160 got_sigwin = 0; 161 term_size(); 162 163 MD = tgetstr("md", &ptr); 164 if (MD != NULL) 165 { 166 ME = tgetstr("me", &ptr); 167 if (ME == NULL) 168 MD = NULL; 169 } 170 else 171 ME = NULL; 172 CR = tgetstr("cr", &ptr); 173 if (CR == NULL) 174 CR = "\r"; /* Let's just assume.. */ 175 NL = tgetstr("nl", &ptr); 176 if (NL == NULL) 177 NL = "\n"; /* As per specification. */ 178 LE = tgetstr("le", &ptr); 179 CE = tgetstr("ce", &ptr); 180 if (progress == 0) 181 CE = NULL; 182 183 if (CE == NULL && progress != 0 && ttyout != NULL) 184 fprintf(stderr, "%*s: Terminal ``%s'' is too dumb for the progress status bar! (no ce)\n", padding, myname, term); 185 186 tty_init(interactive); 187 } 188 189 /* 190 ** term_size: 191 ** Query /dev/tty to find its size 192 */ 193 void 194 term_size(void) 195 { 196 #if defined(TIOCGWINSZ) 197 struct winsize ws; 198 199 if (ttyout == NULL) 200 return; 201 if (ioctl(fileno(ttyout), TIOCGWINSZ, &ws) < 0) 202 { 203 eprint("ioctl(tty, TIOCGWINSZ) failed: %s", strerror(errno)); 204 return; 205 } 206 if (ws.ws_col > 0) 207 { 208 CO = ws.ws_col; 209 dprint("window seems to have %d columns.", CO); 210 } 211 #endif 212 } 213 214 /* 215 ** tty_init 216 ** /dev/tty initialization 217 */ 218 static void 219 tty_init(interactive) 220 int interactive; 221 { 222 if (ttyin < 0) 223 { 224 dprint("No input tty available."); 225 return; 226 } 227 if (ttyout == NULL) 228 { 229 dprint("No output tty available."); 230 ttyin = -1; 231 return; 232 } 233 234 if (tcgetattr(ttyin, &origt) < 0) /* save original tty settings */ 235 { 236 eprint("tcgetattr() failed: %s", strerror(errno)); 237 ttyin = -1; 238 } 239 else if (tcgetpgrp(ttyin) != getpgrp()) 240 ttyin = -1; /* Running in the background */ 241 else 242 { 243 shmuxt = origt; 244 shmuxt.c_lflag &= ~(ICANON|ECHO); /* no echo or canonical processing */ 245 #if !defined(BROKEN_POLL) 246 shmuxt.c_cc[VMIN] = 1; /* no buffering */ 247 #else 248 shmuxt.c_cc[VMIN] = 0; /* no blocking */ 249 #endif 250 shmuxt.c_cc[VTIME] = 0; /* no delaying */ 251 if (tcsetattr(ttyin, TCSANOW, &shmuxt) == 0) 252 { 253 struct sigaction sa; 254 255 atexit(tty_restore); 256 257 /* 258 ** Catch signals which require reinitializing or restoring 259 ** tty settings 260 */ 261 sigemptyset(&sa.sa_mask); 262 sa.sa_flags = 0; 263 sa.sa_handler = shmux_signal; 264 sigaction(SIGTSTP, &sa, NULL); 265 /* Only need to catch the following signals once, then we die. */ 266 sa.sa_flags = SA_RESETHAND; 267 sigaction(SIGINT, &sa, NULL); 268 sigaction(SIGQUIT, &sa, NULL); 269 sigaction(SIGABRT, &sa, NULL); 270 sigaction(SIGTERM, &sa, NULL); 271 272 dprint("Input tty initialized (0x%lX -> 0x%lX)", 273 origt.c_lflag, shmuxt.c_lflag); 274 } 275 else 276 { 277 eprint("tcsetattr() failed: %s", strerror(errno)); 278 ttyin = -1; 279 } 280 } 281 282 if (interactive != 0 && ttyin == -1) 283 { 284 fprintf(ttyout, 285 "%*s: Input unavailable, interactive mode is disabled.\n", 286 padding, myname); 287 fflush(ttyout); 288 } 289 } 290 291 /* 292 ** tty_fd 293 ** Returns the file descriptor associated with the tty input 294 */ 295 int 296 tty_fd(void) 297 { 298 return ttyin; 299 } 300 301 /* 302 ** tty_restore 303 ** Restores /dev/tty original settings 304 */ 305 void 306 tty_restore(void) 307 { 308 if (getpid() != mypid) 309 return; 310 /* restore original tty settings */ 311 if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &origt) < 0) 312 eprint("tcsetattr() failed: %s", strerror(errno)); 313 ttyin = -1; 314 } 315 316 /* 317 ** term_togglemsg 318 ** Toggles verbose mode 319 */ 320 int 321 term_togglemsg(void) 322 { 323 internalmsgs = 1 - internalmsgs; 324 return internalmsgs; 325 } 326 327 /* 328 ** term_debugmsg 329 ** Toggles debug messages 330 */ 331 int 332 term_toggledbg(void) 333 { 334 debugmsgs = 1 - debugmsgs; 335 return debugmsgs; 336 } 337 338 /* 339 ** sprint: 340 ** progress status printf wrapper responsible for displaying a string 341 ** on a pseudo status line, leaving the cursor at the beginning of the 342 ** line so that it can easily be overwritten afterwards. 343 */ 344 void 345 sprint(char *format, ...) 346 { 347 char *ch; 348 int bold; 349 350 if (CE == NULL || ttyout == NULL) 351 return; 352 353 if (got_sigwin > 0) 354 { 355 got_sigwin = 0; 356 term_size(); 357 } 358 359 if (format != NULL) 360 { 361 va_list va; 362 char tmp[512]; 363 364 va_start(va, format); 365 vsnprintf(tmp, 512, format, va); 366 va_end(va); 367 tmp[CO-1] = '\0'; 368 if (strcmp(tmp, status) == 0) 369 return; 370 strlcpy(status, tmp, sizeof(status)); 371 } 372 373 bold = 0; 374 ch = status; 375 while (*ch != '\0') 376 { 377 if (*ch == '\a' && bold == 0) 378 { 379 if (otty == 1 && MD != NULL) 380 { 381 tputs(MD, 0, putchar3); 382 bold = 1; 383 } 384 } 385 else 386 { 387 if ((*ch == ' ' || *ch == '\a') && bold == 1) 388 { 389 tputs(ME, 0, putchar3); 390 bold = 0; 391 } 392 fprintf(ttyout, "%c", *ch); 393 } 394 ch += 1; 395 } 396 397 tputs(CE, 0, putchar3); 398 tputs(CR, 0, putchar3); 399 fflush(ttyout); 400 } 401 402 /* 403 ** putchar2: 404 ** Same as putchar, for stderr. 405 */ 406 static int 407 putchar2(c) 408 int c; 409 { 410 return fputc(c, stderr); 411 } 412 413 /* 414 ** putchar3: 415 ** Same as putchar, for tty (either stdout or stderr). 416 */ 417 static int 418 putchar3(c) 419 int c; 420 { 421 return fputc(c, ttyout); 422 } 423 424 /* 425 ** uprint: 426 ** printf wrapper for messages destined to the user (e.g. /dev/tty) 427 */ 428 void 429 uprint(char *format, ...) 430 { 431 va_list va; 432 433 assert( ttyout != NULL ); 434 435 if (CE != NULL) 436 tputs(CE, 0, putchar3); 437 438 fprintf(ttyout, ">> "); 439 va_start(va, format); 440 vfprintf(ttyout, format, va); 441 va_end(va); 442 tputs(NL, 0, putchar3); 443 fflush(ttyout); 444 445 sprint(NULL); 446 } 447 448 /* 449 ** uprompt: 450 ** prompt the user (e.g. /dev/tty) 451 */ 452 char * 453 uprompt(char *format, ...) 454 { 455 va_list va; 456 static char input[1024]; 457 int pos; 458 459 if (CE != NULL) tputs(CE, 0, putchar3); 460 461 /* Display the prompt. */ 462 fprintf(ttyout, ">> "); 463 va_start(va, format); 464 vfprintf(ttyout, format, va); 465 va_end(va); 466 fprintf(ttyout, " "); 467 fflush(ttyout); 468 469 /* Read/process user input */ 470 pos = -1; 471 while (1) 472 { 473 switch (read(ttyin, input+pos+1, 1)) 474 { 475 case -1: 476 eprint("Unexpected read(/dev/tty) error: %s", strerror(errno)); 477 pos = -2; 478 break; 479 case 0: 480 eprint("Unexpected empty read(/dev/tty) result"); 481 pos = -2; 482 break; 483 default: 484 pos += 1; 485 break; 486 } 487 if (pos < 0) 488 break; 489 490 if (input[pos] == origt.c_cc[VERASE]) 491 { 492 pos -= 1; 493 if (pos >= 0) 494 { 495 pos -= 1; 496 if (LE != NULL) tputs(LE, 0, putchar3); 497 if (CE != NULL) tputs(CE, 0, putchar3); 498 } 499 } 500 else if (input[pos] == origt.c_cc[VKILL]) 501 { 502 pos -= 1; 503 while (pos >= 0) 504 { 505 pos -= 1; 506 if (LE != NULL) tputs(LE, 0, putchar3); 507 } 508 if (CE != NULL) tputs(CE, 0, putchar3); 509 } 510 else if (input[pos] == '\n') 511 { 512 input[pos] = '\0'; 513 break; 514 } 515 else 516 { 517 if (pos < 1023) 518 { 519 fprintf(ttyout, "%c", input[pos]); 520 if (CE != NULL) tputs(CE, 0, putchar3); /* Useful after a multiline VKILL */ 521 } 522 else 523 pos -= 1; 524 } 525 fflush(ttyout); 526 } 527 528 tputs(NL, 0, putchar3); 529 fflush(ttyout); 530 531 sprint(NULL); 532 533 if (pos == -2) 534 return NULL; 535 input[pos+1] = '\0'; 536 return input; 537 } 538 539 /* 540 ** gprint: 541 ** generic (private) printf wrapper used by (most of) the public wrappers 542 ** to display a line with the proper indentation. 543 */ 544 static void 545 gprint(char *prefix, char separator, char *format, va_list va) 546 { 547 FILE *std; 548 int (*pc)(int); 549 550 if (CE != NULL) 551 { 552 tputs(CE, 0, putchar3); 553 fflush(ttyout); 554 } 555 556 std = stdout; pc = putchar; 557 if (separator == MSG_STDERR || separator == MSG_STDERRTRUNC) 558 { 559 std = stderr; pc = putchar2; 560 if (etty == 1 && MD != NULL) 561 tputs(MD, 0, pc); 562 } 563 564 if ((prefix != NULL && targets != 0) || (prefix == myname)) 565 fprintf(std, "%*s%c ", padding, prefix, separator); 566 vfprintf(std, format, va); 567 if (std == stderr && etty == 1 && ME != NULL) 568 tputs(ME, 0, pc); 569 tputs(NL, 0, pc); 570 fflush(std); 571 572 sprint(NULL); 573 } 574 575 /* 576 ** dprint: 577 ** debug printf wrapper 578 */ 579 void 580 dprint(char *format, ...) 581 { 582 va_list va; 583 584 if (debugmsgs == 0) 585 return; 586 587 va_start(va, format); 588 gprint(myname, MSG_DEBUG, format, va); 589 va_end(va); 590 } 591 592 /* 593 ** iprint: 594 ** internal printf wrapper 595 */ 596 void 597 iprint(char *format, ...) 598 { 599 va_list va; 600 601 if (internalmsgs == 0) 602 return; 603 604 va_start(va, format); 605 gprint(myname, MSG_STDOUT, format, va); 606 va_end(va); 607 } 608 609 /* 610 ** eprint: 611 ** internal printf wrapper for errors 612 */ 613 void 614 eprint(char *format, ...) 615 { 616 va_list va; 617 618 va_start(va, format); 619 gprint(myname, MSG_STDERR, format, va); 620 va_end(va); 621 } 622 623 /* 624 ** tprint: 625 ** general printf wrapper 626 */ 627 void 628 tprint(char *target, char separator, char *format, ...) 629 { 630 va_list va; 631 632 va_start(va, format); 633 gprint(target, separator, format, va); 634 va_end(va); 635 } 636 637 /* 638 ** nprint: 639 ** normal printf wrapper.. total overkill 640 */ 641 void 642 nprint(char *format, ...) 643 { 644 va_list va; 645 646 va_start(va, format); 647 vprintf(format, va); 648 va_end(va); 649 tputs(NL, 0, putchar); 650 } 651