From: Andrew Deason Date: Thu, 29 Jul 2010 19:47:03 +0000 (-0500) Subject: util: add afs_exec_alt X-Git-Tag: openafs-devel-1_5_76~44 X-Git-Url: http://git.openafs.org/?p=openafs.git;a=commitdiff_plain;h=a5bcd61a16a3d6b0b458f4710c0d537cfee7743d util: add afs_exec_alt 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 Tested-by: BuildBot Reviewed-by: Derrick Brashear Tested-by: Derrick Brashear --- diff --git a/src/util/Makefile.in b/src/util/Makefile.in index 6868bc7..0226e8f 100644 --- a/src/util/Makefile.in +++ b/src/util/Makefile.in @@ -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 diff --git a/src/util/afsutil_prototypes.h b/src/util/afsutil_prototypes.h index 87d3de6..fba3932 100644 --- a/src/util/afsutil_prototypes.h +++ b/src/util/afsutil_prototypes.h @@ -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 index 0000000..cd84cd9 --- /dev/null +++ b/src/util/exec.c @@ -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 +#include + +#include +#include +#include +#include + +/* 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; +} diff --git a/tests/TESTS b/tests/TESTS index 38f3826..03b038e 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -1 +1,2 @@ util/ktime +util/exec-alt diff --git a/tests/util/Makefile.in b/tests/util/Makefile.in index 9fc34fa..29babbb 100644 --- a/tests/util/Makefile.in +++ b/tests/util/Makefile.in @@ -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 index 0000000..f8921a0 --- /dev/null +++ b/tests/util/exec-alt-t.c @@ -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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}