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