diff options
author | Russ Allbery <eagle@eyrie.org> | 2015-08-18 19:55:54 -0700 |
---|---|---|
committer | Russ Allbery <eagle@eyrie.org> | 2015-08-18 20:00:41 -0700 |
commit | 531cdbab45a87eee3ee98b6ad187c2962f50fca5 (patch) | |
tree | 010ff71c67fee1a16a32761b8139bf86dd9e35fb | |
parent | 0bb82d59d7733657c724fb4698153cf8ec304c25 (diff) |
Update to rra-c-util 5.8 and C TAP Harness 3.3
Update to rra-c-util 5.8:
* Support the Solaris 10 embedded Kerberos implementation.
* Use calloc or reallocarray instead of malloc.
* Fix compilation with a C++ compiler.
Update to C TAP Harness 3.3:
* Display verbose test results with -v or C_TAP_VERBOSE.
* Reopen standard input to /dev/null when running a test list.
* Don't leak extraneous file descriptors to tests.
48 files changed, 1934 insertions, 475 deletions
@@ -20,6 +20,8 @@ /tests/plugin/queue-only-t /tests/plugin/queuing-t /tests/portable/asprintf-t +/tests/portable/mkstemp-t +/tests/portable/reallocarray-t /tests/portable/snprintf-t /tests/runtests /tests/util/concat-t diff --git a/Makefile.am b/Makefile.am index 46eadd3..e494999 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,7 @@ # Automake makefile for krb5-sync. # # Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2015 Russ Allbery <eagle@eyrie.org> # Copyright 2006, 2007, 2010, 2012, 2013 # The Board of Trustees of the Leland Stanford Junior University # @@ -80,16 +81,17 @@ MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/ar-lib \ # # -Wconversion http://bugs.debian.org/488884 (htons warnings) # -# Last checked against gcc 4.7.2 (2013-04-22). -D_FORTIFY_SOURCE=2 enables +# Last checked against gcc 4.8.2 (2014-04-12). -D_FORTIFY_SOURCE=2 enables # warn_unused_result attribute markings on glibc functions on Linux, which # catches a few more issues. -WARNINGS = -g -O -D_FORTIFY_SOURCE=2 -Wall -Wextra -Wendif-labels \ - -Wformat=2 -Winit-self -Wswitch-enum -Wuninitialized -Wfloat-equal \ - -Wdeclaration-after-statement -Wshadow -Wpointer-arith \ - -Wbad-function-cast -Wcast-align -Wwrite-strings \ - -Wjump-misses-init -Wlogical-op -Wstrict-prototypes \ - -Wold-style-definition -Wmissing-prototypes -Wnormalized=nfc \ - -Wpacked -Wredundant-decls -Wnested-externs -Winline -Wvla -Werror +WARNINGS = -g -O -fstrict-overflow -fstrict-aliasing -D_FORTIFY_SOURCE=2 \ + -Wall -Wextra -Wendif-labels -Wformat=2 -Winit-self -Wswitch-enum \ + -Wstrict-overflow=5 -Wmissing-format-attribute -Wfloat-equal \ + -Wdeclaration-after-statement -Wshadow -Wpointer-arith \ + -Wbad-function-cast -Wcast-align -Wwrite-strings -Wjump-misses-init \ + -Wlogical-op -Wstrict-prototypes -Wold-style-definition \ + -Wmissing-prototypes -Wnormalized=nfc -Wpacked -Wredundant-decls \ + -Wnested-externs -Winline -Wvla -Werror warnings: $(MAKE) V=0 CFLAGS='$(WARNINGS)' @@ -98,7 +100,8 @@ warnings: # The bits below are for the test suite, not for the main package. check_PROGRAMS = tests/runtests tests/plugin/heimdal-t tests/plugin/mit-t \ tests/plugin/queue-only-t tests/plugin/queuing-t \ - tests/portable/asprintf-t tests/portable/snprintf-t \ + tests/portable/asprintf-t tests/portable/mkstemp-t \ + tests/portable/reallocarray-t tests/portable/snprintf-t \ tests/util/messages-krb5-t tests/util/messages-t tests/util/xmalloc check_LIBRARIES = tests/tap/libtap.a tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \ @@ -134,6 +137,13 @@ tests_plugin_queuing_t_LDADD = tests/tap/libtap.a portable/libportable.la \ tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \ tests/portable/asprintf.c tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la +tests_portable_mkstemp_t_SOURCES = tests/portable/mkstemp-t.c \ + tests/portable/mkstemp.c +tests_portable_mkstemp_t_LDADD = tests/tap/libtap.a portable/libportable.la +tests_portable_reallocarray_t_SOURCES = tests/portable/reallocarray-t.c \ + tests/portable/reallocarray.c +tests_portable_reallocarray_t_LDADD = tests/tap/libtap.a \ + portable/libportable.la tests_portable_snprintf_t_SOURCES = tests/portable/snprintf-t.c \ tests/portable/snprintf.c tests_portable_snprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la @@ -1,5 +1,19 @@ User-Visible krb5-sync Changes +krb5-sync 3.1 (unreleased) + + Update to rra-c-util 5.8: + + * Support the Solaris 10 embedded Kerberos implementation. + * Use calloc or reallocarray instead of malloc. + * Fix compilation with a C++ compiler. + + Update to C TAP Harness 3.3: + + * Display verbose test results with -v or C_TAP_VERBOSE. + * Reopen standard input to /dev/null when running a test list. + * Don't leak extraneous file descriptors to tests. + krb5-sync 3.0 (2013-12-09) The default installed module name has been changed to sync.so from diff --git a/configure.ac b/configure.ac index b2c2d45..5291707 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,7 @@ dnl Autoconf configuration for krb5-sync. dnl dnl Written by Russ Allbery <eagle@eyrie.org> +dnl Copyright 2015 Russ Allbery <eagle@eyrie.org> dnl Copyright 2006, 2007, 2010, 2012, 2013 dnl The Board of Trustees of the Leland Stanford Junior University dnl @@ -15,6 +16,9 @@ AM_INIT_AUTOMAKE([1.11 check-news dist-xz foreign silent-rules subdir-objects -Wall -Werror]) AM_MAINTAINER_MODE +dnl Detect unexpanded macros. +m4_pattern_forbid([^_?RRA_]) + AC_PROG_CC AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE @@ -64,7 +68,7 @@ LIBS="$save_LIBS" AC_SUBST([DL_LIBS]) AC_HEADER_STDBOOL -AC_CHECK_HEADERS([sys/bittypes.h syslog.h]) +AC_CHECK_HEADERS([sys/bittypes.h sys/select.h sys/time.h syslog.h]) AC_CHECK_DECLS([snprintf, vsnprintf]) RRA_C_C99_VAMACROS RRA_C_GNU_VAMACROS @@ -74,7 +78,7 @@ AC_CHECK_TYPES([ssize_t], [], [], [#include <sys/types.h>]) RRA_FUNC_SNPRINTF AC_CHECK_FUNCS([setrlimit]) -AC_REPLACE_FUNCS([asprintf strndup]) +AC_REPLACE_FUNCS([asprintf mkstemp reallocarray strndup]) AC_CONFIG_FILES([Makefile]) AC_CONFIG_HEADER([config.h]) @@ -17,6 +17,12 @@ dnl dnl If KRB5_CPPFLAGS, KRB5_LDFLAGS, or KRB5_LIBS are set before calling these dnl macros, their values will be added to whatever the macros discover. dnl +dnl KRB5_CPPFLAGS_GCC will be set to the same value as KRB5_CPPFLAGS but with +dnl any occurrences of -I changed to -isystem. This may be useful to suppress +dnl warnings from the Kerberos header files when building with GCC and +dnl aggressive warning flags. Be aware that this change will change the +dnl compiler header file search order as well. +dnl dnl Provides the RRA_LIB_KRB5_OPTIONAL macro, which should be used if Kerberos dnl support is optional. In this case, Kerberos libraries are mandatory if dnl --with-krb5 is given, and will not be probed for if --without-krb5 is @@ -44,7 +50,7 @@ dnl The canonical version of this file is maintained in the rra-c-util dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>. dnl dnl Written by Russ Allbery <eagle@eyrie.org> -dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013 +dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014 dnl The Board of Trustees of the Leland Stanford Junior University dnl dnl This file is free software; the authors give unlimited permission to copy @@ -58,6 +64,8 @@ dnl Headers to include when probing for Kerberos library properties. AC_DEFUN([RRA_INCLUDES_KRB5], [[ #if HAVE_KRB5_H # include <krb5.h> +#elif HAVE_KERBEROSV5_KRB5_H +# include <kerberosv5/krb5.h> #else # include <krb5/krb5.h> #endif @@ -107,6 +115,23 @@ AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER], AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])])]) +dnl Check for the com_err header. Internal helper macro since we need +dnl to do the same checks in multiple places. +AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR], +[AS_IF([test x"$rra_krb5_incroot" = x], + [AC_CHECK_HEADERS([et/com_err.h kerberosv5/com_err.h])], + [_RRA_LIB_KRB5_CHECK_HEADER([et/com_err.h]) + _RRA_LIB_KRB5_CHECK_HEADER([kerberosv5/com_err.h])])]) + +dnl Check for the main Kerberos header. Internal helper macro since we need +dnl to do the same checks in multiple places. +AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER_KRB5], +[AS_IF([test x"$rra_krb5_incroot" = x], + [AC_CHECK_HEADERS([krb5.h kerberosv5/krb5.h krb5/krb5.h])], + [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h]) + _RRA_LIB_KRB5_CHECK_HEADER([kerberosv5/krb5.h]) + _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])])]) + dnl Does the appropriate library checks for reduced-dependency Kerberos dnl linkage. The single argument, if true, says to fail if Kerberos could not dnl be found. @@ -116,10 +141,7 @@ AC_DEFUN([_RRA_LIB_KRB5_REDUCED], [AS_IF([test x"$1" = xtrue], [AC_MSG_ERROR([cannot find usable Kerberos library])])]) LIBS="$KRB5_LIBS $LIBS" - AS_IF([test x"$rra_krb5_incroot" = x], - [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])], - [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h]) - _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])]) + _RRA_LIB_KRB5_CHECK_HEADER_KRB5 AC_CHECK_FUNCS([krb5_get_error_message], [AC_CHECK_FUNCS([krb5_free_error_message])], [AC_CHECK_FUNCS([krb5_get_error_string], [], @@ -134,7 +156,7 @@ AC_DEFUN([_RRA_LIB_KRB5_REDUCED], [AS_IF([test x"$1" = xtrue], [AC_MSG_ERROR([cannot find usable com_err library])], [KRB5_LIBS=""])]) - AC_CHECK_HEADERS([et/com_err.h])])])])]) + _RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])]) RRA_LIB_KRB5_RESTORE]) dnl Does the appropriate library checks for Kerberos linkage when we don't @@ -181,10 +203,7 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL], [$rra_krb5_extra])], [-lasn1 -lcom_err -lcrypto $rra_krb5_extra]) LIBS="$KRB5_LIBS $LIBS" - AS_IF([test x"$rra_krb5_incroot" = x], - [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])], - [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h]) - _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])]) + _RRA_LIB_KRB5_CHECK_HEADER_KRB5 AC_CHECK_FUNCS([krb5_get_error_message], [AC_CHECK_FUNCS([krb5_free_error_message])], [AC_CHECK_FUNCS([krb5_get_error_string], [], @@ -192,7 +211,7 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL], [AC_CHECK_FUNCS([krb5_svc_get_msg], [AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [], [RRA_INCLUDES_KRB5])], - [AC_CHECK_HEADERS([et/com_err.h])])])])]) + [_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])]) RRA_LIB_KRB5_RESTORE]) dnl Sanity-check the results of krb5-config and be sure we can really link a @@ -216,10 +235,7 @@ AC_DEFUN([_RRA_LIB_KRB5_CONFIG], [RRA_KRB5_CONFIG([${rra_krb5_root}], [krb5], [KRB5], [_RRA_LIB_KRB5_CHECK([$1]) RRA_LIB_KRB5_SWITCH - AS_IF([test x"$rra_krb5_incroot" = x], - [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])], - [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h]) - _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])]) + _RRA_LIB_KRB5_CHECK_HEADER_KRB5 AC_CHECK_FUNCS([krb5_get_error_message], [AC_CHECK_FUNCS([krb5_free_error_message])], [AC_CHECK_FUNCS([krb5_get_error_string], [], @@ -227,7 +243,7 @@ AC_DEFUN([_RRA_LIB_KRB5_CONFIG], [AC_CHECK_FUNCS([krb5_svc_get_msg], [AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [], [RRA_INCLUDES_KRB5])], - [AC_CHECK_HEADERS([et/com_err.h])])])])]) + [_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])]) RRA_LIB_KRB5_RESTORE], [_RRA_LIB_KRB5_PATHS _RRA_LIB_KRB5_MANUAL([$1])])]) @@ -239,6 +255,10 @@ dnl checking. AC_DEFUN([_RRA_LIB_KRB5_INTERNAL], [AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS]) rra_krb5_incroot= + AC_SUBST([KRB5_CPPFLAGS]) + AC_SUBST([KRB5_CPPFLAGS_GCC]) + AC_SUBST([KRB5_LDFLAGS]) + AC_SUBST([KRB5_LIBS]) AS_IF([test x"$rra_krb5_includedir" != x], [rra_krb5_incroot="$rra_krb5_includedir"], [AS_IF([test x"$rra_krb5_root" != x], @@ -253,7 +273,8 @@ AC_DEFUN([_RRA_LIB_KRB5_INTERNAL], rra_krb5_uses_com_err=false AS_CASE([$KRB5_LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true]) AM_CONDITIONAL([KRB5_USES_COM_ERR], - [test x"$rra_krb5_uses_com_err" = xtrue])]) + [test x"$rra_krb5_uses_com_err" = xtrue]) + KRB5_CPPFLAGS_GCC=`echo "$KRB5_CPPFLAGS" | sed -e 's/-I/-isystem /g'`]) dnl The main macro for packages with mandatory Kerberos support. AC_DEFUN([RRA_LIB_KRB5], @@ -261,9 +282,6 @@ AC_DEFUN([RRA_LIB_KRB5], rra_krb5_libdir= rra_krb5_includedir= rra_use_KRB5=true - AC_SUBST([KRB5_CPPFLAGS]) - AC_SUBST([KRB5_LDFLAGS]) - AC_SUBST([KRB5_LIBS]) AC_ARG_WITH([krb5], [AS_HELP_STRING([--with-krb5=DIR], @@ -289,9 +307,6 @@ AC_DEFUN([RRA_LIB_KRB5_OPTIONAL], rra_krb5_libdir= rra_krb5_includedir= rra_use_KRB5= - AC_SUBST([KRB5_CPPFLAGS]) - AC_SUBST([KRB5_LDFLAGS]) - AC_SUBST([KRB5_LIBS]) AC_ARG_WITH([krb5], [AS_HELP_STRING([--with-krb5@<:@=DIR@:>@], diff --git a/portable/asprintf.c b/portable/asprintf.c index eb2b713..9693842 100644 --- a/portable/asprintf.c +++ b/portable/asprintf.c @@ -19,6 +19,7 @@ */ #include <config.h> +#include <portable/macros.h> #include <portable/system.h> #include <errno.h> @@ -28,11 +29,14 @@ * with the system versions. */ #if TESTING +# undef asprintf +# undef vasprintf # define asprintf test_asprintf # define vasprintf test_vasprintf int test_asprintf(char **, const char *, ...) __attribute__((__format__(printf, 2, 3))); -int test_vasprintf(char **, const char *, va_list); +int test_vasprintf(char **, const char *, va_list) + __attribute__((__format__(printf, 2, 0))); #endif diff --git a/portable/krb5-extra.c b/portable/krb5-extra.c index b1c8b8d..c8309a4 100644 --- a/portable/krb5-extra.c +++ b/portable/krb5-extra.c @@ -22,6 +22,7 @@ #include <config.h> #include <portable/krb5.h> +#include <portable/macros.h> #include <portable/system.h> #include <errno.h> @@ -33,6 +34,8 @@ # include <ibm_svc/krb5_svc.h> # elif defined(HAVE_ET_COM_ERR_H) # include <et/com_err.h> +# elif defined(HAVE_KERBEROSV5_COM_ERR_H) +# include <kerberosv5/com_err.h> # else # include <com_err.h> # endif diff --git a/portable/krb5.h b/portable/krb5.h index 2d41180..159f40f 100644 --- a/portable/krb5.h +++ b/portable/krb5.h @@ -42,8 +42,10 @@ #endif #include <portable/macros.h> -#ifdef HAVE_KRB5_H +#if defined(HAVE_KRB5_H) # include <krb5.h> +#elif defined(HAVE_KERBEROSV5_KRB5_H) +# include <kerberosv5/krb5.h> #else # include <krb5/krb5.h> #endif @@ -169,4 +171,6 @@ const char *krb5_principal_get_realm(krb5_context, krb5_const_principal); /* Undo default visibility change. */ #pragma GCC visibility pop +END_DECLS + #endif /* !PORTABLE_KRB5_H */ diff --git a/portable/macros.h b/portable/macros.h index b5093f5..d4cc2cc 100644 --- a/portable/macros.h +++ b/portable/macros.h @@ -37,7 +37,8 @@ * variadic macro support. */ #if !defined(__attribute__) && !defined(__alloc_size__) -# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) +# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)) \ + && !defined(__clang__) # define __alloc_size__(spec, args...) /* empty */ # endif #endif diff --git a/portable/mkstemp.c b/portable/mkstemp.c new file mode 100644 index 0000000..7c733a4 --- /dev/null +++ b/portable/mkstemp.c @@ -0,0 +1,100 @@ +/* + * Replacement for a missing mkstemp. + * + * Provides the same functionality as the library function mkstemp for those + * systems that don't have it. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Written by Russ Allbery <eagle@eyrie.org> + * + * The authors hereby relinquish any claim to any copyright that they may have + * in this work, whether granted under contract or by operation of law or + * international treaty, and hereby commit to the public, at large, that they + * shall not, at any time in the future, seek to enforce any copyright in this + * work against any person or entity, or prevent any person or entity from + * copying, publishing, distributing or creating derivative works of this + * work. + */ + +#include <config.h> +#include <portable/system.h> + +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif +#include <time.h> + +/* + * If we're running the test suite, rename mkstemp to avoid conflicts with the + * system version. #undef it first because some systems may define it to + * another name. + */ +#if TESTING +# undef mkstemp +# define mkstemp test_mkstemp +int test_mkstemp(char *); +#endif + +/* Pick the longest available integer type. */ +#if HAVE_LONG_LONG_INT +typedef unsigned long long long_int_type; +#else +typedef unsigned long long_int_type; +#endif + +int +mkstemp(char *template) +{ + static const char letters[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + size_t length; + char *XXXXXX; + struct timeval tv; + long_int_type randnum, working; + int i, tries, fd; + + /* + * Make sure we have a valid template and initialize p to point at the + * beginning of the template portion of the string. + */ + length = strlen(template); + if (length < 6) { + errno = EINVAL; + return -1; + } + XXXXXX = template + length - 6; + if (strcmp(XXXXXX, "XXXXXX") != 0) { + errno = EINVAL; + return -1; + } + + /* Get some more-or-less random information. */ + gettimeofday(&tv, NULL); + randnum = ((long_int_type) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid(); + + /* + * Now, try to find a working file name. We try no more than TMP_MAX file + * names. + */ + for (tries = 0; tries < TMP_MAX; tries++) { + for (working = randnum, i = 0; i < 6; i++) { + XXXXXX[i] = letters[working % 62]; + working /= 62; + } + fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0 || (errno != EEXIST && errno != EISDIR)) + return fd; + + /* + * This is a relatively random increment. Cut off the tail end of + * tv_usec since it's often predictable. + */ + randnum += (tv.tv_usec >> 10) & 0xfff; + } + errno = EEXIST; + return -1; +} diff --git a/portable/reallocarray.c b/portable/reallocarray.c new file mode 100644 index 0000000..e9404e9 --- /dev/null +++ b/portable/reallocarray.c @@ -0,0 +1,56 @@ +/* + * Replacement for a missing reallocarray. + * + * Provides the same functionality as the OpenBSD library function + * reallocarray for those systems that don't have it. This function is the + * same as realloc, but takes the size arguments in the same form as calloc + * and checks for overflow so that the caller doesn't need to. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Written by Russ Allbery <eagle@eyrie.org> + * + * The authors hereby relinquish any claim to any copyright that they may have + * in this work, whether granted under contract or by operation of law or + * international treaty, and hereby commit to the public, at large, that they + * shall not, at any time in the future, seek to enforce any copyright in this + * work against any person or entity, or prevent any person or entity from + * copying, publishing, distributing or creating derivative works of this + * work. + */ + +#include <config.h> +#include <portable/system.h> + +#include <errno.h> + +/* + * If we're running the test suite, rename reallocarray to avoid conflicts + * with the system version. #undef it first because some systems may define + * it to another name. + */ +#if TESTING +# undef reallocarray +# define reallocarray test_reallocarray +void *test_reallocarray(void *, size_t, size_t); +#endif + +/* + * nmemb * size cannot overflow if both are smaller than sqrt(SIZE_MAX). We + * can calculate that value statically by using 2^(sizeof(size_t) * 8) as the + * value of SIZE_MAX and then taking the square root, which gives + * 2^(sizeof(size_t) * 4). Compute the exponentiation with shift. + */ +#define CHECK_THRESHOLD (1UL << (sizeof(size_t) * 4)) + +void * +reallocarray(void *ptr, size_t nmemb, size_t size) +{ + if (nmemb >= CHECK_THRESHOLD || size >= CHECK_THRESHOLD) + if (nmemb > 0 && SIZE_MAX / nmemb <= size) { + errno = ENOMEM; + return NULL; + } + return realloc(ptr, nmemb * size); +} diff --git a/portable/snprintf.c b/portable/snprintf.c index c35ad80..9818acd 100644 --- a/portable/snprintf.c +++ b/portable/snprintf.c @@ -19,6 +19,8 @@ * conflicts with the system version. */ #if TESTING +# undef snprintf +# undef vsnprintf # define snprintf test_snprintf # define vsnprintf test_vsnprintf #endif diff --git a/portable/system.h b/portable/system.h index 5345da3..ef56fae 100644 --- a/portable/system.h +++ b/portable/system.h @@ -5,7 +5,8 @@ * file is the equivalent of including all of the following headers, * portably: * - * #include <sys/types.h> + * #include <inttypes.h> + * #include <limits.h> * #include <stdarg.h> * #include <stdbool.h> * #include <stddef.h> @@ -14,6 +15,7 @@ * #include <stdint.h> * #include <string.h> * #include <strings.h> + * #include <sys/types.h> * #include <unistd.h> * * Missing functions are provided via #define or prototyped if available from @@ -46,6 +48,7 @@ #if HAVE_INTTYPES_H # include <inttypes.h> #endif +#include <limits.h> #include <stdarg.h> #include <stddef.h> #if HAVE_STDINT_H @@ -124,6 +127,12 @@ extern int snprintf(char *, size_t, const char *, ...) #if !HAVE_DECL_VSNPRINTF extern int vsnprintf(char *, size_t, const char *, va_list); #endif +#if !HAVE_MKSTEMP +extern int mkstemp(char *); +#endif +#if !HAVE_REALLOCARRAY +extern void *reallocarray(void *, size_t, size_t); +#endif #if !HAVE_STRNDUP extern char *strndup(const char *, size_t); #endif diff --git a/tests/TESTS b/tests/TESTS index 6d294a4..b13ed60 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -8,6 +8,8 @@ plugin/mit plugin/queue-only plugin/queuing portable/asprintf +portable/mkstemp +portable/reallocarray portable/snprintf tools/backend util/messages diff --git a/tests/data/valgrind.supp b/tests/data/valgrind.supp index 92e6670..8be538f 100644 --- a/tests/data/valgrind.supp +++ b/tests/data/valgrind.supp @@ -11,7 +11,7 @@ # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2011, 2012, 2013 +# Copyright 2011, 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -37,7 +37,33 @@ Memcheck:Leak fun:calloc fun:_dlerror_run - fun:dlopen* +} +{ + heimdal-gss-config + Memcheck:Leak + fun:*alloc + ... + fun:krb5_config_parse_debug +} +{ + heimdal-gss-config-2 + Memcheck:Leak + fun:*alloc + fun:_krb5_config_get_entry +} +{ + heimdal-gss-krb5-init + Memcheck:Leak + fun:*alloc + ... + fun:_gsskrb5_init +} +{ + heimdal-gss-load-mech + Memcheck:Leak + fun:*alloc + ... + fun:_gss_load_mech } { heimdal-krb5-init-context-once @@ -78,6 +104,13 @@ fun:gss_krb5int_ccache_name } { + mit-gss-error + Memcheck:Leak + fun:*alloc + ... + fun:krb5_gss_save_error_string +} +{ mit-krb5-pkinit-openssl-init Memcheck:Leak fun:*alloc diff --git a/tests/docs/pod-spelling-t b/tests/docs/pod-spelling-t index 1a02af8..7b61c86 100755 --- a/tests/docs/pod-spelling-t +++ b/tests/docs/pod-spelling-t @@ -1,14 +1,12 @@ #!/usr/bin/perl # # Checks all POD files in the tree for spelling errors using Test::Spelling. -# This test is disabled unless RRA_MAINTAINER_TESTS is set, since spelling -# dictionaries vary too much between environments. # # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2012, 2013 +# Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -36,11 +34,12 @@ use warnings; use lib "$ENV{SOURCE}/tap/perl"; use Test::More; -use Test::RRA qw(skip_unless_maintainer use_prereq); +use Test::RRA qw(skip_unless_author use_prereq); use Test::RRA::Automake qw(automake_setup perl_dirs); -# Only run this test for the maintainer. -skip_unless_maintainer('Spelling tests'); +# Only run this test for the module author since the required stopwords are +# too sensitive to the exact spell-checking program and dictionary. +skip_unless_author('Spelling tests'); # Load prerequisite modules. use_prereq('Test::Spelling'); diff --git a/tests/docs/pod-t b/tests/docs/pod-t index 6918271..53f9925 100755 --- a/tests/docs/pod-t +++ b/tests/docs/pod-t @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Check all POD documents in the tree, except for any embedded Perl module # distribution, for POD formatting errors. @@ -7,7 +7,7 @@ # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2012, 2013 +# Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -35,9 +35,13 @@ use warnings; use lib "$ENV{SOURCE}/tap/perl"; use Test::More; -use Test::RRA qw(use_prereq); +use Test::RRA qw(skip_unless_automated use_prereq); use Test::RRA::Automake qw(automake_setup perl_dirs); +# Skip this test for normal user installs, since we normally pre-generate all +# of the documentation and the end user doesn't care. +skip_unless_automated('POD syntax tests'); + # Load prerequisite modules. use_prereq('Test::Pod'); diff --git a/tests/perl/critic-t b/tests/perl/critic-t index d588d65..c66227c 100755 --- a/tests/perl/critic-t +++ b/tests/perl/critic-t @@ -2,15 +2,11 @@ # # Check for perlcritic errors in included Perl scripts. # -# Checks all Perl scripts in the tree for problems uncovered by perlcritic. -# This test is disabled unless RRA_MAINTAINER_TESTS is set, since coding style -# will not interfere with functionality. -# # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2012, 2013 +# Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -37,13 +33,15 @@ use warnings; use lib "$ENV{SOURCE}/tap/perl"; +use File::Spec; use Test::More; -use Test::RRA qw(skip_unless_maintainer use_prereq); +use Test::RRA qw(skip_unless_author use_prereq); use Test::RRA::Automake qw(automake_setup perl_dirs test_file_path); use Test::RRA::Config qw(@CRITIC_IGNORE); -# Skip tests unless we're running the test suite in maintainer mode. -skip_unless_maintainer('Coding style tests'); +# Skip tests unless we're running author tests since this test is too +# sensitive to the exact version of Perl::Critic to be generally useful. +skip_unless_author('Coding style tests'); # Set up Automake testing. automake_setup(); @@ -51,11 +49,22 @@ automake_setup(); # Load prerequisite modules. use_prereq('Test::Perl::Critic'); +# Due to an annoying bug in Perl::Tidy 20130922, we cannot run tests if the +# source directory is read-only. It unconditionally tries to create a log +# file in the current directory and fails to run any checks if it cannot. +if (!-w File::Spec->curdir()) { + plan skip_all => 'Perl::Tidy needs writable source directory'; +} + # Force the embedded Perl::Tidy check to use the correct configuration. -local $ENV{PERLTIDY} = test_file_path('/data/perltidyrc'); +local $ENV{PERLTIDY} = test_file_path('data/perltidyrc'); # Import the configuration file. Test::Perl::Critic->import(-profile => test_file_path('data/perlcriticrc')); # Finally, run the actual tests. all_critic_ok(perl_dirs({ skip => [@CRITIC_IGNORE] })); + +# On Debian with perltidy 20130922-1, a perltidy.LOG file gets left behind +# in the current directory. Remove it if it exists. +unlink('perltidy.LOG'); diff --git a/tests/perl/minimum-version-t b/tests/perl/minimum-version-t index 060d51e..8c49327 100755 --- a/tests/perl/minimum-version-t +++ b/tests/perl/minimum-version-t @@ -10,7 +10,7 @@ # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2012, 2013 +# Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -38,10 +38,13 @@ use warnings; use lib "$ENV{SOURCE}/tap/perl"; use Test::More; -use Test::RRA qw(use_prereq); +use Test::RRA qw(skip_unless_automated use_prereq); use Test::RRA::Automake qw(automake_setup perl_dirs); use Test::RRA::Config qw($MINIMUM_VERSION %MINIMUM_VERSION); +# Skip for normal user installs since this doesn't affect functionality. +skip_unless_automated('Minimum version tests'); + # Load prerequisite modules. use_prereq('Test::MinimumVersion'); diff --git a/tests/perl/strict-t b/tests/perl/strict-t index 6bac75f..2df6d58 100755 --- a/tests/perl/strict-t +++ b/tests/perl/strict-t @@ -5,14 +5,12 @@ # Checks all Perl scripts in the tree for problems uncovered by Test::Strict. # This includes using strict and warnings for every script and ensuring they # all pass a syntax check. Currently, test suite coverage is not checked. -# This test is disabled unless RRA_MAINTAINER_TESTS is set, since some scripts -# may require dependencies that aren't necessarily present. # # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2012, 2013 +# Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -40,13 +38,22 @@ use warnings; use lib "$ENV{SOURCE}/tap/perl"; use Test::More; -use Test::RRA qw(use_prereq); +use Test::RRA qw(skip_unless_automated use_prereq); use Test::RRA::Automake qw(automake_setup perl_dirs); -use Test::RRA::Config qw(@STRICT_IGNORE); +use Test::RRA::Config qw(@STRICT_IGNORE @STRICT_PREREQ); + +# Skip for normal user installs since this doesn't affect functionality. +skip_unless_automated('Strictness tests'); # Load prerequisite modules. use_prereq('Test::Strict'); +# Check whether all prerequisites are available, and skip the test if any of +# them are not. +for my $module (@STRICT_PREREQ) { + use_prereq($module); +} + # Set up Automake testing. This must be done after loading Test::Strict, # since it wants to use FindBin to locate this script. automake_setup(); diff --git a/tests/portable/asprintf-t.c b/tests/portable/asprintf-t.c index c61c14a..e556d95 100644 --- a/tests/portable/asprintf-t.c +++ b/tests/portable/asprintf-t.c @@ -16,6 +16,7 @@ */ #include <config.h> +#include <portable/macros.h> #include <portable/system.h> #include <tests/tap/basic.h> diff --git a/tests/portable/mkstemp-t.c b/tests/portable/mkstemp-t.c new file mode 100644 index 0000000..20a83fc --- /dev/null +++ b/tests/portable/mkstemp-t.c @@ -0,0 +1,80 @@ +/* + * mkstemp test suite. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Written by Russ Allbery <eagle@eyrie.org> + * + * The authors hereby relinquish any claim to any copyright that they may have + * in this work, whether granted under contract or by operation of law or + * international treaty, and hereby commit to the public, at large, that they + * shall not, at any time in the future, seek to enforce any copyright in this + * work against any person or entity, or prevent any person or entity from + * copying, publishing, distributing or creating derivative works of this + * work. + */ + +#include <config.h> +#include <portable/system.h> + +#include <errno.h> +#include <sys/stat.h> + +#include <tests/tap/basic.h> + +int test_mkstemp(char *template); + +int +main(void) +{ + int fd; + char template[] = "tsXXXXXXX"; + char tooshort[] = "XXXXX"; + char bad1[] = "/foo/barXXXXX"; + char bad2[] = "/foo/barXXXXXX.out"; + char buffer[256]; + struct stat st1, st2; + ssize_t length; + + plan(20); + + /* First, test a few error messages. */ + errno = 0; + is_int(-1, test_mkstemp(tooshort), "too short of template"); + is_int(EINVAL, errno, "...with correct errno"); + is_string("XXXXX", tooshort, "...and template didn't change"); + errno = 0; + is_int(-1, test_mkstemp(bad1), "bad template"); + is_int(EINVAL, errno, "...with correct errno"); + is_string("/foo/barXXXXX", bad1, "...and template didn't change"); + errno = 0; + is_int(-1, test_mkstemp(bad2), "template doesn't end in XXXXXX"); + is_int(EINVAL, errno, "...with correct errno"); + is_string("/foo/barXXXXXX.out", bad2, "...and template didn't change"); + errno = 0; + + /* Now try creating a real file. */ + fd = test_mkstemp(template); + ok(fd >= 0, "mkstemp works with valid template"); + ok(strcmp(template, "tsXXXXXXX") != 0, "...and template changed"); + ok(strncmp(template, "tsX", 3) == 0, "...and didn't touch first X"); + ok(access(template, F_OK) == 0, "...and the file exists"); + + /* Make sure that it's the same file as template refers to now. */ + ok(stat(template, &st1) == 0, "...and stat of template works"); + ok(fstat(fd, &st2) == 0, "...and stat of open file descriptor works"); + ok(st1.st_ino == st2.st_ino, "...and they're the same file"); + unlink(template); + + /* Make sure the open mode is correct. */ + length = strlen(template); + is_int(length, write(fd, template, length), "write to open file works"); + ok(lseek(fd, 0, SEEK_SET) == 0, "...and rewind works"); + is_int(length, read(fd, buffer, length), "...and the data is there"); + buffer[length] = '\0'; + is_string(template, buffer, "...and matches what we wrote"); + close(fd); + + return 0; +} diff --git a/tests/portable/mkstemp.c b/tests/portable/mkstemp.c new file mode 100644 index 0000000..4632d3d --- /dev/null +++ b/tests/portable/mkstemp.c @@ -0,0 +1,2 @@ +#define TESTING 1 +#include <portable/mkstemp.c> diff --git a/tests/portable/reallocarray-t.c b/tests/portable/reallocarray-t.c new file mode 100644 index 0000000..481da58 --- /dev/null +++ b/tests/portable/reallocarray-t.c @@ -0,0 +1,92 @@ +/* + * reallocarray test suite. + * + * This does some simple sanity checks and checks some of the overflow + * detection, but isn't particularly thorough. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Written by Russ Allbery <eagle@eyrie.org> + * + * The authors hereby relinquish any claim to any copyright that they may have + * in this work, whether granted under contract or by operation of law or + * international treaty, and hereby commit to the public, at large, that they + * shall not, at any time in the future, seek to enforce any copyright in this + * work against any person or entity, or prevent any person or entity from + * copying, publishing, distributing or creating derivative works of this + * work. + */ + +#include <config.h> +#include <portable/system.h> + +#include <errno.h> + +#include <tests/tap/basic.h> + +void *test_reallocarray(void *, size_t, size_t); + + +int +main(void) +{ + char *p, *base; + size_t sqrt_max; + int oerrno; + + plan(15); + + /* Test success cases and write to the memory for valgrind checks. */ + p = test_reallocarray(NULL, 2, 5); + memcpy(p, "123456789", 10); + is_string("123456789", p, "reallocarray of NULL"); + p = test_reallocarray(p, 4, 5); + is_string("123456789", p, "reallocarray after resize"); + memcpy(p + 9, "0123456789", 11); + is_string("1234567890123456789", p, "write to larger memory segment"); + free(p); + + /* + * If nmemb or size are 0, we should either get NULL or a pointer we can + * free. Make sure we don't get something weird, like division by zero. + */ + p = test_reallocarray(NULL, 0, 100); + if (p != NULL) + free(p); + p = test_reallocarray(NULL, 100, 0); + if (p != NULL) + free(p); + + /* Test the range-checking error cases. */ + p = test_reallocarray(NULL, 2, SIZE_MAX / 2); + oerrno = errno; + ok(p == NULL, "reallocarray fails for 2, SIZE_MAX / 2"); + is_int(ENOMEM, oerrno, "...with correct errno"); + base = malloc(10); + p = test_reallocarray(base, 3, SIZE_MAX / 3); + oerrno = errno; + ok(p == NULL, "reallocarray fails for 3, SIZE_MAX / 3"); + is_int(ENOMEM, oerrno, "...with correct errno"); + sqrt_max = (1UL << (sizeof(size_t) * 4)); + p = test_reallocarray(base, sqrt_max, sqrt_max); + oerrno = errno; + ok(p == NULL, "reallocarray fails for sqrt(SIZE_MAX), sqrt(SIZE_MAX)"); + is_int(ENOMEM, oerrno, "...with correct errno"); + p = test_reallocarray(base, 1, SIZE_MAX); + oerrno = errno; + ok(p == NULL, "reallocarray fails for 1, SIZE_MAX"); + is_int(ENOMEM, oerrno, "...with correct errno"); + p = test_reallocarray(base, SIZE_MAX, 1); + oerrno = errno; + ok(p == NULL, "reallocarray fails for SIZE_MAX, 1"); + is_int(ENOMEM, oerrno, "...with correct errno"); + p = test_reallocarray(base, 2, SIZE_MAX); + oerrno = errno; + ok(p == NULL, "reallocarray fails for 2, SIZE_MAX"); + is_int(ENOMEM, oerrno, "...with correct errno"); + + /* Clean up and exit. */ + free(base); + return 0; +} diff --git a/tests/portable/reallocarray.c b/tests/portable/reallocarray.c new file mode 100644 index 0000000..7cd29e2 --- /dev/null +++ b/tests/portable/reallocarray.c @@ -0,0 +1,2 @@ +#define TESTING 1 +#include <portable/reallocarray.c> diff --git a/tests/portable/snprintf-t.c b/tests/portable/snprintf-t.c index 270d2e1..cc8cf00 100644 --- a/tests/portable/snprintf-t.c +++ b/tests/portable/snprintf-t.c @@ -26,7 +26,9 @@ * Disable the requirement that format strings be literals. We need variable * formats for easy testing. */ -#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2) || defined(__clang__) +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif /* * Intentionally don't add the printf attribute here since we pass a diff --git a/tests/runtests.c b/tests/runtests.c index 2b7959b..45e51c8 100644 --- a/tests/runtests.c +++ b/tests/runtests.c @@ -3,15 +3,19 @@ * * Usage: * - * runtests [-b <build-dir>] [-s <source-dir>] <test-list> + * runtests [-b <build-dir>] [-s <source-dir>] -l <test-list> + * runtests [-b <build-dir>] [-s <source-dir>] <test> [<test> ...] * runtests -o [-b <build-dir>] [-s <source-dir>] <test> * * In the first case, expects a list of executables located in the given file, * one line per executable. For each one, runs it as part of a test suite, - * reporting results. Test output should start with a line containing the - * number of tests (numbered from 1 to this number), optionally preceded by - * "1..", although that line may be given anywhere in the output. Each - * additional line should be in the following format: + * reporting results. In the second case, use the same infrastructure, but + * run only the tests listed on the command line. + * + * Test output should start with a line containing the number of tests + * (numbered from 1 to this number), optionally preceded by "1..", although + * that line may be given anywhere in the output. Each additional line should + * be in the following format: * * ok <number> * not ok <number> @@ -54,8 +58,8 @@ * should be sent to the e-mail address below. This program is part of C TAP * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>. * - * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011 - * Russ Allbery <eagle@eyrie.org> + * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, + * 2014, 2015 Russ Allbery <eagle@eyrie.org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -86,6 +90,7 @@ #include <ctype.h> #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <stdarg.h> #include <stddef.h> #include <stdio.h> @@ -102,12 +107,23 @@ /* sys/time.h must be included before sys/resource.h on some platforms. */ #include <sys/resource.h> -/* AIX doesn't have WCOREDUMP. */ +/* AIX 6.1 (and possibly later) doesn't have WCOREDUMP. */ #ifndef WCOREDUMP # define WCOREDUMP(status) ((unsigned)(status) & 0x80) #endif /* + * POSIX requires that these be defined in <unistd.h>, but they're not always + * available. If one of them has been defined, all the rest almost certainly + * have. + */ +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 +#endif + +/* * Used for iterating through arrays. Returns the number of elements in the * array (useful for a < upper bound in a for loop). */ @@ -134,6 +150,12 @@ enum test_status { TEST_INVALID }; +/* Really, just a boolean, but this is more self-documenting. */ +enum test_verbose { + CONCISE = 0, + VERBOSE = 1 +}; + /* Indicates the state of our plan. */ enum plan_status { PLAN_INIT, /* Nothing seen yet. */ @@ -145,7 +167,8 @@ enum plan_status { /* Error exit statuses for test processes. */ #define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */ #define CHILDERR_EXEC 101 /* Couldn't exec child process. */ -#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */ +#define CHILDERR_STDIN 102 /* Couldn't open stdin file. */ +#define CHILDERR_STDERR 103 /* Couldn't open stderr file. */ /* Structure to hold data for a set of tests. */ struct testset { @@ -208,10 +231,11 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\ -------------------------- -------------- ---- ---- ------------------------"; /* Include the file name and line number in malloc failures. */ -#define xcalloc(n, size) x_calloc((n), (size), __FILE__, __LINE__) -#define xmalloc(size) x_malloc((size), __FILE__, __LINE__) -#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__) -#define xstrdup(p) x_strdup((p), __FILE__, __LINE__) +#define xcalloc(n, size) x_calloc((n), (size), __FILE__, __LINE__) +#define xmalloc(size) x_malloc((size), __FILE__, __LINE__) +#define xstrdup(p) x_strdup((p), __FILE__, __LINE__) +#define xreallocarray(p, n, size) \ + x_reallocarray((p), (n), (size), __FILE__, __LINE__) /* * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7 @@ -232,8 +256,10 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\ * variadic macro support. */ #if !defined(__attribute__) && !defined(__alloc_size__) -# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) -# define __alloc_size__(spec, args...) /* empty */ +# if defined(__GNUC__) && !defined(__clang__) +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) +# define __alloc_size__(spec, args...) /* empty */ +# endif # endif #endif @@ -254,8 +280,8 @@ static void *x_calloc(size_t, size_t, const char *, int) __attribute__((__alloc_size__(1, 2), __malloc__, __nonnull__)); static void *x_malloc(size_t, const char *, int) __attribute__((__alloc_size__(1), __malloc__, __nonnull__)); -static void *x_realloc(void *, size_t, const char *, int) - __attribute__((__alloc_size__(2), __malloc__, __nonnull__(3))); +static void *x_reallocarray(void *, size_t, size_t, const char *, int) + __attribute__((__alloc_size__(2, 3), __malloc__, __nonnull__(4))); static char *x_strdup(const char *, const char *, int) __attribute__((__malloc__, __nonnull__)); @@ -316,14 +342,26 @@ x_malloc(size_t size, const char *file, int line) /* * Reallocate memory, reporting a fatal error and exiting on failure. + * + * We should technically use SIZE_MAX here for the overflow check, but + * SIZE_MAX is C99 and we're only assuming C89 + SUSv3, which does not + * guarantee that it exists. They do guarantee that UINT_MAX exists, and we + * can assume that UINT_MAX <= SIZE_MAX. And we should not be allocating + * anything anywhere near that large. + * + * (In theory, C89 and C99 permit size_t to be smaller than unsigned int, but + * I disbelieve in the existence of such systems and they will have to cope + * without overflow checks.) */ static void * -x_realloc(void *p, size_t size, const char *file, int line) +x_reallocarray(void *p, size_t n, size_t size, const char *file, int line) { - p = realloc(p, size); + if (n > 0 && UINT_MAX / n <= size) + sysdie("realloc too large at %s line %d", file, line); + p = realloc(p, n * size); if (p == NULL) sysdie("failed to realloc %lu bytes at %s line %d", - (unsigned long) size, file, line); + (unsigned long) (n * size), file, line); return p; } @@ -348,6 +386,55 @@ x_strdup(const char *s, const char *file, int line) /* + * Form a new string by concatenating multiple strings. The arguments must be + * terminated by (const char *) 0. + * + * This function only exists because we can't assume asprintf. We can't + * simulate asprintf with snprintf because we're only assuming SUSv3, which + * does not require that snprintf with a NULL buffer return the required + * length. When those constraints are relaxed, this should be ripped out and + * replaced with asprintf or a more trivial replacement with snprintf. + */ +static char * +concat(const char *first, ...) +{ + va_list args; + char *result; + const char *string; + size_t offset; + size_t length = 0; + + /* + * Find the total memory required. Ensure we don't overflow length. We + * aren't guaranteed to have SIZE_MAX, so use UINT_MAX as an acceptable + * substitute (see the x_nrealloc comments). + */ + va_start(args, first); + for (string = first; string != NULL; string = va_arg(args, const char *)) { + if (length >= UINT_MAX - strlen(string)) { + errno = EINVAL; + sysdie("strings too long in concat"); + } + length += strlen(string); + } + va_end(args); + length++; + + /* Create the string. */ + result = xmalloc(length); + va_start(args, first); + offset = 0; + for (string = first; string != NULL; string = va_arg(args, const char *)) { + memcpy(result + offset, string, strlen(string)); + offset += strlen(string); + } + va_end(args); + result[offset] = '\0'; + return result; +} + + +/* * Given a struct timeval, return the number of seconds it represents as a * double. Use difftime() to convert a time_t to a double. */ @@ -399,36 +486,62 @@ skip_whitespace(const char *p) static pid_t test_start(const char *path, int *fd) { - int fds[2], errfd; + int fds[2], infd, errfd; pid_t child; + /* Create a pipe used to capture the output from the test program. */ if (pipe(fds) == -1) { puts("ABORTED"); fflush(stdout); sysdie("can't create pipe"); } + + /* Fork a child process, massage the file descriptors, and exec. */ child = fork(); - if (child == (pid_t) -1) { + switch (child) { + case -1: puts("ABORTED"); fflush(stdout); sysdie("can't fork"); - } else if (child == 0) { - /* In child. Set up our stdout and stderr. */ + + /* In the child. Set up our standard output. */ + case 0: + close(fds[0]); + close(STDOUT_FILENO); + if (dup2(fds[1], STDOUT_FILENO) < 0) + _exit(CHILDERR_DUP); + close(fds[1]); + + /* Point standard input at /dev/null. */ + close(STDIN_FILENO); + infd = open("/dev/null", O_RDONLY); + if (infd < 0) + _exit(CHILDERR_STDIN); + if (infd != STDIN_FILENO) { + if (dup2(infd, STDIN_FILENO) < 0) + _exit(CHILDERR_DUP); + close(infd); + } + + /* Point standard error at /dev/null. */ + close(STDERR_FILENO); errfd = open("/dev/null", O_WRONLY); if (errfd < 0) _exit(CHILDERR_STDERR); - if (dup2(errfd, 2) == -1) - _exit(CHILDERR_DUP); - close(fds[0]); - if (dup2(fds[1], 1) == -1) - _exit(CHILDERR_DUP); + if (errfd != STDERR_FILENO) { + if (dup2(errfd, STDERR_FILENO) < 0) + _exit(CHILDERR_DUP); + close(errfd); + } /* Now, exec our process. */ if (execl(path, path, (char *) 0) == -1) _exit(CHILDERR_EXEC); - } else { - /* In parent. Close the extra file descriptor. */ + + /* In parent. Close the extra file descriptor. */ + default: close(fds[1]); + break; } *fd = fds[0]; return child; @@ -456,15 +569,63 @@ test_backspace(struct testset *ts) /* + * Allocate or resize the array of test results to be large enough to contain + * the test number in. + */ +static void +resize_results(struct testset *ts, unsigned long n) +{ + unsigned long i; + size_t s; + + /* If there's already enough space, return quickly. */ + if (n <= ts->allocated) + return; + + /* + * If no space has been allocated, do the initial allocation. Otherwise, + * resize. Start with 32 test cases and then add 1024 with each resize to + * try to reduce the number of reallocations. + */ + if (ts->allocated == 0) { + s = (n > 32) ? n : 32; + ts->results = xcalloc(s, sizeof(enum test_status)); + } else { + s = (n > ts->allocated + 1024) ? n : ts->allocated + 1024; + ts->results = xreallocarray(ts->results, s, sizeof(enum test_status)); + } + + /* Set the results for the newly-allocated test array. */ + for (i = ts->allocated; i < s; i++) + ts->results[i] = TEST_INVALID; + ts->allocated = s; +} + + +/* + * Report an invalid test number and set the appropriate flags. Pulled into a + * separate function since we do this in several places. + */ +static void +invalid_test_number(struct testset *ts, long n, enum test_verbose verbose) +{ + if (!verbose) + test_backspace(ts); + printf("ABORTED (invalid test number %ld)\n", n); + ts->aborted = 1; + ts->reported = 1; +} + + +/* * Read the plan line of test output, which should contain the range of test * numbers. We may initialize the testset structure here if we haven't yet * seen a test. Return true if initialization succeeded and the test should * continue, false otherwise. */ static int -test_plan(const char *line, struct testset *ts) +test_plan(const char *line, struct testset *ts, enum test_verbose verbose) { - unsigned long i; long n; /* @@ -477,12 +638,14 @@ test_plan(const char *line, struct testset *ts) line += 3; /* - * Get the count, check it for validity, and initialize the struct. If we - * have something of the form "1..0 # skip foo", the whole file was + * Get the count and check it for validity. + * + * If we have something of the form "1..0 # skip foo", the whole file was * skipped; record that. If we do skip the whole file, zero out all of - * our statistics, since they're no longer relevant. strtol is called - * with a second argument to advance the line pointer past the count to - * make it simpler to detect the # skip case. + * our statistics, since they're no longer relevant. + * + * strtol is called with a second argument to advance the line pointer + * past the count to make it simpler to detect the # skip case. */ n = strtol(line, (char **) &line, 10); if (n == 0) { @@ -511,30 +674,27 @@ test_plan(const char *line, struct testset *ts) ts->reported = 1; return 0; } - if (ts->plan == PLAN_INIT && ts->allocated == 0) { - ts->count = n; - ts->allocated = n; + + /* + * If we are doing lazy planning, check the plan against the largest test + * number that we saw and fail now if we saw a check outside the plan + * range. + */ + if (ts->plan == PLAN_PENDING && (unsigned long) n < ts->count) { + invalid_test_number(ts, (long) ts->count, verbose); + return 0; + } + + /* + * Otherwise, allocated or resize the results if needed and update count, + * and then record that we've seen a plan. + */ + resize_results(ts, (unsigned long) n); + ts->count = (unsigned long) n; + if (ts->plan == PLAN_INIT) ts->plan = PLAN_FIRST; - ts->results = xmalloc(ts->count * sizeof(enum test_status)); - for (i = 0; i < ts->count; i++) - ts->results[i] = TEST_INVALID; - } else if (ts->plan == PLAN_PENDING) { - if ((unsigned long) n < ts->count) { - test_backspace(ts); - printf("ABORTED (invalid test number %lu)\n", ts->count); - ts->aborted = 1; - ts->reported = 1; - return 0; - } - ts->count = n; - if ((unsigned long) n > ts->allocated) { - ts->results = xrealloc(ts->results, n * sizeof(enum test_status)); - for (i = ts->allocated; i < ts->count; i++) - ts->results[i] = TEST_INVALID; - ts->allocated = n; - } + else if (ts->plan == PLAN_PENDING) ts->plan = PLAN_FINAL; - } return 1; } @@ -546,13 +706,14 @@ test_plan(const char *line, struct testset *ts) * reported status. */ static void -test_checkline(const char *line, struct testset *ts) +test_checkline(const char *line, struct testset *ts, + enum test_verbose verbose) { enum test_status status = TEST_PASS; const char *bail; char *end; long number; - unsigned long i, current; + unsigned long current; int outlen; /* Before anything, check for a test abort. */ @@ -565,7 +726,8 @@ test_checkline(const char *line, struct testset *ts) length = strlen(bail); if (bail[length - 1] == '\n') length--; - test_backspace(ts); + if (!verbose) + test_backspace(ts); printf("ABORTED (%.*s)\n", (int) length, bail); ts->reported = 1; } @@ -586,14 +748,15 @@ test_checkline(const char *line, struct testset *ts) /* If we haven't yet seen a plan, look for one. */ if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) { - if (!test_plan(line, ts)) + if (!test_plan(line, ts, verbose)) return; } else if (strncmp(line, "1..", 3) == 0) { if (ts->plan == PLAN_PENDING) { - if (!test_plan(line, ts)) + if (!test_plan(line, ts, verbose)) return; } else { - test_backspace(ts); + if (!verbose) + test_backspace(ts); puts("ABORTED (multiple plans)"); ts->aborted = 1; ts->reported = 1; @@ -612,32 +775,23 @@ test_checkline(const char *line, struct testset *ts) errno = 0; number = strtol(line, &end, 10); if (errno != 0 || end == line) - number = ts->current + 1; - current = number; - if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) { - test_backspace(ts); - printf("ABORTED (invalid test number %lu)\n", current); - ts->aborted = 1; - ts->reported = 1; + current = ts->current + 1; + else if (number <= 0) { + invalid_test_number(ts, number, verbose); + return; + } else + current = (unsigned long) number; + if (current > ts->count && ts->plan == PLAN_FIRST) { + invalid_test_number(ts, (long) current, verbose); return; } /* We have a valid test result. Tweak the results array if needed. */ if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) { ts->plan = PLAN_PENDING; + resize_results(ts, current); if (current > ts->count) ts->count = current; - if (current > ts->allocated) { - unsigned long n; - - n = (ts->allocated == 0) ? 32 : ts->allocated * 2; - if (n < current) - n = current; - ts->results = xrealloc(ts->results, n * sizeof(enum test_status)); - for (i = ts->allocated; i < n; i++) - ts->results[i] = TEST_INVALID; - ts->allocated = n; - } } /* @@ -657,7 +811,8 @@ test_checkline(const char *line, struct testset *ts) /* Make sure that the test number is in range and not a duplicate. */ if (ts->results[current - 1] != TEST_INVALID) { - test_backspace(ts); + if (!verbose) + test_backspace(ts); printf("ABORTED (duplicate test number %lu)\n", current); ts->aborted = 1; ts->reported = 1; @@ -673,13 +828,13 @@ test_checkline(const char *line, struct testset *ts) } ts->current = current; ts->results[current - 1] = status; - if (isatty(STDOUT_FILENO)) { + if (!verbose && isatty(STDOUT_FILENO)) { test_backspace(ts); if (ts->plan == PLAN_PENDING) outlen = printf("%lu/?", current); else outlen = printf("%lu/%lu", current, ts->count); - ts->length = (outlen >= 0) ? outlen : 0; + ts->length = (outlen >= 0) ? (unsigned int) outlen : 0; fflush(stdout); } } @@ -695,7 +850,7 @@ test_checkline(const char *line, struct testset *ts) * disable this). */ static unsigned int -test_print_range(unsigned long first, unsigned long last, unsigned int chars, +test_print_range(unsigned long first, unsigned long last, unsigned long chars, unsigned int limit) { unsigned int needed = 0; @@ -835,6 +990,7 @@ test_analyze(struct testset *ts) if (!ts->reported) puts("ABORTED (execution failed -- not found?)"); break; + case CHILDERR_STDIN: case CHILDERR_STDERR: if (!ts->reported) puts("ABORTED (can't open /dev/null)"); @@ -864,7 +1020,7 @@ test_analyze(struct testset *ts) * false otherwise. */ static int -test_run(struct testset *ts) +test_run(struct testset *ts, enum test_verbose verbose) { pid_t testpid, child; int outfd, status; @@ -881,12 +1037,19 @@ test_run(struct testset *ts) sysdie("fdopen failed"); } - /* Pass each line of output to test_checkline(). */ - while (!ts->aborted && fgets(buffer, sizeof(buffer), output)) - test_checkline(buffer, ts); + /* + * Pass each line of output to test_checkline(), and print the line if + * verbosity is requested. + */ + while (!ts->aborted && fgets(buffer, sizeof(buffer), output)) { + if (verbose) + printf("%s", buffer); + test_checkline(buffer, ts, verbose); + } if (ferror(output) || ts->plan == PLAN_INIT) ts->aborted = 1; - test_backspace(ts); + if (!verbose) + test_backspace(ts); /* * Consume the rest of the test output, close the output descriptor, @@ -894,7 +1057,8 @@ test_run(struct testset *ts) * for eventual output. */ while (fgets(buffer, sizeof(buffer), output)) - ; + if (verbose) + printf("%s", buffer); fclose(output); child = waitpid(testpid, &ts->status, 0); if (child == (pid_t) -1) { @@ -1002,7 +1166,7 @@ is_valid_test(const char *path) static char * find_test(const char *name, const char *source, const char *build) { - char *path; + char *path = NULL; const char *bases[3], *suffix, *base; unsigned int i, j; const char *suffixes[3] = { "-t", ".t", "" }; @@ -1019,8 +1183,7 @@ find_test(const char *name, const char *source, const char *build) base = bases[j]; if (base == NULL) continue; - path = xmalloc(strlen(base) + strlen(name) + strlen(suffix) + 2); - sprintf(path, "%s/%s%s", base, name, suffix); + path = concat(base, "/", name, suffix, (const char *) 0); if (is_valid_test(path)) return path; free(path); @@ -1045,12 +1208,11 @@ read_test_list(const char *filename) unsigned int line; size_t length; char buffer[BUFSIZ]; + const char *testname; struct testlist *listhead, *current; /* Create the initial container list that will hold our results. */ - listhead = xmalloc(sizeof(struct testlist)); - listhead->ts = NULL; - listhead->next = NULL; + listhead = xcalloc(1, sizeof(struct testlist)); current = NULL; /* @@ -1069,17 +1231,24 @@ read_test_list(const char *filename) exit(1); } buffer[length] = '\0'; + + /* Skip comments, leading spaces, and blank lines. */ + testname = skip_whitespace(buffer); + if (strlen(testname) == 0) + continue; + if (testname[0] == '#') + continue; + + /* Allocate the new testset structure. */ if (current == NULL) current = listhead; else { - current->next = xmalloc(sizeof(struct testlist)); + current->next = xcalloc(1, sizeof(struct testlist)); current = current->next; - current->next = NULL; } current->ts = xcalloc(1, sizeof(struct testset)); current->ts->plan = PLAN_INIT; - current->ts->file = xstrdup(buffer); - current->ts->reason = NULL; + current->ts->file = xstrdup(testname); } fclose(file); @@ -1100,9 +1269,7 @@ build_test_list(char *argv[], int argc) struct testlist *listhead, *current; /* Create the initial container list that will hold our results. */ - listhead = xmalloc(sizeof(struct testlist)); - listhead->ts = NULL; - listhead->next = NULL; + listhead = xcalloc(1, sizeof(struct testlist)); current = NULL; /* Walk the list of arguments and create test sets for them. */ @@ -1110,14 +1277,12 @@ build_test_list(char *argv[], int argc) if (current == NULL) current = listhead; else { - current->next = xmalloc(sizeof(struct testlist)); + current->next = xcalloc(1, sizeof(struct testlist)); current = current->next; - current->next = NULL; } current->ts = xcalloc(1, sizeof(struct testset)); current->ts->plan = PLAN_INIT; current->ts->file = xstrdup(argv[i]); - current->ts->reason = NULL; } /* Return the results. */ @@ -1132,8 +1297,7 @@ free_testset(struct testset *ts) free(ts->file); free(ts->path); free(ts->results); - if (ts->reason != NULL) - free(ts->reason); + free(ts->reason); free(ts); } @@ -1146,11 +1310,11 @@ free_testset(struct testset *ts) * frees the test list that's passed in. */ static int -test_batch(struct testlist *tests, const char *source, const char *build) +test_batch(struct testlist *tests, const char *source, const char *build, + enum test_verbose verbose) { - size_t length; - unsigned int i; - unsigned int longest = 0; + size_t length, i; + size_t longest = 0; unsigned int count = 0; struct testset *ts; struct timeval start, end; @@ -1189,15 +1353,20 @@ test_batch(struct testlist *tests, const char *source, const char *build) /* Print out the name of the test file. */ fputs(ts->file, stdout); - for (i = strlen(ts->file); i < longest; i++) - putchar('.'); + if (verbose) + fputs("\n\n", stdout); + else + for (i = strlen(ts->file); i < longest; i++) + putchar('.'); if (isatty(STDOUT_FILENO)) fflush(stdout); /* Run the test. */ ts->path = find_test(ts->file, source, build); - succeeded = test_run(ts); + succeeded = test_run(ts, verbose); fflush(stdout); + if (verbose) + putchar('\n'); /* Record cumulative statistics. */ aborted += ts->aborted; @@ -1299,23 +1468,25 @@ main(int argc, char *argv[]) int option; int status = 0; int single = 0; + enum test_verbose verbose = CONCISE; char *source_env = NULL; char *build_env = NULL; + const char *program; const char *shortlist; const char *list = NULL; const char *source = SOURCE; const char *build = BUILD; struct testlist *tests; - while ((option = getopt(argc, argv, "b:hl:os:")) != EOF) { + program = argv[0]; + while ((option = getopt(argc, argv, "b:hl:os:v")) != EOF) { switch (option) { case 'b': build = optarg; break; case 'h': - printf(usage_message, argv[0], argv[0], argv[0], usage_extra); + printf(usage_message, program, program, program, usage_extra); exit(0); - break; case 'l': list = optarg; break; @@ -1325,6 +1496,9 @@ main(int argc, char *argv[]) case 's': source = optarg; break; + case 'v': + verbose = VERBOSE; + break; default: exit(1); } @@ -1332,20 +1506,25 @@ main(int argc, char *argv[]) argv += optind; argc -= optind; if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) { - fprintf(stderr, usage_message, argv[0], argv[0], argv[0], usage_extra); + fprintf(stderr, usage_message, program, program, program, usage_extra); exit(1); } + /* + * If C_TAP_VERBOSE is set in the environment, that also turns on verbose + * mode. + */ + if (getenv("C_TAP_VERBOSE") != NULL) + verbose = VERBOSE; + /* Set SOURCE and BUILD environment variables. */ if (source != NULL) { - source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1); - sprintf(source_env, "SOURCE=%s", source); + source_env = concat("SOURCE=", source, (const char *) 0); if (putenv(source_env) != 0) sysdie("cannot set SOURCE in the environment"); } if (build != NULL) { - build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1); - sprintf(build_env, "BUILD=%s", build); + build_env = concat("BUILD=", build, (const char *) 0); if (putenv(build_env) != 0) sysdie("cannot set BUILD in the environment"); } @@ -1361,10 +1540,10 @@ main(int argc, char *argv[]) shortlist++; printf(banner, shortlist); tests = read_test_list(list); - status = test_batch(tests, source, build) ? 0 : 1; + status = test_batch(tests, source, build, verbose) ? 0 : 1; } else { tests = build_test_list(argv, argc); - status = test_batch(tests, source, build) ? 0 : 1; + status = test_batch(tests, source, build, verbose) ? 0 : 1; } /* For valgrind cleanliness, free all our memory. */ diff --git a/tests/tap/basic.c b/tests/tap/basic.c index bb51606..0b8be8f 100644 --- a/tests/tap/basic.c +++ b/tests/tap/basic.c @@ -12,8 +12,9 @@ * This file is part of C TAP Harness. The current version plus supporting * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>. * - * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 + * Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -35,7 +36,9 @@ * DEALINGS IN THE SOFTWARE. */ +#include <assert.h> #include <errno.h> +#include <limits.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -102,6 +105,21 @@ struct cleanup_func { static struct cleanup_func *cleanup_funcs = NULL; /* + * Registered diag files. Any output found in these files will be printed out + * as if it were passed to diag() before any other output we do. This allows + * background processes to log to a file and have that output interleaved with + * the test output. + */ +struct diag_file { + char *name; + FILE *file; + char *buffer; + size_t bufsize; + struct diag_file *next; +}; +static struct diag_file *diag_files = NULL; + +/* * Print a specified prefix and then the test description. Handles turning * the argument list into a va_args structure suitable for passing to * print_desc, which has to be done in a macro. Assumes that format is the @@ -121,6 +139,126 @@ static struct cleanup_func *cleanup_funcs = NULL; /* + * Form a new string by concatenating multiple strings. The arguments must be + * terminated by (const char *) 0. + * + * This function only exists because we can't assume asprintf. We can't + * simulate asprintf with snprintf because we're only assuming SUSv3, which + * does not require that snprintf with a NULL buffer return the required + * length. When those constraints are relaxed, this should be ripped out and + * replaced with asprintf or a more trivial replacement with snprintf. + */ +static char * +concat(const char *first, ...) +{ + va_list args; + char *result; + const char *string; + size_t offset; + size_t length = 0; + + /* + * Find the total memory required. Ensure we don't overflow length. See + * the comment for breallocarray for why we're using UINT_MAX here. + */ + va_start(args, first); + for (string = first; string != NULL; string = va_arg(args, const char *)) { + if (length >= UINT_MAX - strlen(string)) + bail("strings too long in concat"); + length += strlen(string); + } + va_end(args); + length++; + + /* Create the string. */ + result = bmalloc(length); + va_start(args, first); + offset = 0; + for (string = first; string != NULL; string = va_arg(args, const char *)) { + memcpy(result + offset, string, strlen(string)); + offset += strlen(string); + } + va_end(args); + result[offset] = '\0'; + return result; +} + + +/* + * Check all registered diag_files for any output. We only print out the + * output if we see a complete line; otherwise, we wait for the next newline. + */ +static void +check_diag_files(void) +{ + struct diag_file *file; + fpos_t where; + size_t length; + int size, incomplete; + + /* + * Walk through each file and read each line of output available. The + * general scheme here used is as follows: try to read a line of output at + * a time. If we get NULL, check for EOF; on EOF, advance to the next + * file. + * + * If we get some data, see if it ends in a newline. If it doesn't end in + * a newline, we have one of two cases: our buffer isn't large enough, in + * which case we resize it and try again, or we have incomplete data in + * the file, in which case we rewind the file and will try again next + * time. + */ + for (file = diag_files; file != NULL; file = file->next) { + clearerr(file->file); + + /* Store the current position in case we have to rewind. */ + if (fgetpos(file->file, &where) < 0) + sysbail("cannot get position in %s", file->name); + + /* Continue until we get EOF or an incomplete line of data. */ + incomplete = 0; + while (!feof(file->file) && !incomplete) { + size = file->bufsize > INT_MAX ? INT_MAX : (int) file->bufsize; + if (fgets(file->buffer, size, file->file) == NULL) { + if (ferror(file->file)) + sysbail("cannot read from %s", file->name); + continue; + } + + /* + * See if the line ends in a newline. If not, see which error + * case we have. Use UINT_MAX as a substitute for SIZE_MAX (see + * the comment for breallocarray). + */ + length = strlen(file->buffer); + if (file->buffer[length - 1] != '\n') { + if (length < file->bufsize - 1) + incomplete = 1; + else { + if (file->bufsize >= UINT_MAX - BUFSIZ) + sysbail("line too long in %s", file->name); + file->bufsize += BUFSIZ; + file->buffer = brealloc(file->buffer, file->bufsize); + } + + /* + * On either incomplete lines or too small of a buffer, rewind + * and read the file again (on the next pass, if incomplete). + * It's simpler than trying to double-buffer the file. + */ + if (fsetpos(file->file, &where) < 0) + sysbail("cannot set position in %s", file->name); + continue; + } + + /* We saw a complete line. Print it out. */ + printf("# %s", file->buffer); + } + } +} + + +/* * Our exit handler. Called on completion of the test to report a summary of * results provided we're still in the original process. This also handles * printing out the plan if we used plan_lazy(), although that's suppressed if @@ -130,22 +268,25 @@ static struct cleanup_func *cleanup_funcs = NULL; static void finish(void) { - int success; + int success, primary; struct cleanup_func *current; unsigned long highest = testnum - 1; - - /* - * Don't do anything except free the cleanup functions if we aren't the - * primary process (the process in which plan or plan_lazy was called). - */ - if (_process != 0 && getpid() != _process) { - while (cleanup_funcs != NULL) { - current = cleanup_funcs; - cleanup_funcs = cleanup_funcs->next; - free(current); - } - return; + struct diag_file *file, *tmp; + + /* Check for pending diag_file output. */ + check_diag_files(); + + /* Free the diag_files. */ + file = diag_files; + while (file != NULL) { + tmp = file; + file = file->next; + fclose(tmp->file); + free(tmp->name); + free(tmp->buffer); + free(tmp); } + diag_files = NULL; /* * Determine whether all tests were successful, which is needed before @@ -157,14 +298,20 @@ finish(void) /* * If there are any registered cleanup functions, we run those first. We - * always run them, even if we didn't run a test. + * always run them, even if we didn't run a test. Don't do anything + * except free the diag_files and call cleanup functions if we aren't the + * primary process (the process in which plan or plan_lazy was called), + * and tell the cleanup functions that fact. */ + primary = (_process == 0 || getpid() == _process); while (cleanup_funcs != NULL) { - cleanup_funcs->func(success); + cleanup_funcs->func(success, primary); current = cleanup_funcs; cleanup_funcs = cleanup_funcs->next; free(current); } + if (!primary) + return; /* Don't do anything further if we never planned a test. */ if (_planned == 0) @@ -198,7 +345,8 @@ finish(void) /* * Initialize things. Turns on line buffering on stdout and then prints out - * the number of tests in the test suite. + * the number of tests in the test suite. We intentionally don't check for + * pending diag_file output here, since it should really come after the plan. */ void plan(unsigned long count) @@ -236,7 +384,8 @@ plan_lazy(void) /* * Skip the entire test suite and exits. Should be called instead of plan(), - * not after it, since it prints out a special plan line. + * not after it, since it prints out a special plan line. Ignore diag_file + * output here, since it's not clear if it's allowed before the plan. */ void skip_all(const char *format, ...) @@ -253,25 +402,28 @@ skip_all(const char *format, ...) * Takes a boolean success value and assumes the test passes if that value * is true and fails if that value is false. */ -void +int ok(int success, const char *format, ...) { fflush(stderr); + check_diag_files(); printf("%sok %lu", success ? "" : "not ", testnum++); if (!success) _failed++; PRINT_DESC(" - ", format); putchar('\n'); + return success; } /* * Same as ok(), but takes the format arguments as a va_list. */ -void +int okv(int success, const char *format, va_list args) { fflush(stderr); + check_diag_files(); printf("%sok %lu", success ? "" : "not ", testnum++); if (!success) _failed++; @@ -280,6 +432,7 @@ okv(int success, const char *format, va_list args) vprintf(format, args); } putchar('\n'); + return success; } @@ -290,6 +443,7 @@ void skip(const char *reason, ...) { fflush(stderr); + check_diag_files(); printf("ok %lu # skip", testnum++); PRINT_DESC(" ", reason); putchar('\n'); @@ -299,19 +453,21 @@ skip(const char *reason, ...) /* * Report the same status on the next count tests. */ -void -ok_block(unsigned long count, int status, const char *format, ...) +int +ok_block(unsigned long count, int success, const char *format, ...) { unsigned long i; fflush(stderr); + check_diag_files(); for (i = 0; i < count; i++) { - printf("%sok %lu", status ? "" : "not ", testnum++); - if (!status) + printf("%sok %lu", success ? "" : "not ", testnum++); + if (!success) _failed++; PRINT_DESC(" - ", format); putchar('\n'); } + return success; } @@ -324,6 +480,7 @@ skip_block(unsigned long count, const char *reason, ...) unsigned long i; fflush(stderr); + check_diag_files(); for (i = 0; i < count; i++) { printf("ok %lu # skip", testnum++); PRINT_DESC(" ", reason); @@ -336,11 +493,15 @@ skip_block(unsigned long count, const char *reason, ...) * Takes an expected integer and a seen integer and assumes the test passes * if those two numbers match. */ -void +int is_int(long wanted, long seen, const char *format, ...) { + int success; + fflush(stderr); - if (wanted == seen) + check_diag_files(); + success = (wanted == seen); + if (success) printf("ok %lu", testnum++); else { diag("wanted: %ld", wanted); @@ -350,6 +511,7 @@ is_int(long wanted, long seen, const char *format, ...) } PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -357,15 +519,19 @@ is_int(long wanted, long seen, const char *format, ...) * Takes a string and what the string should be, and assumes the test passes * if those strings match (using strcmp). */ -void +int is_string(const char *wanted, const char *seen, const char *format, ...) { + int success; + if (wanted == NULL) wanted = "(null)"; if (seen == NULL) seen = "(null)"; fflush(stderr); - if (strcmp(wanted, seen) == 0) + check_diag_files(); + success = (strcmp(wanted, seen) == 0); + if (success) printf("ok %lu", testnum++); else { diag("wanted: %s", wanted); @@ -375,6 +541,7 @@ is_string(const char *wanted, const char *seen, const char *format, ...) } PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -382,11 +549,15 @@ is_string(const char *wanted, const char *seen, const char *format, ...) * Takes an expected unsigned long and a seen unsigned long and assumes the * test passes if the two numbers match. Otherwise, reports them in hex. */ -void +int is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) { + int success; + fflush(stderr); - if (wanted == seen) + check_diag_files(); + success = (wanted == seen); + if (success) printf("ok %lu", testnum++); else { diag("wanted: %lx", (unsigned long) wanted); @@ -396,6 +567,7 @@ is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) } PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -409,6 +581,7 @@ bail(const char *format, ...) _aborted = 1; fflush(stderr); + check_diag_files(); fflush(stdout); printf("Bail out! "); va_start(args, format); @@ -430,6 +603,7 @@ sysbail(const char *format, ...) _aborted = 1; fflush(stderr); + check_diag_files(); fflush(stdout); printf("Bail out! "); va_start(args, format); @@ -441,39 +615,96 @@ sysbail(const char *format, ...) /* - * Report a diagnostic to stderr. + * Report a diagnostic to stderr. Always returns 1 to allow embedding in + * compound statements. */ -void +int diag(const char *format, ...) { va_list args; fflush(stderr); + check_diag_files(); fflush(stdout); printf("# "); va_start(args, format); vprintf(format, args); va_end(args); printf("\n"); + return 1; } /* - * Report a diagnostic to stderr, appending strerror(errno). + * Report a diagnostic to stderr, appending strerror(errno). Always returns 1 + * to allow embedding in compound statements. */ -void +int sysdiag(const char *format, ...) { va_list args; int oerrno = errno; fflush(stderr); + check_diag_files(); fflush(stdout); printf("# "); va_start(args, format); vprintf(format, args); va_end(args); printf(": %s\n", strerror(oerrno)); + return 1; +} + + +/* + * Register a new file for diag_file processing. + */ +void +diag_file_add(const char *name) +{ + struct diag_file *file, *prev; + + file = bcalloc(1, sizeof(struct diag_file)); + file->name = bstrdup(name); + file->file = fopen(file->name, "r"); + if (file->file == NULL) + sysbail("cannot open %s", name); + file->buffer = bmalloc(BUFSIZ); + file->bufsize = BUFSIZ; + if (diag_files == NULL) + diag_files = file; + else { + for (prev = diag_files; prev->next != NULL; prev = prev->next) + ; + prev->next = file; + } +} + + +/* + * Remove a file from diag_file processing. If the file is not found, do + * nothing, since there are some situations where it can be removed twice + * (such as if it's removed from a cleanup function, since cleanup functions + * are called after freeing all the diag_files). + */ +void +diag_file_remove(const char *name) +{ + struct diag_file *file; + struct diag_file **prev = &diag_files; + + for (file = diag_files; file != NULL; file = file->next) { + if (strcmp(file->name, name) == 0) { + *prev = file->next; + fclose(file->file); + free(file->name); + free(file->buffer); + free(file); + return; + } + prev = &file->next; + } } @@ -521,6 +752,32 @@ brealloc(void *p, size_t size) /* + * The same as brealloc, but determine the size by multiplying an element + * count by a size, similar to calloc. The multiplication is checked for + * integer overflow. + * + * We should technically use SIZE_MAX here for the overflow check, but + * SIZE_MAX is C99 and we're only assuming C89 + SUSv3, which does not + * guarantee that it exists. They do guarantee that UINT_MAX exists, and we + * can assume that UINT_MAX <= SIZE_MAX. + * + * (In theory, C89 and C99 permit size_t to be smaller than unsigned int, but + * I disbelieve in the existence of such systems and they will have to cope + * without overflow checks.) + */ +void * +breallocarray(void *p, size_t n, size_t size) +{ + if (n > 0 && UINT_MAX / n <= size) + bail("reallocarray too large"); + p = realloc(p, n * size); + if (p == NULL) + sysbail("failed to realloc %lu bytes", (unsigned long) (n * size)); + return p; +} + + +/* * Copy a string, reporting a fatal error with bail on failure. */ char * @@ -553,7 +810,7 @@ bstrndup(const char *s, size_t n) /* Don't assume that the source string is nul-terminated. */ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++) ; - length = p - s; + length = (size_t) (p - s); copy = malloc(length + 1); if (p == NULL) sysbail("failed to strndup %lu bytes", (unsigned long) length); @@ -568,17 +825,12 @@ bstrndup(const char *s, size_t n) * then SOURCE for the file and return the full path to the file. Returns * NULL if the file doesn't exist. A non-NULL return should be freed with * test_file_path_free(). - * - * This function uses sprintf because it attempts to be independent of all - * other portability layers. The use immediately after a memory allocation - * should be safe without using snprintf or strlcpy/strlcat. */ char * test_file_path(const char *file) { char *base; char *path = NULL; - size_t length; const char *envs[] = { "BUILD", "SOURCE", NULL }; int i; @@ -586,9 +838,7 @@ test_file_path(const char *file) base = getenv(envs[i]); if (base == NULL) continue; - length = strlen(base) + 1 + strlen(file) + 1; - path = bmalloc(length); - sprintf(path, "%s/%s", base, file); + path = concat(base, "/", file, (const char *) 0); if (access(path, R_OK) == 0) break; free(path); @@ -606,8 +856,7 @@ test_file_path(const char *file) void test_file_path_free(char *path) { - if (path != NULL) - free(path); + free(path); } @@ -626,14 +875,11 @@ test_tmpdir(void) { const char *build; char *path = NULL; - size_t length; build = getenv("BUILD"); if (build == NULL) build = "."; - length = strlen(build) + strlen("/tmp") + 1; - path = bmalloc(length); - sprintf(path, "%s/tmp", build); + path = concat(build, "/tmp", (const char *) 0); if (access(path, X_OK) < 0) if (mkdir(path, 0777) < 0) sysbail("error creating temporary directory %s", path); @@ -649,9 +895,9 @@ test_tmpdir(void) void test_tmpdir_free(char *path) { - rmdir(path); if (path != NULL) - free(path); + rmdir(path); + free(path); } diff --git a/tests/tap/basic.h b/tests/tap/basic.h index 92d348a..4ecaaec 100644 --- a/tests/tap/basic.h +++ b/tests/tap/basic.h @@ -4,8 +4,9 @@ * This file is part of C TAP Harness. The current version plus supporting * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>. * - * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 + * Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -67,26 +68,34 @@ void skip_all(const char *format, ...) /* * Basic reporting functions. The okv() function is the same as ok() but * takes the test description as a va_list to make it easier to reuse the - * reporting infrastructure when writing new tests. + * reporting infrastructure when writing new tests. ok() and okv() return the + * value of the success argument. */ -void ok(int success, const char *format, ...) +int ok(int success, const char *format, ...) __attribute__((__format__(printf, 2, 3))); -void okv(int success, const char *format, va_list args); +int okv(int success, const char *format, va_list args) + __attribute__((__format__(printf, 2, 0))); void skip(const char *reason, ...) __attribute__((__format__(printf, 1, 2))); -/* Report the same status on, or skip, the next count tests. */ -void ok_block(unsigned long count, int success, const char *format, ...) +/* + * Report the same status on, or skip, the next count tests. ok_block() + * returns the value of the success argument. + */ +int ok_block(unsigned long count, int success, const char *format, ...) __attribute__((__format__(printf, 3, 4))); void skip_block(unsigned long count, const char *reason, ...) __attribute__((__format__(printf, 2, 3))); -/* Check an expected value against a seen value. */ -void is_int(long wanted, long seen, const char *format, ...) +/* + * Check an expected value against a seen value. Returns true if the test + * passes and false if it fails. + */ +int is_int(long wanted, long seen, const char *format, ...) __attribute__((__format__(printf, 3, 4))); -void is_string(const char *wanted, const char *seen, const char *format, ...) +int is_string(const char *wanted, const char *seen, const char *format, ...) __attribute__((__format__(printf, 3, 4))); -void is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) +int is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) __attribute__((__format__(printf, 3, 4))); /* Bail out with an error. sysbail appends strerror(errno). */ @@ -96,16 +105,30 @@ void sysbail(const char *format, ...) __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2))); /* Report a diagnostic to stderr prefixed with #. */ -void diag(const char *format, ...) +int diag(const char *format, ...) __attribute__((__nonnull__, __format__(printf, 1, 2))); -void sysdiag(const char *format, ...) +int sysdiag(const char *format, ...) __attribute__((__nonnull__, __format__(printf, 1, 2))); +/* + * Register or unregister a file that contains supplementary diagnostics. + * Before any other output, all registered files will be read, line by line, + * and each line will be reported as a diagnostic as if it were passed to + * diag(). Nul characters are not supported in these files and will result in + * truncated output. + */ +void diag_file_add(const char *file) + __attribute__((__nonnull__)); +void diag_file_remove(const char *file) + __attribute__((__nonnull__)); + /* Allocate memory, reporting a fatal error with bail on failure. */ void *bcalloc(size_t, size_t) __attribute__((__alloc_size__(1, 2), __malloc__, __warn_unused_result__)); void *bmalloc(size_t) __attribute__((__alloc_size__(1), __malloc__, __warn_unused_result__)); +void *breallocarray(void *, size_t, size_t) + __attribute__((__alloc_size__(2, 3), __malloc__, __warn_unused_result__)); void *brealloc(void *, size_t) __attribute__((__alloc_size__(2), __malloc__, __warn_unused_result__)); char *bstrdup(const char *) @@ -132,11 +155,14 @@ void test_tmpdir_free(char *path); /* * Register a cleanup function that is called when testing ends. All such * registered functions will be run during atexit handling (and are therefore - * subject to all the same constraints and caveats as atexit functions). The - * function must return void and will be passed one argument, an int that will - * be true if the test completed successfully and false otherwise. + * subject to all the same constraints and caveats as atexit functions). + * + * The function must return void and will be passed two arguments: an int that + * will be true if the test completed successfully and false otherwise, and an + * int that will be true if the cleanup function is run in the primary process + * (the one that called plan or plan_lazy) and false otherwise. */ -typedef void (*test_cleanup_func)(int); +typedef void (*test_cleanup_func)(int, int); void test_cleanup_register(test_cleanup_func) __attribute__((__nonnull__)); diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c index 58315ee..6a5025a 100644 --- a/tests/tap/kerberos.c +++ b/tests/tap/kerberos.c @@ -15,7 +15,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013 + * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -47,6 +47,7 @@ #include <tests/tap/basic.h> #include <tests/tap/kerberos.h> +#include <tests/tap/macros.h> #include <tests/tap/process.h> #include <tests/tap/string.h> @@ -54,7 +55,9 @@ * Disable the requirement that format strings be literals, since it's easier * to handle the possible patterns for kinit commands as an array. */ -#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2) || defined(__clang__) +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif /* @@ -201,33 +204,25 @@ kerberos_kinit(void) /* - * Clean up at the end of a test. This removes the ticket cache and resets - * and frees the memory allocated for the environment variables so that - * valgrind output on test suites is cleaner. + * Free all the memory associated with our Kerberos setup, but don't remove + * the ticket cache. This is used when cleaning up on exit from a non-primary + * process so that test programs that fork don't remove the ticket cache still + * used by the main program. */ -void -kerberos_cleanup(void) +static void +kerberos_free(void) { - char *path; - - if (tmpdir_ticket != NULL) { - basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); - unlink(path); - free(path); - test_tmpdir_free(tmpdir_ticket); - tmpdir_ticket = NULL; - } + test_tmpdir_free(tmpdir_ticket); + tmpdir_ticket = NULL; if (config != NULL) { - if (config->keytab != NULL) { - test_file_path_free(config->keytab); - free(config->principal); - free(config->cache); - } - if (config->userprinc != NULL) { - free(config->userprinc); - free(config->username); - free(config->password); - } + test_file_path_free(config->keytab); + free(config->principal); + free(config->cache); + free(config->userprinc); + free(config->username); + free(config->password); + free(config->pkinit_principal); + free(config->pkinit_cert); free(config); config = NULL; } @@ -245,6 +240,42 @@ kerberos_cleanup(void) /* + * Clean up at the end of a test. This removes the ticket cache and resets + * and frees the memory allocated for the environment variables so that + * valgrind output on test suites is cleaner. Most of the work is done by + * kerberos_free, but this function also deletes the ticket cache. + */ +void +kerberos_cleanup(void) +{ + char *path; + + if (tmpdir_ticket != NULL) { + basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); + unlink(path); + free(path); + } + kerberos_free(); +} + + +/* + * The cleanup handler for the TAP framework. Call kerberos_cleanup if we're + * in the primary process and kerberos_free if not. The first argument, which + * indicates whether the test succeeded or not, is ignored, since we need to + * do the same thing either way. + */ +static void +kerberos_cleanup_handler(int success UNUSED, int primary) +{ + if (primary) + kerberos_cleanup(); + else + kerberos_free(); +} + + +/* * Obtain Kerberos tickets for the principal specified in config/principal * using the keytab specified in config/keytab, both of which are presumed to * be in tests in either the build or the source tree. Also sets KRB5_KTNAME @@ -321,15 +352,38 @@ kerberos_setup(enum kerberos_needs needs) *config->realm = '\0'; config->realm++; } + test_file_path_free(path); + + /* + * If we have PKINIT configuration, read it and fill out the relevant + * members of our config struct. + */ + path = test_file_path("config/pkinit-principal"); if (path != NULL) + file = fopen(path, "r"); + if (file != NULL) { + if (fgets(buffer, sizeof(buffer), file) == NULL) + bail("cannot read %s", path); + if (buffer[strlen(buffer) - 1] != '\n') + bail("no newline in %s", path); + buffer[strlen(buffer) - 1] = '\0'; + fclose(file); test_file_path_free(path); + path = test_file_path("config/pkinit-cert"); + if (path != NULL) { + config->pkinit_principal = bstrdup(buffer); + config->pkinit_cert = bstrdup(path); + } + } + test_file_path_free(path); + if (config->pkinit_cert == NULL && (needs & TAP_KRB_NEEDS_PKINIT) != 0) + skip_all("PKINIT tests not configured"); /* - * Register the cleanup function as an atexit handler so that the caller - * doesn't have to worry about cleanup. + * Register the cleanup function so that the caller doesn't have to do + * explicit cleanup. */ - if (atexit(kerberos_cleanup) != 0) - sysdiag("cannot register cleanup function"); + test_cleanup_register(kerberos_cleanup_handler); /* Return the configuration. */ return config; @@ -357,10 +411,8 @@ kerberos_cleanup_conf(void) tmpdir_conf = NULL; } putenv((char *) "KRB5_CONFIG="); - if (krb5_config != NULL) { - free(krb5_config); - krb5_config = NULL; - } + free(krb5_config); + krb5_config = NULL; } diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h index c34f891..26f45f9 100644 --- a/tests/tap/kerberos.h +++ b/tests/tap/kerberos.h @@ -5,7 +5,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2006, 2007, 2009, 2011, 2012, 2013 + * Copyright 2006, 2007, 2009, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -46,17 +46,21 @@ struct kerberos_config { char *username; /* The local (non-realm) part of principal. */ char *realm; /* The realm part of the principal. */ char *password; /* The password. */ + char *pkinit_principal; /* Principal for PKINIT authentication. */ + char *pkinit_cert; /* Path to certificates for PKINIT. */ }; /* * Whether to skip all tests (by calling skip_all) in kerberos_setup if - * certain configuration information isn't available. + * certain configuration information isn't available. "_BOTH" means that the + * tests require both keytab and password, but PKINIT is not required. */ enum kerberos_needs { TAP_KRB_NEEDS_NONE = 0x00, TAP_KRB_NEEDS_KEYTAB = 0x01, TAP_KRB_NEEDS_PASSWORD = 0x02, - TAP_KRB_NEEDS_BOTH = 0x01 | 0x02 + TAP_KRB_NEEDS_BOTH = 0x01 | 0x02, + TAP_KRB_NEEDS_PKINIT = 0x04 }; BEGIN_DECLS @@ -73,11 +77,11 @@ BEGIN_DECLS * the principal field will be NULL. If the files exist but loading them * fails, or authentication fails, kerberos_setup calls bail. * - * kerberos_cleanup will be set up to run from an atexit handler. This means - * that any child processes that should not remove the Kerberos ticket cache - * should call _exit instead of exit. The principal will be automatically - * freed when kerberos_cleanup is called or if kerberos_setup is called again. - * The caller doesn't need to worry about it. + * kerberos_cleanup will be run as a cleanup function normally, freeing all + * resources and cleaning up temporary files on process exit. It can, + * however, be called directly if for some reason the caller needs to delete + * the Kerberos environment again. However, normally the caller can just call + * kerberos_setup again. */ struct kerberos_config *kerberos_setup(enum kerberos_needs) __attribute__((__malloc__)); diff --git a/tests/tap/macros.h b/tests/tap/macros.h index 04cc420..139cff0 100644 --- a/tests/tap/macros.h +++ b/tests/tap/macros.h @@ -8,7 +8,7 @@ * This file is part of C TAP Harness. The current version plus supporting * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>. * - * Copyright 2008, 2012, 2013 Russ Allbery <eagle@eyrie.org> + * Copyright 2008, 2012, 2013, 2015 Russ Allbery <eagle@eyrie.org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -53,8 +53,10 @@ * variadic macro support. */ #if !defined(__attribute__) && !defined(__alloc_size__) -# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) -# define __alloc_size__(spec, args...) /* empty */ +# if defined(__GNUC__) && !defined(__clang__) +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) +# define __alloc_size__(spec, args...) /* empty */ +# endif # endif #endif diff --git a/tests/tap/messages.c b/tests/tap/messages.c index 3754d18..9c28789 100644 --- a/tests/tap/messages.c +++ b/tests/tap/messages.c @@ -8,8 +8,8 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org> - * Copyright 2006, 2007, 2009, 2012 + * Copyright 2002, 2004, 2005, 2015 Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2012, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -47,7 +47,7 @@ char *errors = NULL; * An error handler that appends all errors to the errors global. Used by * error_capture. */ -static void +static void __attribute__((__format__(printf, 2, 0))) message_log_buffer(int len UNUSED, const char *fmt, va_list args, int error UNUSED) { @@ -75,10 +75,8 @@ message_log_buffer(int len UNUSED, const char *fmt, va_list args, void errors_capture(void) { - if (errors != NULL) { - free(errors); - errors = NULL; - } + free(errors); + errors = NULL; message_handlers_warn(1, message_log_buffer); message_handlers_notice(1, message_log_buffer); } diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm index 1db6230..55e8c8d 100644 --- a/tests/tap/perl/Test/RRA.pm +++ b/tests/tap/perl/Test/RRA.pm @@ -5,31 +5,6 @@ # by both C packages with Automake and by stand-alone Perl modules. See # Test::RRA::Automake for additional functions specifically for C Automake # distributions. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. -# -# Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2013 -# The Board of Trustees of the Leland Stanford Junior University -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. package Test::RRA; @@ -51,29 +26,47 @@ our (@EXPORT_OK, @ISA, $VERSION); # consistency is good). BEGIN { @ISA = qw(Exporter); - @EXPORT_OK = qw(skip_unless_maintainer use_prereq); + @EXPORT_OK = qw(skip_unless_author skip_unless_automated use_prereq); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.12'; + $VERSION = '5.08'; } -# Skip this test unless maintainer tests are requested. Takes a short -# description of what tests this script would perform, which is used in the -# skip message. Calls plan skip_all, which will terminate the program. +# Skip this test unless author tests are requested. Takes a short description +# of what tests this script would perform, which is used in the skip message. +# Calls plan skip_all, which will terminate the program. # # $description - Short description of the tests # # Returns: undef -sub skip_unless_maintainer { +sub skip_unless_author { my ($description) = @_; - if (!$ENV{RRA_MAINTAINER_TESTS}) { - plan skip_all => "$description only run for maintainer"; + if (!$ENV{AUTHOR_TESTING}) { + plan skip_all => "$description only run for author"; } return; } +# Skip this test unless doing automated testing or release testing. This is +# used for tests that should be run by CPAN smoke testing or during releases, +# but not for manual installs by end users. Takes a short description of what +# tests this script would perform, which is used in the skip message. Calls +# plan skip_all, which will terminate the program. +# +# $description - Short description of the tests +# +# Returns: undef +sub skip_unless_automated { + my ($description) = @_; + for my $env (qw(AUTOMATED_TESTING RELEASE_TESTING AUTHOR_TESTING)) { + return if $ENV{$env}; + } + plan skip_all => "$description normally skipped"; + return; +} + # Attempt to load a module and skip the test if the module could not be # loaded. If the module could be loaded, call its import function manually. # If the module could not be loaded, calls plan skip_all, which will terminate @@ -143,13 +136,17 @@ Test::RRA - Support functions for Perl tests =head1 SYNOPSIS - use Test::RRA qw(skip_unless_maintainer use_prereq); + use Test::RRA + qw(skip_unless_author skip_unless_automated use_prereq); + + # Skip this test unless author tests are requested. + skip_unless_author('Coding style tests'); - # Skip this test unless maintainer tests are requested. - skip_unless_maintainer('Coding style tests'); + # Skip this test unless doing automated or release testing. + skip_unless_automated('POD syntax tests'); # Load modules, skipping the test if they're not available. - use_prereq('File::Slurp'); + use_prereq('Perl6::Slurp', 'slurp'); use_prereq('Test::Script::Run', '0.04'); =head1 DESCRIPTION @@ -166,12 +163,23 @@ script should be explicitly imported. =over 4 -=item skip_unless_maintainer(DESC) +=item skip_unless_author(DESC) -Checks whether RRA_MAINTAINER_TESTS is set in the environment and skips -the whole test (by calling C<plan skip_all> from Test::More) if it is not. +Checks whether AUTHOR_TESTING is set in the environment and skips the +whole test (by calling C<plan skip_all> from Test::More) if it is not. DESC is a description of the tests being skipped. A space and C<only run -for maintainer> will be appended to it and used as the skip reason. +for author> will be appended to it and used as the skip reason. + +=item skip_unless_automated(DESC) + +Checks whether AUTHOR_TESTING, AUTOMATED_TESTING, or RELEASE_TESTING are +set in the environment and skips the whole test (by calling C<plan +skip_all> from Test::More) if they are not. This should be used by tests +that should not run during end-user installs of the module, but which +should run as part of CPAN smoke testing and release testing. + +DESC is a description of the tests being skipped. A space and C<normally +skipped> will be appended to it and used as the skip reason. =item use_prereq(MODULE[, VERSION][, IMPORT ...]) @@ -192,7 +200,7 @@ Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior University Permission is hereby granted, free of charge, to any person obtaining a @@ -220,4 +228,8 @@ Test::More(3), Test::RRA::Automake(3), Test::RRA::Config(3) This module is maintained in the rra-c-util package. The current version is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. +The functions to control when tests are run use environment variables +defined by the L<Lancaster +Consensus|https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md>. + =cut diff --git a/tests/tap/perl/Test/RRA/Automake.pm b/tests/tap/perl/Test/RRA/Automake.pm index b8ce095..1a7fa93 100644 --- a/tests/tap/perl/Test/RRA/Automake.pm +++ b/tests/tap/perl/Test/RRA/Automake.pm @@ -9,31 +9,6 @@ # # All the functions here assume that BUILD and SOURCE are set in the # environment. This is normally done via the C TAP Harness runtests wrapper. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. -# -# Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2013 -# The Board of Trustees of the Leland Stanford Junior University -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. package Test::RRA::Automake; @@ -87,7 +62,7 @@ BEGIN { # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.12'; + $VERSION = '5.08'; } # Perl directories to skip globally for perl_dirs. We ignore the perl @@ -196,7 +171,7 @@ sub perl_dirs { # Build the list of top-level directories to test. opendir(my $rootdir, q{.}) or BAIL_OUT("cannot open .: $!"); - my @dirs = grep { -d $_ && !$skip{$_} } readdir($rootdir); + my @dirs = grep { -d && !$skip{$_} } readdir($rootdir); closedir($rootdir); @dirs = File::Spec->no_upwards(@dirs); @@ -387,6 +362,8 @@ Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE +Copyright 2014 Russ Allbery <eagle@eyrie.org> + Copyright 2013 The Board of Trustees of the Leland Stanford Junior University diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm index 106cc6e..3e04bdb 100644 --- a/tests/tap/perl/Test/RRA/Config.pm +++ b/tests/tap/perl/Test/RRA/Config.pm @@ -4,9 +4,6 @@ # configuration file to store some package-specific data. This module loads # that configuration and provides the namespace for the configuration # settings. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. package Test::RRA::Config; @@ -31,12 +28,13 @@ BEGIN { @EXPORT_OK = qw( $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE @STRICT_IGNORE + @STRICT_PREREQ ); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.12'; + $VERSION = '5.08'; } # If BUILD or SOURCE are set in the environment, look for data/perl.conf under @@ -65,6 +63,7 @@ our $MINIMUM_VERSION = '5.008'; our %MINIMUM_VERSION; our @POD_COVERAGE_EXCLUDE; our @STRICT_IGNORE; +our @STRICT_PREREQ; # Load the configuration. if (!do($PATH)) { @@ -163,6 +162,13 @@ for C<use strict> and C<use warnings>. The contents of this directory must be either top-level directory names or directory names starting with F<tests/>. +=item @STRICT_PREREQ + +A list of Perl modules that have to be available in order to do meaningful +Test::Strict testing. If any of the modules cannot be loaded via C<use>, +Test::Strict checking will be skipped. There is currently no way to +require specific versions of the modules. + =back No variables are exported by default, but the variables can be imported @@ -174,7 +180,7 @@ Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior University Permission is hereby granted, free of charge, to any person obtaining a diff --git a/tests/tap/process.c b/tests/tap/process.c index b8d6ff9..8c22324 100644 --- a/tests/tap/process.c +++ b/tests/tap/process.c @@ -7,12 +7,15 @@ * runs a function in a subprocess and checks its output and exit status * against expected values. * + * Requires an Autoconf probe for sys/select.h and a replacement for a missing + * mkstemp. + * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org> - * Copyright 2009, 2010, 2011, 2013 + * Copyright 2002, 2004, 2005, 2013 Russ Allbery <eagle@eyrie.org> + * Copyright 2009, 2010, 2011, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -37,12 +40,51 @@ #include <config.h> #include <portable/system.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#include <sys/stat.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif #include <sys/wait.h> +#include <time.h> #include <tests/tap/basic.h> #include <tests/tap/process.h> #include <tests/tap/string.h> +/* May be defined by the build system. */ +#ifndef PATH_FAKEROOT +# define PATH_FAKEROOT "" +#endif + +/* How long to wait for the process to start in seconds. */ +#define PROCESS_WAIT 10 + +/* + * Used to store information about a background process. This contains + * everything required to stop the process and clean up after it. + */ +struct process { + pid_t pid; /* PID of child process */ + char *pidfile; /* PID file to delete on process stop */ + char *tmpdir; /* Temporary directory for log file */ + char *logfile; /* Log file of process output */ + bool is_child; /* Whether we can waitpid for process */ + struct process *next; /* Next process in global list */ +}; + +/* + * Global list of started processes, which will be cleaned up automatically on + * program exit if they haven't been explicitly stopped with process_stop + * prior to that point. + */ +static struct process *processes = NULL; + /* * Given a function, an expected exit status, and expected output, runs that @@ -178,3 +220,309 @@ run_setup(const char *const argv[]) } free(output); } + + +/* + * Free the resources associated with tracking a process, without doing + * anything to the process. This is kept separate so that we can free + * resources during shutdown in a non-primary process. + */ +static void +process_free(struct process *process) +{ + struct process **prev; + + /* Do nothing if called with a NULL argument. */ + if (process == NULL) + return; + + /* Remove the process from the global list. */ + prev = &processes; + while (*prev != NULL && *prev != process) + prev = &(*prev)->next; + if (*prev == process) + *prev = process->next; + + /* Free resources. */ + free(process->pidfile); + free(process->logfile); + test_tmpdir_free(process->tmpdir); + free(process); +} + + +/* + * Kill a process and wait for it to exit. Returns the status of the process. + * Calls bail on a system failure or a failure of the process to exit. + * + * We are quite aggressive with error reporting here because child processes + * that don't exit or that don't exist often indicate some form of test + * failure. + */ +static int +process_kill(struct process *process) +{ + int result, i; + int status = -1; + struct timeval tv; + unsigned long pid = process->pid; + + /* If the process is not a child, just kill it and hope. */ + if (!process->is_child) { + if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) + sysbail("cannot send SIGTERM to process %lu", pid); + return 0; + } + + /* Check if the process has already exited. */ + result = waitpid(process->pid, &status, WNOHANG); + if (result < 0) + sysbail("cannot wait for child process %lu", pid); + else if (result > 0) + return status; + + /* + * Kill the process and wait for it to exit. I don't want to go to the + * work of setting up a SIGCHLD handler or a full event loop here, so we + * effectively poll every tenth of a second for process exit (and + * hopefully faster when it does since the SIGCHLD may interrupt our + * select, although we're racing with it. + */ + if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) + sysbail("cannot send SIGTERM to child process %lu", pid); + for (i = 0; i < PROCESS_WAIT * 10; i++) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + result = waitpid(process->pid, &status, WNOHANG); + if (result < 0) + sysbail("cannot wait for child process %lu", pid); + else if (result > 0) + return status; + } + + /* The process still hasn't exited. Bail. */ + bail("child process %lu did not exit on SIGTERM", pid); + + /* Not reached, but some compilers may get confused. */ + return status; +} + + +/* + * Stop a particular process given its process struct. This kills the + * process, waits for it to exit if possible (giving it at most five seconds), + * and then removes it from the global processes struct so that it isn't + * stopped again during global shutdown. + */ +void +process_stop(struct process *process) +{ + int status; + unsigned long pid = process->pid; + + /* Stop the process. */ + status = process_kill(process); + + /* Call diag to flush logs as well as provide exit status. */ + if (process->is_child) + diag("stopped process %lu (exit status %d)", pid, status); + else + diag("stopped process %lu", pid); + + /* Remove the log and PID file. */ + diag_file_remove(process->logfile); + unlink(process->pidfile); + unlink(process->logfile); + + /* Free resources. */ + process_free(process); +} + + +/* + * Stop all running processes. This is called as a cleanup handler during + * process shutdown. The first argument, which says whether the test was + * successful, is ignored, since the same actions should be performed + * regardless. The second argument says whether this is the primary process, + * in which case we do the full shutdown. Otherwise, we only free resources + * but don't stop the process. + */ +static void +process_stop_all(int success UNUSED, int primary) +{ + while (processes != NULL) { + if (primary) + process_stop(processes); + else + process_free(processes); + } +} + + +/* + * Read the PID of a process from a file. This is necessary when running + * under fakeroot to get the actual PID of the remctld process. + */ +static long +read_pidfile(const char *path) +{ + FILE *file; + char buffer[BUFSIZ]; + long pid; + + file = fopen(path, "r"); + if (file == NULL) + sysbail("cannot open %s", path); + if (fgets(buffer, sizeof(buffer), file) == NULL) + sysbail("cannot read from %s", path); + fclose(file); + pid = strtol(buffer, NULL, 10); + if (pid <= 0) + bail("cannot read PID from %s", path); + return pid; +} + + +/* + * Start a process and return its status information. The status information + * is also stored in the global processes linked list so that it can be + * stopped automatically on program exit. + * + * The boolean argument says whether to start the process under fakeroot. If + * true, PATH_FAKEROOT must be defined, generally by Autoconf. If it's not + * found, call skip_all. + * + * This is a helper function for process_start and process_start_fakeroot. + */ +static struct process * +process_start_internal(const char *const argv[], const char *pidfile, + bool fakeroot) +{ + size_t i; + int log_fd; + const char *name; + struct timeval tv; + struct process *process; + const char **fakeroot_argv = NULL; + const char *path_fakeroot = PATH_FAKEROOT; + + /* Check prerequisites. */ + if (fakeroot && path_fakeroot[0] == '\0') + skip_all("fakeroot not found"); + + /* Create the process struct and log file. */ + process = bcalloc(1, sizeof(struct process)); + process->pidfile = bstrdup(pidfile); + process->tmpdir = test_tmpdir(); + name = strrchr(argv[0], '/'); + if (name != NULL) + name++; + else + name = argv[0]; + basprintf(&process->logfile, "%s/%s.log.XXXXXX", process->tmpdir, name); + log_fd = mkstemp(process->logfile); + if (log_fd < 0) + sysbail("cannot create log file for %s", argv[0]); + + /* If using fakeroot, rewrite argv accordingly. */ + if (fakeroot) { + for (i = 0; argv[i] != NULL; i++) + ; + fakeroot_argv = bcalloc(2 + i + 1, sizeof(const char *)); + fakeroot_argv[0] = path_fakeroot; + fakeroot_argv[1] = "--"; + for (i = 0; argv[i] != NULL; i++) + fakeroot_argv[i + 2] = argv[i]; + fakeroot_argv[i + 2] = NULL; + argv = fakeroot_argv; + } + + /* + * Fork off the child process, redirect its standard output and standard + * error to the log file, and then exec the program. + */ + process->pid = fork(); + if (process->pid < 0) + sysbail("fork failed"); + else if (process->pid == 0) { + if (dup2(log_fd, STDOUT_FILENO) < 0) + sysbail("cannot redirect standard output"); + if (dup2(log_fd, STDERR_FILENO) < 0) + sysbail("cannot redirect standard error"); + close(log_fd); + if (execv(argv[0], (char *const *) argv) < 0) + sysbail("exec of %s failed", argv[0]); + } + close(log_fd); + free(fakeroot_argv); + + /* + * In the parent. Wait for the child to start by watching for the PID + * file to appear in 100ms intervals. + */ + for (i = 0; i < PROCESS_WAIT * 10 && access(pidfile, F_OK) != 0; i++) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + } + + /* + * If the PID file still hasn't appeared after ten seconds, attempt to + * kill the process and then bail. + */ + if (access(pidfile, F_OK) != 0) { + kill(process->pid, SIGTERM); + alarm(5); + waitpid(process->pid, NULL, 0); + alarm(0); + bail("cannot start %s", argv[0]); + } + + /* + * Read the PID back from the PID file. This usually isn't necessary for + * non-forking daemons, but always doing this makes this function general, + * and it's required when running under fakeroot. + */ + if (fakeroot) + process->pid = read_pidfile(pidfile); + process->is_child = !fakeroot; + + /* Register the log file as a source of diag messages. */ + diag_file_add(process->logfile); + + /* + * Add the process to our global list and set our cleanup handler if this + * is the first process we started. + */ + if (processes == NULL) + test_cleanup_register(process_stop_all); + process->next = processes; + processes = process; + + /* All done. */ + return process; +} + + +/* + * Start a process and return the opaque process struct. The process must + * create pidfile with its PID when startup is complete. + */ +struct process * +process_start(const char *const argv[], const char *pidfile) +{ + return process_start_internal(argv, pidfile, false); +} + + +/* + * Start a process under fakeroot and return the opaque process struct. If + * fakeroot is not available, calls skip_all. The process must create pidfile + * with its PID when startup is complete. + */ +struct process * +process_start_fakeroot(const char *const argv[], const char *pidfile) +{ + return process_start_internal(argv, pidfile, true); +} diff --git a/tests/tap/process.h b/tests/tap/process.h index ed90345..8137d5d 100644 --- a/tests/tap/process.h +++ b/tests/tap/process.h @@ -5,7 +5,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2009, 2010 + * Copyright 2009, 2010, 2013 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -33,6 +33,9 @@ #include <config.h> #include <tests/tap/macros.h> +/* Opaque data type for process_start and friends. */ +struct process; + BEGIN_DECLS /* @@ -60,6 +63,32 @@ void is_function_output(test_function_type, void *data, int status, void run_setup(const char *const argv[]) __attribute__((__nonnull__)); +/* + * process_start starts a process in the background, returning an opaque data + * struct that can be used to stop the process later. The standard output and + * standard error of the process will be sent to a log file registered with + * diag_file_add, so its output will be properly interleaved with the test + * case output. + * + * The process should create a PID file in the path given as the second + * argument when it's finished initialization. + * + * process_start_fakeroot is the same but starts the process under fakeroot. + * PATH_FAKEROOT must be defined (generally by Autoconf). If fakeroot is not + * found, process_start_fakeroot will call skip_all, so be sure to call this + * function before plan. + * + * process_stop can be called to explicitly stop the process. If it isn't + * called by the test program, it will be called automatically when the + * program exits. + */ +struct process *process_start(const char *const argv[], const char *pidfile) + __attribute__((__nonnull__)); +struct process *process_start_fakeroot(const char *const argv[], + const char *pidfile) + __attribute__((__nonnull__)); +void process_stop(struct process *); + END_DECLS #endif /* TAP_PROCESS_H */ diff --git a/tests/tap/string.h b/tests/tap/string.h index cc51945..d58f75d 100644 --- a/tests/tap/string.h +++ b/tests/tap/string.h @@ -42,7 +42,7 @@ BEGIN_DECLS void basprintf(char **, const char *, ...) __attribute__((__nonnull__, __format__(printf, 2, 3))); void bvasprintf(char **, const char *, va_list) - __attribute__((__nonnull__)); + __attribute__((__nonnull__, __format__(printf, 2, 0))); END_DECLS diff --git a/tests/util/messages-krb5-t.c b/tests/util/messages-krb5-t.c index 8015c4e..c6de5a5 100644 --- a/tests/util/messages-krb5-t.c +++ b/tests/util/messages-krb5-t.c @@ -5,7 +5,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2010, 2011, 2013 + * Copyright 2010, 2011, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -28,17 +28,31 @@ */ #include <config.h> -#include <portable/krb5.h> +#ifdef HAVE_KRB5 +# include <portable/krb5.h> +#endif #include <portable/system.h> #include <tests/tap/basic.h> #include <tests/tap/process.h> #include <util/macros.h> -#include <util/messages-krb5.h> +#ifdef HAVE_KRB5 +# include <util/messages-krb5.h> +#endif #include <util/messages.h> #include <util/xmalloc.h> +/* Skip the whole test if not built with Kerberos support. */ +#ifndef HAVE_KRB5 +int +main(void) +{ + skip_all("not built with Kerberos support"); + return 0; +} +#else + /* * Test functions. */ @@ -120,3 +134,5 @@ main(void) krb5_free_context(ctx); return 0; } + +#endif /* HAVE_KRB5 */ diff --git a/tests/util/messages-t.c b/tests/util/messages-t.c index f60fa6a..1098314 100644 --- a/tests/util/messages-t.c +++ b/tests/util/messages-t.c @@ -5,7 +5,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Written by Russ Allbery <eagle@eyrie.org> - * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org> + * Copyright 2002, 2004, 2005, 2015 Russ Allbery <eagle@eyrie.org> * Copyright 2009, 2010, 2011, 2012 * The Board of Trustees of the Leland Stanford Junior University * @@ -92,7 +92,8 @@ static void test11(void *data UNUSED) { sysdie("fatal"); } -static void log_msg(size_t len, const char *format, va_list args, int error) { +static void __attribute__((__format__(printf, 2, 0))) +log_msg(size_t len, const char *format, va_list args, int error) { fprintf(stderr, "%lu %d ", (unsigned long) len, error); vfprintf(stderr, format, args); fprintf(stderr, "\n"); diff --git a/tests/util/xmalloc-t b/tests/util/xmalloc-t index 74e4bbd..af604ed 100755 --- a/tests/util/xmalloc-t +++ b/tests/util/xmalloc-t @@ -6,7 +6,7 @@ # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # # Written by Russ Allbery <eagle@eyrie.org> -# Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org> +# Copyright 2000, 2001, 2006, 2014 Russ Allbery <eagle@eyrie.org> # Copyright 2008, 2009, 2010, 2012 # The Board of Trustees of the Leland Stanford Junior University # @@ -59,31 +59,33 @@ ok_xmalloc () { # failures in automated testing have been problems with the assumptions around # memory allocation or problems with the test suite, not problems with the # underlying xmalloc code. -if [ -z "$RRA_MAINTAINER_TESTS" ] ; then - skip_all 'xmalloc tests only run for maintainer' +if [ -z "$AUTHOR_TESTING" ] ; then + skip_all 'xmalloc tests only run for author' fi # Total tests. -plan 36 +plan 41 # First run the tests expected to succeed. -ok_xmalloc "malloc small" 0 "" "m" "21" "0" -ok_xmalloc "malloc large" 0 "" "m" "5500000" "0" -ok_xmalloc "malloc zero" 0 "" "m" "0" "0" -ok_xmalloc "realloc small" 0 "" "r" "21" "0" -ok_xmalloc "realloc large" 0 "" "r" "5500000" "0" -ok_xmalloc "strdup small" 0 "" "s" "21" "0" -ok_xmalloc "strdup large" 0 "" "s" "5500000" "0" -ok_xmalloc "strndup small" 0 "" "n" "21" "0" -ok_xmalloc "strndup large" 0 "" "n" "5500000" "0" -ok_xmalloc "calloc small" 0 "" "c" "24" "0" -ok_xmalloc "calloc large" 0 "" "c" "5500000" "0" -ok_xmalloc "asprintf small" 0 "" "a" "24" "0" -ok_xmalloc "asprintf large" 0 "" "a" "5500000" "0" -ok_xmalloc "vasprintf small" 0 "" "v" "24" "0" -ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0" +ok_xmalloc "malloc small" 0 "" "m" "21" "0" +ok_xmalloc "malloc large" 0 "" "m" "30000000" "0" +ok_xmalloc "malloc zero" 0 "" "m" "0" "0" +ok_xmalloc "realloc small" 0 "" "r" "21" "0" +ok_xmalloc "realloc large" 0 "" "r" "30000000" "0" +ok_xmalloc "reallocarray small" 0 "" "y" "20" "0" +ok_xmalloc "reallocarray large" 0 "" "y" "30000000" "0" +ok_xmalloc "strdup small" 0 "" "s" "21" "0" +ok_xmalloc "strdup large" 0 "" "s" "30000000" "0" +ok_xmalloc "strndup small" 0 "" "n" "21" "0" +ok_xmalloc "strndup large" 0 "" "n" "30000000" "0" +ok_xmalloc "calloc small" 0 "" "c" "24" "0" +ok_xmalloc "calloc large" 0 "" "c" "30000000" "0" +ok_xmalloc "asprintf small" 0 "" "a" "24" "0" +ok_xmalloc "asprintf large" 0 "" "a" "30000000" "0" +ok_xmalloc "vasprintf small" 0 "" "v" "24" "0" +ok_xmalloc "vasprintf large" 0 "" "v" "30000000" "0" -# Now limit our memory to 5.5MB and then try the large ones again, all of +# Now limit our memory to 30MB and then try the large ones again, all of # which should fail. # # The exact memory limits used here are essentially black magic. They need to @@ -91,53 +93,60 @@ ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0" # but not so large that we can't reasonably expect to allocate that much # memory normally. The amount of memory required varies a lot based on what # shared libraries are loaded, and if it's too small, all memory allocations -# fail. 5.5MB seems to work reasonably well on both Solaris and Linux. +# fail. 30MB seems to work reasonably well on both Solaris and Linux, even +# when the program is linked with additional libraries. # # We assume that there are enough miscellaneous allocations that an allocation # exactly as large as the limit will always fail. ok_xmalloc "malloc fail" 1 \ - "failed to malloc 5500000 bytes at xmalloc.c line 38" \ - "m" "5500000" "5500000" + "failed to malloc 30000000 bytes at xmalloc.c line 41" \ + "m" "30000000" "30000000" ok_xmalloc "realloc fail" 1 \ - "failed to realloc 5500000 bytes at xmalloc.c line 66" \ - "r" "5500000" "5500000" + "failed to realloc 30000000 bytes at xmalloc.c line 69" \ + "r" "30000000" "30000000" +ok_xmalloc "reallocarray fail" 1 \ + "failed to reallocarray 30000000 bytes at xmalloc.c line 99" \ + "y" "30000000" "30000000" ok_xmalloc "strdup fail" 1 \ - "failed to strdup 5500000 bytes at xmalloc.c line 97" \ - "s" "5500000" "5500000" + "failed to strdup 30000000 bytes at xmalloc.c line 130" \ + "s" "30000000" "30000000" ok_xmalloc "strndup fail" 1 \ - "failed to strndup 5500000 bytes at xmalloc.c line 143" \ - "n" "5500000" "5500000" + "failed to strndup 30000000 bytes at xmalloc.c line 176" \ + "n" "30000000" "30000000" ok_xmalloc "calloc fail" 1 \ - "failed to calloc 5500000 bytes at xmalloc.c line 167" \ - "c" "5500000" "5500000" + "failed to calloc 30000000 bytes at xmalloc.c line 200" \ + "c" "30000000" "30000000" ok_xmalloc "asprintf fail" 1 \ - "failed to asprintf 5500000 bytes at xmalloc.c line 191" \ - "a" "5500000" "5500000" + "failed to asprintf 30000000 bytes at xmalloc.c line 224" \ + "a" "30000000" "30000000" ok_xmalloc "vasprintf fail" 1 \ - "failed to vasprintf 5500000 bytes at xmalloc.c line 210" \ - "v" "5500000" "5500000" + "failed to vasprintf 30000000 bytes at xmalloc.c line 243" \ + "v" "30000000" "30000000" # Check our custom error handler. -ok_xmalloc "malloc custom" 1 "malloc 5500000 xmalloc.c 38" \ - "M" "5500000" "5500000" -ok_xmalloc "realloc custom" 1 "realloc 5500000 xmalloc.c 66" \ - "R" "5500000" "5500000" -ok_xmalloc "strdup custom" 1 "strdup 5500000 xmalloc.c 97" \ - "S" "5500000" "5500000" -ok_xmalloc "strndup custom" 1 "strndup 5500000 xmalloc.c 143" \ - "N" "5500000" "5500000" -ok_xmalloc "calloc custom" 1 "calloc 5500000 xmalloc.c 167" \ - "C" "5500000" "5500000" -ok_xmalloc "asprintf custom" 1 "asprintf 5500000 xmalloc.c 191" \ - "A" "5500000" "5500000" -ok_xmalloc "vasprintf custom" 1 "vasprintf 5500000 xmalloc.c 210" \ - "V" "5500000" "5500000" +ok_xmalloc "malloc custom" 1 "malloc 30000000 xmalloc.c 41" \ + "M" "30000000" "30000000" +ok_xmalloc "realloc custom" 1 "realloc 30000000 xmalloc.c 69" \ + "R" "30000000" "30000000" +ok_xmalloc "reallocarray custom" 1 "reallocarray 30000000 xmalloc.c 99" \ + "Y" "30000000" "30000000" +ok_xmalloc "strdup custom" 1 "strdup 30000000 xmalloc.c 130" \ + "S" "30000000" "30000000" +ok_xmalloc "strndup custom" 1 "strndup 30000000 xmalloc.c 176" \ + "N" "30000000" "30000000" +ok_xmalloc "calloc custom" 1 "calloc 30000000 xmalloc.c 200" \ + "C" "30000000" "30000000" +ok_xmalloc "asprintf custom" 1 "asprintf 30000000 xmalloc.c 224" \ + "A" "30000000" "30000000" +ok_xmalloc "vasprintf custom" 1 "vasprintf 30000000 xmalloc.c 243" \ + "V" "30000000" "30000000" # Check the smaller ones again just for grins. -ok_xmalloc "malloc retry" 0 "" "m" "21" "5500000" -ok_xmalloc "realloc retry" 0 "" "r" "32" "5500000" -ok_xmalloc "strdup retry" 0 "" "s" "64" "5500000" -ok_xmalloc "strndup retry" 0 "" "n" "20" "5500000" -ok_xmalloc "calloc retry" 0 "" "c" "24" "5500000" -ok_xmalloc "asprintf retry" 0 "" "a" "30" "5500000" -ok_xmalloc "vasprintf retry" 0 "" "v" "35" "5500000" +ok_xmalloc "malloc retry" 0 "" "m" "21" "30000000" +ok_xmalloc "realloc retry" 0 "" "r" "32" "30000000" +ok_xmalloc "reallocarray retry" 0 "" "y" "32" "30000000" +ok_xmalloc "strdup retry" 0 "" "s" "64" "30000000" +ok_xmalloc "strndup retry" 0 "" "n" "20" "30000000" +ok_xmalloc "calloc retry" 0 "" "c" "24" "30000000" +ok_xmalloc "asprintf retry" 0 "" "a" "30" "30000000" +ok_xmalloc "vasprintf retry" 0 "" "v" "35" "30000000" diff --git a/tests/util/xmalloc.c b/tests/util/xmalloc.c index a415614..84ba081 100644 --- a/tests/util/xmalloc.c +++ b/tests/util/xmalloc.c @@ -5,7 +5,7 @@ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * * Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org> - * Copyright 2008, 2012, 2013 + * Copyright 2008, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -34,7 +34,10 @@ #include <ctype.h> #include <errno.h> -#include <sys/time.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif +#include <time.h> /* Linux requires sys/time.h be included before sys/resource.h. */ #include <sys/resource.h> @@ -110,6 +113,36 @@ test_realloc(size_t size) /* + * Like test_realloc, but test allocating an array instead. Returns true on + * success, false on any failure. + */ +static int +test_reallocarray(size_t n, size_t size) +{ + char *buffer; + size_t i; + + buffer = xmalloc(10); + if (buffer == NULL) + return 0; + memset(buffer, 1, 10); + buffer = xreallocarray(buffer, n, size); + if (buffer == NULL) + return 0; + if (n > 0 && size > 0) + memset(buffer + 10, 2, (n * size) - 10); + for (i = 0; i < 10; i++) + if (buffer[i] != 1) + return 0; + for (i = 10; i < n * size; i++) + if (buffer[i] != 2) + return 0; + free(buffer); + return 1; +} + + +/* * Generate a string of the size indicated, call xstrdup on it, and then * ensure the result matches. Returns true on success, false on any failure. */ @@ -231,7 +264,7 @@ test_asprintf(size_t size) /* Wrapper around vasprintf to do the va_list stuff. */ -static void +static void __attribute__((__format__(printf, 2, 3))) xvasprintf_wrapper(char **strp, const char *format, ...) { va_list args; @@ -330,8 +363,8 @@ main(int argc, char *argv[]) syswarn("Can't set data limit to %lu", (unsigned long) limit); exit(2); } - if (size < limit || code == 'r') { - test_size = code == 'r' ? 10 : size; + if (size < limit || code == 'r' || code == 'y') { + test_size = (code == 'r' || code == 'y') ? 10 : size; if (test_size == 0) test_size = 1; tmp = malloc(test_size); @@ -352,6 +385,7 @@ main(int argc, char *argv[]) case 'c': exit(test_calloc(size) ? willfail : 1); case 'm': exit(test_malloc(size) ? willfail : 1); case 'r': exit(test_realloc(size) ? willfail : 1); + case 'y': exit(test_reallocarray(4, size / 4) ? willfail : 1); case 's': exit(test_strdup(size) ? willfail : 1); case 'n': exit(test_strndup(size) ? willfail : 1); case 'a': exit(test_asprintf(size) ? willfail : 1); diff --git a/util/macros.h b/util/macros.h index d071793..4a773a2 100644 --- a/util/macros.h +++ b/util/macros.h @@ -29,6 +29,10 @@ #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) #define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)]) +/* Used to name the elements of the array passed to pipe. */ +#define PIPE_READ 0 +#define PIPE_WRITE 1 + /* Used for unused parameters to silence gcc warnings. */ #define UNUSED __attribute__((__unused__)) diff --git a/util/messages.c b/util/messages.c index 769166f..b5c2dba 100644 --- a/util/messages.c +++ b/util/messages.c @@ -131,7 +131,7 @@ message_handlers(message_handler_func **list, unsigned int count, va_list args) if (*list != stdout_handlers && *list != stderr_handlers) free(*list); - *list = xmalloc(sizeof(message_handler_func) * (count + 1)); + *list = xcalloc(count + 1, sizeof(message_handler_func)); for (i = 0; i < count; i++) (*list)[i] = (message_handler_func) va_arg(args, message_handler_func); (*list)[count] = NULL; @@ -160,6 +160,31 @@ HANDLER_FUNCTION(die) /* + * Reset all handlers back to the defaults and free all allocated memory. + * This is primarily useful for programs that undergo comprehensive memory + * allocation analysis. + */ +void +message_handlers_reset(void) +{ + free(debug_handlers); + debug_handlers = NULL; + if (notice_handlers != stdout_handlers) { + free(notice_handlers); + notice_handlers = stdout_handlers; + } + if (warn_handlers != stderr_handlers) { + free(warn_handlers); + warn_handlers = stderr_handlers; + } + if (die_handlers != stderr_handlers) { + free(die_handlers); + die_handlers = stderr_handlers; + } +} + + +/* * Print a message to stdout, supporting message_program_name. */ void @@ -200,7 +225,7 @@ message_log_stderr(size_t len UNUSED, const char *fmt, va_list args, int err) * This needs further attention on Windows. For example, it currently doesn't * log the errno information. */ -static void +static void __attribute__((__format__(printf, 3, 0))) message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err) { char *buffer; diff --git a/util/messages.h b/util/messages.h index 9ea1a8b..cf91ba7 100644 --- a/util/messages.h +++ b/util/messages.h @@ -4,7 +4,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2008, 2010, 2013 + * Copyright 2008, 2010, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * Copyright (c) 2004, 2005, 2006 * by Internet Systems Consortium, Inc. ("ISC") @@ -73,29 +73,36 @@ void message_handlers_warn(unsigned int count, ...); void message_handlers_die(unsigned int count, ...); /* + * Reset all message handlers back to the defaults and free any memory that + * was allocated by the other message_handlers_* functions. + */ +void message_handlers_reset(void); + +/* * Some useful handlers, intended to be passed to message_handlers_*. All * handlers take the length of the formatted message, the format, a variadic * argument list, and the errno setting if any. */ void message_log_stdout(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_stderr(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_debug(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_info(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_notice(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_warning(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_err(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); void message_log_syslog_crit(size_t, const char *, va_list, int) - __attribute__((__nonnull__)); + __attribute__((__format__(printf, 2, 0), __nonnull__)); /* The type of a message handler. */ -typedef void (*message_handler_func)(size_t, const char *, va_list, int); +typedef void (*message_handler_func)(size_t, const char *, va_list, int) + __attribute__((__format__(printf, 2, 0))); /* If non-NULL, called before exit and its return value passed to exit. */ extern int (*message_fatal_cleanup)(void); diff --git a/util/xmalloc.c b/util/xmalloc.c index 5bfd555..4af8ee9 100644 --- a/util/xmalloc.c +++ b/util/xmalloc.c @@ -33,6 +33,10 @@ * allocation function will try its allocation again (calling the handler * again if it still fails). * + * xreallocarray behaves the same as the OpenBSD reallocarray function but for + * the same error checking, which in turn is the same as realloc but with + * calloc-style arguments and size overflow checking. + * * xstrndup behaves like xstrdup but only copies the given number of * characters. It allocates an additional byte over its second argument and * always nul-terminates the string. @@ -58,7 +62,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2012, 2013 + * Copyright 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * Copyright (c) 2004, 2005, 2006 * by Internet Systems Consortium, Inc. ("ISC") @@ -152,6 +156,20 @@ x_realloc(void *p, size_t size, const char *file, int line) } +void * +x_reallocarray(void *p, size_t n, size_t size, const char *file, int line) +{ + void *newp; + + newp = reallocarray(p, n, size); + while (newp == NULL && size > 0 && n > 0) { + (*xmalloc_error_handler)("reallocarray", n * size, file, line); + newp = reallocarray(p, n, size); + } + return newp; +} + + char * x_strdup(const char *s, const char *file, int line) { @@ -240,6 +258,7 @@ x_asprintf(char **strp, const char *file, int line, const char *fmt, ...) status = vasprintf(strp, fmt, args_copy); va_end(args_copy); } + va_end(args); } #else /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */ void @@ -262,5 +281,6 @@ x_asprintf(char **strp, const char *fmt, ...) status = vasprintf(strp, fmt, args_copy); va_end(args_copy); } + va_end(args); } #endif /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */ diff --git a/util/xmalloc.h b/util/xmalloc.h index ac4d796..6aa9b93 100644 --- a/util/xmalloc.h +++ b/util/xmalloc.h @@ -4,7 +4,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2010, 2012, 2013 + * Copyright 2010, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * Copyright (c) 2004, 2005, 2006 * by Internet Systems Consortium, Inc. ("ISC") @@ -47,6 +47,8 @@ #define xstrdup(p) x_strdup((p), __FILE__, __LINE__) #define xstrndup(p, size) x_strndup((p), (size), __FILE__, __LINE__) #define xvasprintf(p, f, a) x_vasprintf((p), (f), (a), __FILE__, __LINE__) +#define xreallocarray(p, n, size) \ + x_reallocarray((p), (n), (size), __FILE__, __LINE__) /* * asprintf is a special case since it takes variable arguments. If we have @@ -81,12 +83,14 @@ void *x_malloc(size_t, const char *, int) __attribute__((__alloc_size__(1), __malloc__, __nonnull__)); void *x_realloc(void *, size_t, const char *, int) __attribute__((__alloc_size__(2), __malloc__, __nonnull__(3))); +void *x_reallocarray(void *, size_t, size_t, const char *, int) + __attribute__((__alloc_size__(2, 3), __malloc__, __nonnull__(4))); char *x_strdup(const char *, const char *, int) __attribute__((__malloc__, __nonnull__)); char *x_strndup(const char *, size_t, const char *, int) __attribute__((__malloc__, __nonnull__)); void x_vasprintf(char **, const char *, va_list, const char *, int) - __attribute__((__nonnull__)); + __attribute__((__nonnull__, __format__(printf, 2, 0))); /* asprintf special case. */ #if HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS |