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