diff options
Diffstat (limited to 'libglusterfs/src/run.c')
| -rw-r--r-- | libglusterfs/src/run.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/libglusterfs/src/run.c b/libglusterfs/src/run.c new file mode 100644 index 00000000000..58f95a7e610 --- /dev/null +++ b/libglusterfs/src/run.c @@ -0,0 +1,570 @@ +/* + Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <dirent.h> +#include <assert.h> +#include <signal.h> +#include <sys/wait.h> +#include "glusterfs/syscall.h" + +/* + * Following defines are available for helping development: + * RUN_STANDALONE and RUN_DO_DEMO. + * + * Compiling a standalone object file with no dependencies + * on glusterfs: + * $ cc -DRUN_STANDALONE -c run.c + * + * Compiling a demo program that exercises bits of run.c + * functionality (linking to glusterfs): + * $ cc -DRUN_DO_DEMO -orun run.c `pkg-config --libs --cflags glusterfs-api` + * + * Compiling a demo program that exercises bits of run.c + * functionality (with no dependence on glusterfs): + * + * $ cc -DRUN_DO_DEMO -DRUN_STANDALONE -orun run.c + */ +#if defined(RUN_STANDALONE) || defined(RUN_DO_DEMO) +int +close_fds_except(int *fdv, size_t count); +#define sys_read(f, b, c) read(f, b, c) +#define sys_write(f, b, c) write(f, b, c) +#define sys_close(f) close(f) +#define GF_CALLOC(n, s, t) calloc(n, s) +#define GF_ASSERT(cond) assert(cond) +#define GF_REALLOC(p, s) realloc(p, s) +#define GF_FREE(p) free(p) +#define gf_strdup(s) strdup(s) +#define gf_vasprintf(p, f, va) vasprintf(p, f, va) +#define gf_loglevel_t int +#define gf_msg_callingfn(dom, level, errnum, msgid, fmt, args...) \ + printf("LOG: " fmt "\n", ##args) +#define LOG_DEBUG 0 +#ifdef RUN_STANDALONE +#include <stdbool.h> +#include <sys/resource.h> +int +close_fds_except(int *fdv, size_t count) +{ + int i = 0; + size_t j = 0; + bool should_close = true; + struct rlimit rl; + int ret = -1; + + ret = getrlimit(RLIMIT_NOFILE, &rl); + if (ret) + return ret; + + for (i = 0; i < rl.rlim_cur; i++) { + should_close = true; + for (j = 0; j < count; j++) { + if (i == fdv[j]) { + should_close = false; + break; + } + } + if (should_close) + sys_close(i); + } + return 0; +} +#endif +#ifdef __linux__ +#define GF_LINUX_HOST_OS +#endif +#else /* ! RUN_STANDALONE || RUN_DO_DEMO */ +#include "glusterfs/glusterfs.h" +#include "glusterfs/common-utils.h" +#include "glusterfs/libglusterfs-messages.h" +#endif + +#include "glusterfs/run.h" +void +runinit(runner_t *runner) +{ + int i = 0; + + runner->argvlen = 64; + runner->argv = GF_CALLOC(runner->argvlen, sizeof(*runner->argv), + gf_common_mt_run_argv); + runner->runerr = runner->argv ? 0 : errno; + runner->chpid = -1; + for (i = 0; i < 3; i++) { + runner->chfd[i] = -1; + runner->chio[i] = NULL; + } +} + +FILE * +runner_chio(runner_t *runner, int fd) +{ + GF_ASSERT(fd > 0 && fd < 3); + + if ((fd > 0) && (fd < 3)) + return runner->chio[fd]; + + return NULL; +} + +static void +runner_insert_arg(runner_t *runner, char *arg) +{ + int i = 0; + + GF_ASSERT(arg); + + if (runner->runerr || !runner->argv) + return; + + for (i = 0; i < runner->argvlen; i++) { + if (runner->argv[i] == NULL) + break; + } + GF_ASSERT(i < runner->argvlen); + + if (i == runner->argvlen - 1) { + runner->argv = GF_REALLOC(runner->argv, + runner->argvlen * 2 * sizeof(*runner->argv)); + if (!runner->argv) { + runner->runerr = errno; + return; + } + memset(/* "+" is aware of the type of its left side, + * no need to multiply with type-size */ + runner->argv + runner->argvlen, 0, + runner->argvlen * sizeof(*runner->argv)); + runner->argvlen *= 2; + } + + runner->argv[i] = arg; +} + +void +runner_add_arg(runner_t *runner, const char *arg) +{ + arg = gf_strdup(arg); + if (!arg) { + runner->runerr = errno; + return; + } + + runner_insert_arg(runner, (char *)arg); +} + +static void +runner_va_add_args(runner_t *runner, va_list argp) +{ + const char *arg; + + while ((arg = va_arg(argp, const char *))) + runner_add_arg(runner, arg); +} + +void +runner_add_args(runner_t *runner, ...) +{ + va_list argp; + + va_start(argp, runner); + runner_va_add_args(runner, argp); + va_end(argp); +} + +void +runner_argprintf(runner_t *runner, const char *format, ...) +{ + va_list argva; + char *arg = NULL; + int ret = 0; + + va_start(argva, format); + ret = gf_vasprintf(&arg, format, argva); + va_end(argva); + + if (ret < 0) { + runner->runerr = errno; + return; + } + + runner_insert_arg(runner, arg); +} + +void +runner_log(runner_t *runner, const char *dom, gf_loglevel_t lvl, + const char *msg) +{ + char *buf = NULL; + size_t len = 0; + int i = 0; + + if (runner->runerr) + return; + + for (i = 0;; i++) { + if (runner->argv[i] == NULL) + break; + len += (strlen(runner->argv[i]) + 1); + } + + buf = GF_CALLOC(1, len + 1, gf_common_mt_run_logbuf); + if (!buf) { + runner->runerr = errno; + return; + } + for (i = 0;; i++) { + if (runner->argv[i] == NULL) + break; + strcat(buf, runner->argv[i]); + strcat(buf, " "); + } + if (len > 0) + buf[len - 1] = '\0'; + + gf_msg_callingfn(dom, lvl, 0, LG_MSG_RUNNER_LOG, "%s: %s", msg, buf); + + GF_FREE(buf); +} + +void +runner_redir(runner_t *runner, int fd, int tgt_fd) +{ + GF_ASSERT(fd > 0 && fd < 3); + + if ((fd > 0) && (fd < 3)) + runner->chfd[fd] = (tgt_fd >= 0) ? tgt_fd : -2; +} + +int +runner_start(runner_t *runner) +{ + int pi[3][2] = {{-1, -1}, {-1, -1}, {-1, -1}}; + int xpi[2]; + int ret = 0; + int errno_priv = 0; + int i = 0; + sigset_t set; + + if (runner->runerr || !runner->argv) { + errno = (runner->runerr) ? runner->runerr : EINVAL; + return -1; + } + + GF_ASSERT(runner->argv[0]); + + /* set up a channel to child to communicate back + * possible execve(2) failures + */ + ret = pipe(xpi); + if (ret != -1) + ret = fcntl(xpi[1], F_SETFD, FD_CLOEXEC); + + for (i = 0; i < 3; i++) { + if (runner->chfd[i] != -2) + continue; + ret = pipe(pi[i]); + if (ret != -1) { + runner->chio[i] = fdopen(pi[i][i ? 0 : 1], i ? "r" : "w"); + if (!runner->chio[i]) + ret = -1; + } + } + + if (ret != -1) + runner->chpid = fork(); + switch (runner->chpid) { + case -1: + errno_priv = errno; + sys_close(xpi[0]); + sys_close(xpi[1]); + for (i = 0; i < 3; i++) { + sys_close(pi[i][0]); + sys_close(pi[i][1]); + } + errno = errno_priv; + return -1; + case 0: + for (i = 0; i < 3; i++) + sys_close(pi[i][i ? 0 : 1]); + sys_close(xpi[0]); + ret = 0; + + for (i = 0; i < 3; i++) { + if (ret == -1) + break; + switch (runner->chfd[i]) { + case -1: + /* no redir */ + break; + case -2: + /* redir to pipe */ + ret = dup2(pi[i][i ? 1 : 0], i); + break; + default: + /* redir to file */ + ret = dup2(runner->chfd[i], i); + } + } + + if (ret != -1) { + int fdv[4] = {0, 1, 2, xpi[1]}; + + ret = close_fds_except(fdv, sizeof(fdv) / sizeof(*fdv)); + } + + if (ret != -1) { + /* save child from inheriting our signal handling */ + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + execvp(runner->argv[0], runner->argv); + } + ret = sys_write(xpi[1], &errno, sizeof(errno)); + _exit(1); + } + + errno_priv = errno; + for (i = 0; i < 3; i++) + sys_close(pi[i][i ? 1 : 0]); + sys_close(xpi[1]); + if (ret == -1) { + for (i = 0; i < 3; i++) { + if (runner->chio[i]) { + fclose(runner->chio[i]); + runner->chio[i] = NULL; + } + } + } else { + ret = sys_read(xpi[0], (char *)&errno_priv, sizeof(errno_priv)); + sys_close(xpi[0]); + if (ret <= 0) + return 0; + GF_ASSERT(ret == sizeof(errno_priv)); + } + errno = errno_priv; + return -1; +} + +int +runner_end_reuse(runner_t *runner) +{ + int i = 0; + int ret = 1; + int chstat = 0; + + if (runner->chpid > 0) { + if (waitpid(runner->chpid, &chstat, 0) == runner->chpid) { + if (WIFEXITED(chstat)) { + ret = WEXITSTATUS(chstat); + } else { + ret = chstat; + } + } + } + + for (i = 0; i < 3; i++) { + if (runner->chio[i]) { + fclose(runner->chio[i]); + runner->chio[i] = NULL; + } + } + + return -ret; +} + +int +runner_end(runner_t *runner) +{ + int i = 0; + int ret = -1; + char **p = NULL; + + ret = runner_end_reuse(runner); + + if (runner->argv) { + for (p = runner->argv; *p; p++) + GF_FREE(*p); + GF_FREE(runner->argv); + } + for (i = 0; i < 3; i++) + sys_close(runner->chfd[i]); + + return ret; +} + +static int +runner_run_generic(runner_t *runner, int (*rfin)(runner_t *runner)) +{ + int ret = 0; + + ret = runner_start(runner); + if (ret) + goto out; + ret = rfin(runner); + +out: + return ret; +} + +int +runner_run(runner_t *runner) +{ + return runner_run_generic(runner, runner_end); +} + +int +runner_run_nowait(runner_t *runner) +{ + int pid; + + pid = fork(); + + if (!pid) { + setsid(); + _exit(runner_start(runner)); + } + + if (pid > 0) + runner->chpid = pid; + return runner_end(runner); +} + +int +runner_run_reuse(runner_t *runner) +{ + return runner_run_generic(runner, runner_end_reuse); +} + +int +runcmd(const char *arg, ...) +{ + runner_t runner; + va_list argp; + + runinit(&runner); + /* ISO C requires a named argument before '...' */ + runner_add_arg(&runner, arg); + + va_start(argp, arg); + runner_va_add_args(&runner, argp); + va_end(argp); + + return runner_run(&runner); +} + +#ifdef RUN_DO_DEMO +static void +TBANNER(const char *txt) +{ + printf("######\n### demoing %s\n", txt); +} + +int +main(int argc, char **argv) +{ + runner_t runner; + char buf[80]; + char *wdbuf; + ; + int ret; + int fd; + long pathmax = pathconf("/", _PC_PATH_MAX); + struct timeval tv = { + 0, + }; + struct timeval *tvp = NULL; + char *tfile; + + wdbuf = malloc(pathmax); + assert(wdbuf); + getcwd(wdbuf, pathmax); + + TBANNER("basic functionality: running \"echo a b\""); + runcmd("echo", "a", "b", NULL); + + TBANNER("argv extension: running \"echo 1 2 ... 100\""); + runcmd("echo", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", + "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", + "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", + "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", + "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", + "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", + "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", + "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", + "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + "100", NULL); + + TBANNER( + "add_args, argprintf, log, and popen-style functionality:\n" + " running a multiline echo command, emit a log about it,\n" + " redirect it to a pipe, read output lines\n" + " and print them prefixed with \"got: \""); + runinit(&runner); + runner_add_args(&runner, "echo", "pid:", NULL); + runner_argprintf(&runner, "%d\n", getpid()); + runner_add_arg(&runner, "wd:"); + runner_add_arg(&runner, wdbuf); + runner_redir(&runner, 1, RUN_PIPE); + runner_start(&runner); + runner_log(&runner, "(x)", LOG_DEBUG, "starting program"); + while (fgets(buf, sizeof(buf), runner_chio(&runner, 1))) + printf("got: %s", buf); + runner_end(&runner); + + TBANNER("execve error reporting: running a non-existent command"); + ret = runcmd("bafflavvitty", NULL); + printf("%d %d [%s]\n", ret, errno, strerror(errno)); + + TBANNER( + "output redirection: running \"echo foo\" redirected " + "to a temp file"); + tfile = strdup("/tmp/foofXXXXXX"); + assert(tfile); + fd = mkstemp(tfile); + assert(fd != -1); + printf("redirecting to %s\n", tfile); + runinit(&runner); + runner_add_args(&runner, "echo", "foo", NULL); + runner_redir(&runner, 1, fd); + ret = runner_run(&runner); + printf("runner_run returned: %d", ret); + if (ret != 0) + printf(", with errno %d [%s]", errno, strerror(errno)); + putchar('\n'); + + /* sleep for seconds given as argument (0 means forever) + * to allow investigation of post-execution state to + * cbeck for resource leaks (eg. zombies). + */ + if (argc > 1) { + tv.tv_sec = strtoul(argv[1], NULL, 10); + printf("### %s", "sleeping for"); + if (tv.tv_sec > 0) { + printf(" %d seconds\n", tv.tv_sec); + tvp = &tv; + } else + printf("%s\n", "ever"); + select(0, 0, 0, 0, tvp); + } + + return 0; +} +#endif |
