Blob Blame History Raw
From 06ef8406cf224af6b94dc4672c9b6caa15133f89 Mon Sep 17 00:00:00 2001
From: Klaus Wenninger <klaus.wenninger@aon.at>
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 <stdio.h>
 #include <string.h>
 #include <ctype.h>
+#include <libgen.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -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