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