/* Copyright (c) 2008-2012 Red Hat, Inc. 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 #include #include #include #include #include #include #include #include #include #include #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 #include 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