--- /dev/null
+/*
+ * Copyright 2010, Sine Nomine Associates and others.
+ * All Rights Reserved.
+ *
+ * This software has been released under the terms of the IBM Public
+ * License. For details, see the LICENSE file in the top-level source
+ * directory or online at http://www.openafs.org/dl/license10.html
+ */
+
+/* exec-related helper routines */
+
+#include <afsconfig.h>
+#include <afs/param.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* helper for afs_exec_alt; just constructs the string for the 'alternate'
+ * program */
+static char *
+construct_alt(const char *argv0, const char *prefix, const char *suffix)
+{
+ size_t prefix_len = strlen(prefix);
+ size_t suffix_len = strlen(suffix);
+ size_t replace_len;
+ char *alt, *replace;
+
+ alt = malloc(strlen(argv0) + prefix_len + suffix_len + 1);
+ if (!alt) {
+ return NULL;
+ }
+ strcpy(alt, argv0);
+
+ replace = strrchr(alt, '/');
+ if (replace) {
+ replace++;
+ } else {
+ replace = alt;
+ }
+
+ replace_len = strlen(replace);
+
+ memmove(replace + prefix_len, replace, replace_len);
+ memcpy(replace, prefix, prefix_len);
+ /* basically strcat(replace, suffix), but a touch faster */
+ memcpy(replace + prefix_len + replace_len, suffix, suffix_len);
+
+ return alt;
+}
+
+/**
+ * execute an alternate version of the running program.
+ *
+ * This takes the current argc/argv, and executes an "anternate" version
+ * of the current program ("alternate" meaning e.g. DAFS/non-DAFS,
+ * 32-bit/64-bit, etc). For example, if the current running program is
+ * fssync-debug, running afs_exec_alt(argc, argc, "da", NULL) will try
+ * to execute a "dafssync-debug" program, if it can be run.
+ *
+ * @param[in] argc argc for the current program
+ * @param[in] argv argv for the current program
+ * @param[in] prefix prefix for the alternate program (or NULL)
+ * @param[in] suffix suffix for the alternate program (or NULL)
+ *
+ * @return the program that we failed to run, or NULL if we failed
+ * before constructing it. This string must be freed by the caller
+ *
+ * @pre at least one of 'prefix' or 'suffix' is non-NULL and non-empty
+ *
+ * @note if successful, never returns
+ */
+char *
+afs_exec_alt(int argc, char **argv, const char *prefix, const char *suffix)
+{
+ char **targv = NULL;
+ char *ret = NULL;
+ int errno_save;
+ int i = 0;
+
+ if (!prefix) {
+ prefix = "";
+ }
+ if (!suffix) {
+ suffix = "";
+ }
+
+ if (prefix[0] == '\0' && suffix[0] == '\0') {
+ /* we'd be exec'ing ourselves; that's no good */
+ errno = EINVAL;
+ return NULL;
+ }
+
+ targv = malloc(sizeof(char*) * (argc+1));
+ if (!targv) {
+ goto error;
+ }
+
+ targv[0] = construct_alt(argv[0], prefix, suffix);
+ if (!targv[0]) {
+ goto error;
+ }
+
+ for (i = 1; i < argc; i++) {
+ targv[i] = strdup(argv[i]);
+ if (!targv[i]) {
+ goto error;
+ }
+ }
+ targv[i] = NULL;
+
+ execvp(targv[0], targv);
+
+ error:
+ /* we only call free() beyond this point, which I don't think would ever
+ * muck with errno, but I don't trust everyone's libc... */
+ errno_save = errno;
+ if (targv) {
+ i--;
+ for (; i >= 1; i--) {
+ free(targv[i]);
+ }
+ ret = targv[0];
+ free(targv);
+ }
+ errno = errno_save;
+ return ret;
+}
--- /dev/null
+/*
+ * Copyright 2010, Sine Nomine Associates and others.
+ * All Rights Reserved.
+ *
+ * This software has been released under the terms of the IBM Public
+ * License. For details, see the LICENSE file in the top-level source
+ * directory or online at http://www.openafs.org/dl/license10.html
+ */
+
+#include <afsconfig.h>
+#include <afs/param.h>
+
+#include <afs/afsutil.h>
+#include <tap/basic.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define FAILSTR "exec test failure\n"
+#define ARGSTRING "teststring"
+
+static struct exec_test {
+ const char *prefix; /* program prefix to run */
+ const char *suffix; /* program suffix to run */
+ const char *result; /* expected output from stdout from the child program,
+ or NULL if we should fail to run anything */
+} tests[] = {
+ { "exec-test1.", NULL, "exec test 1\n" },
+ { NULL, ".exec-test2", "exec test 2\n" },
+ { "exec-test3.", ".suffix", "exec test 3\n" },
+ { NULL, NULL, NULL },
+ { "nonexistent", NULL, NULL },
+};
+
+static char*
+create_child_script(const char *argv0, struct exec_test *test)
+{
+ char *script;
+ char *dirc, *basec, *bname, *dname;
+ const char *prefix, *suffix;
+ int fd;
+ FILE *fh;
+
+ if (!test->result) {
+ return NULL;
+ }
+
+ dirc = strdup(argv0);
+ basec = strdup(argv0);
+ script = malloc(PATH_MAX);
+
+ if (!dirc || !basec || !script) {
+ sysbail("malloc");
+ }
+
+ dname = dirname(dirc);
+ bname = basename(basec);
+
+ prefix = test->prefix;
+ if (!prefix) {
+ prefix = "";
+ }
+ suffix = test->suffix;
+ if (!suffix) {
+ suffix = "";
+ }
+
+ sprintf(script, "%s/%s%s%s", dname, prefix, bname, suffix);
+
+ free(dirc);
+ free(basec);
+
+ fd = open(script, O_WRONLY | O_CREAT | O_EXCL, 0770);
+ if (fd < 0) {
+ sysbail("open(%s)", script);
+ }
+
+ fh = fdopen(fd, "w");
+ if (!fh) {
+ sysbail("fdopen");
+ }
+
+ fprintf(fh, "#!/bin/sh\n"
+"if test x\"$1\" = x%s ; then\n"
+" echo %s\n"
+"else\n"
+" exit 1\n"
+"fi\n", ARGSTRING, test->result);
+
+ fclose(fh);
+
+ return script;
+}
+
+int
+main(int argc, char **argv)
+{
+ int fds[2];
+ pid_t pid;
+ char *child_argv[3];
+ int child_argc = 2;
+ char buf[1024];
+ int i;
+ unsigned long nTests = sizeof(tests) / sizeof(tests[0]);
+
+ if (argc != 1) {
+ /* in case afs_exec_alt tries to run us again */
+ exit(1);
+ }
+
+ child_argv[0] = argv[0];
+ child_argv[1] = ARGSTRING;
+ child_argv[2] = NULL;
+
+ plan(nTests * 4);
+
+ /*
+ * Testing afs_exec_alt is a little interesting, since a successful run
+ * means that we exec() someone else. Our general strategy is to fork and
+ * run some generated shell scripts that just output a (known) string and
+ * exit successfully. We have each of those scripts output something
+ * different so we can make sure we're executing the right one.
+ *
+ * This isn't 100% foolproof, since afs_exec_alt could bug out and exec
+ * some entirely different program, which happens to have the exact same
+ * behavior as our shell scripts. But we've tried to make that unlikely.
+ *
+ * The shell scripts are passed exactly one argument: 'teststring'. All
+ * they should do is see if that were given that argument, and if so,
+ * they should print out the expected 'result' string to stdout, and
+ * should exit with successful status.
+ */
+
+ for (i = 0; i < nTests; i++) {
+ char *child_script;
+ struct exec_test *t;
+
+ t = &tests[i];
+
+ if (pipe(fds)) {
+ sysbail("pipe");
+ }
+
+ child_script = create_child_script(argv[0], t);
+
+ pid = fork();
+ if (pid < 0) {
+ sysbail("fork");
+ }
+
+ if (pid == 0) {
+ char *prog;
+
+ if (close(fds[0])) {
+ printf("child: close(%d) failed: %s", fds[0], strerror(errno));
+ }
+
+ /* child */
+ if (dup2(fds[1], 1) < 0) {
+ printf("dup2: %s", strerror(errno));
+ exit(1);
+ }
+
+ prog = afs_exec_alt(child_argc, child_argv, t->prefix, t->suffix);
+ if (t->result == NULL) {
+ printf("%s", FAILSTR);
+ exit(0);
+ }
+ printf("afs_exec_alt failed to exec %s: %s", prog, strerror(errno));
+ exit(1);
+
+ } else {
+ /* parent */
+
+ ssize_t result_len, nBytes;
+ const char *result;
+ int status;
+
+ if (close(fds[1])) {
+ sysdiag("parent: close(%d) failed", fds[1]);
+ }
+
+ result = t->result;
+ if (!result) {
+ result = FAILSTR;
+ }
+
+ result_len = strlen(result);
+
+ nBytes = read(fds[0], buf, sizeof(buf)-1);
+ is_int(result_len, nBytes,
+ "child output size for prefix=%s, suffix=%s",
+ t->prefix, t->suffix);
+
+ if (nBytes < 0) {
+ skip("read() failed; cannot test read buffer");
+ } else {
+ /* just so is_string doesn't go running off into memory... */
+ buf[nBytes] = '\0';
+
+ is_string(result, buf,
+ "child output for prefix=%s, suffix=%s",
+ t->prefix, t->suffix);
+ }
+
+ if (close(fds[0])) {
+ sysdiag("parent: close(%d) failed", fds[0]);
+ }
+
+ if (waitpid(pid, &status, 0) <= 0) {
+ sysbail("waitpid");
+ }
+
+ if (child_script) {
+ if (unlink(child_script)) {
+ sysdiag("unlink(%s)", child_script);
+ }
+ free(child_script);
+ }
+
+ ok(WIFEXITED(status), "child exited for prefix=%s, suffix=%s",
+ t->prefix, t->suffix);
+
+ if (WIFEXITED(status)) {
+ is_int(0, WEXITSTATUS(status),
+ "child exit code for prefix=%s, suffix=%s",
+ t->prefix, t->suffix);
+ } else {
+ skip("!WIFEXITED(status) (status=%d), cannot check exit code",
+ status);
+ if (WIFSIGNALED(status)) {
+ diag("terminated with signal %d", WTERMSIG(status));
+ }
+ }
+ }
+ }
+ return 0;
+}