ncurses 4.2
[ncurses.git] / ncurses / read_termcap.c
1 /****************************************************************************
2  * Copyright (c) 1998 Free Software Foundation, Inc.                        *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34
35 /*
36  * Termcap compatibility support
37  *
38  * If your OS integrator didn't install a terminfo database, you can call
39  * _nc_read_termcap_entry() to support reading and translating capabilities
40  * from the system termcap file.  This is a kludge; it will bulk up and slow
41  * down every program that uses ncurses, and translated termcap entries cannot
42  * use full terminfo capabilities.  Don't use it unless you absolutely have to;
43  * instead, get your system people to run tic(1) from root on the terminfo
44  * master included with ncurses to translate it into a terminfo database.
45  *
46  * If USE_GETCAP is enabled, we use what is effectively a copy of the 4.4BSD
47  * getcap code to fetch entries.  There are disadvantages to this; mainly that
48  * getcap(3) does its own resolution, meaning that entries read in in this way
49  * can't reference the terminfo tree.  The only thing it buys is faster startup
50  * time, getcap(3) is much faster than our tic parser.
51  */
52
53 #include <curses.priv.h>
54
55 #include <ctype.h>
56 #include <term.h>
57 #include <tic.h>
58 #include <term_entry.h>
59
60 #if HAVE_FCNTL_H
61 #include <fcntl.h>
62 #endif
63
64 MODULE_ID("$Id: read_termcap.c,v 1.27 1998/02/11 12:13:58 tom Exp $")
65
66 #ifdef __EMX__
67 #define is_pathname(s) ((((s) != 0) && ((s)[0] == '/')) \
68                   || (((s)[0] != 0) && ((s)[1] == ':')))
69 #else
70 #define is_pathname(s) ((s) != 0 && (s)[0] == '/')
71 #endif
72
73 #define TC_SUCCESS     0
74 #define TC_UNRESOLVED -1
75 #define TC_NOT_FOUND  -2
76 #define TC_SYS_ERR    -3
77 #define TC_REF_LOOP   -4
78
79 #if USE_GETCAP
80
81 #if HAVE_BSD_CGETENT
82 #define _nc_cgetcap   cgetcap
83 #define _nc_cgetent(buf, oline, db_array, name) cgetent(buf, db_array, name)
84 #define _nc_cgetmatch cgetmatch
85 #define _nc_cgetset   cgetset
86 #else
87 static int _nc_cgetmatch(char *, const char *);
88 static int _nc_getent(char **, unsigned int *, int *, int, char **, int, const char *, int, char *);
89 static int _nc_nfcmp(const char *, char *);
90
91 /*-
92  * Copyright (c) 1992, 1993
93  *      The Regents of the University of California.  All rights reserved.
94  *
95  * This code is derived from software contributed to Berkeley by
96  * Casey Leedom of Lawrence Livermore National Laboratory.
97  *
98  * Redistribution and use in source and binary forms, with or without
99  * modification, are permitted provided that the following conditions
100  * are met:
101  * 1. Redistributions of source code must retain the above copyright
102  *    notice, this list of conditions and the following disclaimer.
103  * 2. Redistributions in binary form must reproduce the above copyright
104  *    notice, this list of conditions and the following disclaimer in the
105  *    documentation and/or other materials provided with the distribution.
106  * 3. All advertising materials mentioning features or use of this software
107  *    must display the following acknowledgment:
108  *      This product includes software developed by the University of
109  *      California, Berkeley and its contributors.
110  * 4. Neither the name of the University nor the names of its contributors
111  *    may be used to endorse or promote products derived from this software
112  *    without specific prior written permission.
113  *
114  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
115  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
116  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
117  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
118  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
119  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
120  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
121  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
122  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
123  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
124  * SUCH DAMAGE.
125  */
126
127 /* static char sccsid[] = "@(#)getcap.c 8.3 (Berkeley) 3/25/94"; */
128
129 #define BFRAG           1024
130 #define BSIZE           1024
131 #define ESC             ('[' & 037)     /* ASCII ESC */
132 #define MAX_RECURSION   32              /* maximum getent recursion */
133 #define SFRAG           100             /* cgetstr mallocs in SFRAG chunks */
134
135 #define RECOK   (char)0
136 #define TCERR   (char)1
137 #define SHADOW  (char)2
138
139 static size_t    topreclen;     /* toprec length */
140 static char     *toprec;        /* Additional record specified by cgetset() */
141 static int       gottoprec;     /* Flag indicating retrieval of toprecord */
142
143 /*
144  * Cgetset() allows the addition of a user specified buffer to be added to the
145  * database array, in effect "pushing" the buffer on top of the virtual
146  * database.  0 is returned on success, -1 on failure.
147  */
148 static int
149 _nc_cgetset(const char *ent)
150 {
151         if (ent == 0) {
152                 FreeIfNeeded(toprec);
153                 toprec = 0;
154                 topreclen = 0;
155                 return (0);
156         }
157         topreclen = strlen(ent);
158         if ((toprec = malloc (topreclen + 1)) == 0) {
159                 errno = ENOMEM;
160                 return (-1);
161         }
162         gottoprec = 0;
163         (void)strcpy(toprec, ent);
164         return (0);
165 }
166
167 /*
168  * Cgetcap searches the capability record buf for the capability cap with type
169  * `type'.  A pointer to the value of cap is returned on success, 0 if the
170  * requested capability couldn't be found.
171  *
172  * Specifying a type of ':' means that nothing should follow cap (:cap:).  In
173  * this case a pointer to the terminating ':' or NUL will be returned if cap is
174  * found.
175  *
176  * If (cap, '@') or (cap, terminator, '@') is found before (cap, terminator)
177  * return 0.
178  */
179 static char *
180 _nc_cgetcap(char *buf, const char *cap, int type)
181 {
182         register const char *cp;
183         register char *bp;
184
185         bp = buf;
186         for (;;) {
187                 /*
188                  * Skip past the current capability field - it's either the
189                  * name field if this is the first time through the loop, or
190                  * the remainder of a field whose name failed to match cap.
191                  */
192                 for (;;) {
193                         if (*bp == '\0')
194                                 return (0);
195                         else if (*bp++ == ':')
196                                 break;
197                 }
198
199                 /*
200                  * Try to match (cap, type) in buf.
201                  */
202                 for (cp = cap; *cp == *bp && *bp != '\0'; cp++, bp++)
203                         continue;
204                 if (*cp != '\0')
205                         continue;
206                 if (*bp == '@')
207                         return (0);
208                 if (type == ':') {
209                         if (*bp != '\0' && *bp != ':')
210                                 continue;
211                         return(bp);
212                 }
213                 if (*bp != type)
214                         continue;
215                 bp++;
216                 return (*bp == '@' ? 0 : bp);
217         }
218         /* NOTREACHED */
219 }
220
221 /*
222  * Cgetent extracts the capability record name from the NULL terminated file
223  * array db_array and returns a pointer to a malloc'd copy of it in buf.  Buf
224  * must be retained through all subsequent calls to cgetcap, cgetnum, cgetflag,
225  * and cgetstr, but may then be freed.
226  *
227  * Returns:
228  *
229  * positive #    on success (i.e., the index in db_array)
230  * TC_UNRESOLVED if we had too many recurrences to resolve
231  * TC_NOT_FOUND  if the requested record couldn't be found
232  * TC_SYS_ERR    if a system error was encountered (e.g.,couldn't open a file)
233  * TC_REF_LOOP   if a potential reference loop is detected
234  */
235 static int
236 _nc_cgetent(char **buf, int *oline, char **db_array, const char *name)
237 {
238         unsigned int dummy;
239
240         return (_nc_getent(buf, &dummy, oline, 0, db_array, -1, name, 0, 0));
241 }
242
243 /*
244  * Getent implements the functions of cgetent.  If fd is non-negative,
245  * *db_array has already been opened and fd is the open file descriptor.  We
246  * do this to save time and avoid using up file descriptors for tc=
247  * recursions.
248  *
249  * Getent returns the same success/failure codes as cgetent.  On success, a
250  * pointer to a malloc'd capability record with all tc= capabilities fully
251  * expanded and its length (not including trailing ASCII NUL) are left in
252  * *cap and *len.
253  *
254  * Basic algorithm:
255  *      + Allocate memory incrementally as needed in chunks of size BFRAG
256  *        for capability buffer.
257  *      + Recurse for each tc=name and interpolate result.  Stop when all
258  *        names interpolated, a name can't be found, or depth exceeds
259  *        MAX_RECURSION.
260  */
261 static int
262 _nc_getent(
263         char **cap,         /* termcap-content */
264         unsigned int *len,  /* length, needed for recursion */
265         int *beginning,     /* line-number at match */
266         int in_array,       /* index in 'db_array[] */
267         char **db_array,    /* list of files to search */
268         int fd,
269         const char *name,
270         int depth,
271         char *nfield)
272 {
273         register char *r_end, *rp;
274         int myfd = FALSE;
275         char *record;
276         int tc_not_resolved;
277         int current;
278         int lineno;
279
280         /*
281          * Return with ``loop detected'' error if we've recurred more than
282          * MAX_RECURSION times.
283          */
284         if (depth > MAX_RECURSION)
285                 return (TC_REF_LOOP);
286
287         /*
288          * Check if we have a top record from cgetset().
289          */
290         if (depth == 0 && toprec != 0 && _nc_cgetmatch(toprec, name) == 0) {
291                 if ((record = malloc (topreclen + BFRAG)) == 0) {
292                         errno = ENOMEM;
293                         return (TC_SYS_ERR);
294                 }
295                 (void)strcpy(record, toprec);
296                 rp = record + topreclen + 1;
297                 r_end = rp + BFRAG;
298                 current = in_array;
299         } else {
300                 int foundit;
301
302                 /*
303                  * Allocate first chunk of memory.
304                  */
305                 if ((record = malloc(BFRAG)) == 0) {
306                         errno = ENOMEM;
307                         return (TC_SYS_ERR);
308                 }
309                 rp = r_end = record + BFRAG;
310                 foundit = FALSE;
311
312                 /*
313                  * Loop through database array until finding the record.
314                  */
315                 for (current = in_array; db_array[current] != 0; current++) {
316                         int eof = FALSE;
317
318                         /*
319                          * Open database if not already open.
320                          */
321                         if (fd >= 0) {
322                                 (void)lseek(fd, (off_t)0, SEEK_SET);
323                         } else {
324                                 fd = open(db_array[current], O_RDONLY, 0);
325                                 if (fd < 0) {
326                                         /* No error on unfound file. */
327                                         if (errno == ENOENT)
328                                                 continue;
329                                         free(record);
330                                         return (TC_SYS_ERR);
331                                 }
332                                 myfd = TRUE;
333                         }
334                         lineno = 0;
335
336                         /*
337                          * Find the requested capability record ...
338                          */
339                         {
340                                 char buf[2048];
341                                 register char *b_end = buf;
342                                 register char *bp = buf;
343                                 register int c;
344
345                                 /*
346                                  * Loop invariants:
347                                  *      There is always room for one more character in record.
348                                  *      R_end always points just past end of record.
349                                  *      Rp always points just past last character in record.
350                                  *      B_end always points just past last character in buf.
351                                  *      Bp always points at next character in buf.
352                                  */
353
354                                 for (;;) {
355                                         int first = lineno + 1;
356
357                                         /*
358                                          * Read in a line implementing (\, newline)
359                                          * line continuation.
360                                          */
361                                         rp = record;
362                                         for (;;) {
363                                                 if (bp >= b_end) {
364                                                         int n;
365
366                                                         n = read(fd, buf, sizeof(buf));
367                                                         if (n <= 0) {
368                                                                 if (myfd)
369                                                                         (void)close(fd);
370                                                                 if (n < 0) {
371                                                                         free(record);
372                                                                         return (TC_SYS_ERR);
373                                                                 }
374                                                                 fd = -1;
375                                                                 eof = TRUE;
376                                                                 break;
377                                                         }
378                                                         b_end = buf+n;
379                                                         bp = buf;
380                                                 }
381
382                                                 c = *bp++;
383                                                 if (c == '\n') {
384                                                         lineno++;
385                                                         if (rp == record || *(rp-1) != '\\')
386                                                                 break;
387                                                 }
388                                                 *rp++ = c;
389
390                                                 /*
391                                                  * Enforce loop invariant: if no room
392                                                  * left in record buffer, try to get
393                                                  * some more.
394                                                  */
395                                                 if (rp >= r_end) {
396                                                         unsigned int pos;
397                                                         size_t newsize;
398
399                                                         pos = rp - record;
400                                                         newsize = r_end - record + BFRAG;
401                                                         record = realloc(record, newsize);
402                                                         if (record == 0) {
403                                                                 errno = ENOMEM;
404                                                                 if (myfd)
405                                                                         (void)close(fd);
406                                                                 return (TC_SYS_ERR);
407                                                         }
408                                                         r_end = record + newsize;
409                                                         rp = record + pos;
410                                                 }
411                                         }
412                                         /* loop invariant lets us do this */
413                                         *rp++ = '\0';
414
415                                         /*
416                                          * If encountered eof check next file.
417                                          */
418                                         if (eof)
419                                                 break;
420
421                                         /*
422                                          * Toss blank lines and comments.
423                                          */
424                                         if (*record == '\0' || *record == '#')
425                                                 continue;
426
427                                         /*
428                                          * See if this is the record we want ...
429                                          */
430                                         if (_nc_cgetmatch(record, name) == 0
431                                          && (nfield == 0
432                                           || !_nc_nfcmp(nfield, record))) {
433                                                 foundit = TRUE;
434                                                 *beginning = first;
435                                                 break;  /* found it! */
436                                         }
437                                 }
438                         }
439                         if (foundit)
440                                 break;
441                 }
442
443                 if (!foundit)
444                         return (TC_NOT_FOUND);
445         }
446
447         /*
448          * Got the capability record, but now we have to expand all tc=name
449          * references in it ...
450          */
451         {
452                 register char *newicap, *s;
453                 register int newilen;
454                 unsigned int ilen;
455                 int diff, iret, tclen, oline;
456                 char *icap, *scan, *tc, *tcstart, *tcend;
457
458                 /*
459                  * Loop invariants:
460                  *      There is room for one more character in record.
461                  *      R_end points just past end of record.
462                  *      Rp points just past last character in record.
463                  *      Scan points at remainder of record that needs to be
464                  *      scanned for tc=name constructs.
465                  */
466                 scan = record;
467                 tc_not_resolved = FALSE;
468                 for (;;) {
469                         if ((tc = _nc_cgetcap(scan, "tc", '=')) == 0)
470                                 break;
471
472                         /*
473                          * Find end of tc=name and stomp on the trailing `:'
474                          * (if present) so we can use it to call ourselves.
475                          */
476                         s = tc;
477                         while (*s != '\0') {
478                                 if (*s++ == ':') {
479                                         *(s - 1) = '\0';
480                                         break;
481                                 }
482                         }
483                         tcstart = tc - 3;
484                         tclen = s - tcstart;
485                         tcend = s;
486
487                         iret = _nc_getent(&icap, &ilen, &oline, current, db_array, fd, tc, depth+1, 0);
488                         newicap = icap;         /* Put into a register. */
489                         newilen = ilen;
490                         if (iret != TC_SUCCESS) {
491                                 /* an error */
492                                 if (iret < TC_NOT_FOUND) {
493                                         if (myfd)
494                                                 (void)close(fd);
495                                         free(record);
496                                         return (iret);
497                                 }
498                                 if (iret == TC_UNRESOLVED)
499                                         tc_not_resolved = TRUE;
500                                 /* couldn't resolve tc */
501                                 if (iret == TC_NOT_FOUND) {
502                                         *(s - 1) = ':';
503                                         scan = s - 1;
504                                         tc_not_resolved = TRUE;
505                                         continue;
506                                 }
507                         }
508
509                         /* not interested in name field of tc'ed record */
510                         s = newicap;
511                         while (*s != '\0' && *s++ != ':')
512                                 ;
513                         newilen -= s - newicap;
514                         newicap = s;
515
516                         /* make sure interpolated record is `:'-terminated */
517                         s += newilen;
518                         if (*(s-1) != ':') {
519                                 *s = ':';       /* overwrite NUL with : */
520                                 newilen++;
521                         }
522
523                         /*
524                          * Make sure there's enough room to insert the
525                          * new record.
526                          */
527                         diff = newilen - tclen;
528                         if (diff >= r_end - rp) {
529                                 unsigned int pos, tcpos, tcposend;
530                                 size_t newsize;
531
532                                 pos = rp - record;
533                                 newsize = r_end - record + diff + BFRAG;
534                                 tcpos = tcstart - record;
535                                 tcposend = tcend - record;
536                                 record = realloc(record, newsize);
537                                 if (record == 0) {
538                                         errno = ENOMEM;
539                                         if (myfd)
540                                                 (void)close(fd);
541                                         free(icap);
542                                         return (TC_SYS_ERR);
543                                 }
544                                 r_end = record + newsize;
545                                 rp = record + pos;
546                                 tcstart = record + tcpos;
547                                 tcend = record + tcposend;
548                         }
549
550                         /*
551                          * Insert tc'ed record into our record.
552                          */
553                         s = tcstart + newilen;
554                         memmove(s, tcend, (size_t)(rp - tcend));
555                         memmove(tcstart, newicap, (size_t)newilen);
556                         rp += diff;
557                         free(icap);
558
559                         /*
560                          * Start scan on `:' so next cgetcap works properly
561                          * (cgetcap always skips first field).
562                          */
563                         scan = s-1;
564                 }
565         }
566
567         /*
568          * Close file (if we opened it), give back any extra memory, and
569          * return capability, length and success.
570          */
571         if (myfd)
572                 (void)close(fd);
573         *len = rp - record - 1; /* don't count NUL */
574         if (r_end > rp) {
575                 if ((record = realloc(record, (size_t)(rp - record))) == 0) {
576                         errno = ENOMEM;
577                         return (TC_SYS_ERR);
578                 }
579         }
580
581         *cap = record;
582         if (tc_not_resolved)
583                 return (TC_UNRESOLVED);
584         return (current);
585 }
586
587 /*
588  * Cgetmatch will return 0 if name is one of the names of the capability
589  * record buf, -1 if not.
590  */
591 static int
592 _nc_cgetmatch(char *buf, const char *name)
593 {
594         register const char *np;
595         register char *bp;
596
597         /*
598          * Start search at beginning of record.
599          */
600         bp = buf;
601         for (;;) {
602                 /*
603                  * Try to match a record name.
604                  */
605                 np = name;
606                 for (;;) {
607                         if (*np == '\0') {
608                                 if (*bp == '|' || *bp == ':' || *bp == '\0')
609                                         return (0);
610                                 else
611                                         break;
612                         } else if (*bp++ != *np++) {
613                                 break;
614                         }
615                 }
616
617                 /*
618                  * Match failed, skip to next name in record.
619                  */
620                 bp--;   /* a '|' or ':' may have stopped the match */
621                 for (;;) {
622                         if (*bp == '\0' || *bp == ':')
623                                 return (-1);    /* match failed totally */
624                         else if (*bp++ == '|')
625                                 break;  /* found next name */
626                 }
627         }
628 }
629
630 /*
631  * Compare name field of record.
632  */
633 static int
634 _nc_nfcmp(const char *nf, char *rec)
635 {
636         char *cp, tmp;
637         int ret;
638
639         for (cp = rec; *cp != ':'; cp++)
640                 ;
641
642         tmp = *(cp + 1);
643         *(cp + 1) = '\0';
644         ret = strcmp(nf, rec);
645         *(cp + 1) = tmp;
646
647         return (ret);
648 }
649 #endif /* HAVE_BSD_CGETENT */
650
651 /*
652  * Since ncurses provides its own 'tgetent()', we cannot use the native one.
653  * So we reproduce the logic to get down to cgetent() -- or our cut-down
654  * version of that -- to circumvent the problem of configuring against the
655  * termcap library.
656  */
657 #define USE_BSD_TGETENT 1
658
659 #if USE_BSD_TGETENT
660 /*
661  * Copyright (c) 1980, 1993
662  *      The Regents of the University of California.  All rights reserved.
663  *
664  * Redistribution and use in source and binary forms, with or without
665  * modification, are permitted provided that the following conditions
666  * are met:
667  * 1. Redistributions of source code must retain the above copyright
668  *    notice, this list of conditions and the following disclaimer.
669  * 2. Redistributions in binary form must reproduce the above copyright
670  *    notice, this list of conditions and the following disclaimer in the
671  *    documentation and/or other materials provided with the distribution.
672  * 3. All advertising materials mentioning features or use of this software
673  *    must display the following acknowledgment:
674  *      This product includes software developed by the University of
675  *      California, Berkeley and its contributors.
676  * 4. Neither the name of the University nor the names of its contributors
677  *    may be used to endorse or promote products derived from this software
678  *    without specific prior written permission.
679  *
680  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
681  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
682  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
683  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
684  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
685  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
686  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
687  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
688  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
689  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
690  * SUCH DAMAGE.
691  */
692
693 /* static char sccsid[] = "@(#)termcap.c        8.1 (Berkeley) 6/4/93" */
694
695 #define PBUFSIZ         512     /* max length of filename path */
696 #define PVECSIZ         32      /* max number of names in path */
697 #define TBUFSIZ (2048*2)
698
699 static char *tbuf;
700
701 /*
702  * On entry, srcp points to a non ':' character which is the beginning of the
703  * token, if any.  We'll try to return a string that doesn't end with a ':'.
704  */
705 static char *
706 get_tc_token(char **srcp, int *endp)
707 {
708         int ch;
709         bool found = FALSE;
710         char *s, *base;
711         char *tok = 0;
712
713         *endp = TRUE;
714         for (s = base = *srcp; *s != '\0'; ) {
715                 ch = *s++;
716                 if (ch == '\\') {
717                         if (*s == '\0') {
718                                 break;
719                         } else if (*s++ == '\n') {
720                                 while (isspace(*s))
721                                         s++;
722                         } else {
723                                 found = TRUE;
724                         }
725                 } else if (ch == ':') {
726                         if (found) {
727                                 tok = base;
728                                 s[-1] = '\0';
729                                 *srcp = s;
730                                 *endp = FALSE;
731                                 break;
732                         }
733                         base = s;
734                 } else if (isgraph(ch)) {
735                         found = TRUE;
736                 }
737         }
738
739         /* malformed entry may end without a ':' */
740         if (tok == 0 && found) {
741                 tok = base;
742         }
743
744         return tok;
745 }
746
747 static char *
748 copy_tc_token(char *dst, const char *src, size_t len)
749 {
750         int ch;
751
752         while ((ch = *src++) != '\0') {
753                 if (ch == '\\' && *src == '\n') {
754                         while (isspace(*src))
755                                 src++;
756                         continue;
757                 }
758                 if (--len == 0) {
759                         dst = 0;
760                         break;
761                 }
762                 *dst++ = ch;
763         }
764         return dst;
765 }
766
767 /*
768  * Get an entry for terminal name in buffer bp from the termcap file.
769  */
770 static int
771 _nc_tgetent(char *bp, char **sourcename, int *lineno, const char *name)
772 {
773         static char *the_source;
774
775         register char *p;
776         register char *cp;
777         char  *dummy;
778         char **fname;
779         char  *home;
780         int    i;
781         char   pathbuf[PBUFSIZ];        /* holds raw path of filenames */
782         char  *pathvec[PVECSIZ];        /* to point to names in pathbuf */
783         char **pvec;                    /* holds usable tail of path vector */
784         char  *termpath;
785
786         fname = pathvec;
787         pvec = pathvec;
788         tbuf = bp;
789         p = pathbuf;
790         cp = getenv("TERMCAP");
791
792         /*
793          * TERMCAP can have one of two things in it.  It can be the name of a
794          * file to use instead of /etc/termcap.  In this case it better start
795          * with a "/".  Or it can be an entry to use so we don't have to read
796          * the file.  In this case it has to already have the newlines crunched
797          * out.  If TERMCAP does not hold a file name then a path of names is
798          * searched instead.  The path is found in the TERMPATH variable, or
799          * becomes "$HOME/.termcap /etc/termcap" if no TERMPATH exists.
800          */
801         if (!is_pathname(cp)) { /* no TERMCAP or it holds an entry */
802                 if ((termpath = getenv("TERMPATH")) != 0) {
803                         strncpy(pathbuf, termpath, sizeof(pathbuf)-1);
804                 } else {
805                         if ((home = getenv("HOME")) != 0) { /* setup path */
806                                 p += strlen(home);      /* path, looking in */
807                                 strcpy(pathbuf, home);  /* $HOME first */
808                                 *p++ = '/';
809                         }       /* if no $HOME look in current directory */
810 #define MY_PATH_DEF     ".termcap /etc/termcap /usr/share/misc/termcap"
811                         strncpy(p, MY_PATH_DEF, (size_t)(PBUFSIZ - (p - pathbuf)));
812                 }
813         }
814         else                            /* user-defined name in TERMCAP */
815                 strncpy(pathbuf, cp, PBUFSIZ);  /* still can be tokenized */
816
817         *fname++ = pathbuf;     /* tokenize path into vector of names */
818         while (*++p) {
819                 if (*p == ' ' || *p == ':') {
820                         *p = '\0';
821                         while (*++p)
822                                 if (*p != ' ' && *p != ':')
823                                         break;
824                         if (*p == '\0')
825                                 break;
826                         *fname++ = p;
827                         if (fname >= pathvec + PVECSIZ) {
828                                 fname--;
829                                 break;
830                         }
831                 }
832         }
833         *fname = 0;                     /* mark end of vector */
834         if (is_pathname(cp)) {
835                 if (_nc_cgetset(cp) < 0) {
836                         return(TC_SYS_ERR);
837                 }
838         }
839
840         i = _nc_cgetent(&dummy, lineno, pathvec, name);
841
842         /* ncurses' termcap-parsing routines cannot handle multiple adjacent
843          * empty fields, and mistakenly use the last valid cap entry instead of
844          * the first (breaks tc= includes)
845          */
846         if (i >= 0) {
847                 char *pd, *ps, *tok;
848                 int endflag = FALSE;
849                 char *list[1023];
850                 size_t n, count = 0;
851
852                 pd = bp;
853                 ps = dummy;
854                 while (!endflag && (tok = get_tc_token(&ps, &endflag)) != 0) {
855                         bool ignore = FALSE;
856
857                         for (n = 1; n < count; n++) {
858                                 char *s = list[n];
859                                 if (s[0] == tok[0]
860                                  && s[1] == tok[1]) {
861                                         ignore = TRUE;
862                                         break;
863                                 }
864                         }
865                         if (ignore != TRUE) {
866                                 list[count++] = tok;
867                                 pd = copy_tc_token(pd, tok, TBUFSIZ - (2+pd-bp));
868                                 if (pd == 0) {
869                                         i = -1;
870                                         break;
871                                 }
872                                 *pd++ = ':';
873                                 *pd = '\0';
874                         }
875                 }
876         }
877
878         FreeIfNeeded(dummy);
879         FreeIfNeeded(the_source);
880         the_source = 0;
881
882         /* This is not related to the BSD cgetent(), but to fake up a suitable
883          * filename for ncurses' error reporting.  (If we are not using BSD
884          * cgetent, then it is the actual filename).
885          */
886         if (i >= 0) {
887                 the_source = malloc(strlen(pathvec[i]) + 1);
888                 if (the_source != 0)
889                         *sourcename = strcpy(the_source, pathvec[i]);
890         }
891
892         return(i);
893 }
894 #endif /* USE_BSD_TGETENT */
895 #endif /* USE_GETCAP */
896
897 int _nc_read_termcap_entry(const char *const tn, TERMTYPE *const tp)
898 {
899         int found = FALSE;
900         ENTRY   *ep;
901 #if USE_GETCAP_CACHE
902         char    cwd_buf[PATH_MAX];
903 #endif
904 #if USE_GETCAP
905         char    tc[TBUFSIZ];
906         static char     *source;
907         static int lineno;
908
909         /* we're using getcap(3) */
910         if (_nc_tgetent(tc, &source, &lineno, tn) < 0)
911                 return (ERR);
912
913         _nc_curr_line = lineno;
914         _nc_set_source(source);
915         _nc_read_entry_source((FILE *)0, tc, FALSE, FALSE, NULLHOOK);
916 #else
917         /*
918          * Here is what the 4.4BSD termcap(3) page prescribes:
919          *
920          * It will look in the environment for a TERMCAP variable.  If found,
921          * and the value does not begin with a slash, and the terminal type
922          * name is the same as the environment string TERM, the TERMCAP string
923          * is used instead of reading a termcap file.  If it does begin with a
924          * slash, the string is used as a path name of the termcap file to
925          * search.  If TERMCAP does not begin with a slash and name is
926          * different from TERM, tgetent() searches the files $HOME/.termcap and
927          * /usr/share/misc/termcap, in that order, unless the environment
928          * variable TERMPATH exists, in which case it specifies a list of file
929          * pathnames (separated by spaces or colons) to be searched instead.
930          *
931          * It goes on to state:
932          *
933          * Whenever multiple files are searched and a tc field occurs in the
934          * requested entry, the entry it names must be found in the same file
935          * or one of the succeeding files.
936          *
937          * However, this restriction is relaxed in ncurses; tc references to
938          * previous files are permitted.
939          *
940          * This routine returns 1 if an entry is found, 0 if not found, and -1
941          * if the database is not accessible.
942          */
943         FILE    *fp;
944 #define MAXPATHS        32
945         char    *tc, *termpaths[MAXPATHS];
946         int     filecount = 0;
947         bool    use_buffer = FALSE;
948         char    tc_buf[1024];
949         char    pathbuf[PATH_MAX];
950
951         if ((tc = getenv("TERMCAP")) != 0)
952         {
953                 if (is_pathname(tc))    /* interpret as a filename */
954                 {
955                         termpaths[0] = tc;
956                         termpaths[filecount = 1] = 0;
957                 }
958                 else if (_nc_name_match(tc, tn, "|:")) /* treat as a capability file */
959                 {
960                         use_buffer = TRUE;
961                         (void) sprintf(tc_buf, "%.*s\n", (int)sizeof(tc_buf)-2, tc);
962                 }
963                 else if ((tc = getenv("TERMPATH")) != 0)
964                 {
965                         char    *cp;
966
967                         for (cp = tc; *cp; cp++)
968                         {
969                                 if (*cp == ':')
970                                         *cp = '\0';
971                                 else if (cp == tc || cp[-1] == '\0')
972                                 {
973                                         if (filecount >= MAXPATHS - 1)
974                                                 return(-1);
975
976                                         termpaths[filecount++] = cp;
977                                 }
978                         }
979                         termpaths[filecount] = 0;
980                 }
981         }
982         else    /* normal case */
983         {
984                 char    envhome[PATH_MAX], *h;
985
986                 filecount = 0;
987
988                 /*
989                  * Probably /etc/termcap is a symlink to /usr/share/misc/termcap.
990                  * Avoid reading the same file twice.
991                  */
992                 if (access("/etc/termcap", R_OK) == 0)
993                         termpaths[filecount++] = "/etc/termcap";
994                 else if (access("/usr/share/misc/termcap", R_OK) == 0)
995                         termpaths[filecount++] = "/usr/share/misc/termcap";
996
997                 if ((h = getenv("HOME")) != (char *)NULL)
998                 {
999                 /* user's .termcap, if any, should override it */
1000                     (void) strncpy(envhome, h, PATH_MAX - 10);
1001                 envhome[PATH_MAX - 10] = '\0';
1002                 (void) sprintf(pathbuf, "%s/.termcap", envhome);
1003                 termpaths[filecount++] = pathbuf;
1004                 }
1005
1006                 termpaths[filecount] = 0;
1007         }
1008
1009         /* parse the sources */
1010         if (use_buffer)
1011         {
1012                 _nc_set_source("TERMCAP");
1013
1014                 /*
1015                  * We don't suppress warning messages here.  The presumption is
1016                  * that since it's just a single entry, they won't be a pain.
1017                  */
1018                 _nc_read_entry_source((FILE *)0, tc_buf, FALSE, FALSE, NULLHOOK);
1019         } else {
1020                 int     i;
1021
1022                 for (i = 0; i < filecount; i++) {
1023
1024                         T(("Looking for %s in %s", tn, termpaths[i]));
1025                         if ((fp = fopen(termpaths[i], "r")) != (FILE *)0)
1026                         {
1027                                 _nc_set_source(termpaths[i]);
1028
1029                                 /*
1030                                  * Suppress warning messages.  Otherwise you
1031                                  * get 400 lines of crap from archaic termcap
1032                                  * files as ncurses complains about all the
1033                                  * obsolete capabilities.
1034                                  */
1035                                 _nc_read_entry_source(fp, (char*)0, FALSE, TRUE, NULLHOOK);
1036
1037                                 (void) fclose(fp);
1038                         }
1039                 }
1040         }
1041 #endif /* USE_GETCAP */
1042
1043         if (_nc_head == 0)
1044                 return(ERR);
1045
1046         /* resolve all use references */
1047         _nc_resolve_uses();
1048
1049         /* find a terminal matching tn, if we can */
1050 #if USE_GETCAP_CACHE
1051         if (getcwd(cwd_buf, sizeof(cwd_buf)) != 0)
1052         {
1053                 _nc_set_writedir((char *)0); /* note: this does a chdir */
1054 #endif
1055                 for_entry_list(ep) {
1056                         if (_nc_name_match(ep->tterm.term_names, tn, "|:"))
1057                         {
1058                                 /*
1059                                  * Make a local copy of the terminal
1060                                  * capabilities.  Free all entry storage except
1061                                  * the string table for the loaded type (which
1062                                  * we disconnected from the list by NULLing out
1063                                  * ep->tterm.str_table above).
1064                                  */
1065                                 memcpy(tp, &ep->tterm, sizeof(TERMTYPE));
1066                                 ep->tterm.str_table = (char *)0;
1067
1068                                 /*
1069                                  * OK, now try to write the type to user's
1070                                  * terminfo directory.  Next time he loads
1071                                  * this, it will come through terminfo.
1072                                  *
1073                                  * Advantage:  Second and subsequent fetches of
1074                                  * this entry will be very fast.
1075                                  *
1076                                  * Disadvantage:  After the first time a
1077                                  * termcap type is loaded by its user, editing
1078                                  * it in the /etc/termcap file, or in TERMCAP,
1079                                  * or in a local ~/.termcap, will be
1080                                  * ineffective unless the terminfo entry is
1081                                  * explicitly removed.
1082                                  */
1083 #if USE_GETCAP_CACHE
1084                                 (void) _nc_write_entry(tp);
1085 #endif
1086                                 found = TRUE;
1087                                 break;
1088                         }
1089                 }
1090 #if USE_GETCAP_CACHE
1091                 chdir(cwd_buf);
1092         }
1093 #endif
1094
1095         _nc_free_entries(_nc_head);
1096         return(found);
1097 }