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