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