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