/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
$(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
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
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
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.
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
[#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])
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])])
/* Undo default visibility change. */
#pragma GCC visibility pop
+END_DECLS
+
#endif /* !PORTABLE_KRB5_H */
--- /dev/null
+/*
+ * 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;
+}
perl/minimum-version
perl/strict
portable/asprintf
+portable/mkstemp
portable/snprintf
portable/strndup
tools/cdbmake-wordlist
# 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
-#!/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.
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'));
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#define TESTING 1
+#include <portable/mkstemp.c>
* 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).
/* 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 {
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;
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)");
free(ts->file);
free(ts->path);
free(ts->results);
- if (ts->reason != NULL)
- free(ts->reason);
+ free(ts->reason);
free(ts);
}
* 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
};
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
} 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
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
/*
* 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)
/*
* 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)
/*
* 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, ...)
ok(int success, const char *format, ...)
{
fflush(stderr);
+ check_diag_files();
printf("%sok %lu", success ? "" : "not ", testnum++);
if (!success)
_failed++;
okv(int success, const char *format, va_list args)
{
fflush(stderr);
+ check_diag_files();
printf("%sok %lu", success ? "" : "not ", testnum++);
if (!success)
_failed++;
skip(const char *reason, ...)
{
fflush(stderr);
+ check_diag_files();
printf("ok %lu # skip", testnum++);
PRINT_DESC(" ", reason);
putchar('\n');
unsigned long i;
fflush(stderr);
+ check_diag_files();
for (i = 0; i < count; i++) {
printf("%sok %lu", status ? "" : "not ", testnum++);
if (!status)
unsigned long i;
fflush(stderr);
+ check_diag_files();
for (i = 0; i < count; i++) {
printf("ok %lu # skip", testnum++);
PRINT_DESC(" ", reason);
is_int(long wanted, long seen, const char *format, ...)
{
fflush(stderr);
+ check_diag_files();
if (wanted == seen)
printf("ok %lu", testnum++);
else {
if (seen == NULL)
seen = "(null)";
fflush(stderr);
+ check_diag_files();
if (strcmp(wanted, seen) == 0)
printf("ok %lu", testnum++);
else {
is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
{
fflush(stderr);
+ check_diag_files();
if (wanted == seen)
printf("ok %lu", testnum++);
else {
_aborted = 1;
fflush(stderr);
+ check_diag_files();
fflush(stdout);
printf("Bail out! ");
va_start(args, format);
_aborted = 1;
fflush(stderr);
+ check_diag_files();
fflush(stdout);
printf("Bail out! ");
va_start(args, format);
va_list args;
fflush(stderr);
+ check_diag_files();
fflush(stdout);
printf("# ");
va_start(args, format);
int oerrno = errno;
fflush(stderr);
+ check_diag_files();
fflush(stdout);
printf("# ");
va_start(args, 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.
*/
void
test_file_path_free(char *path)
{
- if (path != NULL)
- free(path);
+ free(path);
}
void
test_tmpdir_free(char *path)
{
- rmdir(path);
if (path != NULL)
- free(path);
+ rmdir(path);
+ free(path);
}
* 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
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__));
/*
* 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__));
* 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
#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>
/*
- * 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;
}
}
+/*
+ * 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
*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;
tmpdir_conf = NULL;
}
putenv((char *) "KRB5_CONFIG=");
- if (krb5_config != NULL) {
- free(krb5_config);
- krb5_config = NULL;
- }
+ free(krb5_config);
+ krb5_config = NULL;
}
* 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
* 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__));
# 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
# 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
* 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
}
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);
+}
* 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
#include <config.h>
#include <tests/tap/macros.h>
+/* Opaque data type for process_start and friends. */
+struct process;
+
BEGIN_DECLS
/*
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 */
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.
*/
* 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")
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