summaryrefslogtreecommitdiffstats
path: root/lib/util/util_runcmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/util/util_runcmd.c')
-rw-r--r--lib/util/util_runcmd.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/lib/util/util_runcmd.c b/lib/util/util_runcmd.c
new file mode 100644
index 0000000..ea2e8ee
--- /dev/null
+++ b/lib/util/util_runcmd.c
@@ -0,0 +1,379 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ run a child command
+
+ Copyright (C) Andrew Tridgell 2010
+
+ 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/>.
+
+*/
+
+/*
+ this runs a child command with stdout and stderr going to the Samba
+ log
+ */
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+#include <tevent.h>
+#include "lib/util/samba_util.h"
+#include "lib/util/debug.h"
+#include "../lib/util/tevent_unix.h"
+#include "../lib/util/tfork.h"
+#include "../lib/util/sys_rw.h"
+
+struct samba_runcmd_state {
+ int stdout_log_level;
+ int stderr_log_level;
+ struct tevent_fd *fde_stdout;
+ struct tevent_fd *fde_stderr;
+ struct tevent_fd *fde_status;
+ int fd_stdin, fd_stdout, fd_stderr, fd_status;
+ char *arg0;
+ pid_t pid;
+ struct tfork *tfork;
+ char buf[1024];
+ uint16_t buf_used;
+};
+
+static void samba_runcmd_cleanup_fn(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct samba_runcmd_state *state = tevent_req_data(
+ req, struct samba_runcmd_state);
+
+ if (state->tfork != NULL) {
+ tfork_destroy(&state->tfork);
+ }
+ state->pid = -1;
+
+ if (state->fd_stdin != -1) {
+ close(state->fd_stdin);
+ state->fd_stdin = -1;
+ }
+}
+
+int samba_runcmd_export_stdin(struct tevent_req *req)
+{
+ struct samba_runcmd_state *state = tevent_req_data(req,
+ struct samba_runcmd_state);
+ int ret = state->fd_stdin;
+
+ state->fd_stdin = -1;
+
+ return ret;
+}
+
+static void samba_runcmd_io_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+/*
+ run a command as a child process, with a timeout.
+
+ any stdout/stderr from the child will appear in the Samba logs with
+ the specified log levels
+ */
+struct tevent_req *samba_runcmd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct timeval endtime,
+ int stdout_log_level,
+ int stderr_log_level,
+ const char * const *argv0, ...)
+{
+ struct tevent_req *req;
+ struct samba_runcmd_state *state;
+ int p1[2], p2[2], p3[2];
+ char **argv;
+ va_list ap;
+
+ if (argv0 == NULL) {
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct samba_runcmd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->stdout_log_level = stdout_log_level;
+ state->stderr_log_level = stderr_log_level;
+ state->fd_stdin = -1;
+
+ state->arg0 = talloc_strdup(state, argv0[0]);
+ if (tevent_req_nomem(state->arg0, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (pipe(p1) != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ if (pipe(p2) != 0) {
+ close(p1[0]);
+ close(p1[1]);
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ if (pipe(p3) != 0) {
+ close(p1[0]);
+ close(p1[1]);
+ close(p2[0]);
+ close(p2[1]);
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ state->tfork = tfork_create();
+ if (state->tfork == NULL) {
+ close(p1[0]);
+ close(p1[1]);
+ close(p2[0]);
+ close(p2[1]);
+ close(p3[0]);
+ close(p3[1]);
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ state->pid = tfork_child_pid(state->tfork);
+ if (state->pid != 0) {
+ /* the parent */
+ close(p1[1]);
+ close(p2[1]);
+ close(p3[0]);
+ state->fd_stdout = p1[0];
+ state->fd_stderr = p2[0];
+ state->fd_stdin = p3[1];
+ state->fd_status = tfork_event_fd(state->tfork);
+
+ set_blocking(state->fd_stdout, false);
+ set_blocking(state->fd_stderr, false);
+ set_blocking(state->fd_stdin, false);
+ set_blocking(state->fd_status, false);
+
+ smb_set_close_on_exec(state->fd_stdin);
+ smb_set_close_on_exec(state->fd_stdout);
+ smb_set_close_on_exec(state->fd_stderr);
+ smb_set_close_on_exec(state->fd_status);
+
+ tevent_req_set_cleanup_fn(req, samba_runcmd_cleanup_fn);
+
+ state->fde_stdout = tevent_add_fd(ev, state,
+ state->fd_stdout,
+ TEVENT_FD_READ,
+ samba_runcmd_io_handler,
+ req);
+ if (tevent_req_nomem(state->fde_stdout, req)) {
+ close(state->fd_stdout);
+ close(state->fd_stderr);
+ close(state->fd_status);
+ return tevent_req_post(req, ev);
+ }
+ tevent_fd_set_auto_close(state->fde_stdout);
+
+ state->fde_stderr = tevent_add_fd(ev, state,
+ state->fd_stderr,
+ TEVENT_FD_READ,
+ samba_runcmd_io_handler,
+ req);
+ if (tevent_req_nomem(state->fde_stdout, req)) {
+ close(state->fd_stdout);
+ close(state->fd_stderr);
+ close(state->fd_status);
+ return tevent_req_post(req, ev);
+ }
+ tevent_fd_set_auto_close(state->fde_stderr);
+
+ state->fde_status = tevent_add_fd(ev, state,
+ state->fd_status,
+ TEVENT_FD_READ,
+ samba_runcmd_io_handler,
+ req);
+ if (tevent_req_nomem(state->fde_stdout, req)) {
+ close(state->fd_stdout);
+ close(state->fd_stderr);
+ close(state->fd_status);
+ return tevent_req_post(req, ev);
+ }
+ tevent_fd_set_auto_close(state->fde_status);
+
+ if (!timeval_is_zero(&endtime)) {
+ tevent_req_set_endtime(req, ev, endtime);
+ }
+
+ return req;
+ }
+
+ /* the child */
+ close(p1[0]);
+ close(p2[0]);
+ close(p3[1]);
+ close(0);
+ close(1);
+ close(2);
+
+ /* we want to ensure that all of the network sockets we had
+ open are closed */
+ tevent_re_initialise(ev);
+
+ /* setup for logging to go to the parents debug log */
+ dup2(p3[0], 0);
+ dup2(p1[1], 1);
+ dup2(p2[1], 2);
+
+ close(p1[1]);
+ close(p2[1]);
+ close(p3[0]);
+
+ argv = str_list_copy(state, discard_const_p(const char *, argv0));
+ if (!argv) {
+ fprintf(stderr, "Out of memory in child\n");
+ _exit(255);
+ }
+
+ va_start(ap, argv0);
+ while (1) {
+ const char **l;
+ char *arg = va_arg(ap, char *);
+ if (arg == NULL) break;
+ l = discard_const_p(const char *, argv);
+ l = str_list_add(l, arg);
+ if (l == NULL) {
+ fprintf(stderr, "Out of memory in child\n");
+ _exit(255);
+ }
+ argv = discard_const_p(char *, l);
+ }
+ va_end(ap);
+
+ (void)execvp(state->arg0, argv);
+ fprintf(stderr, "Failed to exec child - %s\n", strerror(errno));
+ _exit(255);
+ return NULL;
+}
+
+/*
+ handle stdout/stderr from the child
+ */
+static void samba_runcmd_io_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(private_data,
+ struct tevent_req);
+ struct samba_runcmd_state *state = tevent_req_data(req,
+ struct samba_runcmd_state);
+ int level;
+ char *p;
+ int n, fd;
+
+ if (!(flags & TEVENT_FD_READ)) {
+ return;
+ }
+
+ if (fde == state->fde_stdout) {
+ level = state->stdout_log_level;
+ fd = state->fd_stdout;
+ } else if (fde == state->fde_stderr) {
+ level = state->stderr_log_level;
+ fd = state->fd_stderr;
+ } else {
+ int status;
+
+ status = tfork_status(&state->tfork, false);
+ if (status == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return;
+ }
+ DBG_ERR("Bad read on status pipe\n");
+ tevent_req_error(req, errno);
+ return;
+ }
+ state->pid = -1;
+ TALLOC_FREE(fde);
+
+ if (WIFEXITED(status)) {
+ status = WEXITSTATUS(status);
+ } else if (WIFSIGNALED(status)) {
+ status = WTERMSIG(status);
+ } else {
+ status = ECHILD;
+ }
+
+ DBG_NOTICE("Child %s exited %d\n", state->arg0, status);
+ if (status != 0) {
+ tevent_req_error(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+ }
+
+ n = read(fd, &state->buf[state->buf_used],
+ sizeof(state->buf) - state->buf_used);
+ if (n > 0) {
+ state->buf_used += n;
+ } else if (n == 0) {
+ if (fde == state->fde_stdout) {
+ talloc_free(fde);
+ state->fde_stdout = NULL;
+ return;
+ }
+ if (fde == state->fde_stderr) {
+ talloc_free(fde);
+ state->fde_stderr = NULL;
+ return;
+ }
+ return;
+ }
+
+ while (state->buf_used > 0 &&
+ (p = (char *)memchr(state->buf, '\n', state->buf_used)) != NULL) {
+ int n1 = (p - state->buf)+1;
+ int n2 = n1 - 1;
+ /* swallow \r from child processes */
+ if (n2 > 0 && state->buf[n2-1] == '\r') {
+ n2--;
+ }
+ DEBUG(level,("%s: %*.*s\n", state->arg0, n2, n2, state->buf));
+ memmove(state->buf, p+1, sizeof(state->buf) - n1);
+ state->buf_used -= n1;
+ }
+
+ /* the buffer could have completely filled - unfortunately we have
+ no choice but to dump it out straight away */
+ if (state->buf_used == sizeof(state->buf)) {
+ DEBUG(level,("%s: %*.*s\n",
+ state->arg0, state->buf_used,
+ state->buf_used, state->buf));
+ state->buf_used = 0;
+ }
+}
+
+int samba_runcmd_recv(struct tevent_req *req, int *perrno)
+{
+ if (tevent_req_is_unix_error(req, perrno)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ tevent_req_received(req);
+ return 0;
+}