xref: /csrg-svn/lib/libterm/termcap.c (revision 54673)
122072Sdist /*
236499Sbostic  * Copyright (c) 1980 The Regents of the University of California.
336499Sbostic  * All rights reserved.
436499Sbostic  *
542660Sbostic  * %sccs.include.redist.c%
622072Sdist  */
722072Sdist 
813300Ssam #ifndef lint
9*54673Storek static char sccsid[] = "@(#)termcap.c	5.6 (Berkeley) 07/03/92";
1036499Sbostic #endif /* not lint */
1113300Ssam 
1213300Ssam #define	BUFSIZ		1024
1313300Ssam #define MAXHOP		32	/* max number of tc= indirections */
1432153Sjak #define	PBUFSIZ		512	/* max length of filename path */
1532153Sjak #define	PVECSIZ		32	/* max number of names in path */
1613300Ssam 
1713300Ssam #include <ctype.h>
18*54673Storek #include <stdlib.h>
1937833Sbostic #include "pathnames.h"
2037833Sbostic 
2113300Ssam /*
2213300Ssam  * termcap - routines for dealing with the terminal capability data base
2313300Ssam  *
2413300Ssam  * BUG:		Should use a "last" pointer in tbuf, so that searching
2513300Ssam  *		for capabilities alphabetically would not be a n**2/2
2613300Ssam  *		process when large numbers of capabilities are given.
2713300Ssam  * Note:	If we add a last pointer now we will screw up the
2813300Ssam  *		tc capability. We really should compile termcap.
2913300Ssam  *
3013300Ssam  * Essentially all the work here is scanning and decoding escapes
3113300Ssam  * in string capabilities.  We don't use stdio because the editor
3213300Ssam  * doesn't, and because living w/o it is not hard.
3313300Ssam  */
3413300Ssam 
3513300Ssam static	char *tbuf;
3613300Ssam static	int hopcount;	/* detect infinite loops in termcap, init 0 */
3732153Sjak static	char pathbuf[PBUFSIZ];		/* holds raw path of filenames */
3832153Sjak static	char *pathvec[PVECSIZ];		/* to point to names in pathbuf */
3932153Sjak static	char **pvec;			/* holds usable tail of path vector */
4013300Ssam 
41*54673Storek static	char *tdecode __P((char *, char **));
42*54673Storek static	char *tskip __P ((char *));
43*54673Storek 
4413300Ssam /*
4532153Sjak  * Get an entry for terminal name in buffer bp from the termcap file.
4613300Ssam  */
4713300Ssam tgetent(bp, name)
4813300Ssam 	char *bp, *name;
4913300Ssam {
5032153Sjak 	register char *p;
5113300Ssam 	register char *cp;
5213300Ssam 	register int c;
5332153Sjak 	char *term, *home, *termpath;
5432153Sjak 	char **fname = pathvec;
5513300Ssam 
5632153Sjak 	pvec = pathvec;
5713300Ssam 	tbuf = bp;
5832153Sjak 	p = pathbuf;
5913300Ssam 	cp = getenv("TERMCAP");
6013300Ssam 	/*
6113300Ssam 	 * TERMCAP can have one of two things in it. It can be the
6213300Ssam 	 * name of a file to use instead of /etc/termcap. In this
6313300Ssam 	 * case it better start with a "/". Or it can be an entry to
6413300Ssam 	 * use so we don't have to read the file. In this case it
6532153Sjak 	 * has to already have the newlines crunched out.  If TERMCAP
6632153Sjak 	 * does not hold a file name then a path of names is searched
6732153Sjak 	 * instead.  The path is found in the TERMPATH variable, or
6832153Sjak 	 * becomes "$HOME/.termcap /etc/termcap" if no TERMPATH exists.
6913300Ssam 	 */
7032153Sjak 	if (!cp || *cp != '/') {	/* no TERMCAP or it holds an entry */
7132153Sjak 		if (termpath = getenv("TERMPATH"))
7232153Sjak 			strncpy(pathbuf, termpath, PBUFSIZ);
7332153Sjak 		else {
7432153Sjak 			if (home = getenv("HOME")) {	/* set up default */
7532153Sjak 				p += strlen(home);	/* path, looking in */
7632153Sjak 				strcpy(pathbuf, home);	/* $HOME first */
7732153Sjak 				*p++ = '/';
7832153Sjak 			}	/* if no $HOME look in current directory */
7937833Sbostic 			strncpy(p, _PATH_DEF, PBUFSIZ - (p - pathbuf));
8017676Sserge 		}
8113300Ssam 	}
8232153Sjak 	else				/* user-defined name in TERMCAP */
8332153Sjak 		strncpy(pathbuf, cp, PBUFSIZ);	/* still can be tokenized */
8437833Sbostic 
8532153Sjak 	*fname++ = pathbuf;	/* tokenize path into vector of names */
8632153Sjak 	while (*++p)
8732153Sjak 		if (*p == ' ' || *p == ':') {
8832153Sjak 			*p = '\0';
8932153Sjak 			while (*++p)
9032153Sjak 				if (*p != ' ' && *p != ':')
9132153Sjak 					break;
9232153Sjak 			if (*p == '\0')
9332153Sjak 				break;
9432153Sjak 			*fname++ = p;
9532153Sjak 			if (fname >= pathvec + PVECSIZ) {
9632153Sjak 				fname--;
9732153Sjak 				break;
9832153Sjak 			}
9932153Sjak 		}
10032153Sjak 	*fname = (char *) 0;			/* mark end of vector */
10132153Sjak 	if (cp && *cp && *cp != '/') {
10232153Sjak 		tbuf = cp;
10332153Sjak 		c = tnamatch(name);
10432153Sjak 		tbuf = bp;
10532153Sjak 		if (c) {
10632153Sjak 			strcpy(bp,cp);
10732153Sjak 			return (tnchktc());
10832153Sjak 		}
10932153Sjak 	}
11032153Sjak 	return (tfindent(bp, name));	/* find terminal entry in path */
11132153Sjak }
11232153Sjak 
11332153Sjak /*
11432153Sjak  * tfindent - reads through the list of files in pathvec as if they were one
11532153Sjak  * continuous file searching for terminal entries along the way.  It will
11632153Sjak  * participate in indirect recursion if the call to tnchktc() finds a tc=
11732153Sjak  * field, which is only searched for in the current file and files ocurring
11832153Sjak  * after it in pathvec.  The usable part of this vector is kept in the global
11932153Sjak  * variable pvec.  Terminal entries may not be broken across files.  Parse is
12032153Sjak  * very rudimentary; we just notice escaped newlines.
12132153Sjak  */
12232153Sjak tfindent(bp, name)
12332153Sjak 	char *bp, *name;
12432153Sjak {
12532153Sjak 	register char *cp;
12632153Sjak 	register int c;
12732153Sjak 	register int i, cnt;
12832153Sjak 	char ibuf[BUFSIZ];
12932153Sjak 	int opencnt = 0;
13032153Sjak 	int tf;
13132153Sjak 
13232153Sjak 	tbuf = bp;
13332153Sjak nextfile:
13432153Sjak 	i = cnt = 0;
13532153Sjak 	while (*pvec && (tf = open(*pvec, 0)) < 0)
13632153Sjak 		pvec++;
13732153Sjak 	if (!*pvec)
13832153Sjak 		return (opencnt ? 0 : -1);
13932153Sjak 	opencnt++;
14013300Ssam 	for (;;) {
14113300Ssam 		cp = bp;
14213300Ssam 		for (;;) {
14313300Ssam 			if (i == cnt) {
14413300Ssam 				cnt = read(tf, ibuf, BUFSIZ);
14513300Ssam 				if (cnt <= 0) {
14613300Ssam 					close(tf);
14732153Sjak 					pvec++;
14832153Sjak 					goto nextfile;
14913300Ssam 				}
15013300Ssam 				i = 0;
15113300Ssam 			}
15213300Ssam 			c = ibuf[i++];
15313300Ssam 			if (c == '\n') {
15413300Ssam 				if (cp > bp && cp[-1] == '\\'){
15513300Ssam 					cp--;
15613300Ssam 					continue;
15713300Ssam 				}
15813300Ssam 				break;
15913300Ssam 			}
16013300Ssam 			if (cp >= bp+BUFSIZ) {
16113300Ssam 				write(2,"Termcap entry too long\n", 23);
16213300Ssam 				break;
16313300Ssam 			} else
16413300Ssam 				*cp++ = c;
16513300Ssam 		}
16613300Ssam 		*cp = 0;
16713300Ssam 
16813300Ssam 		/*
16913300Ssam 		 * The real work for the match.
17013300Ssam 		 */
17113300Ssam 		if (tnamatch(name)) {
17213300Ssam 			close(tf);
17313300Ssam 			return(tnchktc());
17413300Ssam 		}
17513300Ssam 	}
17613300Ssam }
17713300Ssam 
17813300Ssam /*
17913300Ssam  * tnchktc: check the last entry, see if it's tc=xxx. If so,
18013300Ssam  * recursively find xxx and append that entry (minus the names)
18113300Ssam  * to take the place of the tc=xxx entry. This allows termcap
18213300Ssam  * entries to say "like an HP2621 but doesn't turn on the labels".
18313300Ssam  * Note that this works because of the left to right scan.
18413300Ssam  */
18513300Ssam tnchktc()
18613300Ssam {
18713300Ssam 	register char *p, *q;
18813300Ssam 	char tcname[16];	/* name of similar terminal */
18913300Ssam 	char tcbuf[BUFSIZ];
19013300Ssam 	char *holdtbuf = tbuf;
19113300Ssam 	int l;
19213300Ssam 
19313300Ssam 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
19413300Ssam 	while (*--p != ':')
19513300Ssam 		if (p<tbuf) {
19613300Ssam 			write(2, "Bad termcap entry\n", 18);
19713300Ssam 			return (0);
19813300Ssam 		}
19913300Ssam 	p++;
20013300Ssam 	/* p now points to beginning of last field */
20113300Ssam 	if (p[0] != 't' || p[1] != 'c')
20213300Ssam 		return(1);
20313300Ssam 	strcpy(tcname,p+3);
20413300Ssam 	q = tcname;
20517338Sralph 	while (*q && *q != ':')
20613300Ssam 		q++;
20713300Ssam 	*q = 0;
20813300Ssam 	if (++hopcount > MAXHOP) {
20913300Ssam 		write(2, "Infinite tc= loop\n", 18);
21013300Ssam 		return (0);
21113300Ssam 	}
21232153Sjak 	if (tfindent(tcbuf, tcname) != 1) {
21317584Ssam 		hopcount = 0;		/* unwind recursion */
21413300Ssam 		return(0);
21517584Ssam 	}
21613300Ssam 	for (q=tcbuf; *q != ':'; q++)
21713300Ssam 		;
21813300Ssam 	l = p - holdtbuf + strlen(q);
21913300Ssam 	if (l > BUFSIZ) {
22013300Ssam 		write(2, "Termcap entry too long\n", 23);
22113300Ssam 		q[BUFSIZ - (p-tbuf)] = 0;
22213300Ssam 	}
22313300Ssam 	strcpy(p, q+1);
22413300Ssam 	tbuf = holdtbuf;
22517584Ssam 	hopcount = 0;			/* unwind recursion */
22613300Ssam 	return(1);
22713300Ssam }
22813300Ssam 
22913300Ssam /*
23013300Ssam  * Tnamatch deals with name matching.  The first field of the termcap
23113300Ssam  * entry is a sequence of names separated by |'s, so we compare
23213300Ssam  * against each such name.  The normal : terminator after the last
23313300Ssam  * name (before the first field) stops us.
23413300Ssam  */
23513300Ssam tnamatch(np)
23613300Ssam 	char *np;
23713300Ssam {
23813300Ssam 	register char *Np, *Bp;
23913300Ssam 
24013300Ssam 	Bp = tbuf;
24113300Ssam 	if (*Bp == '#')
24213300Ssam 		return(0);
24313300Ssam 	for (;;) {
24413300Ssam 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
24513300Ssam 			continue;
24613300Ssam 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
24713300Ssam 			return (1);
24813300Ssam 		while (*Bp && *Bp != ':' && *Bp != '|')
24913300Ssam 			Bp++;
25013300Ssam 		if (*Bp == 0 || *Bp == ':')
25113300Ssam 			return (0);
25213300Ssam 		Bp++;
25313300Ssam 	}
25413300Ssam }
25513300Ssam 
25613300Ssam /*
25713300Ssam  * Skip to the next field.  Notice that this is very dumb, not
25813300Ssam  * knowing about \: escapes or any such.  If necessary, :'s can be put
25913300Ssam  * into the termcap file in octal.
26013300Ssam  */
26113300Ssam static char *
26213300Ssam tskip(bp)
26313300Ssam 	register char *bp;
26413300Ssam {
26513300Ssam 
26613300Ssam 	while (*bp && *bp != ':')
26713300Ssam 		bp++;
26813300Ssam 	if (*bp == ':')
26913300Ssam 		bp++;
27013300Ssam 	return (bp);
27113300Ssam }
27213300Ssam 
27313300Ssam /*
27413300Ssam  * Return the (numeric) option id.
27513300Ssam  * Numeric options look like
27613300Ssam  *	li#80
27713300Ssam  * i.e. the option string is separated from the numeric value by
27813300Ssam  * a # character.  If the option is not found we return -1.
27913300Ssam  * Note that we handle octal numbers beginning with 0.
28013300Ssam  */
28113300Ssam tgetnum(id)
28213300Ssam 	char *id;
28313300Ssam {
28413300Ssam 	register int i, base;
28513300Ssam 	register char *bp = tbuf;
28613300Ssam 
28713300Ssam 	for (;;) {
28813300Ssam 		bp = tskip(bp);
28913300Ssam 		if (*bp == 0)
29013300Ssam 			return (-1);
29113300Ssam 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
29213300Ssam 			continue;
29313300Ssam 		if (*bp == '@')
29413300Ssam 			return(-1);
29513300Ssam 		if (*bp != '#')
29613300Ssam 			continue;
29713300Ssam 		bp++;
29813300Ssam 		base = 10;
29913300Ssam 		if (*bp == '0')
30013300Ssam 			base = 8;
30113300Ssam 		i = 0;
30213300Ssam 		while (isdigit(*bp))
30313300Ssam 			i *= base, i += *bp++ - '0';
30413300Ssam 		return (i);
30513300Ssam 	}
30613300Ssam }
30713300Ssam 
30813300Ssam /*
30913300Ssam  * Handle a flag option.
31013300Ssam  * Flag options are given "naked", i.e. followed by a : or the end
31113300Ssam  * of the buffer.  Return 1 if we find the option, or 0 if it is
31213300Ssam  * not given.
31313300Ssam  */
31413300Ssam tgetflag(id)
31513300Ssam 	char *id;
31613300Ssam {
31713300Ssam 	register char *bp = tbuf;
31813300Ssam 
31913300Ssam 	for (;;) {
32013300Ssam 		bp = tskip(bp);
32113300Ssam 		if (!*bp)
32213300Ssam 			return (0);
32313300Ssam 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
32413300Ssam 			if (!*bp || *bp == ':')
32513300Ssam 				return (1);
32613300Ssam 			else if (*bp == '@')
32713300Ssam 				return(0);
32813300Ssam 		}
32913300Ssam 	}
33013300Ssam }
33113300Ssam 
33213300Ssam /*
33313300Ssam  * Get a string valued option.
33413300Ssam  * These are given as
33513300Ssam  *	cl=^Z
33613300Ssam  * Much decoding is done on the strings, and the strings are
33713300Ssam  * placed in area, which is a ref parameter which is updated.
33813300Ssam  * No checking on area overflow.
33913300Ssam  */
34013300Ssam char *
34113300Ssam tgetstr(id, area)
34213300Ssam 	char *id, **area;
34313300Ssam {
34413300Ssam 	register char *bp = tbuf;
34513300Ssam 
34613300Ssam 	for (;;) {
34713300Ssam 		bp = tskip(bp);
34813300Ssam 		if (!*bp)
34913300Ssam 			return (0);
35013300Ssam 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
35113300Ssam 			continue;
35213300Ssam 		if (*bp == '@')
35313300Ssam 			return(0);
35413300Ssam 		if (*bp != '=')
35513300Ssam 			continue;
35613300Ssam 		bp++;
35713300Ssam 		return (tdecode(bp, area));
35813300Ssam 	}
35913300Ssam }
36013300Ssam 
36113300Ssam /*
36213300Ssam  * Tdecode does the grung work to decode the
36313300Ssam  * string capability escapes.
36413300Ssam  */
36513300Ssam static char *
36613300Ssam tdecode(str, area)
36713300Ssam 	register char *str;
36813300Ssam 	char **area;
36913300Ssam {
37013300Ssam 	register char *cp;
37113300Ssam 	register int c;
37213300Ssam 	register char *dp;
37313300Ssam 	int i;
37413300Ssam 
37513300Ssam 	cp = *area;
37613300Ssam 	while ((c = *str++) && c != ':') {
37713300Ssam 		switch (c) {
37813300Ssam 
37913300Ssam 		case '^':
38013300Ssam 			c = *str++ & 037;
38113300Ssam 			break;
38213300Ssam 
38313300Ssam 		case '\\':
38413300Ssam 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
38513300Ssam 			c = *str++;
38613300Ssam nextc:
38713300Ssam 			if (*dp++ == c) {
38813300Ssam 				c = *dp++;
38913300Ssam 				break;
39013300Ssam 			}
39113300Ssam 			dp++;
39213300Ssam 			if (*dp)
39313300Ssam 				goto nextc;
39413300Ssam 			if (isdigit(c)) {
39513300Ssam 				c -= '0', i = 2;
39613300Ssam 				do
39713300Ssam 					c <<= 3, c |= *str++ - '0';
39813300Ssam 				while (--i && isdigit(*str));
39913300Ssam 			}
40013300Ssam 			break;
40113300Ssam 		}
40213300Ssam 		*cp++ = c;
40313300Ssam 	}
40413300Ssam 	*cp++ = 0;
40513300Ssam 	str = *area;
40613300Ssam 	*area = cp;
40713300Ssam 	return (str);
40813300Ssam }
409