Home | History | Annotate | Line # | Download | only in libterminfo
      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