From 06ef8406cf224af6b94dc4672c9b6caa15133f89 Mon Sep 17 00:00:00 2001 From: Klaus Wenninger Date: Thu, 14 Feb 2019 13:27:46 +0100 Subject: [PATCH] use common service interface for fence-agents and RAs --- include/crm/services.h | 5 +- lib/Makefile.am | 3 +- lib/fencing/Makefile.am | 1 + lib/fencing/st_client.c | 459 +++++++++------------------------------- lib/services/services_linux.c | 93 ++++++++ lib/services/services_private.h | 2 + 6 files changed, 203 insertions(+), 360 deletions(-) diff --git a/include/crm/services.h b/include/crm/services.h index 0186e66..eddafc3 100644 --- a/include/crm/services.h +++ b/include/crm/services.h @@ -170,7 +170,10 @@ typedef struct svc_action_s { char *agent; int timeout; - GHashTable *params; /* used by OCF agents and alert agents */ + GHashTable *params; /* used for setting up environment for ocf-ra & + alert agents + and to be sent via stdin for fence-agents + */ int rc; int pid; diff --git a/lib/Makefile.am b/lib/Makefile.am index 5563819..d73bf2e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -39,11 +39,10 @@ clean-local: rm -f *.pc ## Subdirectories... -SUBDIRS = gnu common pengine transition cib fencing services lrmd cluster +SUBDIRS = gnu common pengine transition cib services fencing lrmd cluster DIST_SUBDIRS = $(SUBDIRS) ais if BUILD_CS_PLUGIN SUBDIRS += ais endif - diff --git a/lib/fencing/Makefile.am b/lib/fencing/Makefile.am index c3f4ea2..e447627 100644 --- a/lib/fencing/Makefile.am +++ b/lib/fencing/Makefile.am @@ -15,6 +15,7 @@ libstonithd_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libstonithd_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libstonithd_la_LIBADD = $(top_builddir)/lib/common/libcrmcommon.la +libstonithd_la_LIBADD += $(top_builddir)/lib/services/libcrmservice.la libstonithd_la_SOURCES = st_client.c st_rhcs.c if BUILD_LHA_SUPPORT diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c index 3898d45..1c56cf4 100644 --- a/lib/fencing/st_client.c +++ b/lib/fencing/st_client.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -48,23 +49,18 @@ struct stonith_action_s { char *agent; char *action; char *victim; - char *args; + GHashTable *args; int timeout; int async; void *userdata; void (*done_cb) (GPid pid, gint status, const char *output, gpointer user_data); - /*! internal async track data */ - int fd_stdout; - int fd_stderr; - int last_timeout_signo; + svc_action_t *svc_action; /*! internal timing information */ time_t initial_start_time; int tries; int remaining_timeout; - guint timer_sigterm; - guint timer_sigkill; int max_retries; /* device output data */ @@ -448,13 +444,11 @@ stonith_api_register_level(stonith_t * st, int options, const char *node, int le } static void -append_arg(const char *key, const char *value, char **args) +append_arg(const char *key, const char *value, GHashTable **args) { - int len = 3; /* =, \n, \0 */ - int last = 0; - CRM_CHECK(key != NULL, return); CRM_CHECK(value != NULL, return); + CRM_CHECK(args != NULL, return); if (strstr(key, "pcmk_")) { return; @@ -464,15 +458,13 @@ append_arg(const char *key, const char *value, char **args) return; } - len += strlen(key); - len += strlen(value); - if (*args != NULL) { - last = strlen(*args); + if (!*args) { + *args = crm_str_table_new(); } - *args = realloc_safe(*args, last + len); + CRM_CHECK(*args != NULL, return); crm_trace("Appending: %s=%s", key, value); - sprintf((*args) + last, "%s=%s\n", key, value); + g_hash_table_replace(*args, strdup(key), strdup(value)); } static void @@ -489,7 +481,7 @@ append_config_arg(gpointer key, gpointer value, gpointer user_data) } static void -append_host_specific_args(const char *victim, const char *map, GHashTable * params, char **arg_list) +append_host_specific_args(const char *victim, const char *map, GHashTable * params, GHashTable **args) { char *name = NULL; int last = 0, lpc = 0, max = 0; @@ -497,7 +489,7 @@ append_host_specific_args(const char *victim, const char *map, GHashTable * para if (map == NULL) { /* The best default there is for now... */ crm_debug("Using default arg map: port=uname"); - append_arg("port", victim, arg_list); + append_arg("port", victim, args); return; } @@ -540,7 +532,7 @@ append_host_specific_args(const char *victim, const char *map, GHashTable * para if (value) { crm_debug("Setting '%s'='%s' (%s) for %s", name, value, param, victim); - append_arg(name, value, arg_list); + append_arg(name, value, args); } else { crm_err("No node attribute '%s' for '%s'", name, victim); @@ -560,12 +552,12 @@ append_host_specific_args(const char *victim, const char *map, GHashTable * para free(name); } -static char * +static GHashTable * make_args(const char *agent, const char *action, const char *victim, uint32_t victim_nodeid, GHashTable * device_args, GHashTable * port_map) { char buffer[512]; - char *arg_list = NULL; + GHashTable *arg_list = NULL; const char *value = NULL; CRM_CHECK(action != NULL, return NULL); @@ -653,66 +645,6 @@ make_args(const char *agent, const char *action, const char *victim, uint32_t vi return arg_list; } -static gboolean -st_child_term(gpointer data) -{ - int rc = 0; - stonith_action_t *track = data; - - crm_info("Child %d timed out, sending SIGTERM", track->pid); - track->timer_sigterm = 0; - track->last_timeout_signo = SIGTERM; - rc = kill(-track->pid, SIGTERM); - if (rc < 0) { - crm_perror(LOG_ERR, "Couldn't send SIGTERM to %d", track->pid); - } - return FALSE; -} - -static gboolean -st_child_kill(gpointer data) -{ - int rc = 0; - stonith_action_t *track = data; - - crm_info("Child %d timed out, sending SIGKILL", track->pid); - track->timer_sigkill = 0; - track->last_timeout_signo = SIGKILL; - rc = kill(-track->pid, SIGKILL); - if (rc < 0) { - crm_perror(LOG_ERR, "Couldn't send SIGKILL to %d", track->pid); - } - return FALSE; -} - -static void -stonith_action_clear_tracking_data(stonith_action_t * action) -{ - if (action->timer_sigterm > 0) { - g_source_remove(action->timer_sigterm); - action->timer_sigterm = 0; - } - if (action->timer_sigkill > 0) { - g_source_remove(action->timer_sigkill); - action->timer_sigkill = 0; - } - if (action->fd_stdout) { - close(action->fd_stdout); - action->fd_stdout = 0; - } - if (action->fd_stderr) { - close(action->fd_stderr); - action->fd_stderr = 0; - } - free(action->output); - action->output = NULL; - free(action->error); - action->error = NULL; - action->rc = 0; - action->pid = 0; - action->last_timeout_signo = 0; -} - /*! * \internal * \brief Free all memory used by a stonith action @@ -723,11 +655,17 @@ void stonith__destroy_action(stonith_action_t *action) { if (action) { - stonith_action_clear_tracking_data(action); free(action->agent); - free(action->args); + if (action->args) { + g_hash_table_destroy(action->args); + } free(action->action); free(action->victim); + if (action->svc_action) { + services_action_free(action->svc_action); + } + free(action->output); + free(action->error); free(action); } } @@ -809,38 +747,6 @@ stonith_action_create(const char *agent, return action; } -#define READ_MAX 500 -static char * -read_output(int fd) -{ - char buffer[READ_MAX]; - char *output = NULL; - int len = 0; - int more = 0; - - if (!fd) { - return NULL; - } - - do { - errno = 0; - memset(&buffer, 0, READ_MAX); - more = read(fd, buffer, READ_MAX - 1); - - if (more > 0) { - buffer[more] = 0; /* Make sure it's nul-terminated for logging - * 'more' is always less than our buffer size - */ - output = realloc_safe(output, len + more + 1); - snprintf(output + len, more + 1, "%s", buffer); - len += more; - } - - } while (more == (READ_MAX - 1) || (more < 0 && errno == EINTR)); - - return output; -} - static gboolean update_remaining_timeout(stonith_action_t * action) { @@ -860,58 +766,51 @@ update_remaining_timeout(stonith_action_t * action) return action->remaining_timeout ? TRUE : FALSE; } -static void -stonith_action_async_done(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) -{ - stonith_action_t *action = mainloop_child_userdata(p); - - if (action->timer_sigterm > 0) { - g_source_remove(action->timer_sigterm); - action->timer_sigterm = 0; - } - if (action->timer_sigkill > 0) { - g_source_remove(action->timer_sigkill); - action->timer_sigkill = 0; - } +static int +svc_action_to_errno(svc_action_t *svc_action) { + int rv = pcmk_ok; - action->output = read_output(action->fd_stdout); - action->error = read_output(action->fd_stderr); + if (svc_action->rc > 0) { + /* Try to provide a useful error code based on the fence agent's + * error output. + */ + if (svc_action->rc == PCMK_OCF_TIMEOUT) { + rv = -ETIME; - if (action->last_timeout_signo) { - action->rc = -ETIME; - crm_notice("Child process %d performing action '%s' timed out with signal %d", - pid, action->action, action->last_timeout_signo); + } else if (svc_action->stderr_data == NULL) { + rv = -ENODATA; - } else if (signo) { - action->rc = -ECONNABORTED; - crm_notice("Child process %d performing action '%s' timed out with signal %d", - pid, action->action, signo); + } else if (strstr(svc_action->stderr_data, "imed out")) { + /* Some agents have their own internal timeouts */ + rv = -ETIME; - } else { - crm_debug("Child process %d performing action '%s' exited with rc %d", - pid, action->action, exitcode); - if (exitcode > 0) { - /* Try to provide a useful error code based on the fence agent's - * error output. - */ - if (action->error == NULL) { - exitcode = -ENODATA; - - } else if (strstr(action->error, "imed out")) { - /* Some agents have their own internal timeouts */ - exitcode = -ETIMEDOUT; - - } else if (strstr(action->error, "Unrecognised action")) { - exitcode = -EOPNOTSUPP; + } else if (strstr(svc_action->stderr_data, "Unrecognised action")) { + rv = -EOPNOTSUPP; - } else { - exitcode = -pcmk_err_generic; - } + } else { + rv = -pcmk_err_generic; } - action->rc = exitcode; } + return rv; +} - log_action(action, pid); +static void +stonith_action_async_done(svc_action_t *svc_action) +{ + stonith_action_t *action = (stonith_action_t *) svc_action->cb_data; + + action->rc = svc_action_to_errno(svc_action); + action->output = svc_action->stdout_data; + svc_action->stdout_data = NULL; + action->error = svc_action->stderr_data; + svc_action->stderr_data = NULL; + + svc_action->params = NULL; + + crm_debug("Child process %d performing action '%s' exited with rc %d", + action->pid, action->action, svc_action->rc); + + log_action(action, action->pid); if (action->rc != pcmk_ok && update_remaining_timeout(action)) { int rc = internal_stonith_action_execute(action); @@ -921,28 +820,21 @@ stonith_action_async_done(mainloop_child_t * p, pid_t pid, int core, int signo, } if (action->done_cb) { - action->done_cb(pid, action->rc, action->output, action->userdata); + action->done_cb(action->pid, action->rc, action->output, action->userdata); } + action->svc_action = NULL; // don't remove our caller stonith__destroy_action(action); } static int internal_stonith_action_execute(stonith_action_t * action) { - int pid, status = 0, len, rc = -EPROTO; - int ret; - int total = 0; - int p_read_fd, p_write_fd; /* parent read/write file descriptors */ - int c_read_fd, c_write_fd; /* child read/write file descriptors */ - int c_stderr_fd, p_stderr_fd; /* parent/child side file descriptors for stderr */ - int fd1[2]; - int fd2[2]; - int fd3[2]; + int rc = -EPROTO; int is_retry = 0; - - /* clear any previous tracking data */ - stonith_action_clear_tracking_data(action); + svc_action_t *svc_action = NULL; + static int stonith_sequence = 0; + char *buffer = NULL; if (!action->tries) { action->initial_start_time = time(NULL); @@ -955,207 +847,60 @@ internal_stonith_action_execute(stonith_action_t * action) is_retry = 1; } - c_read_fd = c_write_fd = p_read_fd = p_write_fd = c_stderr_fd = p_stderr_fd = -1; - if (action->args == NULL || action->agent == NULL) goto fail; - len = strlen(action->args); - - if (pipe(fd1)) - goto fail; - p_read_fd = fd1[0]; - c_write_fd = fd1[1]; - - if (pipe(fd2)) - goto fail; - c_read_fd = fd2[0]; - p_write_fd = fd2[1]; - - if (pipe(fd3)) - goto fail; - p_stderr_fd = fd3[0]; - c_stderr_fd = fd3[1]; - - crm_debug("forking"); - pid = fork(); - if (pid < 0) { - rc = -ECHILD; - goto fail; - } - - if (!pid) { - /* child */ - setpgid(0, 0); - - close(1); - /* coverity[leaked_handle] False positive */ - if (dup(c_write_fd) < 0) - goto fail; - close(2); - /* coverity[leaked_handle] False positive */ - if (dup(c_stderr_fd) < 0) - goto fail; - close(0); - /* coverity[leaked_handle] False positive */ - if (dup(c_read_fd) < 0) - goto fail; - - /* keep c_stderr_fd open so parent can report all errors. */ - /* keep c_write_fd open so hostlist can be sent to parent. */ - close(c_read_fd); - close(p_read_fd); - close(p_write_fd); - close(p_stderr_fd); - - /* keep retries from executing out of control */ - if (is_retry) { - sleep(1); - } - execlp(action->agent, action->agent, NULL); - exit(EXIT_FAILURE); - } - /* parent */ - action->pid = pid; - ret = crm_set_nonblocking(p_read_fd); - if (ret < 0) { - crm_notice("Could not set output of %s to be non-blocking: %s " - CRM_XS " rc=%d", - action->agent, pcmk_strerror(rc), rc); + buffer = crm_strdup_printf(RH_STONITH_DIR "/%s", basename(action->agent)); + svc_action = services_action_create_generic(buffer, NULL); + free(buffer); + svc_action->timeout = 1000 * action->remaining_timeout; + svc_action->standard = strdup(PCMK_RESOURCE_CLASS_STONITH); + svc_action->id = crm_strdup_printf("%s_%s_%d", basename(action->agent), + action->action, action->tries); + svc_action->agent = strdup(action->agent); + svc_action->sequence = stonith_sequence++; + svc_action->params = action->args; + svc_action->cb_data = (void *) action; + + /* keep retries from executing out of control and free previous results */ + if (is_retry) { + free(action->output); + action->output = NULL; + free(action->error); + action->error = NULL; + sleep(1); } - ret = crm_set_nonblocking(p_stderr_fd); - if (ret < 0) { - crm_notice("Could not set error output of %s to be non-blocking: %s " - CRM_XS " rc=%d", - action->agent, pcmk_strerror(rc), rc); - } - - errno = 0; - do { - crm_debug("sending args"); - ret = write(p_write_fd, action->args + total, len - total); - if (ret > 0) { - total += ret; - } - - } while (errno == EINTR && total < len); - - if (total != len) { - crm_perror(LOG_ERR, "Sent %d not %d bytes", total, len); - if (ret >= 0) { - rc = -ECOMM; - } - goto fail; - } - - close(p_write_fd); p_write_fd = -1; - /* async */ if (action->async) { - action->fd_stdout = p_read_fd; - action->fd_stderr = p_stderr_fd; - mainloop_child_add(pid, 0/* Move the timeout here? */, action->action, action, stonith_action_async_done); - crm_trace("Op: %s on %s, pid: %d, timeout: %ds", action->action, action->agent, pid, - action->remaining_timeout); - action->last_timeout_signo = 0; - if (action->remaining_timeout) { - action->timer_sigterm = - g_timeout_add(1000 * action->remaining_timeout, st_child_term, action); - action->timer_sigkill = - g_timeout_add(1000 * (action->remaining_timeout + 5), st_child_kill, action); + /* async */ + if(services_action_async(svc_action, &stonith_action_async_done) == FALSE) { + services_action_free(svc_action); + svc_action = NULL; } else { - crm_err("No timeout set for stonith operation %s with device %s", - action->action, action->agent); + action->pid = svc_action->pid; + action->svc_action = svc_action; + rc = 0; } - close(c_write_fd); - close(c_read_fd); - close(c_stderr_fd); - return 0; - } else { /* sync */ - int timeout = action->remaining_timeout + 1; - pid_t p = 0; - - while (action->remaining_timeout < 0 || timeout > 0) { - p = waitpid(pid, &status, WNOHANG); - if (p > 0) { - break; - } - sleep(1); - timeout--; - } - - if (timeout == 0) { - int killrc = kill(-pid, SIGKILL); - - if (killrc && errno != ESRCH) { - crm_err("kill(%d, KILL) failed: %s (%d)", pid, pcmk_strerror(errno), errno); - } - /* - * From sigprocmask(2): - * It is not possible to block SIGKILL or SIGSTOP. Attempts to do so are silently ignored. - * - * This makes it safe to skip WNOHANG here - */ - p = waitpid(pid, &status, 0); - } - - if (p <= 0) { - crm_perror(LOG_ERR, "waitpid(%d)", pid); - - } else if (p != pid) { - crm_err("Waited for %d, got %d", pid, p); - } - - action->output = read_output(p_read_fd); - action->error = read_output(p_stderr_fd); - - action->rc = -ECONNABORTED; - - log_action(action, pid); - - rc = action->rc; - if (timeout == 0) { - action->rc = -ETIME; - } else if (WIFEXITED(status)) { - crm_debug("result = %d", WEXITSTATUS(status)); - action->rc = -WEXITSTATUS(status); + if (services_action_sync(svc_action)) { rc = 0; - - } else if (WIFSIGNALED(status)) { - crm_err("call %s for %s exited due to signal %d", action->action, action->agent, - WTERMSIG(status)); - + action->rc = svc_action_to_errno(svc_action); + action->output = svc_action->stdout_data; + svc_action->stdout_data = NULL; + action->error = svc_action->stderr_data; + svc_action->stderr_data = NULL; } else { - crm_err("call %s for %s returned unexpected status %#x", - action->action, action->agent, status); + action->rc = -ECONNABORTED; + rc = action->rc; } - } - fail: - - if (p_read_fd >= 0) { - close(p_read_fd); - } - if (p_write_fd >= 0) { - close(p_write_fd); - } - if (p_stderr_fd >= 0) { - close(p_stderr_fd); - } - - if (c_read_fd >= 0) { - close(c_read_fd); - } - if (c_write_fd >= 0) { - close(c_write_fd); - } - if (c_stderr_fd >= 0) { - close(c_stderr_fd); + svc_action->params = NULL; + services_action_free(svc_action); } + fail: return rc; } diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index a413484..d79c16d 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -195,6 +195,39 @@ add_action_env_vars(const svc_action_t *op) } } +static void +pipe_in_single_parameter(gpointer key, gpointer value, gpointer user_data) +{ + svc_action_t *op = user_data; + char *buffer = crm_strdup_printf("%s=%s\n", (char *)key, (char *) value); + int ret, total = 0, len = strlen(buffer); + + do { + errno = 0; + ret = write(op->opaque->stdin_fd, buffer + total, len - total); + if (ret > 0) { + total += ret; + } + + } while ((errno == EINTR) && (total < len)); + free(buffer); +} + +/*! + * \internal + * \brief Pipe parameters in via stdin for action + * + * \param[in] op Action to use + */ +static void +pipe_in_action_stdin_parameters(const svc_action_t *op) +{ + crm_debug("sending args"); + if (op->params) { + g_hash_table_foreach(op->params, pipe_in_single_parameter, (gpointer) op); + } +} + gboolean recurring_action_timer(gpointer data) { @@ -284,6 +317,10 @@ operation_finished(mainloop_child_t * p, pid_t pid, int core, int signo, int exi op->opaque->stdout_gsource = NULL; } + if (op->opaque->stdin_fd >= 0) { + close(op->opaque->stdin_fd); + } + if (signo) { if (mainloop_child_timeout(p)) { crm_warn("%s - timed out after %dms", prefix, op->timeout); @@ -605,6 +642,9 @@ action_synced_wait(svc_action_t * op, sigset_t *mask) close(op->opaque->stdout_fd); close(op->opaque->stderr_fd); + if (op->opaque->stdin_fd >= 0) { + close(op->opaque->stdin_fd); + } #ifdef HAVE_SYS_SIGNALFD_H close(sfd); @@ -618,6 +658,7 @@ services_os_action_execute(svc_action_t * op) { int stdout_fd[2]; int stderr_fd[2]; + int stdin_fd[2] = {-1, -1}; int rc; struct stat st; sigset_t *pmask; @@ -683,6 +724,25 @@ services_os_action_execute(svc_action_t * op) return FALSE; } + if (safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_STONITH)) { + if (pipe(stdin_fd) < 0) { + rc = errno; + + close(stdout_fd[0]); + close(stdout_fd[1]); + close(stderr_fd[0]); + close(stderr_fd[1]); + + crm_err("pipe(stdin_fd) failed. '%s': %s (%d)", op->opaque->exec, pcmk_strerror(rc), rc); + + services_handle_exec_error(op, rc); + if (!op->synchronous) { + return operation_finalize(op); + } + return FALSE; + } + } + if (op->synchronous) { #ifdef HAVE_SYS_SIGNALFD_H sigemptyset(&mask); @@ -730,6 +790,10 @@ services_os_action_execute(svc_action_t * op) close(stdout_fd[1]); close(stderr_fd[0]); close(stderr_fd[1]); + if (stdin_fd[0] >= 0) { + close(stdin_fd[0]); + close(stdin_fd[1]); + } crm_err("Could not execute '%s': %s (%d)", op->opaque->exec, pcmk_strerror(rc), rc); services_handle_exec_error(op, rc); @@ -743,6 +807,9 @@ services_os_action_execute(svc_action_t * op) case 0: /* Child */ close(stdout_fd[0]); close(stderr_fd[0]); + if (stdin_fd[1] >= 0) { + close(stdin_fd[1]); + } if (STDOUT_FILENO != stdout_fd[1]) { if (dup2(stdout_fd[1], STDOUT_FILENO) != STDOUT_FILENO) { crm_err("dup2() failed (stdout)"); @@ -755,6 +822,13 @@ services_os_action_execute(svc_action_t * op) } close(stderr_fd[1]); } + if ((stdin_fd[0] >= 0) && + (STDIN_FILENO != stdin_fd[0])) { + if (dup2(stdin_fd[0], STDIN_FILENO) != STDIN_FILENO) { + crm_err("dup2() failed (stdin)"); + } + close(stdin_fd[0]); + } if (op->synchronous) { sigchld_cleanup(); @@ -767,6 +841,9 @@ services_os_action_execute(svc_action_t * op) /* Only the parent reaches here */ close(stdout_fd[1]); close(stderr_fd[1]); + if (stdin_fd[0] >= 0) { + close(stdin_fd[0]); + } op->opaque->stdout_fd = stdout_fd[0]; rc = crm_set_nonblocking(op->opaque->stdout_fd); @@ -784,6 +861,22 @@ services_os_action_execute(svc_action_t * op) pcmk_strerror(rc), rc); } + op->opaque->stdin_fd = stdin_fd[1]; + if (op->opaque->stdin_fd >= 0) { + // using buffer behind non-blocking-fd here - that could be improved + // as long as no other standard uses stdin_fd assume stonith + rc = crm_set_nonblocking(op->opaque->stdin_fd); + if (rc < 0) { + crm_warn("Could not set child input non-blocking: %s " + CRM_XS " fd=%d,rc=%d", + pcmk_strerror(rc), op->opaque->stdin_fd, rc); + } + pipe_in_action_stdin_parameters(op); + // as long as we are handling parameters directly in here just close + close(op->opaque->stdin_fd); + op->opaque->stdin_fd = -1; + } + if (op->synchronous) { action_synced_wait(op, pmask); sigchld_cleanup(); diff --git a/lib/services/services_private.h b/lib/services/services_private.h index 0676c6f..9735da7 100644 --- a/lib/services/services_private.h +++ b/lib/services/services_private.h @@ -42,6 +42,8 @@ struct svc_action_private_s { int stdout_fd; mainloop_io_t *stdout_gsource; + + int stdin_fd; #if SUPPORT_DBUS DBusPendingCall* pending; unsigned timerid; -- 1.8.3.1