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.2 (Berkeley) 04/28/95";
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  * getcap-style interface for the old printcap routines.
30  *
31  * !!!USE THIS INTERFACE ONLY IF YOU DON'T HAVE THE REAL GETCAP!!!
32  */
33 
34 static char *pbp;		/* pointer into pbuf for pgetstr() */
35 static char pbuf[BUFSIZ];	/* buffer for capability strings */
36 extern char line[];		/* buffer for printcap entries */
37 
38 int
39 cgetnext(bp, db_array)
40         register char **bp;
41 	char **db_array;
42 {
43 	int ret;
44 	char *strdup();
45 
46 	pbp = pbuf;
47 	ret = getprent(line);
48 	*bp = strdup(line);
49 	return (ret);
50 }
51 
52 int
53 cgetent(bp, db_array, name)
54 	char **bp, **db_array, *name;
55 {
56 	int i;
57 
58 	*bp = line;
59 	pbp = pbuf;
60 	i = pgetent(*bp, name);
61 	if (i < 0)
62 		return (-2);
63 	else if (i == 0)
64 		return (-1);
65 	else
66 		return (0);
67 }
68 
69 char *
70 cgetcap(buf, cap, type)
71 	char *buf, *cap;
72 	int type;
73 {
74 	return ((char *) pgetflag(cap));
75 }
76 
77 int
78 cgetstr(buf, cap, str)
79 	char *buf, *cap;
80 	char **str;
81 {
82 	char *pgetstr __P((char *, char **));
83 
84 	if (pbp >= pbuf+BUFSIZ) {
85 		write(2, "Capability string buffer overflow\n", 34);
86 		return (-1);
87 	}
88 	return ((*str = pgetstr(cap, &pbp)) == NULL ? -1 : 0);
89 }
90 
91 int
92 cgetnum(buf, cap, num)
93 	char *buf, *cap;
94 	long *num;
95 {
96 	return ((*num = pgetnum(cap)) < 0 ? -1 : 0);
97 }
98 
99 int
100 cgetclose()
101 {
102 	void endprent __P((void));
103 
104 	endprent();
105 	return (0);
106 }
107 
108 
109 /*
110  * termcap - routines for dealing with the terminal capability data base
111  *
112  * BUG:		Should use a "last" pointer in tbuf, so that searching
113  *		for capabilities alphabetically would not be a n**2/2
114  *		process when large numbers of capabilities are given.
115  * Note:	If we add a last pointer now we will screw up the
116  *		tc capability. We really should compile termcap.
117  *
118  * Essentially all the work here is scanning and decoding escapes
119  * in string capabilities.  We don't use stdio because the editor
120  * doesn't, and because living w/o it is not hard.
121  */
122 
123 #define PRINTCAP
124 
125 #ifdef PRINTCAP
126 #define tgetent	pgetent
127 #define tskip	pskip
128 #define tgetstr	pgetstr
129 #define tdecode pdecode
130 #define tgetnum	pgetnum
131 #define	tgetflag pgetflag
132 #define tdecode pdecode
133 #define tnchktc	pnchktc
134 #define	tnamatch pnamatch
135 #define V6
136 #endif
137 
138 static	FILE *pfp = NULL;	/* printcap data base file pointer */
139 static	char *tbuf;
140 static	int hopcount;		/* detect infinite loops in termcap, init 0 */
141 static	int tf;
142 
143 char *tgetstr __P((char *, char **));
144 static char *tskip __P((char *));
145 static char *tdecode __P((char *, char **));
146 
147 /*
148  * Similar to tgetent except it returns the next enrty instead of
149  * doing a lookup.
150  */
151 int
152 getprent(bp)
153 	register char *bp;
154 {
155 	register int c, skip = 0;
156 
157 	if (pfp == NULL && (pfp = fopen(_PATH_PRINTCAP, "r")) == NULL)
158 		return(-1);
159 	tbuf = bp;
160 	for (;;) {
161 		switch (c = getc(pfp)) {
162 		case EOF:
163 			fclose(pfp);
164 			pfp = NULL;
165 			return(0);
166 		case '\n':
167 			if (bp == tbuf) {
168 				skip = 0;
169 				continue;
170 			}
171 			if (bp[-1] == '\\') {
172 				bp--;
173 				continue;
174 			}
175 			*bp = '\0';
176 			return(1);
177 		case '#':
178 			if (bp == tbuf)
179 				skip++;
180 		default:
181 			if (skip)
182 				continue;
183 			if (bp >= tbuf+BUFSIZ) {
184 				write(2, "Termcap entry too long\n", 23);
185 				*bp = '\0';
186 				return(1);
187 			}
188 			*bp++ = c;
189 		}
190 	}
191 }
192 
193 void
194 endprent()
195 {
196 	if (pfp != NULL) {
197 		/*
198 		 * Can't use fclose here because on POSIX-compliant
199 		 * systems, fclose() causes the file pointer of the
200 		 * underlying file descriptor (which is possibly shared
201 		 * with a parent process) to be adjusted, and this
202 		 * reeks havoc in the parent because it doesn't know
203 		 * the file pointer has changed.
204 		 */
205 		(void) close(fileno(pfp));
206 		pfp = NULL;
207 	}
208 }
209 
210 /*
211  * Get an entry for terminal name in buffer bp,
212  * from the termcap file.  Parse is very rudimentary;
213  * we just notice escaped newlines.
214  */
215 int
216 tgetent(bp, name)
217 	char *bp, *name;
218 {
219 	register char *cp;
220 	register int c;
221 	register int i = 0, cnt = 0;
222 	char ibuf[BUFSIZ];
223 
224 	tbuf = bp;
225 #ifndef V6
226 	cp = getenv("TERMCAP");
227 	/*
228 	 * TERMCAP can have one of two things in it. It can be the
229 	 * name of a file to use instead of /etc/termcap. In this
230 	 * case it better start with a "/". Or it can be an entry to
231 	 * use so we don't have to read the file. In this case it
232 	 * has to already have the newlines crunched out.
233 	 */
234 	if (cp && *cp) {
235 		if (*cp!='/') {
236 			cp2 = getenv("TERM");
237 			if (cp2==(char *) 0 || strcmp(name,cp2)==0) {
238 				strcpy(bp,cp);
239 				return(tnchktc());
240 			} else {
241 				tf = open(_PATH_PRINTCAP, 0);
242 			}
243 		} else
244 			tf = open(cp, 0);
245 	}
246 #endif
247 	if (tf==0)
248 		tf = open(_PATH_PRINTCAP, 0);
249 	if (tf < 0)
250 		return (-1);
251 	for (;;) {
252 		cp = bp;
253 		for (;;) {
254 			if (i == cnt) {
255 				cnt = read(tf, ibuf, BUFSIZ);
256 				if (cnt <= 0) {
257 					close(tf);
258 					tf = 0;
259 					return (0);
260 				}
261 				i = 0;
262 			}
263 			c = ibuf[i++];
264 			if (c == '\n') {
265 				if (cp > bp && cp[-1] == '\\'){
266 					cp--;
267 					continue;
268 				}
269 				break;
270 			}
271 			if (cp >= bp+BUFSIZ) {
272 				write(2,"Termcap entry too long\n", 23);
273 				break;
274 			} else
275 				*cp++ = c;
276 		}
277 		*cp = 0;
278 
279 		/*
280 		 * The real work for the match.
281 		 */
282 		if (tnamatch(name)) {
283 			lseek(tf, 0L, 0);
284 			i = tnchktc();
285 			if (tf) {
286 				close(tf);
287 				tf = 0;
288 			}
289 			return(i);
290 		}
291 	}
292 }
293 
294 /*
295  * tnchktc: check the last entry, see if it's tc=xxx. If so,
296  * recursively find xxx and append that entry (minus the names)
297  * to take the place of the tc=xxx entry. This allows termcap
298  * entries to say "like an HP2621 but doesn't turn on the labels".
299  * Note that this works because of the left to right scan.
300  */
301 int
302 tnchktc()
303 {
304 	register char *p, *q;
305 	char tcname[16];	/* name of similar terminal */
306 	char tcbuf[BUFSIZ];
307 	char *holdtbuf = tbuf;
308 	int l;
309 
310 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
311 	while (*--p != ':')
312 		if (p<tbuf) {
313 			write(2, "Bad termcap entry\n", 18);
314 			return (0);
315 		}
316 	p++;
317 	/* p now points to beginning of last field */
318 	if (p[0] != 't' || p[1] != 'c')
319 		return(1);
320 	strcpy(tcname,p+3);
321 	q = tcname;
322 	while (q && *q != ':')
323 		q++;
324 	*q = 0;
325 	if (++hopcount > MAXHOP) {
326 		write(2, "Infinite tc= loop\n", 18);
327 		return (0);
328 	}
329 	if (tgetent(tcbuf, tcname) != 1)
330 		return(0);
331 	for (q=tcbuf; *q != ':'; q++)
332 		;
333 	l = p - holdtbuf + strlen(q);
334 	if (l > BUFSIZ) {
335 		write(2, "Termcap entry too long\n", 23);
336 		q[BUFSIZ - (p-tbuf)] = 0;
337 	}
338 	strcpy(p, q+1);
339 	tbuf = holdtbuf;
340 	return(1);
341 }
342 
343 /*
344  * Tnamatch deals with name matching.  The first field of the termcap
345  * entry is a sequence of names separated by |'s, so we compare
346  * against each such name.  The normal : terminator after the last
347  * name (before the first field) stops us.
348  */
349 int
350 tnamatch(np)
351 	char *np;
352 {
353 	register char *Np, *Bp;
354 
355 	Bp = tbuf;
356 	if (*Bp == '#')
357 		return(0);
358 	for (;;) {
359 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
360 			continue;
361 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
362 			return (1);
363 		while (*Bp && *Bp != ':' && *Bp != '|')
364 			Bp++;
365 		if (*Bp == 0 || *Bp == ':')
366 			return (0);
367 		Bp++;
368 	}
369 }
370 
371 /*
372  * Skip to the next field.  Notice that this is very dumb, not
373  * knowing about \: escapes or any such.  If necessary, :'s can be put
374  * into the termcap file in octal.
375  */
376 static char *
377 tskip(bp)
378 	register char *bp;
379 {
380 
381 	while (*bp && *bp != ':')
382 		bp++;
383 	if (*bp == ':')
384 		bp++;
385 	return (bp);
386 }
387 
388 /*
389  * Return the (numeric) option id.
390  * Numeric options look like
391  *	li#80
392  * i.e. the option string is separated from the numeric value by
393  * a # character.  If the option is not found we return -1.
394  * Note that we handle octal numbers beginning with 0.
395  */
396 int
397 tgetnum(id)
398 	char *id;
399 {
400 	register int i, base;
401 	register char *bp = tbuf;
402 
403 	for (;;) {
404 		bp = tskip(bp);
405 		if (*bp == 0)
406 			return (-1);
407 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
408 			continue;
409 		if (*bp == '@')
410 			return(-1);
411 		if (*bp != '#')
412 			continue;
413 		bp++;
414 		base = 10;
415 		if (*bp == '0')
416 			base = 8;
417 		i = 0;
418 		while (isdigit(*bp))
419 			i *= base, i += *bp++ - '0';
420 		return (i);
421 	}
422 }
423 
424 /*
425  * Handle a flag option.
426  * Flag options are given "naked", i.e. followed by a : or the end
427  * of the buffer.  Return 1 if we find the option, or 0 if it is
428  * not given.
429  */
430 int
431 tgetflag(id)
432 	char *id;
433 {
434 	register char *bp = tbuf;
435 
436 	for (;;) {
437 		bp = tskip(bp);
438 		if (!*bp)
439 			return (0);
440 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
441 			if (!*bp || *bp == ':')
442 				return (1);
443 			else if (*bp == '@')
444 				return(0);
445 		}
446 	}
447 }
448 
449 /*
450  * Get a string valued option.
451  * These are given as
452  *	cl=^Z
453  * Much decoding is done on the strings, and the strings are
454  * placed in area, which is a ref parameter which is updated.
455  * No checking on area overflow.
456  */
457 char *
458 tgetstr(id, area)
459 	char *id, **area;
460 {
461 	register char *bp = tbuf;
462 
463 	for (;;) {
464 		bp = tskip(bp);
465 		if (!*bp)
466 			return (0);
467 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
468 			continue;
469 		if (*bp == '@')
470 			return(0);
471 		if (*bp != '=')
472 			continue;
473 		bp++;
474 		return (tdecode(bp, area));
475 	}
476 }
477 
478 /*
479  * Tdecode does the grung work to decode the
480  * string capability escapes.
481  */
482 static char *
483 tdecode(str, area)
484 	register char *str;
485 	char **area;
486 {
487 	register char *cp;
488 	register int c;
489 	register char *dp;
490 	int i;
491 
492 	cp = *area;
493 	while ((c = *str++) && c != ':') {
494 		switch (c) {
495 
496 		case '^':
497 			c = *str++ & 037;
498 			break;
499 
500 		case '\\':
501 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
502 			c = *str++;
503 nextc:
504 			if (*dp++ == c) {
505 				c = *dp++;
506 				break;
507 			}
508 			dp++;
509 			if (*dp)
510 				goto nextc;
511 			if (isdigit(c)) {
512 				c -= '0', i = 2;
513 				do
514 					c <<= 3, c |= *str++ - '0';
515 				while (--i && isdigit(*str));
516 			}
517 			break;
518 		}
519 		*cp++ = c;
520 	}
521 	*cp++ = 0;
522 	str = *area;
523 	*area = cp;
524 	return (str);
525 }
526