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