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