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