xref: /netbsd-src/lib/libterminfo/term.c (revision b6924e8af2776584be61d1d4e9a7b947af510445)
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
allocset(void * pp,int init,size_t nelem,size_t elemsize)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
_ti_readterm(TERMINAL * term,const char * cap,size_t caplen,int flags)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
_ti_checkname(const char * name,const char * termname,const char * termalias)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
_ti_dbgetterm(TERMINAL * term,const char * path,const char * name,int flags)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
_ti_dbgettermp(TERMINAL * term,const char * path,const char * name,int flags)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
_ti_findterm(TERMINAL * term,const char * name,int flags)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
_ti_getterm(TERMINAL * term,const char * name,int flags)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