1 /* $NetBSD: term.c,v 1.32 2020/03/27 17:39:53 christos 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.32 2020/03/27 17:39:53 christos 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 static char __ti_database[PATH_MAX]; 54 const char *_ti_database; 55 56 /* Include a generated list of pre-compiled terminfo descriptions. */ 57 #include "compiled_terms.c" 58 59 static int 60 allocset(void *pp, int init, size_t nelem, size_t elemsize) 61 { 62 void **p = pp; 63 if (*p) { 64 memset(*p, init, nelem * elemsize); 65 return 0; 66 } 67 68 if ((*p = calloc(nelem, elemsize)) == NULL) 69 return -1; 70 71 if (init != 0) 72 memset(*p, init, nelem * elemsize); 73 return 0; 74 } 75 76 static int 77 _ti_readterm(TERMINAL *term, const char *cap, size_t caplen, int flags) 78 { 79 char rtype; 80 uint16_t ind, num; 81 size_t len; 82 TERMUSERDEF *ud; 83 84 if (caplen == 0) 85 goto out; 86 rtype = *cap++; 87 caplen--; 88 /* Only read type 1 or 3 records */ 89 if (rtype != TERMINFO_RTYPE && rtype != TERMINFO_RTYPE_O1) 90 goto out; 91 92 if (allocset(&term->flags, 0, TIFLAGMAX+1, sizeof(*term->flags)) == -1) 93 return -1; 94 95 if (allocset(&term->nums, -1, TINUMMAX+1, sizeof(*term->nums)) == -1) 96 return -1; 97 98 if (allocset(&term->strs, 0, TISTRMAX+1, sizeof(*term->strs)) == -1) 99 return -1; 100 101 if (term->_arealen != caplen) { 102 term->_arealen = caplen; 103 term->_area = realloc(term->_area, term->_arealen); 104 if (term->_area == NULL) 105 return -1; 106 } 107 memcpy(term->_area, cap, term->_arealen); 108 109 cap = term->_area; 110 len = _ti_decode_16(&cap); 111 term->name = cap; 112 cap += len; 113 len = _ti_decode_16(&cap); 114 if (len == 0) 115 term->_alias = NULL; 116 else { 117 term->_alias = cap; 118 cap += len; 119 } 120 len = _ti_decode_16(&cap); 121 if (len == 0) 122 term->desc = NULL; 123 else { 124 term->desc = cap; 125 cap += len; 126 } 127 128 num = _ti_decode_16(&cap); 129 if (num != 0) { 130 num = _ti_decode_16(&cap); 131 for (; num != 0; num--) { 132 ind = _ti_decode_16(&cap); 133 term->flags[ind] = *cap++; 134 if (flags == 0 && !VALID_BOOLEAN(term->flags[ind])) 135 term->flags[ind] = 0; 136 } 137 } 138 139 num = _ti_decode_16(&cap); 140 if (num != 0) { 141 num = _ti_decode_16(&cap); 142 for (; num != 0; num--) { 143 ind = _ti_decode_16(&cap); 144 term->nums[ind] = _ti_decode_num(&cap, rtype); 145 if (flags == 0 && !VALID_NUMERIC(term->nums[ind])) 146 term->nums[ind] = ABSENT_NUMERIC; 147 } 148 } 149 150 num = _ti_decode_16(&cap); 151 if (num != 0) { 152 num = _ti_decode_16(&cap); 153 for (; num != 0; num--) { 154 ind = _ti_decode_16(&cap); 155 len = _ti_decode_16(&cap); 156 if (len > 0) 157 term->strs[ind] = cap; 158 else if (flags == 0) 159 term->strs[ind] = ABSENT_STRING; 160 else 161 term->strs[ind] = CANCELLED_STRING; 162 cap += len; 163 } 164 } 165 166 num = _ti_decode_16(&cap); 167 if (num != 0) { 168 num = _ti_decode_16(&cap); 169 if (num != term->_nuserdefs) { 170 free(term->_userdefs); 171 term->_userdefs = NULL; 172 term->_nuserdefs = num; 173 } 174 if (allocset(&term->_userdefs, 0, term->_nuserdefs, 175 sizeof(*term->_userdefs)) == -1) 176 return -1; 177 for (num = 0; num < term->_nuserdefs; num++) { 178 ud = &term->_userdefs[num]; 179 len = _ti_decode_16(&cap); 180 ud->id = cap; 181 cap += len; 182 ud->type = *cap++; 183 switch (ud->type) { 184 case 'f': 185 ud->flag = *cap++; 186 if (flags == 0 && 187 !VALID_BOOLEAN(ud->flag)) 188 ud->flag = 0; 189 ud->num = ABSENT_NUMERIC; 190 ud->str = ABSENT_STRING; 191 break; 192 case 'n': 193 ud->flag = ABSENT_BOOLEAN; 194 ud->num = _ti_decode_num(&cap, rtype); 195 if (flags == 0 && 196 !VALID_NUMERIC(ud->num)) 197 ud->num = ABSENT_NUMERIC; 198 ud->str = ABSENT_STRING; 199 break; 200 case 's': 201 ud->flag = ABSENT_BOOLEAN; 202 ud->num = ABSENT_NUMERIC; 203 len = _ti_decode_16(&cap); 204 if (len > 0) 205 ud->str = cap; 206 else if (flags == 0) 207 ud->str = ABSENT_STRING; 208 else 209 ud->str = CANCELLED_STRING; 210 cap += len; 211 break; 212 default: 213 goto out; 214 } 215 } 216 } else { 217 term->_nuserdefs = 0; 218 if (term->_userdefs) { 219 free(term->_userdefs); 220 term->_userdefs = NULL; 221 } 222 } 223 224 return 1; 225 out: 226 errno = EINVAL; 227 return -1; 228 } 229 230 static int 231 _ti_checkname(const char *name, const char *termname, const char *termalias) 232 { 233 const char *alias, *s; 234 size_t len, l; 235 236 /* Check terminal name matches. */ 237 if (strcmp(termname, name) == 0) 238 return 1; 239 240 /* Check terminal aliases match. */ 241 if (termalias == NULL) 242 return 0; 243 244 len = strlen(name); 245 alias = termalias; 246 while (*alias != '\0') { 247 s = strchr(alias, '|'); 248 if (s == NULL) 249 l = strlen(alias); 250 else 251 l = (size_t)(s - alias); 252 if (len == l && memcmp(alias, name, l) == 0) 253 return 1; 254 if (s == NULL) 255 break; 256 alias = s + 1; 257 } 258 259 /* No match. */ 260 return 0; 261 } 262 263 static int 264 _ti_dbgetterm(TERMINAL *term, const char *path, const char *name, int flags) 265 { 266 struct cdbr *db; 267 const void *data; 268 const uint8_t *data8; 269 size_t len, klen; 270 int r; 271 272 r = snprintf(__ti_database, sizeof(__ti_database), "%s.cdb", path); 273 if (r < 0 || (size_t)r > sizeof(__ti_database)) { 274 db = NULL; 275 errno = ENOENT; /* To fall back to a non extension. */ 276 } else 277 db = cdbr_open(__ti_database, CDBR_DEFAULT); 278 279 /* Target file *may* be a cdb file without the extension. */ 280 if (db == NULL && errno == ENOENT) { 281 len = strlcpy(__ti_database, path, sizeof(__ti_database)); 282 if (len < sizeof(__ti_database)) 283 db = cdbr_open(__ti_database, CDBR_DEFAULT); 284 } 285 if (db == NULL) 286 return -1; 287 288 r = 0; 289 klen = strlen(name) + 1; 290 if (cdbr_find(db, name, klen, &data, &len) == -1) 291 goto out; 292 data8 = data; 293 if (len == 0) 294 goto out; 295 296 /* If the entry is an alias, load the indexed terminfo description. */ 297 if (data8[0] == TERMINFO_ALIAS) { 298 if (cdbr_get(db, le32dec(data8 + 1), &data, &len)) 299 goto out; 300 data8 = data; 301 } 302 303 r = _ti_readterm(term, data, len, flags); 304 /* Ensure that this is the right terminfo description. */ 305 if (r == 1) 306 r = _ti_checkname(name, term->name, term->_alias); 307 /* Remember the database we read. */ 308 if (r == 1) 309 _ti_database = __ti_database; 310 311 out: 312 cdbr_close(db); 313 return r; 314 } 315 316 static int 317 _ti_dbgettermp(TERMINAL *term, const char *path, const char *name, int flags) 318 { 319 const char *p; 320 char pathbuf[PATH_MAX]; 321 size_t l; 322 int r, e; 323 324 e = -1; 325 r = 0; 326 do { 327 for (p = path; *path != '\0' && *path != ':'; path++) 328 continue; 329 l = (size_t)(path - p); 330 if (l != 0 && l + 1 < sizeof(pathbuf)) { 331 memcpy(pathbuf, p, l); 332 pathbuf[l] = '\0'; 333 r = _ti_dbgetterm(term, pathbuf, name, flags); 334 if (r == 1) 335 return 1; 336 if (r == 0) 337 e = 0; 338 } 339 } while (*path++ == ':'); 340 return e; 341 } 342 343 static int 344 _ti_findterm(TERMINAL *term, const char *name, int flags) 345 { 346 int r; 347 char *c, *e; 348 349 _DIAGASSERT(term != NULL); 350 _DIAGASSERT(name != NULL); 351 352 _ti_database = NULL; 353 r = 0; 354 355 if ((e = getenv("TERMINFO")) != NULL && *e != '\0') { 356 if (e[0] == '/') 357 return _ti_dbgetterm(term, e, name, flags); 358 } 359 360 c = NULL; 361 if (e == NULL && (c = getenv("TERMCAP")) != NULL) { 362 if (*c != '\0' && *c != '/') { 363 c = strdup(c); 364 if (c != NULL) { 365 e = captoinfo(c); 366 free(c); 367 } 368 } 369 } 370 371 if (e != NULL) { 372 TIC *tic; 373 374 if (c == NULL) 375 e = strdup(e); /* So we don't destroy env */ 376 if (e == NULL) 377 tic = NULL; 378 else { 379 tic = _ti_compile(e, TIC_WARNING | 380 TIC_ALIAS | TIC_DESCRIPTION | TIC_EXTRA); 381 free(e); 382 } 383 if (tic != NULL && 384 _ti_checkname(name, tic->name, tic->alias) == 1) 385 { 386 uint8_t *f; 387 ssize_t len; 388 389 len = _ti_flatten(&f, tic); 390 if (len != -1) { 391 r = _ti_readterm(term, (char *)f, (size_t)len, 392 flags); 393 free(f); 394 } 395 } 396 _ti_freetic(tic); 397 if (r == 1) { 398 if (c == NULL) 399 _ti_database = "$TERMINFO"; 400 else 401 _ti_database = "$TERMCAP"; 402 return r; 403 } 404 } 405 406 if ((e = getenv("TERMINFO_DIRS")) != NULL) 407 return _ti_dbgettermp(term, e, name, flags); 408 409 if ((e = getenv("HOME")) != NULL) { 410 char homepath[PATH_MAX]; 411 412 if (snprintf(homepath, sizeof(homepath), "%s/.terminfo", e) > 0) 413 r = _ti_dbgetterm(term, homepath, name, flags); 414 } 415 if (r != 1) 416 r = _ti_dbgettermp(term, _PATH_TERMINFO, name, flags); 417 418 return r; 419 } 420 421 int 422 _ti_getterm(TERMINAL *term, const char *name, int flags) 423 { 424 int r; 425 size_t i; 426 const struct compiled_term *t; 427 char *namev3; 428 429 namev3 = _ti_getname(TERMINFO_RTYPE, name); 430 if (namev3 != NULL) { 431 r = _ti_findterm(term, namev3, flags); 432 free(namev3); 433 if (r == 1) 434 return r; 435 } 436 437 r = _ti_findterm(term, name, flags); 438 if (r == 1) 439 return r; 440 441 for (i = 0; i < __arraycount(compiled_terms); i++) { 442 t = &compiled_terms[i]; 443 if (strcmp(name, t->name) == 0) { 444 r = _ti_readterm(term, t->cap, t->caplen, flags); 445 break; 446 } 447 } 448 449 return r; 450 } 451