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