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