diff --git a/compat.h b/compat.h index cabdf3ad..7b543128 100644 --- a/compat.h +++ b/compat.h @@ -435,6 +435,11 @@ int utf8proc_mbtowc(wchar_t *, const char *, size_t); int utf8proc_wctomb(char *, wchar_t); #endif +/* tparm.c */ +#ifndef HAVE_TIPARM +char *compat_tiparm(const char *, ...); +#endif + #ifdef NEED_FUZZING /* tmux.c */ #define main __weak main diff --git a/compat/tiparm.c b/compat/tiparm.c new file mode 100644 index 00000000..01d0adf0 --- /dev/null +++ b/compat/tiparm.c @@ -0,0 +1,655 @@ +/* $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 +#if 0 +__RCSID("$NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $"); +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#if 0 +#include +#include +#endif + +#define TPARM_MAX 9 /* not likely to change */ + +typedef struct { + /* Output buffer for tparm */ + char *_buf; + size_t _buflen; + size_t _bufpos; + /* A-Z static variables for tparm */ + long _snums[26]; +} TERMINAL; + +#define _DIAGASSERT(x) + +#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. +*/ +static 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(¶ms, 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; +} + +#if 0 +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; +} +#endif + +char * +compat_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; +} + +#if 0 +#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); +} +#endif diff --git a/configure.ac b/configure.ac index 4b9d75b3..ccbedfee 100644 --- a/configure.ac +++ b/configure.ac @@ -351,6 +351,37 @@ else fi fi +# Replace tparm on newer ncurses where it has string requirements that are +# too strict for tmux (requires capabilities to exist). +AC_MSG_CHECKING(for ncurses with suitable tiparm) +AC_RUN_IFELSE([AC_LANG_SOURCE( + [ + #include + #if defined(HAVE_CURSES_H) + #include + #elif defined(HAVE_NCURSES_H) + #include + #endif + int main(void) { + #if defined(NCURSES_VERSION_MAJOR) && \ + (NCURSES_VERSION_MAJOR > 6 || \ + (NCURSES_VERSION_MAJOR == 6 && NCURSES_VERSION_MINOR > 3)) + exit(0); + #else + exit(1); + #endif + } + ])], + [ + AC_MSG_RESULT(no) + AC_LIBOBJ(tiparm) + ], + [ + AC_MSG_RESULT(yes) + AC_REPLACE_FUNCS(tiparm) + ] +) + # Look for utempter. AC_ARG_ENABLE( utempter, @@ -438,7 +469,7 @@ fi # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) - AC_LINK_IFELSE([AC_LANG_PROGRAM( +AC_LINK_IFELSE([AC_LANG_PROGRAM( [ #include #include diff --git a/tty-term.c b/tty-term.c index 4e9b7799..6fad619f 100644 --- a/tty-term.c +++ b/tty-term.c @@ -768,33 +768,78 @@ tty_term_string(struct tty_term *term, enum tty_code_code code) const char * tty_term_string1(struct tty_term *term, enum tty_code_code code, int a) { - return (tparm((char *) tty_term_string(term, code), a, 0, 0, 0, 0, 0, 0, 0, 0)); + const char *s; + +#ifndef HAVE_TIPARM + s = compat_tiparm(tty_term_string(term, code), a); +#else + s = tiparm(tty_term_string(term, code), a); +#endif + if (s == NULL) + fatalx("could not expand %s", tty_term_codes[code].name); + return s; } const char * tty_term_string2(struct tty_term *term, enum tty_code_code code, int a, int b) { - return (tparm((char *) tty_term_string(term, code), a, b, 0, 0, 0, 0, 0, 0, 0)); + const char *s; + +#ifndef HAVE_TIPARM + s = compat_tiparm(tty_term_string(term, code), a, b); +#else + s = tiparm(tty_term_string(term, code), a, b); +#endif + if (s == NULL) + fatalx("could not expand %s", tty_term_codes[code].name); + return s; } const char * tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, int c) { - return (tparm((char *) tty_term_string(term, code), a, b, c, 0, 0, 0, 0, 0, 0)); + const char *s; + +#ifndef HAVE_TIPARM + s = compat_tiparm(tty_term_string(term, code), a, b, c); +#else + s = tiparm(tty_term_string(term, code), a, b, c); +#endif + if (s == NULL) + fatalx("could not expand %s", tty_term_codes[code].name); + return s; } const char * tty_term_ptr1(struct tty_term *term, enum tty_code_code code, const void *a) { - return (tparm((char *) tty_term_string(term, code), (long)a, 0, 0, 0, 0, 0, 0, 0, 0)); + const char *s; + +#ifndef HAVE_TIPARM + s = compat_tiparm(tty_term_string(term, code), a); +#else + s = tiparm(tty_term_string(term, code), a); +#endif + if (s == NULL) + fatalx("could not expand %s", tty_term_codes[code].name); + return s; } const char * tty_term_ptr2(struct tty_term *term, enum tty_code_code code, const void *a, const void *b) { - return (tparm((char *) tty_term_string(term, code), (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0)); + const char *s; + +#ifndef HAVE_TIPARM + s = compat_tiparm(tty_term_string(term, code), a, b); +#else + s = tiparm(tty_term_string(term, code), a, b); +#endif + if (s == NULL) + fatalx("could not expand %s", tty_term_codes[code].name); + return s; } int