xref: /illumos-gate/usr/src/cmd/captoinfo/otermcap.c (revision ddb365bfc9e868ad24ccdcb0dc91af18b10df082)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /* Copyright (c) 1979 Regents of the University of California	*/
31 /* Modified to:							*/
32 /* 1) remember the name of the first tc= parameter		*/
33 /*	encountered during parsing.				*/
34 /* 2) handle multiple invocations of tgetent().			*/
35 /* 3) tskip() is now available outside of the library.		*/
36 /* 4) remember $TERM name for error messages.			*/
37 /* 5) have a larger buffer.					*/
38 /* 6) really fix the bug that 5) got around. This fix by	*/
39 /*		Marion Hakanson, orstcs!hakanson		*/
40 
41 
42 #include "otermcap.h"
43 #define	MAXHOP	32	/* max number of tc= indirections */
44 
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #include <sys/uio.h>
49 #include <unistd.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <ctype.h>
54 
55 #include <signal.h>   /* use this file to determine if this is SVR4.0 system */
56 #ifdef SIGSTOP /* SVR4.0 and beyond */
57 #define	E_TERMCAP "/usr/share/lib/termcap"
58 #else
59 #define	E_TERMCAP "/etc/termcap"
60 #endif
61 
62 /*
63  * termcap - routines for dealing with the terminal capability data base
64  *
65  * BUG:		Should use a "last" pointer in tbuf, so that searching
66  *		for capabilities alphabetically would not be a n**2/2
67  *		process when large numbers of capabilities are given.
68  * Note:	If we add a last pointer now we will screw up the
69  *		tc capability. We really should compile termcap.
70  *
71  * Essentially all the work here is scanning and decoding escapes
72  * in string capabilities.  We don't use stdio because the editor
73  * doesn't, and because living w/o it is not hard.
74  */
75 
76 static	char *tbuf;
77 static	int hopcount;	/* detect infinite loops in termcap, init 0 */
78 char	*tskip(char *);
79 char	*otgetstr(char *, char **);
80 
81 /* Tony Hansen */
82 int	TLHtcfound = 0;
83 char	TLHtcname[16];
84 static	char *termname;
85 
86 static int _tgetent(char *, char *);
87 static int otnchktc(void);
88 static char *tdecode(char *, char **);
89 static int otnamatch(char *);
90 /*
91  * Get an entry for terminal name in buffer bp,
92  * from the termcap file.  Parse is very rudimentary;
93  * we just notice escaped newlines.
94  */
95 int
96 otgetent(char *bp, char *name)
97 {
98 	/* Tony Hansen */
99 	int ret;
100 	TLHtcfound = 0;
101 	hopcount = 0;
102 	termname = name;
103 	ret = _tgetent(bp, name);
104 	/*
105 	 * There is some sort of bug in the check way down below to prevent
106 	 * buffer overflow. I really don't want to track it down, so I
107 	 * upped the standard buffer size and check here to see if the created
108 	 * buffer is larger than the old buffer size.
109 	 */
110 	if (strlen(bp) >= 1024)
111 		(void) fprintf(stderr,
112 		    "tgetent(): TERM=%s: Termcap entry is too long.\n",
113 		    termname);
114 	return (ret);
115 }
116 
117 static int
118 _tgetent(char *bp, char *name)
119 {
120 	char *cp;
121 	int c;
122 	int i = 0, cnt = 0;
123 	char ibuf[TBUFSIZE];
124 	char *cp2;
125 	int tf;
126 
127 	tbuf = bp;
128 	tf = 0;
129 #ifndef V6
130 	cp = getenv("TERMCAP");
131 	/*
132 	 * TERMCAP can have one of two things in it. It can be the
133 	 * name of a file to use instead of /etc/termcap. In this
134 	 * case it better start with a "/". Or it can be an entry to
135 	 * use so we don't have to read the file. In this case it
136 	 * has to already have the newlines crunched out.
137 	 */
138 	if (cp && *cp) {
139 		if (*cp != '/') {
140 			cp2 = getenv("TERM");
141 			if (cp2 == (char *)0 || strcmp(name, cp2) == 0) {
142 				(void) strcpy(bp, cp);
143 				return (otnchktc());
144 			} else {
145 				tf = open(E_TERMCAP, 0);
146 			}
147 		} else
148 			tf = open(cp, 0);
149 	}
150 	if (tf == 0)
151 		tf = open(E_TERMCAP, 0);
152 #else
153 	tf = open(E_TERMCAP, 0);
154 #endif
155 	if (tf < 0)
156 		return (-1);
157 	for (; ; ) {
158 		cp = bp;
159 		for (; ; ) {
160 			if (i == cnt) {
161 				cnt = read(tf, ibuf, TBUFSIZE);
162 				if (cnt <= 0) {
163 					(void) close(tf);
164 					return (0);
165 				}
166 				i = 0;
167 			}
168 			c = ibuf[i++];
169 			if (c == '\n') {
170 				if (cp > bp && cp[-1] == '\\') {
171 					cp--;
172 					continue;
173 				}
174 				break;
175 			}
176 			if (cp >= bp + TBUFSIZE) {
177 				(void) fprintf(stderr, "tgetent(): TERM=%s: "
178 				    "Termcap entry too long\n", termname);
179 				break;
180 			} else
181 				*cp++ = c;
182 		}
183 		*cp = 0;
184 
185 		/*
186 		 * The real work for the match.
187 		 */
188 		if (otnamatch(name)) {
189 			(void) close(tf);
190 			return (otnchktc());
191 		}
192 	}
193 }
194 
195 /*
196  * otnchktc: check the last entry, see if it's tc=xxx. If so,
197  * recursively find xxx and append that entry (minus the names)
198  * to take the place of the tc=xxx entry. This allows termcap
199  * entries to say "like an HP2621 but doesn't turn on the labels".
200  * Note that this works because of the left to right scan.
201  */
202 static int
203 otnchktc(void)
204 {
205 	char *p, *q;
206 #define	TERMNAMESIZE 16
207 	char tcname[TERMNAMESIZE];	/* name of similar terminal */
208 	char tcbuf[TBUFSIZE];
209 	char *holdtbuf = tbuf;
210 	int l;
211 
212 	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
213 	while (*--p != ':')
214 		if (p < tbuf) {
215 			(void) fprintf(stderr, "tnchktc(): TERM=%s: Bad "
216 			    "termcap entry\n", termname);
217 			return (0);
218 		}
219 	p++;
220 	/* p now points to beginning of last field */
221 	if (p[0] != 't' || p[1] != 'c')
222 		return (1);
223 	(void) strncpy(tcname, p + 3, TERMNAMESIZE);	/* TLH */
224 	q = tcname;
225 	while (*q && *q != ':')
226 		q++;
227 	*q = 0;
228 	if (++hopcount > MAXHOP) {
229 		(void) fprintf(stderr, "tnchktc(): TERM=%s: Infinite tc= "
230 		    "loop\n", termname);
231 		return (0);
232 	}
233 	if (_tgetent(tcbuf, tcname) != 1)
234 		return (0);
235 	/* Tony Hansen */
236 	TLHtcfound++;
237 	(void) strcpy(TLHtcname, tcname);
238 
239 	for (q = tcbuf; *q != ':'; q++)
240 		;
241 	l = p - holdtbuf + strlen(q);
242 	if (l > TBUFSIZE) {
243 		(void) fprintf(stderr, "tnchktc(): TERM=%s: Termcap entry "
244 		    "too long\n", termname);
245 		q[TBUFSIZE - (p - holdtbuf)] = 0;
246 	}
247 	(void) strcpy(p, q + 1);
248 	tbuf = holdtbuf;
249 	return (1);
250 }
251 
252 /*
253  * Tnamatch deals with name matching.  The first field of the termcap
254  * entry is a sequence of names separated by |'s, so we compare
255  * against each such name.  The normal : terminator after the last
256  * name (before the first field) stops us.
257  */
258 static int
259 otnamatch(char *np)
260 {
261 	char *Np, *Bp;
262 
263 	Bp = tbuf;
264 	if (*Bp == '#')
265 		return (0);
266 	for (;;) {
267 		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
268 			continue;
269 		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
270 			return (1);
271 		while (*Bp && *Bp != ':' && *Bp != '|')
272 			Bp++;
273 		if (*Bp == 0 || *Bp == ':')
274 			return (0);
275 		Bp++;
276 	}
277 }
278 
279 /*
280  * Skip to the next field.  Notice that this is very dumb, not
281  * knowing about \: escapes or any such.  If necessary, :'s can be put
282  * into the termcap file in octal.
283  */
284 char *
285 tskip(char *bp)
286 {
287 
288 	while (*bp && *bp != ':')
289 		bp++;
290 	if (*bp == ':')
291 		bp++;
292 	return (bp);
293 }
294 
295 /*
296  * Return the (numeric) option id.
297  * Numeric options look like
298  *	li#80
299  * i.e. the option string is separated from the numeric value by
300  * a # character.  If the option is not found we return -1.
301  * Note that we handle octal numbers beginning with 0.
302  */
303 int
304 otgetnum(char *id)
305 {
306 	int i, base;
307 	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 otgetflag(char *id)
338 {
339 	char *bp = tbuf;
340 
341 	for (;;) {
342 		bp = tskip(bp);
343 		if (!*bp)
344 			return (0);
345 		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
346 			if (!*bp || *bp == ':')
347 				return (1);
348 			else if (*bp == '@')
349 				return (0);
350 		}
351 	}
352 }
353 
354 /*
355  * Get a string valued option.
356  * These are given as
357  *	cl=^Z
358  * Much decoding is done on the strings, and the strings are
359  * placed in area, which is a ref parameter which is updated.
360  * No checking on area overflow.
361  */
362 char *
363 otgetstr(char *id, char **area)
364 {
365 	char *bp = tbuf;
366 
367 	for (; ; ) {
368 		bp = tskip(bp);
369 		if (!*bp)
370 			return (0);
371 		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
372 			continue;
373 		if (*bp == '@')
374 			return (0);
375 		if (*bp != '=')
376 			continue;
377 		bp++;
378 		return (tdecode(bp, area));
379 	}
380 }
381 
382 /*
383  * Tdecode does the grung work to decode the
384  * string capability escapes.
385  */
386 static char *
387 tdecode(char *str, char **area)
388 {
389 	char *cp;
390 	int c;
391 	char *dp;
392 	int i;
393 
394 	cp = *area;
395 	while ((c = *str++) != '\0' && c != ':') {
396 		switch (c) {
397 
398 		case '^':
399 			c = *str++ & 037;
400 			break;
401 
402 		case '\\':
403 			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
404 			c = *str++;
405 nextc:
406 			if (*dp++ == c) {
407 				c = *dp++;
408 				break;
409 			}
410 			dp++;
411 			if (*dp)
412 				goto nextc;
413 			if (isdigit(c)) {
414 				c -= '0', i = 2;
415 				do
416 					c <<= 3, c |= *str++ - '0';
417 				while (--i && isdigit(*str))
418 					;
419 			}
420 			break;
421 		}
422 		*cp++ = c;
423 	}
424 	*cp++ = 0;
425 	str = *area;
426 	*area = cp;
427 	return (str);
428 }
429