summaryrefslogtreecommitdiffstats
path: root/ctdb/common/run_proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'ctdb/common/run_proc.c')
-rw-r--r--ctdb/common/run_proc.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/ctdb/common/run_proc.c b/ctdb/common/run_proc.c
new file mode 100644
index 0000000..84bc343
--- /dev/null
+++ b/ctdb/common/run_proc.c
@@ -0,0 +1,503 @@
+/*
+ Run a child process and collect the output
+
+ Copyright (C) Amitay Isaacs 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+
+#include <talloc.h>
+#include <tevent.h>
+
+#include "lib/util/tevent_unix.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/blocking.h"
+#include "lib/util/dlinklist.h"
+
+#include "common/run_proc.h"
+
+/*
+ * Process abstraction
+ */
+
+struct run_proc_context;
+
+struct proc_context {
+ struct proc_context *prev, *next;
+
+ pid_t pid;
+
+ int fd;
+ struct tevent_fd *fde;
+
+ char *output;
+ struct run_proc_result result;
+
+ struct tevent_req *req;
+};
+
+static int proc_destructor(struct proc_context *proc);
+
+static struct proc_context *proc_new(TALLOC_CTX *mem_ctx,
+ struct run_proc_context *run_ctx)
+{
+ struct proc_context *proc;
+
+ proc = talloc_zero(mem_ctx, struct proc_context);
+ if (proc == NULL) {
+ return NULL;
+ }
+
+ proc->pid = -1;
+ proc->fd = -1;
+
+ talloc_set_destructor(proc, proc_destructor);
+
+ return proc;
+}
+
+static void run_proc_kill(struct tevent_req *req);
+
+static int proc_destructor(struct proc_context *proc)
+{
+ if (proc->req != NULL) {
+ run_proc_kill(proc->req);
+ }
+
+ talloc_free(proc->fde);
+ if (proc->pid != -1) {
+ kill(-proc->pid, SIGKILL);
+ }
+
+ return 0;
+}
+
+static void proc_read_handler(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data);
+
+static int proc_start(struct proc_context *proc, struct tevent_context *ev,
+ const char *path, const char **argv, int stdin_fd)
+{
+ int fd[2];
+ int ret;
+
+ ret = pipe(fd);
+ if (ret != 0) {
+ return ret;
+ }
+
+ proc->pid = fork();
+ if (proc->pid == -1) {
+ ret = errno;
+ close(fd[0]);
+ close(fd[1]);
+ return ret;
+ }
+
+ if (proc->pid == 0) {
+ close(fd[0]);
+
+ ret = dup2(fd[1], STDOUT_FILENO);
+ if (ret == -1) {
+ exit(64 + errno);
+ }
+ ret = dup2(fd[1], STDERR_FILENO);
+ if (ret == -1) {
+ exit(64 + errno);
+ }
+
+ close(fd[1]);
+
+ if (stdin_fd != -1) {
+ ret = dup2(stdin_fd, STDIN_FILENO);
+ if (ret == -1) {
+ exit(64 + errno);
+ }
+ }
+
+ ret = setpgid(0, 0);
+ if (ret != 0) {
+ exit(64 + errno);
+ }
+
+ ret = execv(path, discard_const(argv));
+ if (ret != 0) {
+ exit(64 + errno);
+ }
+
+ exit(64 + ENOEXEC);
+ }
+
+ close(fd[1]);
+
+ proc->fd = fd[0];
+ proc->fde = tevent_add_fd(ev, proc, fd[0], TEVENT_FD_READ,
+ proc_read_handler, proc);
+ if (proc->fde == NULL) {
+ close(fd[0]);
+ return ENOMEM;
+ }
+
+ tevent_fd_set_auto_close(proc->fde);
+
+ return 0;
+}
+
+static void proc_read_handler(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data)
+{
+ struct proc_context *proc = talloc_get_type_abort(
+ private_data, struct proc_context);
+ size_t offset;
+ ssize_t nread;
+ int len = 0;
+ int ret;
+
+ ret = ioctl(proc->fd, FIONREAD, &len);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ if (len == 0) {
+ /* pipe closed */
+ goto close;
+ }
+
+ offset = (proc->output == NULL) ? 0 : strlen(proc->output);
+
+ proc->output = talloc_realloc(proc, proc->output, char, offset+len+1);
+ if (proc->output == NULL) {
+ goto fail;
+ }
+
+ nread = sys_read(proc->fd, proc->output + offset, len);
+ if (nread == -1) {
+ goto fail;
+ }
+ proc->output[offset+nread] = '\0';
+ return;
+
+fail:
+ if (proc->pid != -1) {
+ kill(-proc->pid, SIGKILL);
+ proc->pid = -1;
+ }
+close:
+ TALLOC_FREE(proc->fde);
+ proc->fd = -1;
+}
+
+
+/*
+ * Run proc abstraction
+ */
+
+struct run_proc_context {
+ struct tevent_context *ev;
+ struct tevent_signal *se;
+ struct proc_context *plist;
+};
+
+static void run_proc_signal_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum, int count, void *siginfo,
+ void *private_data);
+static int run_proc_context_destructor(struct run_proc_context *run_ctx);
+static void run_proc_done(struct tevent_req *req);
+
+int run_proc_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct run_proc_context **result)
+{
+ struct run_proc_context *run_ctx;
+
+ run_ctx = talloc_zero(mem_ctx, struct run_proc_context);
+ if (run_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ run_ctx->ev = ev;
+ run_ctx->se = tevent_add_signal(ev, run_ctx, SIGCHLD, 0,
+ run_proc_signal_handler, run_ctx);
+ if (run_ctx->se == NULL) {
+ talloc_free(run_ctx);
+ return ENOMEM;
+ }
+
+ talloc_set_destructor(run_ctx, run_proc_context_destructor);
+
+ *result = run_ctx;
+ return 0;
+}
+
+static void run_proc_signal_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum, int count, void *siginfo,
+ void *private_data)
+{
+ struct run_proc_context *run_ctx = talloc_get_type_abort(
+ private_data, struct run_proc_context);
+ struct proc_context *proc;
+ pid_t pid = -1;
+ int status;
+
+again:
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid == -1) {
+ return;
+ }
+
+ if (pid == 0) {
+ return;
+ }
+
+ for (proc = run_ctx->plist; proc != NULL; proc = proc->next) {
+ if (proc->pid == pid) {
+ break;
+ }
+ }
+
+ if (proc == NULL) {
+ /* unknown process */
+ goto again;
+ }
+
+ /* Mark the process as terminated */
+ proc->pid = -1;
+
+ /* Update process status */
+ if (WIFEXITED(status)) {
+ int pstatus = WEXITSTATUS(status);
+ if (WIFSIGNALED(status)) {
+ proc->result.sig = WTERMSIG(status);
+ } else if (pstatus >= 64 && pstatus < 255) {
+ proc->result.err = pstatus-64;
+ } else {
+ proc->result.status = pstatus;
+ }
+ } else if (WIFSIGNALED(status)) {
+ proc->result.sig = WTERMSIG(status);
+ }
+
+ /* Confirm that all data has been read from the pipe */
+ if (proc->fd != -1) {
+ proc_read_handler(ev, proc->fde, 0, proc);
+ TALLOC_FREE(proc->fde);
+ proc->fd = -1;
+ }
+
+ DLIST_REMOVE(run_ctx->plist, proc);
+
+ /* Active run_proc request */
+ if (proc->req != NULL) {
+ run_proc_done(proc->req);
+ } else {
+ talloc_free(proc);
+ }
+
+ goto again;
+}
+
+static int run_proc_context_destructor(struct run_proc_context *run_ctx)
+{
+ struct proc_context *proc;
+
+ /* Get rid of signal handler */
+ TALLOC_FREE(run_ctx->se);
+
+ /* Kill any pending processes */
+ while ((proc = run_ctx->plist) != NULL) {
+ DLIST_REMOVE(run_ctx->plist, proc);
+ talloc_free(proc);
+ }
+
+ return 0;
+}
+
+struct run_proc_state {
+ struct tevent_context *ev;
+ struct run_proc_context *run_ctx;
+ struct proc_context *proc;
+
+ struct run_proc_result result;
+ char *output;
+ pid_t pid;
+};
+
+static int run_proc_state_destructor(struct run_proc_state *state);
+static void run_proc_timedout(struct tevent_req *subreq);
+
+struct tevent_req *run_proc_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct run_proc_context *run_ctx,
+ const char *path, const char **argv,
+ int stdin_fd, struct timeval timeout)
+{
+ struct tevent_req *req;
+ struct run_proc_state *state;
+ struct stat st;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct run_proc_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->run_ctx = run_ctx;
+ state->pid = -1;
+
+ ret = stat(path, &st);
+ if (ret != 0) {
+ state->result.err = errno;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (! (st.st_mode & S_IXUSR)) {
+ state->result.err = EACCES;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ state->proc = proc_new(run_ctx, run_ctx);
+ if (tevent_req_nomem(state->proc, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->proc->req = req;
+ DLIST_ADD(run_ctx->plist, state->proc);
+
+ ret = proc_start(state->proc, ev, path, argv, stdin_fd);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ talloc_set_destructor(state, run_proc_state_destructor);
+
+ if (! tevent_timeval_is_zero(&timeout)) {
+ struct tevent_req *subreq;
+
+ subreq = tevent_wakeup_send(state, ev, timeout);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, run_proc_timedout, req);
+ }
+
+ return req;
+}
+
+static int run_proc_state_destructor(struct run_proc_state *state)
+{
+ /* Do not get rid of the child process if timeout has occurred */
+ if ((state->proc != NULL) && (state->proc->req != NULL)) {
+ state->proc->req = NULL;
+ DLIST_REMOVE(state->run_ctx->plist, state->proc);
+ TALLOC_FREE(state->proc);
+ }
+
+ return 0;
+}
+
+static void run_proc_done(struct tevent_req *req)
+{
+ struct run_proc_state *state = tevent_req_data(
+ req, struct run_proc_state);
+
+ state->proc->req = NULL;
+
+ state->result = state->proc->result;
+ if (state->proc->output != NULL) {
+ state->output = talloc_move(state, &state->proc->output);
+ }
+ talloc_steal(state, state->proc);
+
+ tevent_req_done(req);
+}
+
+static void run_proc_kill(struct tevent_req *req)
+{
+ struct run_proc_state *state = tevent_req_data(
+ req, struct run_proc_state);
+
+ state->proc->req = NULL;
+ state->proc = NULL;
+
+ state->result.sig = SIGKILL;
+
+ tevent_req_done(req);
+}
+
+static void run_proc_timedout(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct run_proc_state *state = tevent_req_data(
+ req, struct run_proc_state);
+ bool status;
+
+ state->proc->req = NULL;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ state->result.err = ETIMEDOUT;
+ if (state->proc->output != NULL) {
+ state->output = talloc_move(state, &state->proc->output);
+ }
+ state->pid = state->proc->pid;
+
+ tevent_req_done(req);
+}
+
+bool run_proc_recv(struct tevent_req *req, int *perr,
+ struct run_proc_result *result, pid_t *pid,
+ TALLOC_CTX *mem_ctx, char **output)
+{
+ struct run_proc_state *state = tevent_req_data(
+ req, struct run_proc_state);
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ if (result != NULL) {
+ *result = state->result;
+ }
+
+ if (pid != NULL) {
+ *pid = state->pid;
+ }
+
+ if (output != NULL) {
+ *output = talloc_move(mem_ctx, &state->output);
+ }
+
+ return true;
+}