openafs/tests/common/exec.c
Andrew Deason 71acc392a3 tests: Generalize temp dir management
Currently, afstest_BuildTestConfig calls afstest_mkdtemp (our thin
wrapper around mkdtemp) to create its temporary config dir. We may
want to make new tests, though, that create a temp dir for other
purposes. To make that easier, move a little more code into
afstest_mkdtemp, so the caller doesn't need to construct the template.

To allow callers to clean up such temporary dirs, change
afstest_UnlinkTestConfig into a more general function,
afstest_rmdtemp. Allow this new function to remove all files in a dir,
not just files one-level-deep. To avoid needing to write our own
traversal and removal logic, just run 'rm -rf' via a new function,
afstest_systemlp().

Move these temp dir-related functions from config.c into files.c,
since they are no longer specific to config dirs.

Change-Id: I16750a2f30e98c9ca2e14dfb7d3fc9bc5d456e8d
Reviewed-on: https://gerrit.openafs.org/14632
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: BuildBot <buildbot@rampaginggeek.com>
2021-08-13 17:50:23 -04:00

277 lines
6.8 KiB
C

/*
* Copyright (c) 2020 Sine Nomine Associates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*!
* Common exec-related functions for testing programs
*/
#include <afsconfig.h>
#include <afs/param.h>
#include <roken.h>
#include <afs/opr.h>
#include <afs/opr.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <tests/tap/basic.h>
#include "common.h"
static void
afstest_command_start(struct afstest_cmdinfo *cmdinfo)
{
int code;
int pipefd[2];
if (cmdinfo->fd == STDOUT_FILENO) {
cmdinfo->fd_descr = "stdout";
} else if (cmdinfo->fd == STDERR_FILENO) {
cmdinfo->fd_descr = "stderr";
} else {
bail("unknown exec fd %d", cmdinfo->fd);
}
code = pipe(pipefd);
if (code < 0) {
sysbail("pipe");
}
cmdinfo->child = fork();
if (cmdinfo->child < 0) {
sysbail("fork");
}
if (cmdinfo->child == 0) {
/* Child pid */
char *cmd = "/bin/sh";
char *argv[] = { "sh", "-c", cmdinfo->command, NULL };
/* Redirect stdout/stderr to our pipe */
code = dup2(pipefd[1], cmdinfo->fd);
if (code < 0) {
sysdiag("dup2");
exit(1);
}
close(pipefd[0]);
close(pipefd[1]);
execv(cmd, argv);
sysdiag("execv");
exit(1);
}
/* Parent pid */
close(pipefd[1]);
cmdinfo->pipe_fh = fdopen(pipefd[0], "r");
if (cmdinfo->pipe_fh == NULL) {
sysbail("fdopen");
}
}
static int
command_end_v(struct afstest_cmdinfo *cmdinfo, const char *format, va_list args)
{
int code;
int success;
size_t buflen;
char *buf = NULL;
int nbytes;
int status = -1;
opr_Assert(cmdinfo->child != 0);
/*
* Read 1 byte more than the 'cmdinfo->output' string. If that's less than
* 4095 bytes, read 4095 bytes (just so we read a decent amount of data, in
* case we unexpectedly get a lot of data back).
*/
buflen = strlen(cmdinfo->output)+1;
if (buflen < 4095) {
buflen = 4095;
}
buf = calloc(1, buflen+1);
if (buf == NULL) {
sysbail("calloc");
}
/*
* Note that we must read the return value of fread() into something,
* because it's declared with warn_unused_result in some glibc. We don't
* actually care about the result, but pretend to care so we don't trigger
* a warning.
*/
nbytes = fread(buf, 1, buflen, cmdinfo->pipe_fh);
if (ferror(cmdinfo->pipe_fh) != 0) {
bail("error reading from %s pipe (read %d bytes)",
cmdinfo->fd_descr, nbytes);
}
fclose(cmdinfo->pipe_fh);
cmdinfo->pipe_fh = NULL;
code = waitpid(cmdinfo->child, &status, 0);
if (code <= 0) {
sysbail("waitpid");
}
success = 0;
if (WIFEXITED(status) && WEXITSTATUS(status) != cmdinfo->exit_code) {
int code = WEXITSTATUS(status);
diag("command %s:", cmdinfo->command);
diag("exit code wanted: %d", cmdinfo->exit_code);
diag(" exit code seen: %d", code);
diag(" ");
diag("%s: %s", cmdinfo->fd_descr, buf);
} else if (WIFSIGNALED(status)) {
diag("command %s was killed by signal %d", cmdinfo->command,
WTERMSIG(status));
} else if (!WIFEXITED(status)) {
/* handle non-exited, non-signalled, just in case */
diag("command %s died with weird status 0x%x", cmdinfo->command,
status);
} else if (strcmp(cmdinfo->output, buf) != 0) {
diag("command %s:", cmdinfo->command);
diag("wanted %s: %s", cmdinfo->fd_descr, cmdinfo->output);
diag(" seen %s: %s", cmdinfo->fd_descr, buf);
} else {
success = 1;
}
okv(success, format, args);
cmdinfo->child = 0;
free(buf);
buf = NULL;
return success;
}
/**
* Test if running the given command matches the given output and exit code.
*
* @param[in] cmdinfo Command to run, and expected output and exit code
* @param[in] format Message to print for the test (a la 'is_int' etc)
* @return whether the test succeeded
*/
int
is_command(struct afstest_cmdinfo *cmdinfo, const char *format, ...)
{
va_list args;
int ret;
afstest_command_start(cmdinfo);
va_start(args, format);
ret = command_end_v(cmdinfo, format, args);
va_end(args);
return ret;
}
static int
do_systemvp(char *command, char **argv)
{
pid_t child;
child = fork();
if (child < 0) {
sysbail("fork");
}
if (child > 0) {
int status = -1;
/* parent */
if (waitpid(child, &status, 0) <= 0) {
sysbail("waitpid");
}
return status;
}
execvp(command, argv);
sysbail("execvp");
exit(1);
}
/*
* This function is mostly the same as system(), in that the given command is
* run with the same stdout, stderr, etc, and we return the exit status when it
* finishes running. But instead of giving the command as a single string,
* interpreted by the shell, the command is given as a list of arguments, like
* execlp().
*
* For example, the following two are equivalent:
*
* code = afstest_systemlp("echo", "foo", "bar", (char*)NULL);
* code = system("echo foo bar");
*
* Except that with afstest_systemlp, the arguments don't go through the shell,
* so you don't need to worry about quoting arguments or escaping shell
* metacharacters.
*/
int
afstest_systemlp(char *command, ...)
{
va_list ap;
int n_args = 1;
int arg_i;
int status;
char **argv;
va_start(ap, command);
while (va_arg(ap, char*) != NULL) {
n_args++;
}
va_end(ap);
argv = calloc(n_args + 1, sizeof(argv[0]));
opr_Assert(argv != NULL);
argv[0] = command;
va_start(ap, command);
for (arg_i = 1; arg_i < n_args; arg_i++) {
argv[arg_i] = va_arg(ap, char*);
opr_Assert(argv[arg_i] != NULL);
}
va_end(ap);
/* Note that argv[n_args] is NULL, since we allocated an extra arg in
* calloc(), above. */
status = do_systemvp(command, argv);
free(argv);
return status;
}