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