]> eyrie.org Git - kerberos/kstart.git/commitdiff
Update to rra-c-util 5.9 and C TAP Harness 3.4
authorRuss Allbery <eagle@eyrie.org>
Sat, 26 Dec 2015 00:11:42 +0000 (16:11 -0800)
committerRuss Allbery <eagle@eyrie.org>
Sat, 26 Dec 2015 00:11:42 +0000 (16:11 -0800)
Update to rra-c-util 5.9:

* Add missing va_end to xasprintf implementation.
* Improve portability to Kerberos included in Solaris 10.
* Use appropriate warning flags with Clang (currently not warning clean).
* Use Lancaster Consensus environment variables to control tests.
* Use calloc or reallocarray for protection against integer overflows.
* Suppress warnings from Kerberos headers in non-system paths.
* Assume calloc initializes pointers to NULL.
* Assume free(NULL) is properly ignored.
* Improve error handling in xasprintf and xvasprintf.
* Check the return status of snprintf and vsnprintf properly.
* Preserve errno if snprintf fails in vasprintf replacement.
* Fix probing for Heimdal's libroken to work with older versions.
* Improve POD tests.
* Fix kafs compilation failure on Solaris 11 or later.
* Drop concat from the util library in favor of asprintf.
* Fail on any error in [bx]asprintf and [bx]vasprintf.
* Pass --deps to krb5-config in the non-reduced-dependencies case.
* Silence __attribute__ warnings on more compilers.

Update to C TAP Harness 3.4:

* Fix segfault in runtests with an empty test list.
* Display verbose test results with -v or C_TAP_VERBOSE.
* Support comments and blank lines in test lists.
* Check for integer overflow on memory allocations.
* Reopen standard input to /dev/null when running a test list.
* Don't leak extraneous file descriptors to tests.
* Suppress lazy plans and test summaries if the test failed with bail.
* runtests now treats the command line as a list of tests by default.
* The full test executable path can now be passed to runtests -o.
* Improved harness output for tests with lazy plans.
* Improved harness output to a terminal for some abort cases.
* Flush harness output after each test even when not on a terminal.
* Only use feature-test macros when requested or built with gcc -ansi.
* Drop is_double from the C TAP library to avoid requiring -lm.
* Avoid using local in the shell libtap.sh library.
* Silence __attribute__ warnings on more compilers.
* runtests now frees all allocated resources on exit.

66 files changed:
.gitignore
Makefile.am
NEWS
autogen
configure.ac
framework.c
k5start.c
kafs/sys-solaris.c
krenew.c
m4/clang.m4 [new file with mode: 0644]
m4/kafs.m4
m4/krb5-config.m4 [new file with mode: 0644]
m4/krb5.m4
portable/asprintf.c
portable/dummy.c
portable/k_haspag.c
portable/kafs.h
portable/krb5-extra.c
portable/krb5.h
portable/macros.h
portable/mkstemp.c
portable/reallocarray.c [new file with mode: 0644]
portable/setenv.c
portable/snprintf.c
portable/strlcat.c
portable/strlcpy.c
portable/strndup.c
portable/system.h
tests/TESTS
tests/data/perl.conf [new file with mode: 0644]
tests/docs/pod-spelling-t
tests/docs/pod-t
tests/kafs/basic-t
tests/kafs/basic.c
tests/portable/asprintf-t.c
tests/portable/daemon-t.c
tests/portable/reallocarray-t.c [new file with mode: 0644]
tests/portable/reallocarray.c [new file with mode: 0644]
tests/portable/snprintf-t.c
tests/portable/strndup-t.c
tests/runtests.c
tests/tap/basic.c
tests/tap/basic.h
tests/tap/libtap.sh
tests/tap/macros.h [new file with mode: 0644]
tests/tap/perl/Test/RRA.pm [new file with mode: 0644]
tests/tap/perl/Test/RRA/Automake.pm [new file with mode: 0644]
tests/tap/perl/Test/RRA/Config.pm [new file with mode: 0644]
tests/tap/process.c
tests/tap/process.h
tests/tap/string.c
tests/tap/string.h
tests/util/concat-t.c [deleted file]
tests/util/messages-krb5-t.c
tests/util/messages-t.c
tests/util/xmalloc-t
tests/util/xmalloc.c
util/concat.c [deleted file]
util/concat.h [deleted file]
util/macros.h
util/messages-krb5.c
util/messages-krb5.h
util/messages.c
util/messages.h
util/xmalloc.c
util/xmalloc.h

index a527c003da3971e193387e149acce2c75ab84ce8..f8dbb6206d3dd1b9428513fa163c66b0c60cdce7 100644 (file)
@@ -1,6 +1,7 @@
 /Makefile
 /Makefile.in
 /aclocal.m4
+/autom4te.cache/
 /build-aux/
 /config.h
 /config.h.in
 /tests/portable/asprintf-t
 /tests/portable/daemon-t
 /tests/portable/mkstemp-t
+/tests/portable/reallocarray-t
 /tests/portable/setenv-t
 /tests/portable/snprintf-t
 /tests/portable/strlcat-t
 /tests/portable/strlcpy-t
 /tests/portable/strndup-t
 /tests/runtests
-/tests/util/concat-t
 /tests/util/messages-krb5-t
 /tests/util/messages-t
 /tests/util/xmalloc
index 7c9f3da4ae37b7e2f193c1e4159f2bd6d94ba767..a4c968da82072bac458a8a565ec5e7c7ac1de28b 100644 (file)
@@ -1,6 +1,7 @@
 # Automake makefile for kstart.
 #
 # Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2015 Russ Allbery <eagle@eyrie.org>
 # Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
 #    2011 The Board of Trustees of the Leland Stanford Junior University
 #
@@ -17,7 +18,11 @@ EXTRA_DIST = .gitignore LICENSE autogen examples/krenew-agent k5start.pod   \
        tests/krenew/afs-t tests/krenew/basic-t tests/krenew/daemon-t       \
        tests/krenew/errors-t tests/krenew/keyring-t                        \
        tests/krenew/non-renewable-t tests/libtest.pl tests/tap/libtap.sh   \
-       tests/util/xmalloc-t
+       tests/tap/perl/Test/RRA.pm tests/tap/perl/Test/RRA/Automake.pm      \
+       tests/tap/perl/Test/RRA/Config.pm tests/util/xmalloc-t
+
+# Enable AFS support when running distcheck.
+DISTCHECK_CONFIGURE_FLAGS = --enable-setpag
 
 # The following library order matters for annoying reasons.  Older
 # libafsauthent contains its own com_err implementation, which we do not
@@ -44,10 +49,9 @@ portable_libportable_a_SOURCES = portable/dummy.c portable/kafs.h    \
        portable/krb5.h portable/macros.h portable/stdbool.h            \
        portable/system.h
 portable_libportable_a_LIBADD = $(LIBOBJS)
-util_libutil_a_SOURCES = util/command.c util/command.h util/concat.c       \
-       util/concat.h util/macros.h util/messages-krb5.c                    \
-       util/messages-krb5.h util/messages.c util/messages.h util/xmalloc.c \
-       util/xmalloc.h
+util_libutil_a_SOURCES = util/command.c util/command.h util/macros.h   \
+       util/messages-krb5.c util/messages-krb5.h util/messages.c       \
+       util/messages.h util/xmalloc.c util/xmalloc.h
 
 # Conditionally build the replacement kafs library and add it to the
 # libraries used by the other programs.
@@ -71,44 +75,59 @@ krenew_LDADD = $(LIBKAFS) util/libutil.a portable/libportable.a \
        $(K5START_LIBS)
 dist_man_MANS = k5start.1 krenew.1
 
-DISTCLEANFILES = tests/data/.placeholder
-MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/compile \
-       build-aux/config.guess build-aux/config.sub build-aux/depcomp \
-       build-aux/install-sh build-aux/missing config.h.in config.h.in~ \
-       configure k5start.1 krenew.1
+DISTCLEANFILES = config.h.in~ tests/data/.placeholder
+MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/compile                \
+       build-aux/config.guess build-aux/config.sub build-aux/depcomp   \
+       build-aux/install-sh build-aux/missing config.h.in configure    \
+       k5start.1 krenew.1
+
+# Remove the Autoconf cache directory on make distclean.
+distclean-local:
+       rm -rf autom4te.cache
 
-# A set of flags for warnings. Add -O because gcc won't find some warnings
+# A set of flags for warnings.  Add -O because gcc won't find some warnings
 # without optimization turned on.  Desirable warnings that can't be turned
 # on due to other problems:
 #
-#     -Wconversion     http://bugs.debian.org/488884 (htons warnings)
+#     -Wconversion      http://bugs.debian.org/488884 (htons warnings)
 #
-# Last checked against gcc 4.6.1 (2011-05-04).
-WARNINGS = -g -O -Wall -Wextra -Wendif-labels -Wformat=2 -Winit-self    \
-       -Wswitch-enum -Wdeclaration-after-statement -Wshadow             \
-       -Wpointer-arith -Wbad-function-cast -Wcast-align -Wwrite-strings \
-       -Wjump-misses-init -Wlogical-op -Wstrict-prototypes              \
-       -Wmissing-prototypes -Wredundant-decls -Wnested-externs -Werror
+# 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.
+if WARNINGS_GCC
+    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
+endif
+if WARNINGS_CLANG
+    WARNINGS = -Weverything -Wno-padded
+endif
 
 warnings:
-       $(MAKE) V=0 CFLAGS='$(WARNINGS)'
-       $(MAKE) V=0 CFLAGS='$(WARNINGS)' $(check_PROGRAMS)
+       $(MAKE) V=0 CFLAGS='$(WARNINGS)' KRB5_CPPFLAGS='$(KRB5_CPPFLAGS_GCC)'
+       $(MAKE) V=0 CFLAGS='$(WARNINGS)' \
+           KRB5_CPPFLAGS='$(KRB5_CPPFLAGS_GCC)' $(check_PROGRAMS)
 
 # The bits below are for the test suite, not for the main package.
 check_PROGRAMS = tests/runtests tests/kafs/basic tests/kafs/haspag-t   \
        tests/portable/asprintf-t tests/portable/daemon-t               \
-       tests/portable/mkstemp-t tests/portable/setenv-t                \
-       tests/portable/snprintf-t tests/portable/strlcat-t              \
-       tests/portable/strlcpy-t tests/portable/strndup-t               \
-       tests/util/concat-t tests/util/messages-krb5-t                  \
+       tests/portable/mkstemp-t tests/portable/reallocarray-t          \
+       tests/portable/setenv-t tests/portable/snprintf-t               \
+       tests/portable/strlcat-t tests/portable/strlcpy-t               \
+       tests/portable/strndup-t tests/util/messages-krb5-t             \
        tests/util/messages-t tests/util/xmalloc
 tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \
        -DBUILD='"$(abs_top_builddir)/tests"'
 check_LIBRARIES = tests/tap/libtap.a
 tests_tap_libtap_a_CPPFLAGS = -I$(abs_top_srcdir)/tests
 tests_tap_libtap_a_SOURCES = tests/tap/basic.c tests/tap/basic.h       \
-       tests/tap/process.c tests/tap/process.h tests/tap/string.c      \
-       tests/tap/string.h
+       tests/tap/macros.h tests/tap/process.c tests/tap/process.h      \
+       tests/tap/string.c tests/tap/string.h
 
 # kafs tests are buit differently depending on whether we use our local
 # libkafs replacement.
@@ -137,6 +156,10 @@ tests_portable_daemon_t_LDADD = tests/tap/libtap.a portable/libportable.a
 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.a
+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.a
 tests_portable_setenv_t_SOURCES = tests/portable/setenv-t.c \
        tests/portable/setenv.c
 tests_portable_setenv_t_LDADD = tests/tap/libtap.a portable/libportable.a
@@ -162,4 +185,4 @@ tests_util_messages_t_LDADD = tests/tap/libtap.a util/libutil.a \
 tests_util_xmalloc_LDADD = util/libutil.a portable/libportable.a
 
 check-local: $(check_PROGRAMS)
-       cd tests && ./runtests $(abs_top_srcdir)/tests/TESTS
+       cd tests && ./runtests -l $(abs_top_srcdir)/tests/TESTS
diff --git a/NEWS b/NEWS
index c674b12b93fe2eaf76d613932b7d89d29ea005ec..0913e053d330ff87d11965f96e26fb9f52a4b6aa 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,47 @@ kstart 4.2 (unreleased)
     Fix k5start and krenew to not incorrectly reject the -b flag in
     conjunction with -K or a command.  Patch from Lars Hanke.
 
+    Update to rra-c-util 5.9:
+
+    * Add missing va_end to xasprintf implementation.
+    * Improve portability to Kerberos included in Solaris 10.
+    * Use appropriate warning flags with Clang (currently not warning clean).
+    * Use Lancaster Consensus environment variables to control tests.
+    * Use calloc or reallocarray for protection against integer overflows.
+    * Suppress warnings from Kerberos headers in non-system paths.
+    * Assume calloc initializes pointers to NULL.
+    * Assume free(NULL) is properly ignored.
+    * Improve error handling in xasprintf and xvasprintf.
+    * Check the return status of snprintf and vsnprintf properly.
+    * Preserve errno if snprintf fails in vasprintf replacement.
+    * Fix probing for Heimdal's libroken to work with older versions.
+    * Improve POD tests.
+    * Fix kafs compilation failure on Solaris 11 or later.
+    * Drop concat from the util library in favor of asprintf.
+    * Fail on any error in [bx]asprintf and [bx]vasprintf.
+    * Pass --deps to krb5-config in the non-reduced-dependencies case.
+    * Silence __attribute__ warnings on more compilers.
+
+    Update to C TAP Harness 3.4:
+
+    * Fix segfault in runtests with an empty test list.
+    * Display verbose test results with -v or C_TAP_VERBOSE.
+    * Support comments and blank lines in test lists.
+    * Check for integer overflow on memory allocations.
+    * Reopen standard input to /dev/null when running a test list.
+    * Don't leak extraneous file descriptors to tests.
+    * Suppress lazy plans and test summaries if the test failed with bail.
+    * runtests now treats the command line as a list of tests by default.
+    * The full test executable path can now be passed to runtests -o.
+    * Improved harness output for tests with lazy plans.
+    * Improved harness output to a terminal for some abort cases.
+    * Flush harness output after each test even when not on a terminal.
+    * Only use feature-test macros when requested or built with gcc -ansi.
+    * Drop is_double from the C TAP library to avoid requiring -lm.
+    * Avoid using local in the shell libtap.sh library.
+    * Silence __attribute__ warnings on more compilers.
+    * runtests now frees all allocated resources on exit.
+
 kstart 4.1 (2012-01-07)
 
     Fix a regression introduced in kstart 4.0 that caused k5start -H and
diff --git a/autogen b/autogen
index 9fca713bd415d375ae0ea9dff73cefae111f431d..780c36c1f244bc2cd58467280bf5fa0ec2f6ff19 100755 (executable)
--- a/autogen
+++ b/autogen
@@ -4,8 +4,8 @@
 
 set -e
 
+# Regenerate all the autotools files.
 autoreconf -i --force
-rm -rf autom4te.cache
 
 # Generate manual pages.
 version=`grep '^kstart' NEWS | head -1 | cut -d' ' -f2`
index bf7df237abe45f7be6d13eaeed2f0ffdc772dac6..eeace975b70c4f5505d7e0abec8720a9c7701a40 100644 (file)
@@ -1,6 +1,7 @@
 dnl Autoconf configuration for kstart.
 dnl
 dnl Written by Russ Allbery <eagle@eyrie.org>
+dnl Copyright 2015 Russ Allbery <eagle@eyrie.org>
 dnl Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
 dnl     2011, 2014
 dnl     The Board of Trustees of the Leland Stanford Junior University
@@ -18,8 +19,12 @@ AM_INIT_AUTOMAKE([1.11 check-news 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
+RRA_PROG_CC_CLANG
 AC_SYS_LARGEFILE
 AM_PROG_CC_C_O
 m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
@@ -66,7 +71,7 @@ AM_CONDITIONAL([NEED_KAFS], [test x"$rra_build_kafs" = xtrue])
 
 dnl Other portability checks.
 AC_HEADER_STDBOOL
-AC_CHECK_HEADERS([strings.h sys/bitypes.h sys/time.h syslog.h])
+AC_CHECK_HEADERS([sys/bitypes.h sys/select.h sys/time.h syslog.h])
 AC_CHECK_DECLS([snprintf, vsnprintf])
 RRA_C_C99_VAMACROS
 RRA_C_GNU_VAMACROS
@@ -79,7 +84,12 @@ AC_CHECK_TYPES([ssize_t], [], [],
     [#include <sys/types.h>])
 RRA_FUNC_SNPRINTF
 AC_CHECK_FUNCS([setrlimit setsid])
-AC_REPLACE_FUNCS([asprintf daemon mkstemp setenv strlcat strlcpy strndup])
+AC_REPLACE_FUNCS([asprintf daemon mkstemp reallocarray setenv strlcat strlcpy])
+AC_REPLACE_FUNCS([strndup])
+
+dnl Enable appropriate warnings.
+AM_CONDITIONAL([WARNINGS_GCC], [test x"$GCC" = xyes && test x"$CLANG" != xyes])
+AM_CONDITIONAL([WARNINGS_CLANG], [test x"$CLANG" = xyes])
 
 dnl Create the tests/data directory.
 AC_CONFIG_COMMANDS([tests/data/.placeholder], [touch tests/data/.placeholder])
index affc80395f8a5e8bc3aa2390b47fe98757f8bff1..586ff4556b756b0433a745ba4df6f712cb62369f 100644 (file)
@@ -348,7 +348,10 @@ run_framework(krb5_context ctx, struct config *config)
      * since otherwise we wouldn't be able to wait for the child process.
      */
     if (config->background)
-        daemon(0, 0);
+        if (daemon(0, 0) < 0) {
+            syswarn("cannot background");
+            exit_cleanup(ctx, config, 1);
+        }
 
     /* Write out the PID file. */
     if (config->pidfile != NULL)
index 029f9a19b1cd0665f6648babfa1dc5129a7c01e2..4285002a7d27e411fe1acca0c608ad02a40dac56 100644 (file)
--- a/k5start.c
+++ b/k5start.c
@@ -31,7 +31,6 @@
 #include <time.h>
 
 #include <internal.h>
-#include <util/concat.h>
 #include <util/macros.h>
 #include <util/messages.h>
 #include <util/messages-krb5.h>
@@ -179,8 +178,7 @@ authenticate(krb5_context ctx, struct config *config,
         int fd;
         char *tmp;
 
-        if (xasprintf(&tmp, "%s_XXXXXX", config->cache) < 0)
-            die("cannot format ticket cache name");
+        xasprintf(&tmp, "%s_XXXXXX", config->cache);
         fd = mkstemp(tmp);
         if (fd < 0) {
             syswarn("cannot create temporary ticket cache file");
@@ -232,7 +230,11 @@ authenticate(krb5_context ctx, struct config *config,
 
         if (!private->quiet)
             printf("Password: ");
-        fgets(buffer, sizeof(buffer), stdin);
+        if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
+            syswarn("cannot read password");
+            code = KRB5_LIBOS_CANTREADPWD;
+            goto done;
+        }
         p = strchr(buffer, '\n');
         if (p != NULL)
             *p = '\0';
@@ -503,7 +505,7 @@ main(int argc, char *argv[])
         argc--;
         argv++;
     }
-    if (argc >= 1)  
+    if (argv[0] != NULL)
         config.command = argv;
 
     /* If -x was given, we still want to exit on initial auth failure. */
@@ -574,15 +576,13 @@ main(int argc, char *argv[])
         int fd;
         char *tmp, *cache;
 
-        if (xasprintf(&tmp, "/tmp/krb5cc_%d_XXXXXX", (int) getuid()) < 0)
-            die("cannot format ticket cache name");
+        xasprintf(&tmp, "/tmp/krb5cc_%d_XXXXXX", (int) getuid());
         fd = mkstemp(tmp);
         if (fd < 0)
             sysdie("cannot create ticket cache file");
         if (fchmod(fd, 0600) < 0)
             sysdie("cannot chmod ticket cache file");
-        if (xasprintf(&cache, "FILE:%s", tmp) < 0)
-            die("cannot format ticket cache name");
+        xasprintf(&cache, "FILE:%s", tmp);
         free(tmp);
         config.cache = cache;
         config.clean_cache = true;
@@ -620,8 +620,7 @@ main(int argc, char *argv[])
      * instance onto the end of the username.
      */
     if (inst != NULL)
-        if (xasprintf(&principal, "%s/%s", principal, inst) < 0)
-            die("cannot format principal name");
+        xasprintf(&principal, "%s/%s", principal, inst);
     code = krb5_parse_name(ctx, principal, &config.client);
     if (code != 0)
         die_krb5(ctx, code, "error parsing %s", principal);
@@ -661,8 +660,7 @@ main(int argc, char *argv[])
         sname = "krbtgt";
     if (sinst == NULL)
         sinst = srealm;
-    if (xasprintf(&private.service, "%s/%s@%s", sname, sinst, srealm) < 0)
-        die("cannot format service principal name");
+    xasprintf(&private.service, "%s/%s@%s", sname, sinst, srealm);
     code = krb5_build_principal(ctx, &private.ksprinc, strlen(srealm),
                                 srealm, sname, sinst, (const char *) NULL);
     if (code != 0)
index 250665541b023312a6cf569406548bbde7f40cef..ae266497f8fa521369f11420d501037d1b69b3cf 100644 (file)
@@ -77,7 +77,7 @@ k_syscall(long call, long param1, long param2, long param3, long param4,
     int fd, code, oerrno, callnum;
 
 #ifdef _ILP32
-    struct afssysargs32 syscall_data;
+    struct afssysargs syscall_data;
 
     syscall_data.syscall = call;
     syscall_data.param1 = param1;
@@ -86,9 +86,9 @@ k_syscall(long call, long param1, long param2, long param3, long param4,
     syscall_data.param4 = param4;
     syscall_data.param5 = 0;
     syscall_data.param6 = 0;
-    callnum = _IOW('C', 2, struct afssysargs32);
+    callnum = _IOW('C', 2, struct afssysargs);
 #else
-    struct afssysargs syscall_data;
+    struct afssysargs64 syscall_data;
 
     syscall_data.syscall = call;
     syscall_data.param1 = param1;
@@ -97,7 +97,7 @@ k_syscall(long call, long param1, long param2, long param3, long param4,
     syscall_data.param4 = param4;
     syscall_data.param5 = 0;
     syscall_data.param6 = 0;
-    callnum = _IOW('C', 1, struct afssysargs);
+    callnum = _IOW('C', 1, struct afssysargs64);
 #endif
 
     fd = open("/dev/afs", O_RDWR);
index 4a553415e07b542890faaaf58ba2a62bd242ee8c..161a5a17b325e138a4c02b2844190341dcedef7c 100644 (file)
--- a/krenew.c
+++ b/krenew.c
@@ -24,7 +24,6 @@
 #include <time.h>
 
 #include <internal.h>
-#include <util/concat.h>
 #include <util/macros.h>
 #include <util/messages.h>
 #include <util/messages-krb5.h>
@@ -100,8 +99,7 @@ copy_cache(krb5_context ctx, krb5_ccache *ccache)
     char *name;
     int fd;
 
-    if (xasprintf(&name, "/tmp/krb5cc_%d_XXXXXX", (int) getuid()) < 0)
-        die("cannot format ticket cache name");
+    xasprintf(&name, "/tmp/krb5cc_%d_XXXXXX", (int) getuid());
     fd = mkstemp(name);
     if (fd < 0)
         sysdie("cannot create ticket cache file");
diff --git a/m4/clang.m4 b/m4/clang.m4
new file mode 100644 (file)
index 0000000..0659d82
--- /dev/null
@@ -0,0 +1,26 @@
+dnl Determine whether the current compiler is Clang.
+dnl
+dnl If the current compiler is Clang, set the shell variable CLANG to yes.
+dnl
+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 Copyright 2015 Russ Allbery <eagle@eyrie.org>
+dnl
+dnl This file is free software; the authors give unlimited permission to copy
+dnl and/or distribute it, with or without modifications, as long as this
+dnl notice is preserved.
+
+dnl Source used by RRA_PROG_CC_CLANG.
+AC_DEFUN([_RRA_PROG_CC_CLANG_SOURCE], [[
+#if ! __clang__
+#error
+#endif
+]])
+
+AC_DEFUN([RRA_PROG_CC_CLANG],
+[AC_CACHE_CHECK([if the compiler is Clang], [rra_cv_prog_cc_clang],
+    [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_RRA_PROG_CC_CLANG_SOURCE])],
+        [rra_cv_prog_cc_clang=yes],
+        [rra_cv_prog_cc_clang=no])])
+ AS_IF([test x"$rra_cv_prog_cc_clang" = xyes], [CLANG=yes])])
index e6578ab2421540b0090719926decdd75cfdc487c..6c604974e0795af756c38930fbb19832be46f6af 100644 (file)
@@ -170,7 +170,7 @@ AC_DEFUN([RRA_LIB_KAFS],
  AC_CHECK_HEADERS([sys/ioccom.h])
  AS_IF([test x"$rra_libkafs" != xfalse],
     [_RRA_LIB_KAFS_PATHS
-     AS_IF([test x"$rra_use_kerberos" = xtrue],
+     AS_IF([test x"$rra_use_KRB5" = xtrue],
          [RRA_LIB_KRB5_SWITCH])
      RRA_LIB_KAFS_SWITCH
      AC_CHECK_LIB([kafs], [k_hasafs],
@@ -185,7 +185,7 @@ AC_DEFUN([RRA_LIB_KAFS],
      AC_CHECK_FUNCS([k_pioctl])
      AC_REPLACE_FUNCS([k_haspag])
      RRA_LIB_KAFS_RESTORE
-     AS_IF([test x"$rra_use_kerberos" = xtrue],
+     AS_IF([test x"$rra_use_KRB5" = xtrue],
          [RRA_LIB_KRB5_RESTORE])])
 
  dnl If we found a libkafs, we have k_hasafs.  Set the appropriate
diff --git a/m4/krb5-config.m4 b/m4/krb5-config.m4
new file mode 100644 (file)
index 0000000..c69c4f3
--- /dev/null
@@ -0,0 +1,101 @@
+dnl Use krb5-config to get link paths for Kerberos libraries.
+dnl
+dnl Provides one macro, RRA_KRB5_CONFIG, which attempts to get compiler and
+dnl linker flags for a library via krb5-config and sets the appropriate shell
+dnl variables.  Defines the Autoconf variable PATH_KRB5_CONFIG, which can be
+dnl used to find the default path to krb5-config.
+dnl
+dnl Depends on RRA_ENABLE_REDUCED_DEPENDS.
+dnl
+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 2011, 2012
+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
+dnl and/or distribute it, with or without modifications, as long as this
+dnl notice is preserved.
+
+dnl Check for krb5-config in the user's path and set PATH_KRB5_CONFIG.  This
+dnl is moved into a separate macro so that it can be loaded via AC_REQUIRE,
+dnl meaning it will only be run once even if we link with multiple krb5-config
+dnl libraries.
+AC_DEFUN([_RRA_KRB5_CONFIG_PATH],
+[AC_ARG_VAR([PATH_KRB5_CONFIG], [Path to krb5-config])
+ AC_PATH_PROG([PATH_KRB5_CONFIG], [krb5-config], [],
+    [${PATH}:/usr/kerberos/bin])])
+
+dnl Check whether the --deps flag is supported by krb5-config.  Takes the path
+dnl to krb5-config to use.  Note that this path is not embedded in the cache
+dnl variable, so this macro implicitly assumes that we will always use the
+dnl same krb5-config program.
+AC_DEFUN([_RRA_KRB5_CONFIG_DEPS],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ AC_CACHE_CHECK([for --deps support in krb5-config],
+    [rra_cv_krb5_config_deps],
+    [AS_IF(["$1" 2>&1 | grep deps >/dev/null 2>&1],
+        [rra_cv_krb5_config_deps=yes],
+        [rra_cv_krb5_config_deps=no])])])
+
+dnl Obtain the library flags for a particular library using krb5-config.
+dnl Takes the path to the krb5-config program to use, the argument to
+dnl krb5-config to use, and the variable prefix under which to store the
+dnl library flags.
+AC_DEFUN([_RRA_KRB5_CONFIG_LIBS],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
+ _RRA_KRB5_CONFIG_DEPS([$1])
+ AS_IF([test x"$rra_reduced_depends" = xfalse \
+        && test x"$rra_cv_krb5_config_deps" = xyes],
+    [$3[]_LIBS=`"$1" --deps --libs $2 2>/dev/null`],
+    [$3[]_LIBS=`"$1" --libs $2 2>/dev/null`])])
+
+dnl Attempt to find the flags for a library using krb5-config.  Takes the
+dnl following arguments (in order):
+dnl
+dnl 1. The root directory for the library in question, generally from an
+dnl    Autoconf --with flag.  Used by preference as the path to krb5-config.
+dnl
+dnl 2. The argument to krb5-config to retrieve flags for this particular
+dnl    library.
+dnl
+dnl 3. The variable prefix to use when setting CPPFLAGS and LIBS variables
+dnl    based on the result of krb5-config.
+dnl
+dnl 4. Further actions to take if krb5-config was found and supported that
+dnl    library type.
+dnl
+dnl 5. Further actions to take if krb5-config could not be used to get flags
+dnl    for that library type.
+dnl
+dnl Special-case a krb5-config argument of krb5 and run krb5-config without an
+dnl argument if that option was requested and not supported.  Old versions of
+dnl krb5-config didn't take an argument to specify the library type, but
+dnl always returned the flags for libkrb5.
+AC_DEFUN([RRA_KRB5_CONFIG],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ rra_krb5_config_$3=
+ rra_krb5_config_$3[]_ok=
+ AS_IF([test x"$1" != x && test -x "$1/bin/krb5-config"],
+    [rra_krb5_config_$3="$1/bin/krb5-config"],
+    [rra_krb5_config_$3="$PATH_KRB5_CONFIG"])
+ AS_IF([test x"$rra_krb5_config_$3" != x && test -x "$rra_krb5_config_$3"],
+    [AC_CACHE_CHECK([for $2 support in krb5-config], [rra_cv_lib_$3[]_config],
+         [AS_IF(["$rra_krb5_config_$3" 2>&1 | grep $2 >/dev/null 2>&1],
+             [rra_cv_lib_$3[]_config=yes],
+             [rra_cv_lib_$3[]_config=no])])
+     AS_IF([test "$rra_cv_lib_$3[]_config" = yes],
+        [$3[]_CPPFLAGS=`"$rra_krb5_config_$3" --cflags $2 2>/dev/null`
+         _RRA_KRB5_CONFIG_LIBS([$rra_krb5_config_$3], [$2], [$3])
+         rra_krb5_config_$3[]_ok=yes],
+        [AS_IF([test x"$2" = xkrb5],
+            [$3[]_CPPFLAGS=`"$rra_krb5_config_$3" --cflags 2>/dev/null`
+             $3[]_LIBS=`"$rra_krb5_config_$3" --libs $2 2>/dev/null`
+             rra_krb5_config_$3[]_ok=yes])])])
+ AS_IF([test x"$rra_krb5_config_$3[]_ok" = xyes],
+    [$3[]_CPPFLAGS=`echo "$$3[]_CPPFLAGS" | sed 's%-I/usr/include %%'`
+     $3[]_CPPFLAGS=`echo "$$3[]_CPPFLAGS" | sed 's%-I/usr/include$%%'`
+     $4],
+    [$5])])
index d4db9333200374d88225a9a1dcc33bdbbd900951..683e30b3bde2d84795f2c139cec3872153562cc1 100644 (file)
@@ -11,23 +11,33 @@ dnl KRB5_CPPFLAGS, KRB5_LDFLAGS, and KRB5_LIBS.  Also provides
 dnl RRA_LIB_KRB5_SWITCH to set CPPFLAGS, LDFLAGS, and LIBS to include the
 dnl Kerberos libraries, saving the current values first, and
 dnl RRA_LIB_KRB5_RESTORE to restore those settings to before the last
-dnl RRA_LIB_KRB5_SWITCH.  HAVE_KERBEROS will always be defined if RRA_LIB_KRB5
-dnl is used.
+dnl RRA_LIB_KRB5_SWITCH.  HAVE_KRB5 will always be defined if RRA_LIB_KRB5 is
+dnl used.
 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.  This macro will still always set the substitution
-dnl variables, but they'll be empty unless --with-krb5 is given.  Also,
-dnl HAVE_KERBEROS will be defined if --with-krb5 is given and
-dnl $rra_use_kerberos will be set to "true".
+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
+dnl given.  Otherwise, they'll be probed for but will not be required.
+dnl Defines HAVE_KRB5 and sets rra_use_KRB5 to true if the libraries are
+dnl found.  The substitution variables will always be set, but they will be
+dnl empty unless Kerberos libraries are found and the user did not disable
+dnl Kerberos support.
 dnl
 dnl Sets the Automake conditional KRB5_USES_COM_ERR saying whether we use
 dnl com_err, since if we're also linking with AFS libraries, we may have to
 dnl change library ordering in that case.
 dnl
-dnl Depends on RRA_ENABLE_REDUCED_DEPENDS and RRA_SET_LDFLAGS.
+dnl Depends on RRA_KRB5_CONFIG, RRA_ENABLE_REDUCED_DEPENDS, and
+dnl RRA_SET_LDFLAGS.
 dnl
 dnl Also provides RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS, which checks
 dnl whether krb5_get_init_creds_opt_free takes one argument or two.  Defines
@@ -40,17 +50,22 @@ 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
+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
 dnl and/or distribute it, with or without modifications, as long as this
 dnl notice is preserved.
 
+dnl Ignore Automake conditionals if not using Automake.
+m4_define_default([AM_CONDITIONAL], [:])
+
 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
@@ -87,6 +102,36 @@ AC_DEFUN([_RRA_LIB_KRB5_PATHS],
         [AS_IF([test x"$rra_krb5_root" != x/usr],
             [KRB5_CPPFLAGS="-I${rra_krb5_root}/include"])])])])
 
+dnl Check for a header using a file existence check rather than using
+dnl AC_CHECK_HEADERS.  This is used if there were arguments to configure
+dnl specifying the Kerberos header path, since we may have one header in the
+dnl default include path and another under our explicitly-configured Kerberos
+dnl location.
+AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER],
+[AC_MSG_CHECKING([for $1])
+ AS_IF([test -f "${rra_krb5_incroot}/$1"],
+    [AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_$1]), [1],
+        [Define to 1 if you have the <$1> header file.])
+     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.
@@ -96,7 +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"
- AC_CHECK_HEADERS([krb5.h 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], [],
@@ -108,8 +153,10 @@ AC_DEFUN([_RRA_LIB_KRB5_REDUCED],
                      [RRA_INCLUDES_KRB5])],
                  [AC_CHECK_LIB([com_err], [com_err],
                      [KRB5_LIBS="$KRB5_LIBS -lcom_err"],
-                     [AC_MSG_ERROR([cannot find usable com_err library])])
-                  AC_CHECK_HEADERS([et/com_err.h])])])])])
+                     [AS_IF([test x"$1" = xtrue],
+                         [AC_MSG_ERROR([cannot find usable com_err library])],
+                         [KRB5_LIBS=""])])
+                  _RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])])
  RRA_LIB_KRB5_RESTORE])
 
 dnl Does the appropriate library checks for Kerberos linkage when we don't
@@ -126,7 +173,7 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL],
     [AC_CHECK_LIB([nsl], [socket], [LIBS="-lnsl -lsocket $LIBS"], [],
         [-lsocket])])
  AC_SEARCH_LIBS([crypt], [crypt])
- AC_SEARCH_LIBS([rk_simple_execve], [roken])
+ AC_SEARCH_LIBS([roken_concat], [roken])
  rra_krb5_extra="$LIBS"
  LIBS="$rra_krb5_save_LIBS"
  AC_CHECK_LIB([krb5], [krb5_init_context],
@@ -156,7 +203,7 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL],
         [$rra_krb5_extra])],
     [-lasn1 -lcom_err -lcrypto $rra_krb5_extra])
  LIBS="$KRB5_LIBS $LIBS"
- AC_CHECK_HEADERS([krb5.h 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], [],
@@ -164,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
@@ -185,45 +232,37 @@ dnl Determine Kerberos compiler and linker flags from krb5-config.  Does the
 dnl additional probing we need to do to uncover error handling features, and
 dnl falls back on the manual checks.
 AC_DEFUN([_RRA_LIB_KRB5_CONFIG],
-[AC_ARG_VAR([PATH_KRB5_CONFIG], [Path to krb5-config])
- AS_IF([test x"$rra_krb5_root" != x && test -z "$PATH_KRB5_CONFIG"],
-     [AS_IF([test -x "${rra_krb5_root}/bin/krb5-config"],
-         [PATH_KRB5_CONFIG="${rra_krb5_root}/bin/krb5-config"])],
-     [AC_PATH_PROG([PATH_KRB5_CONFIG], [krb5-config], [],
-         [${PATH}:/usr/kerberos/bin])])
- AS_IF([test x"$PATH_KRB5_CONFIG" != x && test -x "$PATH_KRB5_CONFIG"],
-     [AC_CACHE_CHECK([for krb5 support in krb5-config],
-         [rra_cv_lib_krb5_config],
-         [AS_IF(["$PATH_KRB5_CONFIG" 2>&1 | grep krb5 >/dev/null 2>&1],
-             [rra_cv_lib_krb5_config=yes],
-             [rra_cv_lib_krb5_config=no])])
-      AS_IF([test x"$rra_cv_lib_krb5_config" = xyes],
-          [KRB5_CPPFLAGS=`"$PATH_KRB5_CONFIG" --cflags krb5 2>/dev/null`
-           KRB5_LIBS=`"$PATH_KRB5_CONFIG" --libs krb5 2>/dev/null`],
-          [KRB5_CPPFLAGS=`"$PATH_KRB5_CONFIG" --cflags 2>/dev/null`
-           KRB5_LIBS=`"$PATH_KRB5_CONFIG" --libs 2>/dev/null`])
-      KRB5_CPPFLAGS=`echo "$KRB5_CPPFLAGS" | sed 's%-I/usr/include %%'`
-      KRB5_CPPFLAGS=`echo "$KRB5_CPPFLAGS" | sed 's%-I/usr/include$%%'`
-      _RRA_LIB_KRB5_CHECK([$1])
-      RRA_LIB_KRB5_SWITCH
-      AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
-      AC_CHECK_FUNCS([krb5_get_error_message],
-          [AC_CHECK_FUNCS([krb5_free_error_message])],
-          [AC_CHECK_FUNCS([krb5_get_error_string], [],
-              [AC_CHECK_FUNCS([krb5_get_err_txt], [],
-                  [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_RESTORE],
-     [_RRA_LIB_KRB5_PATHS
-      _RRA_LIB_KRB5_MANUAL([$1])])])
+[RRA_KRB5_CONFIG([${rra_krb5_root}], [krb5], [KRB5],
+    [_RRA_LIB_KRB5_CHECK([$1])
+     RRA_LIB_KRB5_SWITCH
+     _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], [],
+             [AC_CHECK_FUNCS([krb5_get_err_txt], [],
+                 [AC_CHECK_FUNCS([krb5_svc_get_msg],
+                     [AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [],
+                         [RRA_INCLUDES_KRB5])],
+                     [_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])])
+     RRA_LIB_KRB5_RESTORE],
+    [_RRA_LIB_KRB5_PATHS
+     _RRA_LIB_KRB5_MANUAL([$1])])])
 
 dnl The core of the library checking, shared between RRA_LIB_KRB5 and
 dnl RRA_LIB_KRB5_OPTIONAL.  The single argument, if "true", says to fail if
-dnl Kerberos could not be found.
+dnl Kerberos could not be found.  Set up rra_krb5_incroot for later header
+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],
+        [rra_krb5_incroot="${rra_krb5_root}/include"])])
  AS_IF([test x"$rra_reduced_depends" = xtrue],
     [_RRA_LIB_KRB5_PATHS
      _RRA_LIB_KRB5_REDUCED([$1])],
@@ -232,18 +271,17 @@ AC_DEFUN([_RRA_LIB_KRB5_INTERNAL],
         [_RRA_LIB_KRB5_PATHS
          _RRA_LIB_KRB5_MANUAL([$1])])])
  rra_krb5_uses_com_err=false
- AS_CASE([$LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true])
- AM_CONDITIONAL([KRB5_USES_COM_ERR], [test x"$rra_krb5_uses_com_err" = xtrue])])
+ 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])
+ 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],
 [rra_krb5_root=
  rra_krb5_libdir=
  rra_krb5_includedir=
- rra_use_kerberos=true
- AC_SUBST([KRB5_CPPFLAGS])
- AC_SUBST([KRB5_LDFLAGS])
- AC_SUBST([KRB5_LIBS])
+ rra_use_KRB5=true
 
  AC_ARG_WITH([krb5],
     [AS_HELP_STRING([--with-krb5=DIR],
@@ -261,25 +299,22 @@ AC_DEFUN([RRA_LIB_KRB5],
     [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
         [rra_krb5_libdir="$withval"])])
  _RRA_LIB_KRB5_INTERNAL([true])
- AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])
+ AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])
 
 dnl The main macro for packages with optional Kerberos support.
 AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
 [rra_krb5_root=
  rra_krb5_libdir=
  rra_krb5_includedir=
- rra_use_kerberos=
- AC_SUBST([KRB5_CPPFLAGS])
- AC_SUBST([KRB5_LDFLAGS])
- AC_SUBST([KRB5_LIBS])
+ rra_use_KRB5=
 
  AC_ARG_WITH([krb5],
     [AS_HELP_STRING([--with-krb5@<:@=DIR@:>@],
         [Location of Kerberos headers and libraries])],
     [AS_IF([test x"$withval" = xno],
-        [rra_use_kerberos=false],
+        [rra_use_KRB5=false],
         [AS_IF([test x"$withval" != xyes], [rra_krb5_root="$withval"])
-         rra_use_kerberos=true])])
+         rra_use_KRB5=true])])
  AC_ARG_WITH([krb5-include],
     [AS_HELP_STRING([--with-krb5-include=DIR],
         [Location of Kerberos headers])],
@@ -291,13 +326,14 @@ AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
     [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
         [rra_krb5_libdir="$withval"])])
 
- AS_IF([test x"$rra_use_kerberos" != xfalse],
-     [AS_IF([test x"$rra_use_kerberos" = xtrue],
+ AS_IF([test x"$rra_use_KRB5" != xfalse],
+     [AS_IF([test x"$rra_use_KRB5" = xtrue],
          [_RRA_LIB_KRB5_INTERNAL([true])],
          [_RRA_LIB_KRB5_INTERNAL([false])])],
      [AM_CONDITIONAL([KRB5_USES_COM_ERR], [false])])
  AS_IF([test x"$KRB5_LIBS" != x],
-    [AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])])
+    [rra_use_KRB5=true
+     AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])])
 
 dnl Source used by RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS.
 AC_DEFUN([_RRA_FUNC_KRB5_OPT_FREE_ARGS_SOURCE], [RRA_INCLUDES_KRB5] [[
index 7bdfd0d547c25b0b405779e5f3285d7a22a8dc0f..9693842d6c82cf773ae45010c279af35cce4e42e 100644 (file)
  */
 
 #include <config.h>
+#include <portable/macros.h>
 #include <portable/system.h>
 
+#include <errno.h>
+
 /*
  * If we're running the test suite, rename the functions to avoid conflicts
  * 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
 
+
 int
 asprintf(char **strp, const char *fmt, ...)
 {
@@ -45,11 +52,12 @@ asprintf(char **strp, const char *fmt, ...)
     return status;
 }
 
+
 int
 vasprintf(char **strp, const char *fmt, va_list args)
 {
     va_list args_copy;
-    int status, needed;
+    int status, needed, oerrno;
 
     va_copy(args_copy, args);
     needed = vsnprintf(NULL, 0, fmt, args_copy);
@@ -65,8 +73,10 @@ vasprintf(char **strp, const char *fmt, va_list args)
     if (status >= 0)
         return status;
     else {
+        oerrno = errno;
         free(*strp);
         *strp = NULL;
+        errno = oerrno;
         return status;
     }
 }
index f2ac91765a35139ea702c69d9c9fdfe6456e93d2..890bc0cc3ea8332bd1184d1765992071730fff57 100644 (file)
  * work.
  */
 
-/* Prototype to avoid gcc warnings. */
-int portable_dummy(void);
+#include <portable/macros.h>
+
+/* Prototype to avoid gcc warnings and set visibility. */
+int portable_dummy(void) __attribute__((__visibility__("hidden")));
 
 int
 portable_dummy(void)
index 78a188d68484a088ec512cedd108ef0f83a31469..b95a29ecf7382f00fb3841a82c50d5d531ef47d8 100644 (file)
@@ -63,7 +63,7 @@ k_haspag(void)
      * system call.  Fall back on analyzing the groups.
      */
     ngroups = getgroups(0, NULL);
-    groups = malloc(sizeof(*groups) * ngroups);
+    groups = calloc(ngroups, sizeof(*groups));
     if (groups == NULL)
         return 0;
     ngroups = getgroups(ngroups, groups);
index df18a8032ab3c572f422c9890acd34d69fa49976..03b0739b6fe26d4f476bc1c7623ea13b9b374078 100644 (file)
@@ -16,7 +16,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, 2008, 2010
+ * Copyright 2006, 2007, 2008, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -42,7 +42,7 @@
 #define PORTABLE_KAFS_H 1
 
 #include <config.h>
-#ifdef HAVE_KERBEROS
+#ifdef HAVE_KRB5
 # include <portable/krb5.h>
 #endif
 #include <portable/macros.h>
@@ -64,8 +64,22 @@ BEGIN_DECLS
 #  include <kafs.h>
 # elif HAVE_KOPENAFS_H
 #  include <kopenafs.h>
+# else
+struct ViceIoctl {
+    void *in, *out;
+    short in_size;
+    short out_size;
+};
+int k_hasafs(void);
+int k_pioctl(char *, struct ViceIoctl *, void *, int);
+int k_setpag(void);
+int k_unlog(void);
 # endif
-# ifndef HAVE_K_HASPAG
+# ifdef HAVE_K_HASPAG
+#  if !defined(HAVE_KAFS_H) && !defined(HAVE_KOPENAFS_H)
+int k_haspag(void);
+#  endif
+# else
 int k_haspag(void) __attribute__((__visibility__("hidden")));
 # endif
 
@@ -75,10 +89,12 @@ int k_haspag(void) __attribute__((__visibility__("hidden")));
 #  include <afs/afssyscalls.h>
 # else
 int lsetpag(void);
+int lpioctl(char *, int, void *, int);
 # endif
-# define k_hasafs() (1)
-# define k_setpag() lsetpag()
-# define k_unlog()  (errno = ENOSYS, -1)
+# define k_hasafs()           (1)
+# define k_pioctl(p, c, a, f) lpioctl((p), (c), (a), (f))
+# define k_setpag()           lsetpag()
+# define k_unlog()            (errno = ENOSYS, -1)
 
 int k_haspag(void) __attribute__((__visibility__("hidden")));
 
@@ -107,10 +123,11 @@ int k_unlog(void);
 /* We have no kafs implementation available. */
 #else
 # undef HAVE_KAFS
-# define k_hasafs() (0)
-# define k_haspag() (0)
-# define k_setpag() (errno = ENOSYS, -1)
-# define k_unlog()  (errno = ENOSYS, -1)
+# define k_hasafs()           (0)
+# define k_haspag()           (0)
+# define k_pioctl(p, c, a, f) (errno = ENOSYS, -1)
+# define k_setpag()           (errno = ENOSYS, -1)
+# define k_unlog()            (errno = ENOSYS, -1)
 #endif
 
 END_DECLS
index f4d304bf7a4abf51c870909048bd05a2d4639f7d..63ba0dd6724c41788479390c7672ed6eed53ce24 100644 (file)
@@ -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
@@ -52,7 +55,9 @@ static const char error_unknown[] = "unknown error";
 #ifndef HAVE_KRB5_CC_GET_FULL_NAME
 /*
  * Given a Kerberos ticket cache, return the full name (TYPE:name) in
- * newly-allocated memory.  Returns an error code.
+ * newly-allocated memory.  Returns an error code.  Avoid asprintf and
+ * snprintf here in case someone wants to use this code without the rest of
+ * the portability layer.
  */
 krb5_error_code
 krb5_cc_get_full_name(krb5_context ctx, krb5_ccache ccache, char **out)
index a556bbc09f52edaa64705a34f4c031eb4ee210f6..f2a85cc6679bc0d5bb27d5434929eb1196eefc90 100644 (file)
 #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
@@ -138,4 +140,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 */
index 9ca20aca904357b8e3f0189765b445064ddfcf27..d4cc2ccee0dcbb743068119d9589c4ceebe57876 100644 (file)
 # endif
 #endif
 
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC.  Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)) \
+    && !defined(__clang__)
+#  define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does.  For them, suppress warnings about unknown
+ * attributes on declarations.  This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
 /*
  * BEGIN_DECLS is used at the beginning of declarations so that C++
  * compilers don't mangle their names.  END_DECLS is used at the end.
index 2cbfe081249ff735e7dd890f6492ca7d862179ac..7c733a48cf4cbd14e1118e831a996c3320945efc 100644 (file)
 
 #include <errno.h>
 #include <fcntl.h>
-#include <sys/time.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
diff --git a/portable/reallocarray.c b/portable/reallocarray.c
new file mode 100644 (file)
index 0000000..e9404e9
--- /dev/null
@@ -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);
+}
index 5462547bfa7306cb4e9955ef79e97fbc12ee71d5..f1f6db4cd9649a9b91ab55a9e5b320dd2c877f26 100644 (file)
@@ -26,6 +26,7 @@
  * the system version.
  */
 #if TESTING
+# undef setenv
 # define setenv test_setenv
 int test_setenv(const char *, const char *, int);
 #endif
@@ -34,30 +35,22 @@ int
 setenv(const char *name, const char *value, int overwrite)
 {
     char *envstring;
-    size_t size;
 
+    /* Do nothing if not overwriting and the variable is already set. */
     if (!overwrite && getenv(name) != NULL)
         return 0;
 
-    /*
-     * Allocate memory for the environment string.  We intentionally don't use
-     * concat here, or the xmalloc family of allocation routines, since the
-     * intention is to provide a replacement for the standard library function
-     * which sets errno and returns in the event of a memory allocation
-     * failure.
-     */
-    size = strlen(name) + 1 + strlen(value) + 1;
-    envstring = malloc(size);
-    if (envstring == NULL)
-        return -1;
-
     /*
      * Build the environment string and add it to the environment using
      * putenv.  Systems without putenv lose, but XPG4 requires it.
+     *
+     * We intentionally don't use the xmalloc family of allocation routines
+     * here, since the intention is to provide a replacement for the standard
+     * library function that sets errno and returns in the event of a memory
+     * allocation failure.
      */
-    strlcpy(envstring, name, size);
-    strlcat(envstring, "=", size);
-    strlcat(envstring, value, size);
+    if (asprintf(&envstring, "%s=%s", name, value) < 0)
+        return -1;
     return putenv(envstring);
 
     /*
index 225455bed98c715785dcba41ab3da421c71b5162..9818acdd46a4f420b0734165e09dc1ee3ddd8694 100644 (file)
@@ -2,8 +2,9 @@
  * Replacement for a missing snprintf or vsnprintf.
  *
  * The following implementation of snprintf was taken mostly verbatim from
- * <http://www.fiction.net/~blong/programs/>; it is the version of snprintf
- * used in Mutt.
+ * <http://www.fiction.net/blong/programs/>; it is the version of snprintf
+ * used in Mutt.  A possibly newer version is used in wget, found at
+ * <https://github.com/wertarbyte/wget/blob/master/src/snprintf.c>.
  *
  * Please do not reformat or otherwise change this file more than necessary so
  * that later merges with the original source are easy.  Bug fixes and
@@ -18,6 +19,8 @@
  * conflicts with the system version.
  */
 #if TESTING
+# undef snprintf
+# undef vsnprintf
 # define snprintf test_snprintf
 # define vsnprintf test_vsnprintf
 #endif
@@ -432,7 +435,7 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
        break;
       case 'w':
        /* not supported yet, treat as next char */
-       ch = *format++;
+       format++;
        break;
       default:
        /* Unknown, skip */
@@ -695,7 +698,7 @@ static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
          /* For each leading 0 in fractional part, print one more
             fractional digit. */
          LDOUBLE temp;
-         if (ufvalue != 0)
+         if (ufvalue > 0)
            for (temp = ufvalue; temp < 0.1; temp *= 10)
              ++max;
        }
index 9c8686ded045aa887f85c525c621baf97a6d7316..613d3f2e9e28925ce09b88e2d79847dabe44864a 100644 (file)
@@ -31,6 +31,7 @@
  * the system version.
  */
 #if TESTING
+# undef strlcat
 # define strlcat test_strlcat
 size_t test_strlcat(char *, const char *, size_t);
 #endif
index 592e3ee8957bee3aa27979fa659471bc855836b4..60fdab3f0e68b36d9c0ea4a2b5e23624f7186a86 100644 (file)
@@ -30,6 +30,7 @@
  * the system version.
  */
 #if TESTING
+# undef strlcpy
 # define strlcpy test_strlcpy
 size_t test_strlcpy(char *, const char *, size_t);
 #endif
index 317c6f9d729b98794f5bf5ebce0c38bd7c626b81..e94d944a65d05d4f1391c5fab842ee14d60d86e8 100644 (file)
@@ -33,6 +33,7 @@ char *test_strndup(const char *, size_t);
 char *
 strndup(const char *s, size_t n)
 {
+    const char *p;
     size_t length;
     char *copy;
 
@@ -40,9 +41,11 @@ strndup(const char *s, size_t n)
         errno = EINVAL;
         return NULL;
     }
-    length = strlen(s);
-    if (length > n)
-        length = 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;
     copy = malloc(length + 1);
     if (copy == NULL)
         return NULL;
index 5bb4ca623f0398610bf54da3af1fbb425a44a874..eed0b9e794f9e4fd2fe1fe89bafddf00a6e75940 100644 (file)
@@ -5,15 +5,17 @@
  * 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>
  *     #include <stdio.h>
  *     #include <stdlib.h>
- *     #include <stddef.h>
  *     #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
 #include <portable/macros.h>
 
 /* A set of standard ANSI C headers.  We don't care about pre-ANSI systems. */
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#include <limits.h>
 #include <stdarg.h>
 #include <stddef.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/types.h>
 #include <string.h>
 #if HAVE_STRINGS_H
 # include <strings.h>
 #endif
-#if HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-#if HAVE_STDINT_H
-# include <stdint.h>
-#endif
+#include <sys/types.h>
 #if HAVE_UNISTD_H
 # include <unistd.h>
 #endif
@@ -135,13 +138,16 @@ extern int daemon(int, int);
 #if !HAVE_MKSTEMP
 extern int mkstemp(char *);
 #endif
+#if !HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
 #if !HAVE_SETENV
 extern int setenv(const char *, const char *, int);
 #endif
-#if !HAVE_STRLCAT
+#if !HAVE_DECL_STRLCAT
 extern size_t strlcat(char *, const char *, size_t);
 #endif
-#if !HAVE_STRLCPY
+#if !HAVE_DECL_STRLCPY
 extern size_t strlcpy(char *, const char *, size_t);
 #endif
 #if !HAVE_STRNDUP
index af6328f10173d2f45b3014b174d4539a85e0e211..13385dd845057b1ac635b0b2c15a922a8025e2f0 100644 (file)
@@ -20,12 +20,12 @@ krenew/non-renewable
 portable/asprintf
 portable/daemon
 portable/mkstemp
+portable/reallocarray
 portable/setenv
 portable/snprintf
 portable/strlcat
 portable/strlcpy
 portable/strndup
-util/concat
 util/messages
 util/messages-krb5
 util/xmalloc
diff --git a/tests/data/perl.conf b/tests/data/perl.conf
new file mode 100644 (file)
index 0000000..a344a9a
--- /dev/null
@@ -0,0 +1,4 @@
+# Configuration for Perl tests.  -*- perl -*-
+
+# File must end with this line.
+1;
index e3f5f344e7c86f6b59eaf88c197ba7c2fbad3a5a..7b61c86382b70f22fb7e3c7cff75e8aac6e31230 100755 (executable)
@@ -1,79 +1,51 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
 #
-# Check for spelling errors in POD documentation
+# Checks all POD files in the tree for spelling errors using Test::Spelling.
 #
-# Checks all POD files in the tree for spelling problems using Pod::Spell and
-# either aspell or ispell.  aspell is preferred.  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/>.
 #
-# Copyright 2008, 2009, 2011 Russ Allbery <eagle@eyrie.org>
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2012, 2013, 2014
+#     The Board of Trustees of the Leland Stanford Junior University
 #
-# See LICENSE for licensing terms.
+# 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.
 
+use 5.006;
 use strict;
-use Test::More;
+use warnings;
 
-# Skip all spelling tests unless the maintainer environment variable is set.
-plan skip_all => 'spelling tests only run for maintainer'
-    unless $ENV{RRA_MAINTAINER_TESTS};
+use lib "$ENV{SOURCE}/tap/perl";
+
+use Test::More;
+use Test::RRA qw(skip_unless_author use_prereq);
+use Test::RRA::Automake qw(automake_setup perl_dirs);
 
-# Load required Perl modules.
-eval 'use Test::Pod 1.00';
-plan skip_all => 'Test::Pod 1.00 required for testing POD' if $@;
-eval 'use Pod::Spell';
-plan skip_all => 'Pod::Spell required to test POD spelling' if $@;
+# 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');
 
-# Locate a spell-checker.  hunspell is not currently supported due to its lack
-# of support for contractions (at least in the version in Debian).
-my @spell;
-my %options = (aspell => [ qw(-d en_US --home-dir=./ list) ],
-               ispell => [ qw(-d american -l -p /dev/null) ]);
-SEARCH: for my $program (qw/aspell ispell/) {
-    for my $dir (split ':', $ENV{PATH}) {
-        if (-x "$dir/$program") {
-            @spell = ("$dir/$program", @{ $options{$program} });
-        }
-        last SEARCH if @spell;
-    }
-}
-plan skip_all => 'aspell or ispell required to test POD spelling'
-    unless @spell;
+# Load prerequisite modules.
+use_prereq('Test::Spelling');
 
-# Prerequisites are satisfied, so we're going to do some testing.  Figure out
-# what POD files we have and from that develop our plan.
-$| = 1;
-my @pod = map {
-    my $pod = "$ENV{SOURCE}/../" . $_;
-    $pod =~ s,[^/.][^/]*/../,,g;
-    $pod;
-} qw(k5start.pod krenew.pod);
-plan tests => scalar @pod;
+# Set up Automake testing.
+automake_setup();
 
-# Finally, do the checks.
-for my $pod (@pod) {
-    my $child = open (CHILD, '-|');
-    if (not defined $child) {
-        BAIL_OUT ("cannot fork: $!");
-    } elsif ($child == 0) {
-        my $pid = open (SPELL, '|-', @spell)
-            or BAIL_OUT ("cannot run @spell: $!");
-        open (POD, '<', $pod) or BAIL_OUT ("cannot open $pod: $!");
-        my $parser = Pod::Spell->new;
-        $parser->parse_from_filehandle (\*POD, \*SPELL);
-        close POD;
-        close SPELL;
-        exit ($? >> 8);
-    } else {
-        my @words = <CHILD>;
-        close CHILD;
-      SKIP: {
-            skip "@spell failed for $pod", 1 unless $? == 0;
-            for (@words) {
-                s/^\s+//;
-                s/\s+$//;
-            }
-            is ("@words", '', $pod);
-        }
-    }
-}
+# Run the tests.
+all_pod_files_spelling_ok(perl_dirs());
index 396089ede737c26e5d94aa35ceae66b758d8ed24..53f9925cbf4496c00b6b015a48215bec2cdef89d 100755 (executable)
@@ -1,21 +1,52 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
 #
-# Test formatting of POD documentation.
+# Check all POD documents in the tree, except for any embedded Perl module
+# distribution, for POD formatting errors.
+#
+# 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 2008, 2009, 2011
+# Copyright 2012, 2013, 2014
 #     The Board of Trustees of the Leland Stanford Junior University
 #
-# See LICENSE for licensing terms.
+# 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.
 
+use 5.006;
 use strict;
+use warnings;
+
+use lib "$ENV{SOURCE}/tap/perl";
+
 use Test::More;
+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');
 
-my @podfiles = qw(k5start.pod krenew.pod);
+# Set up Automake testing.
+automake_setup();
 
-eval 'use Test::Pod 1.00';
-plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
-my $srcdir = "$ENV{SOURCE}";
-$srcdir =~ s,[^/]+/*$,,;
-chdir "$srcdir" or die "$0: cannot chdir to $srcdir: $!\n";
-all_pod_files_ok (@podfiles);
+# Run the tests.
+all_pod_files_ok(perl_dirs());
index 000d73813622c76daf9efcf266b9973e1a85e9a4..8375afd5f63ead1d000b566dc08dc38e8d4b151d 100755 (executable)
@@ -63,9 +63,9 @@ else
     fi
 fi
 
-# If aklog failed, we can't run the rest of the tests.
+# If aklog or tokens failed, we can't run the rest of the tests.
 if [ "$status" -eq 3 ] ; then
-    skip_block 3 'aklog failed'
+    skip_block 3 'aklog or tokens failed'
 else
     if sed -n '/^=== tokens .aklog./,/^===/p' basic-output \
             | grep -i 'tokens for ' > /dev/null ; then
index bbef14819b22d6b877411c28eaef35128dc67c5e..9d35c964f95bf37abbdf41c2e129708b9c66ceef 100644 (file)
@@ -7,14 +7,14 @@
  * k_setpag, then tokens, then aklog, then tokens, and then k_unlog, sending
  * the output to standard output and errors to standard error.  If either
  * k_setpag or k_unlog return failure, it reports an error to standard error
- * and exits with status 1.  If aklog fails, it exits with status 3.  If the
- * commands all finish, it exits 0.
+ * and exits with status 1.  If aklog or tokens fails, it exits with status 3.
+ * If the commands all finish, it exits 0.
  *
  * 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 2009, 2010
+ * Copyright 2009, 2010, 2012
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -67,19 +67,22 @@ main(void)
     }
     printf("=== tokens (setpag) ===\n");
     fflush(stdout);
-    system("tokens");
+    if (system("tokens") != 0)
+        exit(3);
     if (system(PATH_AKLOG) != 0)
         exit(3);
     printf("=== tokens (aklog) ===\n");
     fflush(stdout);
-    system("tokens");
+    if (system("tokens") != 0)
+        exit(3);
     if (k_unlog() != 0) {
         fprintf(stderr, "k_unlog failed: %s", strerror(errno));
         exit(1);
     }
     printf("=== tokens (unlog) ===\n");
     fflush(stdout);
-    system("tokens");
+    if (system("tokens") != 0)
+        exit(3);
 
     return 0;
 }
index c61c14aef1415c8b42892cb87568bc73080cc3dd..e556d956543213b2e2b5592d4b2af4d34876ca45 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include <config.h>
+#include <portable/macros.h>
 #include <portable/system.h>
 
 #include <tests/tap/basic.h>
index c8253bf53820acb4c05e39ee0f14283c0ab0255c..f3d473a582cac6394edc1fc96d378ef65f33c4cf 100644 (file)
 # include <sys/select.h>
 #endif
 #include <sys/stat.h>
-#include <sys/time.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
 #include <sys/wait.h>
+#include <time.h>
 
 #include <tests/tap/basic.h>
 
@@ -91,7 +94,7 @@ main(void)
     if (child < 0)
         sysbail("cannot fork");
     else if (child == 0) {
-        is_int(0, daemon(1, 1), "daemon(1, 1)");
+        is_int(0, test_daemon(1, 1), "daemon(1, 1)");
         fd = open("/dev/tty", O_RDONLY);
         ok(fd < 0, "...no tty");
         is_string(start, getcwd(dir, sizeof(dir)), "...in same directory");
@@ -110,7 +113,7 @@ main(void)
     if (child < 0)
         sysbail("cannot fork");
     else if (child == 0) {
-        is_int(0, daemon(0, 1), "daemon(0, 1)");
+        is_int(0, test_daemon(0, 1), "daemon(0, 1)");
         is_string("/", getcwd(dir, sizeof(dir)), "...now in /");
         if (chdir(start) != 0)
             sysbail("cannot chdir to %s", start);
@@ -128,7 +131,8 @@ main(void)
     if (child < 0)
         sysbail("cannot fork");
     else if (child == 0) {
-        daemon(0, 0);
+        if (test_daemon(0, 0) != 0)
+            sysbail("daemon failed");
         if (chdir(start) != 0)
             sysbail("cannot chdir to %s", start);
         ok(0, "output from child that should be hidden");
diff --git a/tests/portable/reallocarray-t.c b/tests/portable/reallocarray-t.c
new file mode 100644 (file)
index 0000000..481da58
--- /dev/null
@@ -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 (file)
index 0000000..7cd29e2
--- /dev/null
@@ -0,0 +1,2 @@
+#define TESTING 1
+#include <portable/reallocarray.c>
index 270d2e1831743b5366c66cf318ca7a38b317dddd..cc8cf00ee3f44040f5a7e619ac21dfa5c6c9be55 100644 (file)
@@ -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
index fa22cf08fcf08e5c2746e1b4b6f39a88c5e3ad3b..0f923b795e00d0c8ccfa6326757b27e5e252fcd2 100644 (file)
@@ -28,9 +28,10 @@ char *test_strndup(const char *, size_t);
 int
 main(void)
 {
+    char buffer[3];
     char *result = NULL;
 
-    plan(6);
+    plan(7);
 
     result = test_strndup("foo", 8);
     is_string("foo", result, "strndup longer than string");
@@ -44,6 +45,10 @@ main(void)
     result = test_strndup("foo", 0);
     is_string("", result, "strndup of size 0");
     free(result);
+    memcpy(buffer, "foo", 3);
+    result = test_strndup(buffer, 3);
+    is_string("foo", result, "strndup of non-nul-terminated string");
+    free(result);
     errno = 0;
     result = test_strndup(NULL, 0);
     is_string(NULL, result, "strndup of NULL");
index 50277faf4961e25fc32724dd7aa9a6d8b9757c10..42a73ea81a49a0be203c4bf1d02dbc0f3ad92363 100644 (file)
@@ -3,15 +3,19 @@
  *
  * Usage:
  *
- *      runtests [-b <build-dir>] [-s <source-dir>] <test-list>
- *      runtests -o [-b <build-dir>] [-s <source-dir>] <test>
+ *      runtests [-hv] [-b <build-dir>] [-s <source-dir>] -l <test-list>
+ *      runtests [-hv] [-b <build-dir>] [-s <source-dir>] <test> [<test> ...]
+ *      runtests -o [-h] [-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>
  * directories.  These paths can also be set with the -b and -s command-line
  * options, which will override anything set at build time.
  *
+ * If the -v option is given, or the C_TAP_VERBOSE environment variable is set,
+ * display the full output of each test as it runs rather than showing a
+ * summary of the results of each test.
+ *
  * Any bug reports, bug fixes, and improvements are very much welcome and
  * 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"),
 */
 
 /* Required for fdopen(), getopt(), and putenv(). */
-#ifndef _XOPEN_SOURCE
-# define _XOPEN_SOURCE 500
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+#  define _XOPEN_SOURCE 500
+# endif
 #endif
 
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 /* 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)
+# 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).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
 /*
  * The source and build versions of the tests directory.  This is used to set
  * the SOURCE and BUILD environment variables and find test programs, if set.
@@ -125,6 +154,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. */
@@ -136,7 +171,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 {
@@ -151,7 +187,7 @@ struct testset {
     unsigned long skipped;      /* Count of skipped tests (passed). */
     unsigned long allocated;    /* The size of the results table. */
     enum test_status *results;  /* Table of results by test number. */
-    unsigned int aborted;       /* Whether the set as aborted. */
+    unsigned int aborted;       /* Whether the set was aborted. */
     int reported;               /* Whether the results were reported. */
     int status;                 /* The exit status of the test. */
     unsigned int all_skipped;   /* Whether all tests were skipped. */
@@ -165,21 +201,27 @@ struct testlist {
 };
 
 /*
- * Usage message.  Should be used as a printf format with two arguments: the
- * path to runtests, given twice.
+ * Usage message.  Should be used as a printf format with four arguments: the
+ * path to runtests, given three times, and the usage_description.  This is
+ * split into variables to satisfy the pedantic ISO C90 limit on strings.
  */
 static const char usage_message[] = "\
-Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
-       %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+Usage: %s [-hv] [-b <build-dir>] [-s <source-dir>] <test> ...\n\
+       %s [-hv] [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
+       %s -o [-h] [-b <build-dir>] [-s <source-dir>] <test>\n\
 \n\
 Options:\n\
     -b <build-dir>      Set the build directory to <build-dir>\n\
+%s";
+static const char usage_extra[] = "\
+    -l <list>           Take the list of tests to run from <test-list>\n\
     -o                  Run a single test rather than a list of tests\n\
     -s <source-dir>     Set the source directory to <source-dir>\n\
+    -v                  Show the full output of each test\n\
 \n\
-runtests normally runs each test listed in a file whose path is given as\n\
-its command-line argument.  With the -o option, it instead runs a single\n\
-test and shows its complete output.\n";
+runtests normally runs each test listed on the command line.  With the -l\n\
+option, it instead runs every test listed in a file.  With the -o option,\n\
+it instead runs a single test and shows its complete output.\n";
 
 /*
  * Header used for test output.  %s is replaced by the file name of the list
@@ -195,9 +237,59 @@ Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\
 -------------------------- -------------- ---- ----  ------------------------";
 
 /* Include the file name and line number in malloc failures. */
-#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
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros).
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+#  define __attribute__(spec)   /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC.  Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if defined(__GNUC__) && !defined(__clang__)
+#  if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+#   define __alloc_size__(spec, args...) /* empty */
+#  endif
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does.  For them, suppress warnings about unknown
+ * attributes on declarations.  This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Declare internal functions that benefit from compiler attributes. */
+static void sysdie(const char *, ...)
+    __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2)));
+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_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__));
 
 
 /*
@@ -220,6 +312,24 @@ sysdie(const char *format, ...)
 }
 
 
+/*
+ * Allocate zeroed memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_calloc(size_t n, size_t size, const char *file, int line)
+{
+    void *p;
+
+    n = (n > 0) ? n : 1;
+    size = (size > 0) ? size : 1;
+    p = calloc(n, size);
+    if (p == NULL)
+        sysdie("failed to calloc %lu bytes at %s line %d",
+               (unsigned long) size, file, line);
+    return p;
+}
+
+
 /*
  * Allocate memory, reporting a fatal error and exiting on failure.
  */
@@ -238,14 +348,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;
 }
 
@@ -269,6 +391,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.
@@ -321,36 +492,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;
@@ -377,6 +574,55 @@ 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
@@ -384,9 +630,8 @@ test_backspace(struct testset *ts)
  * 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;
 
     /*
@@ -399,12 +644,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) {
@@ -433,29 +680,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) {
-            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;
 }
 
@@ -467,13 +712,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. */
@@ -486,7 +732,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;
         }
@@ -507,13 +754,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 {
+            if (!verbose)
+                test_backspace(ts);
             puts("ABORTED (multiple plans)");
             ts->aborted = 1;
             ts->reported = 1;
@@ -532,32 +781,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;
-        }
     }
 
     /*
@@ -577,7 +817,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;
@@ -593,10 +834,13 @@ test_checkline(const char *line, struct testset *ts)
     }
     ts->current = current;
     ts->results[current - 1] = status;
-    test_backspace(ts);
-    if (isatty(STDOUT_FILENO)) {
-        outlen = printf("%lu/%lu", current, ts->count);
-        ts->length = (outlen >= 0) ? outlen : 0;
+    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) ? (unsigned int) outlen : 0;
         fflush(stdout);
     }
 }
@@ -612,7 +856,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;
@@ -752,6 +996,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)");
@@ -781,7 +1026,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;
@@ -798,12 +1043,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,
@@ -811,7 +1063,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) {
@@ -881,109 +1134,227 @@ test_fail_summary(const struct testlist *fails)
         if (first != 0)
             test_print_range(first, last, chars, 19);
         putchar('\n');
-        free(ts->file);
-        free(ts->path);
-        free(ts->results);
-        if (ts->reason != NULL)
-            free(ts->reason);
-        free(ts);
     }
 }
 
 
+/*
+ * Check whether a given file path is a valid test.  Currently, this checks
+ * whether it is executable and is a regular file.  Returns true or false.
+ */
+static int
+is_valid_test(const char *path)
+{
+    struct stat st;
+
+    if (access(path, X_OK) < 0)
+        return 0;
+    if (stat(path, &st) < 0)
+        return 0;
+    if (!S_ISREG(st.st_mode))
+        return 0;
+    return 1;
+}
+
+
 /*
  * Given the name of a test, a pointer to the testset struct, and the source
  * and build directories, find the test.  We try first relative to the current
  * directory, then in the build directory (if not NULL), then in the source
  * directory.  In each of those directories, we first try a "-t" extension and
- * then a ".t" extension.  When we find an executable program, we fill in the
- * path member of the testset struct.  If none of those paths are executable,
- * just fill in the name of the test with "-t" appended.
+ * then a ".t" extension.  When we find an executable program, we return the
+ * path to that program.  If none of those paths are executable, just fill in
+ * the name of the test as is.
  *
  * The caller is responsible for freeing the path member of the testset
  * struct.
  */
-static void
-find_test(const char *name, struct testset *ts, const char *source,
-          const char *build)
+static char *
+find_test(const char *name, const char *source, const char *build)
 {
-    char *path;
-    const char *bases[4];
-    unsigned int i;
+    char *path = NULL;
+    const char *bases[3], *suffix, *base;
+    unsigned int i, j;
+    const char *suffixes[3] = { "-t", ".t", "" };
 
+    /* Possible base directories. */
     bases[0] = ".";
     bases[1] = build;
     bases[2] = source;
-    bases[3] = NULL;
 
-    for (i = 0; i < 3; i++) {
-        if (bases[i] == NULL)
+    /* Try each suffix with each base. */
+    for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
+        suffix = suffixes[i];
+        for (j = 0; j < ARRAY_SIZE(bases); j++) {
+            base = bases[j];
+            if (base == NULL)
+                continue;
+            path = concat(base, "/", name, suffix, (const char *) 0);
+            if (is_valid_test(path))
+                return path;
+            free(path);
+            path = NULL;
+        }
+    }
+    if (path == NULL)
+        path = xstrdup(name);
+    return path;
+}
+
+
+/*
+ * Read a list of tests from a file, returning the list of tests as a struct
+ * testlist, or NULL if there were no tests (such as a file containing only
+ * comments).  Reports an error to standard error and exits if the list of
+ * tests cannot be read.
+ */
+static struct testlist *
+read_test_list(const char *filename)
+{
+    FILE *file;
+    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 = xcalloc(1, sizeof(struct testlist));
+    current = NULL;
+
+    /*
+     * Open our file of tests to run and read it line by line, creating a new
+     * struct testlist and struct testset for each line.
+     */
+    file = fopen(filename, "r");
+    if (file == NULL)
+        sysdie("can't open %s", filename);
+    line = 0;
+    while (fgets(buffer, sizeof(buffer), file)) {
+        line++;
+        length = strlen(buffer) - 1;
+        if (buffer[length] != '\n') {
+            fprintf(stderr, "%s:%u: line too long\n", filename, line);
+            exit(1);
+        }
+        buffer[length] = '\0';
+
+        /* Skip comments, leading spaces, and blank lines. */
+        testname = skip_whitespace(buffer);
+        if (strlen(testname) == 0)
             continue;
-        path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
-        sprintf(path, "%s/%s-t", bases[i], name);
-        if (access(path, X_OK) != 0)
-            path[strlen(path) - 2] = '.';
-        if (access(path, X_OK) == 0)
-            break;
-        free(path);
-        path = NULL;
+        if (testname[0] == '#')
+            continue;
+
+        /* Allocate the new testset structure. */
+        if (current == NULL)
+            current = listhead;
+        else {
+            current->next = xcalloc(1, sizeof(struct testlist));
+            current = current->next;
+        }
+        current->ts = xcalloc(1, sizeof(struct testset));
+        current->ts->plan = PLAN_INIT;
+        current->ts->file = xstrdup(testname);
     }
-    if (path == NULL) {
-        path = xmalloc(strlen(name) + 3);
-        sprintf(path, "%s-t", name);
+    fclose(file);
+
+    /* If there were no tests, current is still NULL. */
+    if (current == NULL) {
+        free(listhead);
+        return NULL;
     }
-    ts->path = path;
+
+    /* Return the results. */
+    return listhead;
 }
 
 
 /*
- * Run a batch of tests from a given file listing each test on a line by
- * itself.  Takes two additional parameters: the root of the source directory
- * and the root of the build directory.  Test programs will be first searched
- * for in the current directory, then the build directory, then the source
- * directory.  The file must be rewindable.  Returns true iff all tests
- * passed.
+ * Build a list of tests from command line arguments.  Takes the argv and argc
+ * representing the command line arguments and returns a newly allocated test
+ * list, or NULL if there were no tests.  The caller is responsible for
+ * freeing.
+ */
+static struct testlist *
+build_test_list(char *argv[], int argc)
+{
+    int i;
+    struct testlist *listhead, *current;
+
+    /* Create the initial container list that will hold our results. */
+    listhead = xcalloc(1, sizeof(struct testlist));
+    current = NULL;
+
+    /* Walk the list of arguments and create test sets for them. */
+    for (i = 0; i < argc; i++) {
+        if (current == NULL)
+            current = listhead;
+        else {
+            current->next = xcalloc(1, sizeof(struct testlist));
+            current = current->next;
+        }
+        current->ts = xcalloc(1, sizeof(struct testset));
+        current->ts->plan = PLAN_INIT;
+        current->ts->file = xstrdup(argv[i]);
+    }
+
+    /* If there were no tests, current is still NULL. */
+    if (current == NULL) {
+        free(listhead);
+        return NULL;
+    }
+
+    /* Return the results. */
+    return listhead;
+}
+
+
+/* Free a struct testset. */
+static void
+free_testset(struct testset *ts)
+{
+    free(ts->file);
+    free(ts->path);
+    free(ts->results);
+    free(ts->reason);
+    free(ts);
+}
+
+
+/*
+ * Run a batch of tests.  Takes two additional parameters: the root of the
+ * source directory and the root of the build directory.  Test programs will
+ * be first searched for in the current directory, then the build directory,
+ * then the source directory.  Returns true iff all tests passed, and always
+ * frees the test list that's passed in.
  */
 static int
-test_batch(const char *testlist, const char *source, const char *build)
+test_batch(struct testlist *tests, const char *source, const char *build,
+           enum test_verbose verbose)
 {
-    FILE *tests;
-    unsigned int length, i;
-    unsigned int longest = 0;
-    char buffer[BUFSIZ];
-    unsigned int line;
-    struct testset ts, *tmp;
+    size_t length, i;
+    size_t longest = 0;
+    unsigned int count = 0;
+    struct testset *ts;
     struct timeval start, end;
     struct rusage stats;
     struct testlist *failhead = NULL;
     struct testlist *failtail = NULL;
-    struct testlist *next;
+    struct testlist *current, *next;
+    int succeeded;
     unsigned long total = 0;
     unsigned long passed = 0;
     unsigned long skipped = 0;
     unsigned long failed = 0;
     unsigned long aborted = 0;
 
-    /*
-     * Open our file of tests to run and scan it, checking for lines that
-     * are too long and searching for the longest line.
-     */
-    tests = fopen(testlist, "r");
-    if (!tests)
-        sysdie("can't open %s", testlist);
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%u: line too long\n", testlist, line);
-            exit(1);
-        }
+    /* Walk the list of tests to find the longest name. */
+    for (current = tests; current != NULL; current = current->next) {
+        length = strlen(current->ts->file);
         if (length > longest)
             longest = length;
     }
-    if (fseek(tests, 0, SEEK_SET) == -1)
-        sysdie("can't rewind %s", testlist);
 
     /*
      * Add two to longest and round up to the nearest tab stop.  This is how
@@ -996,64 +1367,55 @@ test_batch(const char *testlist, const char *source, const char *build)
     /* Start the wall clock timer. */
     gettimeofday(&start, NULL);
 
-    /*
-     * Now, plow through our tests again, running each one.  Check line
-     * length again out of paranoia.
-     */
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%u: line too long\n", testlist, line);
-            exit(1);
-        }
-        buffer[length] = '\0';
-        fputs(buffer, stdout);
-        for (i = length; i < longest; i++)
-            putchar('.');
+    /* Now, plow through our tests again, running each one. */
+    for (current = tests; current != NULL; current = current->next) {
+        ts = current->ts;
+
+        /* Print out the name of the test file. */
+        fputs(ts->file, stdout);
+        if (verbose)
+            fputs("\n\n", stdout);
+        else
+            for (i = strlen(ts->file); i < longest; i++)
+                putchar('.');
         if (isatty(STDOUT_FILENO))
             fflush(stdout);
-        memset(&ts, 0, sizeof(ts));
-        ts.plan = PLAN_INIT;
-        ts.file = xstrdup(buffer);
-        find_test(buffer, &ts, source, build);
-        ts.reason = NULL;
-        if (test_run(&ts)) {
-            free(ts.file);
-            free(ts.path);
-            free(ts.results);
-            if (ts.reason != NULL)
-                free(ts.reason);
-        } else {
-            tmp = xmalloc(sizeof(struct testset));
-            memcpy(tmp, &ts, sizeof(struct testset));
-            if (!failhead) {
+
+        /* Run the test. */
+        ts->path = find_test(ts->file, source, build);
+        succeeded = test_run(ts, verbose);
+        fflush(stdout);
+        if (verbose)
+            putchar('\n');
+
+        /* Record cumulative statistics. */
+        aborted += ts->aborted;
+        total += ts->count + ts->all_skipped;
+        passed += ts->passed;
+        skipped += ts->skipped + ts->all_skipped;
+        failed += ts->failed;
+        count++;
+
+        /* If the test fails, we shuffle it over to the fail list. */
+        if (!succeeded) {
+            if (failhead == NULL) {
                 failhead = xmalloc(sizeof(struct testset));
-                failhead->ts = tmp;
-                failhead->next = NULL;
                 failtail = failhead;
             } else {
                 failtail->next = xmalloc(sizeof(struct testset));
                 failtail = failtail->next;
-                failtail->ts = tmp;
-                failtail->next = NULL;
             }
+            failtail->ts = ts;
+            failtail->next = NULL;
         }
-        aborted += ts.aborted;
-        total += ts.count + ts.all_skipped;
-        passed += ts.passed;
-        skipped += ts.skipped + ts.all_skipped;
-        failed += ts.failed;
     }
     total -= skipped;
-    fclose(tests);
 
     /* Stop the timer and get our child resource statistics. */
     gettimeofday(&end, NULL);
     getrusage(RUSAGE_CHILDREN, &stats);
 
-    /* Print out our final results. */
+    /* Summarize the failures and free the failure list. */
     if (failhead != NULL) {
         test_fail_summary(failhead);
         while (failhead != NULL) {
@@ -1062,6 +1424,16 @@ test_batch(const char *testlist, const char *source, const char *build)
             failhead = next;
         }
     }
+
+    /* Free the memory used by the test lists. */
+    while (tests != NULL) {
+        next = tests->next;
+        free_testset(tests->ts);
+        free(tests);
+        tests = next;
+    }
+
+    /* Print out the final test summary. */
     putchar('\n');
     if (aborted != 0) {
         if (aborted == 1)
@@ -1082,7 +1454,7 @@ test_batch(const char *testlist, const char *source, const char *build)
             printf(", %lu tests skipped", skipped);
     }
     puts(".");
-    printf("Files=%u,  Tests=%lu", line, total);
+    printf("Files=%u,  Tests=%lu", count, total);
     printf(",  %.2f seconds", tv_diff(&end, &start));
     printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
            tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
@@ -1098,12 +1470,11 @@ test_batch(const char *testlist, const char *source, const char *build)
 static void
 test_single(const char *program, const char *source, const char *build)
 {
-    struct testset ts;
+    char *path;
 
-    memset(&ts, 0, sizeof(ts));
-    find_test(program, &ts, source, build);
-    if (execl(ts.path, ts.path, (char *) 0) == -1)
-        sysdie("cannot exec %s", ts.path);
+    path = find_test(program, source, build);
+    if (execl(path, path, (char *) 0) == -1)
+        sysdie("cannot exec %s", path);
 }
 
 
@@ -1117,20 +1488,27 @@ 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 *list;
+    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:hos:")) != 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]);
+            printf(usage_message, program, program, program, usage_extra);
             exit(0);
+        case 'l':
+            list = optarg;
             break;
         case 'o':
             single = 1;
@@ -1138,43 +1516,57 @@ main(int argc, char *argv[])
         case 's':
             source = optarg;
             break;
+        case 'v':
+            verbose = VERBOSE;
+            break;
         default:
             exit(1);
         }
     }
-    if (argc - optind != 1) {
-        fprintf(stderr, usage_message, argv[0], argv[0]);
+    argv += optind;
+    argc -= optind;
+    if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) {
+        fprintf(stderr, usage_message, program, program, program, usage_extra);
         exit(1);
     }
-    argc -= optind;
-    argv += optind;
 
+    /*
+     * 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");
     }
 
+    /* Run the tests as instructed. */
     if (single)
         test_single(argv[0], source, build);
-    else {
-        list = strrchr(argv[0], '/');
-        if (list == NULL)
-            list = argv[0];
+    else if (list != NULL) {
+        shortlist = strrchr(list, '/');
+        if (shortlist == NULL)
+            shortlist = list;
         else
-            list++;
-        printf(banner, list);
-        status = test_batch(argv[0], source, build) ? 0 : 1;
+            shortlist++;
+        printf(banner, shortlist);
+        tests = read_test_list(list);
+        status = test_batch(tests, source, build, verbose) ? 0 : 1;
+    } else {
+        tests = build_test_list(argv, argc);
+        status = test_batch(tests, source, build, verbose) ? 0 : 1;
     }
 
-    /* For valgrind cleanliness. */
+    /* For valgrind cleanliness, free all our memory. */
     if (source_env != NULL) {
         putenv((char *) "SOURCE=");
         free(source_env);
index 475d335e922bb4a20292aee8f71fe510267a2e7c..4f8be04c4c7935c84139e1bd3180768536bfe525 100644 (file)
@@ -6,14 +6,15 @@
  * number and some number of appropriate arguments, check to be sure the
  * results match the expected output using the arguments, and print out
  * something appropriate for that test number.  Other utility routines help in
- * constructing more complex tests, skipping tests, or setting up the TAP
- * output format.
+ * constructing more complex tests, skipping tests, reporting errors, setting
+ * up the TAP output format, or finding things in the test environment.
  *
  * 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 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011
+ * 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
  * DEALINGS IN THE SOFTWARE.
  */
 
-/* Required for isnan() and isinf(). */
-#ifndef _XOPEN_SOURCE
-# define _XOPEN_SOURCE 600
-#endif
-
 #include <errno.h>
-#include <math.h>
+#include <limits.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,7 +50,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <tap/basic.h>
+#include <tests/tap/basic.h>
 
 /* Windows provides mkdir and rmdir under different names. */
 #ifdef _WIN32
@@ -64,7 +60,7 @@
 
 /*
  * The test count.  Always contains the number that will be used for the next
- * test status.
+ * test status.  This is exported to callers of the library.
  */
 unsigned long testnum = 1;
 
@@ -72,70 +68,299 @@ unsigned long testnum = 1;
  * Status information stored so that we can give a test summary at the end of
  * the test case.  We store the planned final test and the count of failures.
  * We can get the highest test count from testnum.
- *
- * We also store the PID of the process that called plan() and only summarize
- * results when that process exits, so as to not misreport results in forked
- * processes.
- *
- * If _lazy is true, we're doing lazy planning and will print out the plan
- * based on the last test number at the end of testing.
  */
 static unsigned long _planned = 0;
 static unsigned long _failed  = 0;
+
+/*
+ * Store the PID of the process that called plan() and only summarize
+ * results when that process exits, so as to not misreport results in forked
+ * processes.
+ */
 static pid_t _process = 0;
+
+/*
+ * If true, we're doing lazy planning and will print out the plan based on the
+ * last test number at the end of testing.
+ */
 static int _lazy = 0;
 
+/*
+ * If true, the test was aborted by calling bail().  Currently, this is only
+ * used to ensure that we pass a false value to any cleanup functions even if
+ * all tests to that point have passed.
+ */
+static int _aborted = 0;
+
+/*
+ * Registered cleanup functions.  These are stored as a linked list and run in
+ * registered order by finish when the test program exits.  Each function is
+ * passed a boolean value indicating whether all tests were successful.
+ */
+struct cleanup_func {
+    test_cleanup_func func;
+    struct cleanup_func *next;
+};
+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
+ * argument immediately before the variadic arguments.
+ */
+#define PRINT_DESC(prefix, format)              \
+    do {                                        \
+        if (format != NULL) {                   \
+            va_list args;                       \
+            if (prefix != NULL)                 \
+                printf("%s", prefix);           \
+            va_start(args, format);             \
+            vprintf(format, args);              \
+            va_end(args);                       \
+        }                                       \
+    } while (0)
+
+
+/*
+ * 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.
+ * 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
+ * we never ran a test (due to an early bail, for example), and running any
+ * registered cleanup functions.
  */
 static void
 finish(void)
 {
+    int success, primary;
+    struct cleanup_func *current;
     unsigned long highest = testnum - 1;
+    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
+     * calling cleanup functions since we pass that fact to the functions.
+     */
+    if (_planned == 0 && _lazy)
+        _planned = highest;
+    success = (!_aborted && _planned == highest && _failed == 0);
+
+    /*
+     * If there are any registered cleanup functions, we run those first.  We
+     * 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, 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)
+        return;
 
-    if (_planned == 0 && !_lazy)
+    /* If we're aborting due to bail, don't print summaries. */
+    if (_aborted)
         return;
+
+    /* Print out the lazy plan if needed. */
     fflush(stderr);
-    if (_process != 0 && getpid() == _process) {
-        if (_lazy) {
-            printf("1..%lu\n", highest);
-            _planned = highest;
-        }
-        if (_planned > highest)
-            printf("# Looks like you planned %lu test%s but only ran %lu\n",
-                   _planned, (_planned > 1 ? "s" : ""), highest);
-        else if (_planned < highest)
-            printf("# Looks like you planned %lu test%s but ran %lu extra\n",
-                   _planned, (_planned > 1 ? "s" : ""), highest - _planned);
-        else if (_failed > 0)
-            printf("# Looks like you failed %lu test%s of %lu\n", _failed,
-                   (_failed > 1 ? "s" : ""), _planned);
-        else if (_planned > 1)
-            printf("# All %lu tests successful or skipped\n", _planned);
-        else
-            printf("# %lu test successful or skipped\n", _planned);
-    }
+    if (_lazy && _planned > 0)
+        printf("1..%lu\n", _planned);
+
+    /* Print out a summary of the results. */
+    if (_planned > highest)
+        diag("Looks like you planned %lu test%s but only ran %lu", _planned,
+             (_planned > 1 ? "s" : ""), highest);
+    else if (_planned < highest)
+        diag("Looks like you planned %lu test%s but ran %lu extra", _planned,
+             (_planned > 1 ? "s" : ""), highest - _planned);
+    else if (_failed > 0)
+        diag("Looks like you failed %lu test%s of %lu", _failed,
+             (_failed > 1 ? "s" : ""), _planned);
+    else if (_planned != 1)
+        diag("All %lu tests successful or skipped", _planned);
+    else
+        diag("%lu test successful or skipped", _planned);
 }
 
 
 /*
  * 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)
 {
     if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
-        fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
-                strerror(errno));
+        sysdiag("cannot set stdout to line buffered");
     fflush(stderr);
     printf("1..%lu\n", count);
     testnum = 1;
     _planned = count;
     _process = getpid();
-    atexit(finish);
+    if (atexit(finish) != 0) {
+        sysdiag("cannot register exit handler");
+        diag("cleanups will not be run");
+    }
 }
 
 
@@ -147,83 +372,66 @@ void
 plan_lazy(void)
 {
     if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
-        fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
-                strerror(errno));
+        sysdiag("cannot set stdout to line buffered");
     testnum = 1;
     _process = getpid();
     _lazy = 1;
-    atexit(finish);
+    if (atexit(finish) != 0)
+        sysbail("cannot register exit handler to display plan");
 }
 
 
 /*
  * 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, ...)
 {
     fflush(stderr);
     printf("1..0 # skip");
-    if (format != NULL) {
-        va_list args;
-
-        putchar(' ');
-        va_start(args, format);
-        vprintf(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" ", format);
     putchar('\n');
     exit(0);
 }
 
 
-/*
- * Print the test description.
- */
-static void
-print_desc(const char *format, va_list args)
-{
-    printf(" - ");
-    vprintf(format, args);
-}
-
-
 /*
  * 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++;
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    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++;
-    if (format != NULL)
-        print_desc(format, args);
+    if (format != NULL) {
+        printf(" - ");
+        vprintf(format, args);
+    }
     putchar('\n');
+    return success;
 }
 
 
@@ -234,15 +442,9 @@ void
 skip(const char *reason, ...)
 {
     fflush(stderr);
+    check_diag_files();
     printf("ok %lu # skip", testnum++);
-    if (reason != NULL) {
-        va_list args;
-
-        va_start(args, reason);
-        putchar(' ');
-        vprintf(reason, args);
-        va_end(args);
-    }
+    PRINT_DESC(" ", reason);
     putchar('\n');
 }
 
@@ -250,25 +452,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++;
-        if (format != NULL) {
-            va_list args;
-
-            va_start(args, format);
-            print_desc(format, args);
-            va_end(args);
-        }
+        PRINT_DESC(" - ", format);
         putchar('\n');
     }
+    return success;
 }
 
 
@@ -281,16 +479,10 @@ 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++);
-        if (reason != NULL) {
-            va_list args;
-
-            va_start(args, reason);
-            putchar(' ');
-            vprintf(reason, args);
-            va_end(args);
-        }
+        PRINT_DESC(" ", reason);
         putchar('\n');
     }
 }
@@ -300,25 +492,25 @@ 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 {
-        printf("# wanted: %ld\n#   seen: %ld\n", wanted, seen);
+        diag("wanted: %ld", wanted);
+        diag("  seen: %ld", seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
+    return success;
 }
 
 
@@ -326,57 +518,29 @@ 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 {
-        printf("# wanted: %s\n#   seen: %s\n", wanted, seen);
+        diag("wanted: %s", wanted);
+        diag("  seen: %s", seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
-    putchar('\n');
-}
-
-
-/*
- * Takes an expected double and a seen double and assumes the test passes if
- * those two numbers are within delta of each other.
- */
-void
-is_double(double wanted, double seen, double epsilon, const char *format, ...)
-{
-    fflush(stderr);
-    if ((isnan(wanted) && isnan(seen))
-        || (isinf(wanted) && isinf(seen) && wanted == seen)
-        || fabs(wanted - seen) <= epsilon)
-        printf("ok %lu", testnum++);
-    else {
-        printf("# wanted: %g\n#   seen: %g\n", wanted, seen);
-        printf("not ok %lu", testnum++);
-        _failed++;
-    }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
+    return success;
 }
 
 
@@ -384,26 +548,25 @@ is_double(double wanted, double seen, double epsilon, 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 {
-        printf("# wanted: %lx\n#   seen: %lx\n", (unsigned long) wanted,
-               (unsigned long) seen);
+        diag("wanted: %lx", (unsigned long) wanted);
+        diag("  seen: %lx", (unsigned long) seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
+    return success;
 }
 
 
@@ -415,14 +578,16 @@ bail(const char *format, ...)
 {
     va_list args;
 
+    _aborted = 1;
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
     vprintf(format, args);
     va_end(args);
     printf("\n");
-    exit(1);
+    exit(255);
 }
 
 
@@ -435,51 +600,110 @@ sysbail(const char *format, ...)
     va_list args;
     int oerrno = errno;
 
+    _aborted = 1;
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
     vprintf(format, args);
     va_end(args);
     printf(": %s\n", strerror(oerrno));
-    exit(1);
+    exit(255);
 }
 
 
 /*
- * 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;
+    }
 }
 
 
@@ -526,6 +750,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.
  */
@@ -544,22 +794,42 @@ bstrdup(const char *s)
 }
 
 
+/*
+ * Copy up to n characters of a string, reporting a fatal error with bail on
+ * failure.  Don't use the system strndup function, since it may not exist and
+ * the TAP library doesn't assume any portability support.
+ */
+char *
+bstrndup(const char *s, size_t n)
+{
+    const char *p;
+    char *copy;
+    size_t length;
+
+    /* Don't assume that the source string is nul-terminated. */
+    for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+        ;
+    length = (size_t) (p - s);
+    copy = malloc(length + 1);
+    if (p == NULL)
+        sysbail("failed to strndup %lu bytes", (unsigned long) length);
+    memcpy(copy, s, length);
+    copy[length] = '\0';
+    return copy;
+}
+
+
 /*
  * Locate a test file.  Given the partial path to a file, look under BUILD and
  * 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;
 
@@ -567,9 +837,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);
@@ -587,8 +855,7 @@ test_file_path(const char *file)
 void
 test_file_path_free(char *path)
 {
-    if (path != NULL)
-        free(path);
+    free(path);
 }
 
 
@@ -607,14 +874,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);
@@ -630,7 +894,26 @@ test_tmpdir(void)
 void
 test_tmpdir_free(char *path)
 {
-    rmdir(path);
     if (path != NULL)
-        free(path);
+        rmdir(path);
+    free(path);
+}
+
+
+/*
+ * Register a cleanup function that is called when testing ends.  All such
+ * registered functions will be run by finish.
+ */
+void
+test_cleanup_register(test_cleanup_func func)
+{
+    struct cleanup_func *cleanup, **last;
+
+    cleanup = bmalloc(sizeof(struct cleanup_func));
+    cleanup->func = func;
+    cleanup->next = NULL;
+    last = &cleanup_funcs;
+    while (*last != NULL)
+        last = &(*last)->next;
+    *last = cleanup;
 }
index 60ac2e06872c876624b6f32aefcdc224cabda405..4ecaaec8e1e30a84d43d0fe79af35279f9290e10 100644 (file)
@@ -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 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011
+ * 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
 #ifndef TAP_BASIC_H
 #define TAP_BASIC_H 1
 
+#include <tests/tap/macros.h>
 #include <stdarg.h>             /* va_list */
-#include <sys/types.h>          /* size_t */
-
-/*
- * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
- * could you use the __format__ form of the attributes, which is what we use
- * (to avoid confusion with other macros).
- */
-#ifndef __attribute__
-# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
-#  define __attribute__(spec)   /* empty */
-# endif
-#endif
-
-/*
- * BEGIN_DECLS is used at the beginning of declarations so that C++
- * compilers don't mangle their names.  END_DECLS is used at the end.
- */
-#undef BEGIN_DECLS
-#undef END_DECLS
-#ifdef __cplusplus
-# define BEGIN_DECLS    extern "C" {
-# define END_DECLS      }
-#else
-# define BEGIN_DECLS    /* empty */
-# define END_DECLS      /* empty */
-#endif
+#include <stddef.h>             /* size_t */
 
 /*
  * Used for iterating through arrays.  ARRAY_SIZE returns the number of
@@ -79,7 +56,7 @@ extern unsigned long testnum;
 void plan(unsigned long count);
 
 /*
- * Prepare for lazy planning, in which the plan will be  printed automatically
+ * Prepare for lazy planning, in which the plan will be printed automatically
  * at the end of the test program.
  */
 void plan_lazy(void);
@@ -91,29 +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_double(double wanted, double seen, double epsilon,
-               const char *format, ...)
-    __attribute__((__format__(printf, 4, 5)));
-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). */
@@ -123,27 +105,43 @@ 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__));
+    __attribute__((__alloc_size__(1, 2), __malloc__, __warn_unused_result__));
 void *bmalloc(size_t)
-    __attribute__((__alloc_size__(1), __malloc__));
+    __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__));
+    __attribute__((__alloc_size__(2), __malloc__, __warn_unused_result__));
 char *bstrdup(const char *)
-    __attribute__((__malloc__, __nonnull__));
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
+char *bstrndup(const char *, size_t)
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
 
 /*
  * Find a test file under BUILD or SOURCE, returning the full path.  The
  * returned path should be freed with test_file_path_free().
  */
 char *test_file_path(const char *file)
-    __attribute__((__malloc__, __nonnull__));
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
 void test_file_path_free(char *path);
 
 /*
@@ -151,9 +149,23 @@ void test_file_path_free(char *path);
  * returned path should be freed with test_tmpdir_free.
  */
 char *test_tmpdir(void)
-    __attribute__((__malloc__));
+    __attribute__((__malloc__, __warn_unused_result__));
 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 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, int);
+void test_cleanup_register(test_cleanup_func)
+    __attribute__((__nonnull__));
+
 END_DECLS
 
 #endif /* TAP_BASIC_H */
index c6e65fd7288217fb5610070940b1438da0a9c302..9731032addeb85b2770248d7725ca07a0d6cdeeb 100644 (file)
@@ -1,12 +1,17 @@
 # Shell function library for test cases.
 #
+# Note that while many of the functions in this library could benefit from
+# using "local" to avoid possibly hammering global variables, Solaris /bin/sh
+# doesn't support local and this library aspires to be portable to Solaris
+# Bourne shell.  Instead, all private variables are prefixed with "tap_".
+#
 # This file provides a TAP-compatible shell function library useful for
 # writing test cases.  It is part of C TAP Harness, which can be found at
 # <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
 #
 # Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2009, 2010, 2011 Russ Allbery <eagle@eyrie.org>
-# Copyright 2006, 2007, 2008
+# Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
+# Copyright 2006, 2007, 2008, 2013
 #     The Board of Trustees of the Leland Stanford Junior University
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -46,33 +51,35 @@ plan_lazy () {
 
 # Report the test status on exit.
 finish () {
-    local highest looks
-    highest=`expr "$count" - 1`
+    tap_highest=`expr "$count" - 1`
     if [ "$planned" = 0 ] ; then
-        echo "1..$highest"
-        planned="$highest"
+        echo "1..$tap_highest"
+        planned="$tap_highest"
     fi
-    looks='# Looks like you'
+    tap_looks='# Looks like you'
     if [ "$planned" -gt 0 ] ; then
-        if [ "$planned" -gt "$highest" ] ; then
+        if [ "$planned" -gt "$tap_highest" ] ; then
             if [ "$planned" -gt 1 ] ; then
-                echo "$looks planned $planned tests but only ran $highest"
+                echo "$tap_looks planned $planned tests but only ran" \
+                    "$tap_highest"
             else
-                echo "$looks planned $planned test but only ran $highest"
+                echo "$tap_looks planned $planned test but only ran" \
+                    "$tap_highest"
             fi
-        elif [ "$planned" -lt "$highest" ] ; then
-            local extra
-            extra=`expr "$highest" - "$planned"`
+        elif [ "$planned" -lt "$tap_highest" ] ; then
+            tap_extra=`expr "$tap_highest" - "$planned"`
             if [ "$planned" -gt 1 ] ; then
-                echo "$looks planned $planned tests but ran $extra extra"
+                echo "$tap_looks planned $planned tests but ran" \
+                    "$tap_extra extra"
             else
-                echo "$looks planned $planned test but ran $extra extra"
+                echo "$tap_looks planned $planned test but ran" \
+                    "$tap_extra extra"
             fi
         elif [ "$failed" -gt 0 ] ; then
             if [ "$failed" -gt 1 ] ; then
-                echo "$looks failed $failed tests of $planned"
+                echo "$tap_looks failed $failed tests of $planned"
             else
-                echo "$looks failed $failed test of $planned"
+                echo "$tap_looks failed $failed test of $planned"
             fi
         elif [ "$planned" -gt 1 ] ; then
             echo "# All $planned tests successful or skipped"
@@ -84,10 +91,9 @@ finish () {
 
 # Skip the entire test suite.  Should be run instead of plan.
 skip_all () {
-    local desc
-    desc="$1"
-    if [ -n "$desc" ] ; then
-        echo "1..0 # skip $desc"
+    tap_desc="$1"
+    if [ -n "$tap_desc" ] ; then
+        echo "1..0 # skip $tap_desc"
     else
         echo "1..0 # skip"
     fi
@@ -98,16 +104,15 @@ skip_all () {
 # command is successful, false otherwise.  The count starts at 1 and is
 # updated each time ok is printed.
 ok () {
-    local desc
-    desc="$1"
-    if [ -n "$desc" ] ; then
-        desc=" - $desc"
+    tap_desc="$1"
+    if [ -n "$tap_desc" ] ; then
+        tap_desc=" - $tap_desc"
     fi
     shift
     if "$@" ; then
-        echo ok $count$desc
+        echo ok "$count$tap_desc"
     else
-        echo not ok $count$desc
+        echo not ok "$count$tap_desc"
         failed=`expr $failed + 1`
     fi
     count=`expr $count + 1`
@@ -122,34 +127,34 @@ skip () {
 # Report the same status on a whole set of tests.  Takes the count of tests,
 # the description, and then the command to run to determine the status.
 ok_block () {
-    local end i desc
-    i=$count
-    end=`expr $count + $1`
-    shift
-    desc="$1"
+    tap_i=$count
+    tap_end=`expr $count + $1`
     shift
-    while [ "$i" -lt "$end" ] ; do
-        ok "$desc" "$@"
-        i=`expr $i + 1`
+    while [ "$tap_i" -lt "$tap_end" ] ; do
+        ok "$@"
+        tap_i=`expr $tap_i + 1`
     done
 }
 
 # Skip a whole set of tests.  Takes the count and then the reason for skipping
 # the test.
 skip_block () {
-    local i end
-    i=$count
-    end=`expr $count + $1`
+    tap_i=$count
+    tap_end=`expr $count + $1`
     shift
-    while [ "$i" -lt "$end" ] ; do
+    while [ "$tap_i" -lt "$tap_end" ] ; do
         skip "$@"
-        i=`expr $i + 1`
+        tap_i=`expr $tap_i + 1`
     done
 }
 
 # Portable variant of printf '%s\n' "$*".  In the majority of cases, this
 # function is slower than printf, because the latter is often implemented
 # as a builtin command.  The value of the variable IFS is ignored.
+#
+# This macro must not be called via backticks inside double quotes, since this
+# will result in bizarre escaping behavior and lots of extra backslashes on
+# Solaris.
 puts () {
     cat << EOH
 $@
@@ -165,21 +170,21 @@ EOH
 # If the command may contain system-specific error messages in its output,
 # add strip_colon_error before the command to post-process its output.
 ok_program () {
-    local desc w_status w_output output status
-    desc="$1"
+    tap_desc="$1"
     shift
-    w_status="$1"
+    tap_w_status="$1"
     shift
-    w_output="$1"
+    tap_w_output="$1"
     shift
-    output=`"$@" 2>&1`
-    status=$?
-    if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
-        ok "$desc" true
+    tap_output=`"$@" 2>&1`
+    tap_status=$?
+    if [ $tap_status = $tap_w_status ] \
+        && [ x"$tap_output" = x"$tap_w_output" ] ; then
+        ok "$tap_desc" true
     else
-        echo "#  saw: ($status) $output"
-        echo "#  not: ($w_status) $w_output"
-        ok "$desc" false
+        echo "#  saw: ($tap_status) $tap_output"
+        echo "#  not: ($tap_w_status) $tap_w_output"
+        ok "$tap_desc" false
     fi
 }
 
@@ -189,18 +194,17 @@ ok_program () {
 # message.)  This is used to remove system-specific error messages (coming
 # from strerror, for example).
 strip_colon_error() {
-    local output status
-    output=`"$@" 2>&1`
-    status=$?
-    output=`puts "$output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
-    puts "$output"
-    return $status
+    tap_output=`"$@" 2>&1`
+    tap_status=$?
+    tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
+    puts "$tap_output"
+    return $tap_status
 }
 
 # Bail out with an error message.
 bail () {
     echo 'Bail out!' "$@"
-    exit 1
+    exit 255
 }
 
 # Output a diagnostic on standard error, preceded by the required # mark.
@@ -211,6 +215,9 @@ diag () {
 # Search for the given file first in $BUILD and then in $SOURCE and echo the
 # path where the file was found, or the empty string if the file wasn't
 # found.
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
 test_file_path () {
     if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
         puts "$BUILD/$1"
@@ -223,15 +230,17 @@ test_file_path () {
 
 # Create $BUILD/tmp for use by tests for storing temporary files and return
 # the path (via standard output).
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
 test_tmpdir () {
-    local tmpdir
     if [ -z "$BUILD" ] ; then
-        tmpdir="./tmp"
+        tap_tmpdir="./tmp"
     else
-        tmpdir="$BUILD"/tmp
+        tap_tmpdir="$BUILD"/tmp
     fi
-    if [ ! -d "$tmpdir" ] ; then
-        mkdir "$tmpdir" || bail "Error creating $tmpdir"
+    if [ ! -d "$tap_tmpdir" ] ; then
+        mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
     fi
-    puts "$tmpdir"
+    puts "$tap_tmpdir"
 }
diff --git a/tests/tap/macros.h b/tests/tap/macros.h
new file mode 100644 (file)
index 0000000..139cff0
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Helpful macros for TAP header files.
+ *
+ * This is not, strictly speaking, related to TAP, but any TAP add-on is
+ * probably going to need these macros, so define them in one place so that
+ * everyone can pull them in.
+ *
+ * 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, 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"),
+ * 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.
+ */
+
+#ifndef TAP_MACROS_H
+#define TAP_MACROS_H 1
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros), and only with gcc 2.96 can you use
+ * the attribute __malloc__.  2.96 is very old, so don't bother trying to get
+ * the other attributes to work with GCC versions between 2.7 and 2.96.
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#  define __attribute__(spec)   /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC.  Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if defined(__GNUC__) && !defined(__clang__)
+#  if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+#   define __alloc_size__(spec, args...) /* empty */
+#  endif
+# endif
+#endif
+
+/* Suppress __warn_unused_result__ if gcc is too old. */
+#if !defined(__attribute__) && !defined(__warn_unused_result__)
+# if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
+#  define __warn_unused_result__ /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does.  For them, suppress warnings about unknown
+ * attributes on declarations.  This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names.  END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS    extern "C" {
+# define END_DECLS      }
+#else
+# define BEGIN_DECLS    /* empty */
+# define END_DECLS      /* empty */
+#endif
+
+#endif /* TAP_MACROS_H */
diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm
new file mode 100644 (file)
index 0000000..3870cc1
--- /dev/null
@@ -0,0 +1,235 @@
+# Helper functions for test programs written in Perl.
+#
+# This module provides a collection of helper functions used by test programs
+# written in Perl.  This is a general collection of functions that can be used
+# by both C packages with Automake and by stand-alone Perl modules.  See
+# Test::RRA::Automake for additional functions specifically for C Automake
+# distributions.
+
+package Test::RRA;
+
+use 5.006;
+use strict;
+use warnings;
+
+use Exporter;
+use Test::More;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+    @ISA       = qw(Exporter);
+    @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 = '5.09';
+}
+
+# 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_author {
+    my ($description) = @_;
+    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
+# the program.
+#
+# The special logic here is based on Test::More and is required to get the
+# imports to happen in the caller's namespace.
+#
+# $module  - Name of the module to load
+# @imports - Any arguments to import, possibly including a version
+#
+# Returns: undef
+sub use_prereq {
+    my ($module, @imports) = @_;
+
+    # If the first import looks like a version, pass it as a bare string.
+    my $version = q{};
+    if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.][\d_]+ )* \z }xms) {
+        $version = shift(@imports);
+    }
+
+    # Get caller information to put imports in the correct package.
+    my ($package) = caller;
+
+    # Do the import with eval, and try to isolate it from the surrounding
+    # context as much as possible.  Based heavily on Test::More::_eval.
+    ## no critic (BuiltinFunctions::ProhibitStringyEval)
+    ## no critic (ValuesAndExpressions::ProhibitImplicitNewlines)
+    my ($result, $error, $sigdie);
+    {
+        local $@            = undef;
+        local $!            = undef;
+        local $SIG{__DIE__} = undef;
+        $result = eval qq{
+            package $package;
+            use $module $version \@imports;
+            1;
+        };
+        $error = $@;
+        $sigdie = $SIG{__DIE__} || undef;
+    }
+
+    # If the use failed for any reason, skip the test.
+    if (!$result || $error) {
+        my $name = length($version) > 0 ? "$module $version" : $module;
+        plan skip_all => "$name required for test";
+    }
+
+    # If the module set $SIG{__DIE__}, we cleared that via local.  Restore it.
+    ## no critic (Variables::RequireLocalizedPunctuationVars)
+    if (defined($sigdie)) {
+        $SIG{__DIE__} = $sigdie;
+    }
+    return;
+}
+
+1;
+__END__
+
+=for stopwords
+Allbery Allbery's DESC bareword sublicense MERCHANTABILITY NONINFRINGEMENT
+rra-c-util
+
+=head1 NAME
+
+Test::RRA - Support functions for Perl tests
+
+=head1 SYNOPSIS
+
+    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 doing automated or release testing.
+    skip_unless_automated('POD syntax tests');
+
+    # Load modules, skipping the test if they're not available.
+    use_prereq('Perl6::Slurp', 'slurp');
+    use_prereq('Test::Script::Run', '0.04');
+
+=head1 DESCRIPTION
+
+This module collects utility functions that are useful for Perl test
+scripts.  It assumes Russ Allbery's Perl module layout and test
+conventions and will only be useful for other people if they use the
+same conventions.
+
+=head1 FUNCTIONS
+
+None of these functions are imported by default.  The ones used by a
+script should be explicitly imported.
+
+=over 4
+
+=item skip_unless_author(DESC)
+
+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 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 ...])
+
+Attempts to load MODULE with the given VERSION and import arguments.  If
+this fails for any reason, the test will be skipped (by calling C<plan
+skip_all> from Test::More) with a skip reason saying that MODULE is
+required for the test.
+
+VERSION will be passed to C<use> as a version bareword if it looks like a
+version number.  The remaining IMPORT arguments will be passed as the
+value of an array.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+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
+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.
+
+=head1 SEE ALSO
+
+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
new file mode 100644 (file)
index 0000000..b820b7b
--- /dev/null
@@ -0,0 +1,406 @@
+# Helper functions for Perl test programs in Automake distributions.
+#
+# This module provides a collection of helper functions used by test programs
+# written in Perl and included in C source distributions that use Automake.
+# They embed knowledge of how I lay out my source trees and test suites with
+# Autoconf and Automake.  They may be usable by others, but doing so will
+# require closely following the conventions implemented by the rra-c-util
+# utility collection.
+#
+# 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.
+
+package Test::RRA::Automake;
+
+use 5.006;
+use strict;
+use warnings;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+use Exporter;
+use File::Spec;
+use Test::More;
+use Test::RRA::Config qw($LIBRARY_PATH);
+
+# Used below for use lib calls.
+my ($PERL_BLIB_ARCH, $PERL_BLIB_LIB);
+
+# Determine the path to the build tree of any embedded Perl module package in
+# this source package.  We do this in a BEGIN block because we're going to use
+# the results in a use lib command below.
+BEGIN {
+    $PERL_BLIB_ARCH = File::Spec->catdir(qw(perl blib arch));
+    $PERL_BLIB_LIB  = File::Spec->catdir(qw(perl blib lib));
+
+    # If BUILD is set, we can come up with better values.
+    if (defined($ENV{BUILD})) {
+        my ($vol, $dirs) = File::Spec->splitpath($ENV{BUILD}, 1);
+        my @dirs = File::Spec->splitdir($dirs);
+        pop(@dirs);
+        $PERL_BLIB_ARCH = File::Spec->catdir(@dirs, qw(perl blib arch));
+        $PERL_BLIB_LIB  = File::Spec->catdir(@dirs, qw(perl blib lib));
+    }
+}
+
+# Prefer the modules built as part of our source package.  Otherwise, we may
+# not find Perl modules while testing, or find the wrong versions.
+use lib $PERL_BLIB_ARCH;
+use lib $PERL_BLIB_LIB;
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+    @ISA       = qw(Exporter);
+    @EXPORT_OK = qw(automake_setup perl_dirs test_file_path test_tmpdir);
+
+    # 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 = '5.09';
+}
+
+# Perl directories to skip globally for perl_dirs.  We ignore the perl
+# directory if it exists since, in my packages, it is treated as a Perl module
+# distribution and has its own standalone test suite.
+my @GLOBAL_SKIP = qw(.git perl);
+
+# The temporary directory created by test_tmpdir, if any.  If this is set,
+# attempt to remove the directory stored here on program exit (but ignore
+# failure to do so).
+my $TMPDIR;
+
+# Perform initial test setup for running a Perl test in an Automake package.
+# This verifies that BUILD and SOURCE are set and then changes directory to
+# the SOURCE directory by default.  Sets LD_LIBRARY_PATH if the $LIBRARY_PATH
+# configuration option is set.  Calls BAIL_OUT if BUILD or SOURCE are missing
+# or if anything else fails.
+#
+# $args_ref - Reference to a hash of arguments to configure behavior:
+#   chdir_build - If set to a true value, changes to BUILD instead of SOURCE
+#
+# Returns: undef
+sub automake_setup {
+    my ($args_ref) = @_;
+
+    # Bail if BUILD or SOURCE are not set.
+    if (!$ENV{BUILD}) {
+        BAIL_OUT('BUILD not defined (run under runtests)');
+    }
+    if (!$ENV{SOURCE}) {
+        BAIL_OUT('SOURCE not defined (run under runtests)');
+    }
+
+    # BUILD or SOURCE will be the test directory.  Change to the parent.
+    my $start = $args_ref->{chdir_build} ? $ENV{BUILD} : $ENV{SOURCE};
+    my ($vol, $dirs) = File::Spec->splitpath($start, 1);
+    my @dirs = File::Spec->splitdir($dirs);
+    pop(@dirs);
+
+    # Simplify relative paths at the end of the directory.
+    my $ups = 0;
+    my $i   = $#dirs;
+    while ($i > 2 && $dirs[$i] eq File::Spec->updir) {
+        $ups++;
+        $i--;
+    }
+    for (1 .. $ups) {
+        pop(@dirs);
+        pop(@dirs);
+    }
+    my $root = File::Spec->catpath($vol, File::Spec->catdir(@dirs), q{});
+    chdir($root) or BAIL_OUT("cannot chdir to $root: $!");
+
+    # If BUILD is a subdirectory of SOURCE, add it to the global ignore list.
+    my ($buildvol, $builddirs) = File::Spec->splitpath($ENV{BUILD}, 1);
+    my @builddirs = File::Spec->splitdir($builddirs);
+    pop(@builddirs);
+    if ($buildvol eq $vol && @builddirs == @dirs + 1) {
+        while (@dirs && $builddirs[0] eq $dirs[0]) {
+            shift(@builddirs);
+            shift(@dirs);
+        }
+        if (@builddirs == 1) {
+            push(@GLOBAL_SKIP, $builddirs[0]);
+        }
+    }
+
+    # Set LD_LIBRARY_PATH if the $LIBRARY_PATH configuration option is set.
+    ## no critic (Variables::RequireLocalizedPunctuationVars)
+    if (defined($LIBRARY_PATH)) {
+        @builddirs = File::Spec->splitdir($builddirs);
+        pop(@builddirs);
+        my $libdir = File::Spec->catdir(@builddirs, $LIBRARY_PATH);
+        my $path = File::Spec->catpath($buildvol, $libdir, q{});
+        if (-d "$path/.libs") {
+            $path .= '/.libs';
+        }
+        if ($ENV{LD_LIBRARY_PATH}) {
+            $ENV{LD_LIBRARY_PATH} .= ":$path";
+        } else {
+            $ENV{LD_LIBRARY_PATH} = $path;
+        }
+    }
+    return;
+}
+
+# Returns a list of directories that may contain Perl scripts and that should
+# be passed to Perl test infrastructure that expects a list of directories to
+# recursively check.  The list will be all eligible top-level directories in
+# the package except for the tests directory, which is broken out to one
+# additional level.  Calls BAIL_OUT on any problems
+#
+# $args_ref - Reference to a hash of arguments to configure behavior:
+#   skip - A reference to an array of directories to skip
+#
+# Returns: List of directories possibly containing Perl scripts to test
+sub perl_dirs {
+    my ($args_ref) = @_;
+
+    # Add the global skip list.
+    my @skip = $args_ref->{skip} ? @{ $args_ref->{skip} } : ();
+    push(@skip, @GLOBAL_SKIP);
+
+    # Separate directories to skip under tests from top-level directories.
+    my @skip_tests = grep { m{ \A tests/ }xms } @skip;
+    @skip = grep { !m{ \A tests }xms } @skip;
+    for my $skip_dir (@skip_tests) {
+        $skip_dir =~ s{ \A tests/ }{}xms;
+    }
+
+    # Convert the skip lists into hashes for convenience.
+    my %skip = map { $_ => 1 } @skip, 'tests';
+    my %skip_tests = map { $_ => 1 } @skip_tests;
+
+    # 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);
+    closedir($rootdir);
+    @dirs = File::Spec->no_upwards(@dirs);
+
+    # Add the list of subdirectories of the tests directory.
+    if (-d 'tests') {
+        opendir(my $testsdir, q{tests}) or BAIL_OUT("cannot open tests: $!");
+
+        # Skip if found in %skip_tests or if not a directory.
+        my $is_skipped = sub {
+            my ($dir) = @_;
+            return 1 if $skip_tests{$dir};
+            $dir = File::Spec->catdir('tests', $dir);
+            return -d $dir ? 0 : 1;
+        };
+
+        # Build the filtered list of subdirectories of tests.
+        my @test_dirs = grep { !$is_skipped->($_) } readdir($testsdir);
+        closedir($testsdir);
+        @test_dirs = File::Spec->no_upwards(@test_dirs);
+
+        # Add the tests directory to the start of the directory name.
+        push(@dirs, map { File::Spec->catdir('tests', $_) } @test_dirs);
+    }
+    return @dirs;
+}
+
+# Find a configuration file for the test suite.  Searches relative to BUILD
+# first and then SOURCE and returns whichever is found first.  Calls BAIL_OUT
+# if the file could not be found.
+#
+# $file - Partial path to the file
+#
+# Returns: Full path to the file
+sub test_file_path {
+    my ($file) = @_;
+  BASE:
+    for my $base ($ENV{BUILD}, $ENV{SOURCE}) {
+        next if !defined($base);
+        if (-f "$base/$file") {
+            return "$base/$file";
+        }
+    }
+    BAIL_OUT("cannot find $file");
+    return;
+}
+
+# Create a temporary directory for tests to use for transient files and return
+# the path to that directory.  The directory is automatically removed on
+# program exit.  The directory permissions use the current umask.  Calls
+# BAIL_OUT if the directory could not be created.
+#
+# Returns: Path to a writable temporary directory
+sub test_tmpdir {
+    my $path;
+
+    # If we already figured out what directory to use, reuse the same path.
+    # Otherwise, create a directory relative to BUILD if set.
+    if (defined($TMPDIR)) {
+        $path = $TMPDIR;
+    } else {
+        my $base = defined($ENV{BUILD}) ? $ENV{BUILD} : File::Spec->curdir;
+        $path = File::Spec->catdir($base, 'tmp');
+    }
+
+    # Create the directory if it doesn't exist.
+    if (!-d $path) {
+        if (!mkdir($path, 0777)) {
+            BAIL_OUT("cannot create directory $path: $!");
+        }
+    }
+
+    # Store the directory name for cleanup and return it.
+    $TMPDIR = $path;
+    return $path;
+}
+
+# On program exit, remove $TMPDIR if set and if possible.  Report errors with
+# diag but otherwise ignore them.
+END {
+    if (defined($TMPDIR) && -d $TMPDIR) {
+        local $! = undef;
+        if (!rmdir($TMPDIR)) {
+            diag("cannot remove temporary directory $TMPDIR: $!");
+        }
+    }
+}
+
+1;
+__END__
+
+=for stopwords
+Allbery Automake Automake-aware Automake-based rra-c-util ARGS
+subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT umask
+
+=head1 NAME
+
+Test::RRA::Automake - Automake-aware support functions for Perl tests
+
+=head1 SYNOPSIS
+
+    use Test::RRA::Automake qw(automake_setup perl_dirs test_file_path);
+    automake_setup({ chdir_build => 1 });
+
+    # Paths to directories that may contain Perl scripts.
+    my @dirs = perl_dirs({ skip => [qw(lib)] });
+
+    # Configuration for Kerberos tests.
+    my $keytab = test_file_path('config/keytab');
+
+=head1 DESCRIPTION
+
+This module collects utility functions that are useful for test scripts
+written in Perl and included in a C Automake-based package.  They assume
+the layout of a package that uses rra-c-util and C TAP Harness for the
+test structure.
+
+Loading this module will also add the directories C<perl/blib/arch> and
+C<perl/blib/lib> to the Perl library search path, relative to BUILD if
+that environment variable is set.  This is harmless for C Automake
+projects that don't contain an embedded Perl module, and for those
+projects that do, this will allow subsequent C<use> calls to find modules
+that are built as part of the package build process.
+
+The automake_setup() function should be called before calling any other
+functions provided by this module.
+
+=head1 FUNCTIONS
+
+None of these functions are imported by default.  The ones used by a
+script should be explicitly imported.  On failure, all of these functions
+call BAIL_OUT (from Test::More).
+
+=over 4
+
+=item automake_setup([ARGS])
+
+Verifies that the BUILD and SOURCE environment variables are set and
+then changes directory to the top of the source tree (which is one
+directory up from the SOURCE path, since SOURCE points to the top of
+the tests directory).
+
+If ARGS is given, it should be a reference to a hash of configuration
+options.  Only one option is supported: C<chdir_build>.  If it is set
+to a true value, automake_setup() changes directories to the top of
+the build tree instead.
+
+=item perl_dirs([ARGS])
+
+Returns a list of directories that may contain Perl scripts that should be
+tested by test scripts that test all Perl in the source tree (such as
+syntax or coding style checks).  The paths will be simple directory names
+relative to the current directory or two-part directory names under the
+F<tests> directory.  (Directories under F<tests> are broken out separately
+since it's common to want to apply different policies to different
+subdirectories of F<tests>.)
+
+If ARGS is given, it should be a reference to a hash of configuration
+options.  Only one option is supported: C<skip>, whose value should be a
+reference to an array of additional top-level directories or directories
+starting with C<tests/> that should be skipped.
+
+=item test_file_path(FILE)
+
+Given FILE, which should be a relative path, locates that file relative to
+the test directory in either the source or build tree.  FILE will be
+checked for relative to the environment variable BUILD first, and then
+relative to SOURCE.  test_file_path() returns the full path to FILE or
+calls BAIL_OUT if FILE could not be found.
+
+=item test_tmpdir()
+
+Create a temporary directory for tests to use for transient files and
+return the path to that directory.  The directory is created relative to
+the BUILD environment variable, which must be set.  Permissions on the
+directory are set using the current umask.  test_tmpdir() returns the full
+path to the temporary directory or calls BAIL_OUT if it could not be
+created.
+
+The directory is automatically removed if possible on program exit.
+Failure to remove the directory on exit is reported with diag() and
+otherwise ignored.
+
+=back
+
+=head1 AUTHOR
+
+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
+
+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.
+
+=head1 SEE ALSO
+
+Test::More(3), Test::RRA(3), Test::RRA::Config(3)
+
+The C TAP Harness test driver and libraries for TAP-based C testing are
+available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+
+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/>.
+
+=cut
diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm
new file mode 100644 (file)
index 0000000..7c29f1a
--- /dev/null
@@ -0,0 +1,215 @@
+# Configuration for Perl test cases.
+#
+# In order to reuse the same Perl test cases in multiple packages, I use a
+# configuration file to store some package-specific data.  This module loads
+# that configuration and provides the namespace for the configuration
+# settings.
+
+package Test::RRA::Config;
+
+use 5.006;
+use strict;
+use warnings;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+use Exporter;
+use Test::More;
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+    @ISA       = qw(Exporter);
+    @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 = '5.09';
+}
+
+# If BUILD or SOURCE are set in the environment, look for data/perl.conf under
+# those paths for a C Automake package.  Otherwise, look in t/data/perl.conf
+# for a standalone Perl module.  Don't use Test::RRA::Automake since it may
+# not exist.
+our $PATH;
+for my $base ($ENV{BUILD}, $ENV{SOURCE}, 't') {
+    next if !defined($base);
+    my $path = "$base/data/perl.conf";
+    if (-r $path) {
+        $PATH = $path;
+        last;
+    }
+}
+if (!defined($PATH)) {
+    BAIL_OUT('cannot find data/perl.conf');
+}
+
+# Pre-declare all of our variables and set any defaults.
+our $COVERAGE_LEVEL = 100;
+our @COVERAGE_SKIP_TESTS;
+our @CRITIC_IGNORE;
+our $LIBRARY_PATH;
+our $MINIMUM_VERSION = '5.008';
+our %MINIMUM_VERSION;
+our @POD_COVERAGE_EXCLUDE;
+our @STRICT_IGNORE;
+our @STRICT_PREREQ;
+
+# Load the configuration.
+if (!do($PATH)) {
+    my $error = $@ || $! || 'loading file did not return true';
+    BAIL_OUT("cannot load data/perl.conf: $error");
+}
+
+1;
+__END__
+
+=for stopwords
+Allbery rra-c-util Automake perlcritic .libs namespace subdirectory
+sublicense MERCHANTABILITY NONINFRINGEMENT
+
+=head1 NAME
+
+Test::RRA::Config - Perl test configuration
+
+=head1 SYNOPSIS
+
+    use Test::RRA::Config qw($MINIMUM_VERSION);
+    print "Required Perl version is $MINIMUM_VERSION\n";
+
+=head1 DESCRIPTION
+
+Test::RRA::Config encapsulates per-package configuration for generic Perl
+test programs that are shared between multiple packages using the
+rra-c-util infrastructure.  It handles locating and loading the test
+configuration file for both C Automake packages and stand-alone Perl
+modules.
+
+Test::RRA::Config looks for a file named F<data/perl.conf> relative to the
+root of the test directory.  That root is taken from the environment
+variables BUILD or SOURCE (in that order) if set, which will be the case
+for C Automake packages using C TAP Harness.  If neither is set, it
+expects the root of the test directory to be a directory named F<t>
+relative to the current directory, which will be the case for stand-alone
+Perl modules.
+
+The following variables are supported:
+
+=over 4
+
+=item $COVERAGE_LEVEL
+
+The coverage level achieved by the test suite for Perl test coverage
+testing using Test::Strict, as a percentage.  The test will fail if test
+coverage less than this percentage is achieved.  If not given, defaults
+to 100.
+
+=item @COVERAGE_SKIP_TESTS
+
+Directories under F<t> whose tests should be skipped when doing coverage
+testing.  This can be tests that won't contribute to coverage or tests
+that don't run properly under Devel::Cover for some reason (such as ones
+that use taint checking).  F<docs> and F<style> will always be skipped
+regardless of this setting.
+
+=item @CRITIC_IGNORE
+
+Additional directories to ignore when doing recursive perlcritic testing.
+The contents of this directory must be either top-level directory names or
+directory names starting with F<tests/>.
+
+=item $LIBRARY_PATH
+
+Add this directory (or a F<.libs> subdirectory) relative to the top of the
+source tree to LD_LIBRARY_PATH when checking the syntax of Perl modules.
+This may be required to pick up libraries that are used by in-tree Perl
+modules so that Perl scripts can pass a syntax check.
+
+=item $MINIMUM_VERSION
+
+Default minimum version requirement for included Perl scripts.  If not
+given, defaults to 5.008.
+
+=item %MINIMUM_VERSION
+
+Minimum version exceptions for specific directories.  The keys should be
+minimum versions of Perl to enforce.  The value for each key should be a
+reference to an array of either top-level directory names or directory
+names starting with F<tests/>.  All files in those directories will have
+that minimum Perl version constraint imposed instead of $MINIMUM_VERSION.
+
+=item @POD_COVERAGE_EXCLUDE
+
+Regexes that match method names that should be excluded from POD coverage
+testing.  Normally, all methods have to be documented in the POD for a
+Perl module, but methods matching any of these regexes will be considered
+private and won't require documentation.
+
+=item @STRICT_IGNORE
+
+Additional directories to ignore when doing recursive Test::Strict testing
+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
+into the local namespace to avoid long variable names.
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+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
+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.
+
+=head1 SEE ALSO
+
+perlcritic(1), Test::MinimumVersion(3), Test::RRA(3),
+Test::RRA::Automake(3), Test::Strict(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 C TAP Harness test driver and libraries for TAP-based C testing are
+available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+
+=cut
index 2a42f8da33bf66882a25397ed0859db314b00a9f..8c22324c20632f7b1244f1a91e027baeb0532f2c 100644 (file)
@@ -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
+ * 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
 #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
@@ -102,6 +144,7 @@ run_child_function(test_function_type function, void *data, int *status,
         buf[count < 0 ? 0 : count] = '\0';
         if (waitpid(child, &rval, 0) == (pid_t) -1)
             sysbail("waitpid failed");
+        close(fds[0]);
     }
 
     /* Store the output and return. */
@@ -170,7 +213,316 @@ run_setup(const char *const argv[])
         p = strchr(output, '\n');
         if (p != NULL)
             *p = '\0';
-        bail("%s", output);
+        if (output[0] != '\0')
+            bail("%s", output);
+        else
+            bail("setup command failed with no output");
     }
     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);
+}
index 23fae258bdaa483591b0b32b468d4f4942177d25..8137d5d26fb485b8ed70c0aa29609185677b91a9 100644 (file)
@@ -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
 #define TAP_PROCESS_H 1
 
 #include <config.h>
-#include <portable/macros.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 */
index d900f8e0b93a459759161d95dd788c475c84297c..6ed7e68dc7c3f4b162de7e6dacf4c359c2df49a7 100644 (file)
@@ -7,7 +7,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 2011 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011, 2012 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"),
@@ -39,7 +39,7 @@
  * vsprintf into a newly allocated string, reporting a fatal error with bail
  * on failure.
  */
-int
+void
 bvasprintf(char **strp, const char *fmt, va_list args)
 {
     int status;
@@ -47,7 +47,6 @@ bvasprintf(char **strp, const char *fmt, va_list args)
     status = vasprintf(strp, fmt, args);
     if (status < 0)
         sysbail("failed to allocate memory for vasprintf");
-    return status;
 }
 
 
@@ -55,14 +54,12 @@ bvasprintf(char **strp, const char *fmt, va_list args)
  * sprintf into a newly allocated string, reporting a fatal error with bail on
  * failure.
  */
-int
+void
 basprintf(char **strp, const char *fmt, ...)
 {
     va_list args;
-    int status;
 
     va_start(args, fmt);
-    status = bvasprintf(strp, fmt, args);
+    bvasprintf(strp, fmt, args);
     va_end(args);
-    return status;
 }
index 11101699145ca2695698bb775fcc19091bb9e408..d58f75d6f002a239e26b02191f6273ebcc7df8f5 100644 (file)
@@ -7,7 +7,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 2011 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011, 2012 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"),
 #define TAP_STRING_H 1
 
 #include <config.h>
-#include <portable/macros.h>
+#include <tests/tap/macros.h>
 
 #include <stdarg.h>             /* va_list */
 
 BEGIN_DECLS
 
-/* sprintf into an allocated string, calling bail on allocation failure. */
-int basprintf(char **, const char *, ...)
+/* sprintf into an allocated string, calling bail on any failure. */
+void basprintf(char **, const char *, ...)
     __attribute__((__nonnull__, __format__(printf, 2, 3)));
-int bvasprintf(char **, const char *, va_list)
-    __attribute__((__nonnull__));
+void bvasprintf(char **, const char *, va_list)
+    __attribute__((__nonnull__, __format__(printf, 2, 0)));
 
 END_DECLS
 
diff --git a/tests/util/concat-t.c b/tests/util/concat-t.c
deleted file mode 100644 (file)
index c670d66..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * concat 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 <tests/tap/basic.h>
-#include <util/concat.h>
-
-#define END (char *) 0
-
-/*
- * Memory leaks everywhere!  Whoo-hoo!
- */
-int
-main(void)
-{
-    plan(13);
-
-    is_string("a",     concat("a",                   END), "concat 1");
-    is_string("ab",    concat("a", "b",              END), "concat 2");
-    is_string("ab",    concat("ab", "",              END), "concat 3");
-    is_string("ab",    concat("", "ab",              END), "concat 4");
-    is_string("",      concat("",                    END), "concat 5");
-    is_string("abcde", concat("ab", "c", "", "de",   END), "concat 6");
-    is_string("abcde", concat("abc", "de", END, "f", END), "concat 7");
-
-    is_string("/foo",             concatpath("/bar", "/foo"),        "path 1");
-    is_string("/foo/bar",         concatpath("/foo", "bar"),         "path 2");
-    is_string("./bar",            concatpath("/foo", "./bar"),       "path 3");
-    is_string("/bar/baz/foo/bar", concatpath("/bar/baz", "foo/bar"), "path 4");
-    is_string("./foo",            concatpath(NULL, "foo"),           "path 5");
-    is_string("/foo/bar",         concatpath(NULL, "/foo/bar"),      "path 6");
-
-    return 0;
-}
index 8e9daf10aeda9b877f9cf73eda1b32b654f11d7b..c6de5a5796f4b218fe1fb7ab044705ba472eed6f 100644 (file)
@@ -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
+ * 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
  */
 
 #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.
  */
@@ -116,5 +130,9 @@ main(void)
     message_handlers_die(0);
     is_function_output(test_die, NULL, 1, "", "warn_krb5 with no handlers");
 
+    krb5_free_error_message(ctx, message);
+    krb5_free_context(ctx);
     return 0;
 }
+
+#endif /* HAVE_KRB5 */
index 1a0d989472873be385ea8b50c2fe7a8ab6aab0fa..109831485ea068b07f7e19aa680dfefeb9c8c3f5 100644 (file)
@@ -5,8 +5,8 @@
  * 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
+ * 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
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -38,7 +38,6 @@
 
 #include <tests/tap/basic.h>
 #include <tests/tap/process.h>
-#include <util/concat.h>
 #include <util/macros.h>
 #include <util/messages.h>
 #include <util/xmalloc.h>
@@ -93,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");
@@ -166,7 +166,7 @@ test_strerror(int status, const char *output, int error,
 {
     char *full_output, *name;
 
-    full_output = concat(output, ": ", strerror(error), "\n", (char *) NULL);
+    xasprintf(&full_output, "%s: %s\n", output, strerror(error));
     xasprintf(&name, "strerror %lu", testnum / 3 + 1);
     is_function_output(function, NULL, status, full_output, "%s", name);
     free(full_output);
index 7560c860a17186521937eb4b10ddc40c91e4cc9b..af604edb84d1f606cb704a53c3a49e94ed40d65c 100755 (executable)
@@ -6,8 +6,8 @@
 # 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 2008, 2009, 2010
+# 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
 #
 # Permission is hereby granted, free of charge, to any person obtaining a
@@ -41,18 +41,16 @@ ok_xmalloc () {
     shift
     w_output="$1"
     shift
-    output=`./xmalloc "$@" 2>&1`
+    output=`strip_colon_error ./xmalloc "$@" 2>&1`
     status=$?
-    if [ "$w_status" -ne 0 ] ; then
-        output=`echo "$output" | sed 's/:.*//'`
-    fi
     if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
         ok "$desc" true
     elif [ $status = 2 ] ; then
+        diag "$output"
         skip "no data limit support"
     else
-        echo "#  saw: ($status) $output"
-        echo "#  not: ($w_status) $w_output"
+        diag "saw: ($status) $output"
+        diag "not: ($w_status) $w_output"
         ok "$desc" false
     fi
 }
@@ -61,84 +59,94 @@ 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" "3500000" "0"
-ok_xmalloc "malloc zero"     0 "" "m" "0"       "0"
-ok_xmalloc "realloc small"   0 "" "r" "21"      "0"
-ok_xmalloc "realloc large"   0 "" "r" "3500000" "0"
-ok_xmalloc "strdup small"    0 "" "s" "21"      "0"
-ok_xmalloc "strdup large"    0 "" "s" "3500000" "0"
-ok_xmalloc "strndup small"   0 "" "n" "21"      "0"
-ok_xmalloc "strndup large"   0 "" "n" "3500000" "0"
-ok_xmalloc "calloc small"    0 "" "c" "24"      "0"
-ok_xmalloc "calloc large"    0 "" "c" "3500000" "0"
-ok_xmalloc "asprintf small"  0 "" "a" "24"      "0"
-ok_xmalloc "asprintf large"  0 "" "a" "3500000" "0"
-ok_xmalloc "vasprintf small" 0 "" "v" "24"      "0"
-ok_xmalloc "vasprintf large" 0 "" "v" "3500000" "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 3.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
 # be large enough to allow the program to be loaded and do small allocations,
 # but not so large that we can't reasonably expect to allocate that much
-# memory normally.  3.5MB seems to work reasonably well on both Solaris and
-# Linux.
+# 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.  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 3500000 bytes at xmalloc.c line 38" \
-    "m" "3500000" "3500000"
+    "failed to malloc 30000000 bytes at xmalloc.c line 41" \
+    "m" "30000000" "30000000"
 ok_xmalloc "realloc fail" 1 \
-    "failed to realloc 3500000 bytes at xmalloc.c line 66" \
-    "r" "3500000" "3500000"
+    "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 3500000 bytes at xmalloc.c line 97" \
-    "s" "3500000" "3500000"
+    "failed to strdup 30000000 bytes at xmalloc.c line 130" \
+    "s" "30000000" "30000000"
 ok_xmalloc "strndup fail" 1 \
-    "failed to strndup 3500000 bytes at xmalloc.c line 124" \
-    "n" "3500000" "3500000"
+    "failed to strndup 30000000 bytes at xmalloc.c line 176" \
+    "n" "30000000" "30000000"
 ok_xmalloc "calloc fail" 1 \
-    "failed to calloc 3500000 bytes at xmalloc.c line 148" \
-    "c" "3500000" "3500000"
+    "failed to calloc 30000000 bytes at xmalloc.c line 200" \
+    "c" "30000000" "30000000"
 ok_xmalloc "asprintf fail" 1 \
-    "failed to asprintf 3500000 bytes at xmalloc.c line 173" \
-    "a" "3500000" "3500000"
+    "failed to asprintf 30000000 bytes at xmalloc.c line 224" \
+    "a" "30000000" "30000000"
 ok_xmalloc "vasprintf fail" 1 \
-    "failed to vasprintf 3500000 bytes at xmalloc.c line 195" \
-    "v" "3500000" "3500000"
+    "failed to vasprintf 30000000 bytes at xmalloc.c line 243" \
+    "v" "30000000" "30000000"
 
 # Check our custom error handler.
-ok_xmalloc "malloc custom"    1 "malloc 3500000 xmalloc.c 38" \
-    "M" "3500000" "3500000"
-ok_xmalloc "realloc custom"   1 "realloc 3500000 xmalloc.c 66" \
-    "R" "3500000" "3500000"
-ok_xmalloc "strdup custom"    1 "strdup 3500000 xmalloc.c 97" \
-    "S" "3500000" "3500000"
-ok_xmalloc "strndup custom"   1 "strndup 3500000 xmalloc.c 124" \
-    "N" "3500000" "3500000"
-ok_xmalloc "calloc custom"    1 "calloc 3500000 xmalloc.c 148" \
-    "C" "3500000" "3500000"
-ok_xmalloc "asprintf custom"  1 "asprintf 3500000 xmalloc.c 173" \
-    "A" "3500000" "3500000"
-ok_xmalloc "vasprintf custom" 1 "vasprintf 3500000 xmalloc.c 195" \
-    "V" "3500000" "3500000"
+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" "3500000"
-ok_xmalloc "realloc retry"   0 "" "r" "32" "3500000"
-ok_xmalloc "strdup retry"    0 "" "s" "64" "3500000"
-ok_xmalloc "strndup retry"   0 "" "n" "20" "3500000"
-ok_xmalloc "calloc retry"    0 "" "c" "24" "3500000"
-ok_xmalloc "asprintf retry"  0 "" "a" "30" "3500000"
-ok_xmalloc "vasprintf retry" 0 "" "v" "35" "3500000"
+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"
index fa194ce7180c85ca0bddcf14092a860c99c2db74..84ba0812aa21244ac6466b4bcccf7b6fd77fd31c 100644 (file)
@@ -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
+ * 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
 
 #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>
@@ -109,6 +112,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.
@@ -136,15 +169,34 @@ test_strdup(size_t size)
 
 /*
  * Generate a string of the size indicated plus some, call xstrndup on it, and
- * then ensure the result matches.  Returns true on success, false on any
- * failure.
+ * then ensure the result matches.  Also test xstrdup on a string that's
+ * shorter than the specified size and ensure that we don't copy too much, and
+ * on a string that's not nul-terminated.  Returns true on success, false on
+ * any failure.
  */
 static int
 test_strndup(size_t size)
 {
     char *string, *copy;
-    int match, toomuch;
+    int shortmatch, nonulmatch, match, toomuch;
+
+    /* Copy a short string. */
+    string = xmalloc(5);
+    memcpy(string, "test", 5);
+    copy = xstrndup(string, size);
+    shortmatch = strcmp(string, copy);
+    free(string);
+    free(copy);
+
+    /* Copy a string that's not nul-terminated. */
+    string = xmalloc(4);
+    memcpy(string, "test", 4);
+    copy = xstrndup(string, 4);
+    nonulmatch = strcmp(copy, "test");
+    free(string);
+    free(copy);
 
+    /* Now the test of running out of memory. */
     string = xmalloc(size + 1);
     if (string == NULL)
         return 0;
@@ -158,7 +210,7 @@ test_strndup(size_t size)
     toomuch = strcmp(string, copy);
     free(string);
     free(copy);
-    return (match == 0 && toomuch != 0);
+    return (shortmatch == 0 && nonulmatch == 0 && match == 0 && toomuch != 0);
 }
 
 
@@ -194,16 +246,13 @@ static int
 test_asprintf(size_t size)
 {
     char *copy, *string;
-    int status;
     size_t i;
 
     string = xmalloc(size);
     memset(string, 42, size - 1);
     string[size - 1] = '\0';
-    status = xasprintf(&copy, "%s", string);
+    xasprintf(&copy, "%s", string);
     free(string);
-    if (status < 0)
-        return 0;
     for (i = 0; i < size - 1; i++)
         if (copy[i] != 42)
             return 0;
@@ -215,16 +264,14 @@ test_asprintf(size_t size)
 
 
 /* Wrapper around vasprintf to do the va_list stuff. */
-static int
+static void __attribute__((__format__(printf, 2, 3)))
 xvasprintf_wrapper(char **strp, const char *format, ...)
 {
     va_list args;
-    int status;
 
     va_start(args, format);
-    status = xvasprintf(strp, format, args);
+    xvasprintf(strp, format, args);
     va_end(args);
-    return status;
 }
 
 
@@ -236,16 +283,13 @@ static int
 test_vasprintf(size_t size)
 {
     char *copy, *string;
-    int status;
     size_t i;
 
     string = xmalloc(size);
     memset(string, 42, size - 1);
     string[size - 1] = '\0';
-    status = xvasprintf_wrapper(&copy, "%s", string);
+    xvasprintf_wrapper(&copy, "%s", string);
     free(string);
-    if (status < 0)
-        return 0;
     for (i = 0; i < size - 1; i++)
         if (copy[i] != 42)
             return 0;
@@ -311,6 +355,7 @@ main(int argc, char *argv[])
 #if HAVE_SETRLIMIT && defined(RLIMIT_AS)
         struct rlimit rl;
         void *tmp;
+        size_t test_size;
 
         rl.rlim_cur = limit;
         rl.rlim_max = limit;
@@ -318,11 +363,14 @@ main(int argc, char *argv[])
             syswarn("Can't set data limit to %lu", (unsigned long) limit);
             exit(2);
         }
-        if (size < limit || code == 'r') {
-            tmp = malloc(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);
             if (tmp == NULL) {
-                syswarn("Can't allocate initial memory of %lu",
-                        (unsigned long) size);
+                syswarn("Can't allocate initial memory of %lu (limit %lu)",
+                        (unsigned long) test_size, (unsigned long) limit);
                 exit(2);
             }
             free(tmp);
@@ -337,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/concat.c b/util/concat.c
deleted file mode 100644 (file)
index bc7d4b6..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Concatenate strings with dynamic memory allocation.
- *
- * Usage:
- *
- *      string = concat(string1, string2, ..., (char *) 0);
- *      path = concatpath(base, name);
- *
- * Dynamically allocates (using xmalloc) sufficient memory to hold all of the
- * strings given and then concatenates them together into that allocated
- * memory, returning a pointer to it.  Caller is responsible for freeing.
- * Assumes xmalloc is available.  The last argument must be a null pointer (to
- * a char *, if you actually find a platform where it matters).
- *
- * concatpath is similar, except that it only takes two arguments.  If the
- * second argument begins with / or ./, a copy of it is returned; otherwise,
- * the first argument, a slash, and the second argument are concatenated
- * together and returned.  This is useful for building file names where names
- * that aren't fully qualified are qualified with some particular directory.
- *
- * 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 <util/concat.h>
-#include <util/xmalloc.h>
-
-/* Abbreviation for cleaner code. */
-#define VA_NEXT(var, type) ((var) = (type) va_arg(args, type))
-
-
-/*
- * Concatenate all of the arguments into a newly allocated string.  ANSI C
- * requires at least one named parameter, but it's not treated any different
- * than the rest.
- */
-char *
-concat(const char *first, ...)
-{
-    va_list args;
-    char *result, *p;
-    const char *string;
-    size_t length = 0;
-
-    /* Find the total memory required. */
-    va_start(args, first);
-    for (string = first; string != NULL; VA_NEXT(string, const char *))
-        length += strlen(string);
-    va_end(args);
-    length++;
-
-    /*
-     * Create the string.  Doing the copy ourselves avoids useless string
-     * traversals of result, if using strcat, or string, if using strlen to
-     * increment a pointer into result, at the cost of losing the native
-     * optimization of strcat if any.
-     */
-    result = xmalloc(length);
-    p = result;
-    va_start(args, first);
-    for (string = first; string != NULL; VA_NEXT(string, const char *))
-        while (*string != '\0')
-            *p++ = *string++;
-    va_end(args);
-    *p = '\0';
-
-    return result;
-}
-
-
-/*
- * Concatenate name with base, unless name begins with / or ./.  Return the
- * new string in newly allocated memory.
- */
-char *
-concatpath(const char *base, const char *name)
-{
-    if (name[0] == '/' || (name[0] == '.' && name[1] == '/'))
-        return xstrdup(name);
-    else
-        return concat(base != NULL ? base : ".", "/", name, (char *) 0);
-}
diff --git a/util/concat.h b/util/concat.h
deleted file mode 100644 (file)
index d02b568..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Prototypes for string concatenation with dynamic memory allocation.
- *
- * 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.
- */
-
-#ifndef UTIL_CONCAT_H
-#define UTIL_CONCAT_H 1
-
-#include <config.h>
-#include <portable/macros.h>
-
-BEGIN_DECLS
-
-/* Default to a hidden visibility for all util functions. */
-#pragma GCC visibility push(hidden)
-
-/* Concatenate NULL-terminated strings into a newly allocated string. */
-char *concat(const char *first, ...)
-    __attribute__((__malloc__, __nonnull__(1)));
-
-/*
- * Given a base path and a file name, create a newly allocated path string.
- * The name will be appended to base with a / between them.  Exceptionally, if
- * name begins with a slash, it will be strdup'd and returned as-is.
- */
-char *concatpath(const char *base, const char *name)
-    __attribute__((__malloc__, __nonnull__(2)));
-
-/* Undo default visibility change. */
-#pragma GCC visibility pop
-
-END_DECLS
-
-#endif /* UTIL_CONCAT_H */
index d071793a807580cf2ecf190cc2ba3f86a5f9bd53..4a773a2dafe34f0e9ec296b3332f7feb4c6df69a 100644 (file)
 #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__))
 
index 0e92b74c150559d02330294b1a0fd980bc3f5884..961ea1d18fec80e2a9f922243b29b52b647b6b28 100644 (file)
@@ -9,7 +9,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, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 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
@@ -35,7 +35,6 @@
 #include <portable/krb5.h>
 #include <portable/system.h>
 
-#include <util/macros.h>
 #include <util/messages.h>
 #include <util/messages-krb5.h>
 #include <util/xmalloc.h>
@@ -54,8 +53,7 @@ die_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
     if (ctx != NULL)
         k5_msg = krb5_get_error_message(ctx, code);
     va_start(args, format);
-    if (xvasprintf(&message, format, args) < 0)
-        die("internal error: unable to format error message");
+    xvasprintf(&message, format, args);
     va_end(args);
     if (k5_msg == NULL)
         die("%s", message);
@@ -77,8 +75,7 @@ warn_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
     if (ctx != NULL)
         k5_msg = krb5_get_error_message(ctx, code);
     va_start(args, format);
-    if (xvasprintf(&message, format, args) < 0)
-        die("internal error: unable to format error message");
+    xvasprintf(&message, format, args);
     va_end(args);
     if (k5_msg == NULL)
         warn("%s", message);
index a9072bf0a40e7b1fdb6277bccb8c0bc929ea1dc7..3fc08620bf89e2b5765d9ecf5af15e29c9effbf2 100644 (file)
@@ -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, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 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
 #define UTIL_MESSAGES_KRB5_H 1
 
 #include <config.h>
+#include <portable/krb5.h>
 #include <portable/macros.h>
 
-#include <krb5.h>
-#include <sys/types.h>
-
 BEGIN_DECLS
 
 /* Default to a hidden visibility for all util functions. */
index 9ec3ba855860445c720e6aa36560e93a124108ce..b5c2dbabcf8e7ba745465df66863fd407f345bb3 100644 (file)
@@ -54,7 +54,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2008, 2009, 2010
+ * Copyright 2008, 2009, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -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;
@@ -159,6 +159,31 @@ HANDLER_FUNCTION(warn)
 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.
  */
@@ -200,10 +225,11 @@ 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;
+    int status;
 
     buffer = malloc(len + 1);
     if (buffer == NULL) {
@@ -211,7 +237,12 @@ message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err)
                 (unsigned long) len + 1, __FILE__, __LINE__, strerror(errno));
         exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
     }
-    vsnprintf(buffer, len + 1, fmt, args);
+    status = vsnprintf(buffer, len + 1, fmt, args);
+    if (status < 0) {
+        warn("failed to format output with vsnprintf in syslog handler");
+        free(buffer);
+        return;
+    }
 #ifdef _WIN32
     {
         HANDLE eventlog;
index 463137c4a6f6c5e0c08d21f61a8282f23e90acdf..cf91ba75a6bed96d1db590c6c9bbb0b5404a2680 100644 (file)
@@ -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
+ * 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")
@@ -34,6 +34,7 @@
 #include <portable/macros.h>
 
 #include <stdarg.h>
+#include <stddef.h>
 
 BEGIN_DECLS
 
@@ -71,30 +72,37 @@ void message_handlers_notice(unsigned int count, ...);
 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);
index 839f5a02d25d20d5c0a64d094626ebb72477496c..7fb040505415608de1da59692c112958a2f7ab8c 100644 (file)
@@ -12,7 +12,7 @@
  *      buffer = xmalloc(1024);
  *      xrealloc(buffer, 2048);
  *      free(buffer);
- *      buffer = xcalloc(1024);
+ *      buffer = xcalloc(1, 1024);
  *      free(buffer);
  *      buffer = xstrdup(string);
  *      free(buffer);
  * 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,6 +62,9 @@
  * 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 2015 Russ Allbery <eagle@eyrie.org>
+ * 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")
  * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
@@ -82,8 +89,6 @@
 #include <config.h>
 #include <portable/system.h>
 
-#include <errno.h>
-
 #include <util/messages.h>
 #include <util/xmalloc.h>
 
 void
 xmalloc_fail(const char *function, size_t size, const char *file, int line)
 {
-    sysdie("failed to %s %lu bytes at %s line %d", function,
-           (unsigned long) size, file, line);
+    if (size == 0)
+        sysdie("failed to format output with %s at %s line %d", function,
+               file, line);
+    else
+        sysdie("failed to %s %lu bytes at %s line %d", function,
+               (unsigned long) size, file, line);
 }
 
 /* Assign to this variable to choose a handler other than the default. */
@@ -148,6 +157,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)
 {
@@ -165,23 +188,34 @@ x_strdup(const char *s, const char *file, int line)
 }
 
 
+/*
+ * Avoid using the system strndup function since it may not exist (on Mac OS
+ * X, for example), and there's no need to introduce another portability
+ * requirement.
+ */
 char *
 x_strndup(const char *s, size_t size, const char *file, int line)
 {
-    char *p;
+    const char *p;
+    size_t length;
+    char *copy;
 
-    p = malloc(size + 1);
-    while (p == NULL) {
-        (*xmalloc_error_handler)("strndup", size + 1, file, line);
-        p = malloc(size + 1);
+    /* Don't assume that the source string is nul-terminated. */
+    for (p = s; (size_t) (p - s) < size && *p != '\0'; p++)
+        ;
+    length = p - s;
+    copy = malloc(length + 1);
+    while (copy == NULL) {
+        (*xmalloc_error_handler)("strndup", length + 1, file, line);
+        copy = malloc(length + 1);
     }
-    memcpy(p, s, size);
-    p[size] = '\0';
-    return p;
+    memcpy(copy, s, length);
+    copy[length] = '\0';
+    return copy;
 }
 
 
-int
+void
 x_vasprintf(char **strp, const char *fmt, va_list args, const char *file,
             int line)
 {
@@ -191,21 +225,21 @@ x_vasprintf(char **strp, const char *fmt, va_list args, const char *file,
     va_copy(args_copy, args);
     status = vasprintf(strp, fmt, args_copy);
     va_end(args_copy);
-    while (status < 0 && errno == ENOMEM) {
+    while (status < 0) {
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("vasprintf", status + 1, file, line);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("vasprintf", status, file, line);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
     }
-    return status;
 }
 
 
 #if HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS
-int
+void
 x_asprintf(char **strp, const char *file, int line, const char *fmt, ...)
 {
     va_list args, args_copy;
@@ -215,19 +249,20 @@ x_asprintf(char **strp, const char *file, int line, const char *fmt, ...)
     va_copy(args_copy, args);
     status = vasprintf(strp, fmt, args_copy);
     va_end(args_copy);
-    while (status < 0 && errno == ENOMEM) {
+    while (status < 0) {
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("asprintf", status + 1, file, line);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("asprintf", status, file, line);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
     }
-    return status;
+    va_end(args);
 }
 #else /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */
-int
+void
 x_asprintf(char **strp, const char *fmt, ...)
 {
     va_list args, args_copy;
@@ -237,15 +272,16 @@ x_asprintf(char **strp, const char *fmt, ...)
     va_copy(args_copy, args);
     status = vasprintf(strp, fmt, args_copy);
     va_end(args_copy);
-    while (status < 0 && errno == ENOMEM) {
+    while (status < 0) {
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("asprintf", status + 1, __FILE__, __LINE__);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("asprintf", status, __FILE__, __LINE__);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
     }
-    return status;
+    va_end(args);
 }
 #endif /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */
index 14d883104bdf34a4862bc98bd800e99a2bdf04d6..6aa9b93c604b579cb126b87f1bdf2e43530fde06 100644 (file)
@@ -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
+ * 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")
@@ -33,7 +33,8 @@
 #include <config.h>
 #include <portable/macros.h>
 
-#include <sys/types.h>
+#include <stdarg.h>
+#include <stddef.h>
 
 /*
  * The functions are actually macros so that we can pick up the file and line
@@ -46,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
@@ -80,23 +83,29 @@ 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__));
-int x_vasprintf(char **, const char *, va_list, const char *, int)
-    __attribute__((__nonnull__));
+void x_vasprintf(char **, const char *, va_list, const char *, int)
+    __attribute__((__nonnull__, __format__(printf, 2, 0)));
 
 /* asprintf special case. */
 #if HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS
-int x_asprintf(char **, const char *, int, const char *, ...)
+void x_asprintf(char **, const char *, int, const char *, ...)
     __attribute__((__nonnull__, __format__(printf, 4, 5)));
 #else
-int x_asprintf(char **, const char *, ...)
+void x_asprintf(char **, const char *, ...)
     __attribute__((__nonnull__, __format__(printf, 2, 3)));
 #endif
 
-/* Failure handler takes the function, the size, the file, and the line. */
+/*
+ * Failure handler takes the function, the size, the file, and the line.  The
+ * size will be zero if the failure was due to some failure in snprintf
+ * instead of a memory allocation failure.
+ */
 typedef void (*xmalloc_handler_type)(const char *, size_t, const char *, int);
 
 /* The default error handler. */