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