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