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