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