Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4624396
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
90 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/services/Makefile.am b/lib/services/Makefile.am
index 15897df889..7e72ac2dfe 100644
--- a/lib/services/Makefile.am
+++ b/lib/services/Makefile.am
@@ -1,41 +1,43 @@
#
# Copyright 2012-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU Lesser General Public License
# version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
#
MAINTAINERCLEANFILES = Makefile.in
AM_CPPFLAGS = -I$(top_srcdir)/include
lib_LTLIBRARIES = libcrmservice.la
noinst_HEADERS = pcmk-dbus.h upstart.h systemd.h \
services_lsb.h services_nagios.h \
+ services_ocf.h \
services_private.h
libcrmservice_la_LDFLAGS = -version-info 30:4:2
libcrmservice_la_CFLAGS =
libcrmservice_la_CFLAGS += $(CFLAGS_HARDENED_LIB)
libcrmservice_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
libcrmservice_la_LIBADD = $(top_builddir)/lib/common/libcrmcommon.la $(DBUS_LIBS)
libcrmservice_la_SOURCES = services.c
libcrmservice_la_SOURCES += services_linux.c
libcrmservice_la_SOURCES += services_lsb.c
+libcrmservice_la_SOURCES += services_ocf.c
if BUILD_DBUS
libcrmservice_la_SOURCES += dbus.c
endif
if BUILD_UPSTART
libcrmservice_la_SOURCES += upstart.c
endif
if BUILD_SYSTEMD
libcrmservice_la_SOURCES += systemd.c
endif
if BUILD_NAGIOS
libcrmservice_la_SOURCES += services_nagios.c
endif
diff --git a/lib/services/services.c b/lib/services/services.c
index a9e56bd1c3..cf7c46c857 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -1,1357 +1,1358 @@
/*
* Copyright 2010-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/common/mainloop.h>
#include <crm/services.h>
#include <crm/services_internal.h>
#include <crm/stonith-ng.h>
#include <crm/msg_xml.h>
#include "services_private.h"
+#include "services_ocf.h"
#include "services_lsb.h"
#if SUPPORT_UPSTART
# include <upstart.h>
#endif
#if SUPPORT_SYSTEMD
# include <systemd.h>
#endif
#if SUPPORT_NAGIOS
# include <services_nagios.h>
#endif
/* TODO: Develop a rollover strategy */
static int operations = 0;
static GHashTable *recurring_actions = NULL;
/* ops waiting to run async because of conflicting active
* pending ops */
static GList *blocked_ops = NULL;
/* ops currently active (in-flight) */
static GList *inflight_ops = NULL;
static void handle_blocked_ops(void);
/*!
* \brief Find first service class that can provide a specified agent
*
* \param[in] agent Name of agent to search for
*
* \return Service class if found, NULL otherwise
*
* \note The priority is LSB, then systemd, then upstart. It would be preferable
* to put systemd first, but LSB merely requires a file existence check,
* while systemd requires contacting D-Bus.
*/
const char *
resources_find_service_class(const char *agent)
{
if (services__lsb_agent_exists(agent)) {
return PCMK_RESOURCE_CLASS_LSB;
}
#if SUPPORT_SYSTEMD
if (systemd_unit_exists(agent)) {
return PCMK_RESOURCE_CLASS_SYSTEMD;
}
#endif
#if SUPPORT_UPSTART
if (upstart_job_exists(agent)) {
return PCMK_RESOURCE_CLASS_UPSTART;
}
#endif
return NULL;
}
static inline void
init_recurring_actions(void)
{
if (recurring_actions == NULL) {
recurring_actions = pcmk__strkey_table(NULL, NULL);
}
}
/*!
* \internal
* \brief Check whether op is in-flight systemd or upstart op
*
* \param[in] op Operation to check
*
* \return TRUE if op is in-flight systemd or upstart op
*/
static inline gboolean
inflight_systemd_or_upstart(svc_action_t *op)
{
return pcmk__strcase_any_of(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
PCMK_RESOURCE_CLASS_UPSTART, NULL) &&
g_list_find(inflight_ops, op) != NULL;
}
/*!
* \internal
* \brief Expand "service" alias to an actual resource class
*
* \param[in] rsc Resource name (for logging only)
* \param[in] standard Resource class as configured
* \param[in] agent Agent name to look for
*
* \return Newly allocated string with actual resource class
*
* \note The caller is responsible for calling free() on the result.
*/
static char *
expand_resource_class(const char *rsc, const char *standard, const char *agent)
{
char *expanded_class = NULL;
if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
const char *found_class = resources_find_service_class(agent);
if (found_class) {
crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
expanded_class = strdup(found_class);
} else {
crm_info("Assuming resource class lsb for agent %s for %s",
agent, rsc);
expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
}
} else {
expanded_class = strdup(standard);
}
CRM_ASSERT(expanded_class);
return expanded_class;
}
/*!
* \internal
* \brief Create a simple svc_action_t instance
*
* \return Newly allocated instance (or NULL if not enough memory)
*/
static svc_action_t *
new_action(void)
{
svc_action_t *op = calloc(1, sizeof(svc_action_t));
if (op == NULL) {
return NULL;
}
op->opaque = calloc(1, sizeof(svc_action_private_t));
if (op->opaque == NULL) {
free(op);
return NULL;
}
// Initialize result
services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN, NULL);
return op;
}
static bool
required_argument_missing(uint32_t ra_caps, const char *name,
const char *standard, const char *provider,
const char *agent, const char *action)
{
if (pcmk__str_empty(name)) {
crm_info("Cannot create operation without resource name (bug?)");
return true;
}
if (pcmk__str_empty(standard)) {
crm_info("Cannot create operation for %s without resource class (bug?)",
name);
return true;
}
if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)
&& pcmk__str_empty(provider)) {
crm_info("Cannot create operation for %s resource %s "
"without provider (bug?)", standard, name);
return true;
}
if (pcmk__str_empty(agent)) {
crm_info("Cannot create operation for %s without agent name (bug?)",
name);
return true;
}
if (pcmk__str_empty(action)) {
crm_info("Cannot create operation for %s without action name (bug?)",
name);
return true;
}
return false;
}
// \return Standard Pacemaker return code (pcmk_rc_ok or ENOMEM)
static int
copy_action_arguments(svc_action_t *op, uint32_t ra_caps, const char *name,
const char *standard, const char *provider,
const char *agent, const char *action)
{
op->rsc = strdup(name);
if (op->rsc == NULL) {
return ENOMEM;
}
op->agent = strdup(agent);
if (op->agent == NULL) {
return ENOMEM;
}
op->standard = expand_resource_class(name, standard, agent);
if (op->standard == NULL) {
return ENOMEM;
}
if (pcmk_is_set(ra_caps, pcmk_ra_cap_status)
&& pcmk__str_eq(action, "monitor", pcmk__str_casei)) {
action = "status";
}
op->action = strdup(action);
if (op->action == NULL) {
return ENOMEM;
}
if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)) {
op->provider = strdup(provider);
if (op->provider == NULL) {
return ENOMEM;
}
}
return pcmk_rc_ok;
}
svc_action_t *
services__create_resource_action(const char *name, const char *standard,
const char *provider, const char *agent,
const char *action, guint interval_ms, int timeout,
GHashTable *params, enum svc_action_flags flags)
{
svc_action_t *op = NULL;
uint32_t ra_caps = pcmk_get_ra_caps(standard);
op = new_action();
if (op == NULL) {
crm_crit("Cannot prepare action: %s", strerror(ENOMEM));
if (params != NULL) {
g_hash_table_destroy(params);
}
return NULL;
}
op->interval_ms = interval_ms;
op->timeout = timeout;
op->flags = flags;
op->sequence = ++operations;
// Take ownership of params
if (pcmk_is_set(ra_caps, pcmk_ra_cap_params)) {
op->params = params;
} else if (params != NULL) {
g_hash_table_destroy(params);
params = NULL;
}
if (required_argument_missing(ra_caps, name, standard, provider, agent,
action)) {
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR_FATAL,
"Required agent or action information missing");
return op;
}
op->id = pcmk__op_key(name, action, interval_ms);
if (copy_action_arguments(op, ra_caps, name, standard, provider, agent,
action) != pcmk_rc_ok) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
char *dirs = NULL;
char *dir = NULL;
char *buf = NULL;
struct stat st;
dirs = strdup(OCF_RA_PATH);
if (dirs == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
buf = crm_strdup_printf("%s/%s/%s", dir, provider, agent);
if (stat(buf, &st) == 0) {
break;
}
free(buf);
buf = NULL;
}
free(dirs);
if (buf) {
op->opaque->exec = buf;
} else {
crm_err("Cannot create %s operation for %s: %s",
action, name, strerror(ENOENT));
services__handle_exec_error(op, ENOENT);
return op;
}
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(op->action);
if ((op->opaque->args[0] == NULL) || (op->opaque->args[1] == NULL)) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
op->opaque->exec = pcmk__full_path(op->agent, LSB_ROOT_DIR);
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(op->action);
if ((op->opaque->args[0] == NULL) || (op->opaque->args[1] == NULL)) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
#if SUPPORT_SYSTEMD
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
op->opaque->exec = strdup("systemd-dbus");
if (op->opaque->exec == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
op->opaque->exec = strdup("upstart-dbus");
if (op->opaque->exec == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
#endif
#if SUPPORT_NAGIOS
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
op->opaque->exec = pcmk__full_path(op->agent, NAGIOS_PLUGIN_DIR);
op->opaque->args[0] = strdup(op->opaque->exec);
if (op->opaque->args[0] == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
if (pcmk__str_eq(op->action, "monitor", pcmk__str_casei) && (op->interval_ms == 0)) {
/* Invoke --version for a nagios probe */
op->opaque->args[1] = strdup("--version");
if (op->opaque->args[1] == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
} else if (op->params) {
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
int index = 1; // 0 is already set to executable name
g_hash_table_iter_init(&iter, op->params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
if (index > (PCMK__NELEM(op->opaque->args) - 2)) {
crm_info("Cannot prepare %s action for %s: Too many parameters",
action, name);
services__set_result(op, NAGIOS_STATE_UNKNOWN,
PCMK_EXEC_ERROR_HARD,
"Too many parameters");
break;
}
if (pcmk__str_eq(key, XML_ATTR_CRM_VERSION, pcmk__str_casei) || strstr(key, CRM_META "_")) {
continue;
}
op->opaque->args[index++] = crm_strdup_printf("--%s", key);
op->opaque->args[index++] = strdup(value);
if (op->opaque->args[index - 1] == NULL) {
crm_crit("Cannot prepare %s action for %s: %s",
action, name, strerror(ENOMEM));
services__handle_exec_error(op, ENOMEM);
return op;
}
}
}
// Nagios actions don't need to keep the parameters
if (op->params != NULL) {
g_hash_table_destroy(op->params);
op->params = NULL;
}
#endif
} else {
crm_err("Unknown resource standard: %s", op->standard);
services__handle_exec_error(op, ENOENT);
}
return op;
}
svc_action_t *
resources_action_create(const char *name, const char *standard,
const char *provider, const char *agent,
const char *action, guint interval_ms, int timeout,
GHashTable *params, enum svc_action_flags flags)
{
svc_action_t *op = services__create_resource_action(name, standard,
provider, agent, action, interval_ms, timeout,
params, flags);
if (op == NULL || op->rc != 0) {
services_action_free(op);
return NULL;
} else {
// Preserve public API backward compatibility
op->rc = PCMK_OCF_OK;
op->status = PCMK_EXEC_DONE;
return op;
}
}
svc_action_t *
services_action_create_generic(const char *exec, const char *args[])
{
svc_action_t *op = new_action();
CRM_ASSERT(op != NULL);
op->opaque->exec = strdup(exec);
op->opaque->args[0] = strdup(exec);
if ((op->opaque->exec == NULL) || (op->opaque->args[0] == NULL)) {
crm_crit("Cannot prepare action for '%s': %s", exec, strerror(ENOMEM));
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
strerror(ENOMEM));
return op;
}
if (args == NULL) {
return op;
}
for (int cur_arg = 1; args[cur_arg - 1] != NULL; cur_arg++) {
if (cur_arg == PCMK__NELEM(op->opaque->args)) {
crm_info("Cannot prepare action for '%s': Too many arguments",
exec);
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR,
PCMK_EXEC_ERROR_HARD, "Too many arguments");
break;
}
op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
if (op->opaque->args[cur_arg] == NULL) {
crm_crit("Cannot prepare action for '%s': %s",
exec, strerror(ENOMEM));
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
strerror(ENOMEM));
break;
}
}
return op;
}
/*!
* \brief Create an alert agent action
*
* \param[in] id Alert ID
* \param[in] exec Path to alert agent executable
* \param[in] timeout Action timeout
* \param[in] params Parameters to use with action
* \param[in] sequence Action sequence number
* \param[in] cb_data Data to pass to callback function
*
* \return New action on success, NULL on error
* \note It is the caller's responsibility to free cb_data.
* The caller should not free params explicitly.
*/
svc_action_t *
services_alert_create(const char *id, const char *exec, int timeout,
GHashTable *params, int sequence, void *cb_data)
{
svc_action_t *action = services_action_create_generic(exec, NULL);
action->timeout = timeout;
action->id = strdup(id);
action->params = params;
action->sequence = sequence;
action->cb_data = cb_data;
return action;
}
/*!
* \brief Set the user and group that an action will execute as
*
* \param[in,out] action Action to modify
* \param[in] user Name of user to execute action as
* \param[in] group Name of group to execute action as
*
* \return pcmk_ok on success, -errno otherwise
*
* \note This will have no effect unless the process executing the action runs
* as root, and the action is not a systemd or upstart action.
* We could implement this for systemd by adding User= and Group= to
* [Service] in the override file, but that seems more likely to cause
* problems than be useful.
*/
int
services_action_user(svc_action_t *op, const char *user)
{
CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
}
/*!
* \brief Execute an alert agent action
*
* \param[in] action Action to execute
* \param[in] cb Function to call when action completes
*
* \return TRUE if the library will free action, FALSE otherwise
*
* \note If this function returns FALSE, it is the caller's responsibility to
* free the action with services_action_free().
*/
gboolean
services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
{
action->synchronous = false;
action->opaque->callback = cb;
return services__execute_file(action) == pcmk_rc_ok;
}
#if SUPPORT_DBUS
/*!
* \internal
* \brief Update operation's pending DBus call, unreferencing old one if needed
*
* \param[in,out] op Operation to modify
* \param[in] pending Pending call to set
*/
void
services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
{
if (op->opaque->pending && (op->opaque->pending != pending)) {
if (pending) {
crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
} else {
crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
}
dbus_pending_call_unref(op->opaque->pending);
}
op->opaque->pending = pending;
if (pending) {
crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
} else {
crm_trace("Cleared pending %s DBus call", op->id);
}
}
#endif
void
services_action_cleanup(svc_action_t * op)
{
if ((op == NULL) || (op->opaque == NULL)) {
return;
}
#if SUPPORT_DBUS
if(op->opaque->timerid != 0) {
crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
g_source_remove(op->opaque->timerid);
op->opaque->timerid = 0;
}
if(op->opaque->pending) {
if (dbus_pending_call_get_completed(op->opaque->pending)) {
// This should never be the case
crm_warn("Result of %s op %s was unhandled",
op->standard, op->id);
} else {
crm_debug("Will ignore any result of canceled %s op %s",
op->standard, op->id);
}
dbus_pending_call_cancel(op->opaque->pending);
services_set_op_pending(op, NULL);
}
#endif
if (op->opaque->stderr_gsource) {
mainloop_del_fd(op->opaque->stderr_gsource);
op->opaque->stderr_gsource = NULL;
}
if (op->opaque->stdout_gsource) {
mainloop_del_fd(op->opaque->stdout_gsource);
op->opaque->stdout_gsource = NULL;
}
}
void
services_action_free(svc_action_t * op)
{
unsigned int i;
if (op == NULL) {
return;
}
/* The operation should be removed from all tracking lists by this point.
* If it's not, we have a bug somewhere, so bail. That may lead to a
* memory leak, but it's better than a use-after-free segmentation fault.
*/
CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
CRM_CHECK((recurring_actions == NULL)
|| (g_hash_table_lookup(recurring_actions, op->id) == NULL),
return);
services_action_cleanup(op);
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
free(op->id);
free(op->opaque->exec);
for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
free(op->opaque->args[i]);
}
free(op->opaque->exit_reason);
free(op->opaque);
free(op->rsc);
free(op->action);
free(op->standard);
free(op->agent);
free(op->provider);
free(op->stdout_data);
free(op->stderr_data);
if (op->params) {
g_hash_table_destroy(op->params);
op->params = NULL;
}
free(op);
}
gboolean
cancel_recurring_action(svc_action_t * op)
{
crm_info("Cancelling %s operation %s", op->standard, op->id);
if (recurring_actions) {
g_hash_table_remove(recurring_actions, op->id);
}
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
return TRUE;
}
/*!
* \brief Cancel a recurring action
*
* \param[in] name Name of resource that operation is for
* \param[in] action Name of operation to cancel
* \param[in] interval_ms Interval of operation to cancel
*
* \return TRUE if action was successfully cancelled, FALSE otherwise
*/
gboolean
services_action_cancel(const char *name, const char *action, guint interval_ms)
{
gboolean cancelled = FALSE;
char *id = pcmk__op_key(name, action, interval_ms);
svc_action_t *op = NULL;
/* We can only cancel a recurring action */
init_recurring_actions();
op = g_hash_table_lookup(recurring_actions, id);
if (op == NULL) {
goto done;
}
// Tell services__finalize_async_op() not to reschedule the operation
op->cancel = TRUE;
/* Stop tracking it as a recurring operation, and stop its repeat timer */
cancel_recurring_action(op);
/* If the op has a PID, it's an in-flight child process, so kill it.
*
* Whether the kill succeeds or fails, the main loop will send the op to
* async_action_complete() (and thus services__finalize_async_op()) when the
* process goes away.
*/
if (op->pid != 0) {
crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
id, op->pid);
cancelled = mainloop_child_kill(op->pid);
if (cancelled == FALSE) {
crm_err("Termination of %s[%d] failed", id, op->pid);
}
goto done;
}
#if SUPPORT_DBUS
// In-flight systemd and upstart ops don't have a pid
if (inflight_systemd_or_upstart(op)) {
inflight_ops = g_list_remove(inflight_ops, op);
/* This will cause any result that comes in later to be discarded, so we
* don't call the callback and free the operation twice.
*/
services_action_cleanup(op);
}
#endif
/* The rest of this is essentially equivalent to
* services__finalize_async_op(), minus the handle_blocked_ops() call.
*/
// Report operation as cancelled
services__set_result(op, op->rc, PCMK_EXEC_CANCELLED, NULL);
if (op->opaque->callback) {
op->opaque->callback(op);
}
blocked_ops = g_list_remove(blocked_ops, op);
services_action_free(op);
cancelled = TRUE;
// @TODO Initiate handle_blocked_ops() asynchronously
done:
free(id);
return cancelled;
}
gboolean
services_action_kick(const char *name, const char *action, guint interval_ms)
{
svc_action_t * op = NULL;
char *id = pcmk__op_key(name, action, interval_ms);
init_recurring_actions();
op = g_hash_table_lookup(recurring_actions, id);
free(id);
if (op == NULL) {
return FALSE;
}
if (op->pid || inflight_systemd_or_upstart(op)) {
return TRUE;
} else {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(op);
return TRUE;
}
}
/*!
* \internal
* \brief Add a new recurring operation, checking for duplicates
*
* \param[in] op Operation to add
*
* \return TRUE if duplicate found (and reschedule), FALSE otherwise
*/
static gboolean
handle_duplicate_recurring(svc_action_t * op)
{
svc_action_t * dup = NULL;
/* check for duplicates */
dup = g_hash_table_lookup(recurring_actions, op->id);
if (dup && (dup != op)) {
/* update user data */
if (op->opaque->callback) {
dup->opaque->callback = op->opaque->callback;
dup->cb_data = op->cb_data;
op->cb_data = NULL;
}
/* immediately execute the next interval */
if (dup->pid != 0) {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(dup);
}
/* free the duplicate */
services_action_free(op);
return TRUE;
}
return FALSE;
}
/*!
* \internal
* \brief Execute an action appropriately according to its standard
*
* \param[in] op Action to execute
*
* \return Standard Pacemaker return code
* \retval EBUSY Recurring operation could not be initiated
* \retval pcmk_rc_error Synchronous action failed
* \retval pcmk_rc_ok Synchronous action succeeded, or asynchronous action
* should not be freed (because it already was or is
* pending)
*
* \note If the return value for an asynchronous action is not pcmk_rc_ok, the
* caller is responsible for freeing the action.
*/
static int
execute_action(svc_action_t *op)
{
#if SUPPORT_UPSTART
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART,
pcmk__str_casei)) {
return services__execute_upstart(op);
}
#endif
#if SUPPORT_SYSTEMD
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
pcmk__str_casei)) {
return services__execute_systemd(op);
}
#endif
return services__execute_file(op);
}
void
services_add_inflight_op(svc_action_t * op)
{
if (op == NULL) {
return;
}
CRM_ASSERT(op->synchronous == FALSE);
/* keep track of ops that are in-flight to avoid collisions in the same namespace */
if (op->rsc) {
inflight_ops = g_list_append(inflight_ops, op);
}
}
/*!
* \internal
* \brief Stop tracking an operation that completed
*
* \param[in] op Operation to stop tracking
*/
void
services_untrack_op(svc_action_t *op)
{
/* Op is no longer in-flight or blocked */
inflight_ops = g_list_remove(inflight_ops, op);
blocked_ops = g_list_remove(blocked_ops, op);
/* Op is no longer blocking other ops, so check if any need to run */
handle_blocked_ops();
}
gboolean
services_action_async_fork_notify(svc_action_t * op,
void (*action_callback) (svc_action_t *),
void (*action_fork_callback) (svc_action_t *))
{
op->synchronous = false;
if (action_callback) {
op->opaque->callback = action_callback;
}
if (action_fork_callback) {
op->opaque->fork_callback = action_fork_callback;
}
if (op->interval_ms > 0) {
init_recurring_actions();
if (handle_duplicate_recurring(op) == TRUE) {
/* entry rescheduled, dup freed */
/* exit early */
return TRUE;
}
g_hash_table_replace(recurring_actions, op->id, op);
}
if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
&& op->rsc && is_op_blocked(op->rsc)) {
blocked_ops = g_list_append(blocked_ops, op);
return TRUE;
}
return execute_action(op) == pcmk_rc_ok;
}
gboolean
services_action_async(svc_action_t * op,
void (*action_callback) (svc_action_t *))
{
return services_action_async_fork_notify(op, action_callback, NULL);
}
static gboolean processing_blocked_ops = FALSE;
gboolean
is_op_blocked(const char *rsc)
{
GList *gIter = NULL;
svc_action_t *op = NULL;
for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
return TRUE;
}
}
return FALSE;
}
static void
handle_blocked_ops(void)
{
GList *executed_ops = NULL;
GList *gIter = NULL;
svc_action_t *op = NULL;
if (processing_blocked_ops) {
/* avoid nested calling of this function */
return;
}
processing_blocked_ops = TRUE;
/* n^2 operation here, but blocked ops are incredibly rare. this list
* will be empty 99% of the time. */
for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (is_op_blocked(op->rsc)) {
continue;
}
executed_ops = g_list_append(executed_ops, op);
if (execute_action(op) != pcmk_rc_ok) {
/* this can cause this function to be called recursively
* which is why we have processing_blocked_ops static variable */
services__finalize_async_op(op);
}
}
for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
blocked_ops = g_list_remove(blocked_ops, op);
}
g_list_free(executed_ops);
processing_blocked_ops = FALSE;
}
/*!
* \internal
* \brief Execute a meta-data action appropriately to standard
*
* \param[in] op Meta-data action to execute
*
* \return Standard Pacemaker return code
*/
static int
execute_metadata_action(svc_action_t *op)
{
const char *class = op->standard;
if (op->agent == NULL) {
crm_err("meta-data requested without specifying agent");
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR_FATAL, "Agent not specified");
return EINVAL;
}
if (class == NULL) {
crm_err("meta-data requested for agent %s without specifying class",
op->agent);
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR_FATAL,
"Agent standard not specified");
return EINVAL;
}
if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
class = resources_find_service_class(op->agent);
}
if (class == NULL) {
crm_err("meta-data requested for %s, but could not determine class",
op->agent);
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR_HARD,
"Agent standard could not be determined");
return EINVAL;
}
if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
return pcmk_legacy2rc(services__get_lsb_metadata(op->agent,
&op->stdout_data));
}
#if SUPPORT_NAGIOS
if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
return pcmk_legacy2rc(services__get_nagios_metadata(op->agent,
&op->stdout_data));
}
#endif
return execute_action(op);
}
gboolean
services_action_sync(svc_action_t * op)
{
gboolean rc = TRUE;
if (op == NULL) {
crm_trace("No operation to execute");
return FALSE;
}
op->synchronous = true;
if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) {
/* Synchronous meta-data operations are handled specially. Since most
* resource classes don't provide any meta-data, it has to be
* synthesized from available information about the agent.
*
* services_action_async() doesn't treat meta-data actions specially, so
* it will result in an error for classes that don't support the action.
*/
rc = (execute_metadata_action(op) == pcmk_rc_ok);
} else {
rc = (execute_action(op) == pcmk_rc_ok);
}
crm_trace(" > " PCMK__OP_FMT ": %s = %d",
op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
if (op->stdout_data) {
crm_trace(" > stdout: %s", op->stdout_data);
}
if (op->stderr_data) {
crm_trace(" > stderr: %s", op->stderr_data);
}
return rc;
}
GList *
get_directory_list(const char *root, gboolean files, gboolean executable)
{
return services_os_get_directory_list(root, files, executable);
}
GList *
resources_list_standards(void)
{
GList *standards = NULL;
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
#if SUPPORT_SYSTEMD
{
GList *agents = systemd_unit_listall();
if (agents != NULL) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
g_list_free_full(agents, free);
}
}
#endif
#if SUPPORT_UPSTART
{
GList *agents = upstart_job_listall();
if (agents != NULL) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_UPSTART));
g_list_free_full(agents, free);
}
}
#endif
#if SUPPORT_NAGIOS
{
GList *agents = services__list_nagios_agents();
if (agents != NULL) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_NAGIOS));
g_list_free_full(agents, free);
}
}
#endif
return standards;
}
GList *
resources_list_providers(const char *standard)
{
if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
return resources_os_list_ocf_providers();
}
return NULL;
}
GList *
resources_list_agents(const char *standard, const char *provider)
{
if ((standard == NULL)
|| (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
GList *tmp1;
GList *tmp2;
GList *result = services__list_lsb_agents();
if (standard == NULL) {
tmp1 = result;
tmp2 = resources_os_list_ocf_agents(NULL);
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
}
#if SUPPORT_SYSTEMD
tmp1 = result;
tmp2 = systemd_unit_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
#if SUPPORT_UPSTART
tmp1 = result;
tmp2 = upstart_job_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
return result;
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
return resources_os_list_ocf_agents(provider);
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
return services__list_lsb_agents();
#if SUPPORT_SYSTEMD
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
return systemd_unit_listall();
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
return upstart_job_listall();
#endif
#if SUPPORT_NAGIOS
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
return services__list_nagios_agents();
#endif
}
return NULL;
}
gboolean
resources_agent_exists(const char *standard, const char *provider, const char *agent)
{
GList *standards = NULL;
GList *providers = NULL;
GList *iter = NULL;
gboolean rc = FALSE;
gboolean has_providers = FALSE;
standards = resources_list_standards();
for (iter = standards; iter != NULL; iter = iter->next) {
if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
rc = TRUE;
break;
}
}
if (rc == FALSE) {
goto done;
}
rc = FALSE;
has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
if (has_providers == TRUE && provider != NULL) {
providers = resources_list_providers(standard);
for (iter = providers; iter != NULL; iter = iter->next) {
if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
rc = TRUE;
break;
}
}
} else if (has_providers == FALSE && provider == NULL) {
rc = TRUE;
}
if (rc == FALSE) {
goto done;
}
if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
if (services__lsb_agent_exists(agent)) {
rc = TRUE;
#if SUPPORT_SYSTEMD
} else if (systemd_unit_exists(agent)) {
rc = TRUE;
#endif
#if SUPPORT_UPSTART
} else if (upstart_job_exists(agent)) {
rc = TRUE;
#endif
} else {
rc = FALSE;
}
} else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
rc = services__ocf_agent_exists(provider, agent);
} else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
rc = services__lsb_agent_exists(agent);
#if SUPPORT_SYSTEMD
} else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
rc = systemd_unit_exists(agent);
#endif
#if SUPPORT_UPSTART
} else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
rc = upstart_job_exists(agent);
#endif
#if SUPPORT_NAGIOS
} else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
rc = services__nagios_agent_exists(agent);
#endif
} else {
rc = FALSE;
}
done:
g_list_free(standards);
g_list_free(providers);
return rc;
}
/*!
* \internal
* \brief Set the result of an action
*
* \param[out] action Where to set action result
* \param[in] agent_status Exit status to set
* \param[in] exec_status Execution status to set
* \param[in] reason Human-friendly description of event to set
*/
void
services__set_result(svc_action_t *action, int agent_status,
enum pcmk_exec_status exec_status, const char *reason)
{
if (action == NULL) {
return;
}
action->rc = agent_status;
action->status = exec_status;
if (!pcmk__str_eq(action->opaque->exit_reason, reason,
pcmk__str_none)) {
free(action->opaque->exit_reason);
action->opaque->exit_reason = (reason == NULL)? NULL : strdup(reason);
}
}
/*!
* \internal
* \brief Get the exit reason of an action
*
* \param[in] action Action to check
*
* \return Action's exit reason (or NULL if none)
*/
const char *
services__exit_reason(svc_action_t *action)
{
return action->opaque->exit_reason;
}
diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c
index 2f2d29561c..80d60f35a3 100644
--- a/lib/services/services_linux.c
+++ b/lib/services/services_linux.c
@@ -1,1430 +1,1341 @@
/*
* Copyright 2010-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <grp.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "crm/crm.h"
#include "crm/common/mainloop.h"
#include "crm/services.h"
#include "crm/services_internal.h"
#include "services_private.h"
static void close_pipe(int fildes[]);
/* We have two alternative ways of handling SIGCHLD when synchronously waiting
* for spawned processes to complete. Both rely on polling a file descriptor to
* discover SIGCHLD events.
*
* If sys/signalfd.h is available (e.g. on Linux), we call signalfd() to
* generate the file descriptor. Otherwise, we use the "self-pipe trick"
* (opening a pipe and writing a byte to it when SIGCHLD is received).
*/
#ifdef HAVE_SYS_SIGNALFD_H
// signalfd() implementation
#include <sys/signalfd.h>
// Everything needed to manage SIGCHLD handling
struct sigchld_data_s {
sigset_t mask; // Signals to block now (including SIGCHLD)
sigset_t old_mask; // Previous set of blocked signals
};
// Initialize SIGCHLD data and prepare for use
static bool
sigchld_setup(struct sigchld_data_s *data)
{
sigemptyset(&(data->mask));
sigaddset(&(data->mask), SIGCHLD);
sigemptyset(&(data->old_mask));
// Block SIGCHLD (saving previous set of blocked signals to restore later)
if (sigprocmask(SIG_BLOCK, &(data->mask), &(data->old_mask)) < 0) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=sigprocmask", pcmk_strerror(errno));
return false;
}
return true;
}
// Get a file descriptor suitable for polling for SIGCHLD events
static int
sigchld_open(struct sigchld_data_s *data)
{
int fd;
CRM_CHECK(data != NULL, return -1);
fd = signalfd(-1, &(data->mask), SFD_NONBLOCK);
if (fd < 0) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=signalfd", pcmk_strerror(errno));
}
return fd;
}
// Close a file descriptor returned by sigchld_open()
static void
sigchld_close(int fd)
{
if (fd > 0) {
close(fd);
}
}
// Return true if SIGCHLD was received from polled fd
static bool
sigchld_received(int fd)
{
struct signalfd_siginfo fdsi;
ssize_t s;
if (fd < 0) {
return false;
}
s = read(fd, &fdsi, sizeof(struct signalfd_siginfo));
if (s != sizeof(struct signalfd_siginfo)) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=read", pcmk_strerror(errno));
} else if (fdsi.ssi_signo == SIGCHLD) {
return true;
}
return false;
}
// Do anything needed after done waiting for SIGCHLD
static void
sigchld_cleanup(struct sigchld_data_s *data)
{
// Restore the original set of blocked signals
if ((sigismember(&(data->old_mask), SIGCHLD) == 0)
&& (sigprocmask(SIG_UNBLOCK, &(data->mask), NULL) < 0)) {
crm_warn("Could not clean up after child process completion: %s",
pcmk_strerror(errno));
}
}
#else // HAVE_SYS_SIGNALFD_H not defined
// Self-pipe implementation (see above for function descriptions)
struct sigchld_data_s {
int pipe_fd[2]; // Pipe file descriptors
struct sigaction sa; // Signal handling info (with SIGCHLD)
struct sigaction old_sa; // Previous signal handling info
};
// We need a global to use in the signal handler
volatile struct sigchld_data_s *last_sigchld_data = NULL;
static void
sigchld_handler()
{
// We received a SIGCHLD, so trigger pipe polling
if ((last_sigchld_data != NULL)
&& (last_sigchld_data->pipe_fd[1] >= 0)
&& (write(last_sigchld_data->pipe_fd[1], "", 1) == -1)) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=write", pcmk_strerror(errno));
}
}
static bool
sigchld_setup(struct sigchld_data_s *data)
{
int rc;
data->pipe_fd[0] = data->pipe_fd[1] = -1;
if (pipe(data->pipe_fd) == -1) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=pipe", pcmk_strerror(errno));
return false;
}
rc = pcmk__set_nonblocking(data->pipe_fd[0]);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set pipe input non-blocking: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
rc = pcmk__set_nonblocking(data->pipe_fd[1]);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set pipe output non-blocking: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
// Set SIGCHLD handler
data->sa.sa_handler = sigchld_handler;
data->sa.sa_flags = 0;
sigemptyset(&(data->sa.sa_mask));
if (sigaction(SIGCHLD, &(data->sa), &(data->old_sa)) < 0) {
crm_err("Wait for child process completion failed: %s "
CRM_XS " source=sigaction", pcmk_strerror(errno));
}
// Remember data for use in signal handler
last_sigchld_data = data;
return true;
}
static int
sigchld_open(struct sigchld_data_s *data)
{
CRM_CHECK(data != NULL, return -1);
return data->pipe_fd[0];
}
static void
sigchld_close(int fd)
{
// Pipe will be closed in sigchld_cleanup()
return;
}
static bool
sigchld_received(int fd)
{
char ch;
if (fd < 0) {
return false;
}
// Clear out the self-pipe
while (read(fd, &ch, 1) == 1) /*omit*/;
return true;
}
static void
sigchld_cleanup(struct sigchld_data_s *data)
{
// Restore the previous SIGCHLD handler
if (sigaction(SIGCHLD, &(data->old_sa), NULL) < 0) {
crm_warn("Could not clean up after child process completion: %s",
pcmk_strerror(errno));
}
close_pipe(data->pipe_fd);
}
#endif
/*!
* \internal
* \brief Close the two file descriptors of a pipe
*
* \param[in] fildes Array of file descriptors opened by pipe()
*/
static void
close_pipe(int fildes[])
{
if (fildes[0] >= 0) {
close(fildes[0]);
fildes[0] = -1;
}
if (fildes[1] >= 0) {
close(fildes[1]);
fildes[1] = -1;
}
}
static gboolean
svc_read_output(int fd, svc_action_t * op, bool is_stderr)
{
char *data = NULL;
int rc = 0, len = 0;
char buf[500];
static const size_t buf_read_len = sizeof(buf) - 1;
if (fd < 0) {
crm_trace("No fd for %s", op->id);
return FALSE;
}
if (is_stderr && op->stderr_data) {
len = strlen(op->stderr_data);
data = op->stderr_data;
crm_trace("Reading %s stderr into offset %d", op->id, len);
} else if (is_stderr == FALSE && op->stdout_data) {
len = strlen(op->stdout_data);
data = op->stdout_data;
crm_trace("Reading %s stdout into offset %d", op->id, len);
} else {
crm_trace("Reading %s %s into offset %d", op->id, is_stderr?"stderr":"stdout", len);
}
do {
rc = read(fd, buf, buf_read_len);
if (rc > 0) {
buf[rc] = 0;
crm_trace("Got %d chars: %.80s", rc, buf);
data = pcmk__realloc(data, len + rc + 1);
len += sprintf(data + len, "%s", buf);
} else if (errno != EINTR) {
/* error or EOF
* Cleanup happens in pipe_done()
*/
rc = FALSE;
break;
}
} while (rc == buf_read_len || rc < 0);
if (is_stderr) {
op->stderr_data = data;
} else {
op->stdout_data = data;
}
return rc;
}
static int
dispatch_stdout(gpointer userdata)
{
svc_action_t *op = (svc_action_t *) userdata;
return svc_read_output(op->opaque->stdout_fd, op, FALSE);
}
static int
dispatch_stderr(gpointer userdata)
{
svc_action_t *op = (svc_action_t *) userdata;
return svc_read_output(op->opaque->stderr_fd, op, TRUE);
}
static void
pipe_out_done(gpointer user_data)
{
svc_action_t *op = (svc_action_t *) user_data;
crm_trace("%p", op);
op->opaque->stdout_gsource = NULL;
if (op->opaque->stdout_fd > STDOUT_FILENO) {
close(op->opaque->stdout_fd);
}
op->opaque->stdout_fd = -1;
}
static void
pipe_err_done(gpointer user_data)
{
svc_action_t *op = (svc_action_t *) user_data;
op->opaque->stderr_gsource = NULL;
if (op->opaque->stderr_fd > STDERR_FILENO) {
close(op->opaque->stderr_fd);
}
op->opaque->stderr_fd = -1;
}
static struct mainloop_fd_callbacks stdout_callbacks = {
.dispatch = dispatch_stdout,
.destroy = pipe_out_done,
};
static struct mainloop_fd_callbacks stderr_callbacks = {
.dispatch = dispatch_stderr,
.destroy = pipe_err_done,
};
static void
set_ocf_env(const char *key, const char *value, gpointer user_data)
{
if (setenv(key, value, 1) != 0) {
crm_perror(LOG_ERR, "setenv failed for key:%s and value:%s", key, value);
}
}
static void
set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data)
{
char buffer[500];
snprintf(buffer, sizeof(buffer), strcmp(key, "OCF_CHECK_LEVEL") != 0 ? "OCF_RESKEY_%s" : "%s", (char *)key);
set_ocf_env(buffer, value, user_data);
}
static void
set_alert_env(gpointer key, gpointer value, gpointer user_data)
{
int rc;
if (value != NULL) {
rc = setenv(key, value, 1);
} else {
rc = unsetenv(key);
}
if (rc < 0) {
crm_perror(LOG_ERR, "setenv %s=%s",
(char*)key, (value? (char*)value : ""));
} else {
crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : ""));
}
}
/*!
* \internal
* \brief Add environment variables suitable for an action
*
* \param[in] op Action to use
*/
static void
add_action_env_vars(const svc_action_t *op)
{
void (*env_setter)(gpointer, gpointer, gpointer) = NULL;
if (op->agent == NULL) {
env_setter = set_alert_env; /* we deal with alert handler */
} else if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
env_setter = set_ocf_env_with_prefix;
}
if (env_setter != NULL && op->params != NULL) {
g_hash_table_foreach(op->params, env_setter, NULL);
}
if (env_setter == NULL || env_setter == set_alert_env) {
return;
}
set_ocf_env("OCF_RA_VERSION_MAJOR", PCMK_OCF_MAJOR_VERSION, NULL);
set_ocf_env("OCF_RA_VERSION_MINOR", PCMK_OCF_MINOR_VERSION, NULL);
set_ocf_env("OCF_ROOT", OCF_ROOT_DIR, NULL);
set_ocf_env("OCF_EXIT_REASON_PREFIX", PCMK_OCF_REASON_PREFIX, NULL);
if (op->rsc) {
set_ocf_env("OCF_RESOURCE_INSTANCE", op->rsc, NULL);
}
if (op->agent != NULL) {
set_ocf_env("OCF_RESOURCE_TYPE", op->agent, NULL);
}
/* Notes: this is not added to specification yet. Sept 10,2004 */
if (op->provider != NULL) {
set_ocf_env("OCF_RESOURCE_PROVIDER", op->provider, NULL);
}
}
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)
{
svc_action_t *op = data;
crm_debug("Scheduling another invocation of %s", op->id);
/* Clean out the old result */
free(op->stdout_data);
op->stdout_data = NULL;
free(op->stderr_data);
op->stderr_data = NULL;
op->opaque->repeat_timer = 0;
services_action_async(op, NULL);
return FALSE;
}
/*!
* \internal
* \brief Finalize handling of an asynchronous operation
*
* Given a completed asynchronous operation, cancel or reschedule it as
* appropriate if recurring, call its callback if registered, stop tracking it,
* and clean it up.
*
* \param[in,out] op Operation to finalize
*
* \return Standard Pacemaker return code
* \retval EINVAL Caller supplied NULL or invalid \p op
* \retval EBUSY Uncanceled recurring action has only been cleaned up
* \retval pcmk_rc_ok Action has been freed
*
* \note If the return value is not pcmk_rc_ok, the caller is responsible for
* freeing the action.
*/
int
services__finalize_async_op(svc_action_t *op)
{
CRM_CHECK((op != NULL) && !(op->synchronous), return EINVAL);
if (op->interval_ms != 0) {
// Recurring operations must be either cancelled or rescheduled
if (op->cancel) {
services__set_result(op, op->rc, PCMK_EXEC_CANCELLED, NULL);
cancel_recurring_action(op);
} else {
op->opaque->repeat_timer = g_timeout_add(op->interval_ms,
recurring_action_timer,
(void *) op);
}
}
if (op->opaque->callback != NULL) {
op->opaque->callback(op);
}
// Stop tracking the operation (as in-flight or blocked)
op->pid = 0;
services_untrack_op(op);
if ((op->interval_ms != 0) && !(op->cancel)) {
// Do not free recurring actions (they will get freed when cancelled)
services_action_cleanup(op);
return EBUSY;
}
services_action_free(op);
return pcmk_rc_ok;
}
static void
close_op_input(svc_action_t *op)
{
if (op->opaque->stdin_fd >= 0) {
close(op->opaque->stdin_fd);
}
}
static void
finish_op_output(svc_action_t *op, bool is_stderr)
{
mainloop_io_t **source;
int fd;
if (is_stderr) {
source = &(op->opaque->stderr_gsource);
fd = op->opaque->stderr_fd;
} else {
source = &(op->opaque->stdout_gsource);
fd = op->opaque->stdout_fd;
}
if (op->synchronous || *source) {
crm_trace("Finish reading %s[%d] %s",
op->id, op->pid, (is_stderr? "stdout" : "stderr"));
svc_read_output(fd, op, is_stderr);
if (op->synchronous) {
close(fd);
} else {
mainloop_del_fd(*source);
*source = NULL;
}
}
}
// Log an operation's stdout and stderr
static void
log_op_output(svc_action_t *op)
{
char *prefix = crm_strdup_printf("%s[%d] error output", op->id, op->pid);
crm_log_output(LOG_NOTICE, prefix, op->stderr_data);
strcpy(prefix + strlen(prefix) - strlen("error output"), "output");
crm_log_output(LOG_DEBUG, prefix, op->stdout_data);
free(prefix);
}
// Truncate exit reasons at this many characters
#define EXIT_REASON_MAX_LEN 128
static void
parse_exit_reason_from_stderr(svc_action_t *op)
{
const char *reason_start = NULL;
const char *reason_end = NULL;
const int prefix_len = strlen(PCMK_OCF_REASON_PREFIX);
if ((op->stderr_data == NULL) ||
// Only OCF agents have exit reasons in stderr
!pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none)) {
return;
}
// Find the last occurrence of the magic string indicating an exit reason
for (const char *cur = strstr(op->stderr_data, PCMK_OCF_REASON_PREFIX);
cur != NULL; cur = strstr(cur, PCMK_OCF_REASON_PREFIX)) {
cur += prefix_len; // Skip over magic string
reason_start = cur;
}
if ((reason_start == NULL) || (reason_start[0] == '\n')
|| (reason_start[0] == '\0')) {
return; // No or empty exit reason
}
// Exit reason goes to end of line (or end of output)
reason_end = strchr(reason_start, '\n');
if (reason_end == NULL) {
reason_end = reason_start + strlen(reason_start);
}
// Limit size of exit reason to something reasonable
if (reason_end > (reason_start + EXIT_REASON_MAX_LEN)) {
reason_end = reason_start + EXIT_REASON_MAX_LEN;
}
free(op->opaque->exit_reason);
op->opaque->exit_reason = strndup(reason_start, reason_end - reason_start);
}
/*!
* \internal
* \brief Process the completion of an asynchronous child process
*
* \param[in] p Child process that completed
* \param[in] pid Process ID of child
* \param[in] core (unused)
* \param[in] signo Signal that interrupted child, if any
* \param[in] exitcode Exit status of child process
*/
static void
async_action_complete(mainloop_child_t *p, pid_t pid, int core, int signo,
int exitcode)
{
svc_action_t *op = mainloop_child_userdata(p);
mainloop_clear_child_userdata(p);
CRM_CHECK(op->pid == pid,
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR, "Bug in mainloop handling");
return);
/* Depending on the priority the mainloop gives the stdout and stderr
* file descriptors, this function could be called before everything has
* been read from them, so force a final read now.
*/
finish_op_output(op, true);
finish_op_output(op, false);
close_op_input(op);
if (signo == 0) {
crm_debug("%s[%d] exited with status %d", op->id, op->pid, exitcode);
services__set_result(op, exitcode, PCMK_EXEC_DONE, NULL);
log_op_output(op);
parse_exit_reason_from_stderr(op);
} else if (mainloop_child_timeout(p)) {
crm_warn("%s[%d] timed out after %dms", op->id, op->pid, op->timeout);
services__set_result(op, services__generic_error(op), PCMK_EXEC_TIMEOUT,
"Process did not exit within specified timeout");
} else if (op->cancel) {
/* If an in-flight recurring operation was killed because it was
* cancelled, don't treat that as a failure.
*/
crm_info("%s[%d] terminated with signal: %s " CRM_XS " (%d)",
op->id, op->pid, strsignal(signo), signo);
services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_CANCELLED, NULL);
} else {
crm_warn("%s[%d] terminated with signal: %s " CRM_XS " (%d)",
op->id, op->pid, strsignal(signo), signo);
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
"Process interrupted by signal");
}
services__finalize_async_op(op);
}
/*!
* \internal
* \brief Return agent standard's exit status for "generic error"
*
* When returning an internal error for an action, a value that is appropriate
* to the action's agent standard must be used. This function returns a value
* appropriate for errors in general.
*
* \param[in] op Action that error is for
*
* \return Exit status appropriate to agent standard
* \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
*/
int
services__generic_error(svc_action_t *op)
{
if ((op == NULL) || (op->standard == NULL)) {
return PCMK_OCF_UNKNOWN_ERROR;
}
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
&& pcmk__str_eq(op->action, "status", pcmk__str_casei)) {
return PCMK_LSB_STATUS_UNKNOWN;
}
#if SUPPORT_NAGIOS
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
return NAGIOS_STATE_UNKNOWN;
}
#endif
return PCMK_OCF_UNKNOWN_ERROR;
}
/*!
* \internal
* \brief Return agent standard's exit status for "not installed"
*
* When returning an internal error for an action, a value that is appropriate
* to the action's agent standard must be used. This function returns a value
* appropriate for "not installed" errors.
*
* \param[in] op Action that error is for
*
* \return Exit status appropriate to agent standard
* \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
*/
int
services__not_installed_error(svc_action_t *op)
{
if ((op == NULL) || (op->standard == NULL)) {
return PCMK_OCF_UNKNOWN_ERROR;
}
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
&& pcmk__str_eq(op->action, "status", pcmk__str_casei)) {
return PCMK_LSB_STATUS_NOT_INSTALLED;
}
#if SUPPORT_NAGIOS
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
return NAGIOS_NOT_INSTALLED;
}
#endif
return PCMK_OCF_NOT_INSTALLED;
}
/*!
* \internal
* \brief Return agent standard's exit status for "insufficient privileges"
*
* When returning an internal error for an action, a value that is appropriate
* to the action's agent standard must be used. This function returns a value
* appropriate for "insufficient privileges" errors.
*
* \param[in] op Action that error is for
*
* \return Exit status appropriate to agent standard
* \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
*/
int
services__authorization_error(svc_action_t *op)
{
if ((op == NULL) || (op->standard == NULL)) {
return PCMK_OCF_UNKNOWN_ERROR;
}
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)
&& pcmk__str_eq(op->action, "status", pcmk__str_casei)) {
return PCMK_LSB_STATUS_INSUFFICIENT_PRIV;
}
#if SUPPORT_NAGIOS
if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
return NAGIOS_INSUFFICIENT_PRIV;
}
#endif
return PCMK_OCF_INSUFFICIENT_PRIV;
}
/*!
* \internal
* \brief Set operation rc and status per errno from stat(), fork() or execvp()
*
* \param[in,out] op Operation to set rc and status for
* \param[in] error Value of errno after system call
*
* \return void
*/
void
services__handle_exec_error(svc_action_t * op, int error)
{
switch (error) { /* see execve(2), stat(2) and fork(2) */
case ENOENT: /* No such file or directory */
case EISDIR: /* Is a directory */
case ENOTDIR: /* Path component is not a directory */
case EINVAL: /* Invalid executable format */
case ENOEXEC: /* Invalid executable format */
services__set_result(op, services__not_installed_error(op),
PCMK_EXEC_NOT_INSTALLED, pcmk_rc_str(error));
break;
case EACCES: /* permission denied (various errors) */
case EPERM: /* permission denied (various errors) */
services__set_result(op, services__authorization_error(op),
PCMK_EXEC_ERROR, pcmk_rc_str(error));
break;
default:
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR, pcmk_rc_str(error));
}
}
static void
action_launch_child(svc_action_t *op)
{
/* SIGPIPE is ignored (which is different from signal blocking) by the gnutls library.
* Depending on the libqb version in use, libqb may set SIGPIPE to be ignored as well.
* We do not want this to be inherited by the child process. By resetting this the signal
* to the default behavior, we avoid some potential odd problems that occur during OCF
* scripts when SIGPIPE is ignored by the environment. */
signal(SIGPIPE, SIG_DFL);
#if defined(HAVE_SCHED_SETSCHEDULER)
if (sched_getscheduler(0) != SCHED_OTHER) {
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = 0;
if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) {
crm_perror(LOG_ERR, "Could not reset scheduling policy to SCHED_OTHER for %s", op->id);
}
}
#endif
if (setpriority(PRIO_PROCESS, 0, 0) == -1) {
crm_perror(LOG_ERR, "Could not reset process priority to 0 for %s", op->id);
}
/* Man: The call setpgrp() is equivalent to setpgid(0,0)
* _and_ compiles on BSD variants too
* need to investigate if it works the same too.
*/
setpgid(0, 0);
pcmk__close_fds_in_child(false);
#if SUPPORT_CIBSECRETS
if (pcmk__substitute_secrets(op->rsc, op->params) != pcmk_rc_ok) {
/* replacing secrets failed! */
if (pcmk__str_eq(op->action, "stop", pcmk__str_casei)) {
/* don't fail on stop! */
crm_info("proceeding with the stop operation for %s", op->rsc);
} else {
crm_err("failed to get secrets for %s, "
"considering resource not configured", op->rsc);
_exit(PCMK_OCF_NOT_CONFIGURED);
}
}
#endif
add_action_env_vars(op);
/* Become the desired user */
if (op->opaque->uid && (geteuid() == 0)) {
// If requested, set effective group
if (op->opaque->gid && (setgid(op->opaque->gid) < 0)) {
crm_perror(LOG_ERR, "Could not set child group to %d", op->opaque->gid);
_exit(PCMK_OCF_NOT_CONFIGURED);
}
// Erase supplementary group list
// (We could do initgroups() if we kept a copy of the username)
if (setgroups(0, NULL) < 0) {
crm_perror(LOG_ERR, "Could not set child groups");
_exit(PCMK_OCF_NOT_CONFIGURED);
}
// Set effective user
if (setuid(op->opaque->uid) < 0) {
crm_perror(LOG_ERR, "setting user to %d", op->opaque->uid);
_exit(PCMK_OCF_NOT_CONFIGURED);
}
}
/* execute the RA */
execvp(op->opaque->exec, op->opaque->args);
/* Most cases should have been already handled by stat() */
services__handle_exec_error(op, errno);
_exit(op->rc);
}
/*!
* \internal
* \brief Wait for synchronous action to complete, and set its result
*
* \param[in] op Action to wait for
* \param[in] data Child signal data
*/
static void
wait_for_sync_result(svc_action_t *op, struct sigchld_data_s *data)
{
int status = 0;
int timeout = op->timeout;
time_t start = time(NULL);
struct pollfd fds[3];
int wait_rc = 0;
const char *wait_reason = NULL;
fds[0].fd = op->opaque->stdout_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = op->opaque->stderr_fd;
fds[1].events = POLLIN;
fds[1].revents = 0;
fds[2].fd = sigchld_open(data);
fds[2].events = POLLIN;
fds[2].revents = 0;
crm_trace("Waiting for %s[%d]", op->id, op->pid);
do {
int poll_rc = poll(fds, 3, timeout);
wait_reason = NULL;
if (poll_rc > 0) {
if (fds[0].revents & POLLIN) {
svc_read_output(op->opaque->stdout_fd, op, FALSE);
}
if (fds[1].revents & POLLIN) {
svc_read_output(op->opaque->stderr_fd, op, TRUE);
}
if ((fds[2].revents & POLLIN) && sigchld_received(fds[2].fd)) {
wait_rc = waitpid(op->pid, &status, WNOHANG);
if ((wait_rc > 0) || ((wait_rc < 0) && (errno == ECHILD))) {
// Child process exited or doesn't exist
break;
} else if (wait_rc < 0) {
wait_reason = pcmk_rc_str(errno);
crm_warn("Wait for completion of %s[%d] failed: %s "
CRM_XS " source=waitpid",
op->id, op->pid, wait_reason);
wait_rc = 0; // Act as if process is still running
}
}
} else if (poll_rc == 0) {
// Poll timed out with no descriptors ready
timeout = 0;
break;
} else if ((poll_rc < 0) && (errno != EINTR)) {
wait_reason = pcmk_rc_str(errno);
crm_err("Wait for completion of %s[%d] failed: %s "
CRM_XS " source=poll", op->id, op->pid, wait_reason);
break;
}
timeout = op->timeout - (time(NULL) - start) * 1000;
} while ((op->timeout < 0 || timeout > 0));
crm_trace("Stopped waiting for %s[%d]", op->id, op->pid);
finish_op_output(op, true);
finish_op_output(op, false);
close_op_input(op);
sigchld_close(fds[2].fd);
if (wait_rc <= 0) {
if ((op->timeout > 0) && (timeout <= 0)) {
services__set_result(op, services__generic_error(op),
PCMK_EXEC_TIMEOUT,
"Process did not exit within specified timeout");
crm_warn("%s[%d] timed out after %dms",
op->id, op->pid, op->timeout);
} else {
services__set_result(op, services__generic_error(op),
PCMK_EXEC_ERROR, wait_reason);
}
/* If only child hasn't been successfully waited for, yet.
This is to limit killing wrong target a bit more. */
if ((wait_rc == 0) && (waitpid(op->pid, &status, WNOHANG) == 0)) {
if (kill(op->pid, SIGKILL)) {
crm_warn("Could not kill rogue child %s[%d]: %s",
op->id, op->pid, pcmk_strerror(errno));
}
/* Safe to skip WNOHANG here as we sent non-ignorable signal. */
while ((waitpid(op->pid, &status, 0) == (pid_t) -1)
&& (errno == EINTR)) {
/* keep waiting */;
}
}
} else if (WIFEXITED(status)) {
services__set_result(op, WEXITSTATUS(status), PCMK_EXEC_DONE, NULL);
parse_exit_reason_from_stderr(op);
crm_info("%s[%d] exited with status %d", op->id, op->pid, op->rc);
} else if (WIFSIGNALED(status)) {
int signo = WTERMSIG(status);
services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR,
"Process interrupted by signal");
crm_err("%s[%d] terminated with signal: %s " CRM_XS " (%d)",
op->id, op->pid, strsignal(signo), signo);
#ifdef WCOREDUMP
if (WCOREDUMP(status)) {
crm_warn("%s[%d] dumped core", op->id, op->pid);
}
#endif
} else {
// Shouldn't be possible to get here
services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR,
"Unable to wait for child to complete");
}
}
/*!
* \internal
* \brief Execute an action whose standard uses executable files
*
* \param[in] op Action to execute
*
* \return Standard Pacemaker return value
* \retval EBUSY Recurring operation could not be initiated
* \retval pcmk_rc_error Synchronous action failed
* \retval pcmk_rc_ok Synchronous action succeeded, or asynchronous action
* should not be freed (because it already was or is
* pending)
*
* \note If the return value for an asynchronous action is not pcmk_rc_ok, the
* caller is responsible for freeing the action.
*/
int
services__execute_file(svc_action_t *op)
{
int stdout_fd[2];
int stderr_fd[2];
int stdin_fd[2] = {-1, -1};
int rc;
struct stat st;
struct sigchld_data_s data;
// Catch common failure conditions early
if (stat(op->opaque->exec, &st) != 0) {
rc = errno;
crm_warn("Cannot execute '%s': %s " CRM_XS " stat rc=%d",
op->opaque->exec, pcmk_strerror(rc), rc);
services__handle_exec_error(op, rc);
goto done;
}
if (pipe(stdout_fd) < 0) {
rc = errno;
crm_err("Cannot execute '%s': %s " CRM_XS " pipe(stdout) rc=%d",
op->opaque->exec, pcmk_strerror(rc), rc);
services__handle_exec_error(op, rc);
goto done;
}
if (pipe(stderr_fd) < 0) {
rc = errno;
close_pipe(stdout_fd);
crm_err("Cannot execute '%s': %s " CRM_XS " pipe(stderr) rc=%d",
op->opaque->exec, pcmk_strerror(rc), rc);
services__handle_exec_error(op, rc);
goto done;
}
if (pcmk_is_set(pcmk_get_ra_caps(op->standard), pcmk_ra_cap_stdin)) {
if (pipe(stdin_fd) < 0) {
rc = errno;
close_pipe(stdout_fd);
close_pipe(stderr_fd);
crm_err("Cannot execute '%s': %s " CRM_XS " pipe(stdin) rc=%d",
op->opaque->exec, pcmk_strerror(rc), rc);
services__handle_exec_error(op, rc);
goto done;
}
}
if (op->synchronous && !sigchld_setup(&data)) {
close_pipe(stdin_fd);
close_pipe(stdout_fd);
close_pipe(stderr_fd);
sigchld_cleanup(&data);
services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR,
"Could not manage signals for child process");
goto done;
}
op->pid = fork();
switch (op->pid) {
case -1:
rc = errno;
close_pipe(stdin_fd);
close_pipe(stdout_fd);
close_pipe(stderr_fd);
crm_err("Cannot execute '%s': %s " CRM_XS " fork rc=%d",
op->opaque->exec, pcmk_strerror(rc), rc);
services__handle_exec_error(op, rc);
if (op->synchronous) {
sigchld_cleanup(&data);
}
goto done;
break;
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_warn("Can't redirect output from '%s': %s "
CRM_XS " errno=%d",
op->opaque->exec, pcmk_strerror(errno), errno);
}
close(stdout_fd[1]);
}
if (STDERR_FILENO != stderr_fd[1]) {
if (dup2(stderr_fd[1], STDERR_FILENO) != STDERR_FILENO) {
crm_warn("Can't redirect error output from '%s': %s "
CRM_XS " errno=%d",
op->opaque->exec, pcmk_strerror(errno), errno);
}
close(stderr_fd[1]);
}
if ((stdin_fd[0] >= 0) &&
(STDIN_FILENO != stdin_fd[0])) {
if (dup2(stdin_fd[0], STDIN_FILENO) != STDIN_FILENO) {
crm_warn("Can't redirect input to '%s': %s "
CRM_XS " errno=%d",
op->opaque->exec, pcmk_strerror(errno), errno);
}
close(stdin_fd[0]);
}
if (op->synchronous) {
sigchld_cleanup(&data);
}
action_launch_child(op);
CRM_ASSERT(0); /* action_launch_child is effectively noreturn */
}
/* 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 = pcmk__set_nonblocking(op->opaque->stdout_fd);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set '%s' output non-blocking: %s "
CRM_XS " rc=%d",
op->opaque->exec, pcmk_rc_str(rc), rc);
}
op->opaque->stderr_fd = stderr_fd[0];
rc = pcmk__set_nonblocking(op->opaque->stderr_fd);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set '%s' error output non-blocking: %s "
CRM_XS " rc=%d",
op->opaque->exec, pcmk_rc_str(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 = pcmk__set_nonblocking(op->opaque->stdin_fd);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set '%s' input non-blocking: %s "
CRM_XS " fd=%d,rc=%d", op->opaque->exec,
pcmk_rc_str(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;
}
// after fds are setup properly and before we plug anything into mainloop
if (op->opaque->fork_callback) {
op->opaque->fork_callback(op);
}
if (op->synchronous) {
wait_for_sync_result(op, &data);
sigchld_cleanup(&data);
goto done;
}
crm_trace("Waiting async for '%s'[%d]", op->opaque->exec, op->pid);
mainloop_child_add_with_flags(op->pid, op->timeout, op->id, op,
pcmk_is_set(op->flags, SVC_ACTION_LEAVE_GROUP)? mainloop_leave_pid_group : 0,
async_action_complete);
op->opaque->stdout_gsource = mainloop_add_fd(op->id,
G_PRIORITY_LOW,
op->opaque->stdout_fd, op,
&stdout_callbacks);
op->opaque->stderr_gsource = mainloop_add_fd(op->id,
G_PRIORITY_LOW,
op->opaque->stderr_fd, op,
&stderr_callbacks);
services_add_inflight_op(op);
return pcmk_rc_ok;
done:
if (op->synchronous) {
return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
} else {
return services__finalize_async_op(op);
}
}
-static GList *
+GList *
services_os_get_single_directory_list(const char *root, gboolean files, gboolean executable)
{
GList *list = NULL;
struct dirent **namelist;
int entries = 0, lpc = 0;
char buffer[PATH_MAX];
entries = scandir(root, &namelist, NULL, alphasort);
if (entries <= 0) {
return list;
}
for (lpc = 0; lpc < entries; lpc++) {
struct stat sb;
if ('.' == namelist[lpc]->d_name[0]) {
free(namelist[lpc]);
continue;
}
snprintf(buffer, sizeof(buffer), "%s/%s", root, namelist[lpc]->d_name);
if (stat(buffer, &sb)) {
continue;
}
if (S_ISDIR(sb.st_mode)) {
if (files) {
free(namelist[lpc]);
continue;
}
} else if (S_ISREG(sb.st_mode)) {
if (files == FALSE) {
free(namelist[lpc]);
continue;
} else if (executable
&& (sb.st_mode & S_IXUSR) == 0
&& (sb.st_mode & S_IXGRP) == 0 && (sb.st_mode & S_IXOTH) == 0) {
free(namelist[lpc]);
continue;
}
}
list = g_list_append(list, strdup(namelist[lpc]->d_name));
free(namelist[lpc]);
}
free(namelist);
return list;
}
GList *
services_os_get_directory_list(const char *root, gboolean files, gboolean executable)
{
GList *result = NULL;
char *dirs = strdup(root);
char *dir = NULL;
if (pcmk__str_empty(dirs)) {
free(dirs);
return result;
}
for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
GList *tmp = services_os_get_single_directory_list(dir, files, executable);
if (tmp) {
result = g_list_concat(result, tmp);
}
}
free(dirs);
return result;
}
-
-static GList *
-services_os_get_directory_list_provider(const char *root, const char *provider, gboolean files, gboolean executable)
-{
- GList *result = NULL;
- char *dirs = strdup(root);
- char *dir = NULL;
- char buffer[PATH_MAX];
-
- if (pcmk__str_empty(dirs)) {
- free(dirs);
- return result;
- }
-
- for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
- GList *tmp = NULL;
-
- sprintf(buffer, "%s/%s", dir, provider);
- tmp = services_os_get_single_directory_list(buffer, files, executable);
-
- if (tmp) {
- result = g_list_concat(result, tmp);
- }
- }
-
- free(dirs);
-
- return result;
-}
-
-GList *
-resources_os_list_ocf_providers(void)
-{
- return get_directory_list(OCF_RA_PATH, FALSE, TRUE);
-}
-
-GList *
-resources_os_list_ocf_agents(const char *provider)
-{
- GList *gIter = NULL;
- GList *result = NULL;
- GList *providers = NULL;
-
- if (provider) {
- return services_os_get_directory_list_provider(OCF_RA_PATH, provider, TRUE, TRUE);
- }
-
- providers = resources_os_list_ocf_providers();
- for (gIter = providers; gIter != NULL; gIter = gIter->next) {
- GList *tmp1 = result;
- GList *tmp2 = resources_os_list_ocf_agents(gIter->data);
-
- if (tmp2) {
- result = g_list_concat(tmp1, tmp2);
- }
- }
- g_list_free_full(providers, free);
- return result;
-}
-
-gboolean
-services__ocf_agent_exists(const char *provider, const char *agent)
-{
- gboolean rc = FALSE;
- struct stat st;
- char *dirs = strdup(OCF_RA_PATH);
- char *dir = NULL;
- char *buf = NULL;
-
- if (provider == NULL || agent == NULL || pcmk__str_empty(dirs)) {
- free(dirs);
- return rc;
- }
-
- for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
- buf = crm_strdup_printf("%s/%s/%s", dir, provider, agent);
- if (stat(buf, &st) == 0) {
- free(buf);
- rc = TRUE;
- break;
- }
-
- free(buf);
- }
-
- free(dirs);
-
- return rc;
-}
diff --git a/lib/services/services_ocf.c b/lib/services/services_ocf.c
new file mode 100644
index 0000000000..1ec577b249
--- /dev/null
+++ b/lib/services/services_ocf.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <crm/crm.h>
+#include <crm/services.h>
+#include <crm/services_internal.h>
+
+#include "services_private.h"
+#include "services_ocf.h"
+
+GList *
+resources_os_list_ocf_providers(void)
+{
+ return get_directory_list(OCF_RA_PATH, FALSE, TRUE);
+}
+
+static GList *
+services_os_get_directory_list_provider(const char *root, const char *provider,
+ gboolean files, gboolean executable)
+{
+ GList *result = NULL;
+ char *dirs = strdup(root);
+ char *dir = NULL;
+ char buffer[PATH_MAX];
+
+ if (pcmk__str_empty(dirs)) {
+ free(dirs);
+ return result;
+ }
+
+ for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
+ GList *tmp = NULL;
+
+ sprintf(buffer, "%s/%s", dir, provider);
+ tmp = services_os_get_single_directory_list(buffer, files, executable);
+
+ if (tmp) {
+ result = g_list_concat(result, tmp);
+ }
+ }
+
+ free(dirs);
+
+ return result;
+}
+
+GList *
+resources_os_list_ocf_agents(const char *provider)
+{
+ GList *gIter = NULL;
+ GList *result = NULL;
+ GList *providers = NULL;
+
+ if (provider) {
+ return services_os_get_directory_list_provider(OCF_RA_PATH, provider,
+ TRUE, TRUE);
+ }
+
+ providers = resources_os_list_ocf_providers();
+ for (gIter = providers; gIter != NULL; gIter = gIter->next) {
+ GList *tmp1 = result;
+ GList *tmp2 = resources_os_list_ocf_agents(gIter->data);
+
+ if (tmp2) {
+ result = g_list_concat(tmp1, tmp2);
+ }
+ }
+ g_list_free_full(providers, free);
+ return result;
+}
+
+gboolean
+services__ocf_agent_exists(const char *provider, const char *agent)
+{
+ gboolean rc = FALSE;
+ struct stat st;
+ char *dirs = strdup(OCF_RA_PATH);
+ char *dir = NULL;
+ char *buf = NULL;
+
+ if (provider == NULL || agent == NULL || pcmk__str_empty(dirs)) {
+ free(dirs);
+ return rc;
+ }
+
+ for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
+ buf = crm_strdup_printf("%s/%s/%s", dir, provider, agent);
+ if (stat(buf, &st) == 0) {
+ free(buf);
+ rc = TRUE;
+ break;
+ }
+
+ free(buf);
+ }
+
+ free(dirs);
+
+ return rc;
+}
diff --git a/lib/services/services_ocf.h b/lib/services/services_ocf.h
new file mode 100644
index 0000000000..32da883370
--- /dev/null
+++ b/lib/services/services_ocf.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010-2011 Red Hat, Inc.
+ * Later changes copyright 2012-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__SERVICES_OCF__H
+#define PCMK__SERVICES_OCF__H
+
+#include <glib.h>
+
+G_GNUC_INTERNAL
+GList *resources_os_list_ocf_providers(void);
+
+G_GNUC_INTERNAL
+GList *resources_os_list_ocf_agents(const char *provider);
+
+G_GNUC_INTERNAL
+gboolean services__ocf_agent_exists(const char *provider, const char *agent);
+
+#endif // PCMK__SERVICES_OCF__H
diff --git a/lib/services/services_private.h b/lib/services/services_private.h
index e3e8bb7198..bc7a2ba8cc 100644
--- a/lib/services/services_private.h
+++ b/lib/services/services_private.h
@@ -1,97 +1,92 @@
/*
* Copyright 2010-2011 Red Hat, Inc.
* Later changes copyright 2012-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef SERVICES_PRIVATE__H
# define SERVICES_PRIVATE__H
# include <glib.h>
# include "crm/services.h"
#if SUPPORT_DBUS
# include <dbus/dbus.h>
#endif
#define MAX_ARGC 255
struct svc_action_private_s {
char *exec;
char *exit_reason;
char *args[MAX_ARGC];
uid_t uid;
gid_t gid;
guint repeat_timer;
void (*callback) (svc_action_t * op);
void (*fork_callback) (svc_action_t * op);
int stderr_fd;
mainloop_io_t *stderr_gsource;
int stdout_fd;
mainloop_io_t *stdout_gsource;
int stdin_fd;
#if SUPPORT_DBUS
DBusPendingCall* pending;
unsigned timerid;
#endif
};
G_GNUC_INTERNAL
-GList *services_os_get_directory_list(const char *root, gboolean files, gboolean executable);
-
-G_GNUC_INTERNAL
-int services__execute_file(svc_action_t *op);
+GList *services_os_get_single_directory_list(const char *root, gboolean files,
+ gboolean executable);
G_GNUC_INTERNAL
-GList *resources_os_list_ocf_providers(void);
-
-G_GNUC_INTERNAL
-GList *resources_os_list_ocf_agents(const char *provider);
+GList *services_os_get_directory_list(const char *root, gboolean files, gboolean executable);
G_GNUC_INTERNAL
-gboolean services__ocf_agent_exists(const char *provider, const char *agent);
+int services__execute_file(svc_action_t *op);
G_GNUC_INTERNAL
gboolean cancel_recurring_action(svc_action_t * op);
G_GNUC_INTERNAL
gboolean recurring_action_timer(gpointer data);
G_GNUC_INTERNAL
int services__finalize_async_op(svc_action_t *op);
G_GNUC_INTERNAL
int services__generic_error(svc_action_t *op);
G_GNUC_INTERNAL
int services__not_installed_error(svc_action_t *op);
G_GNUC_INTERNAL
int services__authorization_error(svc_action_t *op);
G_GNUC_INTERNAL
void services__handle_exec_error(svc_action_t * op, int error);
G_GNUC_INTERNAL
void services_add_inflight_op(svc_action_t *op);
G_GNUC_INTERNAL
void services_untrack_op(svc_action_t *op);
G_GNUC_INTERNAL
gboolean is_op_blocked(const char *rsc);
#if SUPPORT_DBUS
G_GNUC_INTERNAL
void services_set_op_pending(svc_action_t *op, DBusPendingCall *pending);
#endif
#endif /* SERVICES_PRIVATE__H */
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:19 PM (17 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002577
Default Alt Text
(90 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment