read_termcap.c revision 50276
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
63MODULE_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
88static int _nc_cgetmatch(char *, const char *);
89static int _nc_getent(char **, unsigned int *, int *, int, char **, int, const char *, int, char *);
90static 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
140static size_t	 topreclen;	/* toprec length */
141static char	*toprec;	/* Additional record specified by cgetset() */
142static 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 */
149static 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 */
180static 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 */
236static 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)
263static 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 */
592static 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 */
634static 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
700static 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 */
706static char *
707get_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
748static char *
749copy_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 */
771static 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
906static 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
917int _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
1114extern	void _nc_read_termcap(void);
1115	void _nc_read_termcap(void) { }
1116#endif	/* PURE_TERMINFO */
1117