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