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