xref: /original-bsd/lib/libterm/termcap.c (revision 1403a0cd)
1 #ifndef lint
2 static char sccsid[] = "@(#)termcap.c	4.4 (Berkeley) 01/09/85";
3 #endif
4 
5 #define	BUFSIZ		1024
6 #define MAXHOP		32	/* max number of tc= indirections */
7 #define	E_TERMCAP	"/etc/termcap"
8 
9 #include <ctype.h>
10 /*
11  * termcap - routines for dealing with the terminal capability data base
12  *
13  * BUG:		Should use a "last" pointer in tbuf, so that searching
14  *		for capabilities alphabetically would not be a n**2/2
15  *		process when large numbers of capabilities are given.
16  * Note:	If we add a last pointer now we will screw up the
17  *		tc capability. We really should compile termcap.
18  *
19  * Essentially all the work here is scanning and decoding escapes
20  * in string capabilities.  We don't use stdio because the editor
21  * doesn't, and because living w/o it is not hard.
22  */
23 
24 static	char *tbuf;
25 static	int hopcount;	/* detect infinite loops in termcap, init 0 */
26 char	*tskip();
27 char	*tgetstr();
28 char	*tdecode();
29 char	*getenv();
30 
31 /*
32  * Get an entry for terminal name in buffer bp,
33  * from the termcap file.  Parse is very rudimentary;
34  * we just notice escaped newlines.
35  */
36 tgetent(bp, name)
37 	char *bp, *name;
38 {
39 	register char *cp;
40 	register int c;
41 	register int i = 0, cnt = 0;
42 	char ibuf[BUFSIZ];
43 	int tf;
44 
45 	tbuf = bp;
46 	tf = -1;
47 #ifndef V6
48 	cp = getenv("TERMCAP");
49 	/*
50 	 * TERMCAP can have one of two things in it. It can be the
51 	 * name of a file to use instead of /etc/termcap. In this
52 	 * case it better start with a "/". Or it can be an entry to
53 	 * use so we don't have to read the file. In this case it
54 	 * has to already have the newlines crunched out.
55 	 */
56 	if (cp && *cp) {
57 		if (*cp == '/') {
58 			tf = open(cp, 0);
59 		} else {
60 			tbuf = cp;
61 			c = tnamatch(name);
62 			tbuf = bp;
63 			if (c) {
64 				strcpy(bp,cp);
65 				return(tnchktc());
66 			}
67 		}
68 	}
69 	if (tf < 0)
70 		tf = open(E_TERMCAP, 0);
71 #else
72 	tf = open(E_TERMCAP, 0);
73 #endif
74 	if (tf < 0)
75 		return (-1);
76 	for (;;) {
77 		cp = bp;
78 		for (;;) {
79 			if (i == cnt) {
80 				cnt = read(tf, ibuf, BUFSIZ);
81 				if (cnt <= 0) {
82 					close(tf);
83 					return (0);
84 				}
85 				i = 0;
86 			}
87 			c = ibuf[i++];
88 			if (c == '\n') {
89 				if (cp > bp && cp[-1] == '\\'){
90 					cp--;
91 					continue;
92 				}
93 				break;
94 			}
95 			if (cp >= bp+BUFSIZ) {
96 				write(2,"Termcap entry too long\n", 23);
97 				break;
98 			} else
99 				*cp++ = c;
100 		}
101 		*cp = 0;
102 
103 		/*
104 		 * The real work for the match.
105 		 */
106 		if (tnamatch(name)) {
107 			close(tf);
108 			return(tnchktc());
109 		}
110 	}
111 }
112 
113 /*
114  * tnchktc: check the last entry, see if it's tc=xxx. If so,
115  * recursively find xxx and append that entry (minus the names)
116  * to take the place of the tc=xxx entry. This allows termcap
117  * entries to say "like an HP2621 but doesn't turn on the labels".
118  * Note that this works because of the left to right scan.
119  */
120 tnchktc()
121 {
122 	register char *p, *q;
123 	char tcname[16];	/* name of similar terminal */
124 	char tcbuf[BUFSIZ];
125 	char *holdtbuf = tbuf;
126 	int l;
127 
128 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
129 	while (*--p != ':')
130 		if (p<tbuf) {
131 			write(2, "Bad termcap entry\n", 18);
132 			return (0);
133 		}
134 	p++;
135 	/* p now points to beginning of last field */
136 	if (p[0] != 't' || p[1] != 'c')
137 		return(1);
138 	strcpy(tcname,p+3);
139 	q = tcname;
140 	while (*q && *q != ':')
141 		q++;
142 	*q = 0;
143 	if (++hopcount > MAXHOP) {
144 		write(2, "Infinite tc= loop\n", 18);
145 		return (0);
146 	}
147 	if (tgetent(tcbuf, tcname) != 1) {
148 		hopcount = 0;		/* unwind recursion */
149 		return(0);
150 	}
151 	for (q=tcbuf; *q != ':'; q++)
152 		;
153 	l = p - holdtbuf + strlen(q);
154 	if (l > BUFSIZ) {
155 		write(2, "Termcap entry too long\n", 23);
156 		q[BUFSIZ - (p-tbuf)] = 0;
157 	}
158 	strcpy(p, q+1);
159 	tbuf = holdtbuf;
160 	hopcount = 0;			/* unwind recursion */
161 	return(1);
162 }
163 
164 /*
165  * Tnamatch deals with name matching.  The first field of the termcap
166  * entry is a sequence of names separated by |'s, so we compare
167  * against each such name.  The normal : terminator after the last
168  * name (before the first field) stops us.
169  */
170 tnamatch(np)
171 	char *np;
172 {
173 	register char *Np, *Bp;
174 
175 	Bp = tbuf;
176 	if (*Bp == '#')
177 		return(0);
178 	for (;;) {
179 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
180 			continue;
181 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
182 			return (1);
183 		while (*Bp && *Bp != ':' && *Bp != '|')
184 			Bp++;
185 		if (*Bp == 0 || *Bp == ':')
186 			return (0);
187 		Bp++;
188 	}
189 }
190 
191 /*
192  * Skip to the next field.  Notice that this is very dumb, not
193  * knowing about \: escapes or any such.  If necessary, :'s can be put
194  * into the termcap file in octal.
195  */
196 static char *
197 tskip(bp)
198 	register char *bp;
199 {
200 
201 	while (*bp && *bp != ':')
202 		bp++;
203 	if (*bp == ':')
204 		bp++;
205 	return (bp);
206 }
207 
208 /*
209  * Return the (numeric) option id.
210  * Numeric options look like
211  *	li#80
212  * i.e. the option string is separated from the numeric value by
213  * a # character.  If the option is not found we return -1.
214  * Note that we handle octal numbers beginning with 0.
215  */
216 tgetnum(id)
217 	char *id;
218 {
219 	register int i, base;
220 	register char *bp = tbuf;
221 
222 	for (;;) {
223 		bp = tskip(bp);
224 		if (*bp == 0)
225 			return (-1);
226 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
227 			continue;
228 		if (*bp == '@')
229 			return(-1);
230 		if (*bp != '#')
231 			continue;
232 		bp++;
233 		base = 10;
234 		if (*bp == '0')
235 			base = 8;
236 		i = 0;
237 		while (isdigit(*bp))
238 			i *= base, i += *bp++ - '0';
239 		return (i);
240 	}
241 }
242 
243 /*
244  * Handle a flag option.
245  * Flag options are given "naked", i.e. followed by a : or the end
246  * of the buffer.  Return 1 if we find the option, or 0 if it is
247  * not given.
248  */
249 tgetflag(id)
250 	char *id;
251 {
252 	register char *bp = tbuf;
253 
254 	for (;;) {
255 		bp = tskip(bp);
256 		if (!*bp)
257 			return (0);
258 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
259 			if (!*bp || *bp == ':')
260 				return (1);
261 			else if (*bp == '@')
262 				return(0);
263 		}
264 	}
265 }
266 
267 /*
268  * Get a string valued option.
269  * These are given as
270  *	cl=^Z
271  * Much decoding is done on the strings, and the strings are
272  * placed in area, which is a ref parameter which is updated.
273  * No checking on area overflow.
274  */
275 char *
276 tgetstr(id, area)
277 	char *id, **area;
278 {
279 	register char *bp = tbuf;
280 
281 	for (;;) {
282 		bp = tskip(bp);
283 		if (!*bp)
284 			return (0);
285 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
286 			continue;
287 		if (*bp == '@')
288 			return(0);
289 		if (*bp != '=')
290 			continue;
291 		bp++;
292 		return (tdecode(bp, area));
293 	}
294 }
295 
296 /*
297  * Tdecode does the grung work to decode the
298  * string capability escapes.
299  */
300 static char *
301 tdecode(str, area)
302 	register char *str;
303 	char **area;
304 {
305 	register char *cp;
306 	register int c;
307 	register char *dp;
308 	int i;
309 
310 	cp = *area;
311 	while ((c = *str++) && c != ':') {
312 		switch (c) {
313 
314 		case '^':
315 			c = *str++ & 037;
316 			break;
317 
318 		case '\\':
319 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
320 			c = *str++;
321 nextc:
322 			if (*dp++ == c) {
323 				c = *dp++;
324 				break;
325 			}
326 			dp++;
327 			if (*dp)
328 				goto nextc;
329 			if (isdigit(c)) {
330 				c -= '0', i = 2;
331 				do
332 					c <<= 3, c |= *str++ - '0';
333 				while (--i && isdigit(*str));
334 			}
335 			break;
336 		}
337 		*cp++ = c;
338 	}
339 	*cp++ = 0;
340 	str = *area;
341 	*area = cp;
342 	return (str);
343 }
344