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