From d13b044d59619a52277c229eb37d38265690fb64 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Mon, 7 Aug 2023 15:39:57 +0000 Subject: [PATCH] address the strnvis(3) portability fiasco strnvis originates on OpenBSD. When NetBSD added it to their libc they decided to swap the argument. Without starting a holy war on the "best" argument order, adding an implementation of a function that's widely available and making its signature purposefully incompatible is beyond justification. FreeBSD (and so macos too?) followed NetBSD in this, so we end up with *two* major and incompatible strnvis implementations. libbsd is in a limbo, they started with the OpenBSD version but they'll probably switch to the NetBSD version in the future. That's why we can't have nice things. Do the right thing(tm) and check for the presence of the original strnvis(3), if not available or broken use the bundled one. --- compat/Makefile | 5 +- compat/vis.c | 272 +++++++++++++++++++++++++++++++++++++++++++++++ compat/vis/vis.h | 90 ++++++++++++++++ configure | 13 +++ have/Makefile | 3 +- have/vis.c | 26 +++++ 6 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 compat/vis.c create mode 100644 compat/vis/vis.h create mode 100644 have/vis.c diff --git a/compat/Makefile b/compat/Makefile index f820f71..65ec3ef 100644 --- a/compat/Makefile +++ b/compat/Makefile @@ -19,7 +19,8 @@ DISTFILES = Makefile \ strlcpy.c \ strtonum.c \ tree.h \ - vasprintf.c + vasprintf.c \ + vis.c all: false @@ -27,6 +28,8 @@ all: dist: ${DISTFILES} mkdir -p ${DESTDIR}/ ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ + mkdir -p ${DESTDIR}/vis + ${INSTALL} -m 0644 vis/vis.h ${DESTDIR}/vis .PHONY: all dist include ../config.mk diff --git a/compat/vis.c b/compat/vis.c new file mode 100644 index 0000000..9ebdc72 --- /dev/null +++ b/compat/vis.c @@ -0,0 +1,272 @@ +/* $OpenBSD: vis.c,v 1.26 2022/05/04 18:57:50 deraadt Exp $ */ +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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 +#include +#include +#include +#include +#include +#include + +static int +isoctal(int c) +{ + u_char uc = c; + + return uc >= '0' && uc <= '7'; +} + +static int +isvisible(int c, int flag) +{ + int vis_sp = flag & VIS_SP; + int vis_tab = flag & VIS_TAB; + int vis_nl = flag & VIS_NL; + int vis_safe = flag & VIS_SAFE; + int vis_glob = flag & VIS_GLOB; + int vis_all = flag & VIS_ALL; + u_char uc = c; + + if (c == '\\' || !vis_all) { + if ((u_int)c <= UCHAR_MAX && isascii(uc) && + ((c != '*' && c != '?' && c != '[' && c != '#') || !vis_glob) && + isgraph(uc)) + return 1; + if (!vis_sp && c == ' ') + return 1; + if (!vis_tab && c == '\t') + return 1; + if (!vis_nl && c == '\n') + return 1; + if (vis_safe && (c == '\b' || c == '\007' || c == '\r' || isgraph(uc))) + return 1; + } + return 0; +} + +/* + * vis - visually encode characters + */ +char * +vis(char *dst, int c, int flag, int nextc) +{ + int vis_dq = flag & VIS_DQ; + int vis_noslash = flag & VIS_NOSLASH; + int vis_cstyle = flag & VIS_CSTYLE; + int vis_octal = flag & VIS_OCTAL; + int vis_glob = flag & VIS_GLOB; + + if (isvisible(c, flag)) { + if ((c == '"' && vis_dq) || + (c == '\\' && !vis_noslash)) + *dst++ = '\\'; + *dst++ = c; + *dst = '\0'; + return (dst); + } + + if (vis_cstyle) { + switch (c) { + case '\n': + *dst++ = '\\'; + *dst++ = 'n'; + goto done; + case '\r': + *dst++ = '\\'; + *dst++ = 'r'; + goto done; + case '\b': + *dst++ = '\\'; + *dst++ = 'b'; + goto done; + case '\a': + *dst++ = '\\'; + *dst++ = 'a'; + goto done; + case '\v': + *dst++ = '\\'; + *dst++ = 'v'; + goto done; + case '\t': + *dst++ = '\\'; + *dst++ = 't'; + goto done; + case '\f': + *dst++ = '\\'; + *dst++ = 'f'; + goto done; + case ' ': + *dst++ = '\\'; + *dst++ = 's'; + goto done; + case '\0': + *dst++ = '\\'; + *dst++ = '0'; + if (isoctal(nextc)) { + *dst++ = '0'; + *dst++ = '0'; + } + goto done; + } + } + if (((c & 0177) == ' ') || vis_octal || + (vis_glob && (c == '*' || c == '?' || c == '[' || c == '#'))) { + *dst++ = '\\'; + *dst++ = ((u_char)c >> 6 & 07) + '0'; + *dst++ = ((u_char)c >> 3 & 07) + '0'; + *dst++ = ((u_char)c & 07) + '0'; + goto done; + } + if (!vis_noslash) + *dst++ = '\\'; + if (c & 0200) { + c &= 0177; + *dst++ = 'M'; + } + if (iscntrl((u_char)c)) { + *dst++ = '^'; + if (c == 0177) + *dst++ = '?'; + else + *dst++ = c + '@'; + } else { + *dst++ = '-'; + *dst++ = c; + } +done: + *dst = '\0'; + return (dst); +} + +/* + * strvis, strnvis, strvisx - visually encode characters from src into dst + * + * Dst must be 4 times the size of src to account for possible + * expansion. The length of dst, not including the trailing NULL, + * is returned. + * + * Strnvis will write no more than siz-1 bytes (and will NULL terminate). + * The number of bytes needed to fully encode the string is returned. + * + * Strvisx encodes exactly len bytes from src into dst. + * This is useful for encoding a block of data. + */ +int +strvis(char *dst, const char *src, int flag) +{ + char c; + char *start; + + for (start = dst; (c = *src);) + dst = vis(dst, c, flag, *++src); + *dst = '\0'; + return (dst - start); +} + +int +strnvis(char *dst, const char *src, size_t siz, int flag) +{ + int vis_dq = flag & VIS_DQ; + int vis_noslash = flag & VIS_NOSLASH; + char *start, *end; + char tbuf[5]; + int c, i; + + i = 0; + for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) { + if (isvisible(c, flag)) { + if ((c == '"' && vis_dq) || + (c == '\\' && !vis_noslash)) { + /* need space for the extra '\\' */ + if (dst + 1 >= end) { + i = 2; + break; + } + *dst++ = '\\'; + } + i = 1; + *dst++ = c; + src++; + } else { + i = vis(tbuf, c, flag, *++src) - tbuf; + if (dst + i <= end) { + memcpy(dst, tbuf, i); + dst += i; + } else { + src--; + break; + } + } + } + if (siz > 0) + *dst = '\0'; + if (dst + i > end) { + /* adjust return value for truncation */ + while ((c = *src)) + dst += vis(tbuf, c, flag, *++src) - tbuf; + } + return (dst - start); +} + +int +stravis(char **outp, const char *src, int flag) +{ + char *buf; + int len, serrno; + + buf = reallocarray(NULL, 4, strlen(src) + 1); + if (buf == NULL) + return -1; + len = strvis(buf, src, flag); + serrno = errno; + *outp = realloc(buf, len + 1); + if (*outp == NULL) { + *outp = buf; + errno = serrno; + } + return (len); +} + +int +strvisx(char *dst, const char *src, size_t len, int flag) +{ + char c; + char *start; + + for (start = dst; len > 1; len--) { + c = *src; + dst = vis(dst, c, flag, *++src); + } + if (len) + dst = vis(dst, *src, flag, '\0'); + *dst = '\0'; + return (dst - start); +} diff --git a/compat/vis/vis.h b/compat/vis/vis.h new file mode 100644 index 0000000..59b7d6b --- /dev/null +++ b/compat/vis/vis.h @@ -0,0 +1,90 @@ +/* $OpenBSD: vis.h,v 1.15 2015/07/20 01:52:27 millert Exp $ */ +/* $NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)vis.h 5.9 (Berkeley) 4/3/91 + */ + +#ifndef _VIS_H_ +#define _VIS_H_ + +/* + * to select alternate encoding format + */ +#define VIS_OCTAL 0x01 /* use octal \ddd format */ +#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropriate */ + +/* + * to alter set of characters encoded (default is to encode all + * non-graphic except space, tab, and newline). + */ +#define VIS_SP 0x04 /* also encode space */ +#define VIS_TAB 0x08 /* also encode tab */ +#define VIS_NL 0x10 /* also encode newline */ +#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL) +#define VIS_SAFE 0x20 /* only encode "unsafe" characters */ +#define VIS_DQ 0x200 /* backslash-escape double quotes */ +#define VIS_ALL 0x400 /* encode all characters */ + +/* + * other + */ +#define VIS_NOSLASH 0x40 /* inhibit printing '\' */ +#define VIS_GLOB 0x100 /* encode glob(3) magics and '#' */ + +/* + * unvis return codes + */ +#define UNVIS_VALID 1 /* character valid */ +#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */ +#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */ +#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */ +#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */ + +/* + * unvis flags + */ +#define UNVIS_END 1 /* no more characters */ + +#include + +char *vis(char *, int, int, int); +int strvis(char *, const char *, int); +int stravis(char **, const char *, int); +int strnvis(char *, const char *, size_t, int) + __attribute__ ((__bounded__(__string__,1,3))); +int strvisx(char *, const char *, size_t, int) + __attribute__ ((__bounded__(__string__,1,3))); +int strunvis(char *, const char *); +int unvis(char *, char, int *, int); +ssize_t strnunvis(char *, const char *, size_t) + __attribute__ ((__bounded__(__string__,1,3))); + +#endif /* !_VIS_H_ */ diff --git a/configure b/configure index 83332f9..7ae1d5a 100755 --- a/configure +++ b/configure @@ -120,6 +120,7 @@ echo "file config.log: writing..." NEED_GNU_SOURCE=0 NEED_OPENBSD_SOURCE=0 +NEED_LIBBSD_OPENBSD_VIS=0 COMPATS= COMP="${CC} ${CFLAGS} -Wno-unused -Werror" @@ -192,6 +193,10 @@ runtest() { NEED_OPENBSD_SOURCE=1 return 0 fi + if [ "$4" = -DLIBBSD_OPENBSD_VIS ]; then + NEED_LIBBSD_OPENBSD_VIS=1 + return 0 + fi if [ -n "$3" ]; then CFLAGS="$CFLAGS $3" fi @@ -284,6 +289,7 @@ runtest strlcpy STRLCPY || true runtest strtonum STRTONUM -D_OPENBSD_SOURCE || true runtest tree_h TREE_H || true runtest vasprintf VASPRINTF -D_GNU_SOURCE || true +runtest vis VIS -DLIBBSD_OPENBSD_VIS || true deptest libevent2 LIBEVENT2 || true @@ -314,6 +320,10 @@ if [ ${HAVE_QUEUE_H} -eq 0 -o ${HAVE_IMSG} -eq 0 -o ${HAVE_TREE_H} -eq 0 ]; then CFLAGS="${CFLAGS} -I ${PWD}/compat" fi +if [ ${HAVE_VIS} -eq 0 ]; then + CFLAGS="${CFLAGS} -I ${PWD}/compat/vis" +fi + if [ $HAVE_LIBEVENT2 -eq 1 ]; then CFLAGS="$CFLAGS -DHAVE_LIBEVENT2=1" fi @@ -324,6 +334,9 @@ fi if [ $NEED_OPENBSD_SOURCE = 1 ]; then CFLAGS="$CFLAGS -D_OPENBSD_SOURCE" fi +if [ $NEED_LIBBSD_OPENBSD_VIS = 1 ]; then + CFLAGS="$CFLAGS -DLIBBSD_OPENBSD_VIS" +fi CFLAGS="${CFLAGS} ${CDIAGFLAGS}" diff --git a/have/Makefile b/have/Makefile index ee214d5..9fd19e8 100644 --- a/have/Makefile +++ b/have/Makefile @@ -24,7 +24,8 @@ DISTFILES = Makefile \ strlcpy.c \ strtonum.c \ tree_h.c \ - vasprintf.c + vasprintf.c \ + vis.c all: false diff --git a/have/vis.c b/have/vis.c new file mode 100644 index 0000000..85a4307 --- /dev/null +++ b/have/vis.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +int +main(void) +{ + char buf[128]; + + return strnvis(buf, "Hello, world!\n", sizeof(buf), 0); +}