NetBSD/lib/libterminfo/tparm.c

635 lines
12 KiB
C

/* $NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $ */
/*
* Copyright (c) 2009, 2011, 2013 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: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $");
#include <sys/param.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term_private.h>
#include <term.h>
#define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
#define BUFINC 128 /* Size to increment the terminal buffer by */
#define VA_LONG_LONG 1
#define VA_CHAR_INT 2
//#define VA_CHAR_LONG 3 /* No need for this yet */
static TERMINAL *dumbterm; /* For non thread safe functions */
typedef struct {
long nums[20];
char *strings[20];
size_t offset;
} TPSTACK;
typedef struct {
long num;
char *string;
} TPVAR;
static int
push(long num, char *string, TPSTACK *stack)
{
if (stack->offset >= sizeof(stack->nums)) {
errno = E2BIG;
return -1;
}
stack->nums[stack->offset] = num;
stack->strings[stack->offset] = string;
stack->offset++;
return 0;
}
static int
pop(long *num, char **string, TPSTACK *stack)
{
if (stack->offset == 0) {
if (num)
*num = 0;
if (string)
*string = NULL;
errno = E2BIG;
return -1;
}
stack->offset--;
if (num)
*num = stack->nums[stack->offset];
if (string)
*string = stack->strings[stack->offset];
return 0;
}
static char *
checkbuf(TERMINAL *term, size_t len)
{
char *buf;
if (term->_bufpos + len >= term->_buflen) {
len = term->_buflen + MAX(len, BUFINC);
buf = realloc(term->_buf, len);
if (buf == NULL)
return NULL;
term->_buf = buf;
term->_buflen = len;
}
return term->_buf;
}
static size_t
ochar(TERMINAL *term, int c)
{
if (c == 0)
c = 0200;
/* Check we have space and a terminator */
if (checkbuf(term, 2) == NULL)
return 0;
term->_buf[term->_bufpos++] = (char)c;
return 1;
}
static size_t
onum(TERMINAL *term, const char *fmt, int num, size_t len)
{
int l;
size_t r;
if (len < LONG_STR_MAX)
len = LONG_STR_MAX;
if (checkbuf(term, len + 2) == NULL)
return 0;
l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
if (l == -1)
return 0;
r = (size_t)l;
term->_bufpos += r;
return r;
}
/*
Make a pass through the string so we can work out
which parameters are ints and which are char *.
Basically we only use char * if %p[1-9] is followed by %l or %s.
*/
int
_ti_parm_analyse(const char *str, int *piss, int piss_len)
{
int nparm, lpop;
char c;
nparm = 0;
lpop = -1;
while ((c = *str++) != '\0') {
if (c != '%')
continue;
c = *str++;
switch (c) {
case 'l':
case 's':
if (lpop > 0) {
if (lpop <= piss_len)
piss[lpop - 1] = 1;
else if (piss)
errno = E2BIG;
}
break;
case 'p':
c = *str++;
if (c < '1' || c > '9') {
errno = EINVAL;
continue;
} else {
lpop = c - '0';
if (lpop > nparm)
nparm = lpop;
}
break;
default:
lpop = -1;
}
}
return nparm;
}
static char *
_ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
{
char c, fmt[64], *fp, *ostr;
long val, val2;
long dnums[26]; /* dynamic variables a-z, not preserved */
size_t l, max, width, precision, olen;
TPSTACK stack;
TPVAR params[TPARM_MAX];
unsigned int done, dot, minus;
int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */
if (str == NULL)
return NULL;
/*
If not passed a terminal, malloc a dummy one.
This means we can preserve buffers and variables per terminal and
still work with non thread safe functions (which sadly are still the
norm and standard).
*/
if (term == NULL) {
if (dumbterm == NULL) {
dumbterm = malloc(sizeof(*dumbterm));
if (dumbterm == NULL)
return NULL;
dumbterm->_buflen = 0;
}
term = dumbterm;
}
term->_bufpos = 0;
/* Ensure we have an initial buffer */
if (term->_buflen == 0) {
term->_buf = malloc(BUFINC);
if (term->_buf == NULL)
return NULL;
term->_buflen = BUFINC;
}
memset(&piss, 0, sizeof(piss));
max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);
/* Put our parameters into variables */
memset(&params, 0, sizeof(params));
for (l = 0; l < max; l++) {
if (piss[l]) {
if (va_type == VA_LONG_LONG) {
/* This only works if char * fits into a long
* on this platform. */
if (sizeof(char *) <= sizeof(long))
params[l].string =
(char *)va_arg(parms, long);
else {
errno = ENOTSUP;
return NULL;
}
} else
params[l].string = va_arg(parms, char *);
} else {
if (va_type == VA_CHAR_INT)
params[l].num = (long)va_arg(parms, int);
else
params[l].num = va_arg(parms, long);
}
}
memset(&stack, 0, sizeof(stack));
while ((c = *str++) != '\0') {
if (c != '%' || (c = *str++) == '%') {
if (c == '\0')
break;
if (ochar(term, c) == 0)
return NULL;
continue;
}
/* Handle formatting. */
fp = fmt;
*fp++ = '%';
done = dot = minus = 0;
width = precision = 0;
val = 0;
while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
switch (c) {
case 'c':
case 's':
*fp++ = c;
done = 1;
break;
case 'd':
case 'o':
case 'x':
case 'X':
*fp++ = 'l';
*fp++ = c;
done = 1;
break;
case '#':
case ' ':
*fp++ = c;
break;
case '.':
*fp++ = c;
if (dot == 0) {
dot = 1;
width = (size_t)val;
} else
done = 2;
val = 0;
break;
case ':':
minus = 1;
break;
case '-':
if (minus)
*fp++ = c;
else
done = 1;
break;
default:
if (isdigit((unsigned char)c)) {
val = (val * 10) + (c - '0');
if (val > 10000)
done = 2;
else
*fp++ = c;
} else
done = 1;
}
if (done == 0)
c = *str++;
}
if (done == 2) {
/* Found an error in the format */
fp = fmt + 1;
*fp = *str;
olen = 0;
} else {
if (dot == 0)
width = (size_t)val;
else
precision = (size_t)val;
olen = MAX(width, precision);
}
*fp++ = '\0';
/* Handle commands */
switch (c) {
case 'c':
pop(&val, NULL, &stack);
if (ochar(term, (unsigned char)val) == 0)
return NULL;
break;
case 's':
pop(NULL, &ostr, &stack);
if (ostr != NULL) {
int r;
l = strlen(ostr);
if (l < (size_t)olen)
l = olen;
if (checkbuf(term, (size_t)(l + 1)) == NULL)
return NULL;
r = snprintf(term->_buf + term->_bufpos, l + 1,
fmt, ostr);
if (r != -1)
term->_bufpos += (size_t)r;
}
break;
case 'l':
pop(NULL, &ostr, &stack);
if (ostr == NULL)
l = 0;
else
l = strlen(ostr);
#ifdef NCURSES_COMPAT_57
if (onum(term, "%ld", (long)l, 0) == 0)
return NULL;
#else
push((long)l, NULL, &stack);
#endif
break;
case 'd':
case 'o':
case 'x':
case 'X':
pop(&val, NULL, &stack);
if (onum(term, fmt, (int)val, olen) == 0)
return NULL;
break;
case 'p':
if (*str < '1' || *str > '9')
break;
l = (size_t)(*str++ - '1');
if (push(params[l].num, params[l].string, &stack))
return NULL;
break;
case 'P':
pop(&val, NULL, &stack);
if (*str >= 'a' && *str <= 'z')
dnums[*str - 'a'] = val;
else if (*str >= 'A' && *str <= 'Z')
term->_snums[*str - 'A'] = val;
break;
case 'g':
if (*str >= 'a' && *str <= 'z') {
if (push(dnums[*str - 'a'], NULL, &stack))
return NULL;
} else if (*str >= 'A' && *str <= 'Z') {
if (push(term->_snums[*str - 'A'],
NULL, &stack))
return NULL;
}
break;
case 'i':
if (piss[0] == 0)
params[0].num++;
if (piss[1] == 0)
params[1].num++;
break;
case '\'':
if (push((long)(unsigned char)*str++, NULL, &stack))
return NULL;
while (*str != '\0' && *str != '\'')
str++;
if (*str == '\'')
str++;
break;
case '{':
val = 0;
for (; isdigit((unsigned char)*str); str++)
val = (val * 10) + (*str - '0');
if (push(val, NULL, &stack))
return NULL;
while (*str != '\0' && *str != '}')
str++;
if (*str == '}')
str++;
break;
case '+':
case '-':
case '*':
case '/':
case 'm':
case 'A':
case 'O':
case '&':
case '|':
case '^':
case '=':
case '<':
case '>':
pop(&val, NULL, &stack);
pop(&val2, NULL, &stack);
switch (c) {
case '+':
val = val + val2;
break;
case '-':
val = val2 - val;
break;
case '*':
val = val * val2;
break;
case '/':
val = val ? val2 / val : 0;
break;
case 'm':
val = val ? val2 % val : 0;
break;
case 'A':
val = val && val2;
break;
case 'O':
val = val || val2;
break;
case '&':
val = val & val2;
break;
case '|':
val = val | val2;
break;
case '^':
val = val ^ val2;
break;
case '=':
val = val == val2;
break;
case '<':
val = val2 < val;
break;
case '>':
val = val2 > val;
break;
}
if (push(val, NULL, &stack))
return NULL;
break;
case '!':
case '~':
pop(&val, NULL, &stack);
switch (c) {
case '!':
val = !val;
break;
case '~':
val = ~val;
break;
}
if (push(val, NULL, &stack))
return NULL;
break;
case '?': /* if */
break;
case 't': /* then */
pop(&val, NULL, &stack);
if (val == 0) {
l = 0;
for (; *str != '\0'; str++) {
if (*str != '%')
continue;
str++;
if (*str == '?')
l++;
else if (*str == ';') {
if (l > 0)
l--;
else {
str++;
break;
}
} else if (*str == 'e' && l == 0) {
str++;
break;
}
}
}
break;
case 'e': /* else */
l = 0;
for (; *str != '\0'; str++) {
if (*str != '%')
continue;
str++;
if (*str == '?')
l++;
else if (*str == ';') {
if (l > 0)
l--;
else {
str++;
break;
}
}
}
break;
case ';': /* fi */
break;
}
}
term->_buf[term->_bufpos] = '\0';
return term->_buf;
}
char *
ti_tiparm(TERMINAL *term, const char *str, ...)
{
va_list va;
char *ret;
_DIAGASSERT(term != NULL);
_DIAGASSERT(str != NULL);
va_start(va, str);
ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
va_end(va);
return ret;
}
char *
tiparm(const char *str, ...)
{
va_list va;
char *ret;
_DIAGASSERT(str != NULL);
va_start(va, str);
ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
va_end(va);
return ret;
}
#ifdef VA_CHAR_LONG
char *
ti_tlparm(TERMINAL *term, const char *str, ...)
{
va_list va;
char *ret;
_DIAGASSERT(term != NULL);
_DIAGASSERT(str != NULL);
va_start(va, str);
ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
va_end(va);
return ret;
}
char *
tlparm(const char *str, ...)
{
va_list va;
char *ret;
_DIAGASSERT(str != NULL);
va_start(va, str);
ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
va_end(va);
return ret;
}
#endif
static char *
_tparm(const char *str, ...)
{
va_list va;
char *ret;
_DIAGASSERT(str != NULL);
va_start(va, str);
ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
va_end(va);
return ret;
}
char *
tparm(const char *str,
long p1, long p2, long p3, long p4, long p5,
long p6, long p7, long p8, long p9)
{
return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}