xref: /illumos-gate/usr/src/ucblib/libtermcap/termcap.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * Copyright 1997 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
7 /*	  All Rights Reserved  	*/
8 
9 /*
10  * Copyright (c) 1980 Regents of the University of California.
11  * All rights reserved.  The Berkeley software License Agreement
12  * specifies the terms and conditions for redistribution.
13  */
14 
15 #pragma ident	"%Z%%M%	%I%	%E% SMI"
16 
17 /*LINTLIBRARY*/
18 
19 #if 0
20 static char
21 sccsid[] = "@(#)termcap.c 1.11 88/02/08 SMI"; /* from UCB 5.1 6/5/85 */
22 #endif
23 
24 #define	BUFSIZ		1024
25 #define	MAXHOP		32	/* max number of tc= indirections */
26 #define	E_TERMCAP	"/etc/termcap"
27 
28 #include <sys/types.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <stddef.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <ctype.h>
35 
36 /*
37  * termcap - routines for dealing with the terminal capability data base
38  *
39  * BUG:		Should use a "last" pointer in tbuf, so that searching
40  *		for capabilities alphabetically would not be a n**2/2
41  *		process when large numbers of capabilities are given.
42  * Note:	If we add a last pointer now we will screw up the
43  *		tc capability. We really should compile termcap.
44  *
45  * Essentially all the work here is scanning and decoding escapes
46  * in string capabilities.  We don't use stdio because the editor
47  * doesn't, and because living w/o it is not hard.
48  */
49 
50 static	char *tbuf;
51 static	int hopcount;	/* detect infinite loops in termcap, init 0 */
52 
53 /* forward declarations */
54 static char *tdecode(char *, char **);
55 static void tngetsize(char *);
56 static char *tskip(char *bp);
57 static char *appendsmalldec(char *, int);
58 int tnamatch(char *);
59 int tnchktc(void);
60 
61 /*
62  * Get an entry for terminal name in buffer bp,
63  * from the termcap file.  Parse is very rudimentary;
64  * we just notice escaped newlines.
65  */
66 
67 int
68 tgetent(char *bp, char *name)
69 {
70 	char *cp;
71 	int c;
72 	int i = 0;
73 	ssize_t cnt = 0;
74 	char ibuf[BUFSIZ];
75 	int tf;
76 
77 	tbuf = bp;
78 	tf = -1;
79 #ifndef V6
80 	cp = getenv("TERMCAP");
81 	/*
82 	 * TERMCAP can have one of two things in it. It can be the
83 	 * name of a file to use instead of /etc/termcap. In this
84 	 * case it better start with a "/". Or it can be an entry to
85 	 * use so we don't have to read the file. In this case it
86 	 * has to already have the newlines crunched out.
87 	 */
88 	if (cp && *cp) {
89 		if (*cp == '/') {
90 			tf = open(cp, 0);
91 		} else {
92 			tbuf = cp;
93 			c = tnamatch(name);
94 			tbuf = bp;
95 			if (c) {
96 				(void) strcpy(bp, cp);
97 				return (tnchktc());
98 			}
99 		}
100 	}
101 	if (tf < 0)
102 		tf = open(E_TERMCAP, 0);
103 #else
104 	tf = open(E_TERMCAP, 0);
105 #endif
106 	if (tf < 0)
107 		return (-1);
108 	for (;;) {
109 		cp = bp;
110 		for (;;) {
111 			if (i == cnt) {
112 				cnt = read(tf, ibuf, BUFSIZ);
113 				if (cnt <= 0) {
114 					(void) close(tf);
115 					return (0);
116 				}
117 				i = 0;
118 			}
119 			c = ibuf[i++];
120 			if (c == '\n') {
121 				if (cp > bp && cp[-1] == '\\') {
122 					cp--;
123 					continue;
124 				}
125 				break;
126 			}
127 			if (cp >= bp+BUFSIZ) {
128 				(void) write(2, "Termcap entry too long\n", 23);
129 				break;
130 			} else
131 				*cp++ = (char) c;
132 		}
133 		*cp = 0;
134 
135 		/*
136 		 * The real work for the match.
137 		 */
138 		if (tnamatch(name)) {
139 			(void) close(tf);
140 			return (tnchktc());
141 		}
142 	}
143 }
144 
145 /*
146  * tnchktc: check the last entry, see if it's tc=xxx. If so,
147  * recursively find xxx and append that entry (minus the names)
148  * to take the place of the tc=xxx entry. This allows termcap
149  * entries to say "like an HP2621 but doesn't turn on the labels".
150  * Note that this works because of the left to right scan.
151  */
152 
153 int
154 tnchktc(void)
155 {
156 	char *p, *q;
157 	char tcname[16];	/* name of similar terminal */
158 	char tcbuf[BUFSIZ];
159 	char *holdtbuf = tbuf;
160 	ptrdiff_t l;
161 
162 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
163 	while (*--p != ':')
164 		if (p < tbuf) {
165 			(void) write(2, "Bad termcap entry\n", 18);
166 			return (0);
167 		}
168 	p++;
169 	/* p now points to beginning of last field */
170 	if (p[0] != 't' || p[1] != 'c') {
171 		tngetsize(tbuf);
172 		return (1);
173 	}
174 	(void) strcpy(tcname, p+3);
175 	q = tcname;
176 	while (*q && *q != ':')
177 		q++;
178 	*q = 0;
179 	if (++hopcount > MAXHOP) {
180 		(void) write(2, "Infinite tc= loop\n", 18);
181 		return (0);
182 	}
183 	if (tgetent(tcbuf, tcname) != 1) {
184 		hopcount = 0;		/* unwind recursion */
185 		return (0);
186 	}
187 	for (q = tcbuf; *q != ':'; q++)
188 		;
189 	l = p - holdtbuf + strlen(q);
190 	if (l > BUFSIZ) {
191 		(void) write(2, "Termcap entry too long\n", 23);
192 		q[BUFSIZ - (p-tbuf)] = 0;
193 	}
194 	(void) strcpy(p, q+1);
195 	tbuf = holdtbuf;
196 	hopcount = 0;			/* unwind recursion */
197 	tngetsize(tbuf);
198 	return (1);
199 }
200 
201 /*
202  * Tnamatch deals with name matching.  The first field of the termcap
203  * entry is a sequence of names separated by |'s, so we compare
204  * against each such name.  The normal : terminator after the last
205  * name (before the first field) stops us.
206  */
207 
208 int
209 tnamatch(char *np)
210 {
211 	char *Np, *Bp;
212 
213 	Bp = tbuf;
214 	if (*Bp == '#')
215 		return (0);
216 	for (;;) {
217 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
218 			continue;
219 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
220 			return (1);
221 		while (*Bp && *Bp != ':' && *Bp != '|')
222 			Bp++;
223 		if (*Bp == 0 || *Bp == ':')
224 			return (0);
225 		Bp++;
226 	}
227 }
228 
229 /*
230  * Skip to the next field.  Notice that this is very dumb, not
231  * knowing about \: escapes or any such.  If necessary, :'s can be put
232  * into the termcap file in octal.
233  */
234 
235 static char *
236 tskip(char *bp)
237 {
238 
239 	while (*bp && *bp != ':')
240 		bp++;
241 	if (*bp == ':') {
242 		do {
243 			bp++;
244 			while (isspace(*bp))
245 				bp++;
246 		} while (*bp == ':');
247 	}
248 	return (bp);
249 }
250 
251 /*
252  * Return the (numeric) option id.
253  * Numeric options look like
254  *	li#80
255  * i.e. the option string is separated from the numeric value by
256  * a # character.  If the option is not found we return -1.
257  * Note that we handle octal numbers beginning with 0.
258  */
259 
260 int
261 tgetnum(char *id)
262 {
263 	int i, base;
264 	char *bp = tbuf;
265 
266 	for (;;) {
267 		bp = tskip(bp);
268 		if (*bp == 0)
269 			return (-1);
270 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
271 			continue;
272 		if (*bp == '@')
273 			return (-1);
274 		if (*bp != '#')
275 			continue;
276 		bp++;
277 		base = 10;
278 		if (*bp == '0')
279 			base = 8;
280 		i = 0;
281 		while (isdigit(*bp))
282 			i *= base, i += *bp++ - '0';
283 		return (i);
284 	}
285 }
286 
287 /*
288  * Handle a flag option.
289  * Flag options are given "naked", i.e. followed by a : or the end
290  * of the buffer.  Return 1 if we find the option, or 0 if it is
291  * not given.
292  */
293 
294 int
295 tgetflag(char *id)
296 {
297 	char *bp = tbuf;
298 
299 	for (;;) {
300 		bp = tskip(bp);
301 		if (!*bp)
302 			return (0);
303 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
304 			if (!*bp || *bp == ':')
305 				return (1);
306 			else if (*bp == '@')
307 				return (0);
308 		}
309 	}
310 }
311 
312 /*
313  * Get a string valued option.
314  * These are given as
315  *	cl=^Z
316  * Much decoding is done on the strings, and the strings are
317  * placed in area, which is a ref parameter which is updated.
318  * No checking on area overflow.
319  */
320 
321 char *
322 tgetstr(char *id, char **area)
323 {
324 	char *bp = tbuf;
325 
326 	for (;;) {
327 		bp = tskip(bp);
328 		if (!*bp)
329 			return (0);
330 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
331 			continue;
332 		if (*bp == '@')
333 			return (0);
334 		if (*bp != '=')
335 			continue;
336 		bp++;
337 		return (tdecode(bp, area));
338 	}
339 }
340 
341 /*
342  * Tdecode does the grung work to decode the
343  * string capability escapes.
344  */
345 
346 static char *
347 tdecode(char *str, char **area)
348 {
349 	char *cp;
350 	int c;
351 	char *dp;
352 	int i;
353 
354 	cp = *area;
355 	while (((c = *str++) != 0) && c != ':') {
356 		switch (c) {
357 
358 		case '^':
359 			c = *str++ & 037;
360 			break;
361 
362 		case '\\':
363 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
364 			c = *str++;
365 nextc:
366 			if (*dp++ == c) {
367 				c = *dp++;
368 				break;
369 			}
370 			dp++;
371 			if (*dp)
372 				goto nextc;
373 			if (isdigit(c)) {
374 				c -= '0', i = 2;
375 				do
376 					c <<= 3, c |= *str++ - '0';
377 				while (--i && isdigit(*str));
378 			}
379 			break;
380 		}
381 		*cp++ = (char) c;
382 	}
383 	*cp++ = 0;
384 	str = *area;
385 	*area = cp;
386 	return (str);
387 }
388 
389 #include <sys/ioctl.h>
390 
391 static void
392 tngetsize(char *bp)
393 {
394 	struct winsize ws;
395 	char *np, *cp;
396 
397 	if (ioctl(1, TIOCGWINSZ, (char *)&ws) < 0)
398 		return;
399 	if (ws.ws_row == 0 || ws.ws_col == 0 ||
400 	    ws.ws_row > 999 || ws.ws_col > 999)
401 		return;
402 	cp = index(bp, ':');	/* find start of description */
403 	bp = rindex(bp, 0);	/* find end of description */
404 	np = bp + 15;		/* allow enough room for stuff below */
405 	while (bp >= cp)	/* move description right 15 chars */
406 		*np-- = *bp--;
407 	bp++;			/* bp now points to where ':' used to be */
408 	*bp++ = ':';
409 	*bp++ = 'l';
410 	*bp++ = 'i';
411 	*bp++ = '#';
412 	bp = appendsmalldec(bp, ws.ws_row);
413 	*bp++ = ':';
414 	*bp++ = 'c';
415 	*bp++ = 'o';
416 	*bp++ = '#';
417 	bp = appendsmalldec(bp, ws.ws_col);
418 	*bp++ = ':';
419 	while (bp <= np)	/* space fill to start of orig description */
420 		*bp++ = ' ';
421 }
422 
423 static char *
424 appendsmalldec(char *bp, int val)
425 {
426 	int	i;
427 
428 	if ((i = val / 100) != 0) {
429 		*bp++ = '0' + i;
430 		val %= 100;
431 		if (0 == val / 10)
432 			*bp++ = '0'; /* place holder because next test fails */
433 	}
434 	if ((i = val / 10) != 0)
435 		*bp++ = '0' + i;
436 	*bp++ = '0' + val % 10;
437 	return (bp);
438 }
439