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