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