]> eyrie.org Git - kerberos/krb5-strength.git/commitdiff
Update to rra-c-util 5.3 and C TAP Harness 3.0
authorRuss Allbery <eagle@eyrie.org>
Thu, 6 Mar 2014 03:49:23 +0000 (19:49 -0800)
committerRuss Allbery <eagle@eyrie.org>
Thu, 6 Mar 2014 03:49:23 +0000 (19:49 -0800)
Update to rra-c-util 5.3:

* Fix portable/krb5.h build with a C++ compiler.

Update to C TAP Harness 3.0:

* Reopen standard input to /dev/null when running a test list.
* Don't leak extraneous file descriptors to tests.

24 files changed:
.gitignore
Makefile.am
NEWS
configure.ac
m4/tinycdb.m4
portable/krb5.h
portable/mkstemp.c [new file with mode: 0644]
tests/TESTS
tests/data/valgrind.supp
tests/docs/pod-t
tests/perl/critic-t
tests/portable/mkstemp-t.c [new file with mode: 0644]
tests/portable/mkstemp.c [new file with mode: 0644]
tests/runtests.c
tests/tap/basic.c
tests/tap/basic.h
tests/tap/kerberos.c
tests/tap/kerberos.h
tests/tap/perl/Test/RRA.pm
tests/tap/perl/Test/RRA/Automake.pm
tests/tap/process.c
tests/tap/process.h
util/messages.c
util/messages.h

index 9d75f42ea1cc9e0f81adc522aed5d879c91f52ee..8013fb4f3de312ec813060dc53da65921b1d2e9b 100644 (file)
@@ -24,6 +24,7 @@
 /tests/plugin/heimdal-t
 /tests/plugin/mit-t
 /tests/portable/asprintf-t
+/tests/portable/mkstemp-t
 /tests/portable/snprintf-t
 /tests/portable/strndup-t
 /tests/runtests
index 9aa9d8d300fd6a22cfe87cb6b38da0c65dedf51b..6d388764a85b7d2d45351bde9c15d338e85aa38f 100644 (file)
@@ -122,10 +122,10 @@ warnings:
        $(MAKE) V=0 CFLAGS='$(WARNINGS)' $(check_PROGRAMS)
 
 # The bits below are for the test suite, not for the main package.
-check_PROGRAMS = tests/runtests tests/plugin/heimdal-t tests/plugin/mit-t \
-       tests/portable/asprintf-t tests/portable/snprintf-t               \
-       tests/portable/strndup-t tests/util/messages-krb5-t               \
-       tests/util/messages-t tests/util/xmalloc
+check_PROGRAMS = tests/runtests tests/plugin/heimdal-t tests/plugin/mit-t   \
+       tests/portable/asprintf-t tests/portable/mkstemp-t                  \
+       tests/portable/snprintf-t tests/portable/strndup-t                  \
+       tests/util/messages-krb5-t tests/util/messages-t tests/util/xmalloc
 if EMBEDDED_CRACKLIB
     check_PROGRAMS += cracklib/packer
 endif
@@ -148,6 +148,9 @@ tests_plugin_mit_t_LDADD = tests/tap/libtap.a portable/libportable.la \
 tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \
        tests/portable/asprintf.c
 tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
+tests_portable_mkstemp_t_SOURCES = tests/portable/mkstemp-t.c \
+       tests/portable/mkstemp.c
+tests_portable_mkstemp_t_LDADD = tests/tap/libtap.a portable/libportable.la
 tests_portable_snprintf_t_SOURCES = tests/portable/snprintf-t.c \
        tests/portable/snprintf.c
 tests_portable_snprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
diff --git a/NEWS b/NEWS
index 3acab414c3c8bc917f6821087bc5de5c42a11025..0457d3ee77595919fa5620cd4e5ed37d8d971622 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,15 @@ krb5-strength 3.0 (unreleased)
     be used to reject long strings of identical characters or short
     patterns, which may pass other checks but still be too easy to guess.
 
+    Update to rra-c-util 5.3:
+
+    * Fix portable/krb5.h build with a C++ compiler.
+
+    Update to C TAP Harness 3.0:
+
+    * Reopen standard input to /dev/null when running a test list.
+    * Don't leak extraneous file descriptors to tests.
+
 krb5-strength 2.2 (2013-12-16)
 
     More complex character class requirements can be specified with the
index 519cfe3d7eb0346f6847a19daf651f915e0fe7dd..be9468d17b8f342c58b431b3cb8250019f2b9386 100644 (file)
@@ -1,7 +1,7 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl
 dnl Written by Russ Allbery <eagle@eyrie.org>
-dnl Copyright 2006, 2007, 2009, 2010, 2012, 2013
+dnl Copyright 2006, 2007, 2009, 2010, 2012, 2013, 2014
 dnl     The Board of Trustees of the Leland Stanford Junior University
 dnl
 dnl See LICENSE for licensing terms.
@@ -59,7 +59,7 @@ AC_SUBST([DL_LIBS])
 
 dnl Checks for basic C functionality.
 AC_HEADER_STDBOOL
-AC_CHECK_HEADERS([sys/bittypes.h syslog.h])
+AC_CHECK_HEADERS([sys/bittypes.h sys/select.h syslog.h])
 AC_CHECK_DECLS([snprintf, vsnprintf])
 RRA_C_C99_VAMACROS
 RRA_C_GNU_VAMACROS
@@ -71,7 +71,7 @@ AC_CHECK_TYPES([ssize_t], [], [],
     [#include <sys/types.h>])
 RRA_FUNC_SNPRINTF
 AC_CHECK_FUNCS([setrlimit])
-AC_REPLACE_FUNCS([asprintf strndup])
+AC_REPLACE_FUNCS([asprintf mkstemp strndup])
 
 dnl Write out the results.
 AC_CONFIG_FILES([Makefile])
index d7fd22f4143a86db5b5cebc1cc90569bc76f391f..1b28bcad9d597c9b09350842f2301d0cdffc0170 100644 (file)
@@ -25,7 +25,7 @@ dnl and/or distribute it, with or without modifications, as long as this
 dnl notice is preserved.
 
 dnl Save the current CPPFLAGS, LDFLAGS, and LIBS settings and switch to
-dnl versions that include the kadmin client flags.  Used as a wrapper, with
+dnl versions that include the TinyCDB flags.  Used as a wrapper, with
 dnl RRA_LIB_CDB_RESTORE, around tests.
 AC_DEFUN([RRA_LIB_CDB_SWITCH], [RRA_LIB_HELPER_SWITCH([CDB])])
 
index 19016ba8efe85aef0ca1a92a977e4267a0069330..b50d75d1b8783d21227bd95cb3aeac3b2e9057e1 100644 (file)
@@ -142,4 +142,6 @@ const char *krb5_principal_get_realm(krb5_context, krb5_const_principal);
 /* Undo default visibility change. */
 #pragma GCC visibility pop
 
+END_DECLS
+
 #endif /* !PORTABLE_KRB5_H */
diff --git a/portable/mkstemp.c b/portable/mkstemp.c
new file mode 100644 (file)
index 0000000..2cbfe08
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Replacement for a missing mkstemp.
+ *
+ * Provides the same functionality as the library function mkstemp for those
+ * systems that don't have it.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+/*
+ * If we're running the test suite, rename mkstemp to avoid conflicts with the
+ * system version.  #undef it first because some systems may define it to
+ * another name.
+ */
+#if TESTING
+# undef mkstemp
+# define mkstemp test_mkstemp
+int test_mkstemp(char *);
+#endif
+
+/* Pick the longest available integer type. */
+#if HAVE_LONG_LONG_INT
+typedef unsigned long long long_int_type;
+#else
+typedef unsigned long long_int_type;
+#endif
+
+int
+mkstemp(char *template)
+{
+    static const char letters[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    size_t length;
+    char *XXXXXX;
+    struct timeval tv;
+    long_int_type randnum, working;
+    int i, tries, fd;
+
+    /*
+     * Make sure we have a valid template and initialize p to point at the
+     * beginning of the template portion of the string.
+     */
+    length = strlen(template);
+    if (length < 6) {
+        errno = EINVAL;
+        return -1;
+    }
+    XXXXXX = template + length - 6;
+    if (strcmp(XXXXXX, "XXXXXX") != 0) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    /* Get some more-or-less random information. */
+    gettimeofday(&tv, NULL);
+    randnum = ((long_int_type) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
+
+    /*
+     * Now, try to find a working file name.  We try no more than TMP_MAX file
+     * names.
+     */
+    for (tries = 0; tries < TMP_MAX; tries++) {
+        for (working = randnum, i = 0; i < 6; i++) {
+            XXXXXX[i] = letters[working % 62];
+            working /= 62;
+        }
+        fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
+        if (fd >= 0 || (errno != EEXIST && errno != EISDIR))
+            return fd;
+
+        /*
+         * This is a relatively random increment.  Cut off the tail end of
+         * tv_usec since it's often predictable.
+         */
+        randnum += (tv.tv_usec >> 10) & 0xfff;
+    }
+    errno = EEXIST;
+    return -1;
+}
index 99f9f7fc8353b0009a4261835428b7570b645778..2ba810ce8446eb9baa6fbfd198b7820771501f66 100644 (file)
@@ -6,6 +6,7 @@ perl/critic
 perl/minimum-version
 perl/strict
 portable/asprintf
+portable/mkstemp
 portable/snprintf
 portable/strndup
 tools/cdbmake-wordlist
index 8bbc29095c67ef9234233df4d870ec79a06fb1a1..8be538f95a04710c5007b988287b4a0ea387d8a0 100644 (file)
@@ -11,7 +11,7 @@
 # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
 #
 # Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2011, 2012, 2013
+# Copyright 2011, 2012, 2013, 2014
 #     The Board of Trustees of the Leland Stanford Junior University
 #
 # Permission is hereby granted, free of charge, to any person obtaining a
    fun:calloc
    fun:_dlerror_run
 }
+{
+   heimdal-gss-config
+   Memcheck:Leak
+   fun:*alloc
+   ...
+   fun:krb5_config_parse_debug
+}
+{
+   heimdal-gss-config-2
+   Memcheck:Leak
+   fun:*alloc
+   fun:_krb5_config_get_entry
+}
+{
+   heimdal-gss-krb5-init
+   Memcheck:Leak
+   fun:*alloc
+   ...
+   fun:_gsskrb5_init
+}
+{
+   heimdal-gss-load-mech
+   Memcheck:Leak
+   fun:*alloc
+   ...
+   fun:_gss_load_mech
+}
 {
    heimdal-krb5-init-context-once
    Memcheck:Leak
    fun:kg_set_ccache_name
    fun:gss_krb5int_ccache_name
 }
+{
+   mit-gss-error
+   Memcheck:Leak
+   fun:*alloc
+   ...
+   fun:krb5_gss_save_error_string
+}
 {
    mit-krb5-pkinit-openssl-init
    Memcheck:Leak
index 69182713f88b4f9b2fb91de357b166c9bad4c389..7662b08aaf96d81daaac053e3f5df3569b8155e3 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
 #
 # Check all POD documents in the tree, except for any embedded Perl module
 # distribution, for POD formatting errors.
index d588d65c9fac761f2e029328dc3b6581f51f6448..75f1928f829b353b65046b744acb65cac0cef487 100755 (executable)
@@ -52,7 +52,7 @@ automake_setup();
 use_prereq('Test::Perl::Critic');
 
 # Force the embedded Perl::Tidy check to use the correct configuration.
-local $ENV{PERLTIDY} = test_file_path('/data/perltidyrc');
+local $ENV{PERLTIDY} = test_file_path('data/perltidyrc');
 
 # Import the configuration file.
 Test::Perl::Critic->import(-profile => test_file_path('data/perlcriticrc'));
diff --git a/tests/portable/mkstemp-t.c b/tests/portable/mkstemp-t.c
new file mode 100644 (file)
index 0000000..20a83fc
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * mkstemp test suite.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <tests/tap/basic.h>
+
+int test_mkstemp(char *template);
+
+int
+main(void)
+{
+    int fd;
+    char template[] = "tsXXXXXXX";
+    char tooshort[] = "XXXXX";
+    char bad1[] = "/foo/barXXXXX";
+    char bad2[] = "/foo/barXXXXXX.out";
+    char buffer[256];
+    struct stat st1, st2;
+    ssize_t length;
+
+    plan(20);
+
+    /* First, test a few error messages. */
+    errno = 0;
+    is_int(-1, test_mkstemp(tooshort), "too short of template");
+    is_int(EINVAL, errno, "...with correct errno");
+    is_string("XXXXX", tooshort, "...and template didn't change");
+    errno = 0;
+    is_int(-1, test_mkstemp(bad1), "bad template");
+    is_int(EINVAL, errno, "...with correct errno");
+    is_string("/foo/barXXXXX", bad1, "...and template didn't change");
+    errno = 0;
+    is_int(-1, test_mkstemp(bad2), "template doesn't end in XXXXXX");
+    is_int(EINVAL, errno, "...with correct errno");
+    is_string("/foo/barXXXXXX.out", bad2, "...and template didn't change");
+    errno = 0;
+
+    /* Now try creating a real file. */
+    fd = test_mkstemp(template);
+    ok(fd >= 0, "mkstemp works with valid template");
+    ok(strcmp(template, "tsXXXXXXX") != 0, "...and template changed");
+    ok(strncmp(template, "tsX", 3) == 0, "...and didn't touch first X");
+    ok(access(template, F_OK) == 0, "...and the file exists");
+
+    /* Make sure that it's the same file as template refers to now. */
+    ok(stat(template, &st1) == 0, "...and stat of template works");
+    ok(fstat(fd, &st2) == 0, "...and stat of open file descriptor works");
+    ok(st1.st_ino == st2.st_ino, "...and they're the same file");
+    unlink(template);
+
+    /* Make sure the open mode is correct. */
+    length = strlen(template);
+    is_int(length, write(fd, template, length), "write to open file works");
+    ok(lseek(fd, 0, SEEK_SET) == 0, "...and rewind works");
+    is_int(length, read(fd, buffer, length), "...and the data is there");
+    buffer[length] = '\0';
+    is_string(template, buffer, "...and matches what we wrote");
+    close(fd);
+
+    return 0;
+}
diff --git a/tests/portable/mkstemp.c b/tests/portable/mkstemp.c
new file mode 100644 (file)
index 0000000..4632d3d
--- /dev/null
@@ -0,0 +1,2 @@
+#define TESTING 1
+#include <portable/mkstemp.c>
index 2b7959bfc854d9751774b1d4d7c58966ea900904..047c031205eb30e5446f481b4d8ec2eae50aeef7 100644 (file)
@@ -54,8 +54,8 @@
  * should be sent to the e-mail address below.  This program is part of C TAP
  * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
- * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
- *     Russ Allbery <eagle@eyrie.org>
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
+ *     2014 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"),
 /* sys/time.h must be included before sys/resource.h on some platforms. */
 #include <sys/resource.h>
 
-/* AIX doesn't have WCOREDUMP. */
+/* AIX 6.1 (and possibly later) doesn't have WCOREDUMP. */
 #ifndef WCOREDUMP
 # define WCOREDUMP(status) ((unsigned)(status) & 0x80)
 #endif
 
+/*
+ * POSIX requires that these be defined in <unistd.h>, but they're not always
+ * available.  If one of them has been defined, all the rest almost certainly
+ * have.
+ */
+#ifndef STDIN_FILENO
+# define STDIN_FILENO  0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+#endif
+
 /*
  * Used for iterating through arrays.  Returns the number of elements in the
  * array (useful for a < upper bound in a for loop).
@@ -145,7 +156,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 {
@@ -399,36 +411,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;
@@ -835,6 +873,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)");
@@ -1132,8 +1171,7 @@ free_testset(struct testset *ts)
     free(ts->file);
     free(ts->path);
     free(ts->results);
-    if (ts->reason != NULL)
-        free(ts->reason);
+    free(ts->reason);
     free(ts);
 }
 
index bb51606b639b08350820bad6936386437b713c01..bedcc35daaeb1f17b54cf607cb618fd77810a3a1 100644 (file)
@@ -13,7 +13,7 @@
  * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
  * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013
+ * Copyright 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
@@ -101,6 +101,21 @@ struct cleanup_func {
 };
 static struct cleanup_func *cleanup_funcs = NULL;
 
+/*
+ * Registered diag files.  Any output found in these files will be printed out
+ * as if it were passed to diag() before any other output we do.  This allows
+ * background processes to log to a file and have that output interleved 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
@@ -120,6 +135,76 @@ static struct cleanup_func *cleanup_funcs = NULL;
     } while (0)
 
 
+/*
+ * 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 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) {
+            if (fgets(file->buffer, file->bufsize, 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.
+             */
+            length = strlen(file->buffer);
+            if (file->buffer[length - 1] != '\n') {
+                if (length < file->bufsize - 1)
+                    incomplete = 1;
+                else {
+                    file->bufsize += BUFSIZ;
+                    file->buffer = brealloc(file->buffer, file->bufsize);
+                }
+
+                /*
+                 * On either incomplete lines or too small of a buffer, rewind
+                 * and read the file again (on the next pass, if incomplete).
+                 * It's simpler than trying to double-buffer the file.
+                 */
+                if (fsetpos(file->file, &where) < 0)
+                    sysbail("cannot set position in %s", file->name);
+                continue;
+            }
+
+            /* We saw a complete line.  Print it out. */
+            printf("# %s", file->buffer);
+        }
+    }
+}
+
+
 /*
  * Our exit handler.  Called on completion of the test to report a summary of
  * results provided we're still in the original process.  This also handles
@@ -130,22 +215,25 @@ static struct cleanup_func *cleanup_funcs = NULL;
 static void
 finish(void)
 {
-    int success;
+    int success, primary;
     struct cleanup_func *current;
     unsigned long highest = testnum - 1;
-
-    /*
-     * Don't do anything except free the cleanup functions if we aren't the
-     * primary process (the process in which plan or plan_lazy was called).
-     */
-    if (_process != 0 && getpid() != _process) {
-        while (cleanup_funcs != NULL) {
-            current = cleanup_funcs;
-            cleanup_funcs = cleanup_funcs->next;
-            free(current);
-        }
-        return;
+    struct diag_file *file, *tmp;
+
+    /* Check for pending diag_file output. */
+    check_diag_files();
+
+    /* Free the diag_files. */
+    file = diag_files;
+    while (file != NULL) {
+        tmp = file;
+        file = file->next;
+        fclose(tmp->file);
+        free(tmp->name);
+        free(tmp->buffer);
+        free(tmp);
     }
+    diag_files = NULL;
 
     /*
      * Determine whether all tests were successful, which is needed before
@@ -157,14 +245,20 @@ finish(void)
 
     /*
      * If there are any registered cleanup functions, we run those first.  We
-     * always run them, even if we didn't run a test.
+     * always run them, even if we didn't run a test.  Don't do anything
+     * except free the diag_files and call cleanup functions if we aren't the
+     * primary process (the process in which plan or plan_lazy was called),
+     * and tell the cleanup functions that fact.
      */
+    primary = (_process == 0 || getpid() == _process);
     while (cleanup_funcs != NULL) {
-        cleanup_funcs->func(success);
+        cleanup_funcs->func(success, primary);
         current = cleanup_funcs;
         cleanup_funcs = cleanup_funcs->next;
         free(current);
     }
+    if (!primary)
+        return;
 
     /* Don't do anything further if we never planned a test. */
     if (_planned == 0)
@@ -198,7 +292,8 @@ finish(void)
 
 /*
  * Initialize things.  Turns on line buffering on stdout and then prints out
- * the number of tests in the test suite.
+ * the number of tests in the test suite.  We intentionally don't check for
+ * pending diag_file output here, since it should really come after the plan.
  */
 void
 plan(unsigned long count)
@@ -236,7 +331,8 @@ plan_lazy(void)
 
 /*
  * Skip the entire test suite and exits.  Should be called instead of plan(),
- * not after it, since it prints out a special plan line.
+ * not after it, since it prints out a special plan line.  Ignore diag_file
+ * output here, since it's not clear if it's allowed before the plan.
  */
 void
 skip_all(const char *format, ...)
@@ -257,6 +353,7 @@ void
 ok(int success, const char *format, ...)
 {
     fflush(stderr);
+    check_diag_files();
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
@@ -272,6 +369,7 @@ void
 okv(int success, const char *format, va_list args)
 {
     fflush(stderr);
+    check_diag_files();
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
@@ -290,6 +388,7 @@ void
 skip(const char *reason, ...)
 {
     fflush(stderr);
+    check_diag_files();
     printf("ok %lu # skip", testnum++);
     PRINT_DESC(" ", reason);
     putchar('\n');
@@ -305,6 +404,7 @@ ok_block(unsigned long count, int status, 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)
@@ -324,6 +424,7 @@ skip_block(unsigned long count, const char *reason, ...)
     unsigned long i;
 
     fflush(stderr);
+    check_diag_files();
     for (i = 0; i < count; i++) {
         printf("ok %lu # skip", testnum++);
         PRINT_DESC(" ", reason);
@@ -340,6 +441,7 @@ void
 is_int(long wanted, long seen, const char *format, ...)
 {
     fflush(stderr);
+    check_diag_files();
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
@@ -365,6 +467,7 @@ is_string(const char *wanted, const char *seen, const char *format, ...)
     if (seen == NULL)
         seen = "(null)";
     fflush(stderr);
+    check_diag_files();
     if (strcmp(wanted, seen) == 0)
         printf("ok %lu", testnum++);
     else {
@@ -386,6 +489,7 @@ void
 is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
 {
     fflush(stderr);
+    check_diag_files();
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
@@ -409,6 +513,7 @@ bail(const char *format, ...)
 
     _aborted = 1;
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
@@ -430,6 +535,7 @@ sysbail(const char *format, ...)
 
     _aborted = 1;
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
@@ -449,6 +555,7 @@ diag(const char *format, ...)
     va_list args;
 
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("# ");
     va_start(args, format);
@@ -468,6 +575,7 @@ sysdiag(const char *format, ...)
     int oerrno = errno;
 
     fflush(stderr);
+    check_diag_files();
     fflush(stdout);
     printf("# ");
     va_start(args, format);
@@ -477,6 +585,57 @@ sysdiag(const char *format, ...)
 }
 
 
+/*
+ * 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;
+    }
+}
+
+
 /*
  * Allocate cleared memory, reporting a fatal error with bail on failure.
  */
@@ -606,8 +765,7 @@ test_file_path(const char *file)
 void
 test_file_path_free(char *path)
 {
-    if (path != NULL)
-        free(path);
+    free(path);
 }
 
 
@@ -649,9 +807,9 @@ test_tmpdir(void)
 void
 test_tmpdir_free(char *path)
 {
-    rmdir(path);
     if (path != NULL)
-        free(path);
+        rmdir(path);
+    free(path);
 }
 
 
index 92d348a15a0c6af752e66d796b0cd57e331a44ad..166804b40cc400257d1c0ea687ee828b1ed86405 100644 (file)
@@ -5,7 +5,7 @@
  * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
  * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * Copyright 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
@@ -101,6 +101,18 @@ void diag(const char *format, ...)
 void sysdiag(const char *format, ...)
     __attribute__((__nonnull__, __format__(printf, 1, 2)));
 
+/*
+ * Register or unregister a file that contains supplementary diagnostics.
+ * Before any other output, all registered files will be read, line by line,
+ * and each line will be reported as a diagnostic as if it were passed to
+ * diag().  Nul characters are not supported in these files and will result in
+ * truncated output.
+ */
+void diag_file_add(const char *file)
+    __attribute__((__nonnull__));
+void diag_file_remove(const char *file)
+    __attribute__((__nonnull__));
+
 /* Allocate memory, reporting a fatal error with bail on failure. */
 void *bcalloc(size_t, size_t)
     __attribute__((__alloc_size__(1, 2), __malloc__, __warn_unused_result__));
@@ -132,11 +144,14 @@ void test_tmpdir_free(char *path);
 /*
  * Register a cleanup function that is called when testing ends.  All such
  * registered functions will be run during atexit handling (and are therefore
- * subject to all the same constraints and caveats as atexit functions).  The
- * function must return void and will be passed one argument, an int that will
- * be true if the test completed successfully and false otherwise.
+ * subject to all the same constraints and caveats as atexit functions).
+ *
+ * The function must return void and will be passed two argument, an int that
+ * will be true if the test completed successfully and false otherwise, and an
+ * int that will be true if the cleanup function is run in the primary process
+ * (the one that called plan or plan_lazy) and false otherwise.
  */
-typedef void (*test_cleanup_func)(int);
+typedef void (*test_cleanup_func)(int, int);
 void test_cleanup_register(test_cleanup_func)
     __attribute__((__nonnull__));
 
index 58315ee2ca9c302d050134c0dbb99661163af761..578a8583cb50518310462fd228706a106973e956 100644 (file)
@@ -15,7 +15,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013
+ * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -47,6 +47,7 @@
 
 #include <tests/tap/basic.h>
 #include <tests/tap/kerberos.h>
+#include <tests/tap/macros.h>
 #include <tests/tap/process.h>
 #include <tests/tap/string.h>
 
@@ -201,33 +202,23 @@ kerberos_kinit(void)
 
 
 /*
- * Clean up at the end of a test.  This removes the ticket cache and resets
- * and frees the memory allocated for the environment variables so that
- * valgrind output on test suites is cleaner.
+ * Free all the memory associated with our Kerberos setup, but don't remove
+ * the ticket cache.  This is used when cleaning up on exit from a non-primary
+ * process so that test programs that fork don't remove the ticket cache still
+ * used by the main program.
  */
-void
-kerberos_cleanup(void)
+static void
+kerberos_free(void)
 {
-    char *path;
-
-    if (tmpdir_ticket != NULL) {
-        basprintf(&path, "%s/krb5cc_test", tmpdir_ticket);
-        unlink(path);
-        free(path);
-        test_tmpdir_free(tmpdir_ticket);
-        tmpdir_ticket = NULL;
-    }
+    test_tmpdir_free(tmpdir_ticket);
+    tmpdir_ticket = NULL;
     if (config != NULL) {
-        if (config->keytab != NULL) {
-            test_file_path_free(config->keytab);
-            free(config->principal);
-            free(config->cache);
-        }
-        if (config->userprinc != NULL) {
-            free(config->userprinc);
-            free(config->username);
-            free(config->password);
-        }
+        test_file_path_free(config->keytab);
+        free(config->principal);
+        free(config->cache);
+        free(config->userprinc);
+        free(config->username);
+        free(config->password);
         free(config);
         config = NULL;
     }
@@ -244,6 +235,42 @@ kerberos_cleanup(void)
 }
 
 
+/*
+ * Clean up at the end of a test.  This removes the ticket cache and resets
+ * and frees the memory allocated for the environment variables so that
+ * valgrind output on test suites is cleaner.  Most of the work is done by
+ * kerberos_free, but this function also deletes the ticket cache.
+ */
+void
+kerberos_cleanup(void)
+{
+    char *path;
+
+    if (tmpdir_ticket != NULL) {
+        basprintf(&path, "%s/krb5cc_test", tmpdir_ticket);
+        unlink(path);
+        free(path);
+    }
+    kerberos_free();
+}
+
+
+/*
+ * The cleanup handler for the TAP framework.  Call kerberos_cleanup if we're
+ * in the primary process and kerberos_free if not.  The first argument, which
+ * indicates whether the test succeeded or not, is ignored, since we need to
+ * do the same thing either way.
+ */
+static void
+kerberos_cleanup_handler(int success UNUSED, int primary)
+{
+    if (primary)
+        kerberos_cleanup();
+    else
+        kerberos_free();
+}
+
+
 /*
  * Obtain Kerberos tickets for the principal specified in config/principal
  * using the keytab specified in config/keytab, both of which are presumed to
@@ -321,15 +348,13 @@ kerberos_setup(enum kerberos_needs needs)
         *config->realm = '\0';
         config->realm++;
     }
-    if (path != NULL)
-        test_file_path_free(path);
+    test_file_path_free(path);
 
     /*
-     * Register the cleanup function as an atexit handler so that the caller
-     * doesn't have to worry about cleanup.
+     * Register the cleanup function so that the caller doesn't have to do
+     * explicit cleanup.
      */
-    if (atexit(kerberos_cleanup) != 0)
-        sysdiag("cannot register cleanup function");
+    test_cleanup_register(kerberos_cleanup_handler);
 
     /* Return the configuration. */
     return config;
@@ -357,10 +382,8 @@ kerberos_cleanup_conf(void)
         tmpdir_conf = NULL;
     }
     putenv((char *) "KRB5_CONFIG=");
-    if (krb5_config != NULL) {
-        free(krb5_config);
-        krb5_config = NULL;
-    }
+    free(krb5_config);
+    krb5_config = NULL;
 }
 
 
index c34f891005e2e5775f2d9ed41ae14ae93d76ab68..8be0add4c10cef80bcf2a99d7a02843b0623bc50 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, 2009, 2011, 2012, 2013
+ * Copyright 2006, 2007, 2009, 2011, 2012, 2013, 2014
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -73,11 +73,11 @@ BEGIN_DECLS
  * the principal field will be NULL.  If the files exist but loading them
  * fails, or authentication fails, kerberos_setup calls bail.
  *
- * kerberos_cleanup will be set up to run from an atexit handler.  This means
- * that any child processes that should not remove the Kerberos ticket cache
- * should call _exit instead of exit.  The principal will be automatically
- * freed when kerberos_cleanup is called or if kerberos_setup is called again.
- * The caller doesn't need to worry about it.
+ * kerberos_cleanup will be run as a cleanup function normally, freeing all
+ * resources and cleaning up temporary files on process exit.  It can,
+ * however, be called directly if for some reason the caller needs to delete
+ * the Kerberos environment again.  However, normally the caller can just call
+ * kerberos_setup again.
  */
 struct kerberos_config *kerberos_setup(enum kerberos_needs)
     __attribute__((__malloc__));
index b252250e6a0623eb32a2bec5919ce8c36db0b761..3b2a502653a1c90ed67c7c33d0809b1981688f4c 100644 (file)
@@ -56,7 +56,7 @@ BEGIN {
     # This version should match the corresponding rra-c-util release, but with
     # two digits for the minor version, including a leading zero if necessary,
     # so that it will sort properly.
-    $VERSION = '4.12';
+    $VERSION = '5.02';
 }
 
 # Skip this test unless maintainer tests are requested.  Takes a short
index b8ce0958b02c16e2a21968bf43b686221f5f0416..003e9cde0380a4b63434066e9e818a96180f5197 100644 (file)
@@ -87,7 +87,7 @@ BEGIN {
     # This version should match the corresponding rra-c-util release, but with
     # two digits for the minor version, including a leading zero if necessary,
     # so that it will sort properly.
-    $VERSION = '4.12';
+    $VERSION = '5.02';
 }
 
 # Perl directories to skip globally for perl_dirs.  We ignore the perl
index b8d6ff96dce2c5d9dbdf19be3a389c5b3a7ccdfb..54e714aa999a8c5f03894e9853a3e38f7bc7dbdc 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, 2013
+ * Copyright 2002, 2004, 2005, 2013 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2009, 2010, 2011, 2013, 2014
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
 #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>
+#include <sys/time.h>
 #include <sys/wait.h>
 
 #include <tests/tap/basic.h>
 #include <tests/tap/process.h>
 #include <tests/tap/string.h>
 
+/* May be defined by the build system. */
+#ifndef PATH_FAKEROOT
+# define PATH_FAKEROOT ""
+#endif
+
+/* How long to wait for the process to start in seconds. */
+#define PROCESS_WAIT 10
+
+/*
+ * Used to store information about a background process.  This contains
+ * everything required to stop the process and clean up after it.
+ */
+struct process {
+    pid_t pid;                  /* PID of child process */
+    char *pidfile;              /* PID file to delete on process stop */
+    char *tmpdir;               /* Temporary directory for log file */
+    char *logfile;              /* Log file of process output */
+    bool is_child;              /* Whether we can waitpid for process */
+    struct process *next;       /* Next process in global list */
+};
+
+/*
+ * Global list of started processes, which will be cleaned up automatically on
+ * program exit if they haven't been explicitly stopped with process_stop
+ * prior to that point.
+ */
+static struct process *processes = NULL;
+
 
 /*
  * Given a function, an expected exit status, and expected output, runs that
@@ -178,3 +217,306 @@ run_setup(const char *const argv[])
     }
     free(output);
 }
+
+
+/*
+ * Free the resources associated with tracking a process, without doing
+ * anything to the process.  This is kept separate so that we can free
+ * resources during shutdown in a non-primary process.
+ */
+static void
+process_free(struct process *process)
+{
+    struct process **prev;
+
+    /* 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, size;
+    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++)
+            ;
+        size = 2 + i + 1;
+        fakeroot_argv = bmalloc(size * 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 ed903451c00ede6be9b1c65f456b3b6d5df1a062..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
@@ -33,6 +33,9 @@
 #include <config.h>
 #include <tests/tap/macros.h>
 
+/* Opaque data type for process_start and friends. */
+struct process;
+
 BEGIN_DECLS
 
 /*
@@ -60,6 +63,32 @@ void is_function_output(test_function_type, void *data, int status,
 void run_setup(const char *const argv[])
     __attribute__((__nonnull__));
 
+/*
+ * process_start starts a process in the background, returning an opaque data
+ * struct that can be used to stop the process later.  The standard output and
+ * standard error of the process will be sent to a log file registered with
+ * diag_file_add, so its output will be properly interleaved with the test
+ * case output.
+ *
+ * The process should create a PID file in the path given as the second
+ * argument when it's finished initialization.
+ *
+ * process_start_fakeroot is the same but starts the process under fakeroot.
+ * PATH_FAKEROOT must be defined (generally by Autoconf).  If fakeroot is not
+ * found, process_start_fakeroot will call skip_all, so be sure to call this
+ * function before plan.
+ *
+ * process_stop can be called to explicitly stop the process.  If it isn't
+ * called by the test program, it will be called automatically when the
+ * program exits.
+ */
+struct process *process_start(const char *const argv[], const char *pidfile)
+    __attribute__((__nonnull__));
+struct process *process_start_fakeroot(const char *const argv[],
+                                       const char *pidfile)
+    __attribute__((__nonnull__));
+void process_stop(struct process *);
+
 END_DECLS
 
 #endif /* TAP_PROCESS_H */
index 769166fc4c01b4cd0cb33a13092ce129d0b205e4..4dac8236ca030cc3e5eee756ad4e3e1a3925eb21 100644 (file)
@@ -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.
  */
index 9ea1a8b4272647ba20e8f148af8427b3d65e38ab..8c731b7169aabe4058f0d089b61bbb2c46cdf29f 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, 2013
+ * Copyright 2008, 2010, 2013, 2014
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -72,6 +72,12 @@ 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