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