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