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