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