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