1/* Work-alike for termcap, plus extra features. 2 Copyright (C) 1985, 86, 93, 94, 95, 2000, 2001 3 Free Software Foundation, Inc. 4 5This program is free software; you can redistribute it and/or modify 6it under the terms of the GNU General Public License as published by 7the Free Software Foundation; either version 2, or (at your option) 8any later version. 9 10This program is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program; see the file COPYING. If not, write to 17the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18Boston, MA 02111-1307, USA. */ 19 20/* Emacs config.h may rename various library functions such as malloc. */ 21#ifdef HAVE_CONFIG_H 22#include <config.h> 23#endif 24 25#ifdef emacs 26 27#include <lisp.h> /* xmalloc is here */ 28/* Get the O_* definitions for open et al. */ 29#include <sys/file.h> 30#ifdef HAVE_FCNTL_H 31#include <fcntl.h> 32#endif 33#ifdef HAVE_UNISTD_H 34#include <unistd.h> 35#endif 36 37#else /* not emacs */ 38 39#ifdef STDC_HEADERS 40#include <stdlib.h> 41#include <string.h> 42#else 43char *getenv (); 44char *malloc (); 45char *realloc (); 46#endif 47 48/* Do this after the include, in case string.h prototypes bcopy. */ 49#if (defined(HAVE_STRING_H) || defined(STDC_HEADERS)) && !defined(bcopy) 50#define bcopy(s, d, n) memcpy ((d), (s), (n)) 51#endif 52 53#ifdef HAVE_UNISTD_H 54#include <unistd.h> 55#endif 56#ifdef _POSIX_VERSION 57#include <fcntl.h> 58#endif 59 60#endif /* not emacs */ 61 62#ifndef NULL 63#define NULL (char *) 0 64#endif 65 66#ifndef O_RDONLY 67#define O_RDONLY 0 68#endif 69 70/* BUFSIZE is the initial size allocated for the buffer 71 for reading the termcap file. 72 It is not a limit. 73 Make it large normally for speed. 74 Make it variable when debugging, so can exercise 75 increasing the space dynamically. */ 76 77#ifndef BUFSIZE 78#ifdef DEBUG 79#define BUFSIZE bufsize 80 81int bufsize = 128; 82#else 83#define BUFSIZE 2048 84#endif 85#endif 86 87#ifndef TERMCAP_FILE 88#define TERMCAP_FILE "/etc/termcap" 89#endif 90 91#ifndef emacs 92static void 93memory_out () 94{ 95 write (2, "virtual memory exhausted\n", 25); 96 exit (1); 97} 98 99static char * 100xmalloc (size) 101 unsigned size; 102{ 103 register char *tem = malloc (size); 104 105 if (!tem) 106 memory_out (); 107 return tem; 108} 109 110static char * 111xrealloc (ptr, size) 112 char *ptr; 113 unsigned size; 114{ 115 register char *tem = realloc (ptr, size); 116 117 if (!tem) 118 memory_out (); 119 return tem; 120} 121#endif /* not emacs */ 122 123/* Looking up capabilities in the entry already found. */ 124 125/* The pointer to the data made by tgetent is left here 126 for tgetnum, tgetflag and tgetstr to find. */ 127static char *term_entry; 128 129static char *tgetst1 (); 130 131/* Search entry BP for capability CAP. 132 Return a pointer to the capability (in BP) if found, 133 0 if not found. */ 134 135static char * 136find_capability (bp, cap) 137 register char *bp, *cap; 138{ 139 for (; *bp; bp++) 140 if (bp[0] == ':' 141 && bp[1] == cap[0] 142 && bp[2] == cap[1]) 143 return &bp[4]; 144 return NULL; 145} 146 147int 148tgetnum (cap) 149 char *cap; 150{ 151 register char *ptr = find_capability (term_entry, cap); 152 if (!ptr || ptr[-1] != '#') 153 return -1; 154 return atoi (ptr); 155} 156 157int 158tgetflag (cap) 159 char *cap; 160{ 161 register char *ptr = find_capability (term_entry, cap); 162 return ptr && ptr[-1] == ':'; 163} 164 165/* Look up a string-valued capability CAP. 166 If AREA is non-null, it points to a pointer to a block in which 167 to store the string. That pointer is advanced over the space used. 168 If AREA is null, space is allocated with `malloc'. */ 169 170char * 171tgetstr (cap, area) 172 char *cap; 173 char **area; 174{ 175 register char *ptr = find_capability (term_entry, cap); 176 if (!ptr || (ptr[-1] != '=' && ptr[-1] != '~')) 177 return NULL; 178 return tgetst1 (ptr, area); 179} 180 181#ifdef IS_EBCDIC_HOST 182/* Table, indexed by a character in range 0200 to 0300 with 0200 subtracted, 183 gives meaning of character following \, or a space if no special meaning. 184 Sixteen characters per line within the string. */ 185 186static char esctab[] 187 = " \057\026 \047\014 \ 188 \025 \015 \ 189 \005 \013 \ 190 "; 191#else 192/* Table, indexed by a character in range 0100 to 0140 with 0100 subtracted, 193 gives meaning of character following \, or a space if no special meaning. 194 Eight characters per line within the string. */ 195 196static char esctab[] 197 = " \007\010 \033\014 \ 198 \012 \ 199 \015 \011 \013 \ 200 "; 201#endif 202 203/* PTR points to a string value inside a termcap entry. 204 Copy that value, processing \ and ^ abbreviations, 205 into the block that *AREA points to, 206 or to newly allocated storage if AREA is NULL. 207 Return the address to which we copied the value, 208 or NULL if PTR is NULL. */ 209 210static char * 211tgetst1 (ptr, area) 212 char *ptr; 213 char **area; 214{ 215 register char *p, *r; 216 register int c; 217 register int size; 218 char *ret; 219 register int c1; 220 221 if (!ptr) 222 return NULL; 223 224 /* `ret' gets address of where to store the string. */ 225 if (!area) 226 { 227 /* Compute size of block needed (may overestimate). */ 228 p = ptr; 229 while ((c = *p++) && c != ':' && c != '\n') 230 ; 231 ret = (char *) xmalloc (p - ptr + 1); 232 } 233 else 234 ret = *area; 235 236 /* Copy the string value, stopping at null or colon. 237 Also process ^ and \ abbreviations. */ 238 p = ptr; 239 r = ret; 240 while ((c = *p++) && c != ':' && c != '\n') 241 { 242 if (c == '^') 243 { 244 c = *p++; 245 if (c == '?') 246 c = 0177; 247 else 248 c &= 037; 249 } 250 else if (c == '\\') 251 { 252 c = *p++; 253 if (c >= '0' && c <= '7') 254 { 255 c -= '0'; 256 size = 0; 257 258 while (++size < 3 && (c1 = *p) >= '0' && c1 <= '7') 259 { 260 c *= 8; 261 c += c1 - '0'; 262 p++; 263 } 264 } 265#ifdef IS_EBCDIC_HOST 266 else if (c >= 0200 && c < 0360) 267 { 268 c1 = esctab[(c & ~0100) - 0200]; 269 if (c1 != ' ') 270 c = c1; 271 } 272#else 273 else if (c >= 0100 && c < 0200) 274 { 275 c1 = esctab[(c & ~040) - 0100]; 276 if (c1 != ' ') 277 c = c1; 278 } 279#endif 280 } 281 *r++ = c; 282 } 283 *r = '\0'; 284 /* Update *AREA. */ 285 if (area) 286 *area = r + 1; 287 return ret; 288} 289 290/* Outputting a string with padding. */ 291 292#ifndef emacs 293short ospeed; 294/* If OSPEED is 0, we use this as the actual baud rate. */ 295int tputs_baud_rate; 296#endif 297char PC; 298 299#ifndef emacs 300/* Actual baud rate if positive; 301 - baud rate / 100 if negative. */ 302 303static int speeds[] = 304 { 305#ifdef VMS 306 0, 50, 75, 110, 134, 150, -3, -6, -12, -18, 307 -20, -24, -36, -48, -72, -96, -192 308#else /* not VMS */ 309 0, 50, 75, 110, 135, 150, -2, -3, -6, -12, 310 -18, -24, -48, -96, -192, -288, -384, -576, -1152 311#endif /* not VMS */ 312 }; 313 314#endif /* not emacs */ 315 316void 317tputs (str, nlines, outfun) 318 register char *str; 319 int nlines; 320 register int (*outfun) (); 321{ 322 register int padcount = 0; 323 register int speed; 324 325#ifdef emacs 326 extern int baud_rate; 327 speed = baud_rate; 328 /* For quite high speeds, convert to the smaller 329 units to avoid overflow. */ 330 if (speed > 10000) 331 speed = - speed / 100; 332#else 333 if (ospeed == 0) 334 speed = tputs_baud_rate; 335 else 336 speed = speeds[ospeed]; 337#endif 338 339 if (!str) 340 return; 341 342 while (*str >= '0' && *str <= '9') 343 { 344 padcount += *str++ - '0'; 345 padcount *= 10; 346 } 347 if (*str == '.') 348 { 349 str++; 350 padcount += *str++ - '0'; 351 } 352 if (*str == '*') 353 { 354 str++; 355 padcount *= nlines; 356 } 357 while (*str) 358 (*outfun) (*str++); 359 360 /* PADCOUNT is now in units of tenths of msec. 361 SPEED is measured in characters per 10 seconds 362 or in characters per .1 seconds (if negative). 363 We use the smaller units for larger speeds to avoid overflow. */ 364 padcount *= speed; 365 padcount += 500; 366 padcount /= 1000; 367 if (speed < 0) 368 padcount = -padcount; 369 else 370 { 371 padcount += 50; 372 padcount /= 100; 373 } 374 375 while (padcount-- > 0) 376 (*outfun) (PC); 377} 378 379/* Finding the termcap entry in the termcap data base. */ 380 381struct termcap_buffer 382 { 383 char *beg; 384 int size; 385 char *ptr; 386 int ateof; 387 int full; 388 }; 389 390/* Forward declarations of static functions. */ 391 392static int scan_file (); 393static char *gobble_line (); 394static int compare_contin (); 395static int name_match (); 396 397#ifdef VMS 398 399#include <rmsdef.h> 400#include <fab.h> 401#include <nam.h> 402 403static int 404valid_filename_p (fn) 405 char *fn; 406{ 407 struct FAB fab = cc$rms_fab; 408 struct NAM nam = cc$rms_nam; 409 char esa[NAM$C_MAXRSS]; 410 411 fab.fab$l_fna = fn; 412 fab.fab$b_fns = strlen(fn); 413 fab.fab$l_nam = &nam; 414 fab.fab$l_fop = FAB$M_NAM; 415 416 nam.nam$l_esa = esa; 417 nam.nam$b_ess = sizeof esa; 418 419 return SYS$PARSE(&fab, 0, 0) == RMS$_NORMAL; 420} 421 422#else /* !VMS */ 423 424#ifdef MSDOS /* MW, May 1993 */ 425static int 426valid_filename_p (fn) 427 char *fn; 428{ 429 return *fn == '/' || fn[1] == ':'; 430} 431#else 432#define valid_filename_p(fn) (*(fn) == '/') 433#endif 434 435#endif /* !VMS */ 436 437/* Find the termcap entry data for terminal type NAME 438 and store it in the block that BP points to. 439 Record its address for future use. 440 441 If BP is null, space is dynamically allocated. 442 443 Return -1 if there is some difficulty accessing the data base 444 of terminal types, 445 0 if the data base is accessible but the type NAME is not defined 446 in it, and some other value otherwise. */ 447 448int 449tgetent (bp, name) 450 char *bp, *name; 451{ 452 register char *termcap_name; 453 register int fd; 454 struct termcap_buffer buf; 455 register char *bp1; 456 char *tc_search_point; 457 char *term; 458 int malloc_size = 0; 459 register int c; 460 char *tcenv = NULL; /* TERMCAP value, if it contains :tc=. */ 461 char *indirect = NULL; /* Terminal type in :tc= in TERMCAP value. */ 462 int filep; 463 464// bonefish: HACK to avoid problems. Our termcap entries are longer than the 465// 2KB that are advised as the size of the buffer to be passed to tgetent(). 466// This leads to nasty problems, so just always allocate a buffer. 467bp = NULL; 468 469#ifdef INTERNAL_TERMINAL 470 /* For the internal terminal we don't want to read any termcap file, 471 so fake it. */ 472 if (!strcmp (name, "internal")) 473 { 474 term = INTERNAL_TERMINAL; 475 if (!bp) 476 { 477 malloc_size = 1 + strlen (term); 478 bp = (char *) xmalloc (malloc_size); 479 } 480 strcpy (bp, term); 481 goto ret; 482 } 483#endif /* INTERNAL_TERMINAL */ 484 485 /* For compatibility with programs like `less' that want to 486 put data in the termcap buffer themselves as a fallback. */ 487 if (bp) 488 term_entry = bp; 489 490 termcap_name = getenv ("TERMCAP"); 491 if (termcap_name && *termcap_name == '\0') 492 termcap_name = NULL; 493#if defined (MSDOS) && !defined (TEST) 494 if (termcap_name && (*termcap_name == '\\' 495 || *termcap_name == '/' 496 || termcap_name[1] == ':')) 497 dostounix_filename(termcap_name); 498#endif 499 500 filep = termcap_name && valid_filename_p (termcap_name); 501 502 /* If termcap_name is non-null and starts with / (in the un*x case, that is), 503 it is a file name to use instead of /etc/termcap. 504 If it is non-null and does not start with /, 505 it is the entry itself, but only if 506 the name the caller requested matches the TERM variable. */ 507 508 if (termcap_name && !filep && !strcmp (name, getenv ("TERM"))) 509 { 510 indirect = tgetst1 (find_capability (termcap_name, "tc"), (char **) 0); 511 if (!indirect) 512 { 513 if (!bp) 514 bp = termcap_name; 515 else 516 strcpy (bp, termcap_name); 517 goto ret; 518 } 519 else 520 { /* It has tc=. Need to read /etc/termcap. */ 521 tcenv = termcap_name; 522 termcap_name = NULL; 523 } 524 } 525 526 if (!termcap_name || !filep) 527 termcap_name = TERMCAP_FILE; 528 529 /* Here we know we must search a file and termcap_name has its name. */ 530 531#ifdef MSDOS 532 fd = open (termcap_name, O_RDONLY|O_TEXT, 0); 533#else 534 fd = open (termcap_name, O_RDONLY, 0); 535#endif 536 if (fd < 0) 537 return -1; 538 539 buf.size = BUFSIZE; 540 /* Add 1 to size to ensure room for terminating null. */ 541 buf.beg = (char *) xmalloc (buf.size + 1); 542 term = indirect ? indirect : name; 543 544 if (!bp) 545 { 546 malloc_size = indirect ? strlen (tcenv) + 1 : buf.size; 547 bp = (char *) xmalloc (malloc_size); 548 } 549 tc_search_point = bp1 = bp; 550 551 if (indirect) 552 /* Copy the data from the environment variable. */ 553 { 554 strcpy (bp, tcenv); 555 bp1 += strlen (tcenv); 556 } 557 558 while (term) 559 { 560 /* Scan the file, reading it via buf, till find start of main entry. */ 561 if (scan_file (term, fd, &buf) == 0) 562 { 563 close (fd); 564 free (buf.beg); 565 if (malloc_size) 566 free (bp); 567 return 0; 568 } 569 570 /* Free old `term' if appropriate. */ 571 if (term != name) 572 free (term); 573 574 /* If BP is malloc'd by us, make sure it is big enough. */ 575 if (malloc_size) 576 { 577 int offset1 = bp1 - bp, offset2 = tc_search_point - bp; 578 malloc_size = offset1 + buf.size; 579 bp = termcap_name = (char *) xrealloc (bp, malloc_size); 580 bp1 = termcap_name + offset1; 581 tc_search_point = termcap_name + offset2; 582 } 583 584 /* Copy the line of the entry from buf into bp. */ 585 termcap_name = buf.ptr; 586 while ((*bp1++ = c = *termcap_name++) && c != '\n') 587 /* Drop out any \ newline sequence. */ 588 if (c == '\\' && *termcap_name == '\n') 589 { 590 bp1--; 591 termcap_name++; 592 } 593 *bp1 = '\0'; 594 595 /* Does this entry refer to another terminal type's entry? 596 If something is found, copy it into heap and null-terminate it. */ 597 tc_search_point = find_capability (tc_search_point, "tc"); 598 term = tgetst1 (tc_search_point, (char **) 0); 599 } 600 601 close (fd); 602 free (buf.beg); 603 604 if (malloc_size) 605 bp = (char *) xrealloc (bp, bp1 - bp + 1); 606 607 ret: 608 term_entry = bp; 609 return 1; 610} 611 612/* Given file open on FD and buffer BUFP, 613 scan the file from the beginning until a line is found 614 that starts the entry for terminal type STR. 615 Return 1 if successful, with that line in BUFP, 616 or 0 if no entry is found in the file. */ 617 618static int 619scan_file (str, fd, bufp) 620 char *str; 621 int fd; 622 register struct termcap_buffer *bufp; 623{ 624 register char *end; 625 626 bufp->ptr = bufp->beg; 627 bufp->full = 0; 628 bufp->ateof = 0; 629 *bufp->ptr = '\0'; 630 631 lseek (fd, 0L, 0); 632 633 while (!bufp->ateof) 634 { 635 /* Read a line into the buffer. */ 636 end = NULL; 637 do 638 { 639 /* if it is continued, append another line to it, 640 until a non-continued line ends. */ 641 end = gobble_line (fd, bufp, end); 642 } 643 while (!bufp->ateof && end[-2] == '\\'); 644 645 if (*bufp->ptr != '#' 646 && name_match (bufp->ptr, str)) 647 return 1; 648 649 /* Discard the line just processed. */ 650 bufp->ptr = end; 651 } 652 return 0; 653} 654 655/* Return nonzero if NAME is one of the names specified 656 by termcap entry LINE. */ 657 658static int 659name_match (line, name) 660 char *line, *name; 661{ 662 register char *tem; 663 664 if (!compare_contin (line, name)) 665 return 1; 666 /* This line starts an entry. Is it the right one? */ 667 for (tem = line; *tem && *tem != '\n' && *tem != ':'; tem++) 668 if (*tem == '|' && !compare_contin (tem + 1, name)) 669 return 1; 670 671 return 0; 672} 673 674static int 675compare_contin (str1, str2) 676 register char *str1, *str2; 677{ 678 register int c1, c2; 679 while (1) 680 { 681 c1 = *str1++; 682 c2 = *str2++; 683 while (c1 == '\\' && *str1 == '\n') 684 { 685 str1++; 686 while ((c1 = *str1++) == ' ' || c1 == '\t'); 687 } 688 if (c2 == '\0') 689 { 690 /* End of type being looked up. */ 691 if (c1 == '|' || c1 == ':') 692 /* If end of name in data base, we win. */ 693 return 0; 694 else 695 return 1; 696 } 697 else if (c1 != c2) 698 return 1; 699 } 700} 701 702/* Make sure that the buffer <- BUFP contains a full line 703 of the file open on FD, starting at the place BUFP->ptr 704 points to. Can read more of the file, discard stuff before 705 BUFP->ptr, or make the buffer bigger. 706 707 Return the pointer to after the newline ending the line, 708 or to the end of the file, if there is no newline to end it. 709 710 Can also merge on continuation lines. If APPEND_END is 711 non-null, it points past the newline of a line that is 712 continued; we add another line onto it and regard the whole 713 thing as one line. The caller decides when a line is continued. */ 714 715static char * 716gobble_line (fd, bufp, append_end) 717 int fd; 718 register struct termcap_buffer *bufp; 719 char *append_end; 720{ 721 register char *end; 722 register int nread; 723 register char *buf = bufp->beg; 724 register char *tem; 725 726 if (!append_end) 727 append_end = bufp->ptr; 728 729 while (1) 730 { 731 end = append_end; 732 while (*end && *end != '\n') end++; 733 if (*end) 734 break; 735 if (bufp->ateof) 736 return buf + bufp->full; 737 if (bufp->ptr == buf) 738 { 739 if (bufp->full == bufp->size) 740 { 741 bufp->size *= 2; 742 /* Add 1 to size to ensure room for terminating null. */ 743 tem = (char *) xrealloc (buf, bufp->size + 1); 744 bufp->ptr = (bufp->ptr - buf) + tem; 745 append_end = (append_end - buf) + tem; 746 bufp->beg = buf = tem; 747 } 748 } 749 else 750 { 751 append_end -= bufp->ptr - buf; 752 bcopy (bufp->ptr, buf, bufp->full -= bufp->ptr - buf); 753 bufp->ptr = buf; 754 } 755 if (!(nread = read (fd, buf + bufp->full, bufp->size - bufp->full))) 756 bufp->ateof = 1; 757 bufp->full += nread; 758 buf[bufp->full] = '\0'; 759 } 760 return end + 1; 761} 762 763#ifdef TEST 764 765#ifdef NULL 766#undef NULL 767#endif 768 769#include <stdio.h> 770 771main (argc, argv) 772 int argc; 773 char **argv; 774{ 775 char *term; 776 char *buf; 777 778 term = argv[1]; 779 printf ("TERM: %s\n", term); 780 781 buf = (char *) tgetent (0, term); 782 if ((int) buf <= 0) 783 { 784 printf ("No entry.\n"); 785 return 0; 786 } 787 788 printf ("Entry: %s\n", buf); 789 790 tprint ("cm"); 791 tprint ("AL"); 792 793 printf ("co: %d\n", tgetnum ("co")); 794 printf ("am: %d\n", tgetflag ("am")); 795} 796 797tprint (cap) 798 char *cap; 799{ 800 char *x = tgetstr (cap, 0); 801 register char *y; 802 803 printf ("%s: ", cap); 804 if (x) 805 { 806 for (y = x; *y; y++) 807 if (*y <= ' ' || *y == 0177) 808 printf ("\\%0o", *y); 809 else 810 putchar (*y); 811 free (x); 812 } 813 else 814 printf ("none"); 815 putchar ('\n'); 816} 817 818#endif /* TEST */ 819