mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
adef87a621
If a script times out the caller can talloc_free() the script_list output of run_event_recv, which talloc_free's proc->output from run_proc.c as well. If the script generates further output after the timeout and then exits after a while, the SIGCHLD handler in the eventd tries to read into proc->output, which was already free'ed. Fix this by not doing just a talloc_steal but a talloc_move. This way proc_read_handler() called from run_proc_signal_handler() does not try to realloc the stale reference to proc->output but gets a NULL reference. I don't really know how to do a knownfail in ctdb, so this commit actually activates catching the signal by waiting long enough for 22.bar to exit and generate the SIGCHLD. Bug: https://bugzilla.samba.org/show_bug.cgi?id=14475 Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
503 lines
10 KiB
C
503 lines
10 KiB
C
/*
|
|
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->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->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;
|
|
}
|