root/src/termcap.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. find_capability
  2. tgetnum
  3. tgetflag
  4. tgetstr
  5. tgetst1
  6. tputs
  7. valid_filename_p
  8. tgetent
  9. scan_file
  10. name_match
  11. compare_contin
  12. gobble_line

     1 /* Work-alike for termcap, plus extra features.
     2    Copyright (C) 1985-1986, 1993-1995, 2000-2008, 2011, 2013-2023 Free
     3    Software Foundation, Inc.
     4 
     5 This program is free software; you can redistribute it and/or modify
     6 it under the terms of the GNU General Public License as published by
     7 the Free Software Foundation; either version 2, or (at your option)
     8 any later version.
     9 
    10 This program is distributed in the hope that it will be useful,
    11 but WITHOUT ANY WARRANTY; without even the implied warranty of
    12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13 GNU General Public License for more details.
    14 
    15 You should have received a copy of the GNU General Public License
    16 along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
    17 
    18 /* Since 2010-03, 073589f4, Emacs 24.1, this file is only used
    19    by the MS-DOS port of Emacs.  */
    20 
    21 /* Emacs config.h may rename various library functions such as malloc.  */
    22 #include <config.h>
    23 
    24 #include <stdlib.h>
    25 #include <sys/file.h>
    26 #include <fcntl.h>
    27 #include <unistd.h>
    28 
    29 #include <intprops.h>
    30 
    31 #include "lisp.h"
    32 #include "tparam.h"
    33 #ifdef MSDOS
    34 #include "msdos.h"
    35 #endif
    36 
    37 /* BUFSIZE is the initial size allocated for the buffer
    38    for reading the termcap file.
    39    It is not a limit.
    40    Make it large normally for speed.
    41    Make it variable when debugging, so can exercise
    42    increasing the space dynamically.  */
    43 
    44 #ifndef BUFSIZE
    45 #ifdef DEBUG
    46 #define BUFSIZE bufsize
    47 
    48 int bufsize = 128;
    49 #else
    50 #define BUFSIZE 2048
    51 #endif
    52 #endif
    53 
    54 #ifndef TERMCAP_FILE
    55 #define TERMCAP_FILE "/etc/termcap"
    56 #endif
    57 
    58 
    59 /* Looking up capabilities in the entry already found.  */
    60 
    61 /* The pointer to the data made by tgetent is left here
    62    for tgetnum, tgetflag and tgetstr to find.  */
    63 static char *term_entry;
    64 
    65 static char *tgetst1 (char *ptr, char **area);
    66 
    67 /* Search entry BP for capability CAP.
    68    Return a pointer to the capability (in BP) if found,
    69    0 if not found.  */
    70 
    71 static char *
    72 find_capability (register char *bp, register const char *cap)
    73 {
    74   for (; *bp; bp++)
    75     if (bp[0] == ':'
    76         && bp[1] == cap[0]
    77         && bp[2] == cap[1])
    78       return &bp[4];
    79   return NULL;
    80 }
    81 
    82 int
    83 tgetnum (const char *cap)
    84 {
    85   register char *ptr = find_capability (term_entry, cap);
    86   if (!ptr || ptr[-1] != '#')
    87     return -1;
    88   return atoi (ptr);
    89 }
    90 
    91 int
    92 tgetflag (const char *cap)
    93 {
    94   register char *ptr = find_capability (term_entry, cap);
    95   return ptr && ptr[-1] == ':';
    96 }
    97 
    98 /* Look up a string-valued capability CAP.
    99    If AREA is non-null, it points to a pointer to a block in which
   100    to store the string.  That pointer is advanced over the space used.
   101    If AREA is null, space is allocated with `malloc'.  */
   102 
   103 char *
   104 tgetstr (const char *cap, char **area)
   105 {
   106   register char *ptr = find_capability (term_entry, cap);
   107   if (!ptr || (ptr[-1] != '=' && ptr[-1] != '~'))
   108     return NULL;
   109   return tgetst1 (ptr, area);
   110 }
   111 
   112 #ifdef IS_EBCDIC_HOST
   113 /* Table, indexed by a character in range 0200 to 0300 with 0200 subtracted,
   114    gives meaning of character following \, or a space if no special meaning.
   115    Sixteen characters per line within the string.  */
   116 
   117 static const char esctab[]
   118   = " \057\026  \047\014         \
   119      \025   \015      \
   120    \005 \013          \
   121                 ";
   122 #else
   123 /* Table, indexed by a character in range 0100 to 0140 with 0100 subtracted,
   124    gives meaning of character following \, or a space if no special meaning.
   125    Eight characters per line within the string.  */
   126 
   127 static const char esctab[]
   128   = " \007\010  \033\014 \
   129       \012 \
   130   \015 \011 \013 \
   131         ";
   132 #endif
   133 
   134 /* PTR points to a string value inside a termcap entry.
   135    Copy that value, processing \ and ^ abbreviations,
   136    into the block that *AREA points to,
   137    or to newly allocated storage if AREA is NULL.
   138    Return the address to which we copied the value,
   139    or NULL if PTR is NULL.  */
   140 
   141 static char *
   142 tgetst1 (char *ptr, char **area)
   143 {
   144   register char *p, *r;
   145   register int c;
   146   register int size;
   147   char *ret;
   148   register int c1;
   149 
   150   if (!ptr)
   151     return NULL;
   152 
   153   /* `ret' gets address of where to store the string.  */
   154   if (!area)
   155     {
   156       /* Compute size of block needed (may overestimate).  */
   157       p = ptr;
   158       while ((c = *p++) && c != ':' && c != '\n')
   159         ;
   160       ret = xmalloc (p - ptr + 1);
   161     }
   162   else
   163     ret = *area;
   164 
   165   /* Copy the string value, stopping at null or colon.
   166      Also process ^ and \ abbreviations.  */
   167   p = ptr;
   168   r = ret;
   169   while ((c = *p++) && c != ':' && c != '\n')
   170     {
   171       if (c == '^')
   172         {
   173           c = *p++;
   174           if (c == '?')
   175             c = 0177;
   176           else
   177             c &= 037;
   178         }
   179       else if (c == '\\')
   180         {
   181           c = *p++;
   182           if (c >= '0' && c <= '7')
   183             {
   184               c -= '0';
   185               size = 0;
   186 
   187               while (++size < 3 && (c1 = *p) >= '0' && c1 <= '7')
   188                 {
   189                   c *= 8;
   190                   c += c1 - '0';
   191                   p++;
   192                 }
   193             }
   194 #ifdef IS_EBCDIC_HOST
   195           else if (c >= 0200 && c < 0360)
   196             {
   197               c1 = esctab[(c & ~0100) - 0200];
   198               if (c1 != ' ')
   199                 c = c1;
   200             }
   201 #else
   202           else if (c >= 0100 && c < 0200)
   203             {
   204               c1 = esctab[(c & ~040) - 0100];
   205               if (c1 != ' ')
   206                 c = c1;
   207             }
   208 #endif
   209         }
   210       *r++ = c;
   211     }
   212 
   213   /* Sometimes entries have "%pN" which means use parameter N in the
   214      next %-substitution.  If all such N are continuous in the range
   215      [1,9] we can remove each "%pN" because they are redundant, thus
   216      reducing bandwidth requirements.  True, Emacs is well beyond the
   217      days of 150baud teletypes, but some of its users aren't much so.
   218 
   219      This pass could probably be integrated into the one above but
   220      abbreviation expansion makes that effort a little more hairy than
   221      its worth; this is cleaner.  */
   222   {
   223     int last_p_param = 0;
   224     bool remove_p_params = 1;
   225     struct { char *beg; int len; } cut[11];
   226 
   227     for (cut[0].beg = p = ret; p < r - 3; p++)
   228       {
   229         if (!remove_p_params)
   230           break;
   231         if (*p == '%' && *(p + 1) == 'p')
   232           {
   233             if (*(p + 2) - '0' == 1 + last_p_param)
   234               {
   235                 cut[last_p_param].len = p - cut[last_p_param].beg;
   236                 last_p_param++;
   237                 p += 3;
   238                 cut[last_p_param].beg = p;
   239               }
   240             else                                /* not continuous: bail */
   241               remove_p_params = 0;
   242             if (last_p_param > 10)              /* too many: bail */
   243               remove_p_params = 0;
   244           }
   245       }
   246     if (remove_p_params && last_p_param)
   247       {
   248         register int i;
   249         char *wp;
   250 
   251         cut[last_p_param].len = r - cut[last_p_param].beg;
   252         for (i = 0, wp = ret; i <= last_p_param; wp += cut[i++].len)
   253           memcpy (wp, cut[i].beg, cut[i].len);
   254         r = wp;
   255       }
   256   }
   257 
   258   *r = '\0';
   259   /* Update *AREA.  */
   260   if (area)
   261     *area = r + 1;
   262   return ret;
   263 }
   264 
   265 /* Outputting a string with padding.  */
   266 
   267 char PC;
   268 
   269 void
   270 tputs (register const char *str, int nlines, int (*outfun) (int))
   271 {
   272   int padcount = 0;
   273 
   274   if (!str)
   275     return;
   276 
   277   while (*str >= '0' && *str <= '9')
   278     {
   279       padcount += *str++ - '0';
   280       padcount *= 10;
   281     }
   282   if (*str == '.')
   283     {
   284       str++;
   285       padcount += *str++ - '0';
   286     }
   287   if (*str == '*')
   288     {
   289       str++;
   290       padcount *= nlines;
   291     }
   292   while (*str)
   293     (*outfun) (*str++);
   294 
   295   /* PADCOUNT is now in units of tenths of msec.
   296      BAUD_RATE is measured in characters per 10 seconds.
   297      Compute PADFACTOR = 100000 * (how many padding bytes are needed).  */
   298   intmax_t padfactor;
   299   if (ckd_mul (&padfactor, padcount, baud_rate))
   300     padfactor = baud_rate < 0 ? INTMAX_MIN : INTMAX_MAX;
   301 
   302   for (; 50000 <= padfactor; padfactor -= 100000)
   303     (*outfun) (PC);
   304 }
   305 
   306 /* Finding the termcap entry in the termcap data base.  */
   307 
   308 struct termcap_buffer
   309   {
   310     char *beg;
   311     ptrdiff_t size;
   312     char *ptr;
   313     bool ateof;
   314     ptrdiff_t full;
   315   };
   316 
   317 /* Forward declarations of static functions.  */
   318 
   319 static bool scan_file (char *, int, struct termcap_buffer *);
   320 static char *gobble_line (int, struct termcap_buffer *, char *);
   321 static bool compare_contin (char *, char *);
   322 static bool name_match (char *, char *);
   323 
   324 static bool
   325 valid_filename_p (char *fn)
   326 {
   327 #ifdef MSDOS
   328   return *fn == '/' || fn[1] == ':';
   329 #else
   330   return *fn == '/';
   331 #endif
   332 }
   333 
   334 /* Find the termcap entry data for terminal type NAME
   335    and store it in the block that BP points to.
   336    Record its address for future use.
   337 
   338    If BP is null, space is dynamically allocated.
   339 
   340    Return -1 if there is some difficulty accessing the data base
   341    of terminal types,
   342    0 if the data base is accessible but the type NAME is not defined
   343    in it, and some other value otherwise.  */
   344 
   345 int
   346 tgetent (char *bp, const char *name)
   347 {
   348   register char *termcap_name;
   349   register int fd;
   350   struct termcap_buffer buf;
   351   register char *bp1;
   352   char *tc_search_point;
   353   char *term;
   354   ptrdiff_t malloc_size = 0;
   355   int c;
   356   char *tcenv = NULL;           /* TERMCAP value, if it contains :tc=.  */
   357   char *indirect = NULL;        /* Terminal type in :tc= in TERMCAP value.  */
   358   bool filep;
   359 
   360 #ifdef INTERNAL_TERMINAL
   361   /* For the internal terminal we don't want to read any termcap file,
   362      so fake it.  */
   363   if (!strcmp (name, "internal"))
   364     {
   365       term = INTERNAL_TERMINAL;
   366       if (!bp)
   367         {
   368           malloc_size = 1 + strlen (term);
   369           bp = xmalloc (malloc_size);
   370         }
   371       strcpy (bp, term);
   372       goto ret;
   373     }
   374 #endif /* INTERNAL_TERMINAL */
   375 
   376   /* For compatibility with programs like `less' that want to
   377      put data in the termcap buffer themselves as a fallback.  */
   378   if (bp)
   379     term_entry = bp;
   380 
   381   termcap_name = getenv ("TERMCAP");
   382   if (termcap_name && *termcap_name == '\0')
   383     termcap_name = NULL;
   384 #if defined (MSDOS) && !defined (TEST)
   385   if (termcap_name && (*termcap_name == '\\'
   386                        || *termcap_name == '/'
   387                        || termcap_name[1] == ':'))
   388     dostounix_filename (termcap_name);
   389 #endif
   390 
   391   filep = termcap_name && valid_filename_p (termcap_name);
   392 
   393   /* If termcap_name is non-null and starts with / (in the un*x case, that is),
   394      it is a file name to use instead of /etc/termcap.
   395      If it is non-null and does not start with /,
   396      it is the entry itself, but only if
   397      the name the caller requested matches the TERM variable.  */
   398 
   399   if (termcap_name && !filep && !strcmp (name, getenv ("TERM")))
   400     {
   401       indirect = tgetst1 (find_capability (termcap_name, "tc"), 0);
   402       if (!indirect)
   403         {
   404           if (!bp)
   405             bp = termcap_name;
   406           else
   407             strcpy (bp, termcap_name);
   408           goto ret;
   409         }
   410       else
   411         {                       /* It has tc=.  Need to read /etc/termcap.  */
   412           tcenv = termcap_name;
   413           termcap_name = NULL;
   414         }
   415     }
   416 
   417   if (!termcap_name || !filep)
   418     termcap_name = (char *) TERMCAP_FILE;
   419 
   420   /* Here we know we must search a file and termcap_name has its name.  */
   421 
   422   fd = emacs_open (termcap_name, O_RDONLY | O_TEXT, 0);
   423   if (fd < 0)
   424     return -1;
   425 
   426   buf.size = BUFSIZE;
   427   /* Add 1 to size to ensure room for terminating null.  */
   428   buf.beg = xmalloc (buf.size + 1);
   429   term = indirect ? indirect : (char *)name;
   430 
   431   if (!bp)
   432     {
   433       malloc_size = indirect ? strlen (tcenv) + 1 : buf.size;
   434       bp = xmalloc (malloc_size);
   435     }
   436   tc_search_point = bp1 = bp;
   437 
   438   if (indirect)
   439     /* Copy the data from the environment variable.  */
   440     {
   441       strcpy (bp, tcenv);
   442       bp1 += strlen (tcenv);
   443     }
   444 
   445   while (term)
   446     {
   447       /* Scan the file, reading it via buf, till find start of main entry.  */
   448       if (scan_file (term, fd, &buf) == 0)
   449         {
   450           emacs_close (fd);
   451           xfree (buf.beg);
   452           if (malloc_size)
   453             xfree (bp);
   454           return 0;
   455         }
   456 
   457       /* Free old `term' if appropriate.  */
   458       if (term != name)
   459         xfree (term);
   460 
   461       /* If BP is malloc'd by us, make sure it is big enough.  */
   462       if (malloc_size)
   463         {
   464           ptrdiff_t offset1 = bp1 - bp, offset2 = tc_search_point - bp;
   465           malloc_size = offset1 + buf.size;
   466           bp = termcap_name = xrealloc (bp, malloc_size);
   467           bp1 = termcap_name + offset1;
   468           tc_search_point = termcap_name + offset2;
   469         }
   470 
   471       /* Copy the line of the entry from buf into bp.  */
   472       termcap_name = buf.ptr;
   473       while ((*bp1++ = c = *termcap_name++) && c != '\n')
   474         /* Drop out any \ newline sequence.  */
   475         if (c == '\\' && *termcap_name == '\n')
   476           {
   477             bp1--;
   478             termcap_name++;
   479           }
   480       *bp1 = '\0';
   481 
   482       /* Does this entry refer to another terminal type's entry?
   483          If something is found, copy it into heap and null-terminate it.  */
   484       tc_search_point = find_capability (tc_search_point, "tc");
   485       term = tgetst1 (tc_search_point, 0);
   486     }
   487 
   488   emacs_close (fd);
   489   xfree (buf.beg);
   490 
   491   if (malloc_size)
   492     bp = xrealloc (bp, bp1 - bp + 1);
   493 
   494  ret:
   495   term_entry = bp;
   496   return 1;
   497 }
   498 
   499 /* Given file open on FD and buffer BUFP,
   500    scan the file from the beginning until a line is found
   501    that starts the entry for terminal type STR.
   502    Return 1 if successful, with that line in BUFP,
   503    or 0 if no entry is found in the file.  */
   504 
   505 static bool
   506 scan_file (char *str, int fd, struct termcap_buffer *bufp)
   507 {
   508   char *end;
   509 
   510   bufp->ptr = bufp->beg;
   511   bufp->full = 0;
   512   bufp->ateof = 0;
   513   *bufp->ptr = '\0';
   514 
   515   lseek (fd, 0, 0);
   516 
   517   while (!bufp->ateof)
   518     {
   519       /* Read a line into the buffer.  */
   520       end = NULL;
   521       do
   522         {
   523           /* if it is continued, append another line to it,
   524              until a non-continued line ends.  */
   525           end = gobble_line (fd, bufp, end);
   526         }
   527       while (!bufp->ateof && end[-2] == '\\');
   528 
   529       if (*bufp->ptr != '#'
   530           && name_match (bufp->ptr, str))
   531         return 1;
   532 
   533       /* Discard the line just processed.  */
   534       bufp->ptr = end;
   535     }
   536   return 0;
   537 }
   538 
   539 /* Return true if NAME is one of the names specified
   540    by termcap entry LINE.  */
   541 
   542 static bool
   543 name_match (char *line, char *name)
   544 {
   545   char *tem;
   546 
   547   if (!compare_contin (line, name))
   548     return 1;
   549   /* This line starts an entry.  Is it the right one?  */
   550   for (tem = line; *tem && *tem != '\n' && *tem != ':'; tem++)
   551     if (*tem == '|' && !compare_contin (tem + 1, name))
   552       return 1;
   553 
   554   return 0;
   555 }
   556 
   557 static bool
   558 compare_contin (char *str1, char *str2)
   559 {
   560   while (1)
   561     {
   562       int c1 = *str1++;
   563       int c2 = *str2++;
   564       while (c1 == '\\' && *str1 == '\n')
   565         {
   566           str1++;
   567           while ((c1 = *str1++) == ' ' || c1 == '\t')
   568             continue;
   569         }
   570       if (c2 == '\0')
   571         {
   572           /* End of type being looked up.  */
   573           if (c1 == '|' || c1 == ':')
   574             /* If end of name in data base, we win.  */
   575             return 0;
   576           else
   577             return 1;
   578         }
   579       else if (c1 != c2)
   580         return 1;
   581     }
   582 }
   583 
   584 /* Make sure that the buffer <- BUFP contains a full line
   585    of the file open on FD, starting at the place BUFP->ptr
   586    points to.  Can read more of the file, discard stuff before
   587    BUFP->ptr, or make the buffer bigger.
   588 
   589    Return the pointer to after the newline ending the line,
   590    or to the end of the file, if there is no newline to end it.
   591 
   592    Can also merge on continuation lines.  If APPEND_END is
   593    non-null, it points past the newline of a line that is
   594    continued; we add another line onto it and regard the whole
   595    thing as one line.  The caller decides when a line is continued.  */
   596 
   597 static char *
   598 gobble_line (int fd, register struct termcap_buffer *bufp, char *append_end)
   599 {
   600   register char *end;
   601   register int nread;
   602   register char *buf = bufp->beg;
   603 
   604   if (!append_end)
   605     append_end = bufp->ptr;
   606 
   607   while (1)
   608     {
   609       end = append_end;
   610       while (*end && *end != '\n') end++;
   611       if (*end)
   612         break;
   613       if (bufp->ateof)
   614         return buf + bufp->full;
   615       if (bufp->ptr == buf)
   616         {
   617           if (bufp->full == bufp->size)
   618             {
   619               ptrdiff_t ptr_offset = bufp->ptr - buf;
   620               ptrdiff_t append_end_offset = append_end - buf;
   621               /* Add 1 to size to ensure room for terminating null.  */
   622               ptrdiff_t size = bufp->size + 1;
   623               bufp->beg = buf = xpalloc (buf, &size, 1, -1, 1);
   624               bufp->size = size - 1;
   625               bufp->ptr = buf + ptr_offset;
   626               append_end = buf + append_end_offset;
   627             }
   628         }
   629       else
   630         {
   631           append_end -= bufp->ptr - buf;
   632           memcpy (buf, bufp->ptr, bufp->full -= bufp->ptr - buf);
   633           bufp->ptr = buf;
   634         }
   635       if (!(nread = read (fd, buf + bufp->full, bufp->size - bufp->full)))
   636         bufp->ateof = 1;
   637       bufp->full += nread;
   638       buf[bufp->full] = '\0';
   639     }
   640   return end + 1;
   641 }

/* [<][>][^][v][top][bottom][index][help] */