util: add afs_exec_alt
authorAndrew Deason <adeason@sinenomine.net>
Thu, 29 Jul 2010 19:47:03 +0000 (14:47 -0500)
committerDerrick Brashear <shadow@dementia.org>
Wed, 4 Aug 2010 12:24:04 +0000 (05:24 -0700)
Add the function afs_exec_alt to help programs easily execute an
"alternate" version of themselves. For example, for programs that are
built with/without DAFS support, or are built for 32-bit/64-bit
structures, etc.

Change-Id: Ibb2b7105d58476f84bd9f15987f8b7df37314b6b
Reviewed-on: http://gerrit.openafs.org/2483
Tested-by: Andrew Deason <adeason@sinenomine.net>
Tested-by: BuildBot <buildbot@rampaginggeek.com>
Reviewed-by: Derrick Brashear <shadow@dementia.org>
Tested-by: Derrick Brashear <shadow@dementia.org>

src/util/Makefile.in
src/util/afsutil_prototypes.h
src/util/exec.c [new file with mode: 0644]
tests/TESTS
tests/util/Makefile.in
tests/util/exec-alt-t.c [new file with mode: 0644]

index 6868bc7..0226e8f 100644 (file)
@@ -15,7 +15,7 @@ HELPER_SPLINT=@HELPER_SPLINT@
 
 
 objects = assert.o base64.o casestrcpy.o config_file.o ktime.o volparse.o \
-        hostparse.o \
+        hostparse.o exec.o \
         hputil.o kreltime.o isathing.o get_krbrlm.o uuid.o serverLog.o \
         dirpath.o fileutil.o netutils.o flipbase64.o fstab.o \
         afs_atomlist.o afs_lhash.o snprintf.o strlcat.o strlcpy.o strnlen.o \
@@ -165,6 +165,9 @@ config_file.o: ${TOP_SRCDIR}/external/heimdal/krb5/config_file.c krb5_locl.h
 hostparse.o: ${srcdir}/hostparse.c ${includes}
        ${CCOBJ} ${CFLAGS} -c ${srcdir}/hostparse.c
 
+exec.o: ${srcdir}/exec.c ${includes}
+       ${CCOBJ} ${CFLAGS} -c ${srcdir}/exec.c
+
 ktime.o: ${srcdir}/ktime.c ${includes}
        ${CCOBJ} ${CFLAGS} -c ${srcdir}/ktime.c
 
index 87d3de6..fba3932 100644 (file)
@@ -60,6 +60,10 @@ extern int ConstructLocalLogPath(const char *cpath, char **fullPathBufp);
 /* errmap_nt.c */
 extern int nterr_nt2unix(long ntErr, int defaultErr);
 
+/* exec.c */
+extern char* afs_exec_alt(int argc, char **argv, const char *prefix,
+                          const char *suffix);
+
 /* fileutil.c */
 extern int renamefile(const char *oldname, const char *newname);
 extern void FilepathNormalizeEx(char *path, int slashType);
diff --git a/src/util/exec.c b/src/util/exec.c
new file mode 100644 (file)
index 0000000..cd84cd9
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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;
+}
index 38f3826..03b038e 100644 (file)
@@ -1 +1,2 @@
 util/ktime
+util/exec-alt
index 9fc34fa..29babbb 100644 (file)
@@ -6,7 +6,7 @@ include @TOP_OBJDIR@/src/config/Makefile.config
 
 CFLAGS += -I$(srcdir)/..
 
-tests = ktime-t
+tests = ktime-t exec-alt-t
 
 all check test tests: $(tests)
 
@@ -14,6 +14,10 @@ ktime-t: ktime-t.o
        $(CC) $(LDFLAGS) -o ktime-t ktime-t.o ../tap/libtap.a \
            $(abs_top_srcdir)/lib/util.a $(XLIBS)
 
+exec-alt-t: exec-alt-t.o
+       $(CC) $(LDFLAGS) -o exec-alt-t exec-alt-t.o ../tap/libtap.a \
+           $(abs_top_srcdir)/lib/util.a $(XLIBS)
+
 install:
 
 clean distclean:
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;
+}