xref: /csrg-svn/lib/libterm/termcap.c (revision 36499)
122072Sdist /*
2*36499Sbostic  * Copyright (c) 1980 The Regents of the University of California.
3*36499Sbostic  * All rights reserved.
4*36499Sbostic  *
5*36499Sbostic  * Redistribution and use in source and binary forms are permitted
6*36499Sbostic  * provided that the above copyright notice and this paragraph are
7*36499Sbostic  * duplicated in all such forms and that any documentation,
8*36499Sbostic  * advertising materials, and other materials related to such
9*36499Sbostic  * distribution and use acknowledge that the software was developed
10*36499Sbostic  * by the University of California, Berkeley.  The name of the
11*36499Sbostic  * University may not be used to endorse or promote products derived
12*36499Sbostic  * from this software without specific prior written permission.
13*36499Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14*36499Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15*36499Sbostic  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1622072Sdist  */
1722072Sdist 
1813300Ssam #ifndef lint
19*36499Sbostic static char sccsid[] = "@(#)termcap.c	5.3 (Berkeley) 01/03/89";
20*36499Sbostic #endif /* not lint */
2113300Ssam 
2213300Ssam #define	BUFSIZ		1024
2313300Ssam #define MAXHOP		32	/* max number of tc= indirections */
2432153Sjak #define	PBUFSIZ		512	/* max length of filename path */
2532153Sjak #define	PVECSIZ		32	/* max number of names in path */
2632153Sjak #define DEF_PATH	".termcap /etc/termcap"
2713300Ssam 
2813300Ssam #include <ctype.h>
2913300Ssam /*
3013300Ssam  * termcap - routines for dealing with the terminal capability data base
3113300Ssam  *
3213300Ssam  * BUG:		Should use a "last" pointer in tbuf, so that searching
3313300Ssam  *		for capabilities alphabetically would not be a n**2/2
3413300Ssam  *		process when large numbers of capabilities are given.
3513300Ssam  * Note:	If we add a last pointer now we will screw up the
3613300Ssam  *		tc capability. We really should compile termcap.
3713300Ssam  *
3813300Ssam  * Essentially all the work here is scanning and decoding escapes
3913300Ssam  * in string capabilities.  We don't use stdio because the editor
4013300Ssam  * doesn't, and because living w/o it is not hard.
4113300Ssam  */
4213300Ssam 
4313300Ssam static	char *tbuf;
4413300Ssam static	int hopcount;	/* detect infinite loops in termcap, init 0 */
4532153Sjak static	char pathbuf[PBUFSIZ];		/* holds raw path of filenames */
4632153Sjak static	char *pathvec[PVECSIZ];		/* to point to names in pathbuf */
4732153Sjak static	char **pvec;			/* holds usable tail of path vector */
4813300Ssam char	*tskip();
4913300Ssam char	*tgetstr();
5013300Ssam char	*tdecode();
5113300Ssam char	*getenv();
5213300Ssam 
5313300Ssam /*
5432153Sjak  * Get an entry for terminal name in buffer bp from the termcap file.
5513300Ssam  */
5613300Ssam tgetent(bp, name)
5713300Ssam 	char *bp, *name;
5813300Ssam {
5932153Sjak 	register char *p;
6013300Ssam 	register char *cp;
6113300Ssam 	register int c;
6232153Sjak 	char *term, *home, *termpath;
6332153Sjak 	char **fname = pathvec;
6413300Ssam 
6532153Sjak 	pvec = pathvec;
6613300Ssam 	tbuf = bp;
6732153Sjak 	p = pathbuf;
6813300Ssam #ifndef V6
6913300Ssam 	cp = getenv("TERMCAP");
7013300Ssam 	/*
7113300Ssam 	 * TERMCAP can have one of two things in it. It can be the
7213300Ssam 	 * name of a file to use instead of /etc/termcap. In this
7313300Ssam 	 * case it better start with a "/". Or it can be an entry to
7413300Ssam 	 * use so we don't have to read the file. In this case it
7532153Sjak 	 * has to already have the newlines crunched out.  If TERMCAP
7632153Sjak 	 * does not hold a file name then a path of names is searched
7732153Sjak 	 * instead.  The path is found in the TERMPATH variable, or
7832153Sjak 	 * becomes "$HOME/.termcap /etc/termcap" if no TERMPATH exists.
7913300Ssam 	 */
8032153Sjak 	if (!cp || *cp != '/') {	/* no TERMCAP or it holds an entry */
8132153Sjak 		if (termpath = getenv("TERMPATH"))
8232153Sjak 			strncpy(pathbuf, termpath, PBUFSIZ);
8332153Sjak 		else {
8432153Sjak 			if (home = getenv("HOME")) {	/* set up default */
8532153Sjak 				p += strlen(home);	/* path, looking in */
8632153Sjak 				strcpy(pathbuf, home);	/* $HOME first */
8732153Sjak 				*p++ = '/';
8832153Sjak 			}	/* if no $HOME look in current directory */
8932153Sjak 			strncpy(p, DEF_PATH, PBUFSIZ - (p - pathbuf));
9017676Sserge 		}
9113300Ssam 	}
9232153Sjak 	else				/* user-defined name in TERMCAP */
9332153Sjak 		strncpy(pathbuf, cp, PBUFSIZ);	/* still can be tokenized */
9413300Ssam #else
9532153Sjak 	strncpy(pathbuf, "/etc/termcap", PBUFSIZ);
9613300Ssam #endif
9732153Sjak 	*fname++ = pathbuf;	/* tokenize path into vector of names */
9832153Sjak 	while (*++p)
9932153Sjak 		if (*p == ' ' || *p == ':') {
10032153Sjak 			*p = '\0';
10132153Sjak 			while (*++p)
10232153Sjak 				if (*p != ' ' && *p != ':')
10332153Sjak 					break;
10432153Sjak 			if (*p == '\0')
10532153Sjak 				break;
10632153Sjak 			*fname++ = p;
10732153Sjak 			if (fname >= pathvec + PVECSIZ) {
10832153Sjak 				fname--;
10932153Sjak 				break;
11032153Sjak 			}
11132153Sjak 		}
11232153Sjak 	*fname = (char *) 0;			/* mark end of vector */
11332153Sjak 	if (cp && *cp && *cp != '/') {
11432153Sjak 		tbuf = cp;
11532153Sjak 		c = tnamatch(name);
11632153Sjak 		tbuf = bp;
11732153Sjak 		if (c) {
11832153Sjak 			strcpy(bp,cp);
11932153Sjak 			return (tnchktc());
12032153Sjak 		}
12132153Sjak 	}
12232153Sjak 	return (tfindent(bp, name));	/* find terminal entry in path */
12332153Sjak }
12432153Sjak 
12532153Sjak /*
12632153Sjak  * tfindent - reads through the list of files in pathvec as if they were one
12732153Sjak  * continuous file searching for terminal entries along the way.  It will
12832153Sjak  * participate in indirect recursion if the call to tnchktc() finds a tc=
12932153Sjak  * field, which is only searched for in the current file and files ocurring
13032153Sjak  * after it in pathvec.  The usable part of this vector is kept in the global
13132153Sjak  * variable pvec.  Terminal entries may not be broken across files.  Parse is
13232153Sjak  * very rudimentary; we just notice escaped newlines.
13332153Sjak  */
13432153Sjak tfindent(bp, name)
13532153Sjak 	char *bp, *name;
13632153Sjak {
13732153Sjak 	register char *cp;
13832153Sjak 	register int c;
13932153Sjak 	register int i, cnt;
14032153Sjak 	char ibuf[BUFSIZ];
14132153Sjak 	int opencnt = 0;
14232153Sjak 	int tf;
14332153Sjak 
14432153Sjak 	tbuf = bp;
14532153Sjak nextfile:
14632153Sjak 	i = cnt = 0;
14732153Sjak 	while (*pvec && (tf = open(*pvec, 0)) < 0)
14832153Sjak 		pvec++;
14932153Sjak 	if (!*pvec)
15032153Sjak 		return (opencnt ? 0 : -1);
15132153Sjak 	opencnt++;
15213300Ssam 	for (;;) {
15313300Ssam 		cp = bp;
15413300Ssam 		for (;;) {
15513300Ssam 			if (i == cnt) {
15613300Ssam 				cnt = read(tf, ibuf, BUFSIZ);
15713300Ssam 				if (cnt <= 0) {
15813300Ssam 					close(tf);
15932153Sjak 					pvec++;
16032153Sjak 					goto nextfile;
16113300Ssam 				}
16213300Ssam 				i = 0;
16313300Ssam 			}
16413300Ssam 			c = ibuf[i++];
16513300Ssam 			if (c == '\n') {
16613300Ssam 				if (cp > bp && cp[-1] == '\\'){
16713300Ssam 					cp--;
16813300Ssam 					continue;
16913300Ssam 				}
17013300Ssam 				break;
17113300Ssam 			}
17213300Ssam 			if (cp >= bp+BUFSIZ) {
17313300Ssam 				write(2,"Termcap entry too long\n", 23);
17413300Ssam 				break;
17513300Ssam 			} else
17613300Ssam 				*cp++ = c;
17713300Ssam 		}
17813300Ssam 		*cp = 0;
17913300Ssam 
18013300Ssam 		/*
18113300Ssam 		 * The real work for the match.
18213300Ssam 		 */
18313300Ssam 		if (tnamatch(name)) {
18413300Ssam 			close(tf);
18513300Ssam 			return(tnchktc());
18613300Ssam 		}
18713300Ssam 	}
18813300Ssam }
18913300Ssam 
19013300Ssam /*
19113300Ssam  * tnchktc: check the last entry, see if it's tc=xxx. If so,
19213300Ssam  * recursively find xxx and append that entry (minus the names)
19313300Ssam  * to take the place of the tc=xxx entry. This allows termcap
19413300Ssam  * entries to say "like an HP2621 but doesn't turn on the labels".
19513300Ssam  * Note that this works because of the left to right scan.
19613300Ssam  */
19713300Ssam tnchktc()
19813300Ssam {
19913300Ssam 	register char *p, *q;
20013300Ssam 	char tcname[16];	/* name of similar terminal */
20113300Ssam 	char tcbuf[BUFSIZ];
20213300Ssam 	char *holdtbuf = tbuf;
20313300Ssam 	int l;
20413300Ssam 
20513300Ssam 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
20613300Ssam 	while (*--p != ':')
20713300Ssam 		if (p<tbuf) {
20813300Ssam 			write(2, "Bad termcap entry\n", 18);
20913300Ssam 			return (0);
21013300Ssam 		}
21113300Ssam 	p++;
21213300Ssam 	/* p now points to beginning of last field */
21313300Ssam 	if (p[0] != 't' || p[1] != 'c')
21413300Ssam 		return(1);
21513300Ssam 	strcpy(tcname,p+3);
21613300Ssam 	q = tcname;
21717338Sralph 	while (*q && *q != ':')
21813300Ssam 		q++;
21913300Ssam 	*q = 0;
22013300Ssam 	if (++hopcount > MAXHOP) {
22113300Ssam 		write(2, "Infinite tc= loop\n", 18);
22213300Ssam 		return (0);
22313300Ssam 	}
22432153Sjak 	if (tfindent(tcbuf, tcname) != 1) {
22517584Ssam 		hopcount = 0;		/* unwind recursion */
22613300Ssam 		return(0);
22717584Ssam 	}
22813300Ssam 	for (q=tcbuf; *q != ':'; q++)
22913300Ssam 		;
23013300Ssam 	l = p - holdtbuf + strlen(q);
23113300Ssam 	if (l > BUFSIZ) {
23213300Ssam 		write(2, "Termcap entry too long\n", 23);
23313300Ssam 		q[BUFSIZ - (p-tbuf)] = 0;
23413300Ssam 	}
23513300Ssam 	strcpy(p, q+1);
23613300Ssam 	tbuf = holdtbuf;
23717584Ssam 	hopcount = 0;			/* unwind recursion */
23813300Ssam 	return(1);
23913300Ssam }
24013300Ssam 
24113300Ssam /*
24213300Ssam  * Tnamatch deals with name matching.  The first field of the termcap
24313300Ssam  * entry is a sequence of names separated by |'s, so we compare
24413300Ssam  * against each such name.  The normal : terminator after the last
24513300Ssam  * name (before the first field) stops us.
24613300Ssam  */
24713300Ssam tnamatch(np)
24813300Ssam 	char *np;
24913300Ssam {
25013300Ssam 	register char *Np, *Bp;
25113300Ssam 
25213300Ssam 	Bp = tbuf;
25313300Ssam 	if (*Bp == '#')
25413300Ssam 		return(0);
25513300Ssam 	for (;;) {
25613300Ssam 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
25713300Ssam 			continue;
25813300Ssam 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
25913300Ssam 			return (1);
26013300Ssam 		while (*Bp && *Bp != ':' && *Bp != '|')
26113300Ssam 			Bp++;
26213300Ssam 		if (*Bp == 0 || *Bp == ':')
26313300Ssam 			return (0);
26413300Ssam 		Bp++;
26513300Ssam 	}
26613300Ssam }
26713300Ssam 
26813300Ssam /*
26913300Ssam  * Skip to the next field.  Notice that this is very dumb, not
27013300Ssam  * knowing about \: escapes or any such.  If necessary, :'s can be put
27113300Ssam  * into the termcap file in octal.
27213300Ssam  */
27313300Ssam static char *
27413300Ssam tskip(bp)
27513300Ssam 	register char *bp;
27613300Ssam {
27713300Ssam 
27813300Ssam 	while (*bp && *bp != ':')
27913300Ssam 		bp++;
28013300Ssam 	if (*bp == ':')
28113300Ssam 		bp++;
28213300Ssam 	return (bp);
28313300Ssam }
28413300Ssam 
28513300Ssam /*
28613300Ssam  * Return the (numeric) option id.
28713300Ssam  * Numeric options look like
28813300Ssam  *	li#80
28913300Ssam  * i.e. the option string is separated from the numeric value by
29013300Ssam  * a # character.  If the option is not found we return -1.
29113300Ssam  * Note that we handle octal numbers beginning with 0.
29213300Ssam  */
29313300Ssam tgetnum(id)
29413300Ssam 	char *id;
29513300Ssam {
29613300Ssam 	register int i, base;
29713300Ssam 	register char *bp = tbuf;
29813300Ssam 
29913300Ssam 	for (;;) {
30013300Ssam 		bp = tskip(bp);
30113300Ssam 		if (*bp == 0)
30213300Ssam 			return (-1);
30313300Ssam 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
30413300Ssam 			continue;
30513300Ssam 		if (*bp == '@')
30613300Ssam 			return(-1);
30713300Ssam 		if (*bp != '#')
30813300Ssam 			continue;
30913300Ssam 		bp++;
31013300Ssam 		base = 10;
31113300Ssam 		if (*bp == '0')
31213300Ssam 			base = 8;
31313300Ssam 		i = 0;
31413300Ssam 		while (isdigit(*bp))
31513300Ssam 			i *= base, i += *bp++ - '0';
31613300Ssam 		return (i);
31713300Ssam 	}
31813300Ssam }
31913300Ssam 
32013300Ssam /*
32113300Ssam  * Handle a flag option.
32213300Ssam  * Flag options are given "naked", i.e. followed by a : or the end
32313300Ssam  * of the buffer.  Return 1 if we find the option, or 0 if it is
32413300Ssam  * not given.
32513300Ssam  */
32613300Ssam tgetflag(id)
32713300Ssam 	char *id;
32813300Ssam {
32913300Ssam 	register char *bp = tbuf;
33013300Ssam 
33113300Ssam 	for (;;) {
33213300Ssam 		bp = tskip(bp);
33313300Ssam 		if (!*bp)
33413300Ssam 			return (0);
33513300Ssam 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
33613300Ssam 			if (!*bp || *bp == ':')
33713300Ssam 				return (1);
33813300Ssam 			else if (*bp == '@')
33913300Ssam 				return(0);
34013300Ssam 		}
34113300Ssam 	}
34213300Ssam }
34313300Ssam 
34413300Ssam /*
34513300Ssam  * Get a string valued option.
34613300Ssam  * These are given as
34713300Ssam  *	cl=^Z
34813300Ssam  * Much decoding is done on the strings, and the strings are
34913300Ssam  * placed in area, which is a ref parameter which is updated.
35013300Ssam  * No checking on area overflow.
35113300Ssam  */
35213300Ssam char *
35313300Ssam tgetstr(id, area)
35413300Ssam 	char *id, **area;
35513300Ssam {
35613300Ssam 	register char *bp = tbuf;
35713300Ssam 
35813300Ssam 	for (;;) {
35913300Ssam 		bp = tskip(bp);
36013300Ssam 		if (!*bp)
36113300Ssam 			return (0);
36213300Ssam 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
36313300Ssam 			continue;
36413300Ssam 		if (*bp == '@')
36513300Ssam 			return(0);
36613300Ssam 		if (*bp != '=')
36713300Ssam 			continue;
36813300Ssam 		bp++;
36913300Ssam 		return (tdecode(bp, area));
37013300Ssam 	}
37113300Ssam }
37213300Ssam 
37313300Ssam /*
37413300Ssam  * Tdecode does the grung work to decode the
37513300Ssam  * string capability escapes.
37613300Ssam  */
37713300Ssam static char *
37813300Ssam tdecode(str, area)
37913300Ssam 	register char *str;
38013300Ssam 	char **area;
38113300Ssam {
38213300Ssam 	register char *cp;
38313300Ssam 	register int c;
38413300Ssam 	register char *dp;
38513300Ssam 	int i;
38613300Ssam 
38713300Ssam 	cp = *area;
38813300Ssam 	while ((c = *str++) && c != ':') {
38913300Ssam 		switch (c) {
39013300Ssam 
39113300Ssam 		case '^':
39213300Ssam 			c = *str++ & 037;
39313300Ssam 			break;
39413300Ssam 
39513300Ssam 		case '\\':
39613300Ssam 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
39713300Ssam 			c = *str++;
39813300Ssam nextc:
39913300Ssam 			if (*dp++ == c) {
40013300Ssam 				c = *dp++;
40113300Ssam 				break;
40213300Ssam 			}
40313300Ssam 			dp++;
40413300Ssam 			if (*dp)
40513300Ssam 				goto nextc;
40613300Ssam 			if (isdigit(c)) {
40713300Ssam 				c -= '0', i = 2;
40813300Ssam 				do
40913300Ssam 					c <<= 3, c |= *str++ - '0';
41013300Ssam 				while (--i && isdigit(*str));
41113300Ssam 			}
41213300Ssam 			break;
41313300Ssam 		}
41413300Ssam 		*cp++ = c;
41513300Ssam 	}
41613300Ssam 	*cp++ = 0;
41713300Ssam 	str = *area;
41813300Ssam 	*area = cp;
41913300Ssam 	return (str);
42013300Ssam }
421