1 /* Work-alike for termcap, plus extra features.
2    Copyright (C) 1985-1986, 1993-1995, 2000-2008, 2011, 2013-2021 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 *
find_capability(register char * bp,register const char * cap)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
tgetnum(const char * cap)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
tgetflag(const char * cap)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 *
tgetstr(const char * cap,char ** area)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 *
tgetst1(char * ptr,char ** area)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
tputs(register const char * str,int nlines,int (* outfun)(int))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 (INT_MULTIPLY_WRAPV (padcount, baud_rate, &padfactor))
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
valid_filename_p(char * fn)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
tgetent(char * bp,const char * name)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
scan_file(char * str,int fd,struct termcap_buffer * bufp)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
name_match(char * line,char * name)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
compare_contin(char * str1,char * str2)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 *
gobble_line(int fd,register struct termcap_buffer * bufp,char * append_end)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 }
642