NetBSD/lib/libterminfo/term.c

466 lines
10 KiB
C

/* $NetBSD: term.c,v 1.34 2020/04/05 14:53:39 martin Exp $ */
/*
* Copyright (c) 2009, 2010, 2011, 2020 The NetBSD Foundation, Inc.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Roy Marples.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: term.c,v 1.34 2020/04/05 14:53:39 martin Exp $");
#include <sys/stat.h>
#include <assert.h>
#include <cdbr.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term_private.h>
#include <term.h>
/*
* Although we can read v1 structure (which includes v2 alias records)
* we really want a v3 structure to get numerics of type int rather than short.
*/
#define _PATH_TERMINFO "/usr/share/misc/terminfo"
#ifdef TERMINFO_DB
static char __ti_database[PATH_MAX];
#endif
const char *_ti_database;
/* Include a generated list of pre-compiled terminfo descriptions. */
#include "compiled_terms.c"
static int
allocset(void *pp, int init, size_t nelem, size_t elemsize)
{
void **p = pp;
if (*p) {
memset(*p, init, nelem * elemsize);
return 0;
}
if ((*p = calloc(nelem, elemsize)) == NULL)
return -1;
if (init != 0)
memset(*p, init, nelem * elemsize);
return 0;
}
static int
_ti_readterm(TERMINAL *term, const char *cap, size_t caplen, int flags)
{
char rtype;
uint16_t ind, num;
size_t len;
TERMUSERDEF *ud;
if (caplen == 0)
goto out;
rtype = *cap++;
caplen--;
/* Only read type 1 or 3 records */
if (rtype != TERMINFO_RTYPE && rtype != TERMINFO_RTYPE_O1)
goto out;
if (allocset(&term->flags, 0, TIFLAGMAX+1, sizeof(*term->flags)) == -1)
return -1;
if (allocset(&term->nums, -1, TINUMMAX+1, sizeof(*term->nums)) == -1)
return -1;
if (allocset(&term->strs, 0, TISTRMAX+1, sizeof(*term->strs)) == -1)
return -1;
if (term->_arealen != caplen) {
term->_arealen = caplen;
term->_area = realloc(term->_area, term->_arealen);
if (term->_area == NULL)
return -1;
}
memcpy(term->_area, cap, term->_arealen);
cap = term->_area;
len = _ti_decode_16(&cap);
term->name = cap;
cap += len;
len = _ti_decode_16(&cap);
if (len == 0)
term->_alias = NULL;
else {
term->_alias = cap;
cap += len;
}
len = _ti_decode_16(&cap);
if (len == 0)
term->desc = NULL;
else {
term->desc = cap;
cap += len;
}
num = _ti_decode_16(&cap);
if (num != 0) {
num = _ti_decode_16(&cap);
for (; num != 0; num--) {
ind = _ti_decode_16(&cap);
term->flags[ind] = *cap++;
if (flags == 0 && !VALID_BOOLEAN(term->flags[ind]))
term->flags[ind] = 0;
}
}
num = _ti_decode_16(&cap);
if (num != 0) {
num = _ti_decode_16(&cap);
for (; num != 0; num--) {
ind = _ti_decode_16(&cap);
term->nums[ind] = _ti_decode_num(&cap, rtype);
if (flags == 0 && !VALID_NUMERIC(term->nums[ind]))
term->nums[ind] = ABSENT_NUMERIC;
}
}
num = _ti_decode_16(&cap);
if (num != 0) {
num = _ti_decode_16(&cap);
for (; num != 0; num--) {
ind = _ti_decode_16(&cap);
len = _ti_decode_16(&cap);
if (len > 0)
term->strs[ind] = cap;
else if (flags == 0)
term->strs[ind] = ABSENT_STRING;
else
term->strs[ind] = CANCELLED_STRING;
cap += len;
}
}
num = _ti_decode_16(&cap);
if (num != 0) {
num = _ti_decode_16(&cap);
if (num != term->_nuserdefs) {
free(term->_userdefs);
term->_userdefs = NULL;
term->_nuserdefs = num;
}
if (allocset(&term->_userdefs, 0, term->_nuserdefs,
sizeof(*term->_userdefs)) == -1)
return -1;
for (num = 0; num < term->_nuserdefs; num++) {
ud = &term->_userdefs[num];
len = _ti_decode_16(&cap);
ud->id = cap;
cap += len;
ud->type = *cap++;
switch (ud->type) {
case 'f':
ud->flag = *cap++;
if (flags == 0 &&
!VALID_BOOLEAN(ud->flag))
ud->flag = 0;
ud->num = ABSENT_NUMERIC;
ud->str = ABSENT_STRING;
break;
case 'n':
ud->flag = ABSENT_BOOLEAN;
ud->num = _ti_decode_num(&cap, rtype);
if (flags == 0 &&
!VALID_NUMERIC(ud->num))
ud->num = ABSENT_NUMERIC;
ud->str = ABSENT_STRING;
break;
case 's':
ud->flag = ABSENT_BOOLEAN;
ud->num = ABSENT_NUMERIC;
len = _ti_decode_16(&cap);
if (len > 0)
ud->str = cap;
else if (flags == 0)
ud->str = ABSENT_STRING;
else
ud->str = CANCELLED_STRING;
cap += len;
break;
default:
goto out;
}
}
} else {
term->_nuserdefs = 0;
if (term->_userdefs) {
free(term->_userdefs);
term->_userdefs = NULL;
}
}
return 1;
out:
errno = EINVAL;
return -1;
}
#if defined(TERMINFO_DB) || defined(TERMINFO_COMPILE)
static int
_ti_checkname(const char *name, const char *termname, const char *termalias)
{
const char *alias, *s;
size_t len, l;
/* Check terminal name matches. */
if (strcmp(termname, name) == 0)
return 1;
/* Check terminal aliases match. */
if (termalias == NULL)
return 0;
len = strlen(name);
alias = termalias;
while (*alias != '\0') {
s = strchr(alias, '|');
if (s == NULL)
l = strlen(alias);
else
l = (size_t)(s - alias);
if (len == l && memcmp(alias, name, l) == 0)
return 1;
if (s == NULL)
break;
alias = s + 1;
}
/* No match. */
return 0;
}
#endif
#ifdef TERMINFO_DB
static int
_ti_dbgetterm(TERMINAL *term, const char *path, const char *name, int flags)
{
struct cdbr *db;
const void *data;
const uint8_t *data8;
size_t len, klen;
int r;
r = snprintf(__ti_database, sizeof(__ti_database), "%s.cdb", path);
if (r < 0 || (size_t)r > sizeof(__ti_database)) {
db = NULL;
errno = ENOENT; /* To fall back to a non extension. */
} else
db = cdbr_open(__ti_database, CDBR_DEFAULT);
/* Target file *may* be a cdb file without the extension. */
if (db == NULL && errno == ENOENT) {
len = strlcpy(__ti_database, path, sizeof(__ti_database));
if (len < sizeof(__ti_database))
db = cdbr_open(__ti_database, CDBR_DEFAULT);
}
if (db == NULL)
return -1;
r = 0;
klen = strlen(name) + 1;
if (cdbr_find(db, name, klen, &data, &len) == -1)
goto out;
data8 = data;
if (len == 0)
goto out;
/* If the entry is an alias, load the indexed terminfo description. */
if (data8[0] == TERMINFO_ALIAS) {
if (cdbr_get(db, le32dec(data8 + 1), &data, &len))
goto out;
data8 = data;
}
r = _ti_readterm(term, data, len, flags);
/* Ensure that this is the right terminfo description. */
if (r == 1)
r = _ti_checkname(name, term->name, term->_alias);
/* Remember the database we read. */
if (r == 1)
_ti_database = __ti_database;
out:
cdbr_close(db);
return r;
}
static int
_ti_dbgettermp(TERMINAL *term, const char *path, const char *name, int flags)
{
const char *p;
char pathbuf[PATH_MAX];
size_t l;
int r, e;
e = -1;
r = 0;
do {
for (p = path; *path != '\0' && *path != ':'; path++)
continue;
l = (size_t)(path - p);
if (l != 0 && l + 1 < sizeof(pathbuf)) {
memcpy(pathbuf, p, l);
pathbuf[l] = '\0';
r = _ti_dbgetterm(term, pathbuf, name, flags);
if (r == 1)
return 1;
if (r == 0)
e = 0;
}
} while (*path++ == ':');
return e;
}
#endif
static int
_ti_findterm(TERMINAL *term, const char *name, int flags)
{
#ifndef TERMINFO_DB
_ti_database = NULL;
return 0;
#else
int r;
char *c, *e;
_DIAGASSERT(term != NULL);
_DIAGASSERT(name != NULL);
_ti_database = NULL;
r = 0;
e = getenv("TERMINFO");
if (e != NULL && *e == '/')
return _ti_dbgetterm(term, e, name, flags);
c = NULL;
#ifdef TERMINFO_COMPILE
if (e == NULL && (c = getenv("TERMCAP")) != NULL) {
if (*c != '\0' && *c != '/') {
c = strdup(c);
if (c != NULL) {
e = captoinfo(c);
free(c);
}
}
}
if (e != NULL) {
TIC *tic;
if (c == NULL)
e = strdup(e); /* So we don't destroy env */
if (e == NULL)
tic = NULL;
else {
tic = _ti_compile(e, TIC_WARNING |
TIC_ALIAS | TIC_DESCRIPTION | TIC_EXTRA);
free(e);
}
if (tic != NULL &&
_ti_checkname(name, tic->name, tic->alias) == 1)
{
uint8_t *f;
ssize_t len;
len = _ti_flatten(&f, tic);
if (len != -1) {
r = _ti_readterm(term, (char *)f, (size_t)len,
flags);
free(f);
}
}
_ti_freetic(tic);
if (r == 1) {
if (c == NULL)
_ti_database = "$TERMINFO";
else
_ti_database = "$TERMCAP";
return r;
}
}
if ((e = getenv("TERMINFO_DIRS")) != NULL)
return _ti_dbgettermp(term, e, name, flags);
if ((e = getenv("HOME")) != NULL) {
char homepath[PATH_MAX];
if (snprintf(homepath, sizeof(homepath), "%s/.terminfo", e) > 0)
r = _ti_dbgetterm(term, homepath, name, flags);
}
if (r != 1)
r = _ti_dbgettermp(term, _PATH_TERMINFO, name, flags);
#endif
return r;
#endif
}
int
_ti_getterm(TERMINAL *term, const char *name, int flags)
{
int r;
size_t i;
const struct compiled_term *t;
#ifdef TERMINFO_COMPAT
char *namev3;
namev3 = _ti_getname(TERMINFO_RTYPE, name);
if (namev3 != NULL) {
r = _ti_findterm(term, namev3, flags);
free(namev3);
if (r == 1)
return r;
}
#endif
r = _ti_findterm(term, name, flags);
if (r == 1)
return r;
for (i = 0; i < __arraycount(compiled_terms); i++) {
t = &compiled_terms[i];
if (strcmp(name, t->name) == 0) {
r = _ti_readterm(term, t->cap, t->caplen, flags);
break;
}
}
return r;
}