diff --git a/lib/util/util.h b/lib/util/util.h index 264396efe61..e1160d5a3e4 100644 --- a/lib/util/util.h +++ b/lib/util/util.h @@ -890,12 +890,13 @@ bool add_gid_to_array_unique(TALLOC_CTX *mem_ctx, gid_t gid, with the return code from the command */ struct tevent_context; -struct composite_context *samba_runcmd(struct tevent_context *ev, - TALLOC_CTX *mem_ctx, - struct timeval timeout, - int stdout_log_level, - int stderr_log_level, - const char **argv0, ...); - +struct tevent_req; +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, ...); +int samba_runcmd_recv(struct tevent_req *req, int *perrno); #endif /* _SAMBA_UTIL_H_ */ diff --git a/lib/util/util_runcmd.c b/lib/util/util_runcmd.c index dea3ff91b1d..ef897d469f4 100644 --- a/lib/util/util_runcmd.c +++ b/lib/util/util_runcmd.c @@ -28,9 +28,9 @@ #include "includes.h" #include "system/filesys.h" #include "lib/tevent/tevent.h" -#include "libcli/composite/composite.h" +#include "lib/util/tevent_unix.h" -struct samba_runcmd { +struct samba_runcmd_state { int stdout_log_level; int stderr_log_level; struct tevent_fd *fde_stdout; @@ -42,106 +42,20 @@ struct samba_runcmd { uint16_t buf_used; }; -/* - called when a command times out - */ -static void runcmd_timeout(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval current_time, - void *private_data) +static int samba_runcmd_state_destructor(struct samba_runcmd_state *state) { - struct composite_context *c = talloc_get_type_abort(private_data, struct composite_context); - struct samba_runcmd *r = talloc_get_type_abort(c->private_data, struct samba_runcmd); - kill(r->pid, SIGKILL); - waitpid(r->pid, NULL, 0); - talloc_free(r->fde_stderr); - talloc_free(r->fde_stdout); - composite_error(c, NT_STATUS_IO_TIMEOUT); -} - -/* - handle stdout/stderr from the child - */ -static void runcmd_io_handler(struct tevent_context *ev, - struct tevent_fd *fde, - uint16_t flags, - void *private_data) -{ - struct composite_context *c = talloc_get_type_abort(private_data, struct composite_context); - struct samba_runcmd *r = talloc_get_type_abort(c->private_data, struct samba_runcmd); - int level; - char *p; - int n, fd; - - if (fde == r->fde_stdout) { - level = r->stdout_log_level; - fd = r->fd_stdout; - } else { - level = r->stderr_log_level; - fd = r->fd_stderr; - } - - if (!(flags & TEVENT_FD_READ)) { - return; - } - - n = read(fd, &r->buf[r->buf_used], - sizeof(r->buf) - r->buf_used); - if (n > 0) { - r->buf_used += n; - } else if (n == 0) { - if (fde == r->fde_stdout) { - talloc_free(fde); - r->fde_stdout = NULL; - } - if (fde == r->fde_stderr) { - talloc_free(fde); - r->fde_stderr = NULL; - } - if (r->fde_stdout == NULL && - r->fde_stderr == NULL) { - int status; - /* the child has closed both stdout and - * stderr, assume its dead */ - pid_t pid = waitpid(r->pid, &status, 0); - if (pid != r->pid) { - DEBUG(0,("Error in waitpid() for child %s\n", r->arg0)); - composite_error(c, map_nt_error_from_unix(errno)); - return; - } - status = WEXITSTATUS(status); - DEBUG(3,("Child %s exited with status %d\n", r->arg0, status)); - if (status == 0) { - composite_done(c); - } else { - composite_error(c, map_nt_error_from_unix(status)); - } - return; - } - return; - } - - while (r->buf_used > 0 && - (p = memchr(r->buf, '\n', r->buf_used)) != NULL) { - int n1 = (p - r->buf)+1; - int n2 = n1 - 1; - /* swallow \r from child processes */ - if (n2 > 0 && r->buf[n2-1] == '\r') { - n2--; - } - DEBUG(level,("%s: %*.*s\n", r->arg0, n2, n2, r->buf)); - memmove(r->buf, p+1, sizeof(r->buf) - n1); - r->buf_used -= n1; - } - - /* the buffer could have completely filled - unfortunately we have - no choice but to dump it out straight away */ - if (r->buf_used == sizeof(r->buf)) { - DEBUG(level,("%s: %*.*s\n", r->arg0, r->buf_used, r->buf_used, r->buf)); - r->buf_used = 0; - } + if (state->pid > 0) { + kill(state->pid, SIGKILL); + waitpid(state->pid, NULL, 0); + state->pid = -1; + } + return 0; } +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. @@ -149,71 +63,94 @@ static void runcmd_io_handler(struct tevent_context *ev, any stdout/stderr from the child will appear in the Samba logs with the specified log levels */ -struct composite_context *samba_runcmd(struct tevent_context *ev, - TALLOC_CTX *mem_ctx, - struct timeval timeout, - int stdout_log_level, - int stderr_log_level, - const char **argv0, ...) +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 samba_runcmd *r; + struct tevent_req *req; + struct samba_runcmd_state *state; int p1[2], p2[2]; char **argv; int ret; va_list ap; - struct composite_context *c; - c = composite_create(mem_ctx, ev); - if (c == NULL) return NULL; + req = tevent_req_create(mem_ctx, &state, + struct samba_runcmd_state); + if (req == NULL) { + return NULL; + } - r = talloc_zero(c, struct samba_runcmd); - if (composite_nomem(r, c)) return c; + state->stdout_log_level = stdout_log_level; + state->stderr_log_level = stderr_log_level; - c->private_data = r; - - r->stdout_log_level = stdout_log_level; - r->stderr_log_level = stderr_log_level; - - r->arg0 = talloc_strdup(r, argv0[0]); - if (composite_nomem(r->arg0, c)) return c; + state->arg0 = talloc_strdup(state, argv0[0]); + if (tevent_req_nomem(state->arg0, req)) { + return tevent_req_post(req, ev); + } if (pipe(p1) != 0) { - composite_error(c, map_nt_error_from_unix(errno)); - return c; + tevent_req_error(req, errno); + return tevent_req_post(req, ev); } if (pipe(p2) != 0) { - composite_error(c, map_nt_error_from_unix(errno)); close(p1[0]); close(p1[1]); - return c; + tevent_req_error(req, errno); + return tevent_req_post(req, ev); } - r->pid = fork(); - if (r->pid == (pid_t)-1) { - composite_error(c, map_nt_error_from_unix(errno)); + state->pid = fork(); + if (state->pid == (pid_t)-1) { close(p1[0]); close(p1[1]); close(p2[0]); close(p2[1]); - return c; + tevent_req_error(req, errno); + return tevent_req_post(req, ev); } - if (r->pid != 0) { + if (state->pid != 0) { /* the parent */ close(p1[1]); close(p2[1]); - r->fd_stdout = p1[0]; - r->fd_stderr = p2[0]; - set_blocking(r->fd_stdout, false); - set_blocking(r->fd_stderr, false); - r->fde_stdout = tevent_add_fd(ev, r, r->fd_stdout, TEVENT_FD_READ, runcmd_io_handler, c); - tevent_fd_set_auto_close(r->fde_stdout); - r->fde_stderr = tevent_add_fd(ev, r, r->fd_stderr, TEVENT_FD_READ, runcmd_io_handler, c); - tevent_fd_set_auto_close(r->fde_stderr); - if (!timeval_is_zero(&timeout)) { - tevent_add_timer(ev, r, timeout, runcmd_timeout, c); + state->fd_stdout = p1[0]; + state->fd_stderr = p2[0]; + set_blocking(state->fd_stdout, false); + set_blocking(state->fd_stderr, false); + + talloc_set_destructor(state, samba_runcmd_state_destructor); + + 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(p1[0]); + close(p2[0]); + return tevent_req_post(req, ev); } - return c; + 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(p2[0]); + return tevent_req_post(req, ev); + } + tevent_fd_set_auto_close(state->fde_stderr); + + if (!timeval_is_zero(&endtime)) { + tevent_req_set_endtime(req, ev, endtime); + } + + return req; } /* the child */ @@ -228,7 +165,7 @@ struct composite_context *samba_runcmd(struct tevent_context *ev, dup2(p1[1], 1); dup2(p2[1], 2); - argv = str_list_copy(r, argv0); + argv = str_list_copy(state, discard_const_p(const char *, argv0)); if (!argv) { fprintf(stderr, "Out of memory in child\n"); _exit(255); @@ -246,8 +183,112 @@ struct composite_context *samba_runcmd(struct tevent_context *ev, } va_end(ap); - ret = execv(r->arg0, argv); + ret = execv(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 (fde == state->fde_stdout) { + level = state->stdout_log_level; + fd = state->fd_stdout; + } else { + level = state->stderr_log_level; + fd = state->fd_stderr; + } + + if (!(flags & TEVENT_FD_READ)) { + 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; + } + if (fde == state->fde_stderr) { + talloc_free(fde); + state->fde_stderr = NULL; + } + if (state->fde_stdout == NULL && + state->fde_stderr == NULL) { + int status; + /* the child has closed both stdout and + * stderr, assume its dead */ + pid_t pid = waitpid(state->pid, &status, 0); + if (pid != state->pid) { + DEBUG(0,("Error in waitpid() for child %s - %s \n", + state->arg0, strerror(errno))); + if (errno == 0) { + errno = ECHILD; + } + tevent_req_error(req, errno); + return; + } + status = WEXITSTATUS(status); + DEBUG(3,("Child %s exited with status %d - %s\n", + state->arg0, status, strerror(status))); + if (status != 0) { + tevent_req_error(req, status); + return; + } + + tevent_req_done(req); + 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; +}