util: add afs_exec_alt
[openafs.git] / tests / util / exec-alt-t.c
diff --git a/tests/util/exec-alt-t.c b/tests/util/exec-alt-t.c
new file mode 100644 (file)
index 0000000..f8921a0
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * 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;
+}