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