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