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