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