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
20static char
21sccsid[] = "@(#)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
50static	char *tbuf;
51static	int hopcount;	/* detect infinite loops in termcap, init 0 */
52
53/* forward declarations */
54static char *tdecode(char *, char **);
55static void tngetsize(char *);
56static char *tskip(char *bp);
57static char *appendsmalldec(char *, int);
58int tnamatch(char *);
59int 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
67int
68tgetent(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
153int
154tnchktc(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
208int
209tnamatch(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
235static char *
236tskip(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
260int
261tgetnum(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
294int
295tgetflag(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
321char *
322tgetstr(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
346static char *
347tdecode(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++;
365nextc:
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
391static void
392tngetsize(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
423static char *
424appendsmalldec(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