1 /* $NetBSD: term.c,v 1.34 2020/04/05 14:53:39 martin Exp $ */ 2 3 /* 4 * Copyright (c) 2009, 2010, 2011, 2020 The NetBSD Foundation, Inc. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Roy Marples. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __RCSID("$NetBSD: term.c,v 1.34 2020/04/05 14:53:39 martin Exp $"); 32 33 #include <sys/stat.h> 34 35 #include <assert.h> 36 #include <cdbr.h> 37 #include <ctype.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <limits.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <term_private.h> 45 #include <term.h> 46 47 /* 48 * Although we can read v1 structure (which includes v2 alias records) 49 * we really want a v3 structure to get numerics of type int rather than short. 50 */ 51 #define _PATH_TERMINFO "/usr/share/misc/terminfo" 52 53 #ifdef TERMINFO_DB 54 static char __ti_database[PATH_MAX]; 55 #endif 56 const char *_ti_database; 57 58 /* Include a generated list of pre-compiled terminfo descriptions. */ 59 #include "compiled_terms.c" 60 61 static int 62 allocset(void *pp, int init, size_t nelem, size_t elemsize) 63 { 64 void **p = pp; 65 if (*p) { 66 memset(*p, init, nelem * elemsize); 67 return 0; 68 } 69 70 if ((*p = calloc(nelem, elemsize)) == NULL) 71 return -1; 72 73 if (init != 0) 74 memset(*p, init, nelem * elemsize); 75 return 0; 76 } 77 78 static int 79 _ti_readterm(TERMINAL *term, const char *cap, size_t caplen, int flags) 80 { 81 char rtype; 82 uint16_t ind, num; 83 size_t len; 84 TERMUSERDEF *ud; 85 86 if (caplen == 0) 87 goto out; 88 rtype = *cap++; 89 caplen--; 90 /* Only read type 1 or 3 records */ 91 if (rtype != TERMINFO_RTYPE && rtype != TERMINFO_RTYPE_O1) 92 goto out; 93 94 if (allocset(&term->flags, 0, TIFLAGMAX+1, sizeof(*term->flags)) == -1) 95 return -1; 96 97 if (allocset(&term->nums, -1, TINUMMAX+1, sizeof(*term->nums)) == -1) 98 return -1; 99 100 if (allocset(&term->strs, 0, TISTRMAX+1, sizeof(*term->strs)) == -1) 101 return -1; 102 103 if (term->_arealen != caplen) { 104 term->_arealen = caplen; 105 term->_area = realloc(term->_area, term->_arealen); 106 if (term->_area == NULL) 107 return -1; 108 } 109 memcpy(term->_area, cap, term->_arealen); 110 111 cap = term->_area; 112 len = _ti_decode_16(&cap); 113 term->name = cap; 114 cap += len; 115 len = _ti_decode_16(&cap); 116 if (len == 0) 117 term->_alias = NULL; 118 else { 119 term->_alias = cap; 120 cap += len; 121 } 122 len = _ti_decode_16(&cap); 123 if (len == 0) 124 term->desc = NULL; 125 else { 126 term->desc = cap; 127 cap += len; 128 } 129 130 num = _ti_decode_16(&cap); 131 if (num != 0) { 132 num = _ti_decode_16(&cap); 133 for (; num != 0; num--) { 134 ind = _ti_decode_16(&cap); 135 term->flags[ind] = *cap++; 136 if (flags == 0 && !VALID_BOOLEAN(term->flags[ind])) 137 term->flags[ind] = 0; 138 } 139 } 140 141 num = _ti_decode_16(&cap); 142 if (num != 0) { 143 num = _ti_decode_16(&cap); 144 for (; num != 0; num--) { 145 ind = _ti_decode_16(&cap); 146 term->nums[ind] = _ti_decode_num(&cap, rtype); 147 if (flags == 0 && !VALID_NUMERIC(term->nums[ind])) 148 term->nums[ind] = ABSENT_NUMERIC; 149 } 150 } 151 152 num = _ti_decode_16(&cap); 153 if (num != 0) { 154 num = _ti_decode_16(&cap); 155 for (; num != 0; num--) { 156 ind = _ti_decode_16(&cap); 157 len = _ti_decode_16(&cap); 158 if (len > 0) 159 term->strs[ind] = cap; 160 else if (flags == 0) 161 term->strs[ind] = ABSENT_STRING; 162 else 163 term->strs[ind] = CANCELLED_STRING; 164 cap += len; 165 } 166 } 167 168 num = _ti_decode_16(&cap); 169 if (num != 0) { 170 num = _ti_decode_16(&cap); 171 if (num != term->_nuserdefs) { 172 free(term->_userdefs); 173 term->_userdefs = NULL; 174 term->_nuserdefs = num; 175 } 176 if (allocset(&term->_userdefs, 0, term->_nuserdefs, 177 sizeof(*term->_userdefs)) == -1) 178 return -1; 179 for (num = 0; num < term->_nuserdefs; num++) { 180 ud = &term->_userdefs[num]; 181 len = _ti_decode_16(&cap); 182 ud->id = cap; 183 cap += len; 184 ud->type = *cap++; 185 switch (ud->type) { 186 case 'f': 187 ud->flag = *cap++; 188 if (flags == 0 && 189 !VALID_BOOLEAN(ud->flag)) 190 ud->flag = 0; 191 ud->num = ABSENT_NUMERIC; 192 ud->str = ABSENT_STRING; 193 break; 194 case 'n': 195 ud->flag = ABSENT_BOOLEAN; 196 ud->num = _ti_decode_num(&cap, rtype); 197 if (flags == 0 && 198 !VALID_NUMERIC(ud->num)) 199 ud->num = ABSENT_NUMERIC; 200 ud->str = ABSENT_STRING; 201 break; 202 case 's': 203 ud->flag = ABSENT_BOOLEAN; 204 ud->num = ABSENT_NUMERIC; 205 len = _ti_decode_16(&cap); 206 if (len > 0) 207 ud->str = cap; 208 else if (flags == 0) 209 ud->str = ABSENT_STRING; 210 else 211 ud->str = CANCELLED_STRING; 212 cap += len; 213 break; 214 default: 215 goto out; 216 } 217 } 218 } else { 219 term->_nuserdefs = 0; 220 if (term->_userdefs) { 221 free(term->_userdefs); 222 term->_userdefs = NULL; 223 } 224 } 225 226 return 1; 227 out: 228 errno = EINVAL; 229 return -1; 230 } 231 232 #if defined(TERMINFO_DB) || defined(TERMINFO_COMPILE) 233 static int 234 _ti_checkname(const char *name, const char *termname, const char *termalias) 235 { 236 const char *alias, *s; 237 size_t len, l; 238 239 /* Check terminal name matches. */ 240 if (strcmp(termname, name) == 0) 241 return 1; 242 243 /* Check terminal aliases match. */ 244 if (termalias == NULL) 245 return 0; 246 247 len = strlen(name); 248 alias = termalias; 249 while (*alias != '\0') { 250 s = strchr(alias, '|'); 251 if (s == NULL) 252 l = strlen(alias); 253 else 254 l = (size_t)(s - alias); 255 if (len == l && memcmp(alias, name, l) == 0) 256 return 1; 257 if (s == NULL) 258 break; 259 alias = s + 1; 260 } 261 262 /* No match. */ 263 return 0; 264 } 265 #endif 266 267 #ifdef TERMINFO_DB 268 static int 269 _ti_dbgetterm(TERMINAL *term, const char *path, const char *name, int flags) 270 { 271 struct cdbr *db; 272 const void *data; 273 const uint8_t *data8; 274 size_t len, klen; 275 int r; 276 277 r = snprintf(__ti_database, sizeof(__ti_database), "%s.cdb", path); 278 if (r < 0 || (size_t)r > sizeof(__ti_database)) { 279 db = NULL; 280 errno = ENOENT; /* To fall back to a non extension. */ 281 } else 282 db = cdbr_open(__ti_database, CDBR_DEFAULT); 283 284 /* Target file *may* be a cdb file without the extension. */ 285 if (db == NULL && errno == ENOENT) { 286 len = strlcpy(__ti_database, path, sizeof(__ti_database)); 287 if (len < sizeof(__ti_database)) 288 db = cdbr_open(__ti_database, CDBR_DEFAULT); 289 } 290 if (db == NULL) 291 return -1; 292 293 r = 0; 294 klen = strlen(name) + 1; 295 if (cdbr_find(db, name, klen, &data, &len) == -1) 296 goto out; 297 data8 = data; 298 if (len == 0) 299 goto out; 300 301 /* If the entry is an alias, load the indexed terminfo description. */ 302 if (data8[0] == TERMINFO_ALIAS) { 303 if (cdbr_get(db, le32dec(data8 + 1), &data, &len)) 304 goto out; 305 data8 = data; 306 } 307 308 r = _ti_readterm(term, data, len, flags); 309 /* Ensure that this is the right terminfo description. */ 310 if (r == 1) 311 r = _ti_checkname(name, term->name, term->_alias); 312 /* Remember the database we read. */ 313 if (r == 1) 314 _ti_database = __ti_database; 315 316 out: 317 cdbr_close(db); 318 return r; 319 } 320 321 static int 322 _ti_dbgettermp(TERMINAL *term, const char *path, const char *name, int flags) 323 { 324 const char *p; 325 char pathbuf[PATH_MAX]; 326 size_t l; 327 int r, e; 328 329 e = -1; 330 r = 0; 331 do { 332 for (p = path; *path != '\0' && *path != ':'; path++) 333 continue; 334 l = (size_t)(path - p); 335 if (l != 0 && l + 1 < sizeof(pathbuf)) { 336 memcpy(pathbuf, p, l); 337 pathbuf[l] = '\0'; 338 r = _ti_dbgetterm(term, pathbuf, name, flags); 339 if (r == 1) 340 return 1; 341 if (r == 0) 342 e = 0; 343 } 344 } while (*path++ == ':'); 345 return e; 346 } 347 #endif 348 349 static int 350 _ti_findterm(TERMINAL *term, const char *name, int flags) 351 { 352 #ifndef TERMINFO_DB 353 _ti_database = NULL; 354 355 return 0; 356 #else 357 int r; 358 char *c, *e; 359 360 _DIAGASSERT(term != NULL); 361 _DIAGASSERT(name != NULL); 362 363 _ti_database = NULL; 364 r = 0; 365 366 e = getenv("TERMINFO"); 367 if (e != NULL && *e == '/') 368 return _ti_dbgetterm(term, e, name, flags); 369 370 c = NULL; 371 #ifdef TERMINFO_COMPILE 372 if (e == NULL && (c = getenv("TERMCAP")) != NULL) { 373 if (*c != '\0' && *c != '/') { 374 c = strdup(c); 375 if (c != NULL) { 376 e = captoinfo(c); 377 free(c); 378 } 379 } 380 } 381 382 if (e != NULL) { 383 TIC *tic; 384 385 if (c == NULL) 386 e = strdup(e); /* So we don't destroy env */ 387 if (e == NULL) 388 tic = NULL; 389 else { 390 tic = _ti_compile(e, TIC_WARNING | 391 TIC_ALIAS | TIC_DESCRIPTION | TIC_EXTRA); 392 free(e); 393 } 394 if (tic != NULL && 395 _ti_checkname(name, tic->name, tic->alias) == 1) 396 { 397 uint8_t *f; 398 ssize_t len; 399 400 len = _ti_flatten(&f, tic); 401 if (len != -1) { 402 r = _ti_readterm(term, (char *)f, (size_t)len, 403 flags); 404 free(f); 405 } 406 } 407 _ti_freetic(tic); 408 if (r == 1) { 409 if (c == NULL) 410 _ti_database = "$TERMINFO"; 411 else 412 _ti_database = "$TERMCAP"; 413 return r; 414 } 415 } 416 417 if ((e = getenv("TERMINFO_DIRS")) != NULL) 418 return _ti_dbgettermp(term, e, name, flags); 419 420 if ((e = getenv("HOME")) != NULL) { 421 char homepath[PATH_MAX]; 422 423 if (snprintf(homepath, sizeof(homepath), "%s/.terminfo", e) > 0) 424 r = _ti_dbgetterm(term, homepath, name, flags); 425 } 426 if (r != 1) 427 r = _ti_dbgettermp(term, _PATH_TERMINFO, name, flags); 428 #endif 429 430 return r; 431 #endif 432 } 433 434 int 435 _ti_getterm(TERMINAL *term, const char *name, int flags) 436 { 437 int r; 438 size_t i; 439 const struct compiled_term *t; 440 #ifdef TERMINFO_COMPAT 441 char *namev3; 442 443 namev3 = _ti_getname(TERMINFO_RTYPE, name); 444 if (namev3 != NULL) { 445 r = _ti_findterm(term, namev3, flags); 446 free(namev3); 447 if (r == 1) 448 return r; 449 } 450 #endif 451 452 r = _ti_findterm(term, name, flags); 453 if (r == 1) 454 return r; 455 456 for (i = 0; i < __arraycount(compiled_terms); i++) { 457 t = &compiled_terms[i]; 458 if (strcmp(name, t->name) == 0) { 459 r = _ti_readterm(term, t->cap, t->caplen, flags); 460 break; 461 } 462 } 463 464 return r; 465 } 466